summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 15:59:36 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 15:59:36 +0000
commitec52555862913a23417735f9f7f5402f5230da13 (patch)
tree5e43a30d289a3daa69dddfbb060216ff6332f197 /src
parentInitial commit. (diff)
downloadnautilus-upstream.tar.xz
nautilus-upstream.zip
Adding upstream version 3.38.2.upstream/3.38.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/.gitignore13
-rw-r--r--src/animation/egg-animation.c1183
-rw-r--r--src/animation/egg-animation.h74
-rw-r--r--src/animation/egg-frame-source.c130
-rw-r--r--src/animation/egg-frame-source.h29
-rw-r--r--src/animation/ide-box-theatric.c417
-rw-r--r--src/animation/ide-box-theatric.h30
-rw-r--r--src/animation/ide-cairo.c84
-rw-r--r--src/animation/ide-cairo.h65
-rwxr-xr-xsrc/check-nautilus2
-rwxr-xr-xsrc/gtk/gtk-code-generator.sh93
-rw-r--r--src/gtk/nautilusgtkplacesview.c2656
-rw-r--r--src/gtk/nautilusgtkplacesview.ui382
-rw-r--r--src/gtk/nautilusgtkplacesviewprivate.h83
-rw-r--r--src/gtk/nautilusgtkplacesviewrow.c501
-rw-r--r--src/gtk/nautilusgtkplacesviewrow.ui104
-rw-r--r--src/gtk/nautilusgtkplacesviewrowprivate.h61
-rw-r--r--src/meson.build350
-rw-r--r--src/nautilus-application.c1680
-rw-r--r--src/nautilus-application.h89
-rw-r--r--src/nautilus-autorun-software.c282
-rw-r--r--src/nautilus-batch-rename-dialog.c2332
-rw-r--r--src/nautilus-batch-rename-dialog.h232
-rw-r--r--src/nautilus-batch-rename-utilities.c1180
-rw-r--r--src/nautilus-batch-rename-utilities.h70
-rw-r--r--src/nautilus-bookmark-list.c669
-rw-r--r--src/nautilus-bookmark-list.h47
-rw-r--r--src/nautilus-bookmark.c841
-rw-r--r--src/nautilus-bookmark.h57
-rw-r--r--src/nautilus-canvas-container.c6360
-rw-r--r--src/nautilus-canvas-container.h295
-rw-r--r--src/nautilus-canvas-dnd.c1839
-rw-r--r--src/nautilus-canvas-dnd.h56
-rw-r--r--src/nautilus-canvas-item.c2752
-rw-r--r--src/nautilus-canvas-item.h90
-rw-r--r--src/nautilus-canvas-private.h223
-rw-r--r--src/nautilus-canvas-view-container.c377
-rw-r--r--src/nautilus-canvas-view-container.h35
-rw-r--r--src/nautilus-canvas-view.c1647
-rw-r--r--src/nautilus-canvas-view.h45
-rw-r--r--src/nautilus-clipboard.c329
-rw-r--r--src/nautilus-clipboard.h36
-rw-r--r--src/nautilus-column-chooser.c714
-rw-r--r--src/nautilus-column-chooser.h38
-rw-r--r--src/nautilus-column-utilities.c406
-rw-r--r--src/nautilus-column-utilities.h34
-rw-r--r--src/nautilus-compress-dialog-controller.c350
-rw-r--r--src/nautilus-compress-dialog-controller.h33
-rw-r--r--src/nautilus-container-max-width.c301
-rw-r--r--src/nautilus-container-max-width.h19
-rw-r--r--src/nautilus-dbus-manager.c655
-rw-r--r--src/nautilus-dbus-manager.h36
-rw-r--r--src/nautilus-debug.c181
-rw-r--r--src/nautilus-debug.h79
-rw-r--r--src/nautilus-directory-async.c4740
-rw-r--r--src/nautilus-directory-notify.h57
-rw-r--r--src/nautilus-directory-private.h224
-rw-r--r--src/nautilus-directory.c2066
-rw-r--r--src/nautilus-directory.h254
-rw-r--r--src/nautilus-dnd.c978
-rw-r--r--src/nautilus-dnd.h140
-rw-r--r--src/nautilus-enum-types.c.template37
-rw-r--r--src/nautilus-enum-types.h.template25
-rw-r--r--src/nautilus-enums.h83
-rw-r--r--src/nautilus-error-reporting.c443
-rw-r--r--src/nautilus-error-reporting.h53
-rw-r--r--src/nautilus-file-changes-queue.c346
-rw-r--r--src/nautilus-file-changes-queue.h31
-rw-r--r--src/nautilus-file-conflict-dialog.c390
-rw-r--r--src/nautilus-file-conflict-dialog.h58
-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.c8894
-rw-r--r--src/nautilus-file-operations.h165
-rw-r--r--src/nautilus-file-private.h285
-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.c2640
-rw-r--r--src/nautilus-file-undo-operations.h229
-rw-r--r--src/nautilus-file-utilities.c1515
-rw-r--r--src/nautilus-file-utilities.h145
-rw-r--r--src/nautilus-file.c9811
-rw-r--r--src/nautilus-file.h618
-rw-r--r--src/nautilus-files-view-dnd.c416
-rw-r--r--src/nautilus-files-view-dnd.h55
-rw-r--r--src/nautilus-files-view.c9928
-rw-r--r--src/nautilus-files-view.h336
-rw-r--r--src/nautilus-floating-bar.c607
-rw-r--r--src/nautilus-floating-bar.h51
-rw-r--r--src/nautilus-freedesktop-dbus.c327
-rw-r--r--src/nautilus-freedesktop-dbus.h37
-rw-r--r--src/nautilus-global-preferences.c70
-rw-r--r--src/nautilus-global-preferences.h169
-rw-r--r--src/nautilus-icon-info.c625
-rw-r--r--src/nautilus-icon-info.h44
-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-lib-self-check-functions.c35
-rw-r--r--src/nautilus-lib-self-check-functions.h48
-rw-r--r--src/nautilus-list-model.c1866
-rw-r--r--src/nautilus-list-model.h113
-rw-r--r--src/nautilus-list-view-dnd.c305
-rw-r--r--src/nautilus-list-view-dnd.h34
-rw-r--r--src/nautilus-list-view-private.h79
-rw-r--r--src/nautilus-list-view.c4177
-rw-r--r--src/nautilus-list-view.h40
-rw-r--r--src/nautilus-location-entry.c938
-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.c2245
-rw-r--r--src/nautilus-mime-actions.h54
-rw-r--r--src/nautilus-module.c311
-rw-r--r--src/nautilus-module.h38
-rw-r--r--src/nautilus-monitor.c182
-rw-r--r--src/nautilus-monitor.h33
-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-notebook.c572
-rw-r--r--src/nautilus-notebook.h61
-rw-r--r--src/nautilus-operations-ui-manager.c576
-rw-r--r--src/nautilus-operations-ui-manager.h27
-rw-r--r--src/nautilus-other-locations-window-slot.c83
-rw-r--r--src/nautilus-other-locations-window-slot.h33
-rw-r--r--src/nautilus-pathbar.c1699
-rw-r--r--src/nautilus-pathbar.h33
-rw-r--r--src/nautilus-places-view.c430
-rw-r--r--src/nautilus-places-view.h32
-rw-r--r--src/nautilus-preferences-window.c546
-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.c623
-rw-r--r--src/nautilus-program-choosing.h60
-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.c225
-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.c5667
-rw-r--r--src/nautilus-properties-window.h40
-rw-r--r--src/nautilus-query-editor.c751
-rw-r--r--src/nautilus-query-editor.h77
-rw-r--r--src/nautilus-query.c688
-rw-r--r--src/nautilus-query.h88
-rw-r--r--src/nautilus-rename-file-popover-controller.c451
-rw-r--r--src/nautilus-rename-file-popover-controller.h38
-rw-r--r--src/nautilus-search-directory-file.c310
-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.c360
-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.c430
-rw-r--r--src/nautilus-search-engine-recent.h33
-rw-r--r--src/nautilus-search-engine-simple.c593
-rw-r--r--src/nautilus-search-engine-simple.h34
-rw-r--r--src/nautilus-search-engine-tracker.c617
-rw-r--r--src/nautilus-search-engine-tracker.h29
-rw-r--r--src/nautilus-search-engine.c590
-rw-r--r--src/nautilus-search-engine.h48
-rw-r--r--src/nautilus-search-hit.c438
-rw-r--r--src/nautilus-search-hit.h48
-rw-r--r--src/nautilus-search-popover.c1023
-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-selection-canvas-item.c554
-rw-r--r--src/nautilus-selection-canvas-item.h62
-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.c811
-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.c194
-rw-r--r--src/nautilus-special-location-bar.h36
-rw-r--r--src/nautilus-starred-directory.c583
-rw-r--r--src/nautilus-starred-directory.h33
-rw-r--r--src/nautilus-tag-manager.c1016
-rw-r--r--src/nautilus-tag-manager.h62
-rw-r--r--src/nautilus-thumbnails.c560
-rw-r--r--src/nautilus-thumbnails.h35
-rw-r--r--src/nautilus-toolbar-menu-sections.h32
-rw-r--r--src/nautilus-toolbar.c1512
-rw-r--r--src/nautilus-toolbar.h55
-rw-r--r--src/nautilus-tracker-utilities.c145
-rw-r--r--src/nautilus-tracker-utilities.h28
-rw-r--r--src/nautilus-trash-bar.c240
-rw-r--r--src/nautilus-trash-bar.h35
-rw-r--r--src/nautilus-trash-monitor.c262
-rw-r--r--src/nautilus-trash-monitor.h35
-rw-r--r--src/nautilus-tree-view-drag-dest.c1370
-rw-r--r--src/nautilus-tree-view-drag-dest.h96
-rw-r--r--src/nautilus-types.h48
-rw-r--r--src/nautilus-ui-utilities.c381
-rw-r--r--src/nautilus-ui-utilities.h51
-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.c709
-rw-r--r--src/nautilus-vfs-file.h49
-rw-r--r--src/nautilus-video-mime-types.h65
-rw-r--r--src/nautilus-view-icon-controller.c1099
-rw-r--r--src/nautilus-view-icon-controller.h20
-rw-r--r--src/nautilus-view-icon-item-ui.c287
-rw-r--r--src/nautilus-view-icon-item-ui.h18
-rw-r--r--src/nautilus-view-icon-ui.c261
-rw-r--r--src/nautilus-view-icon-ui.h38
-rw-r--r--src/nautilus-view-item-model.c207
-rw-r--r--src/nautilus-view-item-model.h32
-rw-r--r--src/nautilus-view-model.c286
-rw-r--r--src/nautilus-view-model.h40
-rw-r--r--src/nautilus-view.c373
-rw-r--r--src/nautilus-view.h123
-rw-r--r--src/nautilus-window-slot-dnd.c568
-rw-r--r--src/nautilus-window-slot-dnd.h37
-rw-r--r--src/nautilus-window-slot.c3769
-rw-r--r--src/nautilus-window-slot.h150
-rw-r--r--src/nautilus-window.c3100
-rw-r--r--src/nautilus-window.h121
-rw-r--r--src/nautilus-x-content-bar.c352
-rw-r--r--src/nautilus-x-content-bar.h37
-rw-r--r--src/resources/css/Adwaita.css215
-rw-r--r--src/resources/css/nautilus.css10
-rw-r--r--src/resources/gtk/help-overlay.ui413
-rw-r--r--src/resources/nautilus.gresource.xml33
-rw-r--r--src/resources/text-x-preview.pngbin0 -> 923 bytes
-rw-r--r--src/resources/ui/nautilus-batch-rename-dialog-menu.ui95
-rw-r--r--src/resources/ui/nautilus-batch-rename-dialog.ui427
-rw-r--r--src/resources/ui/nautilus-compress-dialog.ui186
-rw-r--r--src/resources/ui/nautilus-create-folder-dialog.ui83
-rw-r--r--src/resources/ui/nautilus-file-properties-change-permissions.ui185
-rw-r--r--src/resources/ui/nautilus-files-view-context-menus.ui239
-rw-r--r--src/resources/ui/nautilus-folder-is-empty.ui46
-rw-r--r--src/resources/ui/nautilus-no-search-results.ui60
-rw-r--r--src/resources/ui/nautilus-pathbar-context-menu.ui19
-rw-r--r--src/resources/ui/nautilus-preferences-window.ui1365
-rw-r--r--src/resources/ui/nautilus-progress-info-widget.ui92
-rw-r--r--src/resources/ui/nautilus-properties-window.ui1382
-rw-r--r--src/resources/ui/nautilus-rename-file-popover.ui86
-rw-r--r--src/resources/ui/nautilus-search-popover.ui430
-rw-r--r--src/resources/ui/nautilus-starred-is-empty.ui46
-rw-r--r--src/resources/ui/nautilus-toolbar-switcher.ui73
-rw-r--r--src/resources/ui/nautilus-toolbar-view-menu.ui296
-rw-r--r--src/resources/ui/nautilus-toolbar.ui744
-rw-r--r--src/resources/ui/nautilus-trash-is-empty.ui46
-rw-r--r--src/resources/ui/nautilus-window.ui197
260 files changed, 138197 insertions, 0 deletions
diff --git a/src/.gitignore b/src/.gitignore
new file mode 100644
index 0000000..40b4e3a
--- /dev/null
+++ b/src/.gitignore
@@ -0,0 +1,13 @@
+.dirstamp
+/nautilus
+/nautilus-autorun-software
+/nautilus-enum-types.c
+/nautilus-enum-types.h
+/nautilus-freedesktop-generated.c
+/nautilus-freedesktop-generated.h
+/nautilus-generated.c
+/nautilus-generated.h
+/nautilus-resources.c
+/nautilus-resources.h
+/nautilus-shell-search-provider-generated.c
+/nautilus-shell-search-provider-generated.h
diff --git a/src/animation/egg-animation.c b/src/animation/egg-animation.c
new file mode 100644
index 0000000..7ceba88
--- /dev/null
+++ b/src/animation/egg-animation.c
@@ -0,0 +1,1183 @@
+/* egg-animation.c
+ *
+ * Copyright (C) 2010-2016 Christian Hergert <christian@hergert.me>
+ *
+ * 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 General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib/gi18n.h>
+#include <gobject/gvaluecollector.h>
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "animation/egg-animation.h"
+#include "animation/egg-frame-source.h"
+
+#define FALLBACK_FRAME_RATE 60
+
+typedef gdouble (*AlphaFunc) (gdouble offset);
+typedef void (*TweenFunc) (const GValue *begin,
+ const GValue *end,
+ GValue *value,
+ gdouble offset);
+
+typedef struct
+{
+ gboolean is_child; /* Does GParamSpec belong to parent widget */
+ GParamSpec *pspec; /* GParamSpec of target property */
+ GValue begin; /* Begin value in animation */
+ GValue end; /* End value in animation */
+} Tween;
+
+
+struct _EggAnimation
+{
+ GInitiallyUnowned parent_instance;
+
+ gpointer target; /* Target object to animate */
+ guint64 begin_msec; /* Time in which animation started */
+ guint duration_msec; /* Duration of animation */
+ guint mode; /* Tween mode */
+ gulong tween_handler; /* GSource or signal handler */
+ gulong after_paint_handler; /* signal handler */
+ gdouble last_offset; /* Track our last offset */
+ GArray *tweens; /* Array of tweens to perform */
+ GdkFrameClock *frame_clock; /* An optional frame-clock for sync. */
+ GDestroyNotify notify; /* Notify callback */
+ gpointer notify_data; /* Data for notify */
+};
+
+G_DEFINE_TYPE (EggAnimation, egg_animation, G_TYPE_INITIALLY_UNOWNED)
+
+enum {
+ PROP_0,
+ PROP_DURATION,
+ PROP_FRAME_CLOCK,
+ PROP_MODE,
+ PROP_TARGET,
+ LAST_PROP
+};
+
+
+enum {
+ TICK,
+ LAST_SIGNAL
+};
+
+
+/*
+ * Helper macros.
+ */
+#define LAST_FUNDAMENTAL 64
+#define TWEEN(type) \
+ static void \
+ tween_ ## type (const GValue * begin, \
+ const GValue * end, \
+ GValue * value, \
+ gdouble offset) \
+ { \
+ g ## type x = g_value_get_ ## type (begin); \
+ g ## type y = g_value_get_ ## type (end); \
+ g_value_set_ ## type (value, x + ((y - x) * offset)); \
+ }
+
+
+/*
+ * Globals.
+ */
+static AlphaFunc alpha_funcs[EGG_ANIMATION_LAST];
+static gboolean debug;
+static GParamSpec *properties[LAST_PROP];
+static guint signals[LAST_SIGNAL];
+static TweenFunc tween_funcs[LAST_FUNDAMENTAL];
+static guint slow_down_factor = 1;
+
+
+/*
+ * Tweeners for basic types.
+ */
+TWEEN (int);
+TWEEN (uint);
+TWEEN (long);
+TWEEN (ulong);
+TWEEN (float);
+TWEEN (double);
+
+
+/**
+ * egg_animation_alpha_ease_in_cubic:
+ * @offset: (in): The position within the animation; 0.0 to 1.0.
+ *
+ * An alpha function to transform the offset within the animation.
+ * @EGG_ANIMATION_CUBIC means the valu ewill be transformed into
+ * cubic acceleration (x * x * x).
+ */
+static gdouble
+egg_animation_alpha_ease_in_cubic (gdouble offset)
+{
+ return offset * offset * offset;
+}
+
+
+static gdouble
+egg_animation_alpha_ease_out_cubic (gdouble offset)
+{
+ gdouble p = offset - 1.0;
+
+ return p * p * p + 1.0;
+}
+
+static gdouble
+egg_animation_alpha_ease_in_out_cubic (gdouble offset)
+{
+ if (offset < .5)
+ return egg_animation_alpha_ease_in_cubic (offset * 2.0) / 2.0;
+ else
+ return .5 + egg_animation_alpha_ease_out_cubic ((offset - .5) * 2.0) / 2.0;
+}
+
+
+/**
+ * egg_animation_alpha_linear:
+ * @offset: (in): The position within the animation; 0.0 to 1.0.
+ *
+ * An alpha function to transform the offset within the animation.
+ * @EGG_ANIMATION_LINEAR means no tranformation will be made.
+ *
+ * Returns: @offset.
+ * Side effects: None.
+ */
+static gdouble
+egg_animation_alpha_linear (gdouble offset)
+{
+ return offset;
+}
+
+
+/**
+ * egg_animation_alpha_ease_in_quad:
+ * @offset: (in): The position within the animation; 0.0 to 1.0.
+ *
+ * An alpha function to transform the offset within the animation.
+ * @EGG_ANIMATION_EASE_IN_QUAD means that the value will be transformed
+ * into a quadratic acceleration.
+ *
+ * Returns: A tranformation of @offset.
+ * Side effects: None.
+ */
+static gdouble
+egg_animation_alpha_ease_in_quad (gdouble offset)
+{
+ return offset * offset;
+}
+
+
+/**
+ * egg_animation_alpha_ease_out_quad:
+ * @offset: (in): The position within the animation; 0.0 to 1.0.
+ *
+ * An alpha function to transform the offset within the animation.
+ * @EGG_ANIMATION_EASE_OUT_QUAD means that the value will be transformed
+ * into a quadratic deceleration.
+ *
+ * Returns: A tranformation of @offset.
+ * Side effects: None.
+ */
+static gdouble
+egg_animation_alpha_ease_out_quad (gdouble offset)
+{
+ return -1.0 * offset * (offset - 2.0);
+}
+
+
+/**
+ * egg_animation_alpha_ease_in_out_quad:
+ * @offset: (in): The position within the animation; 0.0 to 1.0.
+ *
+ * An alpha function to transform the offset within the animation.
+ * @EGG_ANIMATION_EASE_IN_OUT_QUAD means that the value will be transformed
+ * into a quadratic acceleration for the first half, and quadratic
+ * deceleration the second half.
+ *
+ * Returns: A tranformation of @offset.
+ * Side effects: None.
+ */
+static gdouble
+egg_animation_alpha_ease_in_out_quad (gdouble offset)
+{
+ offset *= 2.0;
+ if (offset < 1.0)
+ return 0.5 * offset * offset;
+ offset -= 1.0;
+ return -0.5 * (offset * (offset - 2.0) - 1.0);
+}
+
+
+/**
+ * egg_animation_load_begin_values:
+ * @animation: (in): A #EggAnimation.
+ *
+ * Load the begin values for all the properties we are about to
+ * animate.
+ *
+ * Side effects: None.
+ */
+static void
+egg_animation_load_begin_values (EggAnimation *animation)
+{
+ GtkContainer *container;
+ Tween *tween;
+ guint i;
+
+ g_return_if_fail (EGG_IS_ANIMATION (animation));
+
+ for (i = 0; i < animation->tweens->len; i++)
+ {
+ tween = &g_array_index (animation->tweens, Tween, i);
+ g_value_reset (&tween->begin);
+ if (tween->is_child)
+ {
+ container = GTK_CONTAINER (gtk_widget_get_parent (animation->target));
+ gtk_container_child_get_property (container,
+ animation->target,
+ tween->pspec->name,
+ &tween->begin);
+ }
+ else
+ {
+ g_object_get_property (animation->target,
+ tween->pspec->name,
+ &tween->begin);
+ }
+ }
+}
+
+
+/**
+ * egg_animation_unload_begin_values:
+ * @animation: (in): A #EggAnimation.
+ *
+ * Unloads the begin values for the animation. This might be particularly
+ * useful once we support pointer types.
+ *
+ * Side effects: None.
+ */
+static void
+egg_animation_unload_begin_values (EggAnimation *animation)
+{
+ Tween *tween;
+ guint i;
+
+ g_return_if_fail (EGG_IS_ANIMATION (animation));
+
+ for (i = 0; i < animation->tweens->len; i++)
+ {
+ tween = &g_array_index (animation->tweens, Tween, i);
+ g_value_reset (&tween->begin);
+ }
+}
+
+
+/**
+ * egg_animation_get_offset:
+ * @animation: A #EggAnimation.
+ * @frame_time: the time to present the frame, or 0 for current timing.
+ *
+ * Retrieves the position within the animation from 0.0 to 1.0. This
+ * value is calculated using the msec of the beginning of the animation
+ * and the current time.
+ *
+ * Returns: The offset of the animation from 0.0 to 1.0.
+ */
+static gdouble
+egg_animation_get_offset (EggAnimation *animation,
+ gint64 frame_time)
+{
+ gdouble offset;
+ gint64 frame_msec;
+
+ g_return_val_if_fail (EGG_IS_ANIMATION (animation), 0.0);
+
+ if (frame_time == 0)
+ {
+ if (animation->frame_clock != NULL)
+ frame_time = gdk_frame_clock_get_frame_time (animation->frame_clock);
+ else
+ frame_time = g_get_monotonic_time ();
+ }
+
+ frame_msec = frame_time / 1000L;
+
+ offset = (gdouble) (frame_msec - animation->begin_msec) /
+ (gdouble) MAX (animation->duration_msec, 1);
+
+ return CLAMP (offset, 0.0, 1.0);
+}
+
+
+/**
+ * egg_animation_update_property:
+ * @animation: (in): A #EggAnimation.
+ * @target: (in): A #GObject.
+ * @tween: (in): a #Tween containing the property.
+ * @value: (in): The new value for the property.
+ *
+ * Updates the value of a property on an object using @value.
+ *
+ * Side effects: The property of @target is updated.
+ */
+static void
+egg_animation_update_property (EggAnimation *animation,
+ gpointer target,
+ Tween *tween,
+ const GValue *value)
+{
+ g_assert (EGG_IS_ANIMATION (animation));
+ g_assert (G_IS_OBJECT (target));
+ g_assert (tween);
+ g_assert (value);
+
+ g_object_set_property (target, tween->pspec->name, value);
+}
+
+
+/**
+ * egg_animation_update_child_property:
+ * @animation: (in): A #EggAnimation.
+ * @target: (in): A #GObject.
+ * @tween: (in): A #Tween containing the property.
+ * @value: (in): The new value for the property.
+ *
+ * Updates the value of the parent widget of the target to @value.
+ *
+ * Side effects: The property of @target<!-- -->'s parent widget is updated.
+ */
+static void
+egg_animation_update_child_property (EggAnimation *animation,
+ gpointer target,
+ Tween *tween,
+ const GValue *value)
+{
+ GtkWidget *parent;
+
+ g_assert (EGG_IS_ANIMATION (animation));
+ g_assert (G_IS_OBJECT (target));
+ g_assert (tween);
+ g_assert (value);
+
+ parent = gtk_widget_get_parent (GTK_WIDGET (target));
+ gtk_container_child_set_property (GTK_CONTAINER (parent),
+ target,
+ tween->pspec->name,
+ value);
+}
+
+
+/**
+ * egg_animation_get_value_at_offset:
+ * @animation: (in): A #EggAnimation.
+ * @offset: (in): The offset in the animation from 0.0 to 1.0.
+ * @tween: (in): A #Tween containing the property.
+ * @value: (out): A #GValue in which to store the property.
+ *
+ * Retrieves a value for a particular position within the animation.
+ *
+ * Side effects: None.
+ */
+static void
+egg_animation_get_value_at_offset (EggAnimation *animation,
+ gdouble offset,
+ Tween *tween,
+ GValue *value)
+{
+ g_return_if_fail (EGG_IS_ANIMATION (animation));
+ g_return_if_fail (tween != NULL);
+ g_return_if_fail (value != NULL);
+ g_return_if_fail (value->g_type == tween->pspec->value_type);
+
+ if (value->g_type < LAST_FUNDAMENTAL)
+ {
+ /*
+ * If you hit the following assertion, you need to add a function
+ * to create the new value at the given offset.
+ */
+ g_assert (tween_funcs[value->g_type]);
+ tween_funcs[value->g_type](&tween->begin, &tween->end, value, offset);
+ }
+ else
+ {
+ /*
+ * TODO: Support complex transitions.
+ */
+ if (offset >= 1.0)
+ g_value_copy (&tween->end, value);
+ }
+}
+
+static void
+egg_animation_set_frame_clock (EggAnimation *animation,
+ GdkFrameClock *frame_clock)
+{
+ if (animation->frame_clock != frame_clock)
+ {
+ g_clear_object (&animation->frame_clock);
+ animation->frame_clock = frame_clock ? g_object_ref (frame_clock) : NULL;
+ }
+}
+
+static void
+egg_animation_set_target (EggAnimation *animation,
+ gpointer target)
+{
+ g_assert (!animation->target);
+
+ animation->target = g_object_ref (target);
+
+ if (GTK_IS_WIDGET (animation->target))
+ egg_animation_set_frame_clock (animation,
+ gtk_widget_get_frame_clock (animation->target));
+}
+
+
+/**
+ * egg_animation_tick:
+ * @animation: (in): A #EggAnimation.
+ *
+ * Moves the object properties to the next position in the animation.
+ *
+ * Returns: %TRUE if the animation has not completed; otherwise %FALSE.
+ * Side effects: None.
+ */
+static gboolean
+egg_animation_tick (EggAnimation *animation,
+ gdouble offset)
+{
+ gdouble alpha;
+ GValue value = { 0 };
+ Tween *tween;
+ guint i;
+
+ g_return_val_if_fail (EGG_IS_ANIMATION (animation), FALSE);
+
+ if (offset == animation->last_offset)
+ return offset < 1.0;
+
+ alpha = alpha_funcs[animation->mode](offset);
+
+ /*
+ * Update property values.
+ */
+ for (i = 0; i < animation->tweens->len; i++)
+ {
+ tween = &g_array_index (animation->tweens, Tween, i);
+ g_value_init (&value, tween->pspec->value_type);
+ egg_animation_get_value_at_offset (animation, alpha, tween, &value);
+ if (!tween->is_child)
+ {
+ egg_animation_update_property (animation,
+ animation->target,
+ tween,
+ &value);
+ }
+ else
+ {
+ egg_animation_update_child_property (animation,
+ animation->target,
+ tween,
+ &value);
+ }
+ g_value_unset (&value);
+ }
+
+ /*
+ * Notify anyone interested in the tick signal.
+ */
+ g_signal_emit (animation, signals[TICK], 0);
+
+ /*
+ * Flush any outstanding events to the graphics server (in the case of X).
+ */
+#if !GTK_CHECK_VERSION (3, 13, 0)
+ if (GTK_IS_WIDGET (animation->target))
+ {
+ GdkWindow *window;
+
+ if ((window = gtk_widget_get_window (GTK_WIDGET (animation->target))))
+ gdk_window_flush (window);
+ }
+#endif
+
+ animation->last_offset = offset;
+
+ return offset < 1.0;
+}
+
+
+/**
+ * egg_animation_timeout_cb:
+ * @user_data: (in): A #EggAnimation.
+ *
+ * Timeout from the main loop to move to the next step of the animation.
+ *
+ * Returns: %TRUE until the animation has completed; otherwise %FALSE.
+ * Side effects: None.
+ */
+static gboolean
+egg_animation_timeout_cb (gpointer user_data)
+{
+ EggAnimation *animation = user_data;
+ gboolean ret;
+ gdouble offset;
+
+ offset = egg_animation_get_offset (animation, 0);
+
+ if (!(ret = egg_animation_tick (animation, offset)))
+ egg_animation_stop (animation);
+
+ return ret;
+}
+
+
+static gboolean
+egg_animation_widget_tick_cb (GdkFrameClock *frame_clock,
+ EggAnimation *animation)
+{
+ gboolean ret = G_SOURCE_REMOVE;
+
+ g_assert (GDK_IS_FRAME_CLOCK (frame_clock));
+ g_assert (EGG_IS_ANIMATION (animation));
+
+ if (animation->tween_handler)
+ {
+ gdouble offset;
+
+ offset = egg_animation_get_offset (animation, 0);
+
+ if (!(ret = egg_animation_tick (animation, offset)))
+ egg_animation_stop (animation);
+ }
+
+ return ret;
+}
+
+
+static void
+egg_animation_widget_after_paint_cb (GdkFrameClock *frame_clock,
+ EggAnimation *animation)
+{
+ gint64 base_time;
+ gint64 interval;
+ gint64 next_frame_time;
+ gdouble offset;
+
+ g_assert (GDK_IS_FRAME_CLOCK (frame_clock));
+ g_assert (EGG_IS_ANIMATION (animation));
+
+ base_time = gdk_frame_clock_get_frame_time (frame_clock);
+ gdk_frame_clock_get_refresh_info (frame_clock, base_time, &interval, &next_frame_time);
+
+ offset = egg_animation_get_offset (animation, next_frame_time);
+
+ egg_animation_tick (animation, offset);
+}
+
+
+/**
+ * egg_animation_start:
+ * @animation: (in): A #EggAnimation.
+ *
+ * Start the animation. When the animation stops, the internal reference will
+ * be dropped and the animation may be finalized.
+ *
+ * Side effects: None.
+ */
+void
+egg_animation_start (EggAnimation *animation)
+{
+ g_return_if_fail (EGG_IS_ANIMATION (animation));
+ g_return_if_fail (!animation->tween_handler);
+
+ g_object_ref_sink (animation);
+ egg_animation_load_begin_values (animation);
+
+ if (animation->frame_clock)
+ {
+ animation->begin_msec = gdk_frame_clock_get_frame_time (animation->frame_clock) / 1000UL;
+ animation->tween_handler =
+ g_signal_connect (animation->frame_clock,
+ "update",
+ G_CALLBACK (egg_animation_widget_tick_cb),
+ animation);
+ animation->after_paint_handler =
+ g_signal_connect (animation->frame_clock,
+ "after-paint",
+ G_CALLBACK (egg_animation_widget_after_paint_cb),
+ animation);
+ gdk_frame_clock_begin_updating (animation->frame_clock);
+ }
+ else
+ {
+ animation->begin_msec = g_get_monotonic_time () / 1000UL;
+ animation->tween_handler = egg_frame_source_add (FALLBACK_FRAME_RATE,
+ egg_animation_timeout_cb,
+ animation);
+ }
+}
+
+
+static void
+egg_animation_notify (EggAnimation *self)
+{
+ g_assert (EGG_IS_ANIMATION (self));
+
+ if (self->notify != NULL)
+ {
+ GDestroyNotify notify = self->notify;
+ gpointer data = self->notify_data;
+
+ self->notify = NULL;
+ self->notify_data = NULL;
+
+ notify (data);
+ }
+}
+
+
+/**
+ * egg_animation_stop:
+ * @animation: (in): A #EggAnimation.
+ *
+ * Stops a running animation. The internal reference to the animation is
+ * dropped and therefore may cause the object to finalize.
+ *
+ * Side effects: None.
+ */
+void
+egg_animation_stop (EggAnimation *animation)
+{
+ g_return_if_fail (EGG_IS_ANIMATION (animation));
+
+ if (animation->tween_handler)
+ {
+ if (animation->frame_clock)
+ {
+ gdk_frame_clock_end_updating (animation->frame_clock);
+ g_clear_signal_handler (&animation->tween_handler, animation->frame_clock);
+ g_clear_signal_handler (&animation->after_paint_handler, animation->frame_clock);
+ }
+ else
+ {
+ g_source_remove (animation->tween_handler);
+ animation->tween_handler = 0;
+ }
+ egg_animation_unload_begin_values (animation);
+ egg_animation_notify (animation);
+ g_object_unref (animation);
+ }
+}
+
+
+/**
+ * egg_animation_add_property:
+ * @animation: (in): A #EggAnimation.
+ * @pspec: (in): A #ParamSpec of @target or a #GtkWidget<!-- -->'s parent.
+ * @value: (in): The new value for the property at the end of the animation.
+ *
+ * Adds a new property to the set of properties to be animated during the
+ * lifetime of the animation.
+ *
+ * Side effects: None.
+ */
+void
+egg_animation_add_property (EggAnimation *animation,
+ GParamSpec *pspec,
+ const GValue *value)
+{
+ Tween tween = { 0 };
+ GType type;
+
+ g_return_if_fail (EGG_IS_ANIMATION (animation));
+ g_return_if_fail (pspec != NULL);
+ g_return_if_fail (value != NULL);
+ g_return_if_fail (value->g_type);
+ g_return_if_fail (animation->target);
+ g_return_if_fail (!animation->tween_handler);
+
+ type = G_TYPE_FROM_INSTANCE (animation->target);
+ tween.is_child = !g_type_is_a (type, pspec->owner_type);
+ if (tween.is_child)
+ {
+ if (!GTK_IS_WIDGET (animation->target))
+ {
+ g_critical (_("Cannot locate property %s in class %s"),
+ pspec->name, g_type_name (type));
+ return;
+ }
+ }
+
+ tween.pspec = g_param_spec_ref (pspec);
+ g_value_init (&tween.begin, pspec->value_type);
+ g_value_init (&tween.end, pspec->value_type);
+ g_value_copy (value, &tween.end);
+ g_array_append_val (animation->tweens, tween);
+}
+
+
+/**
+ * egg_animation_dispose:
+ * @object: (in): A #EggAnimation.
+ *
+ * Releases any object references the animation contains.
+ *
+ * Side effects: None.
+ */
+static void
+egg_animation_dispose (GObject *object)
+{
+ EggAnimation *self = EGG_ANIMATION (object);
+
+ g_clear_object (&self->target);
+ g_clear_object (&self->frame_clock);
+
+ G_OBJECT_CLASS (egg_animation_parent_class)->dispose (object);
+}
+
+
+/**
+ * egg_animation_finalize:
+ * @object: (in): A #EggAnimation.
+ *
+ * Finalizes the object and releases any resources allocated.
+ *
+ * Side effects: None.
+ */
+static void
+egg_animation_finalize (GObject *object)
+{
+ EggAnimation *self = EGG_ANIMATION (object);
+ Tween *tween;
+ guint i;
+
+ for (i = 0; i < self->tweens->len; i++)
+ {
+ tween = &g_array_index (self->tweens, Tween, i);
+ g_value_unset (&tween->begin);
+ g_value_unset (&tween->end);
+ g_param_spec_unref (tween->pspec);
+ }
+
+ g_array_unref (self->tweens);
+
+ G_OBJECT_CLASS (egg_animation_parent_class)->finalize (object);
+}
+
+
+/**
+ * egg_animation_set_property:
+ * @object: (in): A #GObject.
+ * @prop_id: (in): The property identifier.
+ * @value: (in): The given property.
+ * @pspec: (in): A #ParamSpec.
+ *
+ * Set a given #GObject property.
+ */
+static void
+egg_animation_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ EggAnimation *animation = EGG_ANIMATION (object);
+
+ switch (prop_id)
+ {
+ case PROP_DURATION:
+ animation->duration_msec = g_value_get_uint (value) * slow_down_factor;
+ break;
+
+ case PROP_FRAME_CLOCK:
+ egg_animation_set_frame_clock (animation, g_value_get_object (value));
+ break;
+
+ case PROP_MODE:
+ animation->mode = g_value_get_enum (value);
+ break;
+
+ case PROP_TARGET:
+ egg_animation_set_target (animation, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+
+/**
+ * egg_animation_class_init:
+ * @klass: (in): A #EggAnimationClass.
+ *
+ * Initializes the GObjectClass.
+ *
+ * Side effects: Properties, signals, and vtables are initialized.
+ */
+static void
+egg_animation_class_init (EggAnimationClass *klass)
+{
+ GObjectClass *object_class;
+ const gchar *slow_down_factor_env;
+
+ debug = !!g_getenv ("EGG_ANIMATION_DEBUG");
+ slow_down_factor_env = g_getenv ("EGG_ANIMATION_SLOW_DOWN_FACTOR");
+
+ if (slow_down_factor_env)
+ slow_down_factor = MAX (1, atoi (slow_down_factor_env));
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->dispose = egg_animation_dispose;
+ object_class->finalize = egg_animation_finalize;
+ object_class->set_property = egg_animation_set_property;
+
+ /**
+ * EggAnimation:duration:
+ *
+ * The "duration" property is the total number of milliseconds that the
+ * animation should run before being completed.
+ */
+ properties[PROP_DURATION] =
+ g_param_spec_uint ("duration",
+ "Duration",
+ "The duration of the animation",
+ 0,
+ G_MAXUINT,
+ 250,
+ (G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ properties[PROP_FRAME_CLOCK] =
+ g_param_spec_object ("frame-clock",
+ "Frame Clock",
+ "An optional frame-clock to synchronize with.",
+ GDK_TYPE_FRAME_CLOCK,
+ (G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * EggAnimation:mode:
+ *
+ * The "mode" property is the Alpha function that should be used to
+ * determine the offset within the animation based on the current
+ * offset in the animations duration.
+ */
+ properties[PROP_MODE] =
+ g_param_spec_enum ("mode",
+ "Mode",
+ "The animation mode",
+ EGG_TYPE_ANIMATION_MODE,
+ EGG_ANIMATION_LINEAR,
+ (G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * EggAnimation:target:
+ *
+ * The "target" property is the #GObject that should have its properties
+ * animated.
+ */
+ properties[PROP_TARGET] =
+ g_param_spec_object ("target",
+ "Target",
+ "The target of the animation",
+ G_TYPE_OBJECT,
+ (G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+ /**
+ * EggAnimation::tick:
+ *
+ * The "tick" signal is emitted on each frame in the animation.
+ */
+ signals[TICK] = g_signal_new ("tick",
+ EGG_TYPE_ANIMATION,
+ G_SIGNAL_RUN_FIRST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 0);
+
+#define SET_ALPHA(_T, _t) \
+ alpha_funcs[EGG_ANIMATION_ ## _T] = egg_animation_alpha_ ## _t
+
+ SET_ALPHA (LINEAR, linear);
+ SET_ALPHA (EASE_IN_QUAD, ease_in_quad);
+ SET_ALPHA (EASE_OUT_QUAD, ease_out_quad);
+ SET_ALPHA (EASE_IN_OUT_QUAD, ease_in_out_quad);
+ SET_ALPHA (EASE_IN_CUBIC, ease_in_cubic);
+ SET_ALPHA (EASE_OUT_CUBIC, ease_out_cubic);
+ SET_ALPHA (EASE_IN_OUT_CUBIC, ease_in_out_cubic);
+
+#define SET_TWEEN(_T, _t) \
+ G_STMT_START { \
+ guint idx = G_TYPE_ ## _T; \
+ tween_funcs[idx] = tween_ ## _t; \
+ } G_STMT_END
+
+ SET_TWEEN (INT, int);
+ SET_TWEEN (UINT, uint);
+ SET_TWEEN (LONG, long);
+ SET_TWEEN (ULONG, ulong);
+ SET_TWEEN (FLOAT, float);
+ SET_TWEEN (DOUBLE, double);
+}
+
+
+/**
+ * egg_animation_init:
+ * @animation: (in): A #EggAnimation.
+ *
+ * Initializes the #EggAnimation instance.
+ *
+ * Side effects: Everything.
+ */
+static void
+egg_animation_init (EggAnimation *animation)
+{
+ animation->duration_msec = 250;
+ animation->mode = EGG_ANIMATION_EASE_IN_OUT_QUAD;
+ animation->tweens = g_array_new (FALSE, FALSE, sizeof (Tween));
+ animation->last_offset = -G_MINDOUBLE;
+}
+
+
+/**
+ * egg_animation_mode_get_type:
+ *
+ * Retrieves the GType for #EggAnimationMode.
+ *
+ * Returns: A GType.
+ * Side effects: GType registered on first call.
+ */
+GType
+egg_animation_mode_get_type (void)
+{
+ static GType type_id = 0;
+ static const GEnumValue values[] = {
+ { EGG_ANIMATION_LINEAR, "EGG_ANIMATION_LINEAR", "linear" },
+ { EGG_ANIMATION_EASE_IN_QUAD, "EGG_ANIMATION_EASE_IN_QUAD", "ease-in-quad" },
+ { EGG_ANIMATION_EASE_IN_OUT_QUAD, "EGG_ANIMATION_EASE_IN_OUT_QUAD", "ease-in-out-quad" },
+ { EGG_ANIMATION_EASE_OUT_QUAD, "EGG_ANIMATION_EASE_OUT_QUAD", "ease-out-quad" },
+ { EGG_ANIMATION_EASE_IN_CUBIC, "EGG_ANIMATION_EASE_IN_CUBIC", "ease-in-cubic" },
+ { EGG_ANIMATION_EASE_OUT_CUBIC, "EGG_ANIMATION_EASE_OUT_CUBIC", "ease-out-cubic" },
+ { EGG_ANIMATION_EASE_IN_OUT_CUBIC, "EGG_ANIMATION_EASE_IN_OUT_CUBIC", "ease-in-out-cubic" },
+ { 0 }
+ };
+
+ if (G_UNLIKELY (!type_id))
+ type_id = g_enum_register_static ("EggAnimationMode", values);
+ return type_id;
+}
+
+/**
+ * egg_object_animatev:
+ * @object: A #GObject.
+ * @mode: The animation mode.
+ * @duration_msec: The duration in milliseconds.
+ * @frame_clock: (nullable): The #GdkFrameClock to synchronize to.
+ * @first_property: The first property to animate.
+ * @args: A variadac list of arguments
+ *
+ * Returns: (transfer none): A #EggAnimation.
+ */
+EggAnimation *
+egg_object_animatev (gpointer object,
+ EggAnimationMode mode,
+ guint duration_msec,
+ GdkFrameClock *frame_clock,
+ const gchar *first_property,
+ va_list args)
+{
+ EggAnimation *animation;
+ GObjectClass *klass;
+ GObjectClass *pklass;
+ const gchar *name;
+ GParamSpec *pspec;
+ GtkWidget *parent;
+ GValue value = { 0 };
+ gchar *error = NULL;
+ GType type;
+ GType ptype;
+ gboolean enable_animations;
+
+ g_return_val_if_fail (first_property != NULL, NULL);
+ g_return_val_if_fail (mode < EGG_ANIMATION_LAST, NULL);
+
+ if ((frame_clock == NULL) && GTK_IS_WIDGET (object))
+ frame_clock = gtk_widget_get_frame_clock (GTK_WIDGET (object));
+
+ /*
+ * If we have a frame clock, then we must be in the gtk thread and we
+ * should check GtkSettings for disabled animations. If we are disabled,
+ * we will just make the timeout immediate.
+ */
+ if (frame_clock != NULL)
+ {
+ g_object_get (gtk_settings_get_default (),
+ "gtk-enable-animations", &enable_animations,
+ NULL);
+
+ if (enable_animations == FALSE)
+ duration_msec = 0;
+ }
+
+ name = first_property;
+ type = G_TYPE_FROM_INSTANCE (object);
+ klass = G_OBJECT_GET_CLASS (object);
+ animation = g_object_new (EGG_TYPE_ANIMATION,
+ "duration", duration_msec,
+ "frame-clock", frame_clock,
+ "mode", mode,
+ "target", object,
+ NULL);
+
+ do
+ {
+ /*
+ * First check for the property on the object. If that does not exist
+ * then check if the object has a parent and look at its child
+ * properties (if it's a GtkWidget).
+ */
+ if (!(pspec = g_object_class_find_property (klass, name)))
+ {
+ if (!g_type_is_a (type, GTK_TYPE_WIDGET))
+ {
+ g_critical (_("Failed to find property %s in %s"),
+ name, g_type_name (type));
+ goto failure;
+ }
+ if (!(parent = gtk_widget_get_parent (object)))
+ {
+ g_critical (_("Failed to find property %s in %s"),
+ name, g_type_name (type));
+ goto failure;
+ }
+ pklass = G_OBJECT_GET_CLASS (parent);
+ ptype = G_TYPE_FROM_INSTANCE (parent);
+ if (!(pspec = gtk_container_class_find_child_property (pklass, name)))
+ {
+ g_critical (_("Failed to find property %s in %s or parent %s"),
+ name, g_type_name (type), g_type_name (ptype));
+ goto failure;
+ }
+ }
+
+ g_value_init (&value, pspec->value_type);
+ G_VALUE_COLLECT (&value, args, 0, &error);
+ if (error != NULL)
+ {
+ g_critical (_("Failed to retrieve va_list value: %s"), error);
+ g_free (error);
+ goto failure;
+ }
+
+ egg_animation_add_property (animation, pspec, &value);
+ g_value_unset (&value);
+ }
+ while ((name = va_arg (args, const gchar *)));
+
+ egg_animation_start (animation);
+
+ return animation;
+
+failure:
+ g_object_ref_sink (animation);
+ g_object_unref (animation);
+ return NULL;
+}
+
+/**
+ * egg_object_animate:
+ * @object: (in): A #GObject.
+ * @mode: (in): The animation mode.
+ * @duration_msec: (in): The duration in milliseconds.
+ * @first_property: (in): The first property to animate.
+ *
+ * Animates the properties of @object. The can be set in a similar manner to g_object_set(). They
+ * will be animated from their current value to the target value over the time period.
+ *
+ * Return value: (transfer none): A #EggAnimation.
+ * Side effects: None.
+ */
+EggAnimation*
+egg_object_animate (gpointer object,
+ EggAnimationMode mode,
+ guint duration_msec,
+ GdkFrameClock *frame_clock,
+ const gchar *first_property,
+ ...)
+{
+ EggAnimation *animation;
+ va_list args;
+
+ va_start (args, first_property);
+ animation = egg_object_animatev (object,
+ mode,
+ duration_msec,
+ frame_clock,
+ first_property,
+ args);
+ va_end (args);
+
+ return animation;
+}
+
+/**
+ * egg_object_animate_full:
+ *
+ * Return value: (transfer none): A #EggAnimation.
+ */
+EggAnimation*
+egg_object_animate_full (gpointer object,
+ EggAnimationMode mode,
+ guint duration_msec,
+ GdkFrameClock *frame_clock,
+ GDestroyNotify notify,
+ gpointer notify_data,
+ const gchar *first_property,
+ ...)
+{
+ EggAnimation *animation;
+ va_list args;
+
+ va_start (args, first_property);
+ animation = egg_object_animatev (object,
+ mode,
+ duration_msec,
+ frame_clock,
+ first_property,
+ args);
+ va_end (args);
+
+ animation->notify = notify;
+ animation->notify_data = notify_data;
+
+ return animation;
+}
diff --git a/src/animation/egg-animation.h b/src/animation/egg-animation.h
new file mode 100644
index 0000000..45b4f39
--- /dev/null
+++ b/src/animation/egg-animation.h
@@ -0,0 +1,74 @@
+/* egg-animation.h
+ *
+ * Copyright (C) 2010-2016 Christian Hergert <christian@hergert.me>
+ *
+ * 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 General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+#define EGG_TYPE_ANIMATION (egg_animation_get_type())
+#define EGG_TYPE_ANIMATION_MODE (egg_animation_mode_get_type())
+
+G_DECLARE_FINAL_TYPE (EggAnimation, egg_animation,
+ EGG, ANIMATION, GInitiallyUnowned)
+
+typedef enum _EggAnimationMode EggAnimationMode;
+
+enum _EggAnimationMode
+{
+ EGG_ANIMATION_LINEAR,
+ EGG_ANIMATION_EASE_IN_QUAD,
+ EGG_ANIMATION_EASE_OUT_QUAD,
+ EGG_ANIMATION_EASE_IN_OUT_QUAD,
+ EGG_ANIMATION_EASE_IN_CUBIC,
+ EGG_ANIMATION_EASE_OUT_CUBIC,
+ EGG_ANIMATION_EASE_IN_OUT_CUBIC,
+
+ EGG_ANIMATION_LAST
+};
+
+GType egg_animation_mode_get_type (void);
+void egg_animation_start (EggAnimation *animation);
+void egg_animation_stop (EggAnimation *animation);
+void egg_animation_add_property (EggAnimation *animation,
+ GParamSpec *pspec,
+ const GValue *value);
+
+EggAnimation *egg_object_animatev (gpointer object,
+ EggAnimationMode mode,
+ guint duration_msec,
+ GdkFrameClock *frame_clock,
+ const gchar *first_property,
+ va_list args);
+EggAnimation* egg_object_animate (gpointer object,
+ EggAnimationMode mode,
+ guint duration_msec,
+ GdkFrameClock *frame_clock,
+ const gchar *first_property,
+ ...) G_GNUC_NULL_TERMINATED;
+EggAnimation* egg_object_animate_full (gpointer object,
+ EggAnimationMode mode,
+ guint duration_msec,
+ GdkFrameClock *frame_clock,
+ GDestroyNotify notify,
+ gpointer notify_data,
+ const gchar *first_property,
+ ...) G_GNUC_NULL_TERMINATED;
+
+G_END_DECLS \ No newline at end of file
diff --git a/src/animation/egg-frame-source.c b/src/animation/egg-frame-source.c
new file mode 100644
index 0000000..5672a05
--- /dev/null
+++ b/src/animation/egg-frame-source.c
@@ -0,0 +1,130 @@
+/* egg-frame-source.c
+ *
+ * Copyright (C) 2010-2016 Christian Hergert <christian@hergert.me>
+ *
+ * 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 General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "animation/egg-frame-source.h"
+
+typedef struct
+{
+ GSource parent;
+ guint fps;
+ guint frame_count;
+ gint64 start_time;
+} EggFrameSource;
+
+static gboolean
+egg_frame_source_prepare (GSource *source,
+ gint *timeout_)
+{
+ EggFrameSource *fsource = (EggFrameSource *)(gpointer)source;
+ gint64 current_time;
+ guint elapsed_time;
+ guint new_frame_num;
+ guint frame_time;
+
+ current_time = g_source_get_time(source) / 1000;
+ elapsed_time = current_time - fsource->start_time;
+ new_frame_num = elapsed_time * fsource->fps / 1000;
+
+ /* If time has gone backwards or the time since the last frame is
+ * greater than the two frames worth then reset the time and do a
+ * frame now */
+ if (new_frame_num < fsource->frame_count ||
+ new_frame_num - fsource->frame_count > 2) {
+ /* Get the frame time rounded up to the nearest ms */
+ frame_time = (1000 + fsource->fps - 1) / fsource->fps;
+
+ /* Reset the start time */
+ fsource->start_time = current_time;
+
+ /* Move the start time as if one whole frame has elapsed */
+ fsource->start_time -= frame_time;
+ fsource->frame_count = 0;
+ *timeout_ = 0;
+ return TRUE;
+ } else if (new_frame_num > fsource->frame_count) {
+ *timeout_ = 0;
+ return TRUE;
+ } else {
+ *timeout_ = (fsource->frame_count + 1) * 1000 / fsource->fps - elapsed_time;
+ return FALSE;
+ }
+}
+
+static gboolean
+egg_frame_source_check (GSource *source)
+{
+ gint timeout_;
+ return egg_frame_source_prepare(source, &timeout_);
+}
+
+static gboolean
+egg_frame_source_dispatch (GSource *source,
+ GSourceFunc source_func,
+ gpointer user_data)
+{
+ EggFrameSource *fsource = (EggFrameSource *)(gpointer)source;
+ gboolean ret;
+
+ if ((ret = source_func(user_data)))
+ fsource->frame_count++;
+ return ret;
+}
+
+static GSourceFuncs source_funcs = {
+ egg_frame_source_prepare,
+ egg_frame_source_check,
+ egg_frame_source_dispatch,
+};
+
+/**
+ * egg_frame_source_add:
+ * @frames_per_sec: (in): Target frames per second.
+ * @callback: (in) (scope notified): A #GSourceFunc to execute.
+ * @user_data: (in): User data for @callback.
+ *
+ * Creates a new frame source that will execute when the timeout interval
+ * for the source has elapsed. The timing will try to synchronize based
+ * on the end time of the animation.
+ *
+ * Returns: A source id that can be removed with g_source_remove().
+ */
+guint
+egg_frame_source_add (guint frames_per_sec,
+ GSourceFunc callback,
+ gpointer user_data)
+{
+ EggFrameSource *fsource;
+ GSource *source;
+ guint ret;
+
+ g_return_val_if_fail (frames_per_sec > 0, 0);
+ g_return_val_if_fail (frames_per_sec <= 120, 0);
+
+ source = g_source_new(&source_funcs, sizeof(EggFrameSource));
+ fsource = (EggFrameSource *)(gpointer)source;
+ fsource->fps = frames_per_sec;
+ fsource->frame_count = 0;
+ fsource->start_time = g_get_monotonic_time() / 1000;
+ g_source_set_callback(source, callback, user_data, NULL);
+ g_source_set_name(source, "EggFrameSource");
+
+ ret = g_source_attach(source, NULL);
+ g_source_unref(source);
+
+ return ret;
+}
diff --git a/src/animation/egg-frame-source.h b/src/animation/egg-frame-source.h
new file mode 100644
index 0000000..376d545
--- /dev/null
+++ b/src/animation/egg-frame-source.h
@@ -0,0 +1,29 @@
+/* egg-frame-source.h
+ *
+ * Copyright (C) 2010-2016 Christian Hergert <christian@hergert.me>
+ *
+ * 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 General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+guint egg_frame_source_add (guint frames_per_sec,
+ GSourceFunc callback,
+ gpointer user_data);
+
+G_END_DECLS \ No newline at end of file
diff --git a/src/animation/ide-box-theatric.c b/src/animation/ide-box-theatric.c
new file mode 100644
index 0000000..1e882ea
--- /dev/null
+++ b/src/animation/ide-box-theatric.c
@@ -0,0 +1,417 @@
+/* ide-box-theatric.c
+ *
+ * Copyright (C) 2014 Christian Hergert <christian@hergert.me>
+ *
+ * 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/>.
+ */
+
+#define G_LOG_DOMAIN "ide-box-theatric"
+
+#include <animation/egg-animation.h>
+#include <glib/gi18n.h>
+
+#include "animation/ide-box-theatric.h"
+#include "animation/ide-cairo.h"
+
+struct _IdeBoxTheatric
+{
+ GObject parent_instance;
+
+ GtkWidget *target;
+ GtkWidget *toplevel;
+
+ GIcon *icon;
+ cairo_surface_t *icon_surface;
+ guint icon_surface_size;
+
+ GdkRectangle area;
+ GdkRectangle last_area;
+ GdkRGBA background_rgba;
+ gdouble alpha;
+
+ gulong draw_handler;
+
+ guint background_set : 1;
+ guint pixbuf_failed : 1;
+};
+
+enum {
+ PROP_0,
+ PROP_ALPHA,
+ PROP_BACKGROUND,
+ PROP_HEIGHT,
+ PROP_ICON,
+ PROP_TARGET,
+ PROP_WIDTH,
+ PROP_X,
+ PROP_Y,
+ LAST_PROP
+};
+
+G_DEFINE_TYPE (IdeBoxTheatric, ide_box_theatric, G_TYPE_OBJECT)
+
+static GParamSpec *properties[LAST_PROP];
+
+static void
+get_toplevel_rect (IdeBoxTheatric *theatric,
+ GdkRectangle *area)
+{
+ gtk_widget_translate_coordinates (theatric->target, theatric->toplevel,
+ theatric->area.x, theatric->area.y,
+ &area->x, &area->y);
+
+ area->width = theatric->area.width;
+ area->height = theatric->area.height;
+}
+
+static gboolean
+on_toplevel_draw (GtkWidget *widget,
+ cairo_t *cr,
+ IdeBoxTheatric *self)
+{
+ GdkRectangle area;
+
+ g_assert (IDE_IS_BOX_THEATRIC (self));
+
+ get_toplevel_rect (self, &area);
+
+#if 0
+ g_print ("Drawing on %s at %d,%d %dx%d\n",
+ g_type_name (G_TYPE_FROM_INSTANCE (widget)),
+ area.x, area.y, area.width, area.height);
+#endif
+
+ if (self->background_set)
+ {
+ GdkRGBA bg;
+
+ bg = self->background_rgba;
+ bg.alpha = self->alpha;
+
+ ide_cairo_rounded_rectangle (cr, &area, 3, 3);
+ gdk_cairo_set_source_rgba (cr, &bg);
+ cairo_fill (cr);
+ }
+
+ /* Load an icon if necessary and cache the surface */
+ if (self->icon != NULL && self->icon_surface == NULL && !self->pixbuf_failed)
+ {
+ g_autoptr(GtkIconInfo) icon_info = NULL;
+ GtkIconTheme *icon_theme;
+ gint width;
+
+ width = area.width * 4;
+ icon_theme = gtk_icon_theme_get_default ();
+ icon_info = gtk_icon_theme_lookup_by_gicon (icon_theme,
+ self->icon,
+ width,
+ GTK_ICON_LOOKUP_FORCE_SIZE);
+
+ if (icon_info != NULL)
+ {
+ GdkWindow *window = gtk_widget_get_window (widget);
+ g_autoptr(GdkPixbuf) pixbuf = NULL;
+ GtkStyleContext *context;
+
+ context = gtk_widget_get_style_context (self->target);
+ pixbuf = gtk_icon_info_load_symbolic_for_context (icon_info, context, NULL, NULL);
+
+ if (pixbuf != NULL)
+ {
+ self->icon_surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, 0, window);
+ self->icon_surface_size = width;
+ self->pixbuf_failed = FALSE;
+ }
+ else
+ {
+ self->pixbuf_failed = TRUE;
+ }
+ }
+ }
+
+ if (self->icon_surface != NULL)
+ {
+ cairo_translate (cr, area.x, area.y);
+ cairo_rectangle (cr, 0, 0, area.width, area.height);
+ cairo_scale (cr,
+ area.width / (gdouble)self->icon_surface_size,
+ area.height / (gdouble)self->icon_surface_size);
+ cairo_set_source_surface (cr, self->icon_surface, 0, 0);
+ cairo_paint_with_alpha (cr, self->alpha);
+ }
+
+ self->last_area = area;
+
+ return FALSE;
+}
+
+static void
+ide_box_theatric_notify (GObject *object,
+ GParamSpec *pspec)
+{
+ IdeBoxTheatric *self = IDE_BOX_THEATRIC (object);
+
+ if (G_OBJECT_CLASS (ide_box_theatric_parent_class)->notify)
+ G_OBJECT_CLASS (ide_box_theatric_parent_class)->notify (object, pspec);
+
+ if (self->target && self->toplevel)
+ {
+ GdkWindow *window;
+ GdkRectangle area;
+
+ get_toplevel_rect (IDE_BOX_THEATRIC (object), &area);
+
+#if 0
+ g_print (" Invalidate : %d,%d %dx%d\n",
+ area.x, area.y, area.width, area.height);
+#endif
+
+ window = gtk_widget_get_window (self->toplevel);
+
+ if (window != NULL)
+ {
+ gdk_window_invalidate_rect (window, &self->last_area, TRUE);
+ gdk_window_invalidate_rect (window, &area, TRUE);
+ }
+ }
+}
+
+static void
+ide_box_theatric_dispose (GObject *object)
+{
+ IdeBoxTheatric *self = IDE_BOX_THEATRIC (object);
+
+ if (self->target)
+ {
+ if (self->toplevel)
+ g_clear_signal_handler (&self->draw_handler, self->toplevel);
+ g_object_remove_weak_pointer (G_OBJECT (self->target),
+ (gpointer *) &self->target);
+ self->target = NULL;
+ }
+
+ g_clear_pointer (&self->icon_surface, cairo_surface_destroy);
+ g_clear_object (&self->icon);
+
+ G_OBJECT_CLASS (ide_box_theatric_parent_class)->dispose (object);
+}
+
+static void
+ide_box_theatric_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeBoxTheatric *theatric = IDE_BOX_THEATRIC (object);
+
+ switch (prop_id)
+ {
+ case PROP_ALPHA:
+ g_value_set_double (value, theatric->alpha);
+ break;
+
+ case PROP_BACKGROUND:
+ g_value_take_string (value,
+ gdk_rgba_to_string (&theatric->background_rgba));
+ break;
+
+ case PROP_HEIGHT:
+ g_value_set_int (value, theatric->area.height);
+ break;
+
+ case PROP_ICON:
+ g_value_set_object (value, theatric->icon);
+ break;
+
+ case PROP_TARGET:
+ g_value_set_object (value, theatric->target);
+ break;
+
+ case PROP_WIDTH:
+ g_value_set_int (value, theatric->area.width);
+ break;
+
+ case PROP_X:
+ g_value_set_int (value, theatric->area.x);
+ break;
+
+ case PROP_Y:
+ g_value_set_int (value, theatric->area.y);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_box_theatric_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeBoxTheatric *theatric = IDE_BOX_THEATRIC (object);
+
+ switch (prop_id)
+ {
+ case PROP_ALPHA:
+ theatric->alpha = g_value_get_double (value);
+ break;
+
+ case PROP_BACKGROUND:
+ {
+ const gchar *str = g_value_get_string (value);
+
+ if (str == NULL)
+ {
+ gdk_rgba_parse (&theatric->background_rgba, "#000000");
+ theatric->background_rgba.alpha = 0;
+ theatric->background_set = FALSE;
+ }
+ else
+ {
+ gdk_rgba_parse (&theatric->background_rgba, str);
+ theatric->background_set = TRUE;
+ }
+ }
+ break;
+
+ case PROP_HEIGHT:
+ theatric->area.height = g_value_get_int (value);
+ break;
+
+ case PROP_ICON:
+ g_clear_pointer (&theatric->icon_surface, cairo_surface_destroy);
+ g_clear_object (&theatric->icon);
+ theatric->icon = g_value_dup_object (value);
+ theatric->pixbuf_failed = FALSE;
+ break;
+
+ case PROP_TARGET:
+ theatric->target = g_value_get_object (value);
+ theatric->toplevel = gtk_widget_get_toplevel (theatric->target);
+ g_object_add_weak_pointer (G_OBJECT (theatric->target),
+ (gpointer *) &theatric->target);
+ theatric->draw_handler =
+ g_signal_connect_after (theatric->toplevel,
+ "draw",
+ G_CALLBACK (on_toplevel_draw),
+ theatric);
+ break;
+
+ case PROP_WIDTH:
+ theatric->area.width = g_value_get_int (value);
+ break;
+
+ case PROP_X:
+ theatric->area.x = g_value_get_int (value);
+ break;
+
+ case PROP_Y:
+ theatric->area.y = g_value_get_int (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+
+ g_object_notify_by_pspec (object, pspec);
+}
+
+static void
+ide_box_theatric_class_init (IdeBoxTheatricClass *klass)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->dispose = ide_box_theatric_dispose;
+ object_class->notify = ide_box_theatric_notify;
+ object_class->get_property = ide_box_theatric_get_property;
+ object_class->set_property = ide_box_theatric_set_property;
+
+ properties[PROP_ALPHA] =
+ g_param_spec_double ("alpha",
+ "Alpha",
+ "Alpha",
+ 0.0,
+ 1.0,
+ 1.0,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties[PROP_BACKGROUND] =
+ g_param_spec_string ("background",
+ "background",
+ "background",
+ "#000000",
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties[PROP_HEIGHT] =
+ g_param_spec_int ("height",
+ "height",
+ "height",
+ 0,
+ G_MAXINT,
+ 0,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_ICON] =
+ g_param_spec_object ("icon",
+ "Icon",
+ "The GIcon to render over the background",
+ G_TYPE_ICON,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties[PROP_TARGET] =
+ g_param_spec_object ("target",
+ "Target",
+ "Target",
+ GTK_TYPE_WIDGET,
+ (G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ properties[PROP_WIDTH] =
+ g_param_spec_int ("width",
+ "width",
+ "width",
+ 0,
+ G_MAXINT,
+ 0,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties[PROP_X] =
+ g_param_spec_int ("x",
+ "x",
+ "x",
+ G_MININT,
+ G_MAXINT,
+ 0,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties[PROP_Y] =
+ g_param_spec_int ("y",
+ "y",
+ "y",
+ G_MININT,
+ G_MAXINT,
+ 0,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+ide_box_theatric_init (IdeBoxTheatric *theatric)
+{
+}
diff --git a/src/animation/ide-box-theatric.h b/src/animation/ide-box-theatric.h
new file mode 100644
index 0000000..9eb81c1
--- /dev/null
+++ b/src/animation/ide-box-theatric.h
@@ -0,0 +1,30 @@
+/* ide-box-theatric.h
+ *
+ * Copyright (C) 2014 Christian Hergert <christian@hergert.me>
+ *
+ * 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 <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_BOX_THEATRIC (ide_box_theatric_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeBoxTheatric, ide_box_theatric,
+ IDE, BOX_THEATRIC, GObject)
+
+G_END_DECLS \ No newline at end of file
diff --git a/src/animation/ide-cairo.c b/src/animation/ide-cairo.c
new file mode 100644
index 0000000..a4fba52
--- /dev/null
+++ b/src/animation/ide-cairo.c
@@ -0,0 +1,84 @@
+/* ide-cairo.c
+ *
+ * Copyright (C) 2014 Christian Hergert <christian@hergert.me>
+ *
+ * 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 General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ide-cairo.h"
+
+cairo_region_t *
+ide_cairo_region_create_from_clip_extents (cairo_t *cr)
+{
+ cairo_rectangle_int_t crect;
+ GdkRectangle rect;
+
+ g_return_val_if_fail (cr, NULL);
+
+ gdk_cairo_get_clip_rectangle (cr, &rect);
+ crect.x = rect.x;
+ crect.y = rect.y;
+ crect.width = rect.width;
+ crect.height = rect.height;
+
+ return cairo_region_create_rectangle (&crect);
+}
+
+void
+ide_cairo_rounded_rectangle (cairo_t *cr,
+ const GdkRectangle *rect,
+ gint x_radius,
+ gint y_radius)
+{
+ gint x;
+ gint y;
+ gint width;
+ gint height;
+ gint x1, x2;
+ gint y1, y2;
+ gint xr1, xr2;
+ gint yr1, yr2;
+
+ g_return_if_fail (cr);
+ g_return_if_fail (rect);
+
+ x = rect->x;
+ y = rect->y;
+ width = rect->width;
+ height = rect->height;
+
+ x1 = x;
+ x2 = x1 + width;
+ y1 = y;
+ y2 = y1 + height;
+
+ x_radius = MIN (x_radius, width / 2.0);
+ y_radius = MIN (y_radius, width / 2.0);
+
+ xr1 = x_radius;
+ xr2 = x_radius / 2.0;
+ yr1 = y_radius;
+ yr2 = y_radius / 2.0;
+
+ cairo_move_to (cr, x1 + xr1, y1);
+ cairo_line_to (cr, x2 - xr1, y1);
+ cairo_curve_to (cr, x2 - xr2, y1, x2, y1 + yr2, x2, y1 + yr1);
+ cairo_line_to (cr, x2, y2 - yr1);
+ cairo_curve_to (cr, x2, y2 - yr2, x2 - xr2, y2, x2 - xr1, y2);
+ cairo_line_to (cr, x1 + xr1, y2);
+ cairo_curve_to (cr, x1 + xr2, y2, x1, y2 - yr2, x1, y2 - yr1);
+ cairo_line_to (cr, x1, y1 + yr1);
+ cairo_curve_to (cr, x1, y1 + yr2, x1 + xr2, y1, x1 + xr1, y1);
+ cairo_close_path (cr);
+}
diff --git a/src/animation/ide-cairo.h b/src/animation/ide-cairo.h
new file mode 100644
index 0000000..5d3bb29
--- /dev/null
+++ b/src/animation/ide-cairo.h
@@ -0,0 +1,65 @@
+/* ide-cairo.h
+ *
+ * Copyright (C) 2014 Christian Hergert <christian@hergert.me>
+ *
+ * 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 General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+cairo_region_t *ide_cairo_region_create_from_clip_extents (cairo_t *cr);
+void ide_cairo_rounded_rectangle (cairo_t *cr,
+ const GdkRectangle *rect,
+ gint x_radius,
+ gint y_radius);
+
+static inline gboolean
+_ide_cairo_rectangle_x2 (const cairo_rectangle_int_t *rect)
+{
+ return rect->x + rect->width;
+}
+
+static inline gboolean
+_ide_cairo_rectangle_y2 (const cairo_rectangle_int_t *rect)
+{
+ return rect->y + rect->height;
+}
+
+static inline gboolean
+_ide_cairo_rectangle_center (const cairo_rectangle_int_t *rect)
+{
+ return rect->x + (rect->width/2);
+}
+
+static inline gboolean
+_ide_cairo_rectangle_middle (const cairo_rectangle_int_t *rect)
+{
+ return rect->y + (rect->height/2);
+}
+
+static inline cairo_bool_t
+_ide_cairo_rectangle_contains_rectangle (const cairo_rectangle_int_t *a,
+ const cairo_rectangle_int_t *b)
+{
+ return (a->x <= b->x &&
+ a->x + (int) a->width >= b->x + (int) b->width &&
+ a->y <= b->y &&
+ a->y + (int) a->height >= b->y + (int) b->height);
+}
+
+G_END_DECLS \ No newline at end of file
diff --git a/src/check-nautilus b/src/check-nautilus
new file mode 100755
index 0000000..c58ceb7
--- /dev/null
+++ b/src/check-nautilus
@@ -0,0 +1,2 @@
+#!/bin/sh
+./nautilus --check --g-fatal-warnings
diff --git a/src/gtk/gtk-code-generator.sh b/src/gtk/gtk-code-generator.sh
new file mode 100755
index 0000000..147efb3
--- /dev/null
+++ b/src/gtk/gtk-code-generator.sh
@@ -0,0 +1,93 @@
+#!/bin/sh
+
+# Fetch the GtkPlacesView files but rename the symbols to avoid symbol clashes
+# when using the file chooser inside nautilus i.e. when activating the "move to"
+# action.
+# Also remove/add the neccesary bits to make it work inside nautilus
+
+URL=https://gitlab.gnome.org/GNOME/gtk/raw/gtk-3-24/gtk/
+URLUI=https://gitlab.gnome.org/GNOME/gtk/raw/gtk-3-24/gtk/ui/
+SUFIX=?h=gtk-3-24
+
+# Since comments are not allowed inside the sed line, this is what it will do
+# by order:
+# type substitution
+# remove marshalers
+# add external localization library after the always there config.h
+# and remove the gtk internal P_ and I_ localization, we don't actually
+# want localization of this in nautilus
+# include gtk.h library after the always there config.h
+# and remove all the other types that get included by the general gtk.h
+# remove the error when including gtk.h
+# load nautilus resources, not gtk resources
+
+update_file () {
+ _source="$1"
+ _dest="$2"
+
+ curl "${_source}" | sed \
+ -e 's/gtkplacesview/nautilusgtkplacesview/g' \
+ -e 's/gtk_places_view/nautilus_gtk_places_view/g' \
+ -e 's/GtkPlacesView/NautilusGtkPlacesView/g' \
+ -e 's/GTK_PLACES_VIEW/NAUTILUS_GTK_PLACES_VIEW/g' \
+ -e 's/GTK_TYPE_PLACES_VIEW/NAUTILUS_TYPE_GTK_PLACES_VIEW/g' \
+ -e 's/GTK_IS_PLACES_VIEW/NAUTILUS_IS_GTK_PLACES_VIEW/g' \
+ -e 's/_gtk_marshal_VOID__STRING_STRING/NULL/g' \
+ -e '/gtkmarshalers.h/d' \
+ -e '/g_signal_set_va_marshaller/,+2d' \
+ -e 's/_gtk_marshal_VOID__OBJECT_FLAGS/NULL/g' \
+ -e '/"config.h"/a #include <glib\/gi18n.h>' \
+ -e "s/P_(\(.*\))/\1/" \
+ -e "s/I_(\(.*\))/\1/" \
+ -e '/"config.h"/a #include <gtk\/gtk.h>' \
+ -e '/gtktypebuiltins.h/d' \
+ -e '/gtkplacessidebar.h/d' \
+ -e '/gtkintl.h/d' \
+ -e '/gtkbox.h/d' \
+ -e '/#error/d' \
+ -e 's/gtk\/libgtk/gnome\/nautilus\/gtk/g' \
+ > "${_dest}"
+}
+
+update_file "${URL}/gtkplacesview.c${SUFIX}" "nautilusgtkplacesview.c"
+update_file "${URL}/gtkplacesviewprivate.h${SUFIX}" "nautilusgtkplacesviewprivate.h"
+update_file "${URLUI}/gtkplacesview.ui${SUFIX}" "nautilusgtkplacesview.ui"
+
+# Since comments are not allowed inside the sed line, this is what it will do
+# by order:
+# type substitution
+# use the correct prefixes for type definition
+# add external localization library after the always there config.h
+# and remove the gtk internal P_ and I_ localization, we don't actually
+# want localization of this in nautilus
+# include gtk.h library after the always there config.h
+# and remove all the other types that get included by the general gtk.h
+# remove the error when including gtk.h
+# load nautilus resources, not gtk resources
+update_file () {
+ _source="$1"
+ _dest="$2"
+
+ curl "${_source}" | sed \
+ -e 's/gtkplacesviewrow/nautilusgtkplacesviewrow/g' \
+ -e 's/gtk_places_view_row/nautilus_gtk_places_view_row/g' \
+ -e 's/GtkPlacesViewRow/NautilusGtkPlacesViewRow/g' \
+ -e 's/GTK_PLACES_VIEW_ROW/NAUTILUS_GTK_PLACES_VIEW_ROW/g' \
+ -e 's/GTK_TYPE_PLACES_VIEW_ROW/NAUTILUS_TYPE_GTK_PLACES_VIEW_ROW/g' \
+ -e 's/GTK_IS_PLACES_VIEW_ROW/NAUTILUS_IS_GTK_PLACES_VIEW_ROW/g' \
+ -e 's/G_DECLARE_FINAL_TYPE (NautilusGtkPlacesViewRow, nautilus_gtk_places_view_row, GTK, PLACES_VIEW_ROW, GtkListBoxRow/ G_DECLARE_FINAL_TYPE (NautilusGtkPlacesViewRow, nautilus_gtk_places_view_row, NAUTILUS, GTK_PLACES_VIEW_ROW, GtkListBoxRow/g' \
+ -e '/"config.h"/a #include <glib\/gi18n.h>' \
+ -e "s/P_(\(.*\))/\1/" \
+ -e "s/I_(\(.*\))/\1/" \
+ -e '/"config.h"/a #include <gtk\/gtk.h>' \
+ -e '/gtksizegroup.h/d' \
+ -e '/gtkwidget.h/d' \
+ -e '/gtklistbox.h/d' \
+ -e '/#error /d' \
+ -e 's/gtk\/libgtk/gnome\/nautilus\/gtk/g' \
+ > "${_dest}"
+}
+
+update_file "${URL}/gtkplacesviewrow.c${SUFIX}" "nautilusgtkplacesviewrow.c"
+update_file "${URL}/gtkplacesviewrowprivate.h${SUFIX}" "nautilusgtkplacesviewrowprivate.h"
+update_file "${URLUI}/gtkplacesviewrow.ui${SUFIX}" "nautilusgtkplacesviewrow.ui"
diff --git a/src/gtk/nautilusgtkplacesview.c b/src/gtk/nautilusgtkplacesview.c
new file mode 100644
index 0000000..af05540
--- /dev/null
+++ b/src/gtk/nautilusgtkplacesview.c
@@ -0,0 +1,2656 @@
+/* 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 <gio/gio.h>
+#include <gio/gvfs.h>
+#include <gtk/gtk.h>
+
+#include "nautilusgtkplacesviewprivate.h"
+#include "nautilusgtkplacesviewrowprivate.h"
+
+/**
+ * SECTION:nautilusgtkplacesview
+ * @Short_description: Widget that displays persistent drives and manages mounted networks
+ * @Title: NautilusGtkPlacesView
+ * @See_also: #GtkFileChooser
+ *
+ * #NautilusGtkPlacesView is a stock 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 _NautilusGtkPlacesViewPrivate
+{
+ GVolumeMonitor *volume_monitor;
+ GtkPlacesOpenFlags open_flags;
+ GtkPlacesOpenFlags current_open_flags;
+
+ GFile *server_list_file;
+ GFileMonitor *server_list_monitor;
+ GFileMonitor *network_monitor;
+
+ GCancellable *cancellable;
+
+ gchar *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;
+
+ guint local_only : 1;
+ 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 gboolean on_button_press_event (NautilusGtkPlacesViewRow *row,
+ GdkEventButton *event);
+
+static void on_eject_button_clicked (GtkWidget *widget,
+ NautilusGtkPlacesViewRow *row);
+
+static gboolean on_row_popup_menu (NautilusGtkPlacesViewRow *row);
+
+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_WITH_PRIVATE (NautilusGtkPlacesView, nautilus_gtk_places_view, GTK_TYPE_BOX)
+
+/* NautilusGtkPlacesView properties & signals */
+enum {
+ PROP_0,
+ PROP_LOCAL_ONLY,
+ PROP_OPEN_FLAGS,
+ PROP_FETCHING_NETWORKS,
+ PROP_LOADING,
+ LAST_PROP
+};
+
+enum {
+ OPEN_LOCATION,
+ SHOW_ERROR_MESSAGE,
+ LAST_SIGNAL
+};
+
+const gchar *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,
+ GtkPlacesOpenFlags open_flags)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+
+ priv = nautilus_gtk_places_view_get_instance_private (view);
+
+ if ((open_flags & priv->open_flags) == 0)
+ open_flags = 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,
+ gchar *primary_message,
+ gchar *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)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+ GBookmarkFile *bookmarks;
+ GError *error = NULL;
+ gchar *datadir;
+ gchar *filename;
+
+ priv = nautilus_gtk_places_view_get_instance_private (view);
+ bookmarks = g_bookmark_file_new ();
+ datadir = g_build_filename (g_get_user_config_dir (), "gtk-3.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 (!priv->server_list_monitor)
+ {
+ priv->server_list_file = g_file_new_for_path (filename);
+
+ if (priv->server_list_file)
+ {
+ priv->server_list_monitor = g_file_monitor_file (priv->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 (priv->server_list_monitor,
+ "changed",
+ G_CALLBACK (server_file_changed_cb),
+ view);
+ }
+ }
+
+ g_clear_object (&priv->server_list_file);
+ }
+
+ g_free (datadir);
+ g_free (filename);
+
+ return bookmarks;
+}
+
+static void
+server_list_save (GBookmarkFile *bookmarks)
+{
+ gchar *filename;
+
+ filename = g_build_filename (g_get_user_config_dir (), "gtk-3.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;
+ gchar *title;
+ gchar *uri;
+
+ 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);
+ g_bookmark_file_set_visited (bookmarks, uri, -1);
+ 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 gchar *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_get_toplevel (widget);
+ if (!gtk_widget_is_toplevel (toplevel))
+ return NULL;
+ else
+ return GTK_WINDOW (toplevel);
+}
+
+static void
+set_busy_cursor (NautilusGtkPlacesView *view,
+ gboolean busy)
+{
+ GtkWidget *widget;
+ GtkWindow *toplevel;
+ GdkDisplay *display;
+ GdkCursor *cursor;
+
+ toplevel = get_toplevel (GTK_WIDGET (view));
+ widget = GTK_WIDGET (toplevel);
+ if (!toplevel || !gtk_widget_get_realized (widget))
+ return;
+
+ display = gtk_widget_get_display (widget);
+
+ if (busy)
+ cursor = gdk_cursor_new_from_name (display, "progress");
+ else
+ cursor = NULL;
+
+ gdk_window_set_cursor (gtk_widget_get_window (widget), cursor);
+ gdk_display_flush (display);
+
+ if (cursor)
+ g_object_unref (cursor);
+}
+
+/* Activates the given row, with the given flags as parameter */
+static void
+activate_row (NautilusGtkPlacesView *view,
+ NautilusGtkPlacesViewRow *row,
+ GtkPlacesOpenFlags flags)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+ GVolume *volume;
+ GMount *mount;
+ GFile *file;
+
+ priv = nautilus_gtk_places_view_get_instance_private (view);
+ 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.
+ */
+ priv->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_destroy (GtkWidget *widget)
+{
+ NautilusGtkPlacesView *self = NAUTILUS_GTK_PLACES_VIEW (widget);
+ NautilusGtkPlacesViewPrivate *priv = nautilus_gtk_places_view_get_instance_private (self);
+
+ priv->destroyed = 1;
+
+ g_signal_handlers_disconnect_by_func (priv->volume_monitor, update_places, widget);
+
+ if (priv->network_monitor)
+ g_signal_handlers_disconnect_by_func (priv->network_monitor, update_places, widget);
+
+ if (priv->server_list_monitor)
+ g_signal_handlers_disconnect_by_func (priv->server_list_monitor, server_file_changed_cb, widget);
+
+ g_cancellable_cancel (priv->cancellable);
+ g_cancellable_cancel (priv->networks_fetching_cancellable);
+
+ GTK_WIDGET_CLASS (nautilus_gtk_places_view_parent_class)->destroy (widget);
+}
+
+static void
+nautilus_gtk_places_view_finalize (GObject *object)
+{
+ NautilusGtkPlacesView *self = (NautilusGtkPlacesView *)object;
+ NautilusGtkPlacesViewPrivate *priv = nautilus_gtk_places_view_get_instance_private (self);
+
+ if (priv->entry_pulse_timeout_id > 0)
+ g_source_remove (priv->entry_pulse_timeout_id);
+
+ g_clear_pointer (&priv->search_query, g_free);
+ g_clear_object (&priv->server_list_file);
+ g_clear_object (&priv->server_list_monitor);
+ g_clear_object (&priv->volume_monitor);
+ g_clear_object (&priv->network_monitor);
+ g_clear_object (&priv->cancellable);
+ g_clear_object (&priv->networks_fetching_cancellable);
+ g_clear_object (&priv->path_size_group);
+ g_clear_object (&priv->space_size_group);
+
+ G_OBJECT_CLASS (nautilus_gtk_places_view_parent_class)->finalize (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_LOCAL_ONLY:
+ g_value_set_boolean (value, nautilus_gtk_places_view_get_local_only (self));
+ break;
+
+ case PROP_LOADING:
+ g_value_set_boolean (value, nautilus_gtk_places_view_get_loading (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_LOCAL_ONLY:
+ nautilus_gtk_places_view_set_local_only (self, g_value_get_boolean (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;
+ gchar *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
+{
+ gchar *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)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+ GBookmarkFile *server_list;
+ GList *children;
+ gchar **uris;
+ gsize num_uris;
+ gint i;
+
+ priv = nautilus_gtk_places_view_get_instance_private (view);
+ 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 (priv->recent_servers_stack),
+ num_uris > 0 ? "list" : "empty");
+
+ if (!uris)
+ {
+ g_bookmark_file_free (server_list);
+ return;
+ }
+
+ /* clear previous items */
+ children = gtk_container_get_children (GTK_CONTAINER (priv->recent_servers_listbox));
+ g_list_free_full (children, (GDestroyNotify) gtk_widget_destroy);
+
+ gtk_list_store_clear (priv->completion_store);
+
+ for (i = 0; i < num_uris; i++)
+ {
+ RemoveServerData *data;
+ GtkTreeIter iter;
+ GtkWidget *row;
+ GtkWidget *grid;
+ GtkWidget *button;
+ GtkWidget *label;
+ gchar *name;
+ gchar *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 (priv->completion_store, &iter);
+ gtk_list_store_set (priv->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,
+ "border-width", 3,
+ 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_container_add (GTK_CONTAINER (grid), label);
+
+ /* 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_style_context_add_class (gtk_widget_get_style_context (label), "dim-label");
+ gtk_container_add (GTK_CONTAINER (grid), label);
+
+ /* remove button */
+ button = gtk_button_new_from_icon_name ("window-close-symbolic", GTK_ICON_SIZE_BUTTON);
+ gtk_widget_set_halign (button, GTK_ALIGN_END);
+ gtk_widget_set_valign (button, GTK_ALIGN_CENTER);
+ gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
+ gtk_style_context_add_class (gtk_widget_get_style_context (button), "sidebar-button");
+ gtk_grid_attach (GTK_GRID (grid), button, 1, 0, 1, 2);
+
+ gtk_container_add (GTK_CONTAINER (row), grid);
+ gtk_container_add (GTK_CONTAINER (priv->recent_servers_listbox), row);
+
+ /* 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);
+
+ gtk_widget_show_all (row);
+
+ g_free (name);
+ }
+
+ g_strfreev (uris);
+ g_bookmark_file_free (server_list);
+}
+
+static void
+update_view_mode (NautilusGtkPlacesView *view)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+ GList *children;
+ GList *l;
+ gboolean show_listbox;
+
+ priv = nautilus_gtk_places_view_get_instance_private (view);
+ show_listbox = FALSE;
+
+ /* drives */
+ children = gtk_container_get_children (GTK_CONTAINER (priv->listbox));
+
+ for (l = children; l; l = l->next)
+ {
+ /* GtkListBox filter rows by changing their GtkWidget::child-visible property */
+ if (gtk_widget_get_child_visible (l->data))
+ {
+ show_listbox = TRUE;
+ break;
+ }
+ }
+
+ g_list_free (children);
+
+ if (!show_listbox &&
+ priv->search_query &&
+ priv->search_query[0] != '\0')
+ {
+ gtk_stack_set_visible_child_name (GTK_STACK (priv->stack), "empty-search");
+ }
+ else
+ {
+ gtk_stack_set_visible_child_name (GTK_STACK (priv->stack), "browse");
+ }
+}
+
+static void
+insert_row (NautilusGtkPlacesView *view,
+ GtkWidget *row,
+ gboolean is_network)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+
+ priv = nautilus_gtk_places_view_get_instance_private (view);
+
+ g_object_set_data (G_OBJECT (row), "is-network", GINT_TO_POINTER (is_network));
+
+ g_signal_connect_swapped (nautilus_gtk_places_view_row_get_event_box (NAUTILUS_GTK_PLACES_VIEW_ROW (row)),
+ "button-press-event",
+ G_CALLBACK (on_button_press_event),
+ row);
+
+ g_signal_connect (row,
+ "popup-menu",
+ G_CALLBACK (on_row_popup_menu),
+ row);
+
+ 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), priv->path_size_group);
+ nautilus_gtk_places_view_row_set_space_size_group (NAUTILUS_GTK_PLACES_VIEW_ROW (row), priv->space_size_group);
+
+ gtk_container_add (GTK_CONTAINER (priv->listbox), row);
+}
+
+static void
+add_volume (NautilusGtkPlacesView *view,
+ GVolume *volume)
+{
+ gboolean is_network;
+ GMount *mount;
+ GFile *root;
+ GIcon *icon;
+ gchar *identifier;
+ gchar *name;
+ gchar *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;
+ gchar *name;
+ gchar *path;
+ gchar *uri;
+ gchar *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 gchar *display_name,
+ const gchar *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)
+{
+ GList *l;
+ NautilusGtkPlacesViewPrivate *priv;
+ GList *children;
+ gboolean has_network = FALSE;
+
+ priv = nautilus_gtk_places_view_get_instance_private (view);
+
+ children = gtk_container_get_children (GTK_CONTAINER (priv->listbox));
+ for (l = children; l != NULL; l = l->next)
+ {
+ if (GPOINTER_TO_INT (g_object_get_data (l->data, "is-network")) == TRUE &&
+ g_object_get_data (l->data, "is-placeholder") == NULL)
+ {
+ has_network = TRUE;
+ break;
+ }
+ }
+
+ g_list_free (children);
+
+ return has_network;
+}
+
+static void
+update_network_state (NautilusGtkPlacesView *view)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+
+ priv = nautilus_gtk_places_view_get_instance_private (view);
+
+ if (priv->network_placeholder == NULL)
+ {
+ priv->network_placeholder = gtk_list_box_row_new ();
+ priv->network_placeholder_label = gtk_label_new ("");
+ gtk_label_set_xalign (GTK_LABEL (priv->network_placeholder_label), 0.0);
+ gtk_widget_set_margin_start (priv->network_placeholder_label, 12);
+ gtk_widget_set_margin_end (priv->network_placeholder_label, 12);
+ gtk_widget_set_margin_top (priv->network_placeholder_label, 6);
+ gtk_widget_set_margin_bottom (priv->network_placeholder_label, 6);
+ gtk_widget_set_hexpand (priv->network_placeholder_label, TRUE);
+ gtk_widget_set_sensitive (priv->network_placeholder, FALSE);
+ gtk_container_add (GTK_CONTAINER (priv->network_placeholder),
+ priv->network_placeholder_label);
+ g_object_set_data (G_OBJECT (priv->network_placeholder),
+ "is-network", GINT_TO_POINTER (TRUE));
+ /* mark the row as placeholder, so it always goes first */
+ g_object_set_data (G_OBJECT (priv->network_placeholder),
+ "is-placeholder", GINT_TO_POINTER (TRUE));
+ gtk_container_add (GTK_CONTAINER (priv->listbox), priv->network_placeholder);
+ }
+
+ 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_all (priv->network_placeholder);
+ gtk_label_set_text (GTK_LABEL (priv->network_placeholder_label),
+ _("Searching for network locations"));
+ }
+ }
+ else if (!has_networks (view))
+ {
+ gtk_widget_show_all (priv->network_placeholder);
+ gtk_label_set_text (GTK_LABEL (priv->network_placeholder_label),
+ _("No network locations found"));
+ }
+ else
+ {
+ gtk_widget_hide (priv->network_placeholder);
+ }
+}
+
+static void
+monitor_network (NautilusGtkPlacesView *self)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+ GFile *network_file;
+ GError *error;
+
+ priv = nautilus_gtk_places_view_get_instance_private (self);
+
+ if (priv->network_monitor)
+ return;
+
+ error = NULL;
+ network_file = g_file_new_for_uri ("network:///");
+ priv->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 (priv->network_monitor,
+ "changed",
+ G_CALLBACK (update_places),
+ self);
+}
+
+static void
+populate_networks (NautilusGtkPlacesView *view,
+ GFileEnumerator *enumerator,
+ GList *detected_networks)
+{
+ GList *l;
+ GFile *file;
+ GFile *activatable_file;
+ gchar *uri;
+ GFileType type;
+ GIcon *icon;
+ gchar *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)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+ NautilusGtkPlacesView *view;
+ GList *detected_networks;
+ GError *error;
+
+ view = NAUTILUS_GTK_PLACES_VIEW (user_data);
+ priv = nautilus_gtk_places_view_get_instance_private (view);
+ 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_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);
+ }
+
+ g_object_unref (view);
+
+ /* avoid to update widgets if we are already destroyed
+ (and got cancelled s a result of that) */
+ if (!priv->destroyed)
+ {
+ update_network_state (view);
+ monitor_network (view);
+ update_loading (view);
+ }
+}
+
+static void
+network_enumeration_finished (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+ 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 (NAUTILUS_GTK_PLACES_VIEW (user_data));
+ }
+ else
+ {
+ priv = nautilus_gtk_places_view_get_instance_private (NAUTILUS_GTK_PLACES_VIEW (user_data));
+ g_file_enumerator_next_files_async (enumerator,
+ G_MAXINT32,
+ G_PRIORITY_DEFAULT,
+ priv->networks_fetching_cancellable,
+ network_enumeration_next_files_finished,
+ user_data);
+ g_object_unref (enumerator);
+ }
+}
+
+static void
+fetch_networks (NautilusGtkPlacesView *view)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+ GFile *network_file;
+ const gchar * const *supported_uris;
+ gboolean found;
+
+ priv = nautilus_gtk_places_view_get_instance_private (view);
+ 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 (priv->networks_fetching_cancellable);
+ g_clear_object (&priv->networks_fetching_cancellable);
+ priv->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,
+ priv->networks_fetching_cancellable,
+ network_enumeration_finished,
+ view);
+
+ g_clear_object (&network_file);
+}
+
+static void
+update_places (NautilusGtkPlacesView *view)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+ GList *children;
+ GList *mounts;
+ GList *volumes;
+ GList *drives;
+ GList *l;
+ GIcon *icon;
+ GFile *file;
+
+ priv = nautilus_gtk_places_view_get_instance_private (view);
+
+ /* Clear all previously added items */
+ children = gtk_container_get_children (GTK_CONTAINER (priv->listbox));
+ g_list_free_full (children, (GDestroyNotify) gtk_widget_destroy);
+ priv->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");
+
+ add_file (view, file, icon, _("Computer"), "/", FALSE);
+
+ g_clear_object (&file);
+ g_clear_object (&icon);
+
+ /* Add currently connected drives */
+ drives = g_volume_monitor_get_connected_drives (priv->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 (priv->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 (priv->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)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+ NautilusGtkPlacesView *view;
+ gboolean should_show;
+ GError *error;
+ GFile *location;
+
+ location = G_FILE (source_file);
+ should_show = TRUE;
+ error = NULL;
+
+ view = NAUTILUS_GTK_PLACES_VIEW (user_data);
+
+ 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);
+ }
+
+ priv = nautilus_gtk_places_view_get_instance_private (view);
+
+ if (priv->destroyed) {
+ g_object_unref (view);
+ return;
+ }
+
+ priv->should_pulse_entry = FALSE;
+
+ /* Restore from Cancel to Connect */
+ gtk_button_set_label (GTK_BUTTON (priv->connect_button), _("Con_nect"));
+ gtk_widget_set_sensitive (priv->address_entry, TRUE);
+ priv->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_entry_set_text (GTK_ENTRY (priv->address_entry), "");
+
+ if (priv->should_open_location)
+ emit_open_location (view, location, priv->open_flags);
+ }
+
+ update_places (view);
+ g_object_unref (view);
+}
+
+static void
+volume_mount_ready_cb (GObject *source_volume,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+ NautilusGtkPlacesView *view;
+ 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);
+ }
+
+ view = NAUTILUS_GTK_PLACES_VIEW (user_data);
+ priv = nautilus_gtk_places_view_get_instance_private (view);
+
+ if (priv->destroyed)
+ {
+ g_object_unref(view);
+ return;
+ }
+
+ priv->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 (priv->should_open_location)
+ emit_open_location (NAUTILUS_GTK_PLACES_VIEW (user_data), root, priv->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;
+ NautilusGtkPlacesViewPrivate *priv;
+ 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);
+ }
+
+ priv = nautilus_gtk_places_view_get_instance_private (view);
+
+ if (priv->destroyed) {
+ g_object_unref (view);
+ return;
+ }
+
+ priv->unmounting_mount = FALSE;
+ update_loading (view);
+
+ g_object_unref (view);
+}
+
+static gboolean
+pulse_entry_cb (gpointer user_data)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+
+ priv = nautilus_gtk_places_view_get_instance_private (NAUTILUS_GTK_PLACES_VIEW (user_data));
+
+ if (priv->destroyed)
+ {
+ priv->entry_pulse_timeout_id = 0;
+
+ return G_SOURCE_REMOVE;
+ }
+ else if (priv->should_pulse_entry)
+ {
+ gtk_entry_progress_pulse (GTK_ENTRY (priv->address_entry));
+
+ return G_SOURCE_CONTINUE;
+ }
+ else
+ {
+ gtk_entry_set_progress_pulse_step (GTK_ENTRY (priv->address_entry), 0.0);
+ gtk_entry_set_progress_fraction (GTK_ENTRY (priv->address_entry), 0.0);
+ priv->entry_pulse_timeout_id = 0;
+
+ return G_SOURCE_REMOVE;
+ }
+}
+
+static void
+unmount_mount (NautilusGtkPlacesView *view,
+ GMount *mount)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+ GMountOperation *operation;
+ GtkWidget *toplevel;
+
+ priv = nautilus_gtk_places_view_get_instance_private (view);
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (view));
+
+ g_cancellable_cancel (priv->cancellable);
+ g_clear_object (&priv->cancellable);
+ priv->cancellable = g_cancellable_new ();
+
+ priv->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,
+ priv->cancellable,
+ unmount_ready_cb,
+ view);
+ g_object_unref (operation);
+}
+
+static void
+mount_server (NautilusGtkPlacesView *view,
+ GFile *location)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+ GMountOperation *operation;
+ GtkWidget *toplevel;
+
+ priv = nautilus_gtk_places_view_get_instance_private (view);
+
+ g_cancellable_cancel (priv->cancellable);
+ g_clear_object (&priv->cancellable);
+ /* User cliked when the operation was ongoing, so wanted to cancel it */
+ if (priv->connecting_to_server)
+ return;
+
+ priv->cancellable = g_cancellable_new ();
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (view));
+ operation = gtk_mount_operation_new (GTK_WINDOW (toplevel));
+
+ priv->should_pulse_entry = TRUE;
+ gtk_entry_set_progress_pulse_step (GTK_ENTRY (priv->address_entry), 0.1);
+ /* Allow to cancel the operation */
+ gtk_button_set_label (GTK_BUTTON (priv->connect_button), _("Cance_l"));
+ gtk_widget_set_sensitive (priv->address_entry, FALSE);
+ priv->connecting_to_server = TRUE;
+ update_loading (view);
+
+ if (priv->entry_pulse_timeout_id == 0)
+ priv->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,
+ priv->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)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+ GMountOperation *operation;
+ GtkWidget *toplevel;
+
+ priv = nautilus_gtk_places_view_get_instance_private (view);
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (view));
+ operation = gtk_mount_operation_new (GTK_WINDOW (toplevel));
+
+ g_cancellable_cancel (priv->cancellable);
+ g_clear_object (&priv->cancellable);
+ priv->cancellable = g_cancellable_new ();
+
+ priv->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,
+ priv->cancellable,
+ volume_mount_ready_cb,
+ view);
+
+ /* unref operation here - g_file_mount_enclosing_volume() does ref for itself */
+ g_object_unref (operation);
+}
+
+/* Callback used when the file list's popup menu is detached */
+static void
+popup_menu_detach_cb (GtkWidget *attach_widget,
+ GtkMenu *menu)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+
+ priv = nautilus_gtk_places_view_get_instance_private (NAUTILUS_GTK_PLACES_VIEW (attach_widget));
+ priv->popup_menu = NULL;
+}
+
+static void
+open_cb (GtkMenuItem *item,
+ NautilusGtkPlacesViewRow *row)
+{
+ NautilusGtkPlacesView *self;
+
+ self = NAUTILUS_GTK_PLACES_VIEW (gtk_widget_get_ancestor (GTK_WIDGET (row), NAUTILUS_TYPE_GTK_PLACES_VIEW));
+ activate_row (self, row, GTK_PLACES_OPEN_NORMAL);
+}
+
+static void
+open_in_new_tab_cb (GtkMenuItem *item,
+ NautilusGtkPlacesViewRow *row)
+{
+ NautilusGtkPlacesView *self;
+
+ self = NAUTILUS_GTK_PLACES_VIEW (gtk_widget_get_ancestor (GTK_WIDGET (row), NAUTILUS_TYPE_GTK_PLACES_VIEW));
+ activate_row (self, row, GTK_PLACES_OPEN_NEW_TAB);
+}
+
+static void
+open_in_new_window_cb (GtkMenuItem *item,
+ NautilusGtkPlacesViewRow *row)
+{
+ NautilusGtkPlacesView *self;
+
+ self = NAUTILUS_GTK_PLACES_VIEW (gtk_widget_get_ancestor (GTK_WIDGET (row), NAUTILUS_TYPE_GTK_PLACES_VIEW));
+ activate_row (self, row, GTK_PLACES_OPEN_NEW_WINDOW);
+}
+
+static void
+mount_cb (GtkMenuItem *item,
+ NautilusGtkPlacesViewRow *row)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+ GtkWidget *view;
+ GVolume *volume;
+
+ view = gtk_widget_get_ancestor (GTK_WIDGET (row), NAUTILUS_TYPE_GTK_PLACES_VIEW);
+ priv = nautilus_gtk_places_view_get_instance_private (NAUTILUS_GTK_PLACES_VIEW (view));
+ volume = nautilus_gtk_places_view_row_get_volume (row);
+
+ /*
+ * When the mount item is activated, it's expected that
+ * the volume only gets mounted, without opening it after
+ * the operation is complete.
+ */
+ priv->should_open_location = FALSE;
+
+ nautilus_gtk_places_view_row_set_busy (row, TRUE);
+ mount_volume (NAUTILUS_GTK_PLACES_VIEW (view), volume);
+}
+
+static void
+unmount_cb (GtkMenuItem *item,
+ NautilusGtkPlacesViewRow *row)
+{
+ GtkWidget *view;
+ GMount *mount;
+
+ view = gtk_widget_get_ancestor (GTK_WIDGET (row), NAUTILUS_TYPE_GTK_PLACES_VIEW);
+ mount = nautilus_gtk_places_view_row_get_mount (row);
+
+ nautilus_gtk_places_view_row_set_busy (row, TRUE);
+
+ unmount_mount (NAUTILUS_GTK_PLACES_VIEW (view), mount);
+}
+
+static void
+attach_protocol_row_to_grid (GtkGrid *grid,
+ const gchar *protocol_name,
+ const gchar *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 gchar* const *supported_protocols;
+
+ 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://");
+
+ if (g_strv_contains (supported_protocols, "ftp"))
+ /* Translators: do not translate ftp:// and ftps:// */
+ attach_protocol_row_to_grid (grid, _("File Transfer Protocol"), _("ftp:// or ftps://"));
+
+ if (g_strv_contains (supported_protocols, "nfs"))
+ attach_protocol_row_to_grid (grid, _("Network File System"), "nfs://");
+
+ if (g_strv_contains (supported_protocols, "smb"))
+ attach_protocol_row_to_grid (grid, _("Samba"), "smb://");
+
+ if (g_strv_contains (supported_protocols, "ssh"))
+ /* Translators: do not translate sftp:// and ssh:// */
+ attach_protocol_row_to_grid (grid, _("SSH File Transfer Protocol"), _("sftp:// or ssh://"));
+
+ if (g_strv_contains (supported_protocols, "dav"))
+ /* Translators: do not translate dav:// and davs:// */
+ attach_protocol_row_to_grid (grid, _("WebDAV"), _("dav:// or davs://"));
+
+ gtk_widget_show_all (GTK_WIDGET (grid));
+}
+
+/* Constructs the popup menu if needed */
+static void
+build_popup_menu (NautilusGtkPlacesView *view,
+ NautilusGtkPlacesViewRow *row)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+ GtkWidget *item;
+ GMount *mount;
+ GFile *file;
+ gboolean is_network;
+
+ priv = nautilus_gtk_places_view_get_instance_private (view);
+ mount = nautilus_gtk_places_view_row_get_mount (row);
+ file = nautilus_gtk_places_view_row_get_file (row);
+ is_network = nautilus_gtk_places_view_row_get_is_network (row);
+
+ priv->popup_menu = gtk_menu_new ();
+ gtk_style_context_add_class (gtk_widget_get_style_context (priv->popup_menu),
+ GTK_STYLE_CLASS_CONTEXT_MENU);
+
+ gtk_menu_attach_to_widget (GTK_MENU (priv->popup_menu),
+ GTK_WIDGET (view),
+ popup_menu_detach_cb);
+
+ /* Open item is always present */
+ item = gtk_menu_item_new_with_mnemonic (_("_Open"));
+ g_signal_connect (item,
+ "activate",
+ G_CALLBACK (open_cb),
+ row);
+ gtk_widget_show (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
+
+ if (priv->open_flags & GTK_PLACES_OPEN_NEW_TAB)
+ {
+ item = gtk_menu_item_new_with_mnemonic (_("Open in New _Tab"));
+ g_signal_connect (item,
+ "activate",
+ G_CALLBACK (open_in_new_tab_cb),
+ row);
+ gtk_widget_show (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
+ }
+
+ if (priv->open_flags & GTK_PLACES_OPEN_NEW_WINDOW)
+ {
+ item = gtk_menu_item_new_with_mnemonic (_("Open in New _Window"));
+ g_signal_connect (item,
+ "activate",
+ G_CALLBACK (open_in_new_window_cb),
+ row);
+ gtk_widget_show (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
+ }
+
+ /*
+ * The only item that contains a file up to now is the Computer
+ * item, which cannot be mounted or unmounted.
+ */
+ if (file)
+ return;
+
+ /* Separator */
+ item = gtk_separator_menu_item_new ();
+ gtk_widget_show (item);
+ gtk_menu_shell_insert (GTK_MENU_SHELL (priv->popup_menu), item, -1);
+
+ /* Mount/Unmount items */
+ if (mount)
+ {
+ item = gtk_menu_item_new_with_mnemonic (is_network ? _("_Disconnect") : _("_Unmount"));
+ g_signal_connect (item,
+ "activate",
+ G_CALLBACK (unmount_cb),
+ row);
+ gtk_widget_show (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
+ }
+ else
+ {
+ item = gtk_menu_item_new_with_mnemonic (is_network ? _("_Connect") : _("_Mount"));
+ g_signal_connect (item,
+ "activate",
+ G_CALLBACK (mount_cb),
+ row);
+ gtk_widget_show (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
+ }
+}
+
+static void
+popup_menu (NautilusGtkPlacesViewRow *row,
+ GdkEventButton *event)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+ GtkWidget *view;
+
+ view = gtk_widget_get_ancestor (GTK_WIDGET (row), NAUTILUS_TYPE_GTK_PLACES_VIEW);
+ priv = nautilus_gtk_places_view_get_instance_private (NAUTILUS_GTK_PLACES_VIEW (view));
+
+ g_clear_pointer (&priv->popup_menu, gtk_widget_destroy);
+
+ build_popup_menu (NAUTILUS_GTK_PLACES_VIEW (view), row);
+
+ gtk_menu_popup_at_pointer (GTK_MENU (priv->popup_menu), (GdkEvent *) event);
+}
+
+static gboolean
+on_row_popup_menu (NautilusGtkPlacesViewRow *row)
+{
+ popup_menu (row, NULL);
+ return TRUE;
+}
+
+static gboolean
+on_button_press_event (NautilusGtkPlacesViewRow *row,
+ GdkEventButton *event)
+{
+ if (row &&
+ gdk_event_triggers_context_menu ((GdkEvent*) event) &&
+ event->type == GDK_BUTTON_PRESS)
+ {
+ popup_menu (row, event);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+on_key_press_event (GtkWidget *widget,
+ GdkEventKey *event,
+ NautilusGtkPlacesView *view)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+
+ priv = nautilus_gtk_places_view_get_instance_private (view);
+
+ if (event)
+ {
+ guint modifiers;
+
+ modifiers = gtk_accelerator_get_default_mod_mask ();
+
+ if (event->keyval == GDK_KEY_Return ||
+ event->keyval == GDK_KEY_KP_Enter ||
+ event->keyval == GDK_KEY_ISO_Enter ||
+ event->keyval == GDK_KEY_space)
+ {
+ GtkWidget *focus_widget;
+ GtkWindow *toplevel;
+
+ priv->current_open_flags = GTK_PLACES_OPEN_NORMAL;
+ toplevel = get_toplevel (GTK_WIDGET (view));
+
+ if (!toplevel)
+ return FALSE;
+
+ focus_widget = gtk_window_get_focus (toplevel);
+
+ if (!NAUTILUS_IS_GTK_PLACES_VIEW_ROW (focus_widget))
+ return FALSE;
+
+ if ((event->state & modifiers) == GDK_SHIFT_MASK)
+ priv->current_open_flags = GTK_PLACES_OPEN_NEW_TAB;
+ else if ((event->state & modifiers) == GDK_CONTROL_MASK)
+ priv->current_open_flags = GTK_PLACES_OPEN_NEW_WINDOW;
+
+ activate_row (view, NAUTILUS_GTK_PLACES_VIEW_ROW (focus_widget), priv->current_open_flags);
+
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+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)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+ const gchar *uri;
+ GFile *file;
+
+ priv = nautilus_gtk_places_view_get_instance_private (view);
+ 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 (priv->connect_button))
+ return;
+
+ uri = gtk_entry_get_text (GTK_ENTRY (priv->address_entry));
+
+ if (uri != NULL && uri[0] != '\0')
+ file = g_file_new_for_commandline_arg (uri);
+
+ if (file)
+ {
+ priv->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)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+ const gchar* const *supported_protocols;
+ gchar *address, *scheme;
+ gboolean supported;
+
+ priv = nautilus_gtk_places_view_get_instance_private (view);
+ supported = FALSE;
+ supported_protocols = g_vfs_get_supported_uri_schemes (g_vfs_get_default ());
+ address = g_strdup (gtk_entry_get_text (GTK_ENTRY (priv->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 (priv->connect_button, supported);
+ if (scheme && !supported)
+ gtk_style_context_add_class (gtk_widget_get_style_context (priv->address_entry),
+ GTK_STYLE_CLASS_ERROR);
+ else
+ gtk_style_context_remove_class (gtk_widget_get_style_context (priv->address_entry),
+ GTK_STYLE_CLASS_ERROR);
+
+ g_free (address);
+ g_free (scheme);
+}
+
+static void
+on_address_entry_show_help_pressed (NautilusGtkPlacesView *view,
+ GtkEntryIconPosition icon_pos,
+ GdkEvent *event,
+ GtkEntry *entry)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+ GdkRectangle rect;
+
+ priv = nautilus_gtk_places_view_get_instance_private (view);
+
+ /* Setup the auxiliary popover's rectangle */
+ gtk_entry_get_icon_area (GTK_ENTRY (priv->address_entry),
+ GTK_ENTRY_ICON_SECONDARY,
+ &rect);
+
+ gtk_popover_set_pointing_to (GTK_POPOVER (priv->server_adresses_popover), &rect);
+ gtk_widget_set_visible (priv->server_adresses_popover, TRUE);
+}
+
+static void
+on_recent_servers_listbox_row_activated (NautilusGtkPlacesView *view,
+ NautilusGtkPlacesViewRow *row,
+ GtkWidget *listbox)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+ gchar *uri;
+
+ priv = nautilus_gtk_places_view_get_instance_private (view);
+ uri = g_object_get_data (G_OBJECT (row), "uri");
+
+ gtk_entry_set_text (GTK_ENTRY (priv->address_entry), uri);
+
+ gtk_widget_hide (priv->recent_servers_popover);
+}
+
+static void
+on_listbox_row_activated (NautilusGtkPlacesView *view,
+ NautilusGtkPlacesViewRow *row,
+ GtkWidget *listbox)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+ GdkEvent *event;
+ guint button;
+ GtkPlacesOpenFlags open_flags;
+
+ priv = nautilus_gtk_places_view_get_instance_private (view);
+
+ event = gtk_get_current_event ();
+ gdk_event_get_button (event, &button);
+
+ if (gdk_event_get_event_type (event) == GDK_BUTTON_RELEASE && button == GDK_BUTTON_MIDDLE)
+ open_flags = GTK_PLACES_OPEN_NEW_TAB;
+ else
+ open_flags = priv->current_open_flags;
+
+ activate_row (view, row, open_flags);
+}
+
+static gboolean
+is_mount_locally_accessible (GMount *mount)
+{
+ GFile *base_file;
+ gchar *path;
+
+ if (mount == NULL)
+ return FALSE;
+
+ base_file = g_mount_get_root (mount);
+
+ if (base_file == NULL)
+ return FALSE;
+
+ path = g_file_get_path (base_file);
+ g_object_unref (base_file);
+
+ if (path == NULL)
+ return FALSE;
+
+ g_free (path);
+ return TRUE;
+}
+
+static gboolean
+listbox_filter_func (GtkListBoxRow *row,
+ gpointer user_data)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+ gboolean is_network;
+ gboolean is_placeholder;
+ gboolean is_local = FALSE;
+ gboolean retval;
+ gboolean searching;
+ gchar *name;
+ gchar *path;
+
+ priv = nautilus_gtk_places_view_get_instance_private (NAUTILUS_GTK_PLACES_VIEW (user_data));
+ retval = FALSE;
+ searching = priv->search_query && priv->search_query[0] != '\0';
+
+ is_network = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "is-network"));
+ is_placeholder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "is-placeholder"));
+
+ if (NAUTILUS_IS_GTK_PLACES_VIEW_ROW (row))
+ {
+ NautilusGtkPlacesViewRow *placesviewrow;
+ GMount *mount;
+
+ placesviewrow = NAUTILUS_GTK_PLACES_VIEW_ROW (row);
+ g_object_get(G_OBJECT (placesviewrow), "mount", &mount, NULL);
+
+ is_local = is_mount_locally_accessible (mount);
+
+ g_clear_object (&mount);
+ }
+
+ if (is_network && priv->local_only && !is_local)
+ return FALSE;
+
+ if (is_placeholder && searching)
+ return FALSE;
+
+ if (!searching)
+ return TRUE;
+
+ g_object_get (row,
+ "name", &name,
+ "path", &path,
+ NULL);
+
+ if (name)
+ retval |= strstr (name, priv->search_query) != NULL;
+
+ if (path)
+ retval |= strstr (path, priv->search_query) != NULL;
+
+ g_free (name);
+ g_free (path);
+
+ return retval;
+}
+
+static void
+listbox_header_func (GtkListBoxRow *row,
+ GtkListBoxRow *before,
+ gpointer user_data)
+{
+ gboolean row_is_network;
+ gchar *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;
+
+ g_object_set (label,
+ "margin-end", 6,
+ NULL);
+
+ header_name = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ network_header_spinner = gtk_spinner_new ();
+ g_object_set (network_header_spinner,
+ "margin-end", 12,
+ NULL);
+ g_object_bind_property (NAUTILUS_GTK_PLACES_VIEW (user_data),
+ "fetching-networks",
+ network_header_spinner,
+ "active",
+ G_BINDING_SYNC_CREATE);
+
+ gtk_container_add (GTK_CONTAINER (header_name), label);
+ gtk_container_add (GTK_CONTAINER (header_name), network_header_spinner);
+ gtk_container_add (GTK_CONTAINER (header), header_name);
+ }
+ else
+ {
+ g_object_set (label,
+ "hexpand", TRUE,
+ "margin-end", 12,
+ NULL);
+ gtk_container_add (GTK_CONTAINER (header), label);
+ }
+
+ gtk_container_add (GTK_CONTAINER (header), separator);
+ gtk_widget_show_all (header);
+
+ gtk_list_box_row_set_header (row, header);
+
+ g_free (text);
+ }
+ else
+ {
+ gtk_list_box_row_set_header (row, NULL);
+ }
+}
+
+static gint
+listbox_sort_func (GtkListBoxRow *row1,
+ GtkListBoxRow *row2,
+ gpointer user_data)
+{
+ gboolean row1_is_network;
+ gboolean row2_is_network;
+ gchar *path1;
+ gchar *path2;
+ gboolean *is_placeholder1;
+ gboolean *is_placeholder2;
+ gint 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)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+
+ priv = nautilus_gtk_places_view_get_instance_private (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 (priv->listbox),
+ listbox_sort_func,
+ object,
+ NULL);
+ gtk_list_box_set_filter_func (GTK_LIST_BOX (priv->listbox),
+ listbox_filter_func,
+ object,
+ NULL);
+ gtk_list_box_set_header_func (GTK_LIST_BOX (priv->listbox),
+ listbox_header_func,
+ object,
+ NULL);
+
+ /* load drives */
+ update_places (NAUTILUS_GTK_PLACES_VIEW (object));
+
+ g_signal_connect_swapped (priv->volume_monitor,
+ "mount-added",
+ G_CALLBACK (update_places),
+ object);
+ g_signal_connect_swapped (priv->volume_monitor,
+ "mount-changed",
+ G_CALLBACK (update_places),
+ object);
+ g_signal_connect_swapped (priv->volume_monitor,
+ "mount-removed",
+ G_CALLBACK (update_places),
+ object);
+ g_signal_connect_swapped (priv->volume_monitor,
+ "volume-added",
+ G_CALLBACK (update_places),
+ object);
+ g_signal_connect_swapped (priv->volume_monitor,
+ "volume-changed",
+ G_CALLBACK (update_places),
+ object);
+ g_signal_connect_swapped (priv->volume_monitor,
+ "volume-removed",
+ G_CALLBACK (update_places),
+ object);
+}
+
+static void
+nautilus_gtk_places_view_map (GtkWidget *widget)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+
+ priv = nautilus_gtk_places_view_get_instance_private (NAUTILUS_GTK_PLACES_VIEW (widget));
+
+ gtk_entry_set_text (GTK_ENTRY (priv->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->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->destroy = nautilus_gtk_places_view_destroy;
+ 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 #GtkPlacesOpenFlags 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.
+ *
+ * Since: 3.18
+ */
+ 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,
+ GTK_TYPE_PLACES_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.
+ *
+ * Since: 3.18
+ */
+ 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_LOCAL_ONLY] =
+ g_param_spec_boolean ("local-only",
+ "Local Only",
+ "Whether the sidebar only includes local files",
+ FALSE,
+ G_PARAM_READWRITE);
+
+ properties[PROP_LOADING] =
+ g_param_spec_boolean ("loading",
+ "Loading",
+ "Whether the view is loading locations",
+ FALSE,
+ G_PARAM_READABLE);
+
+ properties[PROP_FETCHING_NETWORKS] =
+ g_param_spec_boolean ("fetching-networks",
+ "Fetching networks",
+ "Whether the view is fetching networks",
+ FALSE,
+ G_PARAM_READABLE);
+
+ 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",
+ GTK_TYPE_PLACES_OPEN_FLAGS,
+ GTK_PLACES_OPEN_NORMAL,
+ G_PARAM_READWRITE);
+
+ 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_private (widget_class, NautilusGtkPlacesView, actionbar);
+ gtk_widget_class_bind_template_child_private (widget_class, NautilusGtkPlacesView, address_entry);
+ gtk_widget_class_bind_template_child_private (widget_class, NautilusGtkPlacesView, address_entry_completion);
+ gtk_widget_class_bind_template_child_private (widget_class, NautilusGtkPlacesView, completion_store);
+ gtk_widget_class_bind_template_child_private (widget_class, NautilusGtkPlacesView, connect_button);
+ gtk_widget_class_bind_template_child_private (widget_class, NautilusGtkPlacesView, listbox);
+ gtk_widget_class_bind_template_child_private (widget_class, NautilusGtkPlacesView, recent_servers_listbox);
+ gtk_widget_class_bind_template_child_private (widget_class, NautilusGtkPlacesView, recent_servers_popover);
+ gtk_widget_class_bind_template_child_private (widget_class, NautilusGtkPlacesView, recent_servers_stack);
+ gtk_widget_class_bind_template_child_private (widget_class, NautilusGtkPlacesView, stack);
+ gtk_widget_class_bind_template_child_private (widget_class, NautilusGtkPlacesView, server_adresses_popover);
+ gtk_widget_class_bind_template_child_private (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_key_press_event);
+ 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);
+
+ gtk_widget_class_set_css_name (widget_class, "placesview");
+}
+
+static void
+nautilus_gtk_places_view_init (NautilusGtkPlacesView *self)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+
+ priv = nautilus_gtk_places_view_get_instance_private (self);
+
+ priv->volume_monitor = g_volume_monitor_get ();
+ priv->open_flags = GTK_PLACES_OPEN_NORMAL;
+ priv->path_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+ priv->space_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ populate_available_protocols_grid (GTK_GRID (priv->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
+ *
+ * Since: 3.18
+ */
+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 #GTK_PLACES_OPEN_NORMAL to always be sent
+ * to callbacks for the “open-location” signal.
+ *
+ * Since: 3.18
+ */
+void
+nautilus_gtk_places_view_set_open_flags (NautilusGtkPlacesView *view,
+ GtkPlacesOpenFlags flags)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view));
+
+ priv = nautilus_gtk_places_view_get_instance_private (view);
+
+ if (priv->open_flags != flags)
+ {
+ priv->open_flags = flags;
+ g_object_notify_by_pspec (G_OBJECT (view), properties[PROP_OPEN_FLAGS]);
+ }
+}
+
+/**
+ * nautilus_gtk_places_view_get_open_flags:
+ * @view: a #GtkPlacesSidebar
+ *
+ * Gets the open flags.
+ *
+ * Returns: the #GtkPlacesOpenFlags of @view
+ *
+ * Since: 3.18
+ */
+GtkPlacesOpenFlags
+nautilus_gtk_places_view_get_open_flags (NautilusGtkPlacesView *view)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view), 0);
+
+ priv = nautilus_gtk_places_view_get_instance_private (view);
+
+ return priv->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 gchar*
+nautilus_gtk_places_view_get_search_query (NautilusGtkPlacesView *view)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view), NULL);
+
+ priv = nautilus_gtk_places_view_get_instance_private (view);
+
+ return priv->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 gchar *query_text)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view));
+
+ priv = nautilus_gtk_places_view_get_instance_private (view);
+
+ if (g_strcmp0 (priv->search_query, query_text) != 0)
+ {
+ g_clear_pointer (&priv->search_query, g_free);
+ priv->search_query = g_strdup (query_text);
+
+ gtk_list_box_invalidate_filter (GTK_LIST_BOX (priv->listbox));
+ gtk_list_box_invalidate_headers (GTK_LIST_BOX (priv->listbox));
+
+ update_view_mode (view);
+ }
+}
+
+/**
+ * nautilus_gtk_places_view_get_loading:
+ * @view: a #NautilusGtkPlacesView
+ *
+ * Returns %TRUE if the view is loading locations.
+ *
+ * Since: 3.18
+ */
+gboolean
+nautilus_gtk_places_view_get_loading (NautilusGtkPlacesView *view)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view), FALSE);
+
+ priv = nautilus_gtk_places_view_get_instance_private (view);
+
+ return priv->loading;
+}
+
+static void
+update_loading (NautilusGtkPlacesView *view)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+ gboolean loading;
+
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view));
+
+ priv = nautilus_gtk_places_view_get_instance_private (view);
+ loading = priv->fetching_networks || priv->connecting_to_server ||
+ priv->mounting_volume || priv->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)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view));
+
+ priv = nautilus_gtk_places_view_get_instance_private (view);
+
+ if (priv->loading != loading)
+ {
+ priv->loading = loading;
+ g_object_notify_by_pspec (G_OBJECT (view), properties [PROP_LOADING]);
+ }
+}
+
+static gboolean
+nautilus_gtk_places_view_get_fetching_networks (NautilusGtkPlacesView *view)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view), FALSE);
+
+ priv = nautilus_gtk_places_view_get_instance_private (view);
+
+ return priv->fetching_networks;
+}
+
+static void
+nautilus_gtk_places_view_set_fetching_networks (NautilusGtkPlacesView *view,
+ gboolean fetching_networks)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view));
+
+ priv = nautilus_gtk_places_view_get_instance_private (view);
+
+ if (priv->fetching_networks != fetching_networks)
+ {
+ priv->fetching_networks = fetching_networks;
+ g_object_notify_by_pspec (G_OBJECT (view), properties [PROP_FETCHING_NETWORKS]);
+ }
+}
+
+/**
+ * nautilus_gtk_places_view_get_local_only:
+ * @view: a #NautilusGtkPlacesView
+ *
+ * Returns %TRUE if only local volumes are shown, i.e. no networks
+ * are displayed.
+ *
+ * Returns: %TRUE if only local volumes are shown, %FALSE otherwise.
+ *
+ * Since: 3.18
+ */
+gboolean
+nautilus_gtk_places_view_get_local_only (NautilusGtkPlacesView *view)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view), FALSE);
+
+ priv = nautilus_gtk_places_view_get_instance_private (view);
+
+ return priv->local_only;
+}
+
+/**
+ * nautilus_gtk_places_view_set_local_only:
+ * @view: a #NautilusGtkPlacesView
+ * @local_only: %TRUE to hide remote locations, %FALSE to show.
+ *
+ * Sets the #NautilusGtkPlacesView::local-only property to @local_only.
+ *
+ * Since: 3.18
+ */
+void
+nautilus_gtk_places_view_set_local_only (NautilusGtkPlacesView *view,
+ gboolean local_only)
+{
+ NautilusGtkPlacesViewPrivate *priv;
+
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view));
+
+ priv = nautilus_gtk_places_view_get_instance_private (view);
+
+ if (priv->local_only != local_only)
+ {
+ priv->local_only = local_only;
+
+ gtk_widget_set_visible (priv->actionbar, !local_only);
+ update_places (view);
+
+ update_view_mode (view);
+
+ g_object_notify_by_pspec (G_OBJECT (view), properties [PROP_LOCAL_ONLY]);
+ }
+}
diff --git a/src/gtk/nautilusgtkplacesview.ui b/src/gtk/nautilusgtkplacesview.ui
new file mode 100644
index 0000000..c5e7858
--- /dev/null
+++ b/src/gtk/nautilusgtkplacesview.ui
@@ -0,0 +1,382 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface domain="gtk30">
+ <requires lib="gtk+" version="3.16"/>
+ <object class="GtkListStore" id="completion_store">
+ <columns>
+ <!-- column-name name -->
+ <column type="gchararray"/>
+ <!-- column-name uri -->
+ <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="relative-to">address_entry</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="border-width">18</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="hexpand">1</property>
+ <property name="label" translatable="yes">Server Addresses</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <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>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <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>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid" id="available_protocols_grid">
+ <property name="visible">1</property>
+ <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="visible">1</property>
+ <property name="hexpand">1</property>
+ <property name="label" translatable="yes">Available Protocols</property>
+ <property name="xalign">0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="label" translatable="yes">Prefix</property>
+ <property name="xalign">0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <object class="GtkPopover" id="recent_servers_popover">
+ <child>
+ <object class="GtkStack" id="recent_servers_stack">
+ <property name="visible">1</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="vexpand">1</property>
+ <property name="valign">center</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">18</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">1</property>
+ <property name="pixel-size">48</property>
+ <property name="icon-name">network-server-symbolic</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="label" translatable="yes" comments="Translators: Server as any successfully connected network address">No recent servers found</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="name">empty</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="border-width">12</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="label" translatable="yes">Recent Servers</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="visible">1</property>
+ <property name="can-focus">1</property>
+ <property name="vexpand">1</property>
+ <property name="shadow-type">in</property>
+ <property name="min-content-width">250</property>
+ <property name="min-content-height">200</property>
+ <child>
+ <object class="GtkViewport">
+ <property name="visible">1</property>
+ <property name="shadow-type">none</property>
+ <child>
+ <object class="GtkListBox" id="recent_servers_listbox">
+ <property name="visible">1</property>
+ <property name="can-focus">1</property>
+ <property name="selection-mode">none</property>
+ <signal name="row-activated" handler="on_recent_servers_listbox_row_activated" object="NautilusGtkPlacesView" swapped="yes"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="name">list</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <template class="NautilusGtkPlacesView" parent="GtkBox">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="orientation">vertical</property>
+ <signal name="key-press-event" handler="on_key_press_event" object="NautilusGtkPlacesView" swapped="no"/>
+ <child>
+ <object class="GtkStack" id="stack">
+ <property name="visible">1</property>
+ <property name="vhomogeneous">0</property>
+ <property name="transition-type">crossfade</property>
+ <child>
+ <object class="GtkFrame">
+ <property name="visible">1</property>
+ <property name="shadow-type">none</property>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="visible">1</property>
+ <property name="hexpand">1</property>
+ <property name="vexpand">1</property>
+ <child>
+ <object class="GtkViewport">
+ <property name="visible">1</property>
+ <property name="shadow-type">none</property>
+ <child>
+ <object class="GtkListBox" id="listbox">
+ <property name="visible">1</property>
+ <property name="can-focus">1</property>
+ <property name="selection-mode">none</property>
+ <signal name="row-activated" handler="on_listbox_row_activated" object="NautilusGtkPlacesView" swapped="yes"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">browse</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="hexpand">1</property>
+ <property name="vexpand">1</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">1</property>
+ <property name="pixel-size">72</property>
+ <property name="icon-name">edit-find-symbolic</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="label" translatable="yes">No results found</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ <attribute name="scale" value="1.44"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="label" translatable="yes">Try a different search</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="name">empty-search</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkActionBar" id="actionbar">
+ <property name="visible">1</property>
+ <property name="hexpand">1</property>
+ <style>
+ <class name="background"/>
+ </style>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <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"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="connect_button">
+ <property name="label" translatable="yes">Con_nect</property>
+ <property name="use-underline">1</property>
+ <property name="visible">1</property>
+ <property name="can-focus">1</property>
+ <property name="sensitive">0</property>
+ <property name="receives-default">1</property>
+ <property name="valign">center</property>
+ <signal name="clicked" handler="on_connect_button_clicked" object="NautilusGtkPlacesView" swapped="yes"/>
+ </object>
+ <packing>
+ <property name="pack-type">end</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="hexpand">1</property>
+ <child>
+ <object class="GtkEntry" id="address_entry">
+ <property name="visible">1</property>
+ <property name="can-focus">1</property>
+ <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="visible">1</property>
+ <property name="can-focus">1</property>
+ <property name="receives-default">1</property>
+ <property name="direction">up</property>
+ <property name="popover">recent_servers_popover</property>
+ <style>
+ <class name="server-list-button"/>
+ </style>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">1</property>
+ <property name="icon-name">pan-down-symbolic</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <style>
+ <class name="linked"/>
+ </style>
+ </object>
+ <packing>
+ <property name="pack-type">end</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </template>
+</interface>
diff --git a/src/gtk/nautilusgtkplacesviewprivate.h b/src/gtk/nautilusgtkplacesviewprivate.h
new file mode 100644
index 0000000..92f1dd9
--- /dev/null
+++ b/src/gtk/nautilusgtkplacesviewprivate.h
@@ -0,0 +1,83 @@
+/* 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
+
+
+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;
+typedef struct _NautilusGtkPlacesViewPrivate NautilusGtkPlacesViewPrivate;
+
+struct _NautilusGtkPlacesViewClass
+{
+ GtkBoxClass parent_class;
+
+ void (* open_location) (NautilusGtkPlacesView *view,
+ GFile *location,
+ GtkPlacesOpenFlags open_flags);
+
+ void (* show_error_message) (GtkPlacesSidebar *sidebar,
+ const gchar *primary,
+ const gchar *secondary);
+
+ /*< private >*/
+
+ /* Padding for future expansion */
+ gpointer reserved[10];
+};
+
+struct _NautilusGtkPlacesView
+{
+ GtkBox parent_instance;
+};
+
+GType nautilus_gtk_places_view_get_type (void) G_GNUC_CONST;
+
+GtkPlacesOpenFlags nautilus_gtk_places_view_get_open_flags (NautilusGtkPlacesView *view);
+void nautilus_gtk_places_view_set_open_flags (NautilusGtkPlacesView *view,
+ GtkPlacesOpenFlags flags);
+
+const gchar* nautilus_gtk_places_view_get_search_query (NautilusGtkPlacesView *view);
+void nautilus_gtk_places_view_set_search_query (NautilusGtkPlacesView *view,
+ const gchar *query_text);
+
+gboolean nautilus_gtk_places_view_get_local_only (NautilusGtkPlacesView *view);
+
+void nautilus_gtk_places_view_set_local_only (NautilusGtkPlacesView *view,
+ gboolean local_only);
+
+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..121c582
--- /dev/null
+++ b/src/gtk/nautilusgtkplacesviewrow.c
@@ -0,0 +1,501 @@
+/* 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 <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
+#include "gtkbutton.h"
+#include "gtkeventbox.h"
+#include "gtkimage.h"
+#include "gtkintl.h"
+#include "gtklabel.h"
+#include "gtkspinner.h"
+#include "gtkstack.h"
+#include "gtktypebuiltins.h"
+#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;
+ GtkEventBox *event_box;
+ GtkImage *icon_image;
+ GtkLabel *name_label;
+ GtkLabel *path_label;
+
+ GVolume *volume;
+ GMount *mount;
+ GFile *file;
+
+ GCancellable *cancellable;
+
+ gint 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;
+ gchar *formatted_free_size;
+ gchar *formatted_total_size;
+ gchar *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 should_measure;
+
+ should_measure = (!row->is_network && (row->volume || row->mount || row->file));
+
+ 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;
+ GIcon *icon;
+
+ self = NAUTILUS_GTK_PLACES_VIEW_ROW (object);
+ icon = NULL;
+
+ switch (prop_id)
+ {
+ case PROP_ICON:
+ gtk_image_get_gicon (self->icon_image, &icon, NULL);
+ g_value_set_object (value, icon);
+ 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),
+ GTK_ICON_SIZE_LARGE_TOOLBAR);
+ 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_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;
+
+ 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, event_box);
+ 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);
+}
+
+GtkWidget*
+nautilus_gtk_places_view_row_get_event_box (NautilusGtkPlacesViewRow *row)
+{
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW_ROW (row), NULL);
+
+ return GTK_WIDGET (row->event_box);
+}
+
+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);
+ }
+ else
+ {
+ gtk_widget_set_child_visible (GTK_WIDGET (row->mount_stack), FALSE);
+ }
+}
+
+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_ICON_SIZE_BUTTON);
+ 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..99d2dcb
--- /dev/null
+++ b/src/gtk/nautilusgtkplacesviewrow.ui
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface domain="gtk30">
+ <requires lib="gtk+" version="3.16"/>
+ <template class="NautilusGtkPlacesViewRow" parent="GtkListBoxRow">
+ <property name="width-request">100</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <child>
+ <object class="GtkEventBox" id="event_box">
+ <property name="visible">1</property>
+ <child>
+ <object class="GtkBox" id="box">
+ <property name="visible">1</property>
+ <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="visible">1</property>
+ <property name="pixel-size">32</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="name_label">
+ <property name="visible">1</property>
+ <property name="hexpand">1</property>
+ <property name="xalign">0</property>
+ <property name="ellipsize">end</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="available_space_label">
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="path_label">
+ <property name="visible">1</property>
+ <property name="justify">right</property>
+ <property name="ellipsize">middle</property>
+ <property name="xalign">0</property>
+ <property name="max-width-chars">15</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkStack" id="mount_stack">
+ <property name="visible">1</property>
+ <property name="hhomogeneous">1</property>
+ <property name="vhomogeneous">1</property>
+ <child>
+ <object class="GtkButton" id="eject_button">
+ <property name="visible">1</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="tooltip-text" translatable="yes">Unmount</property>
+ <child>
+ <object class="GtkImage" id="eject_icon">
+ <property name="visible">1</property>
+ <property name="icon-name">media-eject-symbolic</property>
+ <property name="icon-size">1</property>
+ </object>
+ </child>
+ <style>
+ <class name="image-button"/>
+ <class name="sidebar-button"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSpinner" id="busy_spinner">
+ <property name="visible">1</property>
+ <property name="active">1</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/src/gtk/nautilusgtkplacesviewrowprivate.h b/src/gtk/nautilusgtkplacesviewrowprivate.h
new file mode 100644
index 0000000..fb32e1c
--- /dev/null
+++ b/src/gtk/nautilusgtkplacesviewrowprivate.h
@@ -0,0 +1,61 @@
+/* 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);
+
+GtkWidget* nautilus_gtk_places_view_row_get_event_box (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/meson.build b/src/meson.build
new file mode 100644
index 0000000..51cdb06
--- /dev/null
+++ b/src/meson.build
@@ -0,0 +1,350 @@
+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: [
+ '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.source_root(), 'data', 'freedesktop-dbus-interfaces.xml'
+ ),
+ interface_prefix: 'org.freedesktop',
+ namespace: 'NautilusFreedesktop'
+ ),
+ gnome.gdbus_codegen(
+ 'nautilus-generated',
+ join_paths(
+ meson.source_root(), 'data', 'dbus-interfaces.xml'
+ ),
+ interface_prefix: 'org.gnome.Nautilus',
+ namespace: 'NautilusDBus'
+ ),
+ gnome.gdbus_codegen(
+ 'nautilus-generated2',
+ join_paths(
+ meson.source_root(), 'data', 'dbus-interfaces2.xml'
+ ),
+ interface_prefix: 'org.gnome.Nautilus',
+ namespace: 'NautilusDBus'
+ ),
+ gnome.gdbus_codegen(
+ 'nautilus-shell-search-provider-generated',
+ join_paths(
+ meson.source_root(), 'data', 'shell-search-provider-dbus-interfaces.xml'
+ ),
+ interface_prefix: 'org.gnome',
+ namespace: 'Nautilus'
+ ),
+ 'animation/egg-animation.c',
+ 'animation/egg-animation.h',
+ 'animation/egg-frame-source.c',
+ 'animation/egg-frame-source.h',
+ 'animation/ide-box-theatric.c',
+ 'animation/ide-box-theatric.h',
+ 'animation/ide-cairo.c',
+ 'animation/ide-cairo.h',
+ 'gtk/nautilusgtkplacesview.c',
+ 'gtk/nautilusgtkplacesviewprivate.h',
+ 'gtk/nautilusgtkplacesviewrow.c',
+ 'gtk/nautilusgtkplacesviewrowprivate.h',
+ 'nautilus-application.c',
+ 'nautilus-application.h',
+ 'nautilus-bookmark-list.c',
+ 'nautilus-bookmark-list.h',
+ 'nautilus-canvas-view.c',
+ 'nautilus-canvas-view.h',
+ 'nautilus-canvas-view-container.c',
+ 'nautilus-canvas-view-container.h',
+ 'nautilus-container-max-width.c',
+ 'nautilus-container-max-width.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-list-model.c',
+ 'nautilus-list-model.h',
+ 'nautilus-list-view.c',
+ 'nautilus-list-view.h',
+ 'nautilus-list-view-private.h',
+ 'nautilus-list-view-dnd.c',
+ 'nautilus-list-view-dnd.h',
+ 'nautilus-location-entry.c',
+ 'nautilus-location-entry.h',
+ 'nautilus-mime-actions.c',
+ 'nautilus-mime-actions.h',
+ 'nautilus-notebook.c',
+ 'nautilus-notebook.h',
+ 'nautilus-other-locations-window-slot.c',
+ 'nautilus-other-locations-window-slot.h',
+ 'nautilus-pathbar.c',
+ 'nautilus-pathbar.h',
+ 'nautilus-places-view.c',
+ 'nautilus-places-view.h',
+ 'nautilus-previewer.c',
+ 'nautilus-previewer.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-toolbar.c',
+ 'nautilus-toolbar.h',
+ 'nautilus-toolbar-menu-sections.h',
+ 'nautilus-trash-bar.c',
+ 'nautilus-trash-bar.h',
+ 'nautilus-view.c',
+ 'nautilus-view.h',
+ 'nautilus-view-icon-controller.c',
+ 'nautilus-view-icon-controller.h',
+ 'nautilus-view-icon-item-ui.c',
+ 'nautilus-view-icon-item-ui.h',
+ 'nautilus-view-icon-ui.c',
+ 'nautilus-view-icon-ui.h',
+ 'nautilus-view-item-model.c',
+ 'nautilus-view-item-model.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-canvas-container.c',
+ 'nautilus-canvas-container.h',
+ 'nautilus-canvas-dnd.c',
+ 'nautilus-canvas-dnd.h',
+ 'nautilus-canvas-item.c',
+ 'nautilus-canvas-item.h',
+ 'nautilus-canvas-private.h',
+ 'nautilus-clipboard.c',
+ 'nautilus-clipboard.h',
+ 'nautilus-column-chooser.c',
+ 'nautilus-column-chooser.h',
+ 'nautilus-column-utilities.c',
+ 'nautilus-column-utilities.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-selection-canvas-item.c',
+ 'nautilus-selection-canvas-item.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-tree-view-drag-dest.c',
+ 'nautilus-tree-view-drag-dest.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,
+ libgd_dep,
+ nautilus_extension,
+ selinux,
+ tracker_sparql,
+ xml
+]
+
+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',
+ '--g-fatal-warnings'
+ ],
+ env: [
+ 'GSETTINGS_SCHEMA_DIR=@0@'.format(join_paths(meson.build_root(), 'data'))
+ ]
+ )
+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
+ ],
+ install: true
+)
diff --git a/src/nautilus-application.c b/src/nautilus-application.c
new file mode 100644
index 0000000..10478da
--- /dev/null
+++ b/src/nautilus-application.c
@@ -0,0 +1,1680 @@
+/*
+ * 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-gtk-extensions.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 <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-dbus-manager.h"
+#include "nautilus-directory-private.h"
+#include "nautilus-file.h"
+#include "nautilus-files-view.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-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;
+ GCancellable *tag_manager_cancellable;
+
+ guint previewer_selection_id;
+} NautilusApplicationPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (NautilusApplication, nautilus_application, GTK_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;
+ GtkDialog *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,
+ GdkScreen *screen)
+{
+ 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 (screen);
+
+ 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,
+ NautilusWindowOpenFlags 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,
+ NautilusWindowOpenFlags 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;
+ GdkScreen *screen;
+
+ 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.*/
+ 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_WINDOW_OPEN_FLAG_NEW_WINDOW) != 0 &&
+ (flags & NAUTILUS_WINDOW_OPEN_FLAG_NEW_TAB) != 0));
+
+ /* and if the flags specify so, this is overridden */
+ if ((flags & NAUTILUS_WINDOW_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
+ {
+ screen = active_window != NULL ?
+ gtk_window_get_screen (GTK_WINDOW (active_window)) :
+ gdk_screen_get_default ();
+
+ target_window = nautilus_application_create_window (self, screen);
+ /* Whatever the caller says, the slot won't be the same */
+ 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_WINDOW_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, gdk_screen_get_default ());
+
+ 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, gdk_screen_get_default ());
+ }
+ 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_cancellable_cancel (priv->tag_manager_cancellable);
+ g_clear_object (&priv->tag_manager_cancellable);
+
+ 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 (NULL, NULL);
+
+ 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_WINDOW_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_WINDOW_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_on_window (window, "help:gnome-help/files",
+ gtk_get_current_event_time (), &error);
+
+ if (error)
+ {
+ dialog = gtk_message_dialog_new (window ? GTK_WINDOW (window) : NULL,
+ GTK_DIALOG_MODAL,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_OK,
+ _("There was an error displaying help: \n%s"),
+ error->message);
+ g_signal_connect (G_OBJECT (dialog), "response",
+ G_CALLBACK (gtk_widget_destroy),
+ NULL);
+
+ gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+ gtk_widget_show (dialog);
+ g_error_free (error);
+ }
+}
+
+static void
+action_kill (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GtkApplication *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_hide_sidebar (GObject *object,
+ GParamSpec *pspec,
+ gpointer *user_data)
+{
+ GList *window, *windows;
+ GVariant *state = g_action_get_state (G_ACTION (object));
+
+ windows = gtk_application_get_windows (GTK_APPLICATION (user_data));
+
+ for (window = windows; window != NULL; window = window->next)
+ {
+ if (g_variant_get_boolean (state))
+ {
+ nautilus_window_show_sidebar (window->data);
+ }
+ else
+ {
+ nautilus_window_hide_sidebar (window->data);
+ }
+ }
+}
+
+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);
+}
+
+static gboolean
+variant_get_mapping (GValue *value,
+ GVariant *variant,
+ gpointer user_data)
+{
+ g_value_set_variant (value, variant);
+ return TRUE;
+}
+
+static GVariant *
+variant_set_mapping (const GValue *value,
+ const GVariantType *expected_type,
+ gpointer user_data)
+{
+ return g_value_get_variant (value);
+}
+
+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 },
+ { "show-hide-sidebar", NULL, NULL, "true", 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)
+{
+ GAction *sidebar_action;
+
+ g_action_map_add_action_entries (G_ACTION_MAP (app),
+ app_entries, G_N_ELEMENTS (app_entries),
+ app);
+
+
+ sidebar_action = g_action_map_lookup_action (G_ACTION_MAP (app),
+ "show-hide-sidebar");
+ g_signal_connect (sidebar_action,
+ "notify::state",
+ G_CALLBACK (action_show_hide_sidebar),
+ app);
+ g_settings_bind_with_mapping (nautilus_window_state,
+ NAUTILUS_WINDOW_STATE_START_WITH_SIDEBAR,
+ sidebar_action,
+ "state",
+ G_SETTINGS_BIND_DEFAULT,
+ variant_get_mapping,
+ variant_set_mapping,
+ NULL, NULL);
+
+ 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");
+ nautilus_application_set_accelerator (G_APPLICATION (app),
+ "app.show-hide-sidebar", "F9");
+}
+
+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;
+ const gchar * const *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_cancellable = g_cancellable_new ();
+ priv->tag_manager = nautilus_tag_manager_get ();
+ nautilus_tag_manager_set_cancellable (priv->tag_manager,
+ priv->tag_manager_cancellable);
+
+ g_application_add_main_option_entries (G_APPLICATION (self), options);
+
+ nautilus_ensure_extension_points ();
+ nautilus_ensure_extension_builtins ();
+}
+
+static void
+theme_changed (GtkSettings *settings)
+{
+ static GtkCssProvider *provider = NULL;
+ static GtkCssProvider *permanent_provider = NULL;
+ gchar *theme;
+ GdkScreen *screen;
+ GFile *file;
+
+ g_object_get (settings, "gtk-theme-name", &theme, NULL);
+ screen = gdk_screen_get_default ();
+
+ /* CSS that themes can override */
+ if (g_str_equal (theme, "Adwaita") || g_str_equal (theme, "Adwaita-dark"))
+ {
+ if (provider == NULL)
+ {
+ provider = gtk_css_provider_new ();
+ file = g_file_new_for_uri ("resource:///org/gnome/nautilus/css/Adwaita.css");
+ gtk_css_provider_load_from_file (provider, file, NULL);
+ g_object_unref (file);
+ }
+
+ gtk_style_context_add_provider_for_screen (screen,
+ GTK_STYLE_PROVIDER (provider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+ else if (provider != NULL)
+ {
+ gtk_style_context_remove_provider_for_screen (screen,
+ GTK_STYLE_PROVIDER (provider));
+ g_clear_object (&provider);
+ }
+
+ /* CSS we want to always load for any theme */
+ if (permanent_provider == NULL)
+ {
+ permanent_provider = gtk_css_provider_new ();
+ file = g_file_new_for_uri ("resource:///org/gnome/nautilus/css/nautilus.css");
+ gtk_css_provider_load_from_file (permanent_provider, file, NULL);
+ /* The behavior of two style providers with the same priority is
+ * undefined and gtk happens to prefer the provider that got added last.
+ * Use a higher priority here to avoid this problem.
+ */
+ gtk_style_context_add_provider_for_screen (screen,
+ GTK_STYLE_PROVIDER (permanent_provider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION + 1);
+ g_object_unref (file);
+ }
+
+ g_free (theme);
+}
+
+static void
+setup_theme_extensions (void)
+{
+ GtkSettings *settings;
+
+ /* Set up a handler to load our custom css for Adwaita.
+ * See https://bugzilla.gnome.org/show_bug.cgi?id=732959
+ * for a more automatic solution that is still under discussion.
+ */
+ settings = gtk_settings_get_default ();
+ g_signal_connect (settings, "notify::gtk-theme-name", G_CALLBACK (theme_changed), NULL);
+ theme_changed (settings);
+}
+
+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
+update_previewer_selection (NautilusApplication *self,
+ NautilusWindow *window)
+{
+ GtkWindow *gtk_window;
+ NautilusWindowSlot *slot;
+ NautilusView *view;
+ GList *selection;
+
+ gtk_window = gtk_application_get_active_window (GTK_APPLICATION (self));
+ if (!NAUTILUS_IS_WINDOW (gtk_window))
+ {
+ return;
+ }
+
+ if (NAUTILUS_WINDOW (gtk_window) != window)
+ {
+ return;
+ }
+
+ slot = nautilus_window_get_active_slot (window);
+ if (slot == NULL)
+ {
+ return;
+ }
+
+ view = nautilus_window_slot_get_current_view (slot);
+ if (!NAUTILUS_IS_FILES_VIEW (view))
+ {
+ return;
+ }
+
+ selection = nautilus_window_slot_get_selection (slot);
+ if (selection != NULL)
+ {
+ nautilus_files_view_preview_update (NAUTILUS_FILES_VIEW (view), selection);
+ }
+}
+
+static void
+on_application_active_window_changed (NautilusApplication *self,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ GtkWindow *window;
+
+ window = gtk_application_get_active_window (GTK_APPLICATION (self));
+ if (NAUTILUS_IS_WINDOW (window))
+ {
+ update_previewer_selection (self, NAUTILUS_WINDOW (window));
+ }
+}
+
+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 ();
+}
+
+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);
+
+ setup_theme_extensions ();
+
+ /* 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);
+
+ nautilus_tag_manager_maybe_migrate_tracker2_data (priv->tag_manager);
+
+ nautilus_profile_end (NULL);
+
+ g_signal_connect (self, "notify::active-window", G_CALLBACK (on_application_active_window_changed), NULL);
+ g_signal_connect (self, "shutdown", G_CALLBACK (on_application_shutdown), NULL);
+
+ g_signal_connect_object (gtk_icon_theme_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
+on_active_selection_changed (NautilusWindow *window,
+ NautilusApplication *self)
+{
+ update_previewer_selection (self, window);
+}
+
+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);
+ g_signal_connect (window, "active-selection-changed", G_CALLBACK (on_active_selection_changed), 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);
+ g_signal_handlers_disconnect_by_func (window, on_active_selection_changed, 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);
+}
diff --git a/src/nautilus-application.h b/src/nautilus-application.h
new file mode 100644
index 0000000..f915b0d
--- /dev/null
+++ b/src/nautilus-application.h
@@ -0,0 +1,89 @@
+/*
+ * 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 <gdk/gdk.h>
+#include <gio/gio.h>
+#include <gtk/gtk.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, GtkApplication)
+
+struct _NautilusApplicationClass {
+ GtkApplicationClass parent_class;
+
+ void (*open_location_full) (NautilusApplication *application,
+ GFile *location,
+ NautilusWindowOpenFlags flags,
+ GList *selection,
+ NautilusWindow *target_window,
+ NautilusWindowSlot *target_slot);
+};
+
+NautilusApplication * nautilus_application_new (void);
+
+NautilusWindow * nautilus_application_create_window (NautilusApplication *application,
+ GdkScreen *screen);
+
+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,
+ NautilusWindowOpenFlags 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);
+G_END_DECLS
diff --git a/src/nautilus-autorun-software.c b/src/nautilus-autorun-software.c
new file mode 100644
index 0000000..732ec2d
--- /dev/null
+++ b/src/nautilus-autorun-software.c
@@ -0,0 +1,282 @@
+/* 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 <gio/gio.h>
+
+#include <glib/gi18n.h>
+
+#include "nautilus-icon-info.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_widget_destroy (GTK_WIDGET (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", TRUE))
+ {
+ 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 = gtk_message_dialog_new_with_markup (NULL, /* TODO: parent window? */
+ 0,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_OK,
+ _("Oops! There was a problem running this software."));
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), "%s", error_string);
+ /* This is required because we don't show dialogs in the
+ * window picker and if the window pops under another window
+ * there is no way to get it back. */
+ gtk_window_set_keep_above (GTK_WINDOW (dialog), TRUE);
+
+ gtk_dialog_run (GTK_DIALOG (dialog));
+ gtk_widget_destroy (dialog);
+ }
+}
+
+static void
+present_autorun_for_software_dialog (GMount *mount)
+{
+ GIcon *icon;
+ int icon_size;
+ g_autoptr (NautilusIconInfo) icon_info = NULL;
+ g_autoptr (GdkPixbuf) pixbuf = NULL;
+ g_autofree char *mount_name = NULL;
+ GtkWidget *dialog;
+ AutorunSoftwareDialogData *data;
+
+ mount_name = g_mount_get_name (mount);
+
+ dialog = gtk_message_dialog_new (NULL, /* TODO: parent window? */
+ 0,
+ GTK_MESSAGE_OTHER,
+ GTK_BUTTONS_CANCEL,
+ _("“%s” contains software intended to be automatically started. Would you like to run it?"),
+ mount_name);
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ "%s",
+ _("If you don’t trust this location or aren’t sure, press Cancel."));
+
+ /* This is required because we don't show dialogs in the
+ * window picker and if the window pops under another window
+ * there is no way to get it back. */
+ gtk_window_set_keep_above (GTK_WINDOW (dialog), TRUE);
+
+ /* 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);
+ icon_size = nautilus_get_icon_size_for_stock_size (GTK_ICON_SIZE_DIALOG);
+ icon_info = nautilus_icon_info_lookup (icon, icon_size,
+ gtk_widget_get_scale_factor (GTK_WIDGET (dialog)));
+ pixbuf = nautilus_icon_info_get_pixbuf_at_size (icon_info, icon_size);
+
+ gtk_window_set_icon (GTK_WINDOW (dialog), pixbuf);
+
+ 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);
+
+ gtk_dialog_add_button (GTK_DIALOG (dialog),
+ _("_Run"),
+ GTK_RESPONSE_OK);
+
+ gtk_widget_show_all (dialog);
+
+ if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK)
+ {
+ gtk_widget_destroy (dialog);
+ autorun (mount);
+ }
+}
+
+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 (&argc, &argv);
+
+ 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);
+
+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..bc456c7
--- /dev/null
+++ b/src/nautilus-batch-rename-dialog.c
@@ -0,0 +1,2332 @@
+/* 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 *add_button;
+ GtkWidget *add_popover;
+ GtkWidget *numbering_order_label;
+ GtkWidget *numbering_label;
+ GtkWidget *scrolled_window;
+ GtkWidget *numbering_order_popover;
+ GtkWidget *numbering_order_button;
+ GtkWidget *numbering_revealer;
+ GtkWidget *conflict_box;
+ GtkWidget *conflict_label;
+ GtkWidget *conflict_down;
+ GtkWidget *conflict_up;
+
+ GList *original_name_listbox_rows;
+ GList *arrow_listbox_rows;
+ GList *result_listbox_rows;
+ 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;
+ GMenu *add_tag_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;
+ GCancellable *conflict_cancellable;
+ gboolean checking_conflicts;
+
+ /* 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);
+
+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_label_set_label (GTK_LABEL (dialog->numbering_order_label),
+ 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);
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->numbering_order_button), FALSE);
+
+ 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 void
+row_selected (GtkListBox *box,
+ GtkListBoxRow *listbox_row,
+ gpointer user_data)
+{
+ NautilusBatchRenameDialog *dialog;
+ GtkListBoxRow *row;
+ gint index;
+
+ if (!GTK_IS_LIST_BOX_ROW (listbox_row))
+ {
+ return;
+ }
+
+ dialog = NAUTILUS_BATCH_RENAME_DIALOG (user_data);
+ index = gtk_list_box_row_get_index (listbox_row);
+
+ if (GTK_WIDGET (box) == dialog->original_name_listbox)
+ {
+ row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (dialog->arrow_listbox), index);
+ gtk_list_box_select_row (GTK_LIST_BOX (dialog->arrow_listbox),
+ row);
+ row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (dialog->result_listbox), index);
+ gtk_list_box_select_row (GTK_LIST_BOX (dialog->result_listbox),
+ row);
+ }
+
+ if (GTK_WIDGET (box) == dialog->arrow_listbox)
+ {
+ row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (dialog->original_name_listbox), index);
+ gtk_list_box_select_row (GTK_LIST_BOX (dialog->original_name_listbox),
+ row);
+ row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (dialog->result_listbox), index);
+ gtk_list_box_select_row (GTK_LIST_BOX (dialog->result_listbox),
+ row);
+ }
+
+ if (GTK_WIDGET (box) == dialog->result_listbox)
+ {
+ row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (dialog->arrow_listbox), index);
+ gtk_list_box_select_row (GTK_LIST_BOX (dialog->arrow_listbox),
+ row);
+ row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (dialog->original_name_listbox), index);
+ gtk_list_box_select_row (GTK_LIST_BOX (dialog->original_name_listbox),
+ row);
+ }
+}
+
+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_entry_get_text (GTK_ENTRY (dialog->find_entry)));
+ }
+ else
+ {
+ entry_text = g_strdup (gtk_entry_get_text (GTK_ENTRY (dialog->name_entry)));
+ }
+
+ replace_text = g_strdup (gtk_entry_get_text (GTK_ENTRY (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);
+
+ gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (dialog->window)), NULL);
+}
+
+static void
+listbox_header_func (GtkListBoxRow *row,
+ GtkListBoxRow *before,
+ NautilusBatchRenameDialog *dialog)
+{
+ 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);
+ }
+}
+
+/* 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;
+ gint current_row_natural_height;
+ 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_height (GTK_WIDGET (l->data),
+ NULL,
+ &current_row_natural_height);
+
+ if (current_row_natural_height > maximum_height)
+ {
+ maximum_height = current_row_natural_height;
+ }
+ }
+
+ for (l = dialog->listbox_labels_old; l != NULL; l = l->next)
+ {
+ gtk_widget_get_preferred_height (GTK_WIDGET (l->data),
+ NULL,
+ &current_row_natural_height);
+
+ if (current_row_natural_height > maximum_height)
+ {
+ maximum_height = current_row_natural_height;
+ }
+ }
+
+ for (l = dialog->listbox_icons; l != NULL; l = l->next)
+ {
+ gtk_widget_get_preferred_height (GTK_WIDGET (l->data),
+ NULL,
+ &current_row_natural_height);
+
+ if (current_row_natural_height > maximum_height)
+ {
+ maximum_height = current_row_natural_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_row_for_label (NautilusBatchRenameDialog *dialog,
+ const gchar *old_text,
+ gboolean show_separator)
+{
+ GtkWidget *row;
+ GtkWidget *label_old;
+
+ row = gtk_list_box_row_new ();
+
+ g_object_set_data (G_OBJECT (row), "show-separator", GINT_TO_POINTER (show_separator));
+
+ 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_container_add (GTK_CONTAINER (row), label_old);
+ gtk_widget_show_all (row);
+
+ return row;
+}
+
+static GtkWidget *
+create_result_row_for_label (NautilusBatchRenameDialog *dialog,
+ const gchar *new_text,
+ gboolean show_separator)
+{
+ GtkWidget *row;
+ GtkWidget *label_new;
+
+ row = gtk_list_box_row_new ();
+
+ g_object_set_data (G_OBJECT (row), "show-separator", GINT_TO_POINTER (show_separator));
+
+ 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_container_add (GTK_CONTAINER (row), label_new);
+ gtk_widget_show_all (row);
+
+ return row;
+}
+
+static GtkWidget *
+create_arrow_row_for_label (NautilusBatchRenameDialog *dialog,
+ gboolean show_separator)
+{
+ GtkWidget *row;
+ GtkWidget *icon;
+
+ row = gtk_list_box_row_new ();
+
+ g_object_set_data (G_OBJECT (row), "show-separator", GINT_TO_POINTER (show_separator));
+
+ if (gtk_widget_get_direction (row) == 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_container_add (GTK_CONTAINER (row), icon);
+ gtk_widget_show_all (row);
+
+ return row;
+}
+
+static void
+prepare_batch_rename (NautilusBatchRenameDialog *dialog)
+{
+ GdkCursor *cursor;
+ GdkDisplay *display;
+
+ /* wait for checking conflicts to finish, to be sure that
+ * the rename can actually take place */
+ if (dialog->checking_conflicts)
+ {
+ dialog->rename_clicked = TRUE;
+ return;
+ }
+
+ if (!gtk_widget_is_sensitive (dialog->rename_button))
+ {
+ return;
+ }
+
+ display = gtk_widget_get_display (GTK_WIDGET (dialog->window));
+ cursor = gdk_cursor_new_from_name (display, "progress");
+ gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (dialog->window)),
+ cursor);
+ g_object_unref (cursor);
+
+ display = gtk_widget_get_display (GTK_WIDGET (dialog));
+ cursor = gdk_cursor_new_from_name (display, "progress");
+ gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (dialog)),
+ cursor);
+ g_object_unref (cursor);
+
+ gtk_widget_hide (GTK_WIDGET (dialog));
+ begin_batch_rename (dialog, dialog->new_names);
+
+ if (dialog->conflict_cancellable)
+ {
+ g_cancellable_cancel (dialog->conflict_cancellable);
+ g_clear_object (&dialog->conflict_cancellable);
+ }
+
+ gtk_widget_destroy (GTK_WIDGET (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->conflict_cancellable)
+ {
+ g_cancellable_cancel (dialog->conflict_cancellable);
+ g_clear_object (&dialog->conflict_cancellable);
+ }
+
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+ }
+}
+
+static void
+fill_display_listbox (NautilusBatchRenameDialog *dialog)
+{
+ GtkWidget *row;
+ GList *l1;
+ GList *l2;
+ NautilusFile *file;
+ GString *new_name;
+ gchar *name;
+
+ dialog->original_name_listbox_rows = NULL;
+ dialog->arrow_listbox_rows = NULL;
+ dialog->result_listbox_rows = NULL;
+
+ gtk_size_group_add_widget (dialog->size_group, dialog->result_listbox);
+ gtk_size_group_add_widget (dialog->size_group, dialog->original_name_listbox);
+
+ 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 = create_original_name_row_for_label (dialog, name, TRUE);
+ gtk_container_add (GTK_CONTAINER (dialog->original_name_listbox), row);
+ dialog->original_name_listbox_rows = g_list_prepend (dialog->original_name_listbox_rows,
+ row);
+
+ row = create_arrow_row_for_label (dialog, TRUE);
+ gtk_container_add (GTK_CONTAINER (dialog->arrow_listbox), row);
+ dialog->arrow_listbox_rows = g_list_prepend (dialog->arrow_listbox_rows,
+ row);
+
+ row = create_result_row_for_label (dialog, new_name->str, TRUE);
+ gtk_container_add (GTK_CONTAINER (dialog->result_listbox), row);
+ dialog->result_listbox_rows = g_list_prepend (dialog->result_listbox_rows,
+ row);
+
+ g_free (name);
+ }
+
+ dialog->original_name_listbox_rows = g_list_reverse (dialog->original_name_listbox_rows);
+ dialog->arrow_listbox_rows = g_list_reverse (dialog->arrow_listbox_rows);
+ dialog->result_listbox_rows = g_list_reverse (dialog->result_listbox_rows);
+ 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;
+
+ 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->original_name_listbox_rows, nth_conflict_index);
+ gtk_list_box_select_row (GTK_LIST_BOX (dialog->original_name_listbox),
+ l->data);
+
+ l = g_list_nth (dialog->arrow_listbox_rows, nth_conflict_index);
+ gtk_list_box_select_row (GTK_LIST_BOX (dialog->arrow_listbox),
+ l->data);
+
+ l = g_list_nth (dialog->result_listbox_rows, nth_conflict_index);
+ gtk_list_box_select_row (GTK_LIST_BOX (dialog->result_listbox),
+ l->data);
+
+ /* 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->original_name_listbox_rows,
+ l2 = dialog->arrow_listbox_rows,
+ l3 = dialog->result_listbox_rows;
+ l1 != NULL && l2 != NULL && l3 != NULL;
+ l1 = l1->next, l2 = l2->next, l3 = l3->next)
+ {
+ context = gtk_widget_get_style_context (GTK_WIDGET (l1->data));
+
+ if (gtk_style_context_has_class (context, "conflict-row"))
+ {
+ gtk_style_context_remove_class (context, "conflict-row");
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (l2->data));
+ gtk_style_context_remove_class (context, "conflict-row");
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (l3->data));
+ 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 (GTK_WIDGET (l1->data));
+ gtk_style_context_add_class (context, "conflict-row");
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (l2->data));
+ gtk_style_context_add_class (context, "conflict-row");
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (l3->data));
+ 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_entry_get_text (GTK_ENTRY (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 gboolean
+file_names_list_has_duplicates_finish (NautilusBatchRenameDialog *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ g_return_val_if_fail (g_task_is_valid (res, self), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+on_file_names_list_has_duplicates (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusBatchRenameDialog *self;
+ GError *error = NULL;
+ gboolean success;
+
+ self = NAUTILUS_BATCH_RENAME_DIALOG (object);
+ success = file_names_list_has_duplicates_finish (self, res, &error);
+
+ if (!success)
+ {
+ g_clear_error (&error);
+ return;
+ }
+
+ self->duplicates = g_list_reverse (self->duplicates);
+ self->checking_conflicts = FALSE;
+ update_listbox (self);
+}
+
+typedef struct
+{
+ GList *directories;
+ NautilusDirectory *current_directory;
+ GMutex wait_ready_mutex;
+ GCond wait_ready_condition;
+ gboolean directory_conflicts_ready;
+} CheckConflictsData;
+
+static void
+on_directory_conflicts_ready (NautilusDirectory *conflict_directory,
+ GList *files,
+ gpointer callback_data)
+{
+ NautilusBatchRenameDialog *self;
+ GTask *task;
+ CheckConflictsData *task_data;
+ GCancellable *cancellable;
+
+ task = G_TASK (callback_data);
+ task_data = g_task_get_task_data (task);
+ cancellable = g_task_get_cancellable (task);
+ self = NAUTILUS_BATCH_RENAME_DIALOG (g_task_get_source_object (task));
+ if (!g_cancellable_is_cancelled (cancellable))
+ {
+ check_conflict_for_files (self, conflict_directory, files);
+ }
+
+ g_mutex_lock (&task_data->wait_ready_mutex);
+ task_data->directory_conflicts_ready = TRUE;
+ g_cond_signal (&task_data->wait_ready_condition);
+ g_mutex_unlock (&task_data->wait_ready_mutex);
+}
+
+static gboolean
+check_conflicts_on_main_thread (gpointer user_data)
+{
+ GTask *task = (GTask *) user_data;
+ CheckConflictsData *task_data = g_task_get_task_data (task);
+
+ nautilus_directory_call_when_ready (task_data->current_directory,
+ NAUTILUS_FILE_ATTRIBUTE_INFO,
+ TRUE,
+ on_directory_conflicts_ready,
+ task);
+ return FALSE;
+}
+
+static void
+file_names_list_has_duplicates_async_thread (GTask *task,
+ gpointer object,
+ gpointer data,
+ GCancellable *cancellable)
+{
+ NautilusBatchRenameDialog *self;
+ CheckConflictsData *task_data;
+ GList *directories;
+ GList *l;
+
+ self = g_task_get_source_object (task);
+ task_data = g_task_get_task_data (task);
+ self->duplicates = NULL;
+
+ g_mutex_init (&task_data->wait_ready_mutex);
+ g_cond_init (&task_data->wait_ready_condition);
+
+ directories = batch_rename_files_get_distinct_parents (self->selection);
+ /* check if this is the last call of the callback */
+ for (l = directories; l != NULL; l = l->next)
+ {
+ if (g_task_return_error_if_cancelled (task))
+ {
+ nautilus_directory_list_free (directories);
+ return;
+ }
+
+ g_mutex_lock (&task_data->wait_ready_mutex);
+ task_data->directory_conflicts_ready = FALSE;
+
+
+ task_data->current_directory = l->data;
+ /* NautilusDirectory and NautilusFile are not thread safe, we need to call
+ * them on the main thread.
+ */
+ g_main_context_invoke (NULL, check_conflicts_on_main_thread, task);
+
+ /* We need to block this thread until the call_when_ready call is done,
+ * if not the GTask would finalize. */
+ while (!task_data->directory_conflicts_ready)
+ {
+ g_cond_wait (&task_data->wait_ready_condition,
+ &task_data->wait_ready_mutex);
+ }
+
+ g_mutex_unlock (&task_data->wait_ready_mutex);
+ }
+
+ g_task_return_boolean (task, TRUE);
+ nautilus_directory_list_free (directories);
+}
+
+static void
+destroy_conflicts_task_data (gpointer data)
+{
+ CheckConflictsData *task_data = data;
+
+ if (task_data->directories)
+ {
+ g_list_free (task_data->directories);
+ }
+
+ g_mutex_clear (&task_data->wait_ready_mutex);
+ g_cond_clear (&task_data->wait_ready_condition);
+
+ g_free (task_data);
+}
+
+static void
+file_names_list_has_duplicates_async (NautilusBatchRenameDialog *dialog,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr (GTask) task = NULL;
+ CheckConflictsData *task_data;
+
+ if (dialog->checking_conflicts == TRUE)
+ {
+ g_cancellable_cancel (dialog->conflict_cancellable);
+ g_clear_object (&dialog->conflict_cancellable);
+ }
+
+ dialog->conflict_cancellable = g_cancellable_new ();
+
+ dialog->checking_conflicts = TRUE;
+
+ task = g_task_new (dialog, dialog->conflict_cancellable, callback, user_data);
+ task_data = g_new0 (CheckConflictsData, 1);
+ g_task_set_task_data (task, task_data, destroy_conflicts_task_data);
+ g_task_run_in_thread (task, file_names_list_has_duplicates_async_thread);
+}
+
+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_entry_get_text (GTK_ENTRY (dialog->name_entry));
+ }
+ else
+ {
+ entry_text = gtk_entry_get_text (GTK_ENTRY (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->conflict_cancellable != NULL)
+ {
+ g_cancellable_cancel (dialog->conflict_cancellable);
+ g_clear_object (&dialog->conflict_cancellable);
+ }
+
+ 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,
+ on_file_names_list_has_duplicates,
+ NULL);
+}
+
+static void
+batch_rename_dialog_mode_changed (NautilusBatchRenameDialog *dialog)
+{
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_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);
+}
+
+static void
+add_button_clicked (NautilusBatchRenameDialog *dialog)
+{
+ if (gtk_widget_is_visible (dialog->add_popover))
+ {
+ gtk_widget_set_visible (dialog->add_popover, FALSE);
+ }
+ else
+ {
+ gtk_widget_set_visible (dialog->add_popover, TRUE);
+ }
+}
+
+static void
+add_popover_closed (NautilusBatchRenameDialog *dialog)
+{
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->add_button), FALSE);
+}
+
+static void
+numbering_order_button_clicked (NautilusBatchRenameDialog *dialog)
+{
+ if (gtk_widget_is_visible (dialog->numbering_order_popover))
+ {
+ gtk_widget_set_visible (dialog->numbering_order_popover, FALSE);
+ }
+ else
+ {
+ gtk_widget_set_visible (dialog->numbering_order_popover, TRUE);
+ }
+}
+
+static void
+numbering_order_popover_closed (NautilusBatchRenameDialog *dialog)
+{
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->numbering_order_button), FALSE);
+}
+
+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_PRELIGHT;
+ }
+ else
+ {
+ flags &= ~GTK_STATE_PRELIGHT;
+ }
+
+ gtk_style_context_set_state (context, flags);
+}
+
+static gboolean
+on_motion_notify (GtkWidget *widget,
+ GdkEvent *event,
+ gpointer user_data)
+{
+ NautilusBatchRenameDialog *dialog;
+ gdouble y;
+ GtkListBoxRow *row;
+
+ 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 (G_UNLIKELY (!gdk_event_get_coords (event, NULL, &y)))
+ {
+ g_return_val_if_reached (GDK_EVENT_PROPAGATE);
+ }
+
+ 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);
+ }
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static gboolean
+on_leave_notify (GtkWidget *widget,
+ GdkEvent *event,
+ 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;
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static gboolean
+on_event (GtkWidget *widget,
+ GdkEvent *event,
+ gpointer user_data)
+{
+ GdkEventType event_type;
+
+ event_type = gdk_event_get_event_type (event);
+
+ if (event_type == GDK_MOTION_NOTIFY)
+ {
+ return on_motion_notify (widget, event, user_data);
+ }
+
+ if (event_type == GDK_LEAVE_NOTIFY)
+ {
+ return on_leave_notify (widget, event, user_data);
+ }
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+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);
+}
+
+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->checking_conflicts)
+ {
+ g_cancellable_cancel (dialog->conflict_cancellable);
+ g_clear_object (&dialog->conflict_cancellable);
+ }
+
+ g_clear_object (&dialog->numbering_order_menu);
+ g_clear_object (&dialog->add_tag_menu);
+ g_list_free (dialog->original_name_listbox_rows);
+ g_list_free (dialog->arrow_listbox_rows);
+ g_list_free (dialog->result_listbox_rows);
+ 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);
+
+ 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, add_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, add_popover);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, numbering_order_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, scrolled_window);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, numbering_order_popover);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, numbering_order_button);
+ 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, add_button_clicked);
+ gtk_widget_class_bind_template_callback (widget_class, add_popover_closed);
+ gtk_widget_class_bind_template_callback (widget_class, numbering_order_button_clicked);
+ gtk_widget_class_bind_template_callback (widget_class, numbering_order_popover_closed);
+ 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);
+ gtk_widget_class_bind_template_callback (widget_class, on_insert_text);
+ gtk_widget_class_bind_template_callback (widget_class, on_delete_text);
+}
+
+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);
+
+ add_tag (dialog, metadata_tags_constants[ORIGINAL_FILE_NAME]);
+
+ nautilus_batch_rename_dialog_initialize_actions (dialog);
+
+ update_display_text (dialog);
+
+ fill_display_listbox (dialog);
+
+ gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (window)), NULL);
+
+ g_string_free (dialog_title, TRUE);
+
+ return GTK_WIDGET (dialog);
+}
+
+static void
+nautilus_batch_rename_dialog_init (NautilusBatchRenameDialog *self)
+{
+ TagData *tag_data;
+ guint i;
+ g_autoptr (GtkBuilder) builder = NULL;
+
+ 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;
+
+ builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-batch-rename-dialog-menu.ui");
+ self->numbering_order_menu = g_object_ref_sink (G_MENU (gtk_builder_get_object (builder, "numbering_order_menu")));
+ self->add_tag_menu = g_object_ref_sink (G_MENU (gtk_builder_get_object (builder, "add_tag_menu")));
+
+ gtk_popover_bind_model (GTK_POPOVER (self->numbering_order_popover),
+ G_MENU_MODEL (self->numbering_order_menu),
+ NULL);
+ gtk_popover_bind_model (GTK_POPOVER (self->add_popover),
+ G_MENU_MODEL (self->add_tag_menu),
+ NULL);
+
+ 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->new_names = NULL;
+
+ self->checking_conflicts = FALSE;
+
+ 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 (self->original_name_listbox, "row-selected", G_CALLBACK (row_selected), self);
+ g_signal_connect (self->arrow_listbox, "row-selected", G_CALLBACK (row_selected), self);
+ g_signal_connect (self->result_listbox, "row-selected", G_CALLBACK (row_selected), self);
+
+ self->size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+
+ g_signal_connect (self->original_name_listbox,
+ "event",
+ G_CALLBACK (on_event),
+ self);
+ g_signal_connect (self->result_listbox,
+ "event",
+ G_CALLBACK (on_event),
+ self);
+ g_signal_connect (self->arrow_listbox,
+ "event",
+ G_CALLBACK (on_event),
+ self);
+
+ 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..49b9813
--- /dev/null
+++ b/src/nautilus-batch-rename-utilities.c
@@ -0,0 +1,1180 @@
+/* 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;
+
+ old_file_name = nautilus_file_get_name (NAUTILUS_FILE (files->data));
+ new_file_name = new_names_list->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;
+
+ file_name = nautilus_file_get_name (NAUTILUS_FILE (files2->data));
+ new_name = new_names_list2->data;
+
+ if (files2 != files && g_strcmp0 (file_name, new_file_name->str) == 0)
+ {
+ 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..a0698d6
--- /dev/null
+++ b/src/nautilus-bookmark-list.c
@@ -0,0 +1,669 @@
+/*
+ * 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;
+ g_autoptr (GFile) bookmark_location = NULL;
+ 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)
+ {
+ 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..f115f24
--- /dev/null
+++ b/src/nautilus-bookmark.c
@@ -0,0 +1,841 @@
+/* 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
+};
+
+#define ELLIPSISED_MENU_ITEM_MIN_CHARS 32
+
+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);
+ }
+}
+
+static void
+apply_warning_emblem (GIcon **base,
+ gboolean symbolic)
+{
+ GIcon *emblemed_icon;
+ g_autoptr (GIcon) warning = NULL;
+ g_autoptr (GEmblem) emblem = NULL;
+
+ if (symbolic)
+ {
+ warning = g_themed_icon_new ("dialog-warning-symbolic");
+ }
+ else
+ {
+ warning = g_themed_icon_new ("dialog-warning");
+ }
+
+ emblem = g_emblem_new (warning);
+ emblemed_icon = g_emblemed_icon_new (*base, emblem);
+
+ g_object_unref (*base);
+
+ *base = emblemed_icon;
+}
+
+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 (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);
+ }
+
+ if (!bookmark->exists)
+ {
+ DEBUG ("%s: file does not exist, add emblem", nautilus_bookmark_get_name (bookmark));
+
+ apply_warning_emblem (&icon, FALSE);
+ apply_warning_emblem (&symbolic_icon, TRUE);
+ }
+
+ 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;
+}
+
+/**
+ * nautilus_bookmark_menu_item_new:
+ *
+ * Return a menu item representing a bookmark.
+ * @bookmark: The bookmark the menu item represents.
+ * Return value: A newly-created bookmark, not yet shown.
+ **/
+GtkWidget *
+nautilus_bookmark_menu_item_new (NautilusBookmark *bookmark)
+{
+ GtkWidget *menu_item;
+ GtkLabel *label;
+ const char *name;
+
+ name = nautilus_bookmark_get_name (bookmark);
+ menu_item = gtk_menu_item_new_with_label (name);
+ label = GTK_LABEL (gtk_bin_get_child (GTK_BIN (menu_item)));
+ gtk_label_set_use_underline (label, FALSE);
+ gtk_label_set_ellipsize (label, PANGO_ELLIPSIZE_END);
+ gtk_label_set_max_width_chars (label, ELLIPSISED_MENU_ITEM_MIN_CHARS);
+
+ return menu_item;
+}
+
+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..ae03bbe
--- /dev/null
+++ b/src/nautilus-bookmark.h
@@ -0,0 +1,57 @@
+
+/* 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);
+
+/* Helper functions for displaying bookmarks */
+GtkWidget * nautilus_bookmark_menu_item_new (NautilusBookmark *bookmark);
+
+G_END_DECLS \ No newline at end of file
diff --git a/src/nautilus-canvas-container.c b/src/nautilus-canvas-container.c
new file mode 100644
index 0000000..71bcf19
--- /dev/null
+++ b/src/nautilus-canvas-container.c
@@ -0,0 +1,6360 @@
+/* nautilus-canvas-container.c - Canvas container widget.
+ *
+ * Copyright (C) 1999, 2000 Free Software Foundation
+ * Copyright (C) 2000, 2001 Eazel, Inc.
+ * Copyright (C) 2002, 2003 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: Ettore Perazzoli <ettore@gnu.org>,
+ * Darin Adler <darin@bentspoon.com>
+ */
+
+#include "nautilus-canvas-container.h"
+
+#include <atk/atkaction.h>
+#include <eel/eel-art-extensions.h>
+#include <eel/eel-gtk-extensions.h>
+#include <eel/eel-vfs-extensions.h>
+#include <gdk/gdkkeysyms.h>
+#include <gdk/gdk.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+
+#define DEBUG_FLAG NAUTILUS_DEBUG_CANVAS_CONTAINER
+#include "nautilus-debug.h"
+
+#include "nautilus-canvas-private.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-icon-info.h"
+#include "nautilus-lib-self-check-functions.h"
+#include "nautilus-selection-canvas-item.h"
+
+/* Interval for updating the rubberband selection, in milliseconds. */
+#define RUBBERBAND_TIMEOUT_INTERVAL 10
+
+#define RUBBERBAND_SCROLL_THRESHOLD 5
+
+/* Initial unpositioned icon value */
+#define ICON_UNPOSITIONED_VALUE -1
+
+/* Timeout for making the icon currently selected for keyboard operation visible.
+ * If this is 0, you can get into trouble with extra scrolling after holding
+ * down the arrow key for awhile when there are many items.
+ */
+#define KEYBOARD_ICON_REVEAL_TIMEOUT 10
+
+#define CONTEXT_MENU_TIMEOUT_INTERVAL 500
+
+/* Maximum amount of milliseconds the mouse button is allowed to stay down
+ * and still be considered a click.
+ */
+#define MAX_CLICK_TIME 1500
+
+/* Button assignments. */
+#define DRAG_BUTTON 1
+#define RUBBERBAND_BUTTON 1
+#define MIDDLE_BUTTON 2
+#define CONTEXTUAL_MENU_BUTTON 3
+#define DRAG_MENU_BUTTON 2
+
+/* Maximum size (pixels) allowed for icons at the standard zoom level. */
+#define MINIMUM_IMAGE_SIZE 24
+#define MAXIMUM_IMAGE_SIZE 96
+
+#define ICON_PAD_LEFT 4
+#define ICON_PAD_RIGHT 4
+#define ICON_PAD_TOP 4
+#define ICON_PAD_BOTTOM 4
+
+#define CONTAINER_PAD_LEFT 4
+#define CONTAINER_PAD_RIGHT 4
+#define CONTAINER_PAD_TOP 4
+#define CONTAINER_PAD_BOTTOM 4
+
+/* Width of a "grid unit". Canvas items will always take up one or more
+ * grid units, rounding up their size relative to the unit width.
+ * So with an 80px grid unit, a 100px canvas item would take two grid units,
+ * where a 76px canvas item would only take one.
+ * Canvas items are then centered in the extra available space.
+ * Keep in sync with MAX_TEXT_WIDTH at nautilus-canvas-item.
+ */
+#define SMALL_ICON_GRID_WIDTH 124
+#define STANDARD_ICON_GRID_WIDTH 112
+#define LARGE_ICON_GRID_WIDTH 106
+#define LARGER_ICON_GRID_WIDTH 128
+
+/* Copied from NautilusCanvasContainer */
+#define NAUTILUS_CANVAS_CONTAINER_SEARCH_DIALOG_TIMEOUT 5
+
+/* Copied from NautilusFile */
+#define UNDEFINED_TIME ((time_t) (-1))
+
+enum
+{
+ ACTION_ACTIVATE,
+ ACTION_MENU,
+ LAST_ACTION
+};
+
+typedef struct
+{
+ GList *selection;
+ char *action_descriptions[LAST_ACTION];
+} NautilusCanvasContainerAccessiblePrivate;
+
+static GType nautilus_canvas_container_accessible_get_type (void);
+static void preview_selected_items (NautilusCanvasContainer *container);
+static void activate_selected_items (NautilusCanvasContainer *container);
+static void activate_selected_items_alternate (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon);
+static NautilusCanvasIcon *get_first_selected_icon (NautilusCanvasContainer *container);
+static NautilusCanvasIcon *get_nth_selected_icon (NautilusCanvasContainer *container,
+ int index);
+static gboolean has_multiple_selection (NautilusCanvasContainer *container);
+static gboolean all_selected (NautilusCanvasContainer *container);
+static gboolean has_selection (NautilusCanvasContainer *container);
+static void icon_destroy (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon);
+static gboolean finish_adding_new_icons (NautilusCanvasContainer *container);
+static inline void icon_get_bounding_box (NautilusCanvasIcon *icon,
+ int *x1_return,
+ int *y1_return,
+ int *x2_return,
+ int *y2_return,
+ NautilusCanvasItemBoundsUsage usage);
+static void handle_hadjustment_changed (GtkAdjustment *adjustment,
+ NautilusCanvasContainer *container);
+static void handle_vadjustment_changed (GtkAdjustment *adjustment,
+ NautilusCanvasContainer *container);
+static GList *nautilus_canvas_container_get_selected_icons (NautilusCanvasContainer *container);
+static void nautilus_canvas_container_update_visible_icons (NautilusCanvasContainer *container);
+static void reveal_icon (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon);
+
+static void nautilus_canvas_container_set_rtl_positions (NautilusCanvasContainer *container);
+static double get_mirror_x_position (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon,
+ double x);
+static void text_ellipsis_limit_changed_container_callback (gpointer callback_data);
+
+static int compare_icons_horizontal (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon_a,
+ NautilusCanvasIcon *icon_b);
+
+static int compare_icons_vertical (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon_a,
+ NautilusCanvasIcon *icon_b);
+
+static void schedule_redo_layout (NautilusCanvasContainer *container);
+
+static const char *nautilus_canvas_container_accessible_action_names[] =
+{
+ "activate",
+ "menu",
+ NULL
+};
+
+static const char *nautilus_canvas_container_accessible_action_descriptions[] =
+{
+ "Activate selected items",
+ "Popup context menu",
+ NULL
+};
+
+G_DEFINE_TYPE (NautilusCanvasContainer, nautilus_canvas_container, EEL_TYPE_CANVAS);
+
+/* The NautilusCanvasContainer signals. */
+enum
+{
+ ACTIVATE,
+ ACTIVATE_ALTERNATE,
+ ACTIVATE_PREVIEWER,
+ BAND_SELECT_STARTED,
+ BAND_SELECT_ENDED,
+ BUTTON_PRESS,
+ CONTEXT_CLICK_BACKGROUND,
+ CONTEXT_CLICK_SELECTION,
+ MIDDLE_CLICK,
+ GET_CONTAINER_URI,
+ GET_ICON_URI,
+ GET_ICON_ACTIVATION_URI,
+ GET_ICON_DROP_TARGET_URI,
+ ICON_RENAME_STARTED,
+ ICON_RENAME_ENDED,
+ ICON_STRETCH_STARTED,
+ ICON_STRETCH_ENDED,
+ MOVE_COPY_ITEMS,
+ HANDLE_NETSCAPE_URL,
+ HANDLE_URI_LIST,
+ HANDLE_TEXT,
+ HANDLE_RAW,
+ HANDLE_HOVER,
+ SELECTION_CHANGED,
+ ICON_ADDED,
+ ICON_REMOVED,
+ CLEARED,
+ LAST_SIGNAL
+};
+
+typedef struct
+{
+ int **icon_grid;
+ int *grid_memory;
+ int num_rows;
+ int num_columns;
+ gboolean tight;
+} PlacementGrid;
+
+static guint signals[LAST_SIGNAL];
+
+/* Functions dealing with NautilusIcons. */
+
+static void
+icon_free (NautilusCanvasIcon *icon)
+{
+ /* Destroy this icon item; the parent will unref it. */
+ eel_canvas_item_destroy (EEL_CANVAS_ITEM (icon->item));
+ g_free (icon);
+}
+
+static gboolean
+icon_is_positioned (const NautilusCanvasIcon *icon)
+{
+ return icon->x != ICON_UNPOSITIONED_VALUE && icon->y != ICON_UNPOSITIONED_VALUE;
+}
+
+
+/* x, y are the top-left coordinates of the icon. */
+static void
+icon_set_position (NautilusCanvasIcon *icon,
+ double x,
+ double y)
+{
+ if (icon->x == x && icon->y == y)
+ {
+ return;
+ }
+
+ if (icon->x == ICON_UNPOSITIONED_VALUE)
+ {
+ icon->x = 0;
+ }
+ if (icon->y == ICON_UNPOSITIONED_VALUE)
+ {
+ icon->y = 0;
+ }
+
+ eel_canvas_item_move (EEL_CANVAS_ITEM (icon->item),
+ x - icon->x,
+ y - icon->y);
+
+ icon->x = x;
+ icon->y = y;
+}
+
+static guint
+nautilus_canvas_container_get_grid_size_for_zoom_level (NautilusCanvasZoomLevel zoom_level)
+{
+ switch (zoom_level)
+ {
+ case NAUTILUS_CANVAS_ZOOM_LEVEL_SMALL:
+ {
+ return SMALL_ICON_GRID_WIDTH;
+ }
+ break;
+
+ case NAUTILUS_CANVAS_ZOOM_LEVEL_STANDARD:
+ {
+ return STANDARD_ICON_GRID_WIDTH;
+ }
+ break;
+
+ case NAUTILUS_CANVAS_ZOOM_LEVEL_LARGE:
+ {
+ return LARGE_ICON_GRID_WIDTH;
+ }
+ break;
+
+ case NAUTILUS_CANVAS_ZOOM_LEVEL_LARGER:
+ {
+ return LARGER_ICON_GRID_WIDTH;
+ }
+ break;
+
+ default:
+ {
+ g_return_val_if_reached (STANDARD_ICON_GRID_WIDTH);
+ }
+ break;
+ }
+}
+
+guint
+nautilus_canvas_container_get_icon_size_for_zoom_level (NautilusCanvasZoomLevel zoom_level)
+{
+ switch (zoom_level)
+ {
+ case NAUTILUS_CANVAS_ZOOM_LEVEL_SMALL:
+ {
+ return NAUTILUS_CANVAS_ICON_SIZE_SMALL;
+ }
+ break;
+
+ case NAUTILUS_CANVAS_ZOOM_LEVEL_STANDARD:
+ {
+ return NAUTILUS_CANVAS_ICON_SIZE_STANDARD;
+ }
+ break;
+
+ case NAUTILUS_CANVAS_ZOOM_LEVEL_LARGE:
+ {
+ return NAUTILUS_CANVAS_ICON_SIZE_LARGE;
+ }
+ break;
+
+ case NAUTILUS_CANVAS_ZOOM_LEVEL_LARGER:
+ {
+ return NAUTILUS_CANVAS_ICON_SIZE_LARGER;
+ }
+ break;
+
+ default:
+ {
+ g_return_val_if_reached (NAUTILUS_CANVAS_ICON_SIZE_STANDARD);
+ }
+ break;
+ }
+}
+
+static void
+icon_get_size (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon,
+ guint *size)
+{
+ if (size != NULL)
+ {
+ *size = MAX (nautilus_canvas_container_get_icon_size_for_zoom_level (container->details->zoom_level),
+ NAUTILUS_CANVAS_ICON_SIZE_SMALL);
+ }
+}
+
+static void
+icon_raise (NautilusCanvasIcon *icon)
+{
+ EelCanvasItem *item, *band;
+
+ item = EEL_CANVAS_ITEM (icon->item);
+ band = NAUTILUS_CANVAS_CONTAINER (item->canvas)->details->rubberband_info.selection_rectangle;
+
+ eel_canvas_item_send_behind (item, band);
+}
+
+static void
+icon_toggle_selected (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon)
+{
+ icon->is_selected = !icon->is_selected;
+ if (icon->is_selected)
+ {
+ container->details->selection = g_list_prepend (container->details->selection, icon->data);
+ container->details->selection_needs_resort = TRUE;
+ }
+ else
+ {
+ container->details->selection = g_list_remove (container->details->selection, icon->data);
+ }
+
+ eel_canvas_item_set (EEL_CANVAS_ITEM (icon->item),
+ "highlighted_for_selection", (gboolean) icon->is_selected,
+ NULL);
+
+ /* Raise each newly-selected icon to the front as it is selected. */
+ if (icon->is_selected)
+ {
+ icon_raise (icon);
+ }
+}
+
+/* Select an icon. Return TRUE if selection has changed. */
+static gboolean
+icon_set_selected (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon,
+ gboolean select)
+{
+ if (select == icon->is_selected)
+ {
+ return FALSE;
+ }
+
+ icon_toggle_selected (container, icon);
+ g_assert (select == icon->is_selected);
+ return TRUE;
+}
+
+static inline void
+icon_get_bounding_box (NautilusCanvasIcon *icon,
+ int *x1_return,
+ int *y1_return,
+ int *x2_return,
+ int *y2_return,
+ NautilusCanvasItemBoundsUsage usage)
+{
+ double x1, y1, x2, y2;
+
+ if (usage == BOUNDS_USAGE_FOR_DISPLAY)
+ {
+ eel_canvas_item_get_bounds (EEL_CANVAS_ITEM (icon->item),
+ &x1, &y1, &x2, &y2);
+ }
+ else if (usage == BOUNDS_USAGE_FOR_LAYOUT)
+ {
+ nautilus_canvas_item_get_bounds_for_layout (icon->item,
+ &x1, &y1, &x2, &y2);
+ }
+ else if (usage == BOUNDS_USAGE_FOR_ENTIRE_ITEM)
+ {
+ nautilus_canvas_item_get_bounds_for_entire_item (icon->item,
+ &x1, &y1, &x2, &y2);
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ if (x1_return != NULL)
+ {
+ *x1_return = x1;
+ }
+
+ if (y1_return != NULL)
+ {
+ *y1_return = y1;
+ }
+
+ if (x2_return != NULL)
+ {
+ *x2_return = x2;
+ }
+
+ if (y2_return != NULL)
+ {
+ *y2_return = y2;
+ }
+}
+
+/* Utility functions for NautilusCanvasContainer. */
+
+gboolean
+nautilus_canvas_container_scroll (NautilusCanvasContainer *container,
+ int delta_x,
+ int delta_y)
+{
+ GtkAdjustment *hadj, *vadj;
+ int old_h_value, old_v_value;
+
+ hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container));
+ vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container));
+
+ /* Store the old ajustment values so we can tell if we
+ * ended up actually scrolling. We may not have in a case
+ * where the resulting value got pinned to the adjustment
+ * min or max.
+ */
+ old_h_value = gtk_adjustment_get_value (hadj);
+ old_v_value = gtk_adjustment_get_value (vadj);
+
+ gtk_adjustment_set_value (hadj, gtk_adjustment_get_value (hadj) + delta_x);
+ gtk_adjustment_set_value (vadj, gtk_adjustment_get_value (vadj) + delta_y);
+
+ /* return TRUE if we did scroll */
+ return gtk_adjustment_get_value (hadj) != old_h_value || gtk_adjustment_get_value (vadj) != old_v_value;
+}
+
+static void
+pending_icon_to_reveal_destroy_callback (NautilusCanvasItem *item,
+ NautilusCanvasContainer *container)
+{
+ g_assert (NAUTILUS_IS_CANVAS_CONTAINER (container));
+ g_assert (container->details->pending_icon_to_reveal != NULL);
+ g_assert (container->details->pending_icon_to_reveal->item == item);
+
+ container->details->pending_icon_to_reveal = NULL;
+}
+
+static NautilusCanvasIcon *
+get_pending_icon_to_reveal (NautilusCanvasContainer *container)
+{
+ return container->details->pending_icon_to_reveal;
+}
+
+static void
+set_pending_icon_to_reveal (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon)
+{
+ NautilusCanvasIcon *old_icon;
+
+ old_icon = container->details->pending_icon_to_reveal;
+
+ if (icon == old_icon)
+ {
+ return;
+ }
+
+ if (old_icon != NULL)
+ {
+ g_signal_handlers_disconnect_by_func
+ (old_icon->item,
+ G_CALLBACK (pending_icon_to_reveal_destroy_callback),
+ container);
+ }
+
+ if (icon != NULL)
+ {
+ g_signal_connect (icon->item, "destroy",
+ G_CALLBACK (pending_icon_to_reveal_destroy_callback),
+ container);
+ }
+
+ container->details->pending_icon_to_reveal = icon;
+}
+
+static void
+item_get_canvas_bounds (EelCanvasItem *item,
+ EelIRect *bounds)
+{
+ EelDRect world_rect;
+
+ eel_canvas_item_get_bounds (item,
+ &world_rect.x0,
+ &world_rect.y0,
+ &world_rect.x1,
+ &world_rect.y1);
+ eel_canvas_item_i2w (item->parent,
+ &world_rect.x0,
+ &world_rect.y0);
+ eel_canvas_item_i2w (item->parent,
+ &world_rect.x1,
+ &world_rect.y1);
+
+ world_rect.x0 -= ICON_PAD_LEFT + ICON_PAD_RIGHT;
+ world_rect.x1 += ICON_PAD_LEFT + ICON_PAD_RIGHT;
+
+ world_rect.y0 -= ICON_PAD_TOP + ICON_PAD_BOTTOM;
+ world_rect.y1 += ICON_PAD_TOP + ICON_PAD_BOTTOM;
+
+ eel_canvas_w2c (item->canvas,
+ world_rect.x0,
+ world_rect.y0,
+ &bounds->x0,
+ &bounds->y0);
+ eel_canvas_w2c (item->canvas,
+ world_rect.x1,
+ world_rect.y1,
+ &bounds->x1,
+ &bounds->y1);
+}
+
+static void
+icon_get_row_and_column_bounds (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon,
+ EelIRect *bounds)
+{
+ GList *p;
+ NautilusCanvasIcon *one_icon;
+ EelIRect one_bounds;
+
+ item_get_canvas_bounds (EEL_CANVAS_ITEM (icon->item), bounds);
+
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ one_icon = p->data;
+
+ if (icon == one_icon)
+ {
+ continue;
+ }
+
+ if (compare_icons_horizontal (container, icon, one_icon) == 0)
+ {
+ item_get_canvas_bounds (EEL_CANVAS_ITEM (one_icon->item), &one_bounds);
+ bounds->x0 = MIN (bounds->x0, one_bounds.x0);
+ bounds->x1 = MAX (bounds->x1, one_bounds.x1);
+ }
+
+ if (compare_icons_vertical (container, icon, one_icon) == 0)
+ {
+ item_get_canvas_bounds (EEL_CANVAS_ITEM (one_icon->item), &one_bounds);
+ bounds->y0 = MIN (bounds->y0, one_bounds.y0);
+ bounds->y1 = MAX (bounds->y1, one_bounds.y1);
+ }
+ }
+}
+
+static void
+reveal_icon (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon)
+{
+ GtkAllocation allocation;
+ GtkAdjustment *hadj, *vadj;
+ EelIRect bounds;
+
+ if (!icon_is_positioned (icon))
+ {
+ set_pending_icon_to_reveal (container, icon);
+ return;
+ }
+
+ set_pending_icon_to_reveal (container, NULL);
+
+ gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
+
+ hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container));
+ vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container));
+
+ /* ensure that we reveal the entire row/column */
+ icon_get_row_and_column_bounds (container, icon, &bounds);
+
+ if (bounds.y0 < gtk_adjustment_get_value (vadj))
+ {
+ gtk_adjustment_set_value (vadj, bounds.y0);
+ }
+ else if (bounds.y1 > gtk_adjustment_get_value (vadj) + allocation.height)
+ {
+ gtk_adjustment_set_value
+ (vadj, bounds.y1 - allocation.height);
+ }
+
+ if (bounds.x0 < gtk_adjustment_get_value (hadj))
+ {
+ gtk_adjustment_set_value (hadj, bounds.x0);
+ }
+ else if (bounds.x1 > gtk_adjustment_get_value (hadj) + allocation.width)
+ {
+ gtk_adjustment_set_value
+ (hadj, bounds.x1 - allocation.width);
+ }
+}
+
+static void
+process_pending_icon_to_reveal (NautilusCanvasContainer *container)
+{
+ NautilusCanvasIcon *pending_icon_to_reveal;
+
+ pending_icon_to_reveal = get_pending_icon_to_reveal (container);
+
+ if (pending_icon_to_reveal != NULL)
+ {
+ reveal_icon (container, pending_icon_to_reveal);
+ }
+}
+
+static gboolean
+keyboard_icon_reveal_timeout_callback (gpointer data)
+{
+ NautilusCanvasContainer *container;
+ NautilusCanvasIcon *icon;
+
+ container = NAUTILUS_CANVAS_CONTAINER (data);
+ icon = container->details->keyboard_icon_to_reveal;
+
+ g_assert (icon != NULL);
+
+ /* Only reveal the icon if it's still the keyboard focus or if
+ * it's still selected. Someone originally thought we should
+ * cancel this reveal if the user manages to sneak a direct
+ * scroll in before the timeout fires, but we later realized
+ * this wouldn't actually be an improvement
+ * (see bugzilla.gnome.org 40612).
+ */
+ if (icon == container->details->focus
+ || icon->is_selected)
+ {
+ reveal_icon (container, icon);
+ }
+ container->details->keyboard_icon_reveal_timer_id = 0;
+
+ return FALSE;
+}
+
+static void
+unschedule_keyboard_icon_reveal (NautilusCanvasContainer *container)
+{
+ NautilusCanvasContainerDetails *details;
+
+ details = container->details;
+
+ if (details->keyboard_icon_reveal_timer_id != 0)
+ {
+ g_source_remove (details->keyboard_icon_reveal_timer_id);
+ }
+}
+
+static void
+schedule_keyboard_icon_reveal (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon)
+{
+ NautilusCanvasContainerDetails *details;
+
+ details = container->details;
+
+ unschedule_keyboard_icon_reveal (container);
+
+ details->keyboard_icon_to_reveal = icon;
+ details->keyboard_icon_reveal_timer_id
+ = g_timeout_add (KEYBOARD_ICON_REVEAL_TIMEOUT,
+ keyboard_icon_reveal_timeout_callback,
+ container);
+}
+
+static void inline
+emit_atk_object_notify_focused (NautilusCanvasIcon *icon,
+ gboolean focused)
+{
+ AtkObject *atk_object = atk_gobject_accessible_for_object (G_OBJECT (icon->item));
+ atk_object_notify_state_change (atk_object, ATK_STATE_FOCUSED, focused);
+}
+
+static void
+clear_focus (NautilusCanvasContainer *container)
+{
+ if (container->details->focus != NULL)
+ {
+ if (container->details->keyboard_focus)
+ {
+ eel_canvas_item_set (EEL_CANVAS_ITEM (container->details->focus->item),
+ "highlighted_as_keyboard_focus", 0,
+ NULL);
+ }
+ else
+ {
+ emit_atk_object_notify_focused (container->details->focus, FALSE);
+ }
+ }
+
+ container->details->focus = NULL;
+}
+
+/* Set @icon as the icon currently focused for accessibility. */
+static void
+set_focus (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon,
+ gboolean keyboard_focus)
+{
+ g_assert (icon != NULL);
+
+ if (icon == container->details->focus)
+ {
+ return;
+ }
+
+ clear_focus (container);
+
+ container->details->focus = icon;
+ container->details->keyboard_focus = keyboard_focus;
+
+ if (keyboard_focus)
+ {
+ eel_canvas_item_set (EEL_CANVAS_ITEM (container->details->focus->item),
+ "highlighted_as_keyboard_focus", 1,
+ NULL);
+ }
+ else
+ {
+ emit_atk_object_notify_focused (container->details->focus, TRUE);
+ }
+}
+
+static void
+set_keyboard_rubberband_start (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon)
+{
+ container->details->keyboard_rubberband_start = icon;
+}
+
+static void
+clear_keyboard_rubberband_start (NautilusCanvasContainer *container)
+{
+ container->details->keyboard_rubberband_start = NULL;
+}
+
+/* carbon-copy of eel_canvas_group_bounds(), but
+ * for NautilusCanvasContainerItems it returns the
+ * bounds for the “entire item”.
+ */
+static void
+get_icon_bounds_for_canvas_bounds (EelCanvasGroup *group,
+ double *x1,
+ double *y1,
+ double *x2,
+ double *y2,
+ NautilusCanvasItemBoundsUsage usage)
+{
+ EelCanvasItem *child;
+ GList *list;
+ double tx1, ty1, tx2, ty2;
+ double minx, miny, maxx, maxy;
+ int set;
+
+ /* Get the bounds of the first visible item */
+
+ child = NULL; /* Unnecessary but eliminates a warning. */
+
+ set = FALSE;
+
+ for (list = group->item_list; list; list = list->next)
+ {
+ child = list->data;
+
+ if (!NAUTILUS_IS_CANVAS_ITEM (child))
+ {
+ continue;
+ }
+
+ if (child->flags & EEL_CANVAS_ITEM_VISIBLE)
+ {
+ set = TRUE;
+ if (!NAUTILUS_IS_CANVAS_ITEM (child) ||
+ usage == BOUNDS_USAGE_FOR_DISPLAY)
+ {
+ eel_canvas_item_get_bounds (child, &minx, &miny, &maxx, &maxy);
+ }
+ else if (usage == BOUNDS_USAGE_FOR_LAYOUT)
+ {
+ nautilus_canvas_item_get_bounds_for_layout (NAUTILUS_CANVAS_ITEM (child),
+ &minx, &miny, &maxx, &maxy);
+ }
+ else if (usage == BOUNDS_USAGE_FOR_ENTIRE_ITEM)
+ {
+ nautilus_canvas_item_get_bounds_for_entire_item (NAUTILUS_CANVAS_ITEM (child),
+ &minx, &miny, &maxx, &maxy);
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+ break;
+ }
+ }
+
+ /* If there were no visible items, return an empty bounding box */
+
+ if (!set)
+ {
+ *x1 = *y1 = *x2 = *y2 = 0.0;
+ return;
+ }
+
+ /* Now we can grow the bounds using the rest of the items */
+
+ list = list->next;
+
+ for (; list; list = list->next)
+ {
+ child = list->data;
+
+ if (!NAUTILUS_IS_CANVAS_ITEM (child))
+ {
+ continue;
+ }
+
+ if (!(child->flags & EEL_CANVAS_ITEM_VISIBLE))
+ {
+ continue;
+ }
+
+ if (!NAUTILUS_IS_CANVAS_ITEM (child) ||
+ usage == BOUNDS_USAGE_FOR_DISPLAY)
+ {
+ eel_canvas_item_get_bounds (child, &tx1, &ty1, &tx2, &ty2);
+ }
+ else if (usage == BOUNDS_USAGE_FOR_LAYOUT)
+ {
+ nautilus_canvas_item_get_bounds_for_layout (NAUTILUS_CANVAS_ITEM (child),
+ &tx1, &ty1, &tx2, &ty2);
+ }
+ else if (usage == BOUNDS_USAGE_FOR_ENTIRE_ITEM)
+ {
+ nautilus_canvas_item_get_bounds_for_entire_item (NAUTILUS_CANVAS_ITEM (child),
+ &tx1, &ty1, &tx2, &ty2);
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ if (tx1 < minx)
+ {
+ minx = tx1;
+ }
+
+ if (ty1 < miny)
+ {
+ miny = ty1;
+ }
+
+ if (tx2 > maxx)
+ {
+ maxx = tx2;
+ }
+
+ if (ty2 > maxy)
+ {
+ maxy = ty2;
+ }
+ }
+
+ /* Make the bounds be relative to our parent's coordinate system */
+
+ if (EEL_CANVAS_ITEM (group)->parent)
+ {
+ minx += group->xpos;
+ miny += group->ypos;
+ maxx += group->xpos;
+ maxy += group->ypos;
+ }
+
+ if (x1 != NULL)
+ {
+ *x1 = minx;
+ }
+
+ if (y1 != NULL)
+ {
+ *y1 = miny;
+ }
+
+ if (x2 != NULL)
+ {
+ *x2 = maxx;
+ }
+
+ if (y2 != NULL)
+ {
+ *y2 = maxy;
+ }
+}
+
+static void
+get_all_icon_bounds (NautilusCanvasContainer *container,
+ double *x1,
+ double *y1,
+ double *x2,
+ double *y2,
+ NautilusCanvasItemBoundsUsage usage)
+{
+ /* FIXME bugzilla.gnome.org 42477: Do we have to do something about the rubberband
+ * here? Any other non-icon items?
+ */
+ get_icon_bounds_for_canvas_bounds (EEL_CANVAS_GROUP (EEL_CANVAS (container)->root),
+ x1, y1, x2, y2, usage);
+}
+
+void
+nautilus_canvas_container_update_scroll_region (NautilusCanvasContainer *container)
+{
+ double x1, y1, x2, y2;
+ double pixels_per_unit;
+ GtkAdjustment *hadj, *vadj;
+ float step_increment;
+ GtkAllocation allocation;
+
+ pixels_per_unit = EEL_CANVAS (container)->pixels_per_unit;
+
+ get_all_icon_bounds (container, &x1, &y1, &x2, &y2, BOUNDS_USAGE_FOR_ENTIRE_ITEM);
+
+ /* Add border at the "end"of the layout (i.e. after the icons), to
+ * ensure we get some space when scrolled to the end.
+ */
+ y2 += ICON_PAD_BOTTOM + CONTAINER_PAD_BOTTOM;
+
+ /* Auto-layout assumes a 0, 0 scroll origin and at least allocation->width.
+ * Then we lay out to the right or to the left, so
+ * x can be < 0 and > allocation */
+ gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
+ x1 = MIN (x1, 0);
+ x2 = MAX (x2, allocation.width / pixels_per_unit);
+ y1 = 0;
+
+ x2 -= 1;
+ x2 = MAX (x1, x2);
+
+ y2 -= 1;
+ y2 = MAX (y1, y2);
+
+ eel_canvas_set_scroll_region (EEL_CANVAS (container), x1, y1, x2, y2);
+
+ hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container));
+ vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container));
+
+ /* Scroll by 1/4 icon each time you click. */
+ step_increment = nautilus_canvas_container_get_icon_size_for_zoom_level
+ (container->details->zoom_level) / 4;
+ if (gtk_adjustment_get_step_increment (hadj) != step_increment)
+ {
+ gtk_adjustment_set_step_increment (hadj, step_increment);
+ }
+ if (gtk_adjustment_get_step_increment (vadj) != step_increment)
+ {
+ gtk_adjustment_set_step_increment (vadj, step_increment);
+ }
+}
+
+static void
+cache_icon_positions (NautilusCanvasContainer *container)
+{
+ GList *l;
+ gint idx;
+ NautilusCanvasIcon *icon;
+
+ for (l = container->details->icons, idx = 0; l != NULL; l = l->next)
+ {
+ icon = l->data;
+ icon->position = idx++;
+ }
+}
+
+static int
+compare_icons_data (gconstpointer a,
+ gconstpointer b,
+ gpointer canvas_container)
+{
+ NautilusCanvasContainerClass *klass;
+ NautilusCanvasIconData *data_a, *data_b;
+
+ data_a = (NautilusCanvasIconData *) a;
+ data_b = (NautilusCanvasIconData *) b;
+ klass = NAUTILUS_CANVAS_CONTAINER_GET_CLASS (canvas_container);
+
+ return klass->compare_icons (canvas_container, data_a, data_b);
+}
+
+static int
+compare_icons (gconstpointer a,
+ gconstpointer b,
+ gpointer canvas_container)
+{
+ NautilusCanvasContainerClass *klass;
+ const NautilusCanvasIcon *icon_a, *icon_b;
+
+ icon_a = a;
+ icon_b = b;
+ klass = NAUTILUS_CANVAS_CONTAINER_GET_CLASS (canvas_container);
+
+ return klass->compare_icons (canvas_container, icon_a->data, icon_b->data);
+}
+
+static void
+sort_selection (NautilusCanvasContainer *container)
+{
+ container->details->selection = g_list_sort_with_data (container->details->selection,
+ compare_icons_data,
+ container);
+ container->details->selection_needs_resort = FALSE;
+}
+
+static void
+sort_icons (NautilusCanvasContainer *container,
+ GList **icons)
+{
+ NautilusCanvasContainerClass *klass;
+
+ klass = NAUTILUS_CANVAS_CONTAINER_GET_CLASS (container);
+ g_assert (klass->compare_icons != NULL);
+
+ *icons = g_list_sort_with_data (*icons, compare_icons, container);
+}
+
+static void
+resort (NautilusCanvasContainer *container)
+{
+ sort_icons (container, &container->details->icons);
+ sort_selection (container);
+ cache_icon_positions (container);
+}
+
+typedef struct
+{
+ double width;
+ double height;
+ double x_offset;
+ double y_offset;
+} IconPositions;
+
+static void
+lay_down_one_line (NautilusCanvasContainer *container,
+ GList *line_start,
+ GList *line_end,
+ double y,
+ double max_height,
+ GArray *positions,
+ gboolean whole_text)
+{
+ GList *p;
+ NautilusCanvasIcon *icon;
+ double x, ltr_icon_x, icon_x, y_offset;
+ IconPositions *position;
+ int i;
+ gboolean is_rtl;
+
+ is_rtl = nautilus_canvas_container_is_layout_rtl (container);
+
+ /* Lay out the icons along the baseline. */
+ x = ICON_PAD_LEFT;
+ i = 0;
+ for (p = line_start; p != line_end; p = p->next)
+ {
+ icon = p->data;
+
+ position = &g_array_index (positions, IconPositions, i++);
+ ltr_icon_x = x + position->x_offset;
+ icon_x = is_rtl ? get_mirror_x_position (container, icon, ltr_icon_x) : ltr_icon_x;
+ y_offset = position->y_offset;
+
+ icon_set_position (icon, icon_x, y + y_offset);
+ nautilus_canvas_item_set_entire_text (icon->item, whole_text);
+
+ icon->saved_ltr_x = is_rtl ? ltr_icon_x : icon->x;
+
+ x += position->width;
+ }
+}
+
+static void
+lay_down_icons_horizontal (NautilusCanvasContainer *container,
+ GList *icons,
+ double start_y)
+{
+ GList *p, *line_start;
+ NautilusCanvasIcon *icon;
+ double canvas_width, y;
+ double available_width;
+ GArray *positions;
+ IconPositions *position;
+ EelDRect bounds;
+ EelDRect icon_bounds;
+ double max_height_above, max_height_below;
+ double height_above, height_below;
+ double line_width;
+ double min_grid_width;
+ double grid_width;
+ double num_columns;
+ double icon_width, icon_size;
+ int i;
+ GtkAllocation allocation;
+
+ g_assert (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ /* We can't get the right allocation if the size hasn't been allocated yet */
+ g_return_if_fail (container->details->has_been_allocated);
+
+ if (icons == NULL)
+ {
+ return;
+ }
+
+ positions = g_array_new (FALSE, FALSE, sizeof (IconPositions));
+ gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
+
+ /* Lay out icons a line at a time. */
+ canvas_width = CANVAS_WIDTH (container, allocation);
+ min_grid_width = nautilus_canvas_container_get_grid_size_for_zoom_level (container->details->zoom_level);
+ icon_size = nautilus_canvas_container_get_icon_size_for_zoom_level (container->details->zoom_level);
+
+ /* Subtracting 1.0 adds some room for error to prevent the jitter due to
+ * the code not being able to decide how many columns should be there, as
+ * "double" is not perfectly precise and increasing the size of the the
+ * window by one pixel could well make it so that the space taken by the
+ * icons and the padding is actually greater than the canvas with by like
+ * 0.01, causing an entire column to be dropped unnecessarily. This fix is
+ * adapted from Nemo.
+ */
+ available_width = MAX (1.0, canvas_width - ICON_PAD_LEFT - ICON_PAD_RIGHT - 1.0);
+ num_columns = MAX (1.0, floor (available_width / min_grid_width));
+
+ if (g_list_nth (icons, num_columns) != NULL)
+ {
+ grid_width = available_width / num_columns;
+ }
+ else
+ {
+ /* It does not look good when the icons jump around when new columns are
+ * added or removed to the grid while there is only one line. It does
+ * not look good either when the icons do not move at all when the
+ * window is resized.
+ *
+ * To do this, we first compute the maximum extra fraction we can add to
+ * the grid width. Adding this much, however, would simply distribute
+ * the icons evenly, which looks bad when there's a wide window with
+ * only a few icons.
+ *
+ * To fix this, we need to apply a function to the fraction which never
+ * makes it larger and instead makes its growth slow down quickly but
+ * smoothly as the window gets wider and wider. Here's the function used
+ * by this code:
+ *
+ * f(x) = ∜(x + 1) - 1
+ *
+ * The +1 and -1 are there to skip the 0 to 1 part of ∜ where it makes
+ * the number larger.
+ */
+
+ double num_icons = MAX (1.0, g_list_length (icons));
+
+ double used_width = num_icons * min_grid_width;
+ double unused_width = available_width - used_width;
+
+ double max_extra_fraction = (unused_width / num_icons) / min_grid_width;
+ double extra_fraction = pow (max_extra_fraction + 1.0, 1.0 / 4.0) - 1.0;
+
+ grid_width = min_grid_width * (1 + extra_fraction);
+ }
+
+ grid_width = MAX (min_grid_width, grid_width);
+
+ line_width = 0;
+ line_start = icons;
+ y = start_y + CONTAINER_PAD_TOP;
+ i = 0;
+
+ max_height_above = 0;
+ max_height_below = 0;
+ for (p = icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+
+ /* Assume it's only one level hierarchy to avoid costly affine calculations */
+ nautilus_canvas_item_get_bounds_for_layout (icon->item,
+ &bounds.x0, &bounds.y0,
+ &bounds.x1, &bounds.y1);
+
+ /* Normalize the icon width to the grid unit.
+ * Use the icon size for this zoom level too in the calculation, since
+ * the actual bounds might be smaller - e.g. because we have a very
+ * narrow thumbnail.
+ */
+ icon_width = ceil (MAX ((bounds.x1 - bounds.x0), icon_size) / grid_width) * grid_width;
+
+ /* Calculate size above/below baseline */
+ icon_bounds = nautilus_canvas_item_get_icon_rectangle (icon->item);
+ height_above = icon_bounds.y1 - bounds.y0;
+ height_below = bounds.y1 - icon_bounds.y1;
+
+ /* If this icon doesn't fit, it's time to lay out the line that's queued up. */
+ if (line_start != p && line_width + icon_width >= canvas_width)
+ {
+ /* Advance to the baseline. */
+ y += ICON_PAD_TOP + max_height_above;
+
+ lay_down_one_line (container, line_start, p, y, max_height_above, positions, FALSE);
+
+ /* Advance to next line. */
+ y += max_height_below + ICON_PAD_BOTTOM;
+
+ line_width = 0;
+ line_start = p;
+ i = 0;
+
+ max_height_above = height_above;
+ max_height_below = height_below;
+ }
+ else
+ {
+ if (height_above > max_height_above)
+ {
+ max_height_above = height_above;
+ }
+ if (height_below > max_height_below)
+ {
+ max_height_below = height_below;
+ }
+ }
+
+ g_array_set_size (positions, i + 1);
+ position = &g_array_index (positions, IconPositions, i++);
+ position->width = icon_width;
+ position->height = icon_bounds.y1 - icon_bounds.y0;
+
+ position->x_offset = (icon_width - (icon_bounds.x1 - icon_bounds.x0)) / 2;
+ position->y_offset = icon_bounds.y0 - icon_bounds.y1;
+
+ /* Add this icon. */
+ line_width += icon_width;
+ }
+
+ /* Lay down that last line of icons. */
+ if (line_start != NULL)
+ {
+ /* Advance to the baseline. */
+ y += ICON_PAD_TOP + max_height_above;
+
+ lay_down_one_line (container, line_start, NULL, y, max_height_above, positions, FALSE);
+ }
+
+ g_array_free (positions, TRUE);
+}
+
+static double
+get_mirror_x_position (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon,
+ double x)
+{
+ EelDRect icon_bounds;
+ GtkAllocation allocation;
+
+ gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
+ icon_bounds = nautilus_canvas_item_get_icon_rectangle (icon->item);
+
+ return CANVAS_WIDTH (container, allocation) - x - (icon_bounds.x1 - icon_bounds.x0);
+}
+
+static void
+nautilus_canvas_container_set_rtl_positions (NautilusCanvasContainer *container)
+{
+ GList *l;
+ NautilusCanvasIcon *icon;
+ double x;
+
+ if (!container->details->icons)
+ {
+ return;
+ }
+
+ for (l = container->details->icons; l != NULL; l = l->next)
+ {
+ icon = l->data;
+ x = get_mirror_x_position (container, icon, icon->saved_ltr_x);
+ icon_set_position (icon, x, icon->y);
+ }
+}
+
+static void
+lay_down_icons (NautilusCanvasContainer *container,
+ GList *icons,
+ double start_y)
+{
+ lay_down_icons_horizontal (container, icons, start_y);
+}
+
+static void
+redo_layout_internal (NautilusCanvasContainer *container)
+{
+ gboolean layout_possible;
+
+ layout_possible = finish_adding_new_icons (container);
+ if (!layout_possible)
+ {
+ schedule_redo_layout (container);
+ return;
+ }
+
+ if (container->details->needs_resort)
+ {
+ resort (container);
+ container->details->needs_resort = FALSE;
+ }
+ lay_down_icons (container, container->details->icons, 0);
+
+ if (nautilus_canvas_container_is_layout_rtl (container))
+ {
+ nautilus_canvas_container_set_rtl_positions (container);
+ }
+
+ nautilus_canvas_container_update_scroll_region (container);
+
+ process_pending_icon_to_reveal (container);
+ nautilus_canvas_container_update_visible_icons (container);
+}
+
+static gboolean
+redo_layout_callback (gpointer callback_data)
+{
+ NautilusCanvasContainer *container;
+
+ container = NAUTILUS_CANVAS_CONTAINER (callback_data);
+ redo_layout_internal (container);
+ container->details->idle_id = 0;
+
+ return FALSE;
+}
+
+static void
+unschedule_redo_layout (NautilusCanvasContainer *container)
+{
+ if (container->details->idle_id != 0)
+ {
+ g_source_remove (container->details->idle_id);
+ container->details->idle_id = 0;
+ }
+}
+
+static void
+schedule_redo_layout (NautilusCanvasContainer *container)
+{
+ if (container->details->idle_id == 0
+ && container->details->has_been_allocated)
+ {
+ container->details->idle_id = g_idle_add
+ (redo_layout_callback, container);
+ }
+}
+
+static void
+redo_layout (NautilusCanvasContainer *container)
+{
+ unschedule_redo_layout (container);
+ /* We can't lay out if the size hasn't been allocated yet; wait for it to
+ * be and then we will be called again from size_allocate ()
+ */
+ if (container->details->has_been_allocated)
+ {
+ redo_layout_internal (container);
+ }
+}
+
+/* Container-level icon handling functions. */
+
+static gboolean
+button_event_modifies_selection (GdkEventButton *event)
+{
+ return (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) != 0;
+}
+
+/* invalidate the cached label sizes for all the icons */
+static void
+invalidate_label_sizes (NautilusCanvasContainer *container)
+{
+ GList *p;
+ NautilusCanvasIcon *icon;
+
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+
+ nautilus_canvas_item_invalidate_label_size (icon->item);
+ }
+}
+
+static gboolean
+select_range (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon1,
+ NautilusCanvasIcon *icon2,
+ gboolean unselect_outside_range)
+{
+ gboolean selection_changed;
+ GList *p;
+ NautilusCanvasIcon *icon;
+ NautilusCanvasIcon *unmatched_icon;
+ gboolean select;
+
+ selection_changed = FALSE;
+
+ unmatched_icon = NULL;
+ select = FALSE;
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+
+ if (unmatched_icon == NULL)
+ {
+ if (icon == icon1)
+ {
+ unmatched_icon = icon2;
+ select = TRUE;
+ }
+ else if (icon == icon2)
+ {
+ unmatched_icon = icon1;
+ select = TRUE;
+ }
+ }
+
+ if (select || unselect_outside_range)
+ {
+ selection_changed |= icon_set_selected
+ (container, icon, select);
+ }
+
+ if (unmatched_icon != NULL && icon == unmatched_icon)
+ {
+ select = FALSE;
+ }
+ }
+ return selection_changed;
+}
+
+
+static gboolean
+select_one_unselect_others (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon_to_select)
+{
+ gboolean selection_changed;
+ GList *p;
+ NautilusCanvasIcon *icon;
+
+ selection_changed = FALSE;
+
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+
+ selection_changed |= icon_set_selected
+ (container, icon, icon == icon_to_select);
+ }
+
+ if (selection_changed && icon_to_select != NULL)
+ {
+ reveal_icon (container, icon_to_select);
+ }
+ return selection_changed;
+}
+
+static gboolean
+unselect_all (NautilusCanvasContainer *container)
+{
+ return select_one_unselect_others (container, NULL);
+}
+
+/* Implementation of rubberband selection. */
+static void
+rubberband_select (NautilusCanvasContainer *container,
+ const EelDRect *current_rect)
+{
+ GList *p;
+ gboolean selection_changed, is_in, canvas_rect_calculated;
+ NautilusCanvasIcon *icon;
+ EelIRect canvas_rect;
+ EelCanvas *canvas;
+
+ selection_changed = FALSE;
+ canvas_rect_calculated = FALSE;
+
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+
+ if (!canvas_rect_calculated)
+ {
+ /* Only do this calculation once, since all the canvas items
+ * we are interating are in the same coordinate space
+ */
+ canvas = EEL_CANVAS_ITEM (icon->item)->canvas;
+ eel_canvas_w2c (canvas,
+ current_rect->x0,
+ current_rect->y0,
+ &canvas_rect.x0,
+ &canvas_rect.y0);
+ eel_canvas_w2c (canvas,
+ current_rect->x1,
+ current_rect->y1,
+ &canvas_rect.x1,
+ &canvas_rect.y1);
+ canvas_rect_calculated = TRUE;
+ }
+
+ is_in = nautilus_canvas_item_hit_test_rectangle (icon->item, canvas_rect);
+
+ selection_changed |= icon_set_selected
+ (container, icon,
+ is_in ^ icon->was_selected_before_rubberband);
+ }
+
+ if (selection_changed)
+ {
+ g_signal_emit (container,
+ signals[SELECTION_CHANGED], 0);
+ }
+}
+
+static int
+rubberband_timeout_callback (gpointer data)
+{
+ NautilusCanvasContainer *container;
+ GtkWidget *widget;
+ NautilusCanvasRubberbandInfo *band_info;
+ int x, y;
+ double x1, y1, x2, y2;
+ double world_x, world_y;
+ int x_scroll, y_scroll;
+ int adj_x, adj_y;
+ gboolean adj_changed;
+ GtkAllocation allocation;
+
+ EelDRect selection_rect;
+
+ widget = GTK_WIDGET (data);
+ container = NAUTILUS_CANVAS_CONTAINER (data);
+ band_info = &container->details->rubberband_info;
+
+ g_assert (band_info->timer_id != 0);
+
+ adj_changed = FALSE;
+ gtk_widget_get_allocation (widget, &allocation);
+
+ adj_x = gtk_adjustment_get_value (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container)));
+ if (adj_x != band_info->last_adj_x)
+ {
+ band_info->last_adj_x = adj_x;
+ adj_changed = TRUE;
+ }
+
+ adj_y = gtk_adjustment_get_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container)));
+ if (adj_y != band_info->last_adj_y)
+ {
+ band_info->last_adj_y = adj_y;
+ adj_changed = TRUE;
+ }
+
+ gdk_window_get_device_position (gtk_widget_get_window (widget),
+ band_info->device,
+ &x, &y, NULL);
+
+ if (x < RUBBERBAND_SCROLL_THRESHOLD)
+ {
+ x_scroll = x - RUBBERBAND_SCROLL_THRESHOLD;
+ x = 0;
+ }
+ else if (x >= allocation.width - RUBBERBAND_SCROLL_THRESHOLD)
+ {
+ x_scroll = x - allocation.width + RUBBERBAND_SCROLL_THRESHOLD + 1;
+ x = allocation.width - 1;
+ }
+ else
+ {
+ x_scroll = 0;
+ }
+
+ if (y < RUBBERBAND_SCROLL_THRESHOLD)
+ {
+ y_scroll = y - RUBBERBAND_SCROLL_THRESHOLD;
+ y = 0;
+ }
+ else if (y >= allocation.height - RUBBERBAND_SCROLL_THRESHOLD)
+ {
+ y_scroll = y - allocation.height + RUBBERBAND_SCROLL_THRESHOLD + 1;
+ y = allocation.height - 1;
+ }
+ else
+ {
+ y_scroll = 0;
+ }
+
+ if (y_scroll == 0 && x_scroll == 0
+ && (int) band_info->prev_x == x && (int) band_info->prev_y == y && !adj_changed)
+ {
+ return TRUE;
+ }
+
+ nautilus_canvas_container_scroll (container, x_scroll, y_scroll);
+
+ /* Remember to convert from widget to scrolled window coords */
+ eel_canvas_window_to_world (EEL_CANVAS (container),
+ x + gtk_adjustment_get_value (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container))),
+ y + gtk_adjustment_get_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container))),
+ &world_x, &world_y);
+
+ if (world_x < band_info->start_x)
+ {
+ x1 = world_x;
+ x2 = band_info->start_x;
+ }
+ else
+ {
+ x1 = band_info->start_x;
+ x2 = world_x;
+ }
+
+ if (world_y < band_info->start_y)
+ {
+ y1 = world_y;
+ y2 = band_info->start_y;
+ }
+ else
+ {
+ y1 = band_info->start_y;
+ y2 = world_y;
+ }
+
+ /* Don't let the area of the selection rectangle be empty.
+ * Aside from the fact that it would be funny when the rectangle disappears,
+ * this also works around a crash in libart that happens sometimes when a
+ * zero height rectangle is passed.
+ */
+ x2 = MAX (x1 + 1, x2);
+ y2 = MAX (y1 + 1, y2);
+
+ eel_canvas_item_set
+ (band_info->selection_rectangle,
+ "x1", x1, "y1", y1,
+ "x2", x2, "y2", y2,
+ NULL);
+
+ selection_rect.x0 = x1;
+ selection_rect.y0 = y1;
+ selection_rect.x1 = x2;
+ selection_rect.y1 = y2;
+
+ rubberband_select (container,
+ &selection_rect);
+
+ band_info->prev_x = x;
+ band_info->prev_y = y;
+
+ return TRUE;
+}
+
+static void
+stop_rubberbanding (NautilusCanvasContainer *container,
+ GdkEventButton *event);
+
+static void
+start_rubberbanding (NautilusCanvasContainer *container,
+ GdkEventButton *event)
+{
+ AtkObject *accessible;
+ NautilusCanvasContainerDetails *details;
+ NautilusCanvasRubberbandInfo *band_info;
+ GList *p;
+ NautilusCanvasIcon *icon;
+
+ details = container->details;
+ band_info = &details->rubberband_info;
+
+ if (band_info->active)
+ {
+ g_debug ("Canceling active rubberband by device %s", gdk_device_get_name (band_info->device));
+ stop_rubberbanding (container, NULL);
+ }
+
+ g_signal_emit (container,
+ signals[BAND_SELECT_STARTED], 0);
+
+ band_info->device = event->device;
+
+ for (p = details->icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+ icon->was_selected_before_rubberband = icon->is_selected;
+ }
+
+ eel_canvas_window_to_world
+ (EEL_CANVAS (container), event->x, event->y,
+ &band_info->start_x, &band_info->start_y);
+
+ band_info->selection_rectangle = eel_canvas_item_new
+ (eel_canvas_root
+ (EEL_CANVAS (container)),
+ NAUTILUS_TYPE_SELECTION_CANVAS_ITEM,
+ "x1", band_info->start_x,
+ "y1", band_info->start_y,
+ "x2", band_info->start_x,
+ "y2", band_info->start_y,
+ NULL);
+
+ accessible = atk_gobject_accessible_for_object
+ (G_OBJECT (band_info->selection_rectangle));
+ atk_object_set_name (accessible, "selection");
+ atk_object_set_description (accessible, _("The selection rectangle"));
+
+ band_info->prev_x = event->x - gtk_adjustment_get_value (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container)));
+ band_info->prev_y = event->y - gtk_adjustment_get_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container)));
+
+ band_info->active = TRUE;
+
+ if (band_info->timer_id == 0)
+ {
+ band_info->timer_id = g_timeout_add
+ (RUBBERBAND_TIMEOUT_INTERVAL,
+ rubberband_timeout_callback,
+ container);
+ }
+
+ eel_canvas_item_grab (band_info->selection_rectangle,
+ (GDK_POINTER_MOTION_MASK
+ | GDK_BUTTON_RELEASE_MASK
+ | GDK_SCROLL_MASK),
+ NULL,
+ (GdkEvent *) event);
+}
+
+static void
+stop_rubberbanding (NautilusCanvasContainer *container,
+ GdkEventButton *event)
+{
+ NautilusCanvasRubberbandInfo *band_info;
+ GList *icons;
+ gboolean enable_animation;
+
+ band_info = &container->details->rubberband_info;
+
+ if (event != NULL && event->device != band_info->device)
+ {
+ return;
+ }
+
+ g_assert (band_info->timer_id != 0);
+ g_source_remove (band_info->timer_id);
+ band_info->timer_id = 0;
+
+ band_info->active = FALSE;
+
+ band_info->device = NULL;
+
+ g_object_get (gtk_settings_get_default (), "gtk-enable-animations", &enable_animation, NULL);
+
+ /* Destroy this canvas item; the parent will unref it. */
+ eel_canvas_item_ungrab (band_info->selection_rectangle);
+ eel_canvas_item_lower_to_bottom (band_info->selection_rectangle);
+ eel_canvas_item_destroy (band_info->selection_rectangle);
+ band_info->selection_rectangle = NULL;
+
+ /* if only one item has been selected, use it as range
+ * selection base (cf. handle_icon_button_press) */
+ icons = nautilus_canvas_container_get_selected_icons (container);
+ if (g_list_length (icons) == 1)
+ {
+ container->details->range_selection_base_icon = icons->data;
+ }
+ g_list_free (icons);
+
+ g_signal_emit (container,
+ signals[BAND_SELECT_ENDED], 0);
+}
+
+/* Keyboard navigation. */
+
+typedef gboolean (*IsBetterCanvasFunction) (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *start_icon,
+ NautilusCanvasIcon *best_so_far,
+ NautilusCanvasIcon *candidate,
+ void *data);
+
+static NautilusCanvasIcon *
+find_best_icon (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *start_icon,
+ IsBetterCanvasFunction function,
+ void *data)
+{
+ GList *p;
+ NautilusCanvasIcon *best, *candidate;
+
+ best = NULL;
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ candidate = p->data;
+
+ if (candidate != start_icon)
+ {
+ if ((*function)(container, start_icon, best, candidate, data))
+ {
+ best = candidate;
+ }
+ }
+ }
+ return best;
+}
+
+static NautilusCanvasIcon *
+find_best_selected_icon (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *start_icon,
+ IsBetterCanvasFunction function,
+ void *data)
+{
+ GList *p;
+ NautilusCanvasIcon *best, *candidate;
+
+ best = NULL;
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ candidate = p->data;
+
+ if (candidate != start_icon && candidate->is_selected)
+ {
+ if ((*function)(container, start_icon, best, candidate, data))
+ {
+ best = candidate;
+ }
+ }
+ }
+ return best;
+}
+
+static int
+compare_icons_by_uri (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon_a,
+ NautilusCanvasIcon *icon_b)
+{
+ char *uri_a, *uri_b;
+ int result;
+
+ g_assert (NAUTILUS_IS_CANVAS_CONTAINER (container));
+ g_assert (icon_a != NULL);
+ g_assert (icon_b != NULL);
+ g_assert (icon_a != icon_b);
+
+ uri_a = nautilus_canvas_container_get_icon_uri (container, icon_a);
+ uri_b = nautilus_canvas_container_get_icon_uri (container, icon_b);
+ result = strcmp (uri_a, uri_b);
+ g_assert (result != 0);
+ g_free (uri_a);
+ g_free (uri_b);
+
+ return result;
+}
+
+static int
+get_cmp_point_x (NautilusCanvasContainer *container,
+ EelDRect icon_rect)
+{
+ return (icon_rect.x0 + icon_rect.x1) / 2;
+}
+
+static int
+get_cmp_point_y (NautilusCanvasContainer *container,
+ EelDRect icon_rect)
+{
+ return icon_rect.y1;
+}
+
+
+static int
+compare_icons_horizontal (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon_a,
+ NautilusCanvasIcon *icon_b)
+{
+ EelDRect world_rect;
+ int ax, bx;
+
+ world_rect = nautilus_canvas_item_get_icon_rectangle (icon_a->item);
+ eel_canvas_w2c
+ (EEL_CANVAS (container),
+ get_cmp_point_x (container, world_rect),
+ get_cmp_point_y (container, world_rect),
+ &ax,
+ NULL);
+ world_rect = nautilus_canvas_item_get_icon_rectangle (icon_b->item);
+ eel_canvas_w2c
+ (EEL_CANVAS (container),
+ get_cmp_point_x (container, world_rect),
+ get_cmp_point_y (container, world_rect),
+ &bx,
+ NULL);
+
+ if (ax < bx)
+ {
+ return -1;
+ }
+ if (ax > bx)
+ {
+ return +1;
+ }
+ return 0;
+}
+
+static int
+compare_icons_vertical (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon_a,
+ NautilusCanvasIcon *icon_b)
+{
+ EelDRect world_rect;
+ int ay, by;
+
+ world_rect = nautilus_canvas_item_get_icon_rectangle (icon_a->item);
+ eel_canvas_w2c
+ (EEL_CANVAS (container),
+ get_cmp_point_x (container, world_rect),
+ get_cmp_point_y (container, world_rect),
+ NULL,
+ &ay);
+ world_rect = nautilus_canvas_item_get_icon_rectangle (icon_b->item);
+ eel_canvas_w2c
+ (EEL_CANVAS (container),
+ get_cmp_point_x (container, world_rect),
+ get_cmp_point_y (container, world_rect),
+ NULL,
+ &by);
+
+ if (ay < by)
+ {
+ return -1;
+ }
+ if (ay > by)
+ {
+ return +1;
+ }
+ return 0;
+}
+
+static int
+compare_icons_horizontal_first (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon_a,
+ NautilusCanvasIcon *icon_b)
+{
+ EelDRect world_rect;
+ int ax, ay, bx, by;
+
+ world_rect = nautilus_canvas_item_get_icon_rectangle (icon_a->item);
+ eel_canvas_w2c
+ (EEL_CANVAS (container),
+ get_cmp_point_x (container, world_rect),
+ get_cmp_point_y (container, world_rect),
+ &ax,
+ &ay);
+ world_rect = nautilus_canvas_item_get_icon_rectangle (icon_b->item);
+ eel_canvas_w2c
+ (EEL_CANVAS (container),
+ get_cmp_point_x (container, world_rect),
+ get_cmp_point_y (container, world_rect),
+ &bx,
+ &by);
+
+ if (ax < bx)
+ {
+ return -1;
+ }
+ if (ax > bx)
+ {
+ return +1;
+ }
+ if (ay < by)
+ {
+ return -1;
+ }
+ if (ay > by)
+ {
+ return +1;
+ }
+ return compare_icons_by_uri (container, icon_a, icon_b);
+}
+
+static int
+compare_icons_vertical_first (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon_a,
+ NautilusCanvasIcon *icon_b)
+{
+ EelDRect world_rect;
+ int ax, ay, bx, by;
+
+ world_rect = nautilus_canvas_item_get_icon_rectangle (icon_a->item);
+ eel_canvas_w2c
+ (EEL_CANVAS (container),
+ get_cmp_point_x (container, world_rect),
+ get_cmp_point_y (container, world_rect),
+ &ax,
+ &ay);
+ world_rect = nautilus_canvas_item_get_icon_rectangle (icon_b->item);
+ eel_canvas_w2c
+ (EEL_CANVAS (container),
+ get_cmp_point_x (container, world_rect),
+ get_cmp_point_y (container, world_rect),
+ &bx,
+ &by);
+
+ if (ay < by)
+ {
+ return -1;
+ }
+ if (ay > by)
+ {
+ return +1;
+ }
+ if (ax < bx)
+ {
+ return -1;
+ }
+ if (ax > bx)
+ {
+ return +1;
+ }
+ return compare_icons_by_uri (container, icon_a, icon_b);
+}
+
+static gboolean
+leftmost_in_top_row (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *start_icon,
+ NautilusCanvasIcon *best_so_far,
+ NautilusCanvasIcon *candidate,
+ void *data)
+{
+ if (best_so_far == NULL)
+ {
+ return TRUE;
+ }
+ return compare_icons_vertical_first (container, best_so_far, candidate) > 0;
+}
+
+static gboolean
+rightmost_in_top_row (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *start_icon,
+ NautilusCanvasIcon *best_so_far,
+ NautilusCanvasIcon *candidate,
+ void *data)
+{
+ if (best_so_far == NULL)
+ {
+ return TRUE;
+ }
+ return compare_icons_vertical (container, best_so_far, candidate) > 0;
+ return compare_icons_horizontal (container, best_so_far, candidate) < 0;
+}
+
+static gboolean
+rightmost_in_bottom_row (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *start_icon,
+ NautilusCanvasIcon *best_so_far,
+ NautilusCanvasIcon *candidate,
+ void *data)
+{
+ if (best_so_far == NULL)
+ {
+ return TRUE;
+ }
+ return compare_icons_vertical_first (container, best_so_far, candidate) < 0;
+}
+
+static int
+compare_with_start_row (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon)
+{
+ EelCanvasItem *item;
+
+ item = EEL_CANVAS_ITEM (icon->item);
+
+ if (container->details->arrow_key_start_y < item->y1)
+ {
+ return -1;
+ }
+ if (container->details->arrow_key_start_y > item->y2)
+ {
+ return +1;
+ }
+ return 0;
+}
+
+static int
+compare_with_start_column (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon)
+{
+ EelCanvasItem *item;
+
+ item = EEL_CANVAS_ITEM (icon->item);
+
+ if (container->details->arrow_key_start_x < item->x1)
+ {
+ return -1;
+ }
+ if (container->details->arrow_key_start_x > item->x2)
+ {
+ return +1;
+ }
+ return 0;
+}
+
+static gboolean
+same_row_right_side_leftmost (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *start_icon,
+ NautilusCanvasIcon *best_so_far,
+ NautilusCanvasIcon *candidate,
+ void *data)
+{
+ /* Candidates not on the start row do not qualify. */
+ if (compare_with_start_row (container, candidate) != 0)
+ {
+ return FALSE;
+ }
+
+ /* Candidates that are farther right lose out. */
+ if (best_so_far != NULL)
+ {
+ if (compare_icons_horizontal_first (container,
+ best_so_far,
+ candidate) < 0)
+ {
+ return FALSE;
+ }
+ }
+
+ /* Candidate to the left of the start do not qualify. */
+ if (compare_icons_horizontal_first (container,
+ candidate,
+ start_icon) <= 0)
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+same_row_left_side_rightmost (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *start_icon,
+ NautilusCanvasIcon *best_so_far,
+ NautilusCanvasIcon *candidate,
+ void *data)
+{
+ /* Candidates not on the start row do not qualify. */
+ if (compare_with_start_row (container, candidate) != 0)
+ {
+ return FALSE;
+ }
+
+ /* Candidates that are farther left lose out. */
+ if (best_so_far != NULL)
+ {
+ if (compare_icons_horizontal_first (container,
+ best_so_far,
+ candidate) > 0)
+ {
+ return FALSE;
+ }
+ }
+
+ /* Candidate to the right of the start do not qualify. */
+ if (compare_icons_horizontal_first (container,
+ candidate,
+ start_icon) >= 0)
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+next_row_leftmost (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *start_icon,
+ NautilusCanvasIcon *best_so_far,
+ NautilusCanvasIcon *candidate,
+ void *data)
+{
+ /* sort out icons that are not below the current row */
+ if (compare_with_start_row (container, candidate) >= 0)
+ {
+ return FALSE;
+ }
+
+ if (best_so_far != NULL)
+ {
+ if (compare_icons_vertical_first (container,
+ best_so_far,
+ candidate) > 0)
+ {
+ /* candidate is above best choice, but below the current row */
+ return TRUE;
+ }
+
+ if (compare_icons_horizontal_first (container,
+ best_so_far,
+ candidate) > 0)
+ {
+ return TRUE;
+ }
+ }
+
+ return best_so_far == NULL;
+}
+
+static gboolean
+next_row_rightmost (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *start_icon,
+ NautilusCanvasIcon *best_so_far,
+ NautilusCanvasIcon *candidate,
+ void *data)
+{
+ /* sort out icons that are not below the current row */
+ if (compare_with_start_row (container, candidate) >= 0)
+ {
+ return FALSE;
+ }
+
+ if (best_so_far != NULL)
+ {
+ if (compare_icons_vertical_first (container,
+ best_so_far,
+ candidate) > 0)
+ {
+ /* candidate is above best choice, but below the current row */
+ return TRUE;
+ }
+
+ if (compare_icons_horizontal_first (container,
+ best_so_far,
+ candidate) < 0)
+ {
+ return TRUE;
+ }
+ }
+
+ return best_so_far == NULL;
+}
+
+static gboolean
+previous_row_rightmost (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *start_icon,
+ NautilusCanvasIcon *best_so_far,
+ NautilusCanvasIcon *candidate,
+ void *data)
+{
+ /* sort out icons that are not above the current row */
+ if (compare_with_start_row (container, candidate) <= 0)
+ {
+ return FALSE;
+ }
+
+ if (best_so_far != NULL)
+ {
+ if (compare_icons_vertical_first (container,
+ best_so_far,
+ candidate) < 0)
+ {
+ /* candidate is below the best choice, but above the current row */
+ return TRUE;
+ }
+
+ if (compare_icons_horizontal_first (container,
+ best_so_far,
+ candidate) < 0)
+ {
+ return TRUE;
+ }
+ }
+
+ return best_so_far == NULL;
+}
+
+static gboolean
+same_column_above_lowest (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *start_icon,
+ NautilusCanvasIcon *best_so_far,
+ NautilusCanvasIcon *candidate,
+ void *data)
+{
+ /* Candidates not on the start column do not qualify. */
+ if (compare_with_start_column (container, candidate) != 0)
+ {
+ return FALSE;
+ }
+
+ /* Candidates that are higher lose out. */
+ if (best_so_far != NULL)
+ {
+ if (compare_icons_vertical_first (container,
+ best_so_far,
+ candidate) > 0)
+ {
+ return FALSE;
+ }
+ }
+
+ /* Candidates below the start do not qualify. */
+ if (compare_icons_vertical_first (container,
+ candidate,
+ start_icon) >= 0)
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+same_column_below_highest (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *start_icon,
+ NautilusCanvasIcon *best_so_far,
+ NautilusCanvasIcon *candidate,
+ void *data)
+{
+ /* Candidates not on the start column do not qualify. */
+ if (compare_with_start_column (container, candidate) != 0)
+ {
+ return FALSE;
+ }
+
+ /* Candidates that are lower lose out. */
+ if (best_so_far != NULL)
+ {
+ if (compare_icons_vertical_first (container,
+ best_so_far,
+ candidate) < 0)
+ {
+ return FALSE;
+ }
+ }
+
+ /* Candidates above the start do not qualify. */
+ if (compare_icons_vertical_first (container,
+ candidate,
+ start_icon) <= 0)
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+closest_in_90_degrees (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *start_icon,
+ NautilusCanvasIcon *best_so_far,
+ NautilusCanvasIcon *candidate,
+ void *data)
+{
+ EelDRect world_rect;
+ int x, y;
+ int dx, dy;
+ int dist;
+ int *best_dist;
+
+
+ world_rect = nautilus_canvas_item_get_icon_rectangle (candidate->item);
+ eel_canvas_w2c
+ (EEL_CANVAS (container),
+ get_cmp_point_x (container, world_rect),
+ get_cmp_point_y (container, world_rect),
+ &x,
+ &y);
+
+ dx = x - container->details->arrow_key_start_x;
+ dy = y - container->details->arrow_key_start_y;
+
+ switch (container->details->arrow_key_direction)
+ {
+ case GTK_DIR_UP:
+ {
+ if (dy > 0 ||
+ ABS (dx) > ABS (dy))
+ {
+ return FALSE;
+ }
+ }
+ break;
+
+ case GTK_DIR_DOWN:
+ {
+ if (dy < 0 ||
+ ABS (dx) > ABS (dy))
+ {
+ return FALSE;
+ }
+ }
+ break;
+
+ case GTK_DIR_LEFT:
+ {
+ if (dx > 0 ||
+ ABS (dy) > ABS (dx))
+ {
+ return FALSE;
+ }
+ }
+ break;
+
+ case GTK_DIR_RIGHT:
+ {
+ if (dx < 0 ||
+ ABS (dy) > ABS (dx))
+ {
+ return FALSE;
+ }
+ }
+ break;
+
+ default:
+ {
+ g_assert_not_reached ();
+ }
+ break;
+ }
+
+ dist = dx * dx + dy * dy;
+ best_dist = data;
+
+ if (best_so_far == NULL)
+ {
+ *best_dist = dist;
+ return TRUE;
+ }
+
+ if (dist < *best_dist)
+ {
+ *best_dist = dist;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static EelDRect
+get_rubberband (NautilusCanvasIcon *icon1,
+ NautilusCanvasIcon *icon2)
+{
+ EelDRect rect1;
+ EelDRect rect2;
+ EelDRect ret;
+
+ eel_canvas_item_get_bounds (EEL_CANVAS_ITEM (icon1->item),
+ &rect1.x0, &rect1.y0,
+ &rect1.x1, &rect1.y1);
+ eel_canvas_item_get_bounds (EEL_CANVAS_ITEM (icon2->item),
+ &rect2.x0, &rect2.y0,
+ &rect2.x1, &rect2.y1);
+
+ eel_drect_union (&ret, &rect1, &rect2);
+
+ return ret;
+}
+
+static void
+keyboard_move_to (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon,
+ NautilusCanvasIcon *from,
+ GdkEventKey *event)
+{
+ if (icon == NULL)
+ {
+ return;
+ }
+
+ set_focus (container, icon, TRUE);
+
+ if (event != NULL &&
+ (event->state & GDK_CONTROL_MASK) != 0 &&
+ (event->state & GDK_SHIFT_MASK) == 0)
+ {
+ clear_keyboard_rubberband_start (container);
+ }
+ else if (event != NULL &&
+ (event->state & GDK_CONTROL_MASK) != 0 &&
+ (event->state & GDK_SHIFT_MASK) != 0)
+ {
+ /* Do rubberband selection */
+ EelDRect rect;
+
+ if (from && !container->details->keyboard_rubberband_start)
+ {
+ set_keyboard_rubberband_start (container, from);
+ }
+
+ if (icon && container->details->keyboard_rubberband_start)
+ {
+ rect = get_rubberband (container->details->keyboard_rubberband_start,
+ icon);
+ rubberband_select (container, &rect);
+ }
+ }
+ else if (event != NULL &&
+ (event->state & GDK_CONTROL_MASK) == 0 &&
+ (event->state & GDK_SHIFT_MASK) != 0)
+ {
+ /* Select range */
+ NautilusCanvasIcon *start_icon;
+
+ start_icon = container->details->range_selection_base_icon;
+ if (start_icon == NULL || !start_icon->is_selected)
+ {
+ start_icon = icon;
+ container->details->range_selection_base_icon = icon;
+ }
+
+ if (select_range (container, start_icon, icon, TRUE))
+ {
+ g_signal_emit (container,
+ signals[SELECTION_CHANGED], 0);
+ }
+ }
+ else
+ {
+ /* Select icon. */
+ clear_keyboard_rubberband_start (container);
+
+ container->details->range_selection_base_icon = icon;
+ if (select_one_unselect_others (container, icon))
+ {
+ g_signal_emit (container,
+ signals[SELECTION_CHANGED], 0);
+ }
+ }
+ schedule_keyboard_icon_reveal (container, icon);
+}
+
+static void
+keyboard_home (NautilusCanvasContainer *container,
+ GdkEventKey *event)
+{
+ NautilusCanvasIcon *from;
+ NautilusCanvasIcon *to;
+
+ /* Home selects the first canvas.
+ * Control-Home sets the keyboard focus to the first canvas.
+ */
+
+ from = find_best_selected_icon (container, NULL,
+ rightmost_in_bottom_row,
+ NULL);
+ to = find_best_icon (container, NULL, leftmost_in_top_row, NULL);
+
+ keyboard_move_to (container, to, from, event);
+}
+
+static void
+keyboard_end (NautilusCanvasContainer *container,
+ GdkEventKey *event)
+{
+ NautilusCanvasIcon *to;
+ NautilusCanvasIcon *from;
+
+ /* End selects the last canvas.
+ * Control-End sets the keyboard focus to the last canvas.
+ */
+ from = find_best_selected_icon (container, NULL,
+ leftmost_in_top_row,
+ NULL);
+ to = find_best_icon (container, NULL, rightmost_in_bottom_row, NULL);
+
+ keyboard_move_to (container, to, from, event);
+}
+
+static void
+record_arrow_key_start (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon,
+ GtkDirectionType direction)
+{
+ EelDRect world_rect;
+
+ world_rect = nautilus_canvas_item_get_icon_rectangle (icon->item);
+ eel_canvas_w2c
+ (EEL_CANVAS (container),
+ get_cmp_point_x (container, world_rect),
+ get_cmp_point_y (container, world_rect),
+ &container->details->arrow_key_start_x,
+ &container->details->arrow_key_start_y);
+ container->details->arrow_key_direction = direction;
+}
+
+static void
+keyboard_arrow_key (NautilusCanvasContainer *container,
+ GdkEventKey *event,
+ GtkDirectionType direction,
+ IsBetterCanvasFunction better_start,
+ IsBetterCanvasFunction empty_start,
+ IsBetterCanvasFunction better_destination,
+ IsBetterCanvasFunction better_destination_fallback,
+ IsBetterCanvasFunction better_destination_fallback_fallback,
+ IsBetterCanvasFunction better_destination_manual)
+{
+ NautilusCanvasIcon *from;
+ NautilusCanvasIcon *to;
+ int data;
+
+ /* Chose the icon to start with.
+ * If we have a keyboard focus, start with it.
+ * Otherwise, use the single selected icon.
+ * If there's multiple selection, use the icon farthest toward the end.
+ */
+
+ from = container->details->focus;
+
+ if (from == NULL)
+ {
+ if (has_multiple_selection (container))
+ {
+ if (all_selected (container))
+ {
+ from = find_best_selected_icon
+ (container, NULL,
+ empty_start, NULL);
+ }
+ else
+ {
+ from = find_best_selected_icon
+ (container, NULL,
+ better_start, NULL);
+ }
+ }
+ else
+ {
+ from = get_first_selected_icon (container);
+ }
+ }
+
+ /* If there's no icon, select the icon farthest toward the end.
+ * If there is an icon, select the next icon based on the arrow direction.
+ */
+ if (from == NULL)
+ {
+ to = from = find_best_icon
+ (container, NULL,
+ empty_start, NULL);
+ }
+ else
+ {
+ record_arrow_key_start (container, from, direction);
+
+ to = find_best_icon
+ (container, from,
+ better_destination,
+ &data);
+
+ /* Wrap around to next/previous row/column */
+ if (to == NULL &&
+ better_destination_fallback != NULL)
+ {
+ to = find_best_icon
+ (container, from,
+ better_destination_fallback,
+ &data);
+ }
+
+ /* With a layout like
+ * 1 2 3
+ * 4
+ * (horizontal layout)
+ *
+ * or
+ *
+ * 1 4
+ * 2
+ * 3
+ * (vertical layout)
+ *
+ * * pressing down for any of 1,2,3 (horizontal layout)
+ * * pressing right for any of 1,2,3 (vertical layout)
+ *
+ * Should select 4.
+ */
+ if (to == NULL &&
+ better_destination_fallback_fallback != NULL)
+ {
+ to = find_best_icon
+ (container, from,
+ better_destination_fallback_fallback,
+ &data);
+ }
+
+ if (to == NULL)
+ {
+ to = from;
+ }
+ }
+
+ keyboard_move_to (container, to, from, event);
+}
+
+static gboolean
+is_rectangle_selection_event (GdkEventKey *event)
+{
+ return event != NULL &&
+ (event->state & GDK_CONTROL_MASK) != 0 &&
+ (event->state & GDK_SHIFT_MASK) != 0;
+}
+
+static void
+keyboard_right (NautilusCanvasContainer *container,
+ GdkEventKey *event)
+{
+ IsBetterCanvasFunction fallback;
+
+ fallback = NULL;
+ if (!is_rectangle_selection_event (event))
+ {
+ fallback = next_row_leftmost;
+ }
+
+ /* Right selects the next icon in the same row.
+ * Control-Right sets the keyboard focus to the next icon in the same row.
+ */
+ keyboard_arrow_key (container,
+ event,
+ GTK_DIR_RIGHT,
+ rightmost_in_bottom_row,
+ nautilus_canvas_container_is_layout_rtl (container) ?
+ rightmost_in_top_row : leftmost_in_top_row,
+ same_row_right_side_leftmost,
+ fallback,
+ NULL,
+ closest_in_90_degrees);
+}
+
+static void
+keyboard_left (NautilusCanvasContainer *container,
+ GdkEventKey *event)
+{
+ IsBetterCanvasFunction fallback;
+
+ fallback = NULL;
+ if (!is_rectangle_selection_event (event))
+ {
+ fallback = previous_row_rightmost;
+ }
+
+ /* Left selects the next icon in the same row.
+ * Control-Left sets the keyboard focus to the next icon in the same row.
+ */
+ keyboard_arrow_key (container,
+ event,
+ GTK_DIR_LEFT,
+ rightmost_in_bottom_row,
+ nautilus_canvas_container_is_layout_rtl (container) ?
+ rightmost_in_top_row : leftmost_in_top_row,
+ same_row_left_side_rightmost,
+ fallback,
+ NULL,
+ closest_in_90_degrees);
+}
+
+static void
+keyboard_down (NautilusCanvasContainer *container,
+ GdkEventKey *event)
+{
+ IsBetterCanvasFunction next_row_fallback;
+
+ next_row_fallback = NULL;
+ if (gtk_widget_get_direction (GTK_WIDGET (container)) == GTK_TEXT_DIR_RTL)
+ {
+ next_row_fallback = next_row_leftmost;
+ }
+ else
+ {
+ next_row_fallback = next_row_rightmost;
+ }
+
+ /* Down selects the next icon in the same column.
+ * Control-Down sets the keyboard focus to the next icon in the same column.
+ */
+ keyboard_arrow_key (container,
+ event,
+ GTK_DIR_DOWN,
+ rightmost_in_bottom_row,
+ nautilus_canvas_container_is_layout_rtl (container) ?
+ rightmost_in_top_row : leftmost_in_top_row,
+ same_column_below_highest,
+ NULL,
+ next_row_fallback,
+ closest_in_90_degrees);
+}
+
+static void
+keyboard_up (NautilusCanvasContainer *container,
+ GdkEventKey *event)
+{
+ /* Up selects the next icon in the same column.
+ * Control-Up sets the keyboard focus to the next icon in the same column.
+ */
+ keyboard_arrow_key (container,
+ event,
+ GTK_DIR_UP,
+ rightmost_in_bottom_row,
+ nautilus_canvas_container_is_layout_rtl (container) ?
+ rightmost_in_top_row : leftmost_in_top_row,
+ same_column_above_lowest,
+ NULL,
+ NULL,
+ closest_in_90_degrees);
+}
+
+void
+nautilus_canvas_container_preview_selection_event (NautilusCanvasContainer *container,
+ GtkDirectionType direction)
+{
+ if (direction == GTK_DIR_UP)
+ {
+ keyboard_up (container, NULL);
+ }
+ else if (direction == GTK_DIR_DOWN)
+ {
+ keyboard_down (container, NULL);
+ }
+ else if (direction == GTK_DIR_LEFT)
+ {
+ keyboard_left (container, NULL);
+ }
+ else if (direction == GTK_DIR_RIGHT)
+ {
+ keyboard_right (container, NULL);
+ }
+}
+
+static void
+keyboard_space (NautilusCanvasContainer *container,
+ GdkEventKey *event)
+{
+ NautilusCanvasIcon *icon;
+
+ if (!has_selection (container) &&
+ container->details->focus != NULL)
+ {
+ keyboard_move_to (container,
+ container->details->focus,
+ NULL, NULL);
+ }
+ else if ((event->state & GDK_CONTROL_MASK) != 0 &&
+ (event->state & GDK_SHIFT_MASK) == 0)
+ {
+ /* Control-space toggles the selection state of the current icon. */
+ if (container->details->focus != NULL)
+ {
+ icon_toggle_selected (container, container->details->focus);
+ g_signal_emit (container, signals[SELECTION_CHANGED], 0);
+ if (container->details->focus->is_selected)
+ {
+ container->details->range_selection_base_icon = container->details->focus;
+ }
+ }
+ else
+ {
+ icon = find_best_selected_icon (container,
+ NULL,
+ leftmost_in_top_row,
+ NULL);
+ if (icon == NULL)
+ {
+ icon = find_best_icon (container,
+ NULL,
+ leftmost_in_top_row,
+ NULL);
+ }
+ if (icon != NULL)
+ {
+ set_focus (container, icon, TRUE);
+ }
+ }
+ }
+ else if ((event->state & GDK_SHIFT_MASK) != 0)
+ {
+ activate_selected_items_alternate (container, NULL);
+ }
+ else
+ {
+ preview_selected_items (container);
+ }
+}
+
+static void
+destroy (GtkWidget *object)
+{
+ NautilusCanvasContainer *container;
+
+ container = NAUTILUS_CANVAS_CONTAINER (object);
+
+ nautilus_canvas_container_clear (container);
+
+ if (container->details->rubberband_info.timer_id != 0)
+ {
+ g_source_remove (container->details->rubberband_info.timer_id);
+ container->details->rubberband_info.timer_id = 0;
+ }
+
+ if (container->details->idle_id != 0)
+ {
+ g_source_remove (container->details->idle_id);
+ container->details->idle_id = 0;
+ }
+
+ if (container->details->align_idle_id != 0)
+ {
+ g_source_remove (container->details->align_idle_id);
+ container->details->align_idle_id = 0;
+ }
+
+ if (container->details->selection_changed_id != 0)
+ {
+ g_source_remove (container->details->selection_changed_id);
+ container->details->selection_changed_id = 0;
+ }
+
+ if (container->details->size_allocation_count_id != 0)
+ {
+ g_source_remove (container->details->size_allocation_count_id);
+ container->details->size_allocation_count_id = 0;
+ }
+
+ GTK_WIDGET_CLASS (nautilus_canvas_container_parent_class)->destroy (object);
+}
+
+static void
+finalize (GObject *object)
+{
+ NautilusCanvasContainerDetails *details;
+
+ details = NAUTILUS_CANVAS_CONTAINER (object)->details;
+
+ g_signal_handlers_disconnect_by_func (nautilus_icon_view_preferences,
+ text_ellipsis_limit_changed_container_callback,
+ object);
+
+ g_hash_table_destroy (details->icon_set);
+ details->icon_set = NULL;
+
+ g_free (details->font);
+
+ if (details->a11y_item_action_queue != NULL)
+ {
+ while (!g_queue_is_empty (details->a11y_item_action_queue))
+ {
+ g_free (g_queue_pop_head (details->a11y_item_action_queue));
+ }
+ g_queue_free (details->a11y_item_action_queue);
+ }
+ if (details->a11y_item_action_idle_handler != 0)
+ {
+ g_source_remove (details->a11y_item_action_idle_handler);
+ }
+
+ g_free (details);
+
+ G_OBJECT_CLASS (nautilus_canvas_container_parent_class)->finalize (object);
+}
+
+/* GtkWidget methods. */
+
+static gboolean
+clear_size_allocation_count (gpointer data)
+{
+ NautilusCanvasContainer *container;
+
+ container = NAUTILUS_CANVAS_CONTAINER (data);
+
+ container->details->size_allocation_count_id = 0;
+ container->details->size_allocation_count = 0;
+
+ return FALSE;
+}
+
+static void
+size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ NautilusCanvasContainer *container;
+ gboolean need_layout_redone;
+ GtkAllocation wid_allocation;
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+
+ need_layout_redone = !container->details->has_been_allocated;
+ gtk_widget_get_allocation (widget, &wid_allocation);
+
+ if (allocation->width != wid_allocation.width)
+ {
+ need_layout_redone = TRUE;
+ }
+
+ if (allocation->height != wid_allocation.height)
+ {
+ need_layout_redone = TRUE;
+ }
+
+ /* Under some conditions we can end up in a loop when size allocating.
+ * This happens when the icons don't fit without a scrollbar, but fits
+ * when a scrollbar is added (bug #129963 for details).
+ * We keep track of this looping by increasing a counter in size_allocate
+ * and clearing it in a high-prio idle (the only way to detect the loop is
+ * done).
+ * When we've done at more than two iterations (with/without scrollbar)
+ * we terminate this looping by not redoing the layout when the width
+ * is wider than the current one (i.e when removing the scrollbar).
+ */
+ if (container->details->size_allocation_count_id == 0)
+ {
+ container->details->size_allocation_count_id =
+ g_idle_add_full (G_PRIORITY_HIGH,
+ clear_size_allocation_count,
+ container, NULL);
+ }
+ container->details->size_allocation_count++;
+ if (container->details->size_allocation_count > 2 &&
+ allocation->width >= wid_allocation.width)
+ {
+ need_layout_redone = FALSE;
+ }
+
+ GTK_WIDGET_CLASS (nautilus_canvas_container_parent_class)->size_allocate (widget, allocation);
+
+ container->details->has_been_allocated = TRUE;
+
+ if (need_layout_redone)
+ {
+ redo_layout (container);
+ }
+}
+
+static GtkSizeRequestMode
+get_request_mode (GtkWidget *widget)
+{
+ /* Don't trade size at all, since we get whatever we get anyway. */
+ return GTK_SIZE_REQUEST_CONSTANT_SIZE;
+}
+
+/* We need to implement these since the GtkScrolledWindow uses them
+ * to guess whether to show scrollbars or not, and if we don't report
+ * anything it'll tend to get it wrong causing double calls
+ * to size_allocate (at different sizes) during its size allocation. */
+static void
+get_prefered_width (GtkWidget *widget,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ EelCanvasGroup *root;
+ double x1, x2;
+ int cx1, cx2;
+ int width;
+
+ root = eel_canvas_root (EEL_CANVAS (widget));
+ eel_canvas_item_get_bounds (EEL_CANVAS_ITEM (root),
+ &x1, NULL, &x2, NULL);
+ eel_canvas_w2c (EEL_CANVAS (widget), x1, 0, &cx1, NULL);
+ eel_canvas_w2c (EEL_CANVAS (widget), x2, 0, &cx2, NULL);
+
+ width = cx2 - cx1;
+ if (natural_size)
+ {
+ *natural_size = width;
+ }
+ if (minimum_size)
+ {
+ *minimum_size = width;
+ }
+}
+
+static void
+get_prefered_height (GtkWidget *widget,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ EelCanvasGroup *root;
+ double y1, y2;
+ int cy1, cy2;
+ int height;
+
+ root = eel_canvas_root (EEL_CANVAS (widget));
+ eel_canvas_item_get_bounds (EEL_CANVAS_ITEM (root),
+ NULL, &y1, NULL, &y2);
+ eel_canvas_w2c (EEL_CANVAS (widget), 0, y1, NULL, &cy1);
+ eel_canvas_w2c (EEL_CANVAS (widget), 0, y2, NULL, &cy2);
+
+ height = cy2 - cy1;
+ if (natural_size)
+ {
+ *natural_size = height;
+ }
+ if (minimum_size)
+ {
+ *minimum_size = height;
+ }
+}
+
+static void
+realize (GtkWidget *widget)
+{
+ GtkAdjustment *vadj, *hadj;
+ NautilusCanvasContainer *container;
+
+ GTK_WIDGET_CLASS (nautilus_canvas_container_parent_class)->realize (widget);
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+
+ /* Set up DnD. */
+ nautilus_canvas_dnd_init (container);
+
+ hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (widget));
+ g_signal_connect (hadj, "value-changed",
+ G_CALLBACK (handle_hadjustment_changed), widget);
+
+ vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (widget));
+ g_signal_connect (vadj, "value-changed",
+ G_CALLBACK (handle_vadjustment_changed), widget);
+}
+
+static void
+unrealize (GtkWidget *widget)
+{
+ NautilusCanvasContainer *container;
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+
+ nautilus_canvas_dnd_fini (container);
+
+ GTK_WIDGET_CLASS (nautilus_canvas_container_parent_class)->unrealize (widget);
+}
+
+static void
+nautilus_canvas_container_request_update_all_internal (NautilusCanvasContainer *container,
+ gboolean invalidate_labels)
+{
+ GList *node;
+ NautilusCanvasIcon *icon;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ for (node = container->details->icons; node != NULL; node = node->next)
+ {
+ icon = node->data;
+
+ if (invalidate_labels)
+ {
+ nautilus_canvas_item_invalidate_label (icon->item);
+ }
+
+ nautilus_canvas_container_update_icon (container, icon);
+ }
+
+ container->details->needs_resort = TRUE;
+ redo_layout (container);
+}
+
+static void
+style_updated (GtkWidget *widget)
+{
+ NautilusCanvasContainer *container;
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+
+ GTK_WIDGET_CLASS (nautilus_canvas_container_parent_class)->style_updated (widget);
+
+ if (gtk_widget_get_realized (widget))
+ {
+ nautilus_canvas_container_request_update_all_internal (container, TRUE);
+ }
+}
+
+static gboolean
+button_press_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ NautilusCanvasContainer *container;
+ gboolean selection_changed;
+ gboolean return_value;
+ gboolean clicked_on_icon;
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+ container->details->button_down_time = event->time;
+
+ /* Forget about the old keyboard selection now that we've started mousing. */
+ clear_keyboard_rubberband_start (container);
+
+ if (event->type == GDK_2BUTTON_PRESS || event->type == GDK_3BUTTON_PRESS)
+ {
+ /* We use our own double-click detection. */
+ return TRUE;
+ }
+
+ /* Invoke the canvas event handler and see if an item picks up the event. */
+ clicked_on_icon = GTK_WIDGET_CLASS (nautilus_canvas_container_parent_class)->button_press_event (widget, event);
+
+ if (!gtk_widget_has_focus (widget))
+ {
+ gtk_widget_grab_focus (widget);
+ }
+
+ if (clicked_on_icon)
+ {
+ return TRUE;
+ }
+
+ clear_focus (container);
+
+ if (event->button == DRAG_BUTTON &&
+ event->type == GDK_BUTTON_PRESS)
+ {
+ /* Clear the last click icon for double click */
+ container->details->double_click_icon[1] = container->details->double_click_icon[0];
+ container->details->double_click_icon[0] = NULL;
+ }
+
+ /* Button 1 does rubber banding. */
+ if (event->button == RUBBERBAND_BUTTON)
+ {
+ if (!button_event_modifies_selection (event))
+ {
+ selection_changed = unselect_all (container);
+ if (selection_changed)
+ {
+ g_signal_emit (container,
+ signals[SELECTION_CHANGED], 0);
+ }
+ }
+
+ start_rubberbanding (container, event);
+ return TRUE;
+ }
+
+ /* Prevent multi-button weirdness such as bug 6181 */
+ if (container->details->rubberband_info.active)
+ {
+ return TRUE;
+ }
+
+ /* Button 2 may be passed to the window manager. */
+ if (event->button == MIDDLE_BUTTON)
+ {
+ selection_changed = unselect_all (container);
+ if (selection_changed)
+ {
+ g_signal_emit (container, signals[SELECTION_CHANGED], 0);
+ }
+ g_signal_emit (widget, signals[MIDDLE_CLICK], 0, event);
+ return TRUE;
+ }
+
+ /* Button 3 does a contextual menu. */
+ if (event->button == CONTEXTUAL_MENU_BUTTON)
+ {
+ selection_changed = unselect_all (container);
+ if (selection_changed)
+ {
+ g_signal_emit (container, signals[SELECTION_CHANGED], 0);
+ }
+ g_signal_emit (widget, signals[CONTEXT_CLICK_BACKGROUND], 0, event);
+ return TRUE;
+ }
+
+ /* Otherwise, we emit a button_press message. */
+ g_signal_emit (widget,
+ signals[BUTTON_PRESS], 0, event,
+ &return_value);
+ return return_value;
+}
+
+static void
+nautilus_canvas_container_did_not_drag (NautilusCanvasContainer *container,
+ GdkEventButton *event)
+{
+ NautilusCanvasContainerDetails *details;
+ gboolean selection_changed;
+ static gint64 last_click_time = 0;
+ static gint click_count = 0;
+ gint double_click_time;
+ gint64 current_time;
+
+ details = container->details;
+
+ if (details->icon_selected_on_button_down &&
+ ((event->state & GDK_CONTROL_MASK) != 0 ||
+ (event->state & GDK_SHIFT_MASK) == 0))
+ {
+ if (button_event_modifies_selection (event))
+ {
+ details->range_selection_base_icon = NULL;
+ icon_toggle_selected (container, details->drag_icon);
+ g_signal_emit (container,
+ signals[SELECTION_CHANGED], 0);
+ }
+ else
+ {
+ details->range_selection_base_icon = details->drag_icon;
+ selection_changed = select_one_unselect_others
+ (container, details->drag_icon);
+
+ if (selection_changed)
+ {
+ g_signal_emit (container,
+ signals[SELECTION_CHANGED], 0);
+ }
+ }
+ }
+
+ if (details->drag_icon != NULL &&
+ (details->single_click_mode ||
+ event->button == MIDDLE_BUTTON))
+ {
+ /* Determine click count */
+ g_object_get (G_OBJECT (gtk_widget_get_settings (GTK_WIDGET (container))),
+ "gtk-double-click-time", &double_click_time,
+ NULL);
+ current_time = g_get_monotonic_time ();
+ if (current_time - last_click_time < double_click_time * 1000)
+ {
+ click_count++;
+ }
+ else
+ {
+ click_count = 0;
+ }
+
+ /* Stash time for next compare */
+ last_click_time = current_time;
+
+ /* If single-click mode, activate the selected icons, unless modifying
+ * the selection or pressing for a very long time, or double clicking.
+ */
+
+
+ if (click_count == 0 &&
+ event->time - details->button_down_time < MAX_CLICK_TIME &&
+ !button_event_modifies_selection (event))
+ {
+ /* It's a tricky UI issue whether this should activate
+ * just the clicked item (as if it were a link), or all
+ * the selected items (as if you were issuing an "activate
+ * selection" command). For now, we're trying the activate
+ * entire selection version to see how it feels. Note that
+ * NautilusList goes the other way because its "links" seem
+ * much more link-like.
+ */
+ if (event->button == MIDDLE_BUTTON)
+ {
+ activate_selected_items_alternate (container, NULL);
+ }
+ else
+ {
+ activate_selected_items (container);
+ }
+ }
+ }
+}
+
+static gboolean
+clicked_within_double_click_interval (NautilusCanvasContainer *container)
+{
+ static gint64 last_click_time = 0;
+ static gint click_count = 0;
+ gint double_click_time;
+ gint64 current_time;
+
+ /* Determine click count */
+ g_object_get (G_OBJECT (gtk_widget_get_settings (GTK_WIDGET (container))),
+ "gtk-double-click-time", &double_click_time,
+ NULL);
+ current_time = g_get_monotonic_time ();
+ if (current_time - last_click_time < double_click_time * 1000)
+ {
+ click_count++;
+ }
+ else
+ {
+ click_count = 0;
+ }
+
+ /* Stash time for next compare */
+ last_click_time = current_time;
+
+ /* Only allow double click */
+ if (click_count == 1)
+ {
+ click_count = 0;
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+static void
+clear_drag_state (NautilusCanvasContainer *container)
+{
+ container->details->drag_icon = NULL;
+ container->details->drag_state = DRAG_STATE_INITIAL;
+}
+
+static gboolean
+button_release_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ NautilusCanvasContainer *container;
+ NautilusCanvasContainerDetails *details;
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+ details = container->details;
+
+ if (event->button == RUBBERBAND_BUTTON && details->rubberband_info.active)
+ {
+ stop_rubberbanding (container, event);
+ return TRUE;
+ }
+
+ if (event->button == details->drag_button)
+ {
+ details->drag_button = 0;
+
+ switch (details->drag_state)
+ {
+ case DRAG_STATE_MOVE_OR_COPY:
+ {
+ if (!details->drag_started)
+ {
+ nautilus_canvas_container_did_not_drag (container, event);
+ }
+ else
+ {
+ nautilus_canvas_dnd_end_drag (container);
+ DEBUG ("Ending drag from canvas container");
+ }
+ }
+ break;
+
+ default:
+ {
+ }
+ break;
+ }
+
+ clear_drag_state (container);
+ return TRUE;
+ }
+
+ return GTK_WIDGET_CLASS (nautilus_canvas_container_parent_class)->button_release_event (widget, event);
+}
+
+static int
+motion_notify_event (GtkWidget *widget,
+ GdkEventMotion *event)
+{
+ NautilusCanvasContainer *container;
+ NautilusCanvasContainerDetails *details;
+ double world_x, world_y;
+ int canvas_x, canvas_y;
+ GdkDragAction actions;
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+ details = container->details;
+
+ if (details->drag_button != 0)
+ {
+ switch (details->drag_state)
+ {
+ case DRAG_STATE_MOVE_OR_COPY:
+ {
+ if (details->drag_started)
+ {
+ break;
+ }
+
+ eel_canvas_window_to_world
+ (EEL_CANVAS (container), event->x, event->y, &world_x, &world_y);
+
+ if (gtk_drag_check_threshold (widget,
+ details->drag_x,
+ details->drag_y,
+ world_x,
+ world_y))
+ {
+ details->drag_started = TRUE;
+ details->drag_state = DRAG_STATE_MOVE_OR_COPY;
+
+ eel_canvas_w2c (EEL_CANVAS (container),
+ details->drag_x,
+ details->drag_y,
+ &canvas_x,
+ &canvas_y);
+
+ actions = GDK_ACTION_COPY
+ | GDK_ACTION_MOVE
+ | GDK_ACTION_LINK
+ | GDK_ACTION_ASK;
+
+ nautilus_canvas_dnd_begin_drag (container,
+ actions,
+ details->drag_button,
+ event,
+ canvas_x,
+ canvas_y);
+ DEBUG ("Beginning drag from canvas container");
+ }
+ }
+ break;
+
+ default:
+ {
+ }
+ break;
+ }
+ }
+
+ return GTK_WIDGET_CLASS (nautilus_canvas_container_parent_class)->motion_notify_event (widget, event);
+}
+
+static void
+nautilus_canvas_container_get_icon_text (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data,
+ char **editable_text,
+ char **additional_text,
+ gboolean include_invisible)
+{
+ NautilusCanvasContainerClass *klass;
+
+ klass = NAUTILUS_CANVAS_CONTAINER_GET_CLASS (container);
+ g_assert (klass->get_icon_text != NULL);
+
+ klass->get_icon_text (container, data, editable_text, additional_text, include_invisible);
+}
+
+static gboolean
+handle_popups (NautilusCanvasContainer *container,
+ GdkEvent *event,
+ const char *signal)
+{
+ /* ensure we clear the drag state before showing the menu */
+ clear_drag_state (container);
+
+ g_signal_emit_by_name (container, signal, event);
+
+ return TRUE;
+}
+
+static int
+key_press_event (GtkWidget *widget,
+ GdkEventKey *event)
+{
+ NautilusCanvasContainer *container;
+ gboolean handled;
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+ handled = FALSE;
+
+ switch (event->keyval)
+ {
+ case GDK_KEY_Home:
+ case GDK_KEY_KP_Home:
+ {
+ keyboard_home (container, event);
+ handled = TRUE;
+ }
+ break;
+
+ case GDK_KEY_End:
+ case GDK_KEY_KP_End:
+ {
+ keyboard_end (container, event);
+ handled = TRUE;
+ }
+ break;
+
+ case GDK_KEY_Left:
+ case GDK_KEY_KP_Left:
+ {
+ /* Don't eat Alt-Left, as that is used for history browsing */
+ if ((event->state & GDK_MOD1_MASK) == 0)
+ {
+ keyboard_left (container, event);
+ handled = TRUE;
+ }
+ }
+ break;
+
+ case GDK_KEY_Up:
+ case GDK_KEY_KP_Up:
+ {
+ /* Don't eat Alt-Up, as that is used for alt-shift-Up */
+ if ((event->state & GDK_MOD1_MASK) == 0)
+ {
+ keyboard_up (container, event);
+ handled = TRUE;
+ }
+ }
+ break;
+
+ case GDK_KEY_Right:
+ case GDK_KEY_KP_Right:
+ {
+ /* Don't eat Alt-Right, as that is used for history browsing */
+ if ((event->state & GDK_MOD1_MASK) == 0)
+ {
+ keyboard_right (container, event);
+ handled = TRUE;
+ }
+ }
+ break;
+
+ case GDK_KEY_Down:
+ case GDK_KEY_KP_Down:
+ {
+ /* Don't eat Alt-Down, as that is used for Open */
+ if ((event->state & GDK_MOD1_MASK) == 0)
+ {
+ keyboard_down (container, event);
+ handled = TRUE;
+ }
+ }
+ break;
+
+ case GDK_KEY_space:
+ {
+ keyboard_space (container, event);
+ handled = TRUE;
+ }
+ break;
+
+ case GDK_KEY_F10:
+ {
+ /* handle Ctrl+F10 because we want to display the
+ * background popup even if something is selected.
+ * The other cases are handled by the "popup-menu" GtkWidget signal.
+ */
+ if (event->state & GDK_CONTROL_MASK)
+ {
+ handled = handle_popups (container, (GdkEvent *) event,
+ "context_click_background");
+ }
+ }
+ break;
+
+ case GDK_KEY_v:
+ {
+ /* Eat Control + v to not enable type ahead */
+ if ((event->state & GDK_CONTROL_MASK) != 0)
+ {
+ handled = TRUE;
+ }
+ }
+ break;
+
+ default:
+ {
+ }
+ break;
+ }
+
+ if (!handled)
+ {
+ handled = GTK_WIDGET_CLASS (nautilus_canvas_container_parent_class)->key_press_event (widget, event);
+ }
+
+ return handled;
+}
+
+static void
+grab_notify_cb (GtkWidget *widget,
+ gboolean was_grabbed)
+{
+ NautilusCanvasContainer *container;
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+
+ if (container->details->rubberband_info.active &&
+ !was_grabbed)
+ {
+ /* we got a (un)grab-notify during rubberband.
+ * This happens when a new modal dialog shows
+ * up (e.g. authentication or an error). Stop
+ * the rubberbanding so that we can handle the
+ * dialog. */
+ stop_rubberbanding (container, NULL);
+ }
+}
+
+static void
+text_ellipsis_limit_changed_container_callback (gpointer callback_data)
+{
+ NautilusCanvasContainer *container;
+
+ container = NAUTILUS_CANVAS_CONTAINER (callback_data);
+ invalidate_label_sizes (container);
+ schedule_redo_layout (container);
+}
+
+static GObject *
+nautilus_canvas_container_constructor (GType type,
+ guint n_construct_params,
+ GObjectConstructParam *construct_params)
+{
+ NautilusCanvasContainer *container;
+ GObject *object;
+
+ object = G_OBJECT_CLASS (nautilus_canvas_container_parent_class)->constructor
+ (type,
+ n_construct_params,
+ construct_params);
+
+ container = NAUTILUS_CANVAS_CONTAINER (object);
+ g_signal_connect_swapped (nautilus_icon_view_preferences,
+ "changed::" NAUTILUS_PREFERENCES_ICON_VIEW_TEXT_ELLIPSIS_LIMIT,
+ G_CALLBACK (text_ellipsis_limit_changed_container_callback),
+ container);
+
+ return object;
+}
+
+/* Initialization. */
+
+static void
+nautilus_canvas_container_class_init (NautilusCanvasContainerClass *class)
+{
+ GtkWidgetClass *widget_class;
+
+ G_OBJECT_CLASS (class)->constructor = nautilus_canvas_container_constructor;
+ G_OBJECT_CLASS (class)->finalize = finalize;
+
+ /* Signals. */
+
+ signals[SELECTION_CHANGED]
+ = g_signal_new ("selection-changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ selection_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ signals[BUTTON_PRESS]
+ = g_signal_new ("button-press",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ button_press),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_BOOLEAN, 1,
+ GDK_TYPE_EVENT);
+ signals[ACTIVATE]
+ = g_signal_new ("activate",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ activate),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
+ signals[ACTIVATE_ALTERNATE]
+ = g_signal_new ("activate-alternate",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ activate_alternate),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
+ signals[ACTIVATE_PREVIEWER]
+ = g_signal_new ("activate-previewer",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ activate_previewer),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 2,
+ G_TYPE_POINTER, G_TYPE_POINTER);
+ signals[CONTEXT_CLICK_SELECTION]
+ = g_signal_new ("context-click-selection",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ context_click_selection),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
+ signals[CONTEXT_CLICK_BACKGROUND]
+ = g_signal_new ("context-click-background",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ context_click_background),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
+ signals[MIDDLE_CLICK]
+ = g_signal_new ("middle-click",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ middle_click),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
+ signals[GET_ICON_URI]
+ = g_signal_new ("get-icon-uri",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ get_icon_uri),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_STRING, 1,
+ G_TYPE_POINTER);
+ signals[GET_ICON_ACTIVATION_URI]
+ = g_signal_new ("get-icon-activation-uri",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ get_icon_activation_uri),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_STRING, 1,
+ G_TYPE_POINTER);
+ signals[GET_ICON_DROP_TARGET_URI]
+ = g_signal_new ("get-icon-drop-target-uri",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ get_icon_drop_target_uri),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_STRING, 1,
+ G_TYPE_POINTER);
+ signals[MOVE_COPY_ITEMS]
+ = g_signal_new ("move-copy-items",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ move_copy_items),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 3,
+ G_TYPE_POINTER,
+ G_TYPE_POINTER,
+ GDK_TYPE_DRAG_ACTION);
+ signals[HANDLE_NETSCAPE_URL]
+ = g_signal_new ("handle-netscape-url",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ handle_netscape_url),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 3,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ GDK_TYPE_DRAG_ACTION);
+ signals[HANDLE_URI_LIST]
+ = g_signal_new ("handle-uri-list",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ handle_uri_list),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 3,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ GDK_TYPE_DRAG_ACTION);
+ signals[HANDLE_TEXT]
+ = g_signal_new ("handle-text",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ handle_text),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 3,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ GDK_TYPE_DRAG_ACTION);
+ signals[HANDLE_RAW]
+ = g_signal_new ("handle-raw",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ handle_raw),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 5,
+ G_TYPE_POINTER,
+ G_TYPE_INT,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ GDK_TYPE_DRAG_ACTION);
+ signals[HANDLE_HOVER] =
+ g_signal_new ("handle-hover",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ handle_hover),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 1,
+ G_TYPE_STRING);
+ signals[GET_CONTAINER_URI]
+ = g_signal_new ("get-container-uri",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ get_container_uri),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_STRING, 0);
+ signals[BAND_SELECT_STARTED]
+ = g_signal_new ("band-select-started",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ band_select_started),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ signals[BAND_SELECT_ENDED]
+ = g_signal_new ("band-select-ended",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ band_select_ended),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ signals[ICON_ADDED]
+ = g_signal_new ("icon-added",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ icon_added),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1, G_TYPE_POINTER);
+ signals[ICON_REMOVED]
+ = g_signal_new ("icon-removed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ icon_removed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1, G_TYPE_POINTER);
+
+ signals[CLEARED]
+ = g_signal_new ("cleared",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ cleared),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ /* GtkWidget class. */
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->destroy = destroy;
+ widget_class->size_allocate = size_allocate;
+ widget_class->get_request_mode = get_request_mode;
+ widget_class->get_preferred_width = get_prefered_width;
+ widget_class->get_preferred_height = get_prefered_height;
+ widget_class->realize = realize;
+ widget_class->unrealize = unrealize;
+ widget_class->button_press_event = button_press_event;
+ widget_class->button_release_event = button_release_event;
+ widget_class->motion_notify_event = motion_notify_event;
+ widget_class->key_press_event = key_press_event;
+ widget_class->style_updated = style_updated;
+ widget_class->grab_notify = grab_notify_cb;
+
+ gtk_widget_class_set_accessible_type (widget_class, nautilus_canvas_container_accessible_get_type ());
+}
+
+static void
+update_selected (NautilusCanvasContainer *container)
+{
+ GList *node;
+ NautilusCanvasIcon *icon;
+
+ for (node = container->details->icons; node != NULL; node = node->next)
+ {
+ icon = node->data;
+ if (icon->is_selected)
+ {
+ eel_canvas_item_request_update (EEL_CANVAS_ITEM (icon->item));
+ }
+ }
+}
+
+static void
+handle_has_focus_changed (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ update_selected (NAUTILUS_CANVAS_CONTAINER (object));
+}
+
+static void
+handle_scale_factor_changed (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ nautilus_canvas_container_request_update_all_internal (NAUTILUS_CANVAS_CONTAINER (object),
+ TRUE);
+}
+
+
+
+static int text_ellipsis_limits[NAUTILUS_CANVAS_ZOOM_LEVEL_N_ENTRIES];
+
+static gboolean
+get_text_ellipsis_limit_for_zoom (char **strs,
+ const char *zoom_level,
+ int *limit)
+{
+ char **p;
+ char *str;
+ gboolean success;
+
+ success = FALSE;
+
+ /* default */
+ *limit = 3;
+
+ if (zoom_level != NULL)
+ {
+ str = g_strdup_printf ("%s:%%d", zoom_level);
+ }
+ else
+ {
+ str = g_strdup ("%d");
+ }
+
+ if (strs != NULL)
+ {
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+ for (p = strs; *p != NULL; p++)
+ {
+ if (sscanf (*p, str, limit))
+ {
+ success = TRUE;
+ }
+ }
+#pragma GCC diagnostic pop
+ }
+
+ g_free (str);
+
+ return success;
+}
+
+static const char *zoom_level_names[] =
+{
+ "small",
+ "standard",
+ "large",
+};
+
+static void
+text_ellipsis_limit_changed_callback (gpointer callback_data)
+{
+ char **pref;
+ unsigned int i;
+ int one_limit;
+
+ pref = g_settings_get_strv (nautilus_icon_view_preferences,
+ NAUTILUS_PREFERENCES_ICON_VIEW_TEXT_ELLIPSIS_LIMIT);
+
+ /* set default */
+ get_text_ellipsis_limit_for_zoom (pref, NULL, &one_limit);
+ for (i = 0; i < NAUTILUS_CANVAS_ZOOM_LEVEL_N_ENTRIES; i++)
+ {
+ text_ellipsis_limits[i] = one_limit;
+ }
+
+ /* override for each zoom level */
+ for (i = 0; i < G_N_ELEMENTS (zoom_level_names); i++)
+ {
+ if (get_text_ellipsis_limit_for_zoom (pref,
+ zoom_level_names[i],
+ &one_limit))
+ {
+ text_ellipsis_limits[i] = one_limit;
+ }
+ }
+
+ g_strfreev (pref);
+}
+
+static void
+nautilus_canvas_container_init (NautilusCanvasContainer *container)
+{
+ NautilusCanvasContainerDetails *details;
+ static gboolean setup_prefs = FALSE;
+
+ details = g_new0 (NautilusCanvasContainerDetails, 1);
+
+ details->icon_set = g_hash_table_new (g_direct_hash, g_direct_equal);
+ details->zoom_level = NAUTILUS_CANVAS_ZOOM_LEVEL_STANDARD;
+
+ container->details = details;
+
+ g_signal_connect (container, "notify::has-focus",
+ G_CALLBACK (handle_has_focus_changed), NULL);
+ g_signal_connect (container, "notify::scale-factor",
+ G_CALLBACK (handle_scale_factor_changed), NULL);
+
+ if (!setup_prefs)
+ {
+ g_signal_connect_swapped (nautilus_icon_view_preferences,
+ "changed::" NAUTILUS_PREFERENCES_ICON_VIEW_TEXT_ELLIPSIS_LIMIT,
+ G_CALLBACK (text_ellipsis_limit_changed_callback),
+ NULL);
+ text_ellipsis_limit_changed_callback (NULL);
+
+ setup_prefs = TRUE;
+ }
+}
+
+typedef struct
+{
+ NautilusCanvasContainer *container;
+ GdkEventButton *event;
+} ContextMenuParameters;
+
+static gboolean
+handle_canvas_double_click (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon,
+ GdkEventButton *event)
+{
+ NautilusCanvasContainerDetails *details;
+
+ if (event->button != DRAG_BUTTON)
+ {
+ return FALSE;
+ }
+
+ details = container->details;
+
+ if (!details->single_click_mode &&
+ clicked_within_double_click_interval (container) &&
+ details->double_click_icon[0] == details->double_click_icon[1] &&
+ details->double_click_button[0] == details->double_click_button[1])
+ {
+ details->double_clicked = TRUE;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/* NautilusCanvasIcon event handling. */
+
+/* Conceptually, pressing button 1 together with CTRL or SHIFT toggles
+ * selection of a single icon without affecting the other icons;
+ * without CTRL or SHIFT, it selects a single icon and un-selects all
+ * the other icons. But in this latter case, the de-selection should
+ * only happen when the button is released if the icon is already
+ * selected, because the user might select multiple icons and drag all
+ * of them by doing a simple click-drag.
+ */
+
+static gboolean
+handle_canvas_button_press (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon,
+ GdkEventButton *event)
+{
+ NautilusCanvasContainerDetails *details;
+
+ details = container->details;
+
+ if (event->type == GDK_2BUTTON_PRESS || event->type == GDK_3BUTTON_PRESS)
+ {
+ return TRUE;
+ }
+
+ if (event->button != DRAG_BUTTON
+ && event->button != CONTEXTUAL_MENU_BUTTON
+ && event->button != DRAG_MENU_BUTTON)
+ {
+ return TRUE;
+ }
+
+ if ((event->button == DRAG_BUTTON) &&
+ event->type == GDK_BUTTON_PRESS)
+ {
+ /* The next double click has to be on this icon */
+ details->double_click_icon[1] = details->double_click_icon[0];
+ details->double_click_icon[0] = icon;
+
+ details->double_click_button[1] = details->double_click_button[0];
+ details->double_click_button[0] = event->button;
+ }
+
+ if (handle_canvas_double_click (container, icon, event))
+ {
+ /* Double clicking does not trigger a D&D action. */
+ details->drag_button = 0;
+ details->drag_icon = NULL;
+ return TRUE;
+ }
+
+ if (event->button == DRAG_BUTTON
+ || event->button == DRAG_MENU_BUTTON)
+ {
+ details->drag_button = event->button;
+ details->drag_icon = icon;
+ details->drag_x = event->x;
+ details->drag_y = event->y;
+ details->drag_state = DRAG_STATE_MOVE_OR_COPY;
+ details->drag_started = FALSE;
+ }
+
+ /* Modify the selection as appropriate. Selection is modified
+ * the same way for contextual menu as it would be without.
+ */
+ details->icon_selected_on_button_down = icon->is_selected;
+
+ if ((event->button == DRAG_BUTTON || event->button == MIDDLE_BUTTON) &&
+ (event->state & GDK_SHIFT_MASK) != 0)
+ {
+ NautilusCanvasIcon *start_icon;
+
+ set_focus (container, icon, FALSE);
+
+ start_icon = details->range_selection_base_icon;
+ if (start_icon == NULL || !start_icon->is_selected)
+ {
+ start_icon = icon;
+ details->range_selection_base_icon = icon;
+ }
+ if (select_range (container, start_icon, icon,
+ (event->state & GDK_CONTROL_MASK) == 0))
+ {
+ g_signal_emit (container,
+ signals[SELECTION_CHANGED], 0);
+ }
+ }
+ else if (!details->icon_selected_on_button_down)
+ {
+ set_focus (container, icon, FALSE);
+
+ details->range_selection_base_icon = icon;
+ if (button_event_modifies_selection (event))
+ {
+ icon_toggle_selected (container, icon);
+ g_signal_emit (container,
+ signals[SELECTION_CHANGED], 0);
+ }
+ else
+ {
+ select_one_unselect_others (container, icon);
+ g_signal_emit (container,
+ signals[SELECTION_CHANGED], 0);
+ }
+ }
+
+ if (event->button == CONTEXTUAL_MENU_BUTTON)
+ {
+ clear_drag_state (container);
+
+ g_signal_emit (container,
+ signals[CONTEXT_CLICK_SELECTION], 0,
+ event);
+ }
+
+
+ return TRUE;
+}
+
+static int
+item_event_callback (EelCanvasItem *item,
+ GdkEvent *event,
+ gpointer data)
+{
+ NautilusCanvasContainer *container;
+ NautilusCanvasIcon *icon;
+ GdkEventButton *event_button;
+
+ container = NAUTILUS_CANVAS_CONTAINER (data);
+
+ icon = NAUTILUS_CANVAS_ITEM (item)->user_data;
+ g_assert (icon != NULL);
+
+ event_button = &event->button;
+
+ switch (event->type)
+ {
+ case GDK_MOTION_NOTIFY:
+ {
+ return FALSE;
+ }
+
+ case GDK_BUTTON_PRESS:
+ {
+ container->details->double_clicked = FALSE;
+ if (handle_canvas_button_press (container, icon, event_button))
+ {
+ /* Stop the event from being passed along further. Returning
+ * TRUE ain't enough.
+ */
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ case GDK_BUTTON_RELEASE:
+ {
+ if (event_button->button == DRAG_BUTTON
+ && container->details->double_clicked)
+ {
+ if (!button_event_modifies_selection (event_button))
+ {
+ activate_selected_items (container);
+ }
+ else if ((event_button->state & GDK_CONTROL_MASK) == 0 &&
+ (event_button->state & GDK_SHIFT_MASK) != 0)
+ {
+ activate_selected_items_alternate (container, icon);
+ }
+ }
+ /* fall through */
+ }
+
+ default:
+ {
+ container->details->double_clicked = FALSE;
+ return FALSE;
+ }
+ break;
+ }
+}
+
+GtkWidget *
+nautilus_canvas_container_new (void)
+{
+ return gtk_widget_new (NAUTILUS_TYPE_CANVAS_CONTAINER, NULL);
+}
+
+/* Clear all of the icons in the container. */
+void
+nautilus_canvas_container_clear (NautilusCanvasContainer *container)
+{
+ NautilusCanvasContainerDetails *details;
+ GList *p;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ details = container->details;
+
+ if (details->icons == NULL)
+ {
+ return;
+ }
+
+ clear_focus (container);
+ clear_keyboard_rubberband_start (container);
+ unschedule_keyboard_icon_reveal (container);
+ set_pending_icon_to_reveal (container, NULL);
+ details->drop_target = NULL;
+
+ for (p = details->icons; p != NULL; p = p->next)
+ {
+ icon_free (p->data);
+ }
+ g_list_free (details->icons);
+ details->icons = NULL;
+ g_list_free (details->new_icons);
+ details->new_icons = NULL;
+ g_list_free (details->selection);
+ details->selection = NULL;
+
+ g_hash_table_destroy (details->icon_set);
+ details->icon_set = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ nautilus_canvas_container_update_scroll_region (container);
+}
+
+gboolean
+nautilus_canvas_container_is_empty (NautilusCanvasContainer *container)
+{
+ return container->details->icons == NULL;
+}
+
+NautilusCanvasIconData *
+nautilus_canvas_container_get_first_visible_icon (NautilusCanvasContainer *container)
+{
+ GList *l;
+ NautilusCanvasIcon *icon, *best_icon;
+ double x, y;
+ double x1, y1, x2, y2;
+ double *pos, best_pos;
+ double hadj_v, vadj_v, h_page_size;
+ gboolean better_icon;
+ gboolean compare_lt;
+
+ hadj_v = gtk_adjustment_get_value (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container)));
+ vadj_v = gtk_adjustment_get_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container)));
+ h_page_size = gtk_adjustment_get_page_size (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container)));
+
+ if (nautilus_canvas_container_is_layout_rtl (container))
+ {
+ x = hadj_v + h_page_size - ICON_PAD_LEFT - 1;
+ y = vadj_v;
+ }
+ else
+ {
+ x = hadj_v;
+ y = vadj_v;
+ }
+
+ eel_canvas_c2w (EEL_CANVAS (container),
+ x, y,
+ &x, &y);
+
+ l = container->details->icons;
+ best_icon = NULL;
+ best_pos = 0;
+ while (l != NULL)
+ {
+ icon = l->data;
+
+ if (icon_is_positioned (icon))
+ {
+ eel_canvas_item_get_bounds (EEL_CANVAS_ITEM (icon->item),
+ &x1, &y1, &x2, &y2);
+
+ compare_lt = FALSE;
+ pos = &y1;
+ better_icon = y2 > y + ICON_PAD_TOP;
+ if (better_icon)
+ {
+ if (best_icon == NULL)
+ {
+ better_icon = TRUE;
+ }
+ else if (compare_lt)
+ {
+ better_icon = best_pos < *pos;
+ }
+ else
+ {
+ better_icon = best_pos > *pos;
+ }
+
+ if (better_icon)
+ {
+ best_icon = icon;
+ best_pos = *pos;
+ }
+ }
+ }
+
+ l = l->next;
+ }
+
+ return best_icon ? best_icon->data : NULL;
+}
+
+NautilusCanvasIconData *
+nautilus_canvas_container_get_focused_icon (NautilusCanvasContainer *container)
+{
+ NautilusCanvasIcon *icon;
+
+ icon = container->details->focus;
+
+ if (icon != NULL)
+ {
+ return icon->data;
+ }
+
+ return NULL;
+}
+
+/* puts the icon at the top of the screen */
+void
+nautilus_canvas_container_scroll_to_canvas (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data)
+{
+ GList *l;
+ NautilusCanvasIcon *icon;
+ GtkAdjustment *vadj;
+ EelIRect bounds;
+ GtkAllocation allocation;
+
+ vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container));
+ gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
+
+ /* We need to force a relayout now if there are updates queued
+ * since we need the final positions */
+ nautilus_canvas_container_layout_now (container);
+
+ l = container->details->icons;
+ while (l != NULL)
+ {
+ icon = l->data;
+
+ if (icon->data == data &&
+ icon_is_positioned (icon))
+ {
+ /* ensure that we reveal the entire row/column */
+ icon_get_row_and_column_bounds (container, icon, &bounds);
+
+ gtk_adjustment_set_value (vadj, bounds.y0);
+ }
+
+ l = l->next;
+ }
+}
+
+/* Call a function for all the icons. */
+typedef struct
+{
+ NautilusCanvasCallback callback;
+ gpointer callback_data;
+} CallbackAndData;
+
+static void
+call_canvas_callback (gpointer data,
+ gpointer callback_data)
+{
+ NautilusCanvasIcon *icon;
+ CallbackAndData *callback_and_data;
+
+ icon = data;
+ callback_and_data = callback_data;
+ (*callback_and_data->callback)(icon->data, callback_and_data->callback_data);
+}
+
+void
+nautilus_canvas_container_for_each (NautilusCanvasContainer *container,
+ NautilusCanvasCallback callback,
+ gpointer callback_data)
+{
+ CallbackAndData callback_and_data;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ callback_and_data.callback = callback;
+ callback_and_data.callback_data = callback_data;
+
+ g_list_foreach (container->details->icons,
+ call_canvas_callback, &callback_and_data);
+}
+
+static int
+selection_changed_at_idle_callback (gpointer data)
+{
+ NautilusCanvasContainer *container;
+
+ container = NAUTILUS_CANVAS_CONTAINER (data);
+
+ g_signal_emit (container,
+ signals[SELECTION_CHANGED], 0);
+
+ container->details->selection_changed_id = 0;
+ return FALSE;
+}
+
+/* utility routine to remove a single icon from the container */
+
+static void
+icon_destroy (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon)
+{
+ NautilusCanvasContainerDetails *details;
+ gboolean was_selected;
+ NautilusCanvasIcon *icon_to_focus;
+ GList *item;
+
+ details = container->details;
+
+ item = g_list_find (details->icons, icon);
+ item = item->next ? item->next : item->prev;
+ icon_to_focus = (item != NULL) ? item->data : NULL;
+
+ details->icons = g_list_remove (details->icons, icon);
+ details->new_icons = g_list_remove (details->new_icons, icon);
+ details->selection = g_list_remove (details->selection, icon->data);
+ g_hash_table_remove (details->icon_set, icon->data);
+
+ was_selected = icon->is_selected;
+
+ if (details->focus == icon ||
+ details->focus == NULL)
+ {
+ if (icon_to_focus != NULL)
+ {
+ set_focus (container, icon_to_focus, TRUE);
+ }
+ else
+ {
+ clear_focus (container);
+ }
+ }
+
+ if (details->keyboard_rubberband_start == icon)
+ {
+ clear_keyboard_rubberband_start (container);
+ }
+
+ if (details->keyboard_icon_to_reveal == icon)
+ {
+ unschedule_keyboard_icon_reveal (container);
+ }
+ if (details->drag_icon == icon)
+ {
+ clear_drag_state (container);
+ }
+ if (details->drop_target == icon)
+ {
+ details->drop_target = NULL;
+ }
+ if (details->range_selection_base_icon == icon)
+ {
+ details->range_selection_base_icon = NULL;
+ }
+ if (details->pending_icon_to_reveal == icon)
+ {
+ set_pending_icon_to_reveal (container, NULL);
+ }
+
+ icon_free (icon);
+
+ if (was_selected)
+ {
+ /* Coalesce multiple removals causing multiple selection_changed events */
+ details->selection_changed_id = g_idle_add (selection_changed_at_idle_callback, container);
+ }
+}
+
+/* activate any selected items in the container */
+static void
+activate_selected_items (NautilusCanvasContainer *container)
+{
+ GList *selection;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ selection = nautilus_canvas_container_get_selection (container);
+ if (selection != NULL)
+ {
+ g_signal_emit (container,
+ signals[ACTIVATE], 0,
+ selection);
+ }
+ g_list_free (selection);
+}
+
+static void
+preview_selected_items (NautilusCanvasContainer *container)
+{
+ GList *selection;
+ GArray *locations;
+ gint idx;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ selection = nautilus_canvas_container_get_selection (container);
+ locations = nautilus_canvas_container_get_selected_icon_locations (container);
+
+ for (idx = 0; idx < locations->len; idx++)
+ {
+ GdkPoint *point = &(g_array_index (locations, GdkPoint, idx));
+ gint scroll_x, scroll_y;
+
+ eel_canvas_get_scroll_offsets (EEL_CANVAS (container),
+ &scroll_x, &scroll_y);
+
+ point->x -= scroll_x;
+ point->y -= scroll_y;
+ }
+
+ if (selection != NULL)
+ {
+ g_signal_emit (container,
+ signals[ACTIVATE_PREVIEWER], 0,
+ selection, locations);
+ }
+ g_list_free (selection);
+ g_array_unref (locations);
+}
+
+static void
+activate_selected_items_alternate (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon)
+{
+ GList *selection;
+
+ g_assert (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ if (icon != NULL)
+ {
+ selection = g_list_prepend (NULL, icon->data);
+ }
+ else
+ {
+ selection = nautilus_canvas_container_get_selection (container);
+ }
+ if (selection != NULL)
+ {
+ g_signal_emit (container,
+ signals[ACTIVATE_ALTERNATE], 0,
+ selection);
+ }
+ g_list_free (selection);
+}
+
+static NautilusIconInfo *
+nautilus_canvas_container_get_icon_images (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data,
+ int size,
+ gboolean for_drag_accept)
+{
+ NautilusCanvasContainerClass *klass;
+
+ klass = NAUTILUS_CANVAS_CONTAINER_GET_CLASS (container);
+ g_assert (klass->get_icon_images != NULL);
+
+ return klass->get_icon_images (container, data, size, for_drag_accept);
+}
+
+static void
+nautilus_canvas_container_prioritize_thumbnailing (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon)
+{
+ NautilusCanvasContainerClass *klass;
+
+ klass = NAUTILUS_CANVAS_CONTAINER_GET_CLASS (container);
+ g_assert (klass->prioritize_thumbnailing != NULL);
+
+ klass->prioritize_thumbnailing (container, icon->data);
+}
+
+static void
+nautilus_canvas_container_update_visible_icons (NautilusCanvasContainer *container)
+{
+ GtkAdjustment *vadj, *hadj;
+ double min_y, max_y;
+ double min_x, max_x;
+ double x0, y0, x1, y1;
+ GList *node;
+ NautilusCanvasIcon *icon;
+ gboolean visible;
+ GtkAllocation allocation;
+
+ hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container));
+ vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container));
+ gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
+
+ min_x = gtk_adjustment_get_value (hadj);
+ max_x = min_x + allocation.width;
+
+ min_y = gtk_adjustment_get_value (vadj);
+ max_y = min_y + allocation.height;
+
+ eel_canvas_c2w (EEL_CANVAS (container),
+ min_x, min_y, &min_x, &min_y);
+ eel_canvas_c2w (EEL_CANVAS (container),
+ max_x, max_y, &max_x, &max_y);
+
+ /* Do the iteration in reverse to get the render-order from top to
+ * bottom for the prioritized thumbnails.
+ */
+ for (node = g_list_last (container->details->icons); node != NULL; node = node->prev)
+ {
+ icon = node->data;
+
+ if (icon_is_positioned (icon))
+ {
+ eel_canvas_item_get_bounds (EEL_CANVAS_ITEM (icon->item),
+ &x0,
+ &y0,
+ &x1,
+ &y1);
+ eel_canvas_item_i2w (EEL_CANVAS_ITEM (icon->item)->parent,
+ &x0,
+ &y0);
+ eel_canvas_item_i2w (EEL_CANVAS_ITEM (icon->item)->parent,
+ &x1,
+ &y1);
+
+ visible = y1 >= min_y && y0 <= max_y;
+
+ if (visible)
+ {
+ nautilus_canvas_item_set_is_visible (icon->item, TRUE);
+ nautilus_canvas_container_prioritize_thumbnailing (container,
+ icon);
+ }
+ else
+ {
+ nautilus_canvas_item_set_is_visible (icon->item, FALSE);
+ }
+ }
+ }
+}
+
+static void
+handle_vadjustment_changed (GtkAdjustment *adjustment,
+ NautilusCanvasContainer *container)
+{
+ nautilus_canvas_container_update_visible_icons (container);
+}
+
+static void
+handle_hadjustment_changed (GtkAdjustment *adjustment,
+ NautilusCanvasContainer *container)
+{
+ nautilus_canvas_container_update_visible_icons (container);
+}
+
+
+void
+nautilus_canvas_container_update_icon (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon)
+{
+ NautilusCanvasContainerDetails *details;
+ guint icon_size;
+ guint min_image_size, max_image_size;
+ NautilusIconInfo *icon_info;
+ GdkPixbuf *pixbuf;
+ char *editable_text, *additional_text;
+
+ if (icon == NULL)
+ {
+ return;
+ }
+
+ details = container->details;
+
+ /* compute the maximum size based on the scale factor */
+ min_image_size = MINIMUM_IMAGE_SIZE * EEL_CANVAS (container)->pixels_per_unit;
+ max_image_size = MAX (MAXIMUM_IMAGE_SIZE * EEL_CANVAS (container)->pixels_per_unit, NAUTILUS_ICON_MAXIMUM_SIZE);
+
+ /* Get the appropriate images for the file. */
+ icon_get_size (container, icon, &icon_size);
+
+ icon_size = MAX (icon_size, min_image_size);
+ icon_size = MIN (icon_size, max_image_size);
+
+ DEBUG ("Icon size, getting for size %d", icon_size);
+
+ /* Get the icons. */
+ icon_info = nautilus_canvas_container_get_icon_images (container, icon->data, icon_size,
+ icon == details->drop_target);
+
+ pixbuf = nautilus_icon_info_get_pixbuf (icon_info);
+ g_object_unref (icon_info);
+
+ nautilus_canvas_container_get_icon_text (container,
+ icon->data,
+ &editable_text,
+ &additional_text,
+ FALSE);
+
+ eel_canvas_item_set (EEL_CANVAS_ITEM (icon->item),
+ "editable_text", editable_text,
+ "additional_text", additional_text,
+ "highlighted_for_drop", icon == details->drop_target,
+ NULL);
+
+ nautilus_canvas_item_set_image (icon->item, pixbuf);
+
+ /* Let the pixbufs go. */
+ g_object_unref (pixbuf);
+
+ g_free (editable_text);
+ g_free (additional_text);
+}
+
+static void
+finish_adding_icon (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon)
+{
+ nautilus_canvas_container_update_icon (container, icon);
+ eel_canvas_item_show (EEL_CANVAS_ITEM (icon->item));
+
+ g_signal_connect_object (icon->item, "event",
+ G_CALLBACK (item_event_callback), container, 0);
+
+ g_signal_emit (container, signals[ICON_ADDED], 0, icon->data);
+}
+
+static gboolean
+finish_adding_new_icons (NautilusCanvasContainer *container)
+{
+ GList *p, *new_icons;
+
+ new_icons = container->details->new_icons;
+ container->details->new_icons = NULL;
+ container->details->is_populating_container = g_list_length (new_icons) ==
+ g_hash_table_size (container->details->icon_set);
+
+ /* Position most icons (not unpositioned manual-layout icons). */
+ new_icons = g_list_reverse (new_icons);
+ for (p = new_icons; p != NULL; p = p->next)
+ {
+ finish_adding_icon (container, p->data);
+ }
+ g_list_free (new_icons);
+
+ return TRUE;
+}
+
+/**
+ * nautilus_canvas_container_add:
+ * @container: A NautilusCanvasContainer
+ * @data: Icon data.
+ *
+ * Add icon to represent @data to container.
+ * Returns FALSE if there was already such an icon.
+ **/
+gboolean
+nautilus_canvas_container_add (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data)
+{
+ NautilusCanvasContainerDetails *details;
+ NautilusCanvasIcon *icon;
+ EelCanvasItem *band, *item;
+
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container), FALSE);
+ g_return_val_if_fail (data != NULL, FALSE);
+
+ details = container->details;
+
+ if (g_hash_table_lookup (details->icon_set, data) != NULL)
+ {
+ return FALSE;
+ }
+
+ /* Create the new icon, including the canvas item. */
+ icon = g_new0 (NautilusCanvasIcon, 1);
+ icon->data = data;
+ icon->x = ICON_UNPOSITIONED_VALUE;
+ icon->y = ICON_UNPOSITIONED_VALUE;
+
+ /* Whether the saved icon position should only be used
+ * if the previous icon position is free. If the position
+ * is occupied, another position near the last one will
+ */
+ icon->item = NAUTILUS_CANVAS_ITEM
+ (eel_canvas_item_new (EEL_CANVAS_GROUP (EEL_CANVAS (container)->root),
+ nautilus_canvas_item_get_type (),
+ "visible", FALSE,
+ NULL));
+ icon->item->user_data = icon;
+
+ /* Make sure the icon is under the selection_rectangle */
+ item = EEL_CANVAS_ITEM (icon->item);
+ band = NAUTILUS_CANVAS_CONTAINER (item->canvas)->details->rubberband_info.selection_rectangle;
+ if (band)
+ {
+ eel_canvas_item_send_behind (item, band);
+ }
+
+ /* Put it on both lists. */
+ details->icons = g_list_prepend (details->icons, icon);
+ details->new_icons = g_list_prepend (details->new_icons, icon);
+
+ g_hash_table_insert (details->icon_set, data, icon);
+
+ details->needs_resort = TRUE;
+
+ /* Run an idle function to add the icons. */
+ schedule_redo_layout (container);
+
+ return TRUE;
+}
+
+void
+nautilus_canvas_container_layout_now (NautilusCanvasContainer *container)
+{
+ container->details->in_layout_now = TRUE;
+ if (container->details->idle_id != 0)
+ {
+ unschedule_redo_layout (container);
+ redo_layout_internal (container);
+ }
+
+ /* Also need to make sure we're properly resized, for instance
+ * newly added files may trigger a change in the size allocation and
+ * thus toggle scrollbars on */
+ gtk_container_check_resize (GTK_CONTAINER (gtk_widget_get_parent (GTK_WIDGET (container))));
+ container->details->in_layout_now = FALSE;
+}
+
+/**
+ * nautilus_canvas_container_remove:
+ * @container: A NautilusCanvasContainer.
+ * @data: Icon data.
+ *
+ * Remove the icon with this data.
+ **/
+gboolean
+nautilus_canvas_container_remove (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data)
+{
+ NautilusCanvasIcon *icon;
+
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container), FALSE);
+ g_return_val_if_fail (data != NULL, FALSE);
+
+ icon = g_hash_table_lookup (container->details->icon_set, data);
+
+ if (icon == NULL)
+ {
+ return FALSE;
+ }
+
+ icon_destroy (container, icon);
+ schedule_redo_layout (container);
+
+ g_signal_emit (container, signals[ICON_REMOVED], 0, icon);
+
+ return TRUE;
+}
+
+/**
+ * nautilus_canvas_container_request_update:
+ * @container: A NautilusCanvasContainer.
+ * @data: Icon data.
+ *
+ * Update the icon with this data.
+ **/
+void
+nautilus_canvas_container_request_update (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data)
+{
+ NautilusCanvasIcon *icon;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+ g_return_if_fail (data != NULL);
+
+ icon = g_hash_table_lookup (container->details->icon_set, data);
+
+ if (icon != NULL)
+ {
+ nautilus_canvas_container_update_icon (container, icon);
+ container->details->needs_resort = TRUE;
+ schedule_redo_layout (container);
+ }
+}
+
+/* zooming */
+
+NautilusCanvasZoomLevel
+nautilus_canvas_container_get_zoom_level (NautilusCanvasContainer *container)
+{
+ return container->details->zoom_level;
+}
+
+void
+nautilus_canvas_container_set_zoom_level (NautilusCanvasContainer *container,
+ int new_level)
+{
+ NautilusCanvasContainerDetails *details;
+ int pinned_level;
+ double pixels_per_unit;
+
+ details = container->details;
+
+ pinned_level = new_level;
+ if (pinned_level < NAUTILUS_CANVAS_ZOOM_LEVEL_SMALL)
+ {
+ pinned_level = NAUTILUS_CANVAS_ZOOM_LEVEL_SMALL;
+ }
+ else if (pinned_level > NAUTILUS_CANVAS_ZOOM_LEVEL_LARGER)
+ {
+ pinned_level = NAUTILUS_CANVAS_ZOOM_LEVEL_LARGER;
+ }
+
+ if (pinned_level == details->zoom_level)
+ {
+ return;
+ }
+
+ details->zoom_level = pinned_level;
+
+ pixels_per_unit = (double) nautilus_canvas_container_get_icon_size_for_zoom_level (pinned_level)
+ / NAUTILUS_CANVAS_ICON_SIZE_STANDARD;
+ eel_canvas_set_pixels_per_unit (EEL_CANVAS (container), pixels_per_unit);
+
+ nautilus_canvas_container_request_update_all_internal (container, TRUE);
+}
+
+/**
+ * nautilus_canvas_container_request_update_all:
+ * For each icon, synchronizes the displayed information (image, text) with the
+ * information from the model.
+ *
+ * @container: An canvas container.
+ **/
+void
+nautilus_canvas_container_request_update_all (NautilusCanvasContainer *container)
+{
+ nautilus_canvas_container_request_update_all_internal (container, FALSE);
+}
+
+/**
+ * nautilus_canvas_container_reveal:
+ * Change scroll position as necessary to reveal the specified item.
+ */
+void
+nautilus_canvas_container_reveal (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data)
+{
+ NautilusCanvasIcon *icon;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+ g_return_if_fail (data != NULL);
+
+ icon = g_hash_table_lookup (container->details->icon_set, data);
+
+ if (icon != NULL)
+ {
+ reveal_icon (container, icon);
+ }
+}
+
+/**
+ * nautilus_canvas_container_get_selection:
+ * @container: An canvas container.
+ *
+ * Get a list of the icons currently selected in @container.
+ *
+ * Return value: A GList of the programmer-specified data associated to each
+ * selected icon, or NULL if no canvas is selected. The caller is expected to
+ * free the list when it is not needed anymore.
+ **/
+GList *
+nautilus_canvas_container_get_selection (NautilusCanvasContainer *container)
+{
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container), NULL);
+
+ if (container->details->selection_needs_resort)
+ {
+ sort_selection (container);
+ }
+
+ return g_list_copy (container->details->selection);
+}
+
+static GList *
+nautilus_canvas_container_get_selected_icons (NautilusCanvasContainer *container)
+{
+ GList *list, *p;
+
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container), NULL);
+
+ list = NULL;
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ NautilusCanvasIcon *icon;
+
+ icon = p->data;
+ if (icon->is_selected)
+ {
+ list = g_list_prepend (list, icon);
+ }
+ }
+
+ return g_list_reverse (list);
+}
+
+/**
+ * nautilus_canvas_container_invert_selection:
+ * @container: An canvas container.
+ *
+ * Inverts the selection in @container.
+ *
+ **/
+void
+nautilus_canvas_container_invert_selection (NautilusCanvasContainer *container)
+{
+ GList *p;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ NautilusCanvasIcon *icon;
+
+ icon = p->data;
+ icon_toggle_selected (container, icon);
+ }
+
+ g_signal_emit (container, signals[SELECTION_CHANGED], 0);
+}
+
+
+/* Returns an array of GdkPoints of locations of the icons. */
+static GArray *
+nautilus_canvas_container_get_icon_locations (NautilusCanvasContainer *container,
+ GList *icons)
+{
+ GArray *result;
+ GList *node;
+ int index;
+
+ result = g_array_new (FALSE, TRUE, sizeof (GdkPoint));
+ result = g_array_set_size (result, g_list_length (icons));
+
+ for (index = 0, node = icons; node != NULL; index++, node = node->next)
+ {
+ g_array_index (result, GdkPoint, index).x =
+ ((NautilusCanvasIcon *) node->data)->x;
+ g_array_index (result, GdkPoint, index).y =
+ ((NautilusCanvasIcon *) node->data)->y;
+ }
+
+ return result;
+}
+
+/* Returns a GdkRectangle of the icon. The bounding box is adjusted with the
+ * pixels_per_unit already, so they are the final positions on the canvas */
+GdkRectangle *
+nautilus_canvas_container_get_icon_bounding_box (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data)
+{
+ NautilusCanvasIcon *icon;
+ int x1, x2, y1, y2;
+ GdkRectangle *bounding_box;
+
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container), NULL);
+ g_return_val_if_fail (data != NULL, NULL);
+
+ icon = g_hash_table_lookup (container->details->icon_set, data);
+ icon_get_bounding_box (icon,
+ &x1, &y1, &x2, &y2,
+ BOUNDS_USAGE_FOR_DISPLAY);
+ bounding_box = g_malloc0 (sizeof (GdkRectangle));
+ bounding_box->x = x1 * EEL_CANVAS (container)->pixels_per_unit;
+ bounding_box->width = (x2 - x1) * EEL_CANVAS (container)->pixels_per_unit;
+ bounding_box->y = y1 * EEL_CANVAS (container)->pixels_per_unit;
+ bounding_box->height = (y2 - y1) * EEL_CANVAS (container)->pixels_per_unit;
+
+ return bounding_box;
+}
+
+/**
+ * nautilus_canvas_container_get_selected_icon_locations:
+ * @container: An canvas container widget.
+ *
+ * Returns an array of GdkPoints of locations of the selected icons.
+ **/
+GArray *
+nautilus_canvas_container_get_selected_icon_locations (NautilusCanvasContainer *container)
+{
+ GArray *result;
+ GList *icons;
+
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container), NULL);
+
+ icons = nautilus_canvas_container_get_selected_icons (container);
+ result = nautilus_canvas_container_get_icon_locations (container, icons);
+ g_list_free (icons);
+
+ return result;
+}
+
+/**
+ * nautilus_canvas_container_select_all:
+ * @container: An canvas container widget.
+ *
+ * Select all the icons in @container at once.
+ **/
+void
+nautilus_canvas_container_select_all (NautilusCanvasContainer *container)
+{
+ gboolean selection_changed;
+ GList *p;
+ NautilusCanvasIcon *icon;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ selection_changed = FALSE;
+
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+
+ selection_changed |= icon_set_selected (container, icon, TRUE);
+ }
+
+ if (selection_changed)
+ {
+ g_signal_emit (container,
+ signals[SELECTION_CHANGED], 0);
+ }
+}
+
+/**
+ * nautilus_canvas_container_select_first:
+ * @container: An canvas container widget.
+ *
+ * Select the first icon in @container.
+ **/
+void
+nautilus_canvas_container_select_first (NautilusCanvasContainer *container)
+{
+ gboolean selection_changed;
+ NautilusCanvasIcon *icon;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ selection_changed = FALSE;
+
+ if (container->details->needs_resort)
+ {
+ resort (container);
+ container->details->needs_resort = FALSE;
+ }
+
+ icon = g_list_nth_data (container->details->icons, 0);
+ if (icon)
+ {
+ selection_changed |= icon_set_selected (container, icon, TRUE);
+ }
+
+ if (selection_changed)
+ {
+ g_signal_emit (container,
+ signals[SELECTION_CHANGED], 0);
+ }
+}
+
+/**
+ * nautilus_canvas_container_set_selection:
+ * @container: An canvas container widget.
+ * @selection: A list of NautilusCanvasIconData *.
+ *
+ * Set the selection to exactly the icons in @container which have
+ * programmer data matching one of the items in @selection.
+ **/
+void
+nautilus_canvas_container_set_selection (NautilusCanvasContainer *container,
+ GList *selection)
+{
+ gboolean selection_changed;
+ GHashTable *hash;
+ GList *p;
+ gboolean res;
+ NautilusCanvasIcon *icon, *selected_icon;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ selection_changed = FALSE;
+ selected_icon = NULL;
+
+ hash = g_hash_table_new (NULL, NULL);
+ for (p = selection; p != NULL; p = p->next)
+ {
+ g_hash_table_insert (hash, p->data, p->data);
+ }
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+
+ res = icon_set_selected
+ (container, icon,
+ g_hash_table_lookup (hash, icon->data) != NULL);
+ selection_changed |= res;
+
+ if (res)
+ {
+ selected_icon = icon;
+ }
+ }
+ g_hash_table_destroy (hash);
+
+ if (selection_changed)
+ {
+ /* if only one item has been selected, use it as range
+ * selection base (cf. handle_canvas_button_press) */
+ if (g_list_length (selection) == 1)
+ {
+ container->details->range_selection_base_icon = selected_icon;
+ }
+
+ g_signal_emit (container,
+ signals[SELECTION_CHANGED], 0);
+ }
+}
+
+/**
+ * nautilus_canvas_container_select_list_unselect_others.
+ * @container: An canvas container widget.
+ * @selection: A list of NautilusCanvasIcon *.
+ *
+ * Set the selection to exactly the icons in @selection.
+ **/
+void
+nautilus_canvas_container_select_list_unselect_others (NautilusCanvasContainer *container,
+ GList *selection)
+{
+ gboolean selection_changed;
+ GHashTable *hash;
+ GList *p;
+ NautilusCanvasIcon *icon;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ selection_changed = FALSE;
+
+ hash = g_hash_table_new (NULL, NULL);
+ for (p = selection; p != NULL; p = p->next)
+ {
+ g_hash_table_insert (hash, p->data, p->data);
+ }
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+
+ selection_changed |= icon_set_selected
+ (container, icon,
+ g_hash_table_lookup (hash, icon) != NULL);
+ }
+ g_hash_table_destroy (hash);
+
+ if (selection_changed)
+ {
+ g_signal_emit (container,
+ signals[SELECTION_CHANGED], 0);
+ }
+}
+
+/**
+ * nautilus_canvas_container_unselect_all:
+ * @container: An canvas container widget.
+ *
+ * Deselect all the icons in @container.
+ **/
+void
+nautilus_canvas_container_unselect_all (NautilusCanvasContainer *container)
+{
+ if (unselect_all (container))
+ {
+ g_signal_emit (container,
+ signals[SELECTION_CHANGED], 0);
+ }
+}
+
+/**
+ * nautilus_canvas_container_get_icon_by_uri:
+ * @container: An canvas container widget.
+ * @uri: The uri of an canvas to find.
+ *
+ * Locate an icon, given the URI. The URI must match exactly.
+ * Later we may have to have some way of figuring out if the
+ * URI specifies the same object that does not require an exact match.
+ **/
+NautilusCanvasIcon *
+nautilus_canvas_container_get_icon_by_uri (NautilusCanvasContainer *container,
+ const char *uri)
+{
+ NautilusCanvasContainerDetails *details;
+ GList *p;
+
+ /* Eventually, we must avoid searching the entire canvas list,
+ * but it's OK for now.
+ * A hash table mapping uri to canvas is one possibility.
+ */
+
+ details = container->details;
+
+ for (p = details->icons; p != NULL; p = p->next)
+ {
+ NautilusCanvasIcon *icon;
+ char *icon_uri;
+ gboolean is_match;
+
+ icon = p->data;
+
+ icon_uri = nautilus_canvas_container_get_icon_uri
+ (container, icon);
+ is_match = strcmp (uri, icon_uri) == 0;
+ g_free (icon_uri);
+
+ if (is_match)
+ {
+ return icon;
+ }
+ }
+
+ return NULL;
+}
+
+static NautilusCanvasIcon *
+get_nth_selected_icon (NautilusCanvasContainer *container,
+ int index)
+{
+ GList *p;
+ NautilusCanvasIcon *icon;
+ int selection_count;
+
+ g_assert (index > 0);
+
+ /* Find the nth selected icon. */
+ selection_count = 0;
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+ if (icon->is_selected)
+ {
+ if (++selection_count == index)
+ {
+ return icon;
+ }
+ }
+ }
+ return NULL;
+}
+
+static NautilusCanvasIcon *
+get_first_selected_icon (NautilusCanvasContainer *container)
+{
+ return get_nth_selected_icon (container, 1);
+}
+
+static gboolean
+has_multiple_selection (NautilusCanvasContainer *container)
+{
+ return get_nth_selected_icon (container, 2) != NULL;
+}
+
+static gboolean
+all_selected (NautilusCanvasContainer *container)
+{
+ GList *p;
+ NautilusCanvasIcon *icon;
+
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+ if (!icon->is_selected)
+ {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static gboolean
+has_selection (NautilusCanvasContainer *container)
+{
+ return get_nth_selected_icon (container, 1) != NULL;
+}
+
+char *
+nautilus_canvas_container_get_icon_uri (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon)
+{
+ char *uri;
+
+ uri = NULL;
+ g_signal_emit (container,
+ signals[GET_ICON_URI], 0,
+ icon->data,
+ &uri);
+ return uri;
+}
+
+char *
+nautilus_canvas_container_get_icon_activation_uri (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon)
+{
+ char *uri;
+
+ uri = NULL;
+ g_signal_emit (container,
+ signals[GET_ICON_ACTIVATION_URI], 0,
+ icon->data,
+ &uri);
+ return uri;
+}
+
+char *
+nautilus_canvas_container_get_icon_drop_target_uri (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon)
+{
+ char *uri;
+
+ uri = NULL;
+ g_signal_emit (container,
+ signals[GET_ICON_DROP_TARGET_URI], 0,
+ icon->data,
+ &uri);
+ return uri;
+}
+
+/* Re-sort, switching to automatic layout if it was in manual layout. */
+void
+nautilus_canvas_container_sort (NautilusCanvasContainer *container)
+{
+ container->details->needs_resort = TRUE;
+ redo_layout (container);
+}
+
+void
+nautilus_canvas_container_set_single_click_mode (NautilusCanvasContainer *container,
+ gboolean single_click_mode)
+{
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ container->details->single_click_mode = single_click_mode;
+}
+
+/* handle theme changes */
+
+void
+nautilus_canvas_container_set_font (NautilusCanvasContainer *container,
+ const char *font)
+{
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ if (g_strcmp0 (container->details->font, font) == 0)
+ {
+ return;
+ }
+
+ g_free (container->details->font);
+ container->details->font = g_strdup (font);
+
+ nautilus_canvas_container_request_update_all_internal (container, TRUE);
+ gtk_widget_queue_draw (GTK_WIDGET (container));
+}
+
+/**
+ * nautilus_canvas_container_get_icon_description
+ * @container: An canvas container widget.
+ * @data: Icon data
+ *
+ * Gets the description for the icon. This function may return NULL.
+ **/
+char *
+nautilus_canvas_container_get_icon_description (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data)
+{
+ NautilusCanvasContainerClass *klass;
+
+ klass = NAUTILUS_CANVAS_CONTAINER_GET_CLASS (container);
+
+ if (klass->get_icon_description)
+ {
+ return klass->get_icon_description (container, data);
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+/**
+ * nautilus_canvas_container_set_highlighted_for_clipboard
+ * @container: An canvas container widget.
+ * @data: Canvas Data associated with all icons that should be highlighted.
+ * Others will be unhighlighted.
+ **/
+void
+nautilus_canvas_container_set_highlighted_for_clipboard (NautilusCanvasContainer *container,
+ GList *clipboard_canvas_data)
+{
+ GList *l;
+ NautilusCanvasIcon *icon;
+ gboolean highlighted_for_clipboard;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ for (l = container->details->icons; l != NULL; l = l->next)
+ {
+ icon = l->data;
+ highlighted_for_clipboard = (g_list_find (clipboard_canvas_data, icon->data) != NULL);
+
+ eel_canvas_item_set (EEL_CANVAS_ITEM (icon->item),
+ "highlighted-for-clipboard", highlighted_for_clipboard,
+ NULL);
+ }
+}
+
+/* NautilusCanvasContainerAccessible */
+typedef struct
+{
+ EelCanvasAccessible parent;
+ NautilusCanvasContainerAccessiblePrivate *priv;
+} NautilusCanvasContainerAccessible;
+
+typedef EelCanvasAccessibleClass NautilusCanvasContainerAccessibleClass;
+
+#define GET_ACCESSIBLE_PRIV(o) ((NautilusCanvasContainerAccessible *) o)->priv
+
+/* AtkAction interface */
+static gboolean
+nautilus_canvas_container_accessible_do_action (AtkAction *accessible,
+ int i)
+{
+ GtkWidget *widget;
+ NautilusCanvasContainer *container;
+ GList *selection;
+
+ g_return_val_if_fail (i < LAST_ACTION, FALSE);
+
+ widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
+ if (!widget)
+ {
+ return FALSE;
+ }
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+ switch (i)
+ {
+ case ACTION_ACTIVATE:
+ {
+ selection = nautilus_canvas_container_get_selection (container);
+
+ if (selection)
+ {
+ g_signal_emit_by_name (container, "activate", selection);
+ g_list_free (selection);
+ }
+ }
+ break;
+
+ case ACTION_MENU:
+ {
+ handle_popups (container, NULL, "context_click_background");
+ }
+ break;
+
+ default:
+ {
+ g_warning ("Invalid action passed to NautilusCanvasContainerAccessible::do_action");
+ return FALSE;
+ }
+ break;
+ }
+ return TRUE;
+}
+
+static int
+nautilus_canvas_container_accessible_get_n_actions (AtkAction *accessible)
+{
+ return LAST_ACTION;
+}
+
+static const char *
+nautilus_canvas_container_accessible_action_get_description (AtkAction *accessible,
+ int i)
+{
+ NautilusCanvasContainerAccessiblePrivate *priv;
+
+ g_assert (i < LAST_ACTION);
+
+ priv = GET_ACCESSIBLE_PRIV (accessible);
+
+ if (priv->action_descriptions[i])
+ {
+ return priv->action_descriptions[i];
+ }
+ else
+ {
+ return nautilus_canvas_container_accessible_action_descriptions[i];
+ }
+}
+
+static const char *
+nautilus_canvas_container_accessible_action_get_name (AtkAction *accessible,
+ int i)
+{
+ g_assert (i < LAST_ACTION);
+
+ return nautilus_canvas_container_accessible_action_names[i];
+}
+
+static const char *
+nautilus_canvas_container_accessible_action_get_keybinding (AtkAction *accessible,
+ int i)
+{
+ g_assert (i < LAST_ACTION);
+
+ return NULL;
+}
+
+static gboolean
+nautilus_canvas_container_accessible_action_set_description (AtkAction *accessible,
+ int i,
+ const char *description)
+{
+ NautilusCanvasContainerAccessiblePrivate *priv;
+
+ g_assert (i < LAST_ACTION);
+
+ priv = GET_ACCESSIBLE_PRIV (accessible);
+
+ if (priv->action_descriptions[i])
+ {
+ g_free (priv->action_descriptions[i]);
+ }
+ priv->action_descriptions[i] = g_strdup (description);
+
+ return FALSE;
+}
+
+static void
+nautilus_canvas_container_accessible_action_interface_init (AtkActionIface *iface)
+{
+ iface->do_action = nautilus_canvas_container_accessible_do_action;
+ iface->get_n_actions = nautilus_canvas_container_accessible_get_n_actions;
+ iface->get_description = nautilus_canvas_container_accessible_action_get_description;
+ iface->get_name = nautilus_canvas_container_accessible_action_get_name;
+ iface->get_keybinding = nautilus_canvas_container_accessible_action_get_keybinding;
+ iface->set_description = nautilus_canvas_container_accessible_action_set_description;
+}
+
+/* AtkSelection interface */
+
+static void
+nautilus_canvas_container_accessible_update_selection (AtkObject *accessible)
+{
+ NautilusCanvasContainer *container;
+ NautilusCanvasContainerAccessiblePrivate *priv;
+
+ container = NAUTILUS_CANVAS_CONTAINER (gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible)));
+ priv = GET_ACCESSIBLE_PRIV (accessible);
+
+ if (priv->selection)
+ {
+ g_list_free (priv->selection);
+ priv->selection = NULL;
+ }
+
+ priv->selection = nautilus_canvas_container_get_selected_icons (container);
+}
+
+static void
+nautilus_canvas_container_accessible_selection_changed_cb (NautilusCanvasContainer *container,
+ gpointer data)
+{
+ g_signal_emit_by_name (data, "selection-changed");
+}
+
+static void
+nautilus_canvas_container_accessible_icon_added_cb (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *icon_data,
+ gpointer data)
+{
+ NautilusCanvasIcon *icon;
+ AtkObject *atk_parent;
+ AtkObject *atk_child;
+
+ /* We don't want to emit children_changed signals during any type of load. */
+ if (!container->details->in_layout_now || container->details->is_populating_container)
+ {
+ return;
+ }
+
+ icon = g_hash_table_lookup (container->details->icon_set, icon_data);
+ if (icon)
+ {
+ atk_parent = ATK_OBJECT (data);
+ atk_child = atk_gobject_accessible_for_object
+ (G_OBJECT (icon->item));
+
+ g_signal_emit_by_name (atk_parent, "children-changed::add",
+ icon->position, atk_child, NULL);
+ }
+}
+
+static void
+nautilus_canvas_container_accessible_icon_removed_cb (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *icon_data,
+ gpointer data)
+{
+ NautilusCanvasIcon *icon;
+ AtkObject *atk_parent;
+ AtkObject *atk_child;
+
+ icon = g_hash_table_lookup (container->details->icon_set, icon_data);
+ if (icon)
+ {
+ atk_parent = ATK_OBJECT (data);
+ atk_child = atk_gobject_accessible_for_object
+ (G_OBJECT (icon->item));
+
+ g_signal_emit_by_name (atk_parent, "children-changed::remove",
+ icon->position, atk_child, NULL);
+ }
+}
+
+static void
+nautilus_canvas_container_accessible_cleared_cb (NautilusCanvasContainer *container,
+ gpointer data)
+{
+ g_signal_emit_by_name (data, "children-changed", 0, NULL, NULL);
+}
+
+static gboolean
+nautilus_canvas_container_accessible_add_selection (AtkSelection *accessible,
+ int i)
+{
+ GtkWidget *widget;
+ NautilusCanvasContainer *container;
+ GList *l;
+ GList *selection;
+ NautilusCanvasIcon *icon;
+
+ widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
+ if (!widget)
+ {
+ return FALSE;
+ }
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+
+ l = g_list_nth (container->details->icons, i);
+ if (l)
+ {
+ icon = l->data;
+
+ selection = nautilus_canvas_container_get_selection (container);
+ selection = g_list_prepend (selection,
+ icon->data);
+ nautilus_canvas_container_set_selection (container, selection);
+
+ g_list_free (selection);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+nautilus_canvas_container_accessible_clear_selection (AtkSelection *accessible)
+{
+ GtkWidget *widget;
+ NautilusCanvasContainer *container;
+
+ widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
+ if (!widget)
+ {
+ return FALSE;
+ }
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+
+ nautilus_canvas_container_unselect_all (container);
+
+ return TRUE;
+}
+
+static AtkObject *
+nautilus_canvas_container_accessible_ref_selection (AtkSelection *accessible,
+ int i)
+{
+ NautilusCanvasContainerAccessiblePrivate *priv;
+ AtkObject *atk_object;
+ GList *item;
+ NautilusCanvasIcon *icon;
+
+ nautilus_canvas_container_accessible_update_selection (ATK_OBJECT (accessible));
+ priv = GET_ACCESSIBLE_PRIV (accessible);
+
+ item = (g_list_nth (priv->selection, i));
+
+ if (item)
+ {
+ icon = item->data;
+ atk_object = atk_gobject_accessible_for_object (G_OBJECT (icon->item));
+ if (atk_object)
+ {
+ g_object_ref (atk_object);
+ }
+
+ return atk_object;
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+static int
+nautilus_canvas_container_accessible_get_selection_count (AtkSelection *accessible)
+{
+ NautilusCanvasContainerAccessiblePrivate *priv;
+ int count;
+
+ priv = GET_ACCESSIBLE_PRIV (accessible);
+ nautilus_canvas_container_accessible_update_selection (ATK_OBJECT (accessible));
+ count = g_list_length (priv->selection);
+
+ return count;
+}
+
+static gboolean
+nautilus_canvas_container_accessible_is_child_selected (AtkSelection *accessible,
+ int i)
+{
+ NautilusCanvasContainer *container;
+ GList *l;
+ NautilusCanvasIcon *icon;
+ GtkWidget *widget;
+
+ widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
+ if (!widget)
+ {
+ return FALSE;
+ }
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+
+ l = g_list_nth (container->details->icons, i);
+ if (l)
+ {
+ icon = l->data;
+ return icon->is_selected;
+ }
+ return FALSE;
+}
+
+static gboolean
+nautilus_canvas_container_accessible_remove_selection (AtkSelection *accessible,
+ int i)
+{
+ NautilusCanvasContainerAccessiblePrivate *priv;
+ NautilusCanvasContainer *container;
+ GList *l;
+ GList *selection;
+ NautilusCanvasIcon *icon;
+ GtkWidget *widget;
+
+ widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
+ if (!widget)
+ {
+ return FALSE;
+ }
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+ nautilus_canvas_container_accessible_update_selection (ATK_OBJECT (accessible));
+
+ priv = GET_ACCESSIBLE_PRIV (accessible);
+ l = g_list_nth (priv->selection, i);
+ if (l)
+ {
+ icon = l->data;
+
+ selection = nautilus_canvas_container_get_selection (container);
+ selection = g_list_remove (selection, icon->data);
+ nautilus_canvas_container_set_selection (container, selection);
+
+ g_list_free (selection);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+nautilus_canvas_container_accessible_select_all_selection (AtkSelection *accessible)
+{
+ NautilusCanvasContainer *container;
+ GtkWidget *widget;
+
+ widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
+ if (!widget)
+ {
+ return FALSE;
+ }
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+
+ nautilus_canvas_container_select_all (container);
+
+ return TRUE;
+}
+
+void
+nautilus_canvas_container_widget_to_file_operation_position (NautilusCanvasContainer *container,
+ GdkPoint *position)
+{
+ double x, y;
+
+ g_return_if_fail (position != NULL);
+
+ x = position->x;
+ y = position->y;
+
+ eel_canvas_window_to_world (EEL_CANVAS (container), x, y, &x, &y);
+
+ position->x = (int) x;
+ position->y = (int) y;
+
+ /* ensure that we end up in the middle of the icon */
+ position->x -= nautilus_canvas_container_get_icon_size_for_zoom_level (container->details->zoom_level) / 2;
+ position->y -= nautilus_canvas_container_get_icon_size_for_zoom_level (container->details->zoom_level) / 2;
+}
+
+static void
+nautilus_canvas_container_accessible_selection_interface_init (AtkSelectionIface *iface)
+{
+ iface->add_selection = nautilus_canvas_container_accessible_add_selection;
+ iface->clear_selection = nautilus_canvas_container_accessible_clear_selection;
+ iface->ref_selection = nautilus_canvas_container_accessible_ref_selection;
+ iface->get_selection_count = nautilus_canvas_container_accessible_get_selection_count;
+ iface->is_child_selected = nautilus_canvas_container_accessible_is_child_selected;
+ iface->remove_selection = nautilus_canvas_container_accessible_remove_selection;
+ iface->select_all_selection = nautilus_canvas_container_accessible_select_all_selection;
+}
+
+
+static gint
+nautilus_canvas_container_accessible_get_n_children (AtkObject *accessible)
+{
+ NautilusCanvasContainer *container;
+ GtkWidget *widget;
+ gint i;
+
+ widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
+ if (!widget)
+ {
+ return FALSE;
+ }
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+
+ i = g_hash_table_size (container->details->icon_set);
+
+ return i;
+}
+
+static AtkObject *
+nautilus_canvas_container_accessible_ref_child (AtkObject *accessible,
+ int i)
+{
+ AtkObject *atk_object;
+ NautilusCanvasContainer *container;
+ GList *item;
+ NautilusCanvasIcon *icon;
+ GtkWidget *widget;
+
+ widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
+ if (!widget)
+ {
+ return NULL;
+ }
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+
+ item = (g_list_nth (container->details->icons, i));
+
+ if (item)
+ {
+ icon = item->data;
+
+ atk_object = atk_gobject_accessible_for_object (G_OBJECT (icon->item));
+ g_object_ref (atk_object);
+
+ return atk_object;
+ }
+ return NULL;
+}
+
+G_DEFINE_TYPE_WITH_CODE (NautilusCanvasContainerAccessible, nautilus_canvas_container_accessible,
+ eel_canvas_accessible_get_type (),
+ G_IMPLEMENT_INTERFACE (ATK_TYPE_ACTION, nautilus_canvas_container_accessible_action_interface_init)
+ G_IMPLEMENT_INTERFACE (ATK_TYPE_SELECTION, nautilus_canvas_container_accessible_selection_interface_init))
+
+static void
+nautilus_canvas_container_accessible_initialize (AtkObject *accessible,
+ gpointer data)
+{
+ NautilusCanvasContainer *container;
+
+ if (ATK_OBJECT_CLASS (nautilus_canvas_container_accessible_parent_class)->initialize)
+ {
+ ATK_OBJECT_CLASS (nautilus_canvas_container_accessible_parent_class)->initialize (accessible, data);
+ }
+
+ if (GTK_IS_ACCESSIBLE (accessible))
+ {
+ nautilus_canvas_container_accessible_update_selection
+ (ATK_OBJECT (accessible));
+
+ container = NAUTILUS_CANVAS_CONTAINER (gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible)));
+ g_signal_connect (container, "selection-changed",
+ G_CALLBACK (nautilus_canvas_container_accessible_selection_changed_cb),
+ accessible);
+ g_signal_connect (container, "icon-added",
+ G_CALLBACK (nautilus_canvas_container_accessible_icon_added_cb),
+ accessible);
+ g_signal_connect (container, "icon-removed",
+ G_CALLBACK (nautilus_canvas_container_accessible_icon_removed_cb),
+ accessible);
+ g_signal_connect (container, "cleared",
+ G_CALLBACK (nautilus_canvas_container_accessible_cleared_cb),
+ accessible);
+ }
+}
+
+static void
+nautilus_canvas_container_accessible_finalize (GObject *object)
+{
+ NautilusCanvasContainerAccessiblePrivate *priv;
+ int i;
+
+ priv = GET_ACCESSIBLE_PRIV (object);
+
+ if (priv->selection)
+ {
+ g_list_free (priv->selection);
+ }
+
+ for (i = 0; i < LAST_ACTION; i++)
+ {
+ if (priv->action_descriptions[i])
+ {
+ g_free (priv->action_descriptions[i]);
+ }
+ }
+
+ G_OBJECT_CLASS (nautilus_canvas_container_accessible_parent_class)->finalize (object);
+}
+
+static void
+nautilus_canvas_container_accessible_init (NautilusCanvasContainerAccessible *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, nautilus_canvas_container_accessible_get_type (),
+ NautilusCanvasContainerAccessiblePrivate);
+}
+
+static void
+nautilus_canvas_container_accessible_class_init (NautilusCanvasContainerAccessibleClass *klass)
+{
+ AtkObjectClass *atk_class = ATK_OBJECT_CLASS (klass);
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->finalize = nautilus_canvas_container_accessible_finalize;
+
+ atk_class->get_n_children = nautilus_canvas_container_accessible_get_n_children;
+ atk_class->ref_child = nautilus_canvas_container_accessible_ref_child;
+ atk_class->initialize = nautilus_canvas_container_accessible_initialize;
+
+ g_type_class_add_private (klass, sizeof (NautilusCanvasContainerAccessiblePrivate));
+}
+
+gboolean
+nautilus_canvas_container_is_layout_rtl (NautilusCanvasContainer *container)
+{
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container), 0);
+
+ return (gtk_widget_get_direction (GTK_WIDGET (container)) == GTK_TEXT_DIR_RTL);
+}
+
+int
+nautilus_canvas_container_get_max_layout_lines_for_pango (NautilusCanvasContainer *container)
+{
+ int limit;
+
+ limit = text_ellipsis_limits[container->details->zoom_level];
+
+ if (limit <= 0)
+ {
+ return G_MININT;
+ }
+
+ return -limit;
+}
+
+int
+nautilus_canvas_container_get_max_layout_lines (NautilusCanvasContainer *container)
+{
+ int limit;
+
+ limit = text_ellipsis_limits[container->details->zoom_level];
+
+ if (limit <= 0)
+ {
+ return G_MAXINT;
+ }
+
+ return limit;
+}
diff --git a/src/nautilus-canvas-container.h b/src/nautilus-canvas-container.h
new file mode 100644
index 0000000..7955cf3
--- /dev/null
+++ b/src/nautilus-canvas-container.h
@@ -0,0 +1,295 @@
+
+/* gnome-canvas-container.h - Canvas container widget.
+
+ Copyright (C) 1999, 2000 Free Software Foundation
+ 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: Ettore Perazzoli <ettore@gnu.org>, Darin Adler <darin@bentspoon.com>
+*/
+
+#pragma once
+
+#include <eel/eel-canvas.h>
+
+#include "nautilus-types.h"
+
+#define NAUTILUS_TYPE_CANVAS_CONTAINER nautilus_canvas_container_get_type()
+#define NAUTILUS_CANVAS_CONTAINER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_CANVAS_CONTAINER, NautilusCanvasContainer))
+#define NAUTILUS_CANVAS_CONTAINER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_CANVAS_CONTAINER, NautilusCanvasContainerClass))
+#define NAUTILUS_IS_CANVAS_CONTAINER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_CANVAS_CONTAINER))
+#define NAUTILUS_IS_CANVAS_CONTAINER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_CANVAS_CONTAINER))
+#define NAUTILUS_CANVAS_CONTAINER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_CANVAS_CONTAINER, NautilusCanvasContainerClass))
+
+
+#define NAUTILUS_CANVAS_ICON_DATA(pointer) \
+ ((NautilusCanvasIconData *) (pointer))
+
+typedef struct NautilusCanvasIconData NautilusCanvasIconData;
+
+typedef void (* NautilusCanvasCallback) (NautilusCanvasIconData *icon_data,
+ gpointer callback_data);
+
+typedef struct {
+ int x;
+ int y;
+ double scale;
+} NautilusCanvasPosition;
+
+#define NAUTILUS_CANVAS_CONTAINER_TYPESELECT_FLUSH_DELAY 1000000
+
+typedef struct _NautilusCanvasContainer NautilusCanvasContainer;
+typedef struct NautilusCanvasContainerDetails NautilusCanvasContainerDetails;
+
+struct _NautilusCanvasContainer {
+ EelCanvas canvas;
+ NautilusCanvasContainerDetails *details;
+};
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (NautilusCanvasContainer, g_object_unref)
+
+typedef struct {
+ EelCanvasClass parent_slot;
+
+ /* Operations on the container. */
+ int (* button_press) (NautilusCanvasContainer *container,
+ GdkEventButton *event);
+ void (* context_click_background) (NautilusCanvasContainer *container,
+ GdkEventButton *event);
+ void (* middle_click) (NautilusCanvasContainer *container,
+ GdkEventButton *event);
+
+ /* Operations on icons. */
+ void (* activate) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data);
+ void (* activate_alternate) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data);
+ void (* activate_previewer) (NautilusCanvasContainer *container,
+ GList *files,
+ GArray *locations);
+ void (* context_click_selection) (NautilusCanvasContainer *container,
+ GdkEventButton *event);
+ void (* move_copy_items) (NautilusCanvasContainer *container,
+ const GList *item_uris,
+ const char *target_uri,
+ GdkDragAction action,
+ int x,
+ int y);
+ void (* handle_netscape_url) (NautilusCanvasContainer *container,
+ const char *url,
+ const char *target_uri,
+ GdkDragAction action,
+ int x,
+ int y);
+ void (* handle_uri_list) (NautilusCanvasContainer *container,
+ const char *uri_list,
+ const char *target_uri,
+ GdkDragAction action,
+ int x,
+ int y);
+ void (* handle_text) (NautilusCanvasContainer *container,
+ const char *text,
+ const char *target_uri,
+ GdkDragAction action,
+ int x,
+ int y);
+ void (* handle_raw) (NautilusCanvasContainer *container,
+ char *raw_data,
+ int length,
+ const char *target_uri,
+ const char *direct_save_uri,
+ GdkDragAction action,
+ int x,
+ int y);
+ void (* handle_hover) (NautilusCanvasContainer *container,
+ const char *target_uri);
+
+ /* Queries on the container for subclass/client.
+ * These must be implemented. The default "do nothing" is not good enough.
+ */
+ char * (* get_container_uri) (NautilusCanvasContainer *container);
+
+ /* Queries on icons for subclass/client.
+ * These must be implemented. The default "do nothing" is not
+ * good enough, these are _not_ signals.
+ */
+ NautilusIconInfo *(* get_icon_images) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data,
+ int canvas_size,
+ gboolean for_drag_accept);
+ void (* get_icon_text) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data,
+ char **editable_text,
+ char **additional_text,
+ gboolean include_invisible);
+ char * (* get_icon_description) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data);
+ int (* compare_icons) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *canvas_a,
+ NautilusCanvasIconData *canvas_b);
+ int (* compare_icons_by_name) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *canvas_a,
+ NautilusCanvasIconData *canvas_b);
+ void (* prioritize_thumbnailing) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data);
+
+ /* Queries on icons for subclass/client.
+ * These must be implemented => These are signals !
+ * The default "do nothing" is not good enough.
+ */
+ gboolean (* get_stored_icon_position) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data,
+ NautilusCanvasPosition *position);
+ char * (* get_icon_uri) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data);
+ char * (* get_icon_activation_uri) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data);
+ char * (* get_icon_drop_target_uri) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data);
+
+ /* If canvas data is NULL, the layout timestamp of the container should be retrieved.
+ * That is the time when the container displayed a fully loaded directory with
+ * all canvas positions assigned.
+ *
+ * If canvas data is not NULL, the position timestamp of the canvas should be retrieved.
+ * That is the time when the file (i.e. canvas data payload) was last displayed in a
+ * fully loaded directory with all canvas positions assigned.
+ */
+ gboolean (* get_stored_layout_timestamp) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data,
+ time_t *time);
+ /* If canvas data is NULL, the layout timestamp of the container should be stored.
+ * If canvas data is not NULL, the position timestamp of the container should be stored.
+ */
+ gboolean (* store_layout_timestamp) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data,
+ const time_t *time);
+
+ /* Notifications for the whole container. */
+ void (* band_select_started) (NautilusCanvasContainer *container);
+ void (* band_select_ended) (NautilusCanvasContainer *container);
+ void (* selection_changed) (NautilusCanvasContainer *container);
+ void (* layout_changed) (NautilusCanvasContainer *container);
+
+ /* Notifications for icons. */
+ void (* icon_position_changed) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data,
+ const NautilusCanvasPosition *position);
+ int (* preview) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data,
+ gboolean start_flag);
+ void (* icon_added) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data);
+ void (* icon_removed) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data);
+ void (* cleared) (NautilusCanvasContainer *container);
+ gboolean (* start_interactive_search) (NautilusCanvasContainer *container);
+} NautilusCanvasContainerClass;
+
+/* GtkObject */
+GType nautilus_canvas_container_get_type (void);
+GtkWidget * nautilus_canvas_container_new (void);
+
+
+/* adding, removing, and managing icons */
+void nautilus_canvas_container_clear (NautilusCanvasContainer *view);
+gboolean nautilus_canvas_container_add (NautilusCanvasContainer *view,
+ NautilusCanvasIconData *data);
+void nautilus_canvas_container_layout_now (NautilusCanvasContainer *container);
+gboolean nautilus_canvas_container_remove (NautilusCanvasContainer *view,
+ NautilusCanvasIconData *data);
+void nautilus_canvas_container_for_each (NautilusCanvasContainer *view,
+ NautilusCanvasCallback callback,
+ gpointer callback_data);
+void nautilus_canvas_container_request_update (NautilusCanvasContainer *view,
+ NautilusCanvasIconData *data);
+void nautilus_canvas_container_request_update_all (NautilusCanvasContainer *container);
+void nautilus_canvas_container_reveal (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data);
+gboolean nautilus_canvas_container_is_empty (NautilusCanvasContainer *container);
+NautilusCanvasIconData *nautilus_canvas_container_get_first_visible_icon (NautilusCanvasContainer *container);
+NautilusCanvasIconData *nautilus_canvas_container_get_focused_icon (NautilusCanvasContainer *container);
+GdkRectangle *nautilus_canvas_container_get_icon_bounding_box (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data);
+void nautilus_canvas_container_scroll_to_canvas (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data);
+
+void nautilus_canvas_container_begin_loading (NautilusCanvasContainer *container);
+void nautilus_canvas_container_end_loading (NautilusCanvasContainer *container,
+ gboolean all_icons_added);
+
+void nautilus_canvas_container_sort (NautilusCanvasContainer *container);
+void nautilus_canvas_container_freeze_icon_positions (NautilusCanvasContainer *container);
+
+int nautilus_canvas_container_get_max_layout_lines (NautilusCanvasContainer *container);
+int nautilus_canvas_container_get_max_layout_lines_for_pango (NautilusCanvasContainer *container);
+
+void nautilus_canvas_container_set_highlighted_for_clipboard (NautilusCanvasContainer *container,
+ GList *clipboard_canvas_data);
+
+/* operations on all icons */
+void nautilus_canvas_container_unselect_all (NautilusCanvasContainer *view);
+void nautilus_canvas_container_select_all (NautilusCanvasContainer *view);
+
+
+void nautilus_canvas_container_select_first (NautilusCanvasContainer *view);
+
+void nautilus_canvas_container_preview_selection_event (NautilusCanvasContainer *view,
+ GtkDirectionType direction);
+
+/* operations on the selection */
+GList * nautilus_canvas_container_get_selection (NautilusCanvasContainer *view);
+void nautilus_canvas_container_invert_selection (NautilusCanvasContainer *view);
+void nautilus_canvas_container_set_selection (NautilusCanvasContainer *view,
+ GList *selection);
+GArray * nautilus_canvas_container_get_selected_icon_locations (NautilusCanvasContainer *view);
+
+/* options */
+NautilusCanvasZoomLevel nautilus_canvas_container_get_zoom_level (NautilusCanvasContainer *view);
+void nautilus_canvas_container_set_zoom_level (NautilusCanvasContainer *view,
+ int new_zoom_level);
+void nautilus_canvas_container_set_single_click_mode (NautilusCanvasContainer *container,
+ gboolean single_click_mode);
+void nautilus_canvas_container_enable_linger_selection (NautilusCanvasContainer *view,
+ gboolean enable);
+void nautilus_canvas_container_set_font (NautilusCanvasContainer *container,
+ const char *font);
+void nautilus_canvas_container_set_margins (NautilusCanvasContainer *container,
+ int left_margin,
+ int right_margin,
+ int top_margin,
+ int bottom_margin);
+char* nautilus_canvas_container_get_icon_description (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data);
+
+gboolean nautilus_canvas_container_is_layout_rtl (NautilusCanvasContainer *container);
+
+gboolean nautilus_canvas_container_get_store_layout_timestamps (NautilusCanvasContainer *container);
+
+void nautilus_canvas_container_widget_to_file_operation_position (NautilusCanvasContainer *container,
+ GdkPoint *position);
+guint nautilus_canvas_container_get_icon_size_for_zoom_level (NautilusCanvasZoomLevel zoom_level);
+
+#define CANVAS_WIDTH(container,allocation) (allocation.width \
+ / EEL_CANVAS (container)->pixels_per_unit)
+
+#define CANVAS_HEIGHT(container,allocation) (allocation.height \
+ / EEL_CANVAS (container)->pixels_per_unit)
diff --git a/src/nautilus-canvas-dnd.c b/src/nautilus-canvas-dnd.c
new file mode 100644
index 0000000..9d5a5c6
--- /dev/null
+++ b/src/nautilus-canvas-dnd.c
@@ -0,0 +1,1839 @@
+/* nautilus-canvas-dnd.c - Drag & drop handling for the canvas container widget.
+ *
+ * Copyright (C) 1999, 2000 Free Software Foundation
+ * 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: Ettore Perazzoli <ettore@gnu.org>,
+ * Darin Adler <darin@bentspoon.com>,
+ * Andy Hertzfeld <andy@eazel.com>
+ * Pavel Cisler <pavel@eazel.com>
+ *
+ *
+ * XDS support: Benedikt Meurer <benny@xfce.org> (adapted by Amos Brocco <amos.brocco@unifr.ch>)
+ *
+ */
+
+
+#include <config.h>
+#include <math.h>
+#include <src/nautilus-window.h>
+
+#include "nautilus-canvas-dnd.h"
+
+#include "nautilus-canvas-private.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-metadata.h"
+#include "nautilus-selection-canvas-item.h"
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-graphic-effects.h>
+#include <eel/eel-gtk-extensions.h>
+#include <eel/eel-stock-dialogs.h>
+#include <eel/eel-string.h>
+#include <eel/eel-vfs-extensions.h>
+#include <gdk/gdkkeysyms.h>
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+
+#include "nautilus-file-utilities.h"
+#include "nautilus-file-changes-queue.h"
+#include <stdio.h>
+#include <string.h>
+
+#define DEBUG_FLAG NAUTILUS_DEBUG_CANVAS_CONTAINER
+#include "nautilus-debug.h"
+
+static const GtkTargetEntry drag_types [] =
+{
+ { NAUTILUS_ICON_DND_GNOME_ICON_LIST_TYPE, 0, NAUTILUS_ICON_DND_GNOME_ICON_LIST },
+ { NAUTILUS_ICON_DND_URI_LIST_TYPE, 0, NAUTILUS_ICON_DND_URI_LIST },
+};
+
+static const GtkTargetEntry drop_types [] =
+{
+ { NAUTILUS_ICON_DND_GNOME_ICON_LIST_TYPE, 0, NAUTILUS_ICON_DND_GNOME_ICON_LIST },
+ /* prefer "_NETSCAPE_URL" over "text/uri-list" to satisfy web browsers. */
+ { NAUTILUS_ICON_DND_NETSCAPE_URL_TYPE, 0, NAUTILUS_ICON_DND_NETSCAPE_URL },
+ /* prefer XDS over "text/uri-list" */
+ { NAUTILUS_ICON_DND_XDNDDIRECTSAVE_TYPE, 0, NAUTILUS_ICON_DND_XDNDDIRECTSAVE }, /* XDS Protocol Type */
+ { NAUTILUS_ICON_DND_URI_LIST_TYPE, 0, NAUTILUS_ICON_DND_URI_LIST },
+ { NAUTILUS_ICON_DND_RAW_TYPE, 0, NAUTILUS_ICON_DND_RAW },
+ /* Must be last: */
+ { NAUTILUS_ICON_DND_ROOTWINDOW_DROP_TYPE, 0, NAUTILUS_ICON_DND_ROOTWINDOW_DROP }
+};
+static void stop_dnd_highlight (GtkWidget *widget);
+static void dnd_highlight_queue_redraw (GtkWidget *widget);
+
+static GtkTargetList *drop_types_list = NULL;
+static GtkTargetList *drop_types_list_root = NULL;
+
+static char *nautilus_canvas_container_find_drop_target (NautilusCanvasContainer *container,
+ GdkDragContext *context,
+ int x,
+ int y,
+ gboolean *icon_hit);
+
+static EelCanvasItem *
+create_selection_shadow (NautilusCanvasContainer *container,
+ GList *list)
+{
+ EelCanvasGroup *group;
+ EelCanvas *canvas;
+ int max_x, max_y;
+ int min_x, min_y;
+ GList *p;
+ GtkAllocation allocation;
+
+ if (list == NULL)
+ {
+ return NULL;
+ }
+
+ /* if we're only dragging a single item, don't worry about the shadow */
+ if (list->next == NULL)
+ {
+ return NULL;
+ }
+
+ canvas = EEL_CANVAS (container);
+ gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
+
+ /* Creating a big set of rectangles in the canvas can be expensive, so
+ * we try to be smart and only create the maximum number of rectangles
+ * that we will need, in the vertical/horizontal directions. */
+
+ max_x = allocation.width;
+ min_x = -max_x;
+
+ max_y = allocation.height;
+ min_y = -max_y;
+
+ /* Create a group, so that it's easier to move all the items around at
+ * once. */
+ group = EEL_CANVAS_GROUP
+ (eel_canvas_item_new (EEL_CANVAS_GROUP (canvas->root),
+ eel_canvas_group_get_type (),
+ NULL));
+
+ for (p = list; p != NULL; p = p->next)
+ {
+ NautilusDragSelectionItem *item;
+ int x1, y1, x2, y2;
+
+ item = p->data;
+
+ if (!item->got_icon_position)
+ {
+ continue;
+ }
+
+ x1 = item->icon_x;
+ y1 = item->icon_y;
+ x2 = x1 + item->icon_width;
+ y2 = y1 + item->icon_height;
+
+ if (x2 >= min_x && x1 <= max_x && y2 >= min_y && y1 <= max_y)
+ {
+ eel_canvas_item_new
+ (group,
+ NAUTILUS_TYPE_SELECTION_CANVAS_ITEM,
+ "x1", (double) x1,
+ "y1", (double) y1,
+ "x2", (double) x2,
+ "y2", (double) y2,
+ NULL);
+ }
+ }
+
+ return EEL_CANVAS_ITEM (group);
+}
+
+/* Set the affine instead of the x and y position.
+ * Simple, and setting x and y was broken at one point.
+ */
+static void
+set_shadow_position (EelCanvasItem *shadow,
+ double x,
+ double y)
+{
+ eel_canvas_item_set (shadow,
+ "x", x, "y", y,
+ NULL);
+}
+
+
+/* Source-side handling of the drag. */
+
+/* iteration glue struct */
+typedef struct
+{
+ gpointer iterator_context;
+ NautilusDragEachSelectedItemDataGet iteratee;
+ gpointer iteratee_data;
+} CanvasGetDataBinderContext;
+
+static void
+canvas_rect_world_to_widget (EelCanvas *canvas,
+ EelDRect *world_rect,
+ EelIRect *widget_rect)
+{
+ EelDRect window_rect;
+ GtkAdjustment *hadj, *vadj;
+
+ hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (canvas));
+ vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (canvas));
+
+ eel_canvas_world_to_window (canvas,
+ world_rect->x0, world_rect->y0,
+ &window_rect.x0, &window_rect.y0);
+ eel_canvas_world_to_window (canvas,
+ world_rect->x1, world_rect->y1,
+ &window_rect.x1, &window_rect.y1);
+ widget_rect->x0 = (int) window_rect.x0 - gtk_adjustment_get_value (hadj);
+ widget_rect->y0 = (int) window_rect.y0 - gtk_adjustment_get_value (vadj);
+ widget_rect->x1 = (int) window_rect.x1 - gtk_adjustment_get_value (hadj);
+ widget_rect->y1 = (int) window_rect.y1 - gtk_adjustment_get_value (vadj);
+}
+
+static void
+canvas_widget_to_world (EelCanvas *canvas,
+ double widget_x,
+ double widget_y,
+ double *world_x,
+ double *world_y)
+{
+ eel_canvas_window_to_world (canvas,
+ widget_x + gtk_adjustment_get_value (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (canvas))),
+ widget_y + gtk_adjustment_get_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (canvas))),
+ world_x, world_y);
+}
+
+static gboolean
+icon_get_data_binder (NautilusCanvasIcon *icon,
+ gpointer data)
+{
+ CanvasGetDataBinderContext *context;
+ EelDRect world_rect;
+ EelIRect widget_rect;
+ char *uri;
+ NautilusCanvasContainer *container;
+ NautilusFile *file;
+
+ context = (CanvasGetDataBinderContext *) data;
+
+ g_assert (NAUTILUS_IS_CANVAS_CONTAINER (context->iterator_context));
+
+ container = NAUTILUS_CANVAS_CONTAINER (context->iterator_context);
+
+ world_rect = nautilus_canvas_item_get_icon_rectangle (icon->item);
+
+ canvas_rect_world_to_widget (EEL_CANVAS (container), &world_rect, &widget_rect);
+
+ uri = nautilus_canvas_container_get_icon_uri (container, icon);
+ file = nautilus_file_get_by_uri (uri);
+ g_free (uri);
+ uri = nautilus_canvas_container_get_icon_activation_uri (container, icon);
+
+ if (uri == NULL)
+ {
+ g_warning ("no URI for one of the iterated icons");
+ nautilus_file_unref (file);
+ return TRUE;
+ }
+
+ widget_rect = eel_irect_offset_by (widget_rect,
+ -container->details->dnd_info->drag_info.start_x,
+ -container->details->dnd_info->drag_info.start_y);
+
+ widget_rect = eel_irect_scale_by (widget_rect,
+ 1 / EEL_CANVAS (container)->pixels_per_unit);
+
+ /* pass the uri, mouse-relative x/y and icon width/height */
+ context->iteratee (uri,
+ (int) widget_rect.x0,
+ (int) widget_rect.y0,
+ widget_rect.x1 - widget_rect.x0,
+ widget_rect.y1 - widget_rect.y0,
+ context->iteratee_data);
+
+ g_free (uri);
+ nautilus_file_unref (file);
+
+ return TRUE;
+}
+
+/* Iterate over each selected icon in a NautilusCanvasContainer,
+ * calling each_function on each.
+ */
+static void
+nautilus_canvas_container_each_selected_icon (NautilusCanvasContainer *container,
+ gboolean (*each_function)(NautilusCanvasIcon *, gpointer),
+ gpointer data)
+{
+ GList *p;
+ NautilusCanvasIcon *icon;
+
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+ if (!icon->is_selected)
+ {
+ continue;
+ }
+ if (!each_function (icon, data))
+ {
+ return;
+ }
+ }
+}
+
+/* Adaptor function used with nautilus_canvas_container_each_selected_icon
+ * to help iterate over all selected items, passing uris, x, y, w and h
+ * values to the iteratee
+ */
+static void
+each_icon_get_data_binder (NautilusDragEachSelectedItemDataGet iteratee,
+ gpointer iterator_context,
+ gpointer data)
+{
+ CanvasGetDataBinderContext context;
+ NautilusCanvasContainer *container;
+
+ g_assert (NAUTILUS_IS_CANVAS_CONTAINER (iterator_context));
+ container = NAUTILUS_CANVAS_CONTAINER (iterator_context);
+
+ context.iterator_context = iterator_context;
+ context.iteratee = iteratee;
+ context.iteratee_data = data;
+ nautilus_canvas_container_each_selected_icon (container, icon_get_data_binder, &context);
+}
+
+/* Called when the data for drag&drop is needed */
+static void
+drag_data_get_callback (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint32 time,
+ gpointer data)
+{
+ NautilusDragInfo *drag_info;
+
+ g_assert (widget != NULL);
+ g_assert (NAUTILUS_IS_CANVAS_CONTAINER (widget));
+ g_return_if_fail (context != NULL);
+
+ /* Call common function from nautilus-drag that set's up
+ * the selection data in the right format. Pass it means to
+ * iterate all the selected icons.
+ */
+ drag_info = &(NAUTILUS_CANVAS_CONTAINER (widget)->details->dnd_info->drag_info);
+ nautilus_drag_drag_data_get_from_cache (drag_info->selection_cache, context, selection_data, info, time);
+}
+
+
+/* Target-side handling of the drag. */
+
+static void
+nautilus_canvas_container_position_shadow (NautilusCanvasContainer *container,
+ int x,
+ int y)
+{
+ EelCanvasItem *shadow;
+ double world_x, world_y;
+
+ shadow = container->details->dnd_info->shadow;
+ if (shadow == NULL)
+ {
+ return;
+ }
+
+ canvas_widget_to_world (EEL_CANVAS (container), x, y,
+ &world_x, &world_y);
+
+ set_shadow_position (shadow, world_x, world_y);
+ eel_canvas_item_show (shadow);
+}
+
+static void
+stop_cache_selection_list (NautilusDragInfo *drag_info)
+{
+ if (drag_info->file_list_info_handler)
+ {
+ nautilus_file_list_cancel_call_when_ready (drag_info->file_list_info_handler);
+ drag_info->file_list_info_handler = NULL;
+ }
+}
+
+static void
+cache_selection_list (NautilusDragInfo *drag_info)
+{
+ GList *files;
+
+ files = nautilus_drag_file_list_from_selection_list (drag_info->selection_list);
+ nautilus_file_list_call_when_ready (files,
+ NAUTILUS_FILE_ATTRIBUTE_INFO,
+ drag_info->file_list_info_handler,
+ NULL, NULL);
+
+ g_list_free_full (files, g_object_unref);
+}
+
+static void
+nautilus_canvas_container_dropped_canvas_feedback (GtkWidget *widget,
+ GtkSelectionData *data,
+ int x,
+ int y)
+{
+ NautilusCanvasContainer *container;
+ NautilusCanvasDndInfo *dnd_info;
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+ dnd_info = container->details->dnd_info;
+
+ /* Delete old selection list. */
+ stop_cache_selection_list (&dnd_info->drag_info);
+ nautilus_drag_destroy_selection_list (dnd_info->drag_info.selection_list);
+ dnd_info->drag_info.selection_list = NULL;
+
+ /* Delete old shadow if any. */
+ if (dnd_info->shadow != NULL)
+ {
+ /* FIXME bugzilla.gnome.org 42484:
+ * Is a destroy really sufficient here? Who does the unref? */
+ eel_canvas_item_destroy (dnd_info->shadow);
+ }
+
+ /* Build the selection list and the shadow. */
+ dnd_info->drag_info.selection_list = nautilus_drag_build_selection_list (data);
+ cache_selection_list (&dnd_info->drag_info);
+ dnd_info->shadow = create_selection_shadow (container, dnd_info->drag_info.selection_list);
+ nautilus_canvas_container_position_shadow (container, x, y);
+}
+
+static char *
+get_direct_save_filename (GdkDragContext *context)
+{
+ guchar *prop_text;
+ gint prop_len;
+
+ if (!gdk_property_get (gdk_drag_context_get_source_window (context), gdk_atom_intern (NAUTILUS_ICON_DND_XDNDDIRECTSAVE_TYPE, FALSE),
+ gdk_atom_intern ("text/plain", FALSE), 0, 1024, FALSE, NULL, NULL,
+ &prop_len, &prop_text))
+ {
+ return NULL;
+ }
+
+ /* Zero-terminate the string */
+ prop_text = g_realloc (prop_text, prop_len + 1);
+ prop_text[prop_len] = '\0';
+
+ /* Verify that the file name provided by the source is valid */
+ if (*prop_text == '\0' ||
+ strchr ((const gchar *) prop_text, G_DIR_SEPARATOR) != NULL)
+ {
+ DEBUG ("Invalid filename provided by XDS drag site");
+ g_free (prop_text);
+ return NULL;
+ }
+
+ return (gchar *) prop_text;
+}
+
+static void
+set_direct_save_uri (GtkWidget *widget,
+ GdkDragContext *context,
+ NautilusDragInfo *drag_info,
+ int x,
+ int y)
+{
+ GFile *base, *child;
+ char *filename, *drop_target;
+ gchar *uri;
+
+ drag_info->got_drop_data_type = TRUE;
+ drag_info->data_type = NAUTILUS_ICON_DND_XDNDDIRECTSAVE;
+
+ uri = NULL;
+
+ filename = get_direct_save_filename (context);
+ drop_target = nautilus_canvas_container_find_drop_target (NAUTILUS_CANVAS_CONTAINER (widget),
+ context, x, y, NULL);
+
+ if (drop_target && eel_uri_is_trash (drop_target))
+ {
+ g_free (drop_target);
+ drop_target = NULL; /* Cannot save to trash ...*/
+ }
+
+ if (filename != NULL && drop_target != NULL)
+ {
+ /* Resolve relative path */
+ base = g_file_new_for_uri (drop_target);
+ child = g_file_get_child (base, filename);
+ uri = g_file_get_uri (child);
+ g_object_unref (base);
+ g_object_unref (child);
+
+ /* Change the uri property */
+ gdk_property_change (gdk_drag_context_get_source_window (context),
+ gdk_atom_intern (NAUTILUS_ICON_DND_XDNDDIRECTSAVE_TYPE, FALSE),
+ gdk_atom_intern ("text/plain", FALSE), 8,
+ GDK_PROP_MODE_REPLACE, (const guchar *) uri,
+ strlen (uri));
+
+ drag_info->direct_save_uri = uri;
+ }
+
+ g_free (filename);
+ g_free (drop_target);
+}
+
+/* FIXME bugzilla.gnome.org 47445: Needs to become a shared function */
+static void
+get_data_on_first_target_we_support (GtkWidget *widget,
+ GdkDragContext *context,
+ guint32 time,
+ int x,
+ int y)
+{
+ GtkTargetList *list;
+ GdkAtom target;
+
+ if (drop_types_list == NULL)
+ {
+ drop_types_list = gtk_target_list_new (drop_types,
+ G_N_ELEMENTS (drop_types) - 1);
+ gtk_target_list_add_text_targets (drop_types_list, NAUTILUS_ICON_DND_TEXT);
+ }
+ if (drop_types_list_root == NULL)
+ {
+ drop_types_list_root = gtk_target_list_new (drop_types,
+ G_N_ELEMENTS (drop_types));
+ gtk_target_list_add_text_targets (drop_types_list_root, NAUTILUS_ICON_DND_TEXT);
+ }
+
+ list = drop_types_list;
+
+ target = gtk_drag_dest_find_target (widget, context, list);
+ if (target != GDK_NONE)
+ {
+ guint info;
+ NautilusDragInfo *drag_info;
+ gboolean found;
+
+ drag_info = &(NAUTILUS_CANVAS_CONTAINER (widget)->details->dnd_info->drag_info);
+
+ found = gtk_target_list_find (list, target, &info);
+ g_assert (found);
+
+ /* Don't get_data for destructive ops */
+ if ((info == NAUTILUS_ICON_DND_ROOTWINDOW_DROP ||
+ info == NAUTILUS_ICON_DND_XDNDDIRECTSAVE) &&
+ !drag_info->drop_occurred)
+ {
+ /* We can't call get_data here, because that would
+ * make the source execute the rootwin action or the direct save */
+ drag_info->got_drop_data_type = TRUE;
+ drag_info->data_type = info;
+ }
+ else
+ {
+ if (info == NAUTILUS_ICON_DND_XDNDDIRECTSAVE)
+ {
+ set_direct_save_uri (widget, context, drag_info, x, y);
+ }
+ gtk_drag_get_data (GTK_WIDGET (widget), context,
+ target, time);
+ }
+ }
+}
+
+static void
+nautilus_canvas_container_ensure_drag_data (NautilusCanvasContainer *container,
+ GdkDragContext *context,
+ guint32 time)
+{
+ NautilusCanvasDndInfo *dnd_info;
+
+ dnd_info = container->details->dnd_info;
+
+ if (!dnd_info->drag_info.got_drop_data_type)
+ {
+ get_data_on_first_target_we_support (GTK_WIDGET (container), context, time, 0, 0);
+ }
+}
+
+static void
+drag_end_callback (GtkWidget *widget,
+ GdkDragContext *context,
+ gpointer data)
+{
+ NautilusCanvasContainer *container;
+ NautilusCanvasDndInfo *dnd_info;
+ NautilusWindow *window;
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+ window = NAUTILUS_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (container)));
+ dnd_info = container->details->dnd_info;
+
+ stop_cache_selection_list (&dnd_info->drag_info);
+ nautilus_drag_destroy_selection_list (dnd_info->drag_info.selection_list);
+ nautilus_drag_destroy_selection_list (dnd_info->drag_info.selection_cache);
+ nautilus_drag_destroy_selection_list (container->details->dnd_source_info->selection_cache);
+ dnd_info->drag_info.selection_list = NULL;
+ dnd_info->drag_info.selection_cache = NULL;
+ container->details->dnd_source_info->selection_cache = NULL;
+
+ nautilus_window_end_dnd (window, context);
+}
+
+static NautilusCanvasIcon *
+nautilus_canvas_container_item_at (NautilusCanvasContainer *container,
+ int x,
+ int y)
+{
+ GList *p;
+ int size;
+ EelDRect point;
+ EelIRect canvas_point;
+
+ /* build the hit-test rectangle. Base the size on the scale factor to ensure that it is
+ * non-empty even at the smallest scale factor
+ */
+
+ size = MAX (1, 1 + (1 / EEL_CANVAS (container)->pixels_per_unit));
+ point.x0 = x;
+ point.y0 = y;
+ point.x1 = x + size;
+ point.y1 = y + size;
+
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ NautilusCanvasIcon *icon;
+ icon = p->data;
+
+ eel_canvas_w2c (EEL_CANVAS (container),
+ point.x0,
+ point.y0,
+ &canvas_point.x0,
+ &canvas_point.y0);
+ eel_canvas_w2c (EEL_CANVAS (container),
+ point.x1,
+ point.y1,
+ &canvas_point.x1,
+ &canvas_point.y1);
+ if (nautilus_canvas_item_hit_test_rectangle (icon->item, canvas_point))
+ {
+ return icon;
+ }
+ }
+
+ return NULL;
+}
+
+static char *
+get_container_uri (NautilusCanvasContainer *container)
+{
+ char *uri;
+
+ /* get the URI associated with the container */
+ uri = NULL;
+ g_signal_emit_by_name (container, "get-container-uri", &uri);
+ return uri;
+}
+
+static gboolean
+nautilus_canvas_container_selection_items_local (NautilusCanvasContainer *container,
+ GList *items)
+{
+ char *container_uri_string;
+ gboolean result;
+
+ /* must have at least one item */
+ g_assert (items);
+
+ /* get the URI associated with the container */
+ container_uri_string = get_container_uri (container);
+
+ result = nautilus_drag_items_local (container_uri_string, items);
+
+ g_free (container_uri_string);
+
+ return result;
+}
+
+/* handle dropped url */
+static void
+receive_dropped_netscape_url (NautilusCanvasContainer *container,
+ const char *encoded_url,
+ GdkDragContext *context,
+ int x,
+ int y)
+{
+ char *drop_target;
+
+ if (encoded_url == NULL)
+ {
+ return;
+ }
+
+ drop_target = nautilus_canvas_container_find_drop_target (container, context, x, y, NULL);
+
+ g_signal_emit_by_name (container, "handle-netscape-url",
+ encoded_url,
+ drop_target,
+ gdk_drag_context_get_selected_action (context));
+
+ g_free (drop_target);
+}
+
+/* handle dropped uri list */
+static void
+receive_dropped_uri_list (NautilusCanvasContainer *container,
+ const char *uri_list,
+ GdkDragContext *context,
+ int x,
+ int y)
+{
+ char *drop_target;
+
+ if (uri_list == NULL)
+ {
+ return;
+ }
+
+ drop_target = nautilus_canvas_container_find_drop_target (container, context, x, y, NULL);
+
+ g_signal_emit_by_name (container, "handle-uri-list",
+ uri_list,
+ drop_target,
+ gdk_drag_context_get_selected_action (context));
+
+ g_free (drop_target);
+}
+
+/* handle dropped text */
+static void
+receive_dropped_text (NautilusCanvasContainer *container,
+ const char *text,
+ GdkDragContext *context,
+ int x,
+ int y)
+{
+ char *drop_target;
+
+ if (text == NULL)
+ {
+ return;
+ }
+
+ drop_target = nautilus_canvas_container_find_drop_target (container, context, x, y, NULL);
+
+ g_signal_emit_by_name (container, "handle-text",
+ text,
+ drop_target,
+ gdk_drag_context_get_selected_action (context));
+
+ g_free (drop_target);
+}
+
+/* handle dropped raw data */
+static void
+receive_dropped_raw (NautilusCanvasContainer *container,
+ const char *raw_data,
+ int length,
+ const char *direct_save_uri,
+ GdkDragContext *context,
+ int x,
+ int y)
+{
+ char *drop_target;
+
+ if (raw_data == NULL)
+ {
+ return;
+ }
+
+ drop_target = nautilus_canvas_container_find_drop_target (container, context, x, y, NULL);
+
+ g_signal_emit_by_name (container, "handle-raw",
+ raw_data,
+ length,
+ drop_target,
+ direct_save_uri,
+ gdk_drag_context_get_selected_action (context));
+
+ g_free (drop_target);
+}
+
+static int
+auto_scroll_timeout_callback (gpointer data)
+{
+ NautilusCanvasContainer *container;
+ GtkWidget *widget;
+ float x_scroll_delta, y_scroll_delta;
+ GdkRectangle exposed_area;
+ GtkAllocation allocation;
+
+ g_assert (NAUTILUS_IS_CANVAS_CONTAINER (data));
+ widget = GTK_WIDGET (data);
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+
+ if (container->details->dnd_info->drag_info.waiting_to_autoscroll
+ && container->details->dnd_info->drag_info.start_auto_scroll_in > g_get_monotonic_time ())
+ {
+ /* not yet */
+ return TRUE;
+ }
+
+ container->details->dnd_info->drag_info.waiting_to_autoscroll = FALSE;
+
+ nautilus_drag_autoscroll_calculate_delta (widget, &x_scroll_delta, &y_scroll_delta);
+ if (x_scroll_delta == 0 && y_scroll_delta == 0)
+ {
+ /* no work */
+ return TRUE;
+ }
+
+ /* Clear the old dnd highlight frame */
+ dnd_highlight_queue_redraw (widget);
+
+ if (!nautilus_canvas_container_scroll (container, (int) x_scroll_delta, (int) y_scroll_delta))
+ {
+ /* the scroll value got pinned to a min or max adjustment value,
+ * we ended up not scrolling
+ */
+ return TRUE;
+ }
+
+ /* Make sure the dnd highlight frame is redrawn */
+ dnd_highlight_queue_redraw (widget);
+
+ /* update cached drag start offsets */
+ container->details->dnd_info->drag_info.start_x -= x_scroll_delta;
+ container->details->dnd_info->drag_info.start_y -= y_scroll_delta;
+
+ /* Due to a glitch in GtkLayout, whe need to do an explicit draw of the exposed
+ * area.
+ * Calculate the size of the area we need to draw
+ */
+ gtk_widget_get_allocation (widget, &allocation);
+ exposed_area.x = allocation.x;
+ exposed_area.y = allocation.y;
+ exposed_area.width = allocation.width;
+ exposed_area.height = allocation.height;
+
+ if (x_scroll_delta > 0)
+ {
+ exposed_area.x = exposed_area.width - x_scroll_delta;
+ }
+ else if (x_scroll_delta < 0)
+ {
+ exposed_area.width = -x_scroll_delta;
+ }
+
+ if (y_scroll_delta > 0)
+ {
+ exposed_area.y = exposed_area.height - y_scroll_delta;
+ }
+ else if (y_scroll_delta < 0)
+ {
+ exposed_area.height = -y_scroll_delta;
+ }
+
+ /* offset it to 0, 0 */
+ exposed_area.x -= allocation.x;
+ exposed_area.y -= allocation.y;
+
+ gtk_widget_queue_draw_area (widget,
+ exposed_area.x,
+ exposed_area.y,
+ exposed_area.width,
+ exposed_area.height);
+
+ return TRUE;
+}
+
+static void
+set_up_auto_scroll_if_needed (NautilusCanvasContainer *container)
+{
+ nautilus_drag_autoscroll_start (&container->details->dnd_info->drag_info,
+ GTK_WIDGET (container),
+ auto_scroll_timeout_callback,
+ container);
+}
+
+static void
+stop_auto_scroll (NautilusCanvasContainer *container)
+{
+ nautilus_drag_autoscroll_stop (&container->details->dnd_info->drag_info);
+}
+
+static void
+handle_nonlocal_move (NautilusCanvasContainer *container,
+ GdkDragAction action,
+ const char *target_uri,
+ gboolean icon_hit)
+{
+ GList *source_uris, *p;
+ gboolean free_target_uri;
+
+ if (container->details->dnd_info->drag_info.selection_list == NULL)
+ {
+ return;
+ }
+
+ source_uris = NULL;
+ for (p = container->details->dnd_info->drag_info.selection_list; p != NULL; p = p->next)
+ {
+ /* do a shallow copy of all the uri strings of the copied files */
+ source_uris = g_list_prepend (source_uris, ((NautilusDragSelectionItem *) p->data)->uri);
+ }
+ source_uris = g_list_reverse (source_uris);
+
+ free_target_uri = FALSE;
+
+ /* start the copy */
+ g_signal_emit_by_name (container, "move-copy-items",
+ source_uris,
+ target_uri,
+ action);
+
+ if (free_target_uri)
+ {
+ g_free ((char *) target_uri);
+ }
+
+ g_list_free (source_uris);
+}
+
+static char *
+nautilus_canvas_container_find_drop_target (NautilusCanvasContainer *container,
+ GdkDragContext *context,
+ int x,
+ int y,
+ gboolean *icon_hit)
+{
+ NautilusCanvasIcon *drop_target_icon;
+ double world_x, world_y;
+ NautilusFile *file;
+ char *icon_uri;
+ char *container_uri;
+
+ if (icon_hit)
+ {
+ *icon_hit = FALSE;
+ }
+
+ if (!container->details->dnd_info->drag_info.got_drop_data_type)
+ {
+ return NULL;
+ }
+
+ canvas_widget_to_world (EEL_CANVAS (container), x, y, &world_x, &world_y);
+
+ /* FIXME bugzilla.gnome.org 42485:
+ * These "can_accept_items" tests need to be done by
+ * the canvas view, not here. This file is not supposed to know
+ * that the target is a file.
+ */
+
+ /* Find the item we hit with our drop, if any */
+ drop_target_icon = nautilus_canvas_container_item_at (container, world_x, world_y);
+ if (drop_target_icon != NULL)
+ {
+ icon_uri = nautilus_canvas_container_get_icon_uri (container, drop_target_icon);
+ if (icon_uri != NULL)
+ {
+ file = nautilus_file_get_by_uri (icon_uri);
+
+ if (!nautilus_drag_can_accept_info (file,
+ container->details->dnd_info->drag_info.data_type,
+ container->details->dnd_info->drag_info.selection_list))
+ {
+ /* the item we dropped our selection on cannot accept the items,
+ * do the same thing as if we just dropped the items on the canvas
+ */
+ drop_target_icon = NULL;
+ }
+
+ g_free (icon_uri);
+ nautilus_file_unref (file);
+ }
+ }
+
+ if (drop_target_icon == NULL)
+ {
+ if (icon_hit)
+ {
+ *icon_hit = FALSE;
+ }
+
+ container_uri = get_container_uri (container);
+
+ if (container_uri != NULL)
+ {
+ gboolean can;
+ file = nautilus_file_get_by_uri (container_uri);
+ can = nautilus_drag_can_accept_info (file,
+ container->details->dnd_info->drag_info.data_type,
+ container->details->dnd_info->drag_info.selection_list);
+ g_object_unref (file);
+ if (!can)
+ {
+ g_free (container_uri);
+ container_uri = NULL;
+ }
+ }
+
+ return container_uri;
+ }
+
+ if (icon_hit)
+ {
+ *icon_hit = TRUE;
+ }
+ return nautilus_canvas_container_get_icon_drop_target_uri (container, drop_target_icon);
+}
+
+static void
+nautilus_canvas_container_receive_dropped_icons (NautilusCanvasContainer *container,
+ GdkDragContext *context,
+ int x,
+ int y)
+{
+ char *drop_target;
+ gboolean local_move_only;
+ double world_x, world_y;
+ gboolean icon_hit;
+ GdkDragAction action, real_action;
+
+ drop_target = NULL;
+
+ if (container->details->dnd_info->drag_info.selection_list == NULL)
+ {
+ return;
+ }
+
+ real_action = gdk_drag_context_get_selected_action (context);
+
+ if (real_action == GDK_ACTION_ASK)
+ {
+ action = GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK;
+ real_action = nautilus_drag_drop_action_ask (GTK_WIDGET (container), action);
+ }
+
+ if (real_action > 0)
+ {
+ eel_canvas_window_to_world (EEL_CANVAS (container),
+ x + gtk_adjustment_get_value (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container))),
+ y + gtk_adjustment_get_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container))),
+ &world_x, &world_y);
+
+ drop_target = nautilus_canvas_container_find_drop_target (container,
+ context, x, y, &icon_hit);
+
+ local_move_only = FALSE;
+ if (!icon_hit && real_action == GDK_ACTION_MOVE)
+ {
+ local_move_only = nautilus_canvas_container_selection_items_local
+ (container, container->details->dnd_info->drag_info.selection_list);
+ }
+
+ /* If the move is local, there is nothing to do. */
+ if (!local_move_only)
+ {
+ handle_nonlocal_move (container, real_action, drop_target, icon_hit);
+ }
+ }
+
+ g_free (drop_target);
+ stop_cache_selection_list (&container->details->dnd_info->drag_info);
+ nautilus_drag_destroy_selection_list (container->details->dnd_info->drag_info.selection_list);
+ container->details->dnd_info->drag_info.selection_list = NULL;
+}
+
+NautilusDragInfo *
+nautilus_canvas_dnd_get_drag_source_data (NautilusCanvasContainer *container,
+ GdkDragContext *context)
+{
+ return container->details->dnd_source_info;
+}
+
+static void
+nautilus_canvas_container_get_drop_action (NautilusCanvasContainer *container,
+ GdkDragContext *context,
+ int x,
+ int y,
+ int *action)
+{
+ char *drop_target;
+ gboolean icon_hit;
+ double world_x, world_y;
+
+ icon_hit = FALSE;
+ if (!container->details->dnd_info->drag_info.got_drop_data_type)
+ {
+ /* drag_data_received_callback didn't get called yet */
+ return;
+ }
+
+ /* find out if we're over an canvas */
+ canvas_widget_to_world (EEL_CANVAS (container), x, y, &world_x, &world_y);
+ *action = 0;
+
+ drop_target = nautilus_canvas_container_find_drop_target (container,
+ context, x, y, &icon_hit);
+ if (drop_target == NULL)
+ {
+ return;
+ }
+
+ /* case out on the type of object being dragged */
+ switch (container->details->dnd_info->drag_info.data_type)
+ {
+ case NAUTILUS_ICON_DND_GNOME_ICON_LIST:
+ {
+ if (container->details->dnd_info->drag_info.selection_list != NULL)
+ {
+ nautilus_drag_default_drop_action_for_icons (context, drop_target,
+ container->details->dnd_info->drag_info.selection_list,
+ 0,
+ action);
+ }
+ }
+ break;
+
+ case NAUTILUS_ICON_DND_URI_LIST:
+ {
+ *action = nautilus_drag_default_drop_action_for_uri_list (context, drop_target);
+ }
+ break;
+
+ case NAUTILUS_ICON_DND_NETSCAPE_URL:
+ {
+ *action = nautilus_drag_default_drop_action_for_netscape_url (context);
+ }
+ break;
+
+ case NAUTILUS_ICON_DND_ROOTWINDOW_DROP:
+ {
+ *action = gdk_drag_context_get_suggested_action (context);
+ }
+ break;
+
+ case NAUTILUS_ICON_DND_TEXT:
+ case NAUTILUS_ICON_DND_XDNDDIRECTSAVE:
+ case NAUTILUS_ICON_DND_RAW:
+ {
+ *action = GDK_ACTION_COPY;
+ }
+ break;
+ }
+
+ g_free (drop_target);
+}
+
+static void
+set_drop_target (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon)
+{
+ NautilusCanvasIcon *old_icon;
+
+ /* Check if current drop target changed, update icon drop
+ * higlight if needed.
+ */
+ old_icon = container->details->drop_target;
+ if (icon == old_icon)
+ {
+ return;
+ }
+
+ /* Remember the new drop target for the next round. */
+ container->details->drop_target = icon;
+ nautilus_canvas_container_update_icon (container, old_icon);
+ nautilus_canvas_container_update_icon (container, icon);
+}
+
+static void
+nautilus_canvas_dnd_update_drop_target (NautilusCanvasContainer *container,
+ GdkDragContext *context,
+ int x,
+ int y)
+{
+ NautilusCanvasIcon *icon;
+ NautilusFile *file;
+ double world_x, world_y;
+ char *uri;
+
+ g_assert (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ canvas_widget_to_world (EEL_CANVAS (container), x, y, &world_x, &world_y);
+
+ /* Find the item we hit with our drop, if any. */
+ icon = nautilus_canvas_container_item_at (container, world_x, world_y);
+
+ /* FIXME bugzilla.gnome.org 42485:
+ * These "can_accept_items" tests need to be done by
+ * the canvas view, not here. This file is not supposed to know
+ * that the target is a file.
+ */
+
+ /* Find if target canvas accepts our drop. */
+ if (icon != NULL)
+ {
+ uri = nautilus_canvas_container_get_icon_uri (container, icon);
+ file = nautilus_file_get_by_uri (uri);
+ g_free (uri);
+
+ if (!nautilus_drag_can_accept_info (file,
+ container->details->dnd_info->drag_info.data_type,
+ container->details->dnd_info->drag_info.selection_list))
+ {
+ icon = NULL;
+ }
+
+ nautilus_file_unref (file);
+ }
+
+ set_drop_target (container, icon);
+}
+
+static void
+remove_hover_timer (NautilusCanvasDndInfo *dnd_info)
+{
+ if (dnd_info->hover_id != 0)
+ {
+ g_source_remove (dnd_info->hover_id);
+ dnd_info->hover_id = 0;
+ }
+}
+
+static void
+nautilus_canvas_container_free_drag_data (NautilusCanvasContainer *container)
+{
+ NautilusCanvasDndInfo *dnd_info;
+
+ dnd_info = container->details->dnd_info;
+
+ dnd_info->drag_info.got_drop_data_type = FALSE;
+
+ if (dnd_info->shadow != NULL)
+ {
+ eel_canvas_item_destroy (dnd_info->shadow);
+ dnd_info->shadow = NULL;
+ }
+
+ if (dnd_info->drag_info.selection_data != NULL)
+ {
+ gtk_selection_data_free (dnd_info->drag_info.selection_data);
+ dnd_info->drag_info.selection_data = NULL;
+ }
+
+ if (dnd_info->drag_info.direct_save_uri != NULL)
+ {
+ g_free (dnd_info->drag_info.direct_save_uri);
+ dnd_info->drag_info.direct_save_uri = NULL;
+ }
+
+ g_free (dnd_info->target_uri);
+ dnd_info->target_uri = NULL;
+
+ remove_hover_timer (dnd_info);
+}
+
+static void
+drag_leave_callback (GtkWidget *widget,
+ GdkDragContext *context,
+ guint32 time,
+ gpointer data)
+{
+ NautilusCanvasDndInfo *dnd_info;
+
+ dnd_info = NAUTILUS_CANVAS_CONTAINER (widget)->details->dnd_info;
+
+ if (dnd_info->shadow != NULL)
+ {
+ eel_canvas_item_hide (dnd_info->shadow);
+ }
+
+ stop_dnd_highlight (widget);
+
+ set_drop_target (NAUTILUS_CANVAS_CONTAINER (widget), NULL);
+ stop_auto_scroll (NAUTILUS_CANVAS_CONTAINER (widget));
+ nautilus_canvas_container_free_drag_data (NAUTILUS_CANVAS_CONTAINER (widget));
+}
+
+static void
+drag_begin_callback (GtkWidget *widget,
+ GdkDragContext *context,
+ gpointer data)
+{
+ NautilusCanvasContainer *container;
+ NautilusDragInfo *drag_info;
+ NautilusWindow *window;
+ cairo_surface_t *surface;
+ double x1, y1, x2, y2, winx, winy;
+ int x_offset, y_offset;
+ int start_x, start_y;
+ GList *dragged_files;
+ double sx, sy;
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+ window = NAUTILUS_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (container)));
+
+ start_x = container->details->dnd_info->drag_info.start_x +
+ gtk_adjustment_get_value (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container)));
+ start_y = container->details->dnd_info->drag_info.start_y +
+ gtk_adjustment_get_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container)));
+
+ /* create a pixmap and mask to drag with */
+ surface = nautilus_canvas_item_get_drag_surface (container->details->drag_icon->item);
+
+ /* compute the image's offset */
+ eel_canvas_item_get_bounds (EEL_CANVAS_ITEM (container->details->drag_icon->item),
+ &x1, &y1, &x2, &y2);
+ eel_canvas_world_to_window (EEL_CANVAS (container),
+ x1, y1, &winx, &winy);
+ x_offset = start_x - winx;
+ y_offset = start_y - winy;
+
+ cairo_surface_get_device_scale (surface, &sx, &sy);
+ cairo_surface_set_device_offset (surface,
+ -x_offset * sx,
+ -y_offset * sy);
+ gtk_drag_set_icon_surface (context, surface);
+ cairo_surface_destroy (surface);
+
+ /* cache the data at the beginning since the view may change */
+ drag_info = &(container->details->dnd_info->drag_info);
+ drag_info->selection_cache = nautilus_drag_create_selection_cache (widget,
+ each_icon_get_data_binder);
+
+ container->details->dnd_source_info->selection_cache = nautilus_drag_create_selection_cache (widget,
+ each_icon_get_data_binder);
+
+ dragged_files = nautilus_drag_file_list_from_selection_list (drag_info->selection_cache);
+ if (nautilus_file_list_are_all_folders (dragged_files))
+ {
+ nautilus_window_start_dnd (window, context);
+ }
+ g_list_free_full (dragged_files, g_object_unref);
+}
+
+void
+nautilus_canvas_dnd_begin_drag (NautilusCanvasContainer *container,
+ GdkDragAction actions,
+ int button,
+ GdkEventMotion *event,
+ int start_x,
+ int start_y)
+{
+ NautilusCanvasDndInfo *dnd_info;
+ NautilusDragInfo *dnd_source_info;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+ g_return_if_fail (event != NULL);
+
+ dnd_info = container->details->dnd_info;
+ container->details->dnd_source_info = g_new0 (NautilusDragInfo, 1);
+ dnd_source_info = container->details->dnd_source_info;
+ g_return_if_fail (dnd_info != NULL);
+
+ /* Notice that the event is in bin_window coordinates, because of
+ * the way the canvas handles events.
+ */
+ dnd_info->drag_info.start_x = start_x -
+ gtk_adjustment_get_value (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container)));
+ dnd_info->drag_info.start_y = start_y -
+ gtk_adjustment_get_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container)));
+
+ dnd_source_info->source_actions = actions;
+ /* start the drag */
+ gtk_drag_begin_with_coordinates (GTK_WIDGET (container),
+ dnd_info->drag_info.target_list,
+ actions,
+ button,
+ (GdkEvent *) event,
+ dnd_info->drag_info.start_x,
+ dnd_info->drag_info.start_y);
+}
+
+static gboolean
+drag_highlight_draw (GtkWidget *widget,
+ cairo_t *cr,
+ gpointer user_data)
+{
+ gint width, height;
+ GdkWindow *window;
+ GtkStyleContext *style;
+
+ window = gtk_widget_get_window (widget);
+ width = gdk_window_get_width (window);
+ height = gdk_window_get_height (window);
+
+ style = gtk_widget_get_style_context (widget);
+
+ gtk_style_context_save (style);
+ gtk_style_context_add_class (style, GTK_STYLE_CLASS_DND);
+ gtk_style_context_set_state (style, GTK_STATE_FLAG_FOCUSED);
+
+ gtk_render_frame (style,
+ cr,
+ 0, 0, width, height);
+
+ gtk_style_context_restore (style);
+
+ return FALSE;
+}
+
+/* Queue a redraw of the dnd highlight rect */
+static void
+dnd_highlight_queue_redraw (GtkWidget *widget)
+{
+ NautilusCanvasDndInfo *dnd_info;
+ int width, height;
+ GtkAllocation allocation;
+
+ dnd_info = NAUTILUS_CANVAS_CONTAINER (widget)->details->dnd_info;
+
+ if (!dnd_info->highlighted)
+ {
+ return;
+ }
+
+ gtk_widget_get_allocation (widget, &allocation);
+ width = allocation.width;
+ height = allocation.height;
+
+ /* we don't know how wide the shadow is exactly,
+ * so we expose a 10-pixel wide border
+ */
+ gtk_widget_queue_draw_area (widget,
+ 0, 0,
+ width, 10);
+ gtk_widget_queue_draw_area (widget,
+ 0, 0,
+ 10, height);
+ gtk_widget_queue_draw_area (widget,
+ 0, height - 10,
+ width, 10);
+ gtk_widget_queue_draw_area (widget,
+ width - 10, 0,
+ 10, height);
+}
+
+static void
+start_dnd_highlight (GtkWidget *widget)
+{
+ NautilusCanvasDndInfo *dnd_info;
+
+ dnd_info = NAUTILUS_CANVAS_CONTAINER (widget)->details->dnd_info;
+
+ if (!dnd_info->highlighted)
+ {
+ dnd_info->highlighted = TRUE;
+ g_signal_connect_after (widget, "draw",
+ G_CALLBACK (drag_highlight_draw),
+ NULL);
+ dnd_highlight_queue_redraw (widget);
+ }
+}
+
+static void
+stop_dnd_highlight (GtkWidget *widget)
+{
+ NautilusCanvasDndInfo *dnd_info;
+
+ dnd_info = NAUTILUS_CANVAS_CONTAINER (widget)->details->dnd_info;
+
+ if (dnd_info->highlighted)
+ {
+ g_signal_handlers_disconnect_by_func (widget,
+ drag_highlight_draw,
+ NULL);
+ dnd_highlight_queue_redraw (widget);
+ dnd_info->highlighted = FALSE;
+ }
+}
+
+static gboolean
+hover_timer (gpointer user_data)
+{
+ NautilusCanvasContainer *container = user_data;
+ NautilusCanvasDndInfo *dnd_info;
+
+ dnd_info = container->details->dnd_info;
+
+ dnd_info->hover_id = 0;
+
+ g_signal_emit_by_name (container, "handle-hover", dnd_info->target_uri);
+
+ return FALSE;
+}
+
+static void
+check_hover_timer (NautilusCanvasContainer *container,
+ const char *uri)
+{
+ NautilusCanvasDndInfo *dnd_info;
+ GtkSettings *settings;
+ guint timeout;
+
+ dnd_info = container->details->dnd_info;
+
+ if (g_strcmp0 (uri, dnd_info->target_uri) == 0)
+ {
+ return;
+ }
+
+ remove_hover_timer (dnd_info);
+
+ settings = gtk_widget_get_settings (GTK_WIDGET (container));
+ g_object_get (settings, "gtk-timeout-expand", &timeout, NULL);
+
+ g_free (dnd_info->target_uri);
+ dnd_info->target_uri = NULL;
+
+ if (uri != NULL)
+ {
+ dnd_info->target_uri = g_strdup (uri);
+ dnd_info->hover_id =
+ gdk_threads_add_timeout (timeout,
+ hover_timer,
+ container);
+ }
+}
+
+static gboolean
+drag_motion_callback (GtkWidget *widget,
+ GdkDragContext *context,
+ int x,
+ int y,
+ guint32 time)
+{
+ int action;
+
+ nautilus_canvas_container_ensure_drag_data (NAUTILUS_CANVAS_CONTAINER (widget), context, time);
+ nautilus_canvas_container_position_shadow (NAUTILUS_CANVAS_CONTAINER (widget), x, y);
+ nautilus_canvas_dnd_update_drop_target (NAUTILUS_CANVAS_CONTAINER (widget), context, x, y);
+ set_up_auto_scroll_if_needed (NAUTILUS_CANVAS_CONTAINER (widget));
+ /* Find out what the drop actions are based on our drag selection and
+ * the drop target.
+ */
+ action = 0;
+ nautilus_canvas_container_get_drop_action (NAUTILUS_CANVAS_CONTAINER (widget), context, x, y,
+ &action);
+ if (action != 0)
+ {
+ char *uri;
+ uri = nautilus_canvas_container_find_drop_target (NAUTILUS_CANVAS_CONTAINER (widget),
+ context, x, y, NULL);
+ check_hover_timer (NAUTILUS_CANVAS_CONTAINER (widget), uri);
+ g_free (uri);
+ start_dnd_highlight (widget);
+ }
+ else
+ {
+ remove_hover_timer (NAUTILUS_CANVAS_CONTAINER (widget)->details->dnd_info);
+ }
+
+ gdk_drag_status (context, action, time);
+
+ return TRUE;
+}
+
+static gboolean
+drag_drop_callback (GtkWidget *widget,
+ GdkDragContext *context,
+ int x,
+ int y,
+ guint32 time,
+ gpointer data)
+{
+ NautilusCanvasDndInfo *dnd_info;
+
+ dnd_info = NAUTILUS_CANVAS_CONTAINER (widget)->details->dnd_info;
+
+ /* tell the drag_data_received callback that
+ * the drop occurred and that it can actually
+ * process the actions.
+ * make sure it is going to be called at least once.
+ */
+ dnd_info->drag_info.drop_occurred = TRUE;
+
+ get_data_on_first_target_we_support (widget, context, time, x, y);
+
+ return TRUE;
+}
+
+void
+nautilus_canvas_dnd_end_drag (NautilusCanvasContainer *container)
+{
+ NautilusCanvasDndInfo *dnd_info;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ dnd_info = container->details->dnd_info;
+ g_return_if_fail (dnd_info != NULL);
+ stop_auto_scroll (container);
+ /* Do nothing.
+ * Can that possibly be right?
+ */
+}
+
+/** this callback is called in 2 cases.
+ * It is called upon drag_motion events to get the actual data
+ * In that case, it just makes sure it gets the data.
+ * It is called upon drop_drop events to execute the actual
+ * actions on the received action. In that case, it actually first makes sure
+ * that we have got the data then processes it.
+ */
+
+static void
+drag_data_received_callback (GtkWidget *widget,
+ GdkDragContext *context,
+ int x,
+ int y,
+ GtkSelectionData *data,
+ guint info,
+ guint32 time,
+ gpointer user_data)
+{
+ NautilusDragInfo *drag_info;
+ guchar *tmp;
+ const guchar *tmp_raw;
+ int length;
+ gboolean success;
+
+ drag_info = &(NAUTILUS_CANVAS_CONTAINER (widget)->details->dnd_info->drag_info);
+
+ drag_info->got_drop_data_type = TRUE;
+ drag_info->data_type = info;
+
+ switch (info)
+ {
+ case NAUTILUS_ICON_DND_GNOME_ICON_LIST:
+ {
+ nautilus_canvas_container_dropped_canvas_feedback (widget, data, x, y);
+ }
+ break;
+
+ case NAUTILUS_ICON_DND_URI_LIST:
+ case NAUTILUS_ICON_DND_TEXT:
+ case NAUTILUS_ICON_DND_XDNDDIRECTSAVE:
+ case NAUTILUS_ICON_DND_RAW:
+ {
+ /* Save the data so we can do the actual work on drop. */
+ if (drag_info->selection_data != NULL)
+ {
+ gtk_selection_data_free (drag_info->selection_data);
+ }
+ drag_info->selection_data = gtk_selection_data_copy (data);
+ }
+ break;
+
+ /* Netscape keeps sending us the data, even though we accept the first drag */
+ case NAUTILUS_ICON_DND_NETSCAPE_URL:
+ {
+ if (drag_info->selection_data != NULL)
+ {
+ gtk_selection_data_free (drag_info->selection_data);
+ drag_info->selection_data = gtk_selection_data_copy (data);
+ }
+ }
+ break;
+
+ case NAUTILUS_ICON_DND_ROOTWINDOW_DROP:
+ {
+ /* Do nothing, this won't even happen, since we don't want to call get_data twice */
+ }
+ break;
+ }
+
+ /* this is the second use case of this callback.
+ * we have to do the actual work for the drop.
+ */
+ if (drag_info->drop_occurred)
+ {
+ success = FALSE;
+ switch (info)
+ {
+ case NAUTILUS_ICON_DND_GNOME_ICON_LIST:
+ {
+ nautilus_canvas_container_receive_dropped_icons
+ (NAUTILUS_CANVAS_CONTAINER (widget),
+ context, x, y);
+ }
+ break;
+
+ case NAUTILUS_ICON_DND_NETSCAPE_URL:
+ {
+ receive_dropped_netscape_url
+ (NAUTILUS_CANVAS_CONTAINER (widget),
+ (char *) gtk_selection_data_get_data (data), context, x, y);
+ success = TRUE;
+ }
+ break;
+
+ case NAUTILUS_ICON_DND_URI_LIST:
+ {
+ receive_dropped_uri_list
+ (NAUTILUS_CANVAS_CONTAINER (widget),
+ (char *) gtk_selection_data_get_data (data), context, x, y);
+ success = TRUE;
+ }
+ break;
+
+ case NAUTILUS_ICON_DND_TEXT:
+ {
+ tmp = gtk_selection_data_get_text (data);
+ receive_dropped_text
+ (NAUTILUS_CANVAS_CONTAINER (widget),
+ (char *) tmp, context, x, y);
+ success = TRUE;
+ g_free (tmp);
+ }
+ break;
+
+ case NAUTILUS_ICON_DND_RAW:
+ {
+ length = gtk_selection_data_get_length (data);
+ tmp_raw = gtk_selection_data_get_data (data);
+ receive_dropped_raw
+ (NAUTILUS_CANVAS_CONTAINER (widget),
+ (const gchar *) tmp_raw, length, drag_info->direct_save_uri,
+ context, x, y);
+ success = TRUE;
+ }
+ break;
+
+ case NAUTILUS_ICON_DND_ROOTWINDOW_DROP:
+ {
+ /* Do nothing, everything is done by the sender */
+ }
+ break;
+
+ case NAUTILUS_ICON_DND_XDNDDIRECTSAVE:
+ {
+ const guchar *selection_data;
+ gint selection_length;
+ gint selection_format;
+
+ selection_data = gtk_selection_data_get_data (drag_info->selection_data);
+ selection_length = gtk_selection_data_get_length (drag_info->selection_data);
+ selection_format = gtk_selection_data_get_format (drag_info->selection_data);
+
+ if (selection_format == 8 &&
+ selection_length == 1 &&
+ selection_data[0] == 'F')
+ {
+ gtk_drag_get_data (widget, context,
+ gdk_atom_intern (NAUTILUS_ICON_DND_RAW_TYPE,
+ FALSE),
+ time);
+ return;
+ }
+ else if (selection_format == 8 &&
+ selection_length == 1 &&
+ selection_data[0] == 'F' &&
+ drag_info->direct_save_uri != NULL)
+ {
+ GFile *location;
+
+ location = g_file_new_for_uri (drag_info->direct_save_uri);
+
+ nautilus_file_changes_queue_file_added (location);
+ g_object_unref (location);
+ nautilus_file_changes_consume_changes (TRUE);
+ success = TRUE;
+ }
+ } /* NAUTILUS_ICON_DND_XDNDDIRECTSAVE */
+ break;
+ }
+ gtk_drag_finish (context, success, FALSE, time);
+
+ nautilus_canvas_container_free_drag_data (NAUTILUS_CANVAS_CONTAINER (widget));
+
+ set_drop_target (NAUTILUS_CANVAS_CONTAINER (widget), NULL);
+
+ /* reinitialise it for the next dnd */
+ drag_info->drop_occurred = FALSE;
+ }
+}
+
+void
+nautilus_canvas_dnd_init (NautilusCanvasContainer *container)
+{
+ GtkTargetList *targets;
+ int n_elements;
+
+ g_return_if_fail (container != NULL);
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+
+ container->details->dnd_info = g_new0 (NautilusCanvasDndInfo, 1);
+ nautilus_drag_init (&container->details->dnd_info->drag_info,
+ drag_types, G_N_ELEMENTS (drag_types), TRUE);
+
+ /* Set up the widget as a drag destination.
+ * (But not a source, as drags starting from this widget will be
+ * implemented by dealing with events manually.)
+ */
+ n_elements = G_N_ELEMENTS (drop_types) - 1;
+ gtk_drag_dest_set (GTK_WIDGET (container),
+ 0,
+ drop_types, n_elements,
+ GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK | GDK_ACTION_ASK);
+
+ targets = gtk_drag_dest_get_target_list (GTK_WIDGET (container));
+ gtk_target_list_add_text_targets (targets, NAUTILUS_ICON_DND_TEXT);
+
+
+ /* Messages for outgoing drag. */
+ g_signal_connect (container, "drag-begin",
+ G_CALLBACK (drag_begin_callback), NULL);
+ g_signal_connect (container, "drag-data-get",
+ G_CALLBACK (drag_data_get_callback), NULL);
+ g_signal_connect (container, "drag-end",
+ G_CALLBACK (drag_end_callback), NULL);
+
+ /* Messages for incoming drag. */
+ g_signal_connect (container, "drag-data-received",
+ G_CALLBACK (drag_data_received_callback), NULL);
+ g_signal_connect (container, "drag-motion",
+ G_CALLBACK (drag_motion_callback), NULL);
+ g_signal_connect (container, "drag-drop",
+ G_CALLBACK (drag_drop_callback), NULL);
+ g_signal_connect (container, "drag-leave",
+ G_CALLBACK (drag_leave_callback), NULL);
+}
+
+void
+nautilus_canvas_dnd_fini (NautilusCanvasContainer *container)
+{
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ if (container->details->dnd_info != NULL)
+ {
+ stop_auto_scroll (container);
+
+ nautilus_drag_finalize (&container->details->dnd_info->drag_info);
+ container->details->dnd_info = NULL;
+ }
+}
diff --git a/src/nautilus-canvas-dnd.h b/src/nautilus-canvas-dnd.h
new file mode 100644
index 0000000..0e8b980
--- /dev/null
+++ b/src/nautilus-canvas-dnd.h
@@ -0,0 +1,56 @@
+
+/* nautilus-canvas-dnd.h - Drag & drop handling for the canvas container widget.
+
+ Copyright (C) 1999, 2000 Free Software Foundation
+ 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: Ettore Perazzoli <ettore@gnu.org>,
+ Darin Adler <darin@bentspoon.com>,
+ Andy Hertzfeld <andy@eazel.com>
+*/
+
+#pragma once
+
+#include "nautilus-canvas-container.h"
+#include "nautilus-dnd.h"
+
+/* DnD-related information. */
+typedef struct {
+ /* inherited drag info context */
+ NautilusDragInfo drag_info;
+
+ gboolean highlighted;
+ char *target_uri;
+
+ /* Shadow for the icons being dragged. */
+ EelCanvasItem *shadow;
+ guint hover_id;
+} NautilusCanvasDndInfo;
+
+
+void nautilus_canvas_dnd_init (NautilusCanvasContainer *container);
+void nautilus_canvas_dnd_fini (NautilusCanvasContainer *container);
+void nautilus_canvas_dnd_begin_drag (NautilusCanvasContainer *container,
+ GdkDragAction actions,
+ gint button,
+ GdkEventMotion *event,
+ int start_x,
+ int start_y);
+void nautilus_canvas_dnd_end_drag (NautilusCanvasContainer *container);
+
+NautilusDragInfo* nautilus_canvas_dnd_get_drag_source_data (NautilusCanvasContainer *container,
+ GdkDragContext *context);
diff --git a/src/nautilus-canvas-item.c b/src/nautilus-canvas-item.c
new file mode 100644
index 0000000..a3c3895
--- /dev/null
+++ b/src/nautilus-canvas-item.c
@@ -0,0 +1,2752 @@
+/* Nautilus - Canvas item class for canvas container.
+ *
+ * Copyright (C) 2000 Eazel, Inc
+ *
+ * Author: Andy Hertzfeld <andy@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/>.
+ */
+
+#include <config.h>
+#include <math.h>
+#include "nautilus-canvas-item.h"
+
+#include <glib/gi18n.h>
+
+#include "nautilus-file-utilities.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-canvas-private.h"
+#include <eel/eel-art-extensions.h>
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-graphic-effects.h>
+#include <eel/eel-string.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+#include <glib/gi18n.h>
+#include <atk/atkimage.h>
+#include <atk/atkcomponent.h>
+#include <atk/atknoopobject.h>
+#include <stdio.h>
+#include <string.h>
+
+/* gap between bottom of icon and start of text box */
+#define LABEL_OFFSET 1
+#define LABEL_LINE_SPACING 0
+
+/* Text padding */
+#define TEXT_BACK_PADDING_X 4
+#define TEXT_BACK_PADDING_Y 1
+
+/* Width of the label, keep in sync with ICON_GRID_WIDTH at nautilus-canvas-container.c */
+#define MAX_TEXT_WIDTH_SMALL 116
+#define MAX_TEXT_WIDTH_STANDARD 104
+#define MAX_TEXT_WIDTH_LARGE 98
+#define MAX_TEXT_WIDTH_LARGER 100
+
+/* special text height handling
+ * each item has three text height variables:
+ * + text_height: actual height of the displayed (i.e. on-screen) PangoLayout.
+ * + text_height_for_layout: height used in canvas grid layout algorithms.
+ * “sane amount” of text.
+ * “sane amount“ as of
+ * + hard-coded to three lines in text-below-icon mode.
+ *
+ * This layout height is used by grid layout algorithms, even
+ * though the actually displayed and/or requested text size may be larger
+ * and overlap adjacent icons, if an icon is selected.
+ *
+ * + text_height_for_entire_text: height needed to display the entire PangoLayout,
+ * if it wasn't ellipsized.
+ */
+
+/* Private part of the NautilusCanvasItem structure. */
+struct NautilusCanvasItemDetails
+{
+ /* The image, text, font. */
+ double x, y;
+ GdkPixbuf *pixbuf;
+ cairo_surface_t *rendered_surface;
+ char *editable_text; /* Text that can be modified by a renaming function */
+ char *additional_text; /* Text that cannot be modifed, such as file size, etc. */
+
+ /* Size of the text at current font. */
+ int text_dx;
+ int text_width;
+
+ /* actual size required for rendering the text to display */
+ int text_height;
+ /* actual size that would be required for rendering the entire text if it wasn't ellipsized */
+ int text_height_for_entire_text;
+ /* actual size needed for rendering a “sane amount” of text */
+ int text_height_for_layout;
+
+ int editable_text_height;
+
+ /* whether the entire text must always be visible. In that case,
+ * text_height_for_layout will always be equal to text_height.
+ * Used for the last line of a line-wise icon layout. */
+ guint entire_text : 1;
+
+ /* Highlight state. */
+ guint is_highlighted_for_selection : 1;
+ guint is_highlighted_as_keyboard_focus : 1;
+ guint is_highlighted_for_drop : 1;
+ guint is_highlighted_for_clipboard : 1;
+ guint is_prelit : 1;
+
+ guint rendered_is_highlighted_for_selection : 1;
+ guint rendered_is_highlighted_for_drop : 1;
+ guint rendered_is_highlighted_for_clipboard : 1;
+ guint rendered_is_prelit : 1;
+ guint rendered_is_focused : 1;
+
+ guint bounds_cached : 1;
+
+ guint is_visible : 1;
+
+ /* Cached PangoLayouts. Only used if the icon is visible */
+ PangoLayout *editable_text_layout;
+ PangoLayout *additional_text_layout;
+
+ /* Cached rectangle in canvas coordinates */
+ EelIRect icon_rect;
+ EelIRect text_rect;
+
+ EelIRect bounds_cache;
+ EelIRect bounds_cache_for_layout;
+ EelIRect bounds_cache_for_entire_item;
+
+ GdkWindow *cursor_window;
+
+ GString *text;
+};
+
+/* Object argument IDs. */
+enum
+{
+ PROP_0,
+ PROP_EDITABLE_TEXT,
+ PROP_ADDITIONAL_TEXT,
+ PROP_HIGHLIGHTED_FOR_SELECTION,
+ PROP_HIGHLIGHTED_AS_KEYBOARD_FOCUS,
+ PROP_HIGHLIGHTED_FOR_DROP,
+ PROP_HIGHLIGHTED_FOR_CLIPBOARD
+};
+
+typedef enum
+{
+ RIGHT_SIDE,
+ BOTTOM_SIDE,
+ LEFT_SIDE,
+ TOP_SIDE
+} RectangleSide;
+
+static GType nautilus_canvas_item_accessible_factory_get_type (void);
+
+G_DEFINE_TYPE (NautilusCanvasItem, nautilus_canvas_item, EEL_TYPE_CANVAS_ITEM)
+
+/* private */
+static void get_icon_rectangle (NautilusCanvasItem *item,
+ EelIRect *rect);
+static PangoLayout *get_label_layout (PangoLayout **layout,
+ NautilusCanvasItem *item,
+ const char *text);
+
+static void nautilus_canvas_item_ensure_bounds_up_to_date (NautilusCanvasItem *canvas_item);
+
+/* Object initialization function for the canvas item. */
+static void
+nautilus_canvas_item_init (NautilusCanvasItem *canvas_item)
+{
+ canvas_item->details = G_TYPE_INSTANCE_GET_PRIVATE ((canvas_item), NAUTILUS_TYPE_CANVAS_ITEM, NautilusCanvasItemDetails);
+ nautilus_canvas_item_invalidate_label_size (canvas_item);
+}
+
+static void
+nautilus_canvas_item_finalize (GObject *object)
+{
+ NautilusCanvasItemDetails *details;
+
+ g_assert (NAUTILUS_IS_CANVAS_ITEM (object));
+
+ details = NAUTILUS_CANVAS_ITEM (object)->details;
+
+ if (details->cursor_window != NULL)
+ {
+ gdk_window_set_cursor (details->cursor_window, NULL);
+ g_object_unref (details->cursor_window);
+ }
+
+ if (details->pixbuf != NULL)
+ {
+ g_object_unref (details->pixbuf);
+ }
+
+ if (details->text != NULL)
+ {
+ g_string_free (details->text, TRUE);
+ details->text = NULL;
+ }
+
+ g_free (details->editable_text);
+ g_free (details->additional_text);
+
+ if (details->rendered_surface != NULL)
+ {
+ cairo_surface_destroy (details->rendered_surface);
+ }
+
+ if (details->editable_text_layout != NULL)
+ {
+ g_object_unref (details->editable_text_layout);
+ }
+
+ if (details->additional_text_layout != NULL)
+ {
+ g_object_unref (details->additional_text_layout);
+ }
+
+ G_OBJECT_CLASS (nautilus_canvas_item_parent_class)->finalize (object);
+}
+
+/* Currently we require pixbufs in this format (for hit testing).
+ * Perhaps gdk-pixbuf will be changed so it can do the hit testing
+ * and we won't have this requirement any more.
+ */
+static gboolean
+pixbuf_is_acceptable (GdkPixbuf *pixbuf)
+{
+ return gdk_pixbuf_get_colorspace (pixbuf) == GDK_COLORSPACE_RGB
+ && ((!gdk_pixbuf_get_has_alpha (pixbuf)
+ && gdk_pixbuf_get_n_channels (pixbuf) == 3)
+ || (gdk_pixbuf_get_has_alpha (pixbuf)
+ && gdk_pixbuf_get_n_channels (pixbuf) == 4))
+ && gdk_pixbuf_get_bits_per_sample (pixbuf) == 8;
+}
+
+static void
+nautilus_canvas_item_invalidate_bounds_cache (NautilusCanvasItem *item)
+{
+ item->details->bounds_cached = FALSE;
+}
+
+/* invalidate the text width and height cached in the item details. */
+void
+nautilus_canvas_item_invalidate_label_size (NautilusCanvasItem *item)
+{
+ if (item->details->editable_text_layout != NULL)
+ {
+ pango_layout_context_changed (item->details->editable_text_layout);
+ }
+ if (item->details->additional_text_layout != NULL)
+ {
+ pango_layout_context_changed (item->details->additional_text_layout);
+ }
+ nautilus_canvas_item_invalidate_bounds_cache (item);
+ item->details->text_width = -1;
+ item->details->text_height = -1;
+ item->details->text_height_for_layout = -1;
+ item->details->text_height_for_entire_text = -1;
+ item->details->editable_text_height = -1;
+}
+
+/* Set property handler for the canvas item. */
+static void
+nautilus_canvas_item_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusCanvasItem *item;
+ NautilusCanvasItemDetails *details;
+ AtkObject *accessible;
+ gboolean is_rename;
+
+ item = NAUTILUS_CANVAS_ITEM (object);
+ accessible = atk_gobject_accessible_for_object (G_OBJECT (item));
+ details = item->details;
+
+ switch (property_id)
+ {
+ case PROP_EDITABLE_TEXT:
+ {
+ if (g_strcmp0 (details->editable_text,
+ g_value_get_string (value)) == 0)
+ {
+ return;
+ }
+
+ is_rename = details->editable_text != NULL;
+ g_free (details->editable_text);
+ details->editable_text = g_strdup (g_value_get_string (value));
+ if (details->text)
+ {
+ details->text = g_string_assign (details->text, details->editable_text);
+
+ if (is_rename)
+ {
+ g_object_notify (G_OBJECT (accessible), "accessible-name");
+ }
+ }
+
+ nautilus_canvas_item_invalidate_label_size (item);
+ if (details->editable_text_layout)
+ {
+ g_object_unref (details->editable_text_layout);
+ details->editable_text_layout = NULL;
+ }
+ }
+ break;
+
+ case PROP_ADDITIONAL_TEXT:
+ {
+ if (g_strcmp0 (details->additional_text,
+ g_value_get_string (value)) == 0)
+ {
+ return;
+ }
+
+ g_free (details->additional_text);
+ details->additional_text = g_strdup (g_value_get_string (value));
+
+ nautilus_canvas_item_invalidate_label_size (item);
+ if (details->additional_text_layout)
+ {
+ g_object_unref (details->additional_text_layout);
+ details->additional_text_layout = NULL;
+ }
+ }
+ break;
+
+ case PROP_HIGHLIGHTED_FOR_SELECTION:
+ {
+ if (!details->is_highlighted_for_selection == !g_value_get_boolean (value))
+ {
+ return;
+ }
+ details->is_highlighted_for_selection = g_value_get_boolean (value);
+ nautilus_canvas_item_invalidate_label_size (item);
+
+ atk_object_notify_state_change (accessible, ATK_STATE_SELECTED,
+ details->is_highlighted_for_selection);
+ }
+ break;
+
+ case PROP_HIGHLIGHTED_AS_KEYBOARD_FOCUS:
+ {
+ if (!details->is_highlighted_as_keyboard_focus == !g_value_get_boolean (value))
+ {
+ return;
+ }
+ details->is_highlighted_as_keyboard_focus = g_value_get_boolean (value);
+
+ atk_object_notify_state_change (accessible, ATK_STATE_FOCUSED,
+ details->is_highlighted_as_keyboard_focus);
+ }
+ break;
+
+ case PROP_HIGHLIGHTED_FOR_DROP:
+ {
+ if (!details->is_highlighted_for_drop == !g_value_get_boolean (value))
+ {
+ return;
+ }
+ details->is_highlighted_for_drop = g_value_get_boolean (value);
+ }
+ break;
+
+ case PROP_HIGHLIGHTED_FOR_CLIPBOARD:
+ {
+ if (!details->is_highlighted_for_clipboard == !g_value_get_boolean (value))
+ {
+ return;
+ }
+ details->is_highlighted_for_clipboard = g_value_get_boolean (value);
+ }
+ break;
+
+ default:
+ g_warning ("nautilus_canvas_item_set_property on unknown argument");
+ return;
+ }
+
+ eel_canvas_item_request_update (EEL_CANVAS_ITEM (object));
+}
+
+/* Get property handler for the canvas item */
+static void
+nautilus_canvas_item_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusCanvasItemDetails *details;
+
+ details = NAUTILUS_CANVAS_ITEM (object)->details;
+
+ switch (property_id)
+ {
+ case PROP_EDITABLE_TEXT:
+ {
+ g_value_set_string (value, details->editable_text);
+ }
+ break;
+
+ case PROP_ADDITIONAL_TEXT:
+ {
+ g_value_set_string (value, details->additional_text);
+ }
+ break;
+
+ case PROP_HIGHLIGHTED_FOR_SELECTION:
+ {
+ g_value_set_boolean (value, details->is_highlighted_for_selection);
+ }
+ break;
+
+ case PROP_HIGHLIGHTED_AS_KEYBOARD_FOCUS:
+ {
+ g_value_set_boolean (value, details->is_highlighted_as_keyboard_focus);
+ }
+ break;
+
+ case PROP_HIGHLIGHTED_FOR_DROP:
+ {
+ g_value_set_boolean (value, details->is_highlighted_for_drop);
+ }
+ break;
+
+ case PROP_HIGHLIGHTED_FOR_CLIPBOARD:
+ {
+ g_value_set_boolean (value, details->is_highlighted_for_clipboard);
+ }
+ break;
+
+ default:
+ {
+ g_warning ("invalid property %d", property_id);
+ }
+ break;
+ }
+}
+
+static void
+get_scaled_icon_size (NautilusCanvasItem *item,
+ gint *width,
+ gint *height)
+{
+ EelCanvas *canvas;
+ GdkPixbuf *pixbuf = NULL;
+ gint scale;
+
+ if (item != NULL)
+ {
+ canvas = EEL_CANVAS_ITEM (item)->canvas;
+ scale = gtk_widget_get_scale_factor (GTK_WIDGET (canvas));
+ pixbuf = item->details->pixbuf;
+ }
+
+ if (width)
+ {
+ *width = (pixbuf == NULL) ? 0 : (gdk_pixbuf_get_width (pixbuf) / scale);
+ }
+ if (height)
+ {
+ *height = (pixbuf == NULL) ? 0 : (gdk_pixbuf_get_height (pixbuf) / scale);
+ }
+}
+
+void
+nautilus_canvas_item_set_image (NautilusCanvasItem *item,
+ GdkPixbuf *image)
+{
+ NautilusCanvasItemDetails *details;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_ITEM (item));
+ g_return_if_fail (image == NULL || pixbuf_is_acceptable (image));
+
+ details = item->details;
+ if (details->pixbuf == image)
+ {
+ return;
+ }
+
+ if (image != NULL)
+ {
+ g_object_ref (image);
+ }
+ if (details->pixbuf != NULL)
+ {
+ g_object_unref (details->pixbuf);
+ }
+ if (details->rendered_surface != NULL)
+ {
+ cairo_surface_destroy (details->rendered_surface);
+ details->rendered_surface = NULL;
+ }
+
+ details->pixbuf = image;
+
+ nautilus_canvas_item_invalidate_bounds_cache (item);
+ eel_canvas_item_request_update (EEL_CANVAS_ITEM (item));
+}
+
+/* Recomputes the bounding box of a canvas item.
+ * This is a generic implementation that could be used for any canvas item
+ * class, it has no assumptions about how the item is used.
+ */
+static void
+recompute_bounding_box (NautilusCanvasItem *canvas_item,
+ double i2w_dx,
+ double i2w_dy)
+{
+ /* The bounds stored in the item is the same as what get_bounds
+ * returns, except it's in canvas coordinates instead of the item's
+ * parent's coordinates.
+ */
+
+ EelCanvasItem *item;
+ EelDRect bounds_rect;
+
+ item = EEL_CANVAS_ITEM (canvas_item);
+
+ eel_canvas_item_get_bounds (item,
+ &bounds_rect.x0, &bounds_rect.y0,
+ &bounds_rect.x1, &bounds_rect.y1);
+
+ bounds_rect.x0 += i2w_dx;
+ bounds_rect.y0 += i2w_dy;
+ bounds_rect.x1 += i2w_dx;
+ bounds_rect.y1 += i2w_dy;
+ eel_canvas_w2c_d (item->canvas,
+ bounds_rect.x0, bounds_rect.y0,
+ &item->x1, &item->y1);
+ eel_canvas_w2c_d (item->canvas,
+ bounds_rect.x1, bounds_rect.y1,
+ &item->x2, &item->y2);
+}
+
+static EelIRect
+compute_text_rectangle (const NautilusCanvasItem *item,
+ EelIRect icon_rectangle,
+ gboolean canvas_coords,
+ NautilusCanvasItemBoundsUsage usage)
+{
+ EelIRect text_rectangle;
+ double pixels_per_unit;
+ double text_width, text_height, text_height_for_layout, text_height_for_entire_text, real_text_height;
+
+ pixels_per_unit = EEL_CANVAS_ITEM (item)->canvas->pixels_per_unit;
+ if (canvas_coords)
+ {
+ text_width = item->details->text_width;
+ text_height = item->details->text_height;
+ text_height_for_layout = item->details->text_height_for_layout;
+ text_height_for_entire_text = item->details->text_height_for_entire_text;
+ }
+ else
+ {
+ text_width = item->details->text_width / pixels_per_unit;
+ text_height = item->details->text_height / pixels_per_unit;
+ text_height_for_layout = item->details->text_height_for_layout / pixels_per_unit;
+ text_height_for_entire_text = item->details->text_height_for_entire_text / pixels_per_unit;
+ }
+
+ text_rectangle.x0 = (icon_rectangle.x0 + icon_rectangle.x1) / 2 - (int) text_width / 2;
+ text_rectangle.y0 = icon_rectangle.y1;
+ text_rectangle.x1 = text_rectangle.x0 + text_width;
+
+ if (usage == BOUNDS_USAGE_FOR_LAYOUT)
+ {
+ real_text_height = text_height_for_layout;
+ }
+ else if (usage == BOUNDS_USAGE_FOR_ENTIRE_ITEM)
+ {
+ real_text_height = text_height_for_entire_text;
+ }
+ else if (usage == BOUNDS_USAGE_FOR_DISPLAY)
+ {
+ real_text_height = text_height;
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ text_rectangle.y1 = text_rectangle.y0 + real_text_height + LABEL_OFFSET / pixels_per_unit;
+
+ return text_rectangle;
+}
+
+static EelIRect
+get_current_canvas_bounds (EelCanvasItem *item)
+{
+ EelIRect bounds;
+
+ g_assert (EEL_IS_CANVAS_ITEM (item));
+
+ bounds.x0 = item->x1;
+ bounds.y0 = item->y1;
+ bounds.x1 = item->x2;
+ bounds.y1 = item->y2;
+
+ return bounds;
+}
+
+void
+nautilus_canvas_item_update_bounds (NautilusCanvasItem *item,
+ double i2w_dx,
+ double i2w_dy)
+{
+ EelIRect before, after;
+ EelCanvasItem *canvas_item;
+
+ canvas_item = EEL_CANVAS_ITEM (item);
+
+ /* Compute new bounds. */
+ before = get_current_canvas_bounds (canvas_item);
+ recompute_bounding_box (item, i2w_dx, i2w_dy);
+ after = get_current_canvas_bounds (canvas_item);
+
+ /* If the bounds didn't change, we are done. */
+ if (eel_irect_equal (before, after))
+ {
+ return;
+ }
+
+ /* Update canvas and text rect cache */
+ get_icon_rectangle (item, &item->details->icon_rect);
+ item->details->text_rect = compute_text_rectangle (item, item->details->icon_rect,
+ TRUE, BOUNDS_USAGE_FOR_DISPLAY);
+
+ /* queue a redraw. */
+ eel_canvas_request_redraw (canvas_item->canvas,
+ before.x0, before.y0,
+ before.x1 + 1, before.y1 + 1);
+}
+
+/* Update handler for the canvas canvas item. */
+static void
+nautilus_canvas_item_update (EelCanvasItem *item,
+ double i2w_dx,
+ double i2w_dy,
+ gint flags)
+{
+ nautilus_canvas_item_update_bounds (NAUTILUS_CANVAS_ITEM (item), i2w_dx, i2w_dy);
+
+ eel_canvas_item_request_redraw (EEL_CANVAS_ITEM (item));
+
+ EEL_CANVAS_ITEM_CLASS (nautilus_canvas_item_parent_class)->update (item, i2w_dx, i2w_dy, flags);
+}
+
+/* Rendering */
+static gboolean
+in_single_click_mode (void)
+{
+ int click_policy;
+
+ click_policy = g_settings_get_enum (nautilus_preferences,
+ NAUTILUS_PREFERENCES_CLICK_POLICY);
+
+ return click_policy == NAUTILUS_CLICK_POLICY_SINGLE;
+}
+
+
+/* Keep these for a bit while we work on performance of draw_or_measure_label_text. */
+/*
+ #define PERFORMANCE_TEST_DRAW_DISABLE
+ #define PERFORMANCE_TEST_MEASURE_DISABLE
+ */
+
+/* This gets the size of the layout from the position of the layout.
+ * This means that if the layout is right aligned we get the full width
+ * of the layout, not just the width of the text snippet on the right side
+ */
+static void
+layout_get_full_size (PangoLayout *layout,
+ int *width,
+ int *height,
+ int *dx)
+{
+ PangoRectangle logical_rect;
+ int the_width, total_width;
+
+ pango_layout_get_extents (layout, NULL, &logical_rect);
+ the_width = (logical_rect.width + PANGO_SCALE / 2) / PANGO_SCALE;
+ total_width = (logical_rect.x + logical_rect.width + PANGO_SCALE / 2) / PANGO_SCALE;
+
+ if (width != NULL)
+ {
+ *width = the_width;
+ }
+
+ if (height != NULL)
+ {
+ *height = (logical_rect.height + PANGO_SCALE / 2) / PANGO_SCALE;
+ }
+
+ if (dx != NULL)
+ {
+ *dx = total_width - the_width;
+ }
+}
+
+static void
+layout_get_size_for_layout (PangoLayout *layout,
+ int max_layout_line_count,
+ int height_for_entire_text,
+ int *height_for_layout)
+{
+ PangoLayoutIter *iter;
+ PangoRectangle logical_rect;
+ int i;
+
+ /* only use the first max_layout_line_count lines for the gridded auto layout */
+ if (pango_layout_get_line_count (layout) <= max_layout_line_count)
+ {
+ *height_for_layout = height_for_entire_text;
+ }
+ else
+ {
+ *height_for_layout = 0;
+ iter = pango_layout_get_iter (layout);
+ for (i = 0; i < max_layout_line_count; i++)
+ {
+ pango_layout_iter_get_line_extents (iter, NULL, &logical_rect);
+ *height_for_layout += (logical_rect.height + PANGO_SCALE / 2) / PANGO_SCALE;
+
+ if (!pango_layout_iter_next_line (iter))
+ {
+ break;
+ }
+
+ *height_for_layout += pango_layout_get_spacing (layout);
+ }
+ pango_layout_iter_free (iter);
+ }
+}
+
+static double
+nautilus_canvas_item_get_max_text_width (NautilusCanvasItem *item)
+{
+ EelCanvasItem *canvas_item;
+ NautilusCanvasContainer *container;
+ guint max_text_width;
+
+
+ canvas_item = EEL_CANVAS_ITEM (item);
+ container = NAUTILUS_CANVAS_CONTAINER (canvas_item->canvas);
+
+ switch (nautilus_canvas_container_get_zoom_level (container))
+ {
+ case NAUTILUS_CANVAS_ZOOM_LEVEL_SMALL:
+ {
+ max_text_width = MAX_TEXT_WIDTH_SMALL;
+ }
+ break;
+
+ case NAUTILUS_CANVAS_ZOOM_LEVEL_STANDARD:
+ {
+ max_text_width = MAX_TEXT_WIDTH_STANDARD;
+ }
+ break;
+
+ case NAUTILUS_CANVAS_ZOOM_LEVEL_LARGE:
+ {
+ max_text_width = MAX_TEXT_WIDTH_LARGE;
+ }
+ break;
+
+ case NAUTILUS_CANVAS_ZOOM_LEVEL_LARGER:
+ {
+ max_text_width = MAX_TEXT_WIDTH_LARGER;
+ }
+ break;
+
+ default:
+ g_warning ("Zoom level not valid. This may incur in missaligned grid");
+ max_text_width = MAX_TEXT_WIDTH_STANDARD;
+ }
+
+ return max_text_width * canvas_item->canvas->pixels_per_unit - 2 * TEXT_BACK_PADDING_X;
+}
+
+static void
+prepare_pango_layout_width (NautilusCanvasItem *item,
+ PangoLayout *layout)
+{
+ pango_layout_set_width (layout, floor (nautilus_canvas_item_get_max_text_width (item)) * PANGO_SCALE);
+ pango_layout_set_ellipsize (layout, PANGO_ELLIPSIZE_END);
+}
+
+static void
+prepare_pango_layout_for_measure_entire_text (NautilusCanvasItem *item,
+ PangoLayout *layout)
+{
+ prepare_pango_layout_width (item, layout);
+ pango_layout_set_height (layout, G_MININT);
+}
+
+static void
+prepare_pango_layout_for_draw (NautilusCanvasItem *item,
+ PangoLayout *layout)
+{
+ NautilusCanvasItemDetails *details;
+ NautilusCanvasContainer *container;
+ gboolean needs_highlight;
+
+ prepare_pango_layout_width (item, layout);
+
+ container = NAUTILUS_CANVAS_CONTAINER (EEL_CANVAS_ITEM (item)->canvas);
+ details = item->details;
+
+ needs_highlight = details->is_highlighted_for_selection || details->is_highlighted_for_drop;
+
+ if (needs_highlight ||
+ details->is_highlighted_as_keyboard_focus ||
+ details->entire_text)
+ {
+ /* VOODOO-TODO, cf. compute_text_rectangle() */
+ pango_layout_set_height (layout, G_MININT);
+ }
+ else
+ {
+ /* TODO? we might save some resources, when the re-layout is not neccessary in case
+ * the layout height already fits into max. layout lines. But pango should figure this
+ * out itself (which it doesn't ATM).
+ */
+ pango_layout_set_height (layout,
+ nautilus_canvas_container_get_max_layout_lines_for_pango (container));
+ }
+}
+
+static void
+measure_label_text (NautilusCanvasItem *item)
+{
+ NautilusCanvasItemDetails *details;
+ NautilusCanvasContainer *container;
+ gint editable_height, editable_height_for_layout, editable_height_for_entire_text, editable_width, editable_dx;
+ gint additional_height, additional_width, additional_dx;
+ PangoLayout *editable_layout;
+ PangoLayout *additional_layout;
+ gboolean have_editable, have_additional;
+
+ /* check to see if the cached values are still valid; if so, there's
+ * no work necessary
+ */
+
+ if (item->details->text_width >= 0 && item->details->text_height >= 0)
+ {
+ return;
+ }
+
+ details = item->details;
+
+ have_editable = details->editable_text != NULL && details->editable_text[0] != '\0';
+ have_additional = details->additional_text != NULL && details->additional_text[0] != '\0';
+
+ /* No font or no text, then do no work. */
+ if (!have_editable && !have_additional)
+ {
+ details->text_height = 0;
+ details->text_height_for_layout = 0;
+ details->text_height_for_entire_text = 0;
+ details->text_width = 0;
+ return;
+ }
+
+#ifdef PERFORMANCE_TEST_MEASURE_DISABLE
+ /* fake out the width */
+ details->text_width = 80;
+ details->text_height = 20;
+ details->text_height_for_layout = 20;
+ details->text_height_for_entire_text = 20;
+ return;
+#endif
+
+ editable_width = 0;
+ editable_height = 0;
+ editable_height_for_layout = 0;
+ editable_height_for_entire_text = 0;
+ editable_dx = 0;
+ additional_width = 0;
+ additional_height = 0;
+ additional_dx = 0;
+
+ container = NAUTILUS_CANVAS_CONTAINER (EEL_CANVAS_ITEM (item)->canvas);
+ editable_layout = NULL;
+ additional_layout = NULL;
+
+ if (have_editable)
+ {
+ /* first, measure required text height: editable_height_for_entire_text
+ * then, measure text height applicable for layout: editable_height_for_layout
+ * next, measure actually displayed height: editable_height
+ */
+ editable_layout = get_label_layout (&details->editable_text_layout, item, details->editable_text);
+
+ prepare_pango_layout_for_measure_entire_text (item, editable_layout);
+ layout_get_full_size (editable_layout,
+ NULL,
+ &editable_height_for_entire_text,
+ NULL);
+ layout_get_size_for_layout (editable_layout,
+ nautilus_canvas_container_get_max_layout_lines (container),
+ editable_height_for_entire_text,
+ &editable_height_for_layout);
+
+ prepare_pango_layout_for_draw (item, editable_layout);
+ layout_get_full_size (editable_layout,
+ &editable_width,
+ &editable_height,
+ &editable_dx);
+ }
+
+ if (have_additional)
+ {
+ additional_layout = get_label_layout (&details->additional_text_layout, item, details->additional_text);
+ prepare_pango_layout_for_draw (item, additional_layout);
+ layout_get_full_size (additional_layout,
+ &additional_width, &additional_height, &additional_dx);
+ }
+
+ details->editable_text_height = editable_height;
+
+ if (editable_width > additional_width)
+ {
+ details->text_width = editable_width;
+ details->text_dx = editable_dx;
+ }
+ else
+ {
+ details->text_width = additional_width;
+ details->text_dx = additional_dx;
+ }
+
+ if (have_additional)
+ {
+ details->text_height = editable_height + LABEL_LINE_SPACING + additional_height;
+ details->text_height_for_layout = editable_height_for_layout + LABEL_LINE_SPACING + additional_height;
+ details->text_height_for_entire_text = editable_height_for_entire_text + LABEL_LINE_SPACING + additional_height;
+ }
+ else
+ {
+ details->text_height = editable_height;
+ details->text_height_for_layout = editable_height_for_layout;
+ details->text_height_for_entire_text = editable_height_for_entire_text;
+ }
+
+ /* add some extra space for highlighting even when we don't highlight so things won't move */
+
+ /* extra slop for nicer highlighting */
+ details->text_height += TEXT_BACK_PADDING_Y * 2;
+ details->text_height_for_layout += TEXT_BACK_PADDING_Y * 2;
+ details->text_height_for_entire_text += TEXT_BACK_PADDING_Y * 2;
+ details->editable_text_height += TEXT_BACK_PADDING_Y * 2;
+
+ /* extra to make it look nicer */
+ details->text_width += TEXT_BACK_PADDING_X * 2;
+
+ if (editable_layout)
+ {
+ g_object_unref (editable_layout);
+ }
+
+ if (additional_layout)
+ {
+ g_object_unref (additional_layout);
+ }
+}
+
+static void
+draw_label_text (NautilusCanvasItem *item,
+ cairo_t *cr,
+ EelIRect icon_rect)
+{
+ NautilusCanvasItemDetails *details;
+ NautilusCanvasContainer *container;
+ PangoLayout *editable_layout;
+ PangoLayout *additional_layout;
+ GtkStyleContext *context;
+ GtkStateFlags state, base_state;
+ gboolean have_editable, have_additional;
+ gboolean needs_highlight;
+ EelIRect text_rect;
+ int x;
+ int max_text_width;
+ gdouble frame_w, frame_h, frame_x, frame_y;
+ gboolean draw_frame = TRUE;
+
+#ifdef PERFORMANCE_TEST_DRAW_DISABLE
+ return;
+#endif
+
+ details = item->details;
+
+ measure_label_text (item);
+ if (details->text_height == 0 ||
+ details->text_width == 0)
+ {
+ return;
+ }
+
+ container = NAUTILUS_CANVAS_CONTAINER (EEL_CANVAS_ITEM (item)->canvas);
+ context = gtk_widget_get_style_context (GTK_WIDGET (container));
+
+ text_rect = compute_text_rectangle (item, icon_rect, TRUE, BOUNDS_USAGE_FOR_DISPLAY);
+
+ needs_highlight = details->is_highlighted_for_selection || details->is_highlighted_for_drop;
+
+ editable_layout = NULL;
+ additional_layout = NULL;
+
+ have_editable = details->editable_text != NULL && details->editable_text[0] != '\0';
+ have_additional = details->additional_text != NULL && details->additional_text[0] != '\0';
+ g_assert (have_editable || have_additional);
+
+ max_text_width = floor (nautilus_canvas_item_get_max_text_width (item));
+
+ base_state = gtk_widget_get_state_flags (GTK_WIDGET (container));
+ base_state &= ~(GTK_STATE_FLAG_SELECTED |
+ GTK_STATE_FLAG_PRELIGHT);
+ state = base_state;
+
+ /* if the canvas is highlighted, do some set-up */
+ if (needs_highlight)
+ {
+ state |= GTK_STATE_FLAG_SELECTED;
+
+ frame_x = text_rect.x0;
+ frame_y = text_rect.y0;
+ frame_w = text_rect.x1 - text_rect.x0;
+ frame_h = text_rect.y1 - text_rect.y0;
+ }
+ else
+ {
+ draw_frame = FALSE;
+ }
+
+ if (draw_frame)
+ {
+ gtk_style_context_save (context);
+ gtk_style_context_set_state (context, state);
+
+ gtk_render_frame (context, cr,
+ frame_x, frame_y,
+ frame_w, frame_h);
+ gtk_render_background (context, cr,
+ frame_x, frame_y,
+ frame_w, frame_h);
+
+ gtk_style_context_restore (context);
+ }
+
+ x = text_rect.x0 + ((text_rect.x1 - text_rect.x0) - max_text_width) / 2;
+
+ if (have_editable)
+ {
+ state = base_state;
+
+ if (needs_highlight)
+ {
+ state |= GTK_STATE_FLAG_SELECTED;
+ }
+
+ editable_layout = get_label_layout (&item->details->editable_text_layout, item, item->details->editable_text);
+ prepare_pango_layout_for_draw (item, editable_layout);
+
+ gtk_style_context_save (context);
+ gtk_style_context_set_state (context, state);
+
+ gtk_render_layout (context, cr,
+ x, text_rect.y0 + TEXT_BACK_PADDING_Y,
+ editable_layout);
+
+ gtk_style_context_restore (context);
+ }
+
+ if (have_additional)
+ {
+ state = base_state;
+
+ if (needs_highlight)
+ {
+ state |= GTK_STATE_FLAG_SELECTED;
+ }
+
+ additional_layout = get_label_layout (&item->details->additional_text_layout, item, item->details->additional_text);
+ prepare_pango_layout_for_draw (item, additional_layout);
+
+ gtk_style_context_save (context);
+ gtk_style_context_set_state (context, state);
+ gtk_style_context_add_class (context, "dim-label");
+
+ gtk_render_layout (context, cr,
+ x, text_rect.y0 + details->editable_text_height + LABEL_LINE_SPACING + TEXT_BACK_PADDING_Y,
+ additional_layout);
+
+ gtk_style_context_restore (context);
+ }
+
+ if (item->details->is_highlighted_as_keyboard_focus)
+ {
+ if (needs_highlight)
+ {
+ state = GTK_STATE_FLAG_SELECTED;
+ }
+
+ gtk_style_context_save (context);
+ gtk_style_context_set_state (context, state);
+
+ gtk_render_focus (context,
+ cr,
+ text_rect.x0,
+ text_rect.y0,
+ text_rect.x1 - text_rect.x0,
+ text_rect.y1 - text_rect.y0);
+
+ gtk_style_context_restore (context);
+ }
+
+ if (editable_layout != NULL)
+ {
+ g_object_unref (editable_layout);
+ }
+
+ if (additional_layout != NULL)
+ {
+ g_object_unref (additional_layout);
+ }
+}
+
+void
+nautilus_canvas_item_set_is_visible (NautilusCanvasItem *item,
+ gboolean visible)
+{
+ if (item->details->is_visible == visible)
+ {
+ return;
+ }
+
+ item->details->is_visible = visible;
+
+ if (!visible)
+ {
+ nautilus_canvas_item_invalidate_label (item);
+ }
+}
+
+void
+nautilus_canvas_item_invalidate_label (NautilusCanvasItem *item)
+{
+ nautilus_canvas_item_invalidate_label_size (item);
+
+ if (item->details->editable_text_layout)
+ {
+ g_object_unref (item->details->editable_text_layout);
+ item->details->editable_text_layout = NULL;
+ }
+
+ if (item->details->additional_text_layout)
+ {
+ g_object_unref (item->details->additional_text_layout);
+ item->details->additional_text_layout = NULL;
+ }
+}
+
+/* shared code to highlight or dim the passed-in pixbuf */
+static cairo_surface_t *
+real_map_surface (NautilusCanvasItem *canvas_item)
+{
+ EelCanvas *canvas;
+ g_autoptr (GdkPixbuf) temp_pixbuf = NULL;
+ gint scale_factor;
+ GdkWindow *window;
+
+ canvas = EEL_CANVAS_ITEM (canvas_item)->canvas;
+ temp_pixbuf = g_object_ref (canvas_item->details->pixbuf);
+ scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (canvas));
+ window = gtk_widget_get_window (GTK_WIDGET (canvas));
+
+ if (canvas_item->details->is_prelit ||
+ canvas_item->details->is_highlighted_for_clipboard)
+ {
+ g_autoptr (GdkPixbuf) old_pixbuf = NULL;
+
+ old_pixbuf = temp_pixbuf;
+ temp_pixbuf = eel_create_spotlight_pixbuf (temp_pixbuf);
+ }
+
+ if (canvas_item->details->is_highlighted_for_selection
+ || canvas_item->details->is_highlighted_for_drop)
+ {
+ GtkWidget *widget;
+ GtkStyleContext *style;
+ gboolean has_focus;
+ GtkStateFlags state;
+ gint width;
+ gint height;
+ gboolean has_alpha;
+ cairo_format_t format;
+ cairo_surface_t *surface;
+ cairo_t *cr;
+ g_autoptr (GdkPixbuf) pixbuf = NULL;
+ g_autoptr (GdkPixbuf) old_pixbuf = NULL;
+
+ widget = GTK_WIDGET (canvas);
+ style = gtk_widget_get_style_context (widget);
+ has_focus = gtk_widget_has_focus (widget);
+ state = has_focus ? GTK_STATE_FLAG_SELECTED : GTK_STATE_FLAG_ACTIVE;
+ width = gdk_pixbuf_get_width (temp_pixbuf);
+ height = gdk_pixbuf_get_height (temp_pixbuf);
+ has_alpha = gdk_pixbuf_get_has_alpha (temp_pixbuf);
+ format = has_alpha ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24;
+ surface = cairo_image_surface_create (format, width, height);
+ cr = cairo_create (surface);
+
+ gtk_style_context_save (style);
+ gtk_style_context_set_state (style, state);
+
+ gtk_render_background (style, cr,
+ 0, 0,
+ width, height);
+
+ gtk_style_context_restore (style);
+
+ cairo_surface_flush (surface);
+
+ pixbuf = gdk_pixbuf_get_from_surface (surface, 0, 0, width, height);
+ old_pixbuf = temp_pixbuf;
+
+ temp_pixbuf = eel_create_colorized_pixbuf (temp_pixbuf, g_steal_pointer (&pixbuf));
+
+ cairo_destroy (cr);
+ cairo_surface_destroy (surface);
+ }
+
+ return gdk_cairo_surface_create_from_pixbuf (temp_pixbuf, scale_factor, window);
+}
+
+static cairo_surface_t *
+map_surface (NautilusCanvasItem *canvas_item)
+{
+ if (!(canvas_item->details->rendered_surface != NULL
+ && canvas_item->details->rendered_is_prelit == canvas_item->details->is_prelit
+ && canvas_item->details->rendered_is_highlighted_for_selection == canvas_item->details->is_highlighted_for_selection
+ && canvas_item->details->rendered_is_highlighted_for_drop == canvas_item->details->is_highlighted_for_drop
+ && canvas_item->details->rendered_is_highlighted_for_clipboard == canvas_item->details->is_highlighted_for_clipboard
+ && (canvas_item->details->is_highlighted_for_selection && canvas_item->details->rendered_is_focused == gtk_widget_has_focus (GTK_WIDGET (EEL_CANVAS_ITEM (canvas_item)->canvas)))))
+ {
+ if (canvas_item->details->rendered_surface != NULL)
+ {
+ cairo_surface_destroy (canvas_item->details->rendered_surface);
+ }
+ canvas_item->details->rendered_surface = real_map_surface (canvas_item);
+ canvas_item->details->rendered_is_prelit = canvas_item->details->is_prelit;
+ canvas_item->details->rendered_is_highlighted_for_selection = canvas_item->details->is_highlighted_for_selection;
+ canvas_item->details->rendered_is_highlighted_for_drop = canvas_item->details->is_highlighted_for_drop;
+ canvas_item->details->rendered_is_highlighted_for_clipboard = canvas_item->details->is_highlighted_for_clipboard;
+ canvas_item->details->rendered_is_focused = gtk_widget_has_focus (GTK_WIDGET (EEL_CANVAS_ITEM (canvas_item)->canvas));
+ }
+
+ cairo_surface_reference (canvas_item->details->rendered_surface);
+
+ return canvas_item->details->rendered_surface;
+}
+
+cairo_surface_t *
+nautilus_canvas_item_get_drag_surface (NautilusCanvasItem *item)
+{
+ cairo_surface_t *surface;
+ EelCanvas *canvas;
+ int width, height;
+ int pix_width, pix_height;
+ int item_offset_x, item_offset_y;
+ EelIRect icon_rect;
+ double item_x, item_y;
+ cairo_t *cr;
+ GtkStyleContext *context;
+ cairo_surface_t *drag_surface;
+
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_ITEM (item), NULL);
+
+ canvas = EEL_CANVAS_ITEM (item)->canvas;
+ context = gtk_widget_get_style_context (GTK_WIDGET (canvas));
+
+ gtk_style_context_save (context);
+ gtk_style_context_add_class (context, "nautilus-canvas-item");
+
+ /* Assume we're updated so canvas item data is right */
+
+ /* Calculate the offset from the top-left corner of the
+ * new image to the item position (where the pixmap is placed) */
+ eel_canvas_world_to_window (canvas,
+ item->details->x, item->details->y,
+ &item_x, &item_y);
+
+ item_offset_x = item_x - EEL_CANVAS_ITEM (item)->x1;
+ item_offset_y = item_y - EEL_CANVAS_ITEM (item)->y1;
+
+ /* Calculate the width of the item */
+ width = EEL_CANVAS_ITEM (item)->x2 - EEL_CANVAS_ITEM (item)->x1;
+ height = EEL_CANVAS_ITEM (item)->y2 - EEL_CANVAS_ITEM (item)->y1;
+
+ surface = gdk_window_create_similar_surface (gtk_widget_get_window (GTK_WIDGET (canvas)),
+ CAIRO_CONTENT_COLOR_ALPHA,
+ width, height);
+ cr = cairo_create (surface);
+
+ drag_surface = map_surface (item);
+ gtk_render_icon_surface (context, cr, drag_surface,
+ item_offset_x, item_offset_y);
+ cairo_surface_destroy (drag_surface);
+
+ get_scaled_icon_size (item, &pix_width, &pix_height);
+
+ icon_rect.x0 = item_offset_x;
+ icon_rect.y0 = item_offset_y;
+ icon_rect.x1 = item_offset_x + pix_width;
+ icon_rect.y1 = item_offset_y + pix_height;
+
+ draw_label_text (item, cr, icon_rect);
+ cairo_destroy (cr);
+
+ gtk_style_context_restore (context);
+
+ return surface;
+}
+
+/* Draw the canvas item for non-anti-aliased mode. */
+static void
+nautilus_canvas_item_draw (EelCanvasItem *item,
+ cairo_t *cr,
+ cairo_region_t *region)
+{
+ NautilusCanvasContainer *container;
+ NautilusCanvasItem *canvas_item;
+ NautilusCanvasItemDetails *details;
+ EelIRect icon_rect;
+ cairo_surface_t *temp_surface;
+ GtkStyleContext *context;
+
+ container = NAUTILUS_CANVAS_CONTAINER (item->canvas);
+ canvas_item = NAUTILUS_CANVAS_ITEM (item);
+ details = canvas_item->details;
+
+ /* Draw the pixbuf. */
+ if (details->pixbuf == NULL)
+ {
+ return;
+ }
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (container));
+ gtk_style_context_save (context);
+ gtk_style_context_add_class (context, "nautilus-canvas-item");
+
+ icon_rect = canvas_item->details->icon_rect;
+ temp_surface = map_surface (canvas_item);
+
+ gtk_render_icon_surface (context, cr,
+ temp_surface,
+ icon_rect.x0, icon_rect.y0);
+ cairo_surface_destroy (temp_surface);
+
+ /* Draw the label text. */
+ draw_label_text (canvas_item, cr, icon_rect);
+
+ gtk_style_context_restore (context);
+}
+
+#define ZERO_WIDTH_SPACE "\xE2\x80\x8B"
+
+static PangoLayout *
+create_label_layout (NautilusCanvasItem *item,
+ const char *text)
+{
+ PangoLayout *layout;
+ PangoContext *context;
+ PangoFontDescription *desc;
+ NautilusCanvasContainer *container;
+ EelCanvasItem *canvas_item;
+ GString *str;
+ char *zeroified_text;
+ const char *p;
+
+ canvas_item = EEL_CANVAS_ITEM (item);
+
+ container = NAUTILUS_CANVAS_CONTAINER (canvas_item->canvas);
+ context = gtk_widget_get_pango_context (GTK_WIDGET (canvas_item->canvas));
+ layout = pango_layout_new (context);
+
+ zeroified_text = NULL;
+
+ if (text != NULL)
+ {
+ str = g_string_new (NULL);
+
+ for (p = text; *p != '\0'; p++)
+ {
+ str = g_string_append_c (str, *p);
+
+ if (*p == '_' || *p == '-' || (*p == '.' && !g_ascii_isdigit (*(p + 1))))
+ {
+ /* Ensure that we allow to break after '_' or '.' characters,
+ * if they are not followed by a number */
+ str = g_string_append (str, ZERO_WIDTH_SPACE);
+ }
+ }
+
+ zeroified_text = g_string_free (str, FALSE);
+ }
+
+ pango_layout_set_text (layout, zeroified_text, -1);
+ pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
+
+ pango_layout_set_spacing (layout, LABEL_LINE_SPACING);
+ pango_layout_set_wrap (layout, PANGO_WRAP_WORD_CHAR);
+
+#if PANGO_VERSION_CHECK (1, 44, 4)
+ {
+ PangoAttrList *attr_list = pango_attr_list_new ();
+
+ pango_attr_list_insert (attr_list, pango_attr_insert_hyphens_new (FALSE));
+ pango_layout_set_attributes (layout, attr_list);
+ pango_attr_list_unref (attr_list);
+ }
+#endif
+
+ /* Create a font description */
+ if (container->details->font)
+ {
+ desc = pango_font_description_from_string (container->details->font);
+ }
+ else
+ {
+ desc = pango_font_description_copy (pango_context_get_font_description (context));
+ }
+ pango_layout_set_font_description (layout, desc);
+ pango_font_description_free (desc);
+ g_free (zeroified_text);
+
+ return layout;
+}
+
+static PangoLayout *
+get_label_layout (PangoLayout **layout_cache,
+ NautilusCanvasItem *item,
+ const char *text)
+{
+ PangoLayout *layout;
+
+ if (*layout_cache != NULL)
+ {
+ return g_object_ref (*layout_cache);
+ }
+
+ layout = create_label_layout (item, text);
+
+ if (item->details->is_visible)
+ {
+ *layout_cache = g_object_ref (layout);
+ }
+
+ return layout;
+}
+
+/* handle events */
+
+static int
+nautilus_canvas_item_event (EelCanvasItem *item,
+ GdkEvent *event)
+{
+ NautilusCanvasItem *canvas_item;
+ GdkCursor *cursor;
+ GdkWindow *cursor_window;
+
+ canvas_item = NAUTILUS_CANVAS_ITEM (item);
+ cursor_window = ((GdkEventAny *) event)->window;
+
+ switch (event->type)
+ {
+ case GDK_ENTER_NOTIFY:
+ {
+ if (!canvas_item->details->is_prelit)
+ {
+ canvas_item->details->is_prelit = TRUE;
+ nautilus_canvas_item_invalidate_label_size (canvas_item);
+ eel_canvas_item_request_update (item);
+ eel_canvas_item_send_behind (item,
+ NAUTILUS_CANVAS_CONTAINER (item->canvas)->details->rubberband_info.selection_rectangle);
+
+ /* show a hand cursor */
+ if (in_single_click_mode ())
+ {
+ cursor = gdk_cursor_new_for_display (gdk_display_get_default (),
+ GDK_HAND2);
+ gdk_window_set_cursor (cursor_window, cursor);
+ g_object_unref (cursor);
+
+ canvas_item->details->cursor_window = g_object_ref (cursor_window);
+ }
+ }
+ return TRUE;
+ }
+
+ case GDK_LEAVE_NOTIFY:
+ {
+ if (canvas_item->details->is_prelit
+ || canvas_item->details->is_highlighted_for_drop)
+ {
+ /* When leaving, turn of the prelight state and the
+ * higlighted for drop. The latter gets turned on
+ * by the drag&drop motion callback.
+ */
+ canvas_item->details->is_prelit = FALSE;
+ canvas_item->details->is_highlighted_for_drop = FALSE;
+ nautilus_canvas_item_invalidate_label_size (canvas_item);
+ eel_canvas_item_request_update (item);
+
+ /* show default cursor */
+ gdk_window_set_cursor (cursor_window, NULL);
+ g_clear_object (&canvas_item->details->cursor_window);
+ }
+ return TRUE;
+ }
+
+ default:
+ /* Don't eat up other events; canvas container might use them. */
+ return FALSE;
+ }
+}
+
+static gboolean
+hit_test (NautilusCanvasItem *canvas_item,
+ EelIRect icon_rect)
+{
+ NautilusCanvasItemDetails *details;
+
+ details = canvas_item->details;
+
+ /* Quick check to see if the rect hits the canvas or text at all. */
+ if (!eel_irect_hits_irect (canvas_item->details->icon_rect, icon_rect)
+ && (!eel_irect_hits_irect (details->text_rect, icon_rect)))
+ {
+ return FALSE;
+ }
+
+ /* Check for hit in the canvas. */
+ if (eel_irect_hits_irect (canvas_item->details->icon_rect, icon_rect))
+ {
+ return TRUE;
+ }
+
+ /* Check for hit in the text. */
+ if (eel_irect_hits_irect (details->text_rect, icon_rect))
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/* Point handler for the canvas canvas item. */
+static double
+nautilus_canvas_item_point (EelCanvasItem *item,
+ double x,
+ double y,
+ int cx,
+ int cy,
+ EelCanvasItem **actual_item)
+{
+ EelIRect icon_rect;
+
+ *actual_item = item;
+ icon_rect.x0 = cx;
+ icon_rect.y0 = cy;
+ icon_rect.x1 = cx + 1;
+ icon_rect.y1 = cy + 1;
+ if (hit_test (NAUTILUS_CANVAS_ITEM (item), icon_rect))
+ {
+ return 0.0;
+ }
+ else
+ {
+ /* This value means not hit.
+ * It's kind of arbitrary. Can we do better?
+ */
+ return item->canvas->pixels_per_unit * 2 + 10;
+ }
+}
+
+static void
+nautilus_canvas_item_translate (EelCanvasItem *item,
+ double dx,
+ double dy)
+{
+ NautilusCanvasItem *canvas_item;
+ NautilusCanvasItemDetails *details;
+
+ canvas_item = NAUTILUS_CANVAS_ITEM (item);
+ details = canvas_item->details;
+
+ details->x += dx;
+ details->y += dy;
+}
+
+void
+nautilus_canvas_item_get_bounds_for_layout (NautilusCanvasItem *canvas_item,
+ double *x1,
+ double *y1,
+ double *x2,
+ double *y2)
+{
+ NautilusCanvasItemDetails *details;
+ EelIRect *total_rect;
+
+ details = canvas_item->details;
+
+ nautilus_canvas_item_ensure_bounds_up_to_date (canvas_item);
+ g_assert (details->bounds_cached);
+
+ total_rect = &details->bounds_cache_for_layout;
+
+ /* Return the result. */
+ if (x1 != NULL)
+ {
+ *x1 = (int) details->x + total_rect->x0;
+ }
+ if (y1 != NULL)
+ {
+ *y1 = (int) details->y + total_rect->y0;
+ }
+ if (x2 != NULL)
+ {
+ *x2 = (int) details->x + total_rect->x1 + 1;
+ }
+ if (y2 != NULL)
+ {
+ *y2 = (int) details->y + total_rect->y1 + 1;
+ }
+}
+
+void
+nautilus_canvas_item_get_bounds_for_entire_item (NautilusCanvasItem *canvas_item,
+ double *x1,
+ double *y1,
+ double *x2,
+ double *y2)
+{
+ NautilusCanvasItemDetails *details;
+ EelIRect *total_rect;
+
+ details = canvas_item->details;
+
+ nautilus_canvas_item_ensure_bounds_up_to_date (canvas_item);
+ g_assert (details->bounds_cached);
+
+ total_rect = &details->bounds_cache_for_entire_item;
+
+ /* Return the result. */
+ if (x1 != NULL)
+ {
+ *x1 = (int) details->x + total_rect->x0;
+ }
+ if (y1 != NULL)
+ {
+ *y1 = (int) details->y + total_rect->y0;
+ }
+ if (x2 != NULL)
+ {
+ *x2 = (int) details->x + total_rect->x1 + 1;
+ }
+ if (y2 != NULL)
+ {
+ *y2 = (int) details->y + total_rect->y1 + 1;
+ }
+}
+
+/* Bounds handler for the canvas canvas item. */
+static void
+nautilus_canvas_item_bounds (EelCanvasItem *item,
+ double *x1,
+ double *y1,
+ double *x2,
+ double *y2)
+{
+ NautilusCanvasItem *canvas_item;
+ NautilusCanvasItemDetails *details;
+ EelIRect *total_rect;
+
+ canvas_item = NAUTILUS_CANVAS_ITEM (item);
+ details = canvas_item->details;
+
+ g_assert (x1 != NULL);
+ g_assert (y1 != NULL);
+ g_assert (x2 != NULL);
+ g_assert (y2 != NULL);
+
+ nautilus_canvas_item_ensure_bounds_up_to_date (canvas_item);
+ g_assert (details->bounds_cached);
+
+ total_rect = &details->bounds_cache;
+
+ /* Return the result. */
+ *x1 = (int) details->x + total_rect->x0;
+ *y1 = (int) details->y + total_rect->y0;
+ *x2 = (int) details->x + total_rect->x1 + 1;
+ *y2 = (int) details->y + total_rect->y1 + 1;
+}
+
+static void
+nautilus_canvas_item_ensure_bounds_up_to_date (NautilusCanvasItem *canvas_item)
+{
+ NautilusCanvasItemDetails *details;
+ EelIRect icon_rect;
+ EelIRect text_rect, text_rect_for_layout, text_rect_for_entire_text;
+ EelIRect total_rect, total_rect_for_layout, total_rect_for_entire_text;
+ EelCanvasItem *item;
+ double pixels_per_unit;
+ gint width, height;
+
+ details = canvas_item->details;
+ item = EEL_CANVAS_ITEM (canvas_item);
+
+ if (!details->bounds_cached)
+ {
+ measure_label_text (canvas_item);
+
+ pixels_per_unit = EEL_CANVAS_ITEM (item)->canvas->pixels_per_unit;
+
+ /* Compute scaled canvas rectangle. */
+ icon_rect.x0 = 0;
+ icon_rect.y0 = 0;
+
+ get_scaled_icon_size (canvas_item, &width, &height);
+
+ icon_rect.x1 = width / pixels_per_unit;
+ icon_rect.y1 = height / pixels_per_unit;
+
+ /* Compute text rectangle. */
+ text_rect = compute_text_rectangle (canvas_item, icon_rect, FALSE, BOUNDS_USAGE_FOR_DISPLAY);
+ text_rect_for_layout = compute_text_rectangle (canvas_item, icon_rect, FALSE, BOUNDS_USAGE_FOR_LAYOUT);
+ text_rect_for_entire_text = compute_text_rectangle (canvas_item, icon_rect, FALSE, BOUNDS_USAGE_FOR_ENTIRE_ITEM);
+
+ /* Compute total rectangle */
+ eel_irect_union (&total_rect, &icon_rect, &text_rect);
+ eel_irect_union (&total_rect_for_layout, &icon_rect, &text_rect_for_layout);
+ eel_irect_union (&total_rect_for_entire_text, &icon_rect, &text_rect_for_entire_text);
+
+ details->bounds_cache = total_rect;
+ details->bounds_cache_for_layout = total_rect_for_layout;
+ details->bounds_cache_for_entire_item = total_rect_for_entire_text;
+ details->bounds_cached = TRUE;
+ }
+}
+
+/* Get the rectangle of the canvas only, in world coordinates. */
+EelDRect
+nautilus_canvas_item_get_icon_rectangle (const NautilusCanvasItem *item)
+{
+ EelDRect rectangle;
+ double pixels_per_unit;
+ gint width, height;
+
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_ITEM (item), eel_drect_empty);
+
+ rectangle.x0 = item->details->x;
+ rectangle.y0 = item->details->y;
+
+ pixels_per_unit = EEL_CANVAS_ITEM (item)->canvas->pixels_per_unit;
+ get_scaled_icon_size (NAUTILUS_CANVAS_ITEM (item), &width, &height);
+ rectangle.x1 = rectangle.x0 + width / pixels_per_unit;
+ rectangle.y1 = rectangle.y0 + height / pixels_per_unit;
+
+ eel_canvas_item_i2w (EEL_CANVAS_ITEM (item),
+ &rectangle.x0,
+ &rectangle.y0);
+ eel_canvas_item_i2w (EEL_CANVAS_ITEM (item),
+ &rectangle.x1,
+ &rectangle.y1);
+
+ return rectangle;
+}
+
+/* Get the rectangle of the icon only, in canvas coordinates. */
+static void
+get_icon_rectangle (NautilusCanvasItem *item,
+ EelIRect *rect)
+{
+ gint width, height;
+
+ g_assert (NAUTILUS_IS_CANVAS_ITEM (item));
+ g_assert (rect != NULL);
+
+
+ eel_canvas_w2c (EEL_CANVAS_ITEM (item)->canvas,
+ item->details->x,
+ item->details->y,
+ &rect->x0,
+ &rect->y0);
+
+ get_scaled_icon_size (item, &width, &height);
+
+ rect->x1 = rect->x0 + width;
+ rect->y1 = rect->y0 + height;
+}
+
+/* nautilus_canvas_item_hit_test_rectangle
+ *
+ * Check and see if there is an intersection between the item and the
+ * canvas rect.
+ */
+gboolean
+nautilus_canvas_item_hit_test_rectangle (NautilusCanvasItem *item,
+ EelIRect icon_rect)
+{
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_ITEM (item), FALSE);
+
+ return hit_test (item, icon_rect);
+}
+
+void
+nautilus_canvas_item_set_entire_text (NautilusCanvasItem *item,
+ gboolean entire_text)
+{
+ if (item->details->entire_text != entire_text)
+ {
+ item->details->entire_text = entire_text;
+
+ nautilus_canvas_item_invalidate_label_size (item);
+ eel_canvas_item_request_update (EEL_CANVAS_ITEM (item));
+ }
+}
+
+/* Class initialization function for the canvas canvas item. */
+static void
+nautilus_canvas_item_class_init (NautilusCanvasItemClass *class)
+{
+ GObjectClass *object_class;
+ EelCanvasItemClass *item_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ item_class = EEL_CANVAS_ITEM_CLASS (class);
+
+ object_class->finalize = nautilus_canvas_item_finalize;
+ object_class->set_property = nautilus_canvas_item_set_property;
+ object_class->get_property = nautilus_canvas_item_get_property;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_EDITABLE_TEXT,
+ g_param_spec_string ("editable_text",
+ "editable text",
+ "the editable label",
+ "", G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_ADDITIONAL_TEXT,
+ g_param_spec_string ("additional_text",
+ "additional text",
+ "some more text",
+ "", G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_HIGHLIGHTED_FOR_SELECTION,
+ g_param_spec_boolean ("highlighted_for_selection",
+ "highlighted for selection",
+ "whether we are highlighted for a selection",
+ FALSE, G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_HIGHLIGHTED_AS_KEYBOARD_FOCUS,
+ g_param_spec_boolean ("highlighted_as_keyboard_focus",
+ "highlighted as keyboard focus",
+ "whether we are highlighted to render keyboard focus",
+ FALSE, G_PARAM_READWRITE));
+
+
+ g_object_class_install_property (
+ object_class,
+ PROP_HIGHLIGHTED_FOR_DROP,
+ g_param_spec_boolean ("highlighted_for_drop",
+ "highlighted for drop",
+ "whether we are highlighted for a D&D drop",
+ FALSE, G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_HIGHLIGHTED_FOR_CLIPBOARD,
+ g_param_spec_boolean ("highlighted_for_clipboard",
+ "highlighted for clipboard",
+ "whether we are highlighted for a clipboard paste (after we have been cut)",
+ FALSE, G_PARAM_READWRITE));
+
+ item_class->update = nautilus_canvas_item_update;
+ item_class->draw = nautilus_canvas_item_draw;
+ item_class->point = nautilus_canvas_item_point;
+ item_class->translate = nautilus_canvas_item_translate;
+ item_class->bounds = nautilus_canvas_item_bounds;
+ item_class->event = nautilus_canvas_item_event;
+
+ atk_registry_set_factory_type (atk_get_default_registry (),
+ NAUTILUS_TYPE_CANVAS_ITEM,
+ nautilus_canvas_item_accessible_factory_get_type ());
+
+ g_type_class_add_private (class, sizeof (NautilusCanvasItemDetails));
+}
+
+/* ============================= a11y interfaces =========================== */
+
+static const char *nautilus_canvas_item_accessible_action_names[] =
+{
+ "open",
+ "menu",
+ NULL
+};
+
+static const char *nautilus_canvas_item_accessible_action_descriptions[] =
+{
+ "Open item",
+ "Popup context menu",
+ NULL
+};
+
+enum
+{
+ ACTION_OPEN,
+ ACTION_MENU,
+ LAST_ACTION
+};
+
+typedef struct
+{
+ char *action_descriptions[LAST_ACTION];
+ char *image_description;
+ char *description;
+} NautilusCanvasItemAccessiblePrivate;
+
+typedef struct
+{
+ NautilusCanvasItem *item;
+ gint action_number;
+} NautilusCanvasItemAccessibleActionContext;
+
+typedef struct
+{
+ EelCanvasItemAccessible parent;
+ NautilusCanvasItemAccessiblePrivate *priv;
+} NautilusCanvasItemAccessible;
+
+typedef struct
+{
+ EelCanvasItemAccessibleClass parent_class;
+} NautilusCanvasItemAccessibleClass;
+
+#define GET_ACCESSIBLE_PRIV(o) ((NautilusCanvasItemAccessible *) o)->priv;
+
+/* accessible AtkAction interface */
+static gboolean
+nautilus_canvas_item_accessible_idle_do_action (gpointer data)
+{
+ NautilusCanvasItem *item;
+ NautilusCanvasItemAccessibleActionContext *ctx;
+ NautilusCanvasIcon *icon;
+ NautilusCanvasContainer *container;
+ GList *selection;
+ GList file_list;
+ GdkEventButton button_event = { 0 };
+ gint action_number;
+
+ container = NAUTILUS_CANVAS_CONTAINER (data);
+ container->details->a11y_item_action_idle_handler = 0;
+ while (!g_queue_is_empty (container->details->a11y_item_action_queue))
+ {
+ ctx = g_queue_pop_head (container->details->a11y_item_action_queue);
+ action_number = ctx->action_number;
+ item = ctx->item;
+ g_free (ctx);
+ icon = item->user_data;
+
+ switch (action_number)
+ {
+ case ACTION_OPEN:
+ {
+ file_list.data = icon->data;
+ file_list.next = NULL;
+ file_list.prev = NULL;
+ g_signal_emit_by_name (container, "activate", &file_list);
+ }
+ break;
+
+ case ACTION_MENU:
+ {
+ selection = nautilus_canvas_container_get_selection (container);
+ if (selection == NULL ||
+ g_list_length (selection) != 1 ||
+ selection->data != icon->data)
+ {
+ g_list_free (selection);
+ return FALSE;
+ }
+ g_list_free (selection);
+ g_signal_emit_by_name (container, "context-click-selection", &button_event);
+ }
+ break;
+
+ default:
+ {
+ g_assert_not_reached ();
+ }
+ break;
+ }
+ }
+ return FALSE;
+}
+
+static gboolean
+nautilus_canvas_item_accessible_do_action (AtkAction *accessible,
+ int i)
+{
+ NautilusCanvasItem *item;
+ NautilusCanvasItemAccessibleActionContext *ctx;
+ NautilusCanvasContainer *container;
+
+ g_assert (i < LAST_ACTION);
+
+ item = NAUTILUS_CANVAS_ITEM (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (accessible)));
+ if (!item)
+ {
+ return FALSE;
+ }
+
+ container = NAUTILUS_CANVAS_CONTAINER (EEL_CANVAS_ITEM (item)->canvas);
+ switch (i)
+ {
+ case ACTION_OPEN:
+ case ACTION_MENU:
+ {
+ if (container->details->a11y_item_action_queue == NULL)
+ {
+ container->details->a11y_item_action_queue = g_queue_new ();
+ }
+ ctx = g_new (NautilusCanvasItemAccessibleActionContext, 1);
+ ctx->action_number = i;
+ ctx->item = item;
+ g_queue_push_head (container->details->a11y_item_action_queue, ctx);
+ if (container->details->a11y_item_action_idle_handler == 0)
+ {
+ container->details->a11y_item_action_idle_handler = g_idle_add (nautilus_canvas_item_accessible_idle_do_action, container);
+ }
+ }
+ break;
+
+ default:
+ g_warning ("Invalid action passed to NautilusCanvasItemAccessible::do_action");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static int
+nautilus_canvas_item_accessible_get_n_actions (AtkAction *accessible)
+{
+ return LAST_ACTION;
+}
+
+static const char *
+nautilus_canvas_item_accessible_action_get_description (AtkAction *accessible,
+ int i)
+{
+ NautilusCanvasItemAccessiblePrivate *priv;
+
+ g_assert (i < LAST_ACTION);
+
+ priv = GET_ACCESSIBLE_PRIV (accessible);
+
+ if (priv->action_descriptions[i])
+ {
+ return priv->action_descriptions[i];
+ }
+ else
+ {
+ return nautilus_canvas_item_accessible_action_descriptions[i];
+ }
+}
+
+static const char *
+nautilus_canvas_item_accessible_action_get_name (AtkAction *accessible,
+ int i)
+{
+ g_assert (i < LAST_ACTION);
+
+ return nautilus_canvas_item_accessible_action_names[i];
+}
+
+static const char *
+nautilus_canvas_item_accessible_action_get_keybinding (AtkAction *accessible,
+ int i)
+{
+ g_assert (i < LAST_ACTION);
+
+ return NULL;
+}
+
+static gboolean
+nautilus_canvas_item_accessible_action_set_description (AtkAction *accessible,
+ int i,
+ const char *description)
+{
+ NautilusCanvasItemAccessiblePrivate *priv;
+
+ g_assert (i < LAST_ACTION);
+
+ priv = GET_ACCESSIBLE_PRIV (accessible);
+
+ if (priv->action_descriptions[i])
+ {
+ g_free (priv->action_descriptions[i]);
+ }
+ priv->action_descriptions[i] = g_strdup (description);
+
+ return TRUE;
+}
+
+static void
+nautilus_canvas_item_accessible_action_interface_init (AtkActionIface *iface)
+{
+ iface->do_action = nautilus_canvas_item_accessible_do_action;
+ iface->get_n_actions = nautilus_canvas_item_accessible_get_n_actions;
+ iface->get_description = nautilus_canvas_item_accessible_action_get_description;
+ iface->get_keybinding = nautilus_canvas_item_accessible_action_get_keybinding;
+ iface->get_name = nautilus_canvas_item_accessible_action_get_name;
+ iface->set_description = nautilus_canvas_item_accessible_action_set_description;
+}
+
+static const gchar *
+nautilus_canvas_item_accessible_get_name (AtkObject *accessible)
+{
+ NautilusCanvasItem *item;
+
+ if (accessible->name)
+ {
+ return accessible->name;
+ }
+
+ item = NAUTILUS_CANVAS_ITEM (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (accessible)));
+ if (!item)
+ {
+ return NULL;
+ }
+ return item->details->editable_text;
+}
+
+static const gchar *
+nautilus_canvas_item_accessible_get_description (AtkObject *accessible)
+{
+ NautilusCanvasItem *item;
+
+ item = NAUTILUS_CANVAS_ITEM (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (accessible)));
+ if (!item)
+ {
+ return NULL;
+ }
+
+ return item->details->additional_text;
+}
+
+static AtkObject *
+nautilus_canvas_item_accessible_get_parent (AtkObject *accessible)
+{
+ NautilusCanvasItem *item;
+
+ item = NAUTILUS_CANVAS_ITEM (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (accessible)));
+ if (!item)
+ {
+ return NULL;
+ }
+
+ return gtk_widget_get_accessible (GTK_WIDGET (EEL_CANVAS_ITEM (item)->canvas));
+}
+
+static int
+nautilus_canvas_item_accessible_get_index_in_parent (AtkObject *accessible)
+{
+ NautilusCanvasItem *item;
+ NautilusCanvasContainer *container;
+ GList *l;
+ NautilusCanvasIcon *icon;
+ int i;
+
+ item = NAUTILUS_CANVAS_ITEM (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (accessible)));
+ if (!item)
+ {
+ return -1;
+ }
+
+ container = NAUTILUS_CANVAS_CONTAINER (EEL_CANVAS_ITEM (item)->canvas);
+
+ l = container->details->icons;
+ i = 0;
+ while (l)
+ {
+ icon = l->data;
+
+ if (icon->item == item)
+ {
+ return i;
+ }
+
+ i++;
+ l = l->next;
+ }
+
+ return -1;
+}
+
+static const gchar *
+nautilus_canvas_item_accessible_get_image_description (AtkImage *image)
+{
+ NautilusCanvasItemAccessiblePrivate *priv;
+ NautilusCanvasItem *item;
+ NautilusCanvasIcon *icon;
+ NautilusCanvasContainer *container;
+ char *description;
+
+ priv = GET_ACCESSIBLE_PRIV (image);
+
+ if (priv->image_description)
+ {
+ return priv->image_description;
+ }
+ else
+ {
+ item = NAUTILUS_CANVAS_ITEM (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (image)));
+ if (item == NULL)
+ {
+ return NULL;
+ }
+ icon = item->user_data;
+ container = NAUTILUS_CANVAS_CONTAINER (EEL_CANVAS_ITEM (item)->canvas);
+ description = nautilus_canvas_container_get_icon_description (container, icon->data);
+ g_free (priv->description);
+ priv->description = description;
+ return priv->description;
+ }
+}
+
+static void
+nautilus_canvas_item_accessible_get_image_size (AtkImage *image,
+ gint *width,
+ gint *height)
+{
+ NautilusCanvasItem *item;
+
+ item = NAUTILUS_CANVAS_ITEM (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (image)));
+ get_scaled_icon_size (item, width, height);
+}
+
+static void
+nautilus_canvas_item_accessible_get_image_position (AtkImage *image,
+ gint *x,
+ gint *y,
+ AtkCoordType coord_type)
+{
+ NautilusCanvasItem *item;
+ gint x_offset, y_offset, itmp;
+
+ item = NAUTILUS_CANVAS_ITEM (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (image)));
+ if (!item)
+ {
+ return;
+ }
+ if (!item->details->icon_rect.x0 && !item->details->icon_rect.x1)
+ {
+ return;
+ }
+ else
+ {
+ x_offset = 0;
+ y_offset = 0;
+ if (item->details->text_width)
+ {
+ itmp = item->details->icon_rect.x0 -
+ item->details->text_rect.x0;
+ if (itmp > x_offset)
+ {
+ x_offset = itmp;
+ }
+ itmp = item->details->icon_rect.y0 -
+ item->details->text_rect.y0;
+ if (itmp > y_offset)
+ {
+ y_offset = itmp;
+ }
+ }
+ }
+ atk_component_get_extents (ATK_COMPONENT (image), x, y, NULL, NULL, coord_type);
+ *x += x_offset;
+ *y += y_offset;
+}
+
+static gboolean
+nautilus_canvas_item_accessible_set_image_description (AtkImage *image,
+ const gchar *description)
+{
+ NautilusCanvasItemAccessiblePrivate *priv;
+
+ priv = GET_ACCESSIBLE_PRIV (image);
+
+ g_free (priv->image_description);
+ priv->image_description = g_strdup (description);
+
+ return TRUE;
+}
+
+static void
+nautilus_canvas_item_accessible_image_interface_init (AtkImageIface *iface)
+{
+ iface->get_image_description = nautilus_canvas_item_accessible_get_image_description;
+ iface->set_image_description = nautilus_canvas_item_accessible_set_image_description;
+ iface->get_image_size = nautilus_canvas_item_accessible_get_image_size;
+ iface->get_image_position = nautilus_canvas_item_accessible_get_image_position;
+}
+
+/* accessible text interface */
+static gint
+nautilus_canvas_item_accessible_get_offset_at_point (AtkText *text,
+ gint x,
+ gint y,
+ AtkCoordType coords)
+{
+ gint real_x, real_y, real_width, real_height;
+ NautilusCanvasItem *item;
+ gint editable_height;
+ gint offset = 0;
+ gint index;
+ PangoLayout *layout, *editable_layout, *additional_layout;
+ PangoRectangle rect0;
+ char *canvas_text;
+ gboolean have_editable;
+ gboolean have_additional;
+ gint text_offset, height;
+
+ atk_component_get_extents (ATK_COMPONENT (text), &real_x, &real_y,
+ &real_width, &real_height, coords);
+
+ x -= real_x;
+ y -= real_y;
+
+ item = NAUTILUS_CANVAS_ITEM (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text)));
+
+ if (item->details->pixbuf)
+ {
+ get_scaled_icon_size (item, NULL, &height);
+ y -= height;
+ }
+ have_editable = item->details->editable_text != NULL &&
+ item->details->editable_text[0] != '\0';
+ have_additional = item->details->additional_text != NULL && item->details->additional_text[0] != '\0';
+
+ editable_layout = NULL;
+ additional_layout = NULL;
+ if (have_editable)
+ {
+ editable_layout = get_label_layout (&item->details->editable_text_layout, item, item->details->editable_text);
+ prepare_pango_layout_for_draw (item, editable_layout);
+ pango_layout_get_pixel_size (editable_layout, NULL, &editable_height);
+ if (y >= editable_height &&
+ have_additional)
+ {
+ prepare_pango_layout_for_draw (item, editable_layout);
+ additional_layout = get_label_layout (&item->details->additional_text_layout, item, item->details->additional_text);
+ layout = additional_layout;
+ canvas_text = item->details->additional_text;
+ y -= editable_height + LABEL_LINE_SPACING;
+ }
+ else
+ {
+ layout = editable_layout;
+ canvas_text = item->details->editable_text;
+ }
+ }
+ else if (have_additional)
+ {
+ additional_layout = get_label_layout (&item->details->additional_text_layout, item, item->details->additional_text);
+ prepare_pango_layout_for_draw (item, additional_layout);
+ layout = additional_layout;
+ canvas_text = item->details->additional_text;
+ }
+ else
+ {
+ return 0;
+ }
+
+ text_offset = 0;
+ if (have_editable)
+ {
+ pango_layout_index_to_pos (editable_layout, 0, &rect0);
+ text_offset = PANGO_PIXELS (rect0.x);
+ }
+ if (have_additional)
+ {
+ gint itmp;
+
+ pango_layout_index_to_pos (additional_layout, 0, &rect0);
+ itmp = PANGO_PIXELS (rect0.x);
+ if (itmp < text_offset)
+ {
+ text_offset = itmp;
+ }
+ }
+ pango_layout_index_to_pos (layout, 0, &rect0);
+ x += text_offset;
+ if (!pango_layout_xy_to_index (layout,
+ x * PANGO_SCALE,
+ y * PANGO_SCALE,
+ &index, NULL))
+ {
+ if (x < 0 || y < 0)
+ {
+ index = 0;
+ }
+ else
+ {
+ index = -1;
+ }
+ }
+ if (index == -1)
+ {
+ offset = g_utf8_strlen (canvas_text, -1);
+ }
+ else
+ {
+ offset = g_utf8_pointer_to_offset (canvas_text, canvas_text + index);
+ }
+ if (layout == additional_layout)
+ {
+ offset += g_utf8_strlen (item->details->editable_text, -1);
+ }
+
+ if (editable_layout != NULL)
+ {
+ g_object_unref (editable_layout);
+ }
+
+ if (additional_layout != NULL)
+ {
+ g_object_unref (additional_layout);
+ }
+
+ return offset;
+}
+
+static void
+nautilus_canvas_item_accessible_get_character_extents (AtkText *text,
+ gint offset,
+ gint *x,
+ gint *y,
+ gint *width,
+ gint *height,
+ AtkCoordType coords)
+{
+ gint pos_x, pos_y;
+ gint len, byte_offset;
+ gint editable_height;
+ gchar *canvas_text;
+ NautilusCanvasItem *item;
+ PangoLayout *layout, *editable_layout, *additional_layout;
+ PangoRectangle rect;
+ PangoRectangle rect0;
+ gboolean have_editable;
+ gint text_offset, pix_height;
+
+ atk_component_get_extents (ATK_COMPONENT (text), &pos_x, &pos_y, NULL, NULL, coords);
+ item = NAUTILUS_CANVAS_ITEM (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text)));
+
+ if (item->details->pixbuf)
+ {
+ get_scaled_icon_size (item, NULL, &pix_height);
+ pos_y += pix_height;
+ }
+
+ have_editable = item->details->editable_text != NULL &&
+ item->details->editable_text[0] != '\0';
+ if (have_editable)
+ {
+ len = g_utf8_strlen (item->details->editable_text, -1);
+ }
+ else
+ {
+ len = 0;
+ }
+
+ editable_layout = get_label_layout (&item->details->editable_text_layout, item, item->details->editable_text);
+ additional_layout = get_label_layout (&item->details->additional_text_layout, item, item->details->additional_text);
+
+ if (offset < len)
+ {
+ canvas_text = item->details->editable_text;
+ layout = editable_layout;
+ }
+ else
+ {
+ offset -= len;
+ canvas_text = item->details->additional_text;
+ layout = additional_layout;
+ pos_y += LABEL_LINE_SPACING;
+ if (have_editable)
+ {
+ pango_layout_get_pixel_size (editable_layout, NULL, &editable_height);
+ pos_y += editable_height;
+ }
+ }
+ byte_offset = g_utf8_offset_to_pointer (canvas_text, offset) - canvas_text;
+ pango_layout_index_to_pos (layout, byte_offset, &rect);
+ text_offset = 0;
+ if (have_editable)
+ {
+ pango_layout_index_to_pos (editable_layout, 0, &rect0);
+ text_offset = PANGO_PIXELS (rect0.x);
+ }
+ if (item->details->additional_text != NULL &&
+ item->details->additional_text[0] != '\0')
+ {
+ gint itmp;
+
+ pango_layout_index_to_pos (additional_layout, 0, &rect0);
+ itmp = PANGO_PIXELS (rect0.x);
+ if (itmp < text_offset)
+ {
+ text_offset = itmp;
+ }
+ }
+
+ g_object_unref (editable_layout);
+ g_object_unref (additional_layout);
+
+ *x = pos_x + PANGO_PIXELS (rect.x) - text_offset;
+ *y = pos_y + PANGO_PIXELS (rect.y);
+ *width = PANGO_PIXELS (rect.width);
+ *height = PANGO_PIXELS (rect.height);
+}
+
+static char *
+nautilus_canvas_item_accessible_text_get_text (AtkText *text,
+ gint start_pos,
+ gint end_pos)
+{
+ GObject *object;
+ NautilusCanvasItem *item;
+
+ object = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text));
+ item = NAUTILUS_CANVAS_ITEM (object);
+
+ return g_utf8_substring (item->details->text->str, start_pos, end_pos);
+}
+
+static gunichar
+nautilus_canvas_item_accessible_text_get_character_at_offset (AtkText *text,
+ gint offset)
+{
+ GObject *object;
+ NautilusCanvasItem *item;
+ gchar *pointer;
+
+ object = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text));
+ item = NAUTILUS_CANVAS_ITEM (object);
+ pointer = g_utf8_offset_to_pointer (item->details->text->str, offset);
+
+ return g_utf8_get_char (pointer);
+}
+
+static gint
+nautilus_canvas_item_accessible_text_get_character_count (AtkText *text)
+{
+ GObject *object;
+ NautilusCanvasItem *item;
+
+ object = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text));
+ item = NAUTILUS_CANVAS_ITEM (object);
+
+ return g_utf8_strlen (item->details->text->str, -1);
+}
+
+static void
+nautilus_canvas_item_accessible_text_interface_init (AtkTextIface *iface)
+{
+ iface->get_text = nautilus_canvas_item_accessible_text_get_text;
+ iface->get_character_at_offset = nautilus_canvas_item_accessible_text_get_character_at_offset;
+ iface->get_character_count = nautilus_canvas_item_accessible_text_get_character_count;
+ iface->get_character_extents = nautilus_canvas_item_accessible_get_character_extents;
+ iface->get_offset_at_point = nautilus_canvas_item_accessible_get_offset_at_point;
+}
+
+static GType nautilus_canvas_item_accessible_get_type (void);
+
+G_DEFINE_TYPE_WITH_CODE (NautilusCanvasItemAccessible,
+ nautilus_canvas_item_accessible,
+ eel_canvas_item_accessible_get_type (),
+ G_IMPLEMENT_INTERFACE (ATK_TYPE_IMAGE,
+ nautilus_canvas_item_accessible_image_interface_init)
+ G_IMPLEMENT_INTERFACE (ATK_TYPE_TEXT,
+ nautilus_canvas_item_accessible_text_interface_init)
+ G_IMPLEMENT_INTERFACE (ATK_TYPE_ACTION,
+ nautilus_canvas_item_accessible_action_interface_init));
+
+static AtkStateSet *
+nautilus_canvas_item_accessible_ref_state_set (AtkObject *accessible)
+{
+ AtkStateSet *state_set;
+ NautilusCanvasItem *item;
+ NautilusCanvasContainer *container;
+ GList *selection;
+ gboolean one_item_selected;
+
+ state_set = ATK_OBJECT_CLASS (nautilus_canvas_item_accessible_parent_class)->ref_state_set (accessible);
+
+ item = NAUTILUS_CANVAS_ITEM (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (accessible)));
+ if (!item)
+ {
+ atk_state_set_add_state (state_set, ATK_STATE_DEFUNCT);
+ return state_set;
+ }
+ container = NAUTILUS_CANVAS_CONTAINER (EEL_CANVAS_ITEM (item)->canvas);
+ if (item->details->is_highlighted_as_keyboard_focus)
+ {
+ atk_state_set_add_state (state_set, ATK_STATE_FOCUSED);
+ }
+ else if (!container->details->keyboard_focus)
+ {
+ selection = nautilus_canvas_container_get_selection (container);
+ one_item_selected = (g_list_length (selection) == 1) &&
+ item->details->is_highlighted_for_selection;
+
+ if (one_item_selected)
+ {
+ atk_state_set_add_state (state_set, ATK_STATE_FOCUSED);
+ }
+
+ g_list_free (selection);
+ }
+
+ return state_set;
+}
+
+static void
+nautilus_canvas_item_accessible_finalize (GObject *object)
+{
+ NautilusCanvasItemAccessiblePrivate *priv;
+ int i;
+
+ priv = GET_ACCESSIBLE_PRIV (object);
+
+ for (i = 0; i < LAST_ACTION; i++)
+ {
+ g_free (priv->action_descriptions[i]);
+ }
+ g_free (priv->image_description);
+ g_free (priv->description);
+
+ G_OBJECT_CLASS (nautilus_canvas_item_accessible_parent_class)->finalize (object);
+}
+
+static void
+nautilus_canvas_item_accessible_initialize (AtkObject *accessible,
+ gpointer widget)
+{
+ ATK_OBJECT_CLASS (nautilus_canvas_item_accessible_parent_class)->initialize (accessible, widget);
+
+ atk_object_set_role (accessible, ATK_ROLE_CANVAS);
+}
+
+static void
+nautilus_canvas_item_accessible_class_init (NautilusCanvasItemAccessibleClass *klass)
+{
+ AtkObjectClass *aclass = ATK_OBJECT_CLASS (klass);
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+
+ oclass->finalize = nautilus_canvas_item_accessible_finalize;
+
+ aclass->initialize = nautilus_canvas_item_accessible_initialize;
+
+ aclass->get_name = nautilus_canvas_item_accessible_get_name;
+ aclass->get_description = nautilus_canvas_item_accessible_get_description;
+ aclass->get_parent = nautilus_canvas_item_accessible_get_parent;
+ aclass->get_index_in_parent = nautilus_canvas_item_accessible_get_index_in_parent;
+ aclass->ref_state_set = nautilus_canvas_item_accessible_ref_state_set;
+
+ g_type_class_add_private (klass, sizeof (NautilusCanvasItemAccessiblePrivate));
+}
+
+static void
+nautilus_canvas_item_accessible_init (NautilusCanvasItemAccessible *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, nautilus_canvas_item_accessible_get_type (),
+ NautilusCanvasItemAccessiblePrivate);
+}
+
+/* dummy typedef */
+typedef AtkObjectFactory NautilusCanvasItemAccessibleFactory;
+typedef AtkObjectFactoryClass NautilusCanvasItemAccessibleFactoryClass;
+
+G_DEFINE_TYPE (NautilusCanvasItemAccessibleFactory, nautilus_canvas_item_accessible_factory,
+ ATK_TYPE_OBJECT_FACTORY);
+
+static AtkObject *
+nautilus_canvas_item_accessible_factory_create_accessible (GObject *for_object)
+{
+ AtkObject *accessible;
+ NautilusCanvasItem *item;
+
+ item = NAUTILUS_CANVAS_ITEM (for_object);
+ g_assert (item != NULL);
+
+ item->details->text = g_string_new (NULL);
+ if (item->details->editable_text)
+ {
+ g_string_append (item->details->text, item->details->editable_text);
+ }
+ if (item->details->additional_text)
+ {
+ g_string_append (item->details->text, item->details->additional_text);
+ }
+
+ accessible = g_object_new (nautilus_canvas_item_accessible_get_type (), NULL);
+ atk_object_initialize (accessible, for_object);
+
+ return accessible;
+}
+
+static GType
+nautilus_canvas_item_accessible_factory_get_accessible_type (void)
+{
+ return nautilus_canvas_item_accessible_get_type ();
+}
+
+static void
+nautilus_canvas_item_accessible_factory_init (NautilusCanvasItemAccessibleFactory *self)
+{
+}
+
+static void
+nautilus_canvas_item_accessible_factory_class_init (NautilusCanvasItemAccessibleFactoryClass *klass)
+{
+ klass->create_accessible = nautilus_canvas_item_accessible_factory_create_accessible;
+ klass->get_accessible_type = nautilus_canvas_item_accessible_factory_get_accessible_type;
+}
diff --git a/src/nautilus-canvas-item.h b/src/nautilus-canvas-item.h
new file mode 100644
index 0000000..436fb6b
--- /dev/null
+++ b/src/nautilus-canvas-item.h
@@ -0,0 +1,90 @@
+
+/* Nautilus - Canvas item class for canvas container.
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ *
+ * Author: Andy Hertzfeld <andy@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 <eel/eel-canvas.h>
+#include <eel/eel-art-extensions.h>
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_CANVAS_ITEM nautilus_canvas_item_get_type()
+#define NAUTILUS_CANVAS_ITEM(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_CANVAS_ITEM, NautilusCanvasItem))
+#define NAUTILUS_CANVAS_ITEM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_CANVAS_ITEM, NautilusCanvasItemClass))
+#define NAUTILUS_IS_CANVAS_ITEM(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_CANVAS_ITEM))
+#define NAUTILUS_IS_CANVAS_ITEM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_CANVAS_ITEM))
+#define NAUTILUS_CANVAS_ITEM_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_CANVAS_ITEM, NautilusCanvasItemClass))
+
+typedef struct NautilusCanvasItem NautilusCanvasItem;
+typedef struct NautilusCanvasItemClass NautilusCanvasItemClass;
+typedef struct NautilusCanvasItemDetails NautilusCanvasItemDetails;
+
+struct NautilusCanvasItem {
+ EelCanvasItem item;
+ NautilusCanvasItemDetails *details;
+ gpointer user_data;
+};
+
+struct NautilusCanvasItemClass {
+ EelCanvasItemClass parent_class;
+};
+
+/* not namespaced due to their length */
+typedef enum {
+ BOUNDS_USAGE_FOR_LAYOUT,
+ BOUNDS_USAGE_FOR_ENTIRE_ITEM,
+ BOUNDS_USAGE_FOR_DISPLAY
+} NautilusCanvasItemBoundsUsage;
+
+/* GObject */
+GType nautilus_canvas_item_get_type (void);
+
+/* attributes */
+void nautilus_canvas_item_set_image (NautilusCanvasItem *item,
+ GdkPixbuf *image);
+cairo_surface_t* nautilus_canvas_item_get_drag_surface (NautilusCanvasItem *item);
+void nautilus_canvas_item_set_emblems (NautilusCanvasItem *item,
+ GList *emblem_pixbufs);
+
+/* geometry and hit testing */
+gboolean nautilus_canvas_item_hit_test_rectangle (NautilusCanvasItem *item,
+ EelIRect canvas_rect);
+void nautilus_canvas_item_invalidate_label (NautilusCanvasItem *item);
+void nautilus_canvas_item_invalidate_label_size (NautilusCanvasItem *item);
+EelDRect nautilus_canvas_item_get_icon_rectangle (const NautilusCanvasItem *item);
+void nautilus_canvas_item_get_bounds_for_layout (NautilusCanvasItem *item,
+ double *x1, double *y1, double *x2, double *y2);
+void nautilus_canvas_item_get_bounds_for_entire_item (NautilusCanvasItem *item,
+ double *x1, double *y1, double *x2, double *y2);
+void nautilus_canvas_item_update_bounds (NautilusCanvasItem *item,
+ double i2w_dx, double i2w_dy);
+void nautilus_canvas_item_set_is_visible (NautilusCanvasItem *item,
+ gboolean visible);
+/* whether the entire label text must be visible at all times */
+void nautilus_canvas_item_set_entire_text (NautilusCanvasItem *canvas_item,
+ gboolean entire_text);
+
+G_END_DECLS
diff --git a/src/nautilus-canvas-private.h b/src/nautilus-canvas-private.h
new file mode 100644
index 0000000..e60e862
--- /dev/null
+++ b/src/nautilus-canvas-private.h
@@ -0,0 +1,223 @@
+/* gnome-canvas-container-private.h
+
+ Copyright (C) 1999, 2000 Free Software Foundation
+ 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: Ettore Perazzoli <ettore@gnu.org>
+*/
+
+#pragma once
+
+#include <eel/eel-glib-extensions.h>
+#include "nautilus-canvas-item.h"
+#include "nautilus-canvas-container.h"
+#include "nautilus-canvas-dnd.h"
+
+/* An Icon. */
+
+typedef struct {
+ /* Object represented by this icon. */
+ NautilusCanvasIconData *data;
+
+ /* Canvas item for the icon. */
+ NautilusCanvasItem *item;
+
+ /* X/Y coordinates. */
+ double x, y;
+
+ /*
+ * In RTL mode x is RTL x position, we use saved_ltr_x for
+ * keeping track of x value before it gets converted into
+ * RTL value, this is used for saving the icon position
+ * to the nautilus metafile.
+ */
+ double saved_ltr_x;
+
+ /* Position in the view */
+ int position;
+
+ /* Whether this item is selected. */
+ eel_boolean_bit is_selected : 1;
+
+ /* Whether this item was selected before rubberbanding. */
+ eel_boolean_bit was_selected_before_rubberband : 1;
+
+ /* Whether this item is visible in the view. */
+ eel_boolean_bit is_visible : 1;
+} NautilusCanvasIcon;
+
+
+/* Private NautilusCanvasContainer members. */
+
+typedef struct {
+ gboolean active;
+
+ double start_x, start_y;
+
+ EelCanvasItem *selection_rectangle;
+ GdkDevice *device;
+
+ guint timer_id;
+
+ guint prev_x, prev_y;
+ int last_adj_x;
+ int last_adj_y;
+} NautilusCanvasRubberbandInfo;
+
+typedef enum {
+ DRAG_STATE_INITIAL,
+ DRAG_STATE_MOVE_OR_COPY,
+ DRAG_STATE_STRETCH
+} DragState;
+
+typedef struct {
+ /* Pointer position in canvas coordinates. */
+ int pointer_x, pointer_y;
+
+ /* Icon top, left, and size in canvas coordinates. */
+ int icon_x, icon_y;
+ guint icon_size;
+} StretchState;
+
+typedef enum {
+ AXIS_NONE,
+ AXIS_HORIZONTAL,
+ AXIS_VERTICAL
+} Axis;
+
+enum {
+ LABEL_COLOR,
+ LABEL_COLOR_HIGHLIGHT,
+ LABEL_COLOR_ACTIVE,
+ LABEL_COLOR_PRELIGHT,
+ LABEL_INFO_COLOR,
+ LABEL_INFO_COLOR_HIGHLIGHT,
+ LABEL_INFO_COLOR_ACTIVE,
+ LAST_LABEL_COLOR
+};
+
+struct NautilusCanvasContainerDetails {
+ /* List of icons. */
+ GList *icons;
+ GList *new_icons;
+ GList *selection;
+ GHashTable *icon_set;
+
+ /* Currently focused icon for accessibility. */
+ NautilusCanvasIcon *focus;
+ gboolean keyboard_focus;
+
+ /* Starting icon for keyboard rubberbanding. */
+ NautilusCanvasIcon *keyboard_rubberband_start;
+
+ /* Last highlighted drop target. */
+ NautilusCanvasIcon *drop_target;
+
+ /* Rubberbanding status. */
+ NautilusCanvasRubberbandInfo rubberband_info;
+
+ /* Timeout used to make a selected icon fully visible after a short
+ * period of time. (The timeout is needed to make sure
+ * double-clicking still works.)
+ */
+ guint keyboard_icon_reveal_timer_id;
+ NautilusCanvasIcon *keyboard_icon_to_reveal;
+
+ /* Used to coalesce selection changed signals in some cases */
+ guint selection_changed_id;
+
+ /* If a request is made to reveal an unpositioned icon we remember
+ * it and reveal it once it gets positioned (in relayout).
+ */
+ NautilusCanvasIcon *pending_icon_to_reveal;
+
+ /* Remembered information about the start of the current event. */
+ guint32 button_down_time;
+
+ /* Drag state. Valid only if drag_button is non-zero. */
+ guint drag_button;
+ NautilusCanvasIcon *drag_icon;
+ int drag_x, drag_y;
+ DragState drag_state;
+ gboolean drag_started;
+
+ gboolean icon_selected_on_button_down;
+ gboolean double_clicked;
+ NautilusCanvasIcon *double_click_icon[2]; /* Both clicks in a double click need to be on the same icon */
+ guint double_click_button[2];
+
+ NautilusCanvasIcon *range_selection_base_icon;
+
+ /* Idle ID. */
+ guint idle_id;
+
+ /* Align idle id */
+ guint align_idle_id;
+
+ /* DnD info. */
+ NautilusCanvasDndInfo *dnd_info;
+ NautilusDragInfo *dnd_source_info;
+
+ /* zoom level */
+ int zoom_level;
+
+ /* specific fonts used to draw labels */
+ char *font;
+
+ /* State used so arrow keys don't wander if icons aren't lined up.
+ */
+ int arrow_key_start_x;
+ int arrow_key_start_y;
+ GtkDirectionType arrow_key_direction;
+
+ /* Mode settings. */
+ gboolean single_click_mode;
+
+ /* Set to TRUE after first allocation has been done */
+ gboolean has_been_allocated;
+
+ int size_allocation_count;
+ guint size_allocation_count_id;
+
+ /* a11y items used by canvas items */
+ guint a11y_item_action_idle_handler;
+ GQueue* a11y_item_action_queue;
+
+ eel_boolean_bit in_layout_now : 1;
+ eel_boolean_bit is_loading : 1;
+ eel_boolean_bit is_populating_container : 1;
+ eel_boolean_bit needs_resort : 1;
+ eel_boolean_bit selection_needs_resort : 1;
+};
+
+/* Private functions shared by mutiple files. */
+NautilusCanvasIcon *nautilus_canvas_container_get_icon_by_uri (NautilusCanvasContainer *container,
+ const char *uri);
+void nautilus_canvas_container_select_list_unselect_others (NautilusCanvasContainer *container,
+ GList *icons);
+char * nautilus_canvas_container_get_icon_uri (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *canvas);
+char * nautilus_canvas_container_get_icon_activation_uri (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *canvas);
+char * nautilus_canvas_container_get_icon_drop_target_uri (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *canvas);
+void nautilus_canvas_container_update_icon (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *canvas);
+gboolean nautilus_canvas_container_scroll (NautilusCanvasContainer *container,
+ int delta_x,
+ int delta_y);
+void nautilus_canvas_container_update_scroll_region (NautilusCanvasContainer *container);
diff --git a/src/nautilus-canvas-view-container.c b/src/nautilus-canvas-view-container.c
new file mode 100644
index 0000000..a8b1f8a
--- /dev/null
+++ b/src/nautilus-canvas-view-container.c
@@ -0,0 +1,377 @@
+/* fm-icon-container.h - the container widget for file manager icons
+ *
+ * Copyright (C) 2002 Sun Microsystems, 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: Michael Meeks <michael@ximian.com>
+ */
+
+#include "nautilus-canvas-view-container.h"
+
+#include <eel/eel-glib-extensions.h>
+#include <gio/gio.h>
+#include <glib/gi18n.h>
+#include <string.h>
+
+#include "nautilus-canvas-view.h"
+#include "nautilus-enums.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-thumbnails.h"
+
+struct _NautilusCanvasViewContainer
+{
+ NautilusCanvasContainer parent;
+
+ NautilusCanvasView *view;
+};
+
+G_DEFINE_TYPE (NautilusCanvasViewContainer, nautilus_canvas_view_container, NAUTILUS_TYPE_CANVAS_CONTAINER);
+
+static GQuark attribute_none_q;
+
+static NautilusCanvasView *
+get_canvas_view (NautilusCanvasContainer *container)
+{
+ /* Type unsafe comparison for performance */
+ return ((NautilusCanvasViewContainer *) container)->view;
+}
+
+static NautilusIconInfo *
+nautilus_canvas_view_container_get_icon_images (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data,
+ int size,
+ gboolean for_drag_accept)
+{
+ NautilusCanvasView *canvas_view;
+ NautilusFile *file;
+ NautilusFileIconFlags flags;
+ NautilusIconInfo *icon_info;
+ gint scale;
+
+ file = (NautilusFile *) data;
+
+ g_assert (NAUTILUS_IS_FILE (file));
+ canvas_view = get_canvas_view (container);
+ g_return_val_if_fail (canvas_view != NULL, NULL);
+
+ flags = NAUTILUS_FILE_ICON_FLAGS_USE_EMBLEMS |
+ NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS;
+
+ if (for_drag_accept)
+ {
+ flags |= NAUTILUS_FILE_ICON_FLAGS_FOR_DRAG_ACCEPT;
+ }
+
+ scale = gtk_widget_get_scale_factor (GTK_WIDGET (canvas_view));
+ icon_info = nautilus_file_get_icon (file, size, scale, flags);
+
+ return icon_info;
+}
+
+static char *
+nautilus_canvas_view_container_get_icon_description (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data)
+{
+ NautilusFile *file;
+ char *mime_type;
+ const char *description;
+
+ file = NAUTILUS_FILE (data);
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ mime_type = nautilus_file_get_mime_type (file);
+ description = g_content_type_get_description (mime_type);
+ g_free (mime_type);
+ return g_strdup (description);
+}
+
+static void
+nautilus_canvas_view_container_prioritize_thumbnailing (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data)
+{
+ NautilusFile *file;
+ char *uri;
+
+ file = (NautilusFile *) data;
+
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ if (nautilus_file_is_thumbnailing (file))
+ {
+ uri = nautilus_file_get_uri (file);
+ nautilus_thumbnail_prioritize (uri);
+ g_free (uri);
+ }
+}
+
+static GQuark *
+get_quark_from_strv (gchar **value)
+{
+ GQuark *quark;
+ int i;
+
+ quark = g_new0 (GQuark, g_strv_length (value) + 1);
+ for (i = 0; value[i] != NULL; ++i)
+ {
+ quark[i] = g_quark_from_string (value[i]);
+ }
+
+ return quark;
+}
+
+/*
+ * Get the preference for which caption text should appear
+ * beneath icons.
+ */
+static GQuark *
+nautilus_canvas_view_container_get_icon_text_attributes_from_preferences (void)
+{
+ GQuark *attributes;
+ gchar **value;
+
+ value = g_settings_get_strv (nautilus_icon_view_preferences,
+ NAUTILUS_PREFERENCES_ICON_VIEW_CAPTIONS);
+ attributes = get_quark_from_strv (value);
+ g_strfreev (value);
+
+ /* We don't need to sanity check the attributes list even though it came
+ * from preferences.
+ *
+ * There are 2 ways that the values in the list could be bad.
+ *
+ * 1) The user picks "bad" values. "bad" values are those that result in
+ * there being duplicate attributes in the list.
+ *
+ * 2) Value stored in GConf are tampered with. Its possible physically do
+ * this by pulling the rug underneath GConf and manually editing its
+ * config files. Its also possible to use a third party GConf key
+ * editor and store garbage for the keys in question.
+ *
+ * Thankfully, the Nautilus preferences machinery deals with both of
+ * these cases.
+ *
+ * In the first case, the preferences dialog widgetry prevents
+ * duplicate attributes by making "bad" choices insensitive.
+ *
+ * In the second case, the preferences getter (and also the auto storage) for
+ * string_array values are always valid members of the enumeration associated
+ * with the preference.
+ *
+ * So, no more error checking on attributes is needed here and we can return
+ * a the auto stored value.
+ */
+ return attributes;
+}
+
+static int
+quarkv_length (GQuark *attributes)
+{
+ int i;
+ i = 0;
+ while (attributes[i] != 0)
+ {
+ i++;
+ }
+ return i;
+}
+
+/**
+ * nautilus_canvas_view_get_icon_text_attribute_names:
+ *
+ * Get a list representing which text attributes should be displayed
+ * beneath an icon. The result is dependent on zoom level and possibly
+ * user configuration. Don't free the result.
+ * @view: NautilusCanvasView to query.
+ *
+ **/
+static GQuark *
+nautilus_canvas_view_container_get_icon_text_attribute_names (NautilusCanvasContainer *container,
+ int *len)
+{
+ GQuark *attributes;
+ int piece_count;
+
+ const int pieces_by_level[] =
+ {
+ 1, /* NAUTILUS_ZOOM_LEVEL_SMALL */
+ 2, /* NAUTILUS_ZOOM_LEVEL_STANDARD */
+ 3, /* NAUTILUS_ZOOM_LEVEL_LARGE */
+ 3, /* NAUTILUS_ZOOM_LEVEL_LARGER */
+ };
+
+ piece_count = pieces_by_level[nautilus_canvas_container_get_zoom_level (container)];
+
+ attributes = nautilus_canvas_view_container_get_icon_text_attributes_from_preferences ();
+
+ *len = MIN (piece_count, quarkv_length (attributes));
+
+ return attributes;
+}
+
+/* This callback returns the text, both the editable part, and the
+ * part below that is not editable.
+ */
+static void
+nautilus_canvas_view_container_get_icon_text (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data,
+ char **editable_text,
+ char **additional_text,
+ gboolean include_invisible)
+{
+ GQuark *attributes;
+ char *text_array[4];
+ int i, j, num_attributes;
+ NautilusCanvasView *canvas_view;
+ NautilusFile *file;
+ gboolean use_additional;
+
+ file = NAUTILUS_FILE (data);
+
+ g_assert (NAUTILUS_IS_FILE (file));
+ g_assert (editable_text != NULL);
+ canvas_view = get_canvas_view (container);
+ g_return_if_fail (canvas_view != NULL);
+
+ use_additional = (additional_text != NULL);
+
+ /* Strip the suffix for nautilus object xml files. */
+ *editable_text = nautilus_file_get_display_name (file);
+
+ if (!use_additional)
+ {
+ return;
+ }
+
+ /* Find out what attributes go below each icon. */
+ attributes = nautilus_canvas_view_container_get_icon_text_attribute_names (container,
+ &num_attributes);
+
+ /* Get the attributes. */
+ j = 0;
+ for (i = 0; i < num_attributes; ++i)
+ {
+ char *text;
+ if (attributes[i] == attribute_none_q)
+ {
+ continue;
+ }
+ text = nautilus_file_get_string_attribute_q (file, attributes[i]);
+ if (text == NULL)
+ {
+ continue;
+ }
+ text_array[j++] = text;
+ }
+ text_array[j] = NULL;
+
+ /* Return them. */
+ if (j == 0)
+ {
+ *additional_text = NULL;
+ }
+ else if (j == 1)
+ {
+ /* Only one item, avoid the strdup + free */
+ *additional_text = text_array[0];
+ }
+ else
+ {
+ *additional_text = g_strjoinv ("\n", text_array);
+
+ for (i = 0; i < j; i++)
+ {
+ g_free (text_array[i]);
+ }
+ }
+
+ g_free (attributes);
+}
+
+static int
+nautilus_canvas_view_container_compare_icons (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *icon_a,
+ NautilusCanvasIconData *icon_b)
+{
+ NautilusCanvasView *canvas_view;
+
+ canvas_view = get_canvas_view (container);
+ g_return_val_if_fail (canvas_view != NULL, 0);
+
+ /* Type unsafe comparisons for performance */
+ return nautilus_canvas_view_compare_files (canvas_view,
+ (NautilusFile *) icon_a,
+ (NautilusFile *) icon_b);
+}
+
+static int
+nautilus_canvas_view_container_compare_icons_by_name (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *icon_a,
+ NautilusCanvasIconData *icon_b)
+{
+ return nautilus_file_compare_for_sort
+ (NAUTILUS_FILE (icon_a),
+ NAUTILUS_FILE (icon_b),
+ NAUTILUS_FILE_SORT_BY_DISPLAY_NAME,
+ FALSE, FALSE);
+}
+
+static void
+nautilus_canvas_view_container_class_init (NautilusCanvasViewContainerClass *klass)
+{
+ NautilusCanvasContainerClass *ic_class;
+
+ ic_class = &klass->parent_class;
+
+ attribute_none_q = g_quark_from_static_string ("none");
+
+ ic_class->get_icon_text = nautilus_canvas_view_container_get_icon_text;
+ ic_class->get_icon_images = nautilus_canvas_view_container_get_icon_images;
+ ic_class->get_icon_description = nautilus_canvas_view_container_get_icon_description;
+ ic_class->prioritize_thumbnailing = nautilus_canvas_view_container_prioritize_thumbnailing;
+
+ ic_class->compare_icons = nautilus_canvas_view_container_compare_icons;
+ ic_class->compare_icons_by_name = nautilus_canvas_view_container_compare_icons_by_name;
+}
+
+static void
+nautilus_canvas_view_container_init (NautilusCanvasViewContainer *canvas_container)
+{
+ gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (canvas_container)),
+ GTK_STYLE_CLASS_VIEW);
+}
+
+NautilusCanvasContainer *
+nautilus_canvas_view_container_construct (NautilusCanvasViewContainer *canvas_container,
+ NautilusCanvasView *view)
+{
+ AtkObject *atk_obj;
+
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_VIEW (view), NULL);
+
+ canvas_container->view = view;
+ atk_obj = gtk_widget_get_accessible (GTK_WIDGET (canvas_container));
+ atk_object_set_name (atk_obj, _("Icon View"));
+
+ return NAUTILUS_CANVAS_CONTAINER (canvas_container);
+}
+
+NautilusCanvasContainer *
+nautilus_canvas_view_container_new (NautilusCanvasView *view)
+{
+ return nautilus_canvas_view_container_construct
+ (g_object_new (NAUTILUS_TYPE_CANVAS_VIEW_CONTAINER, NULL),
+ view);
+}
diff --git a/src/nautilus-canvas-view-container.h b/src/nautilus-canvas-view-container.h
new file mode 100644
index 0000000..b0e1031
--- /dev/null
+++ b/src/nautilus-canvas-view-container.h
@@ -0,0 +1,35 @@
+
+/* fm-icon-container.h - the container widget for file manager icons
+
+ Copyright (C) 2002 Sun Microsystems, 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: Michael Meeks <michael@ximian.com>
+*/
+
+#pragma once
+
+#include "nautilus-canvas-container.h"
+
+#define NAUTILUS_TYPE_CANVAS_VIEW_CONTAINER nautilus_canvas_view_container_get_type()
+
+G_DECLARE_FINAL_TYPE (NautilusCanvasViewContainer, nautilus_canvas_view_container,
+ NAUTILUS, CANVAS_VIEW_CONTAINER,
+ NautilusCanvasContainer)
+
+NautilusCanvasContainer *nautilus_canvas_view_container_construct (NautilusCanvasViewContainer *canvas_container,
+ NautilusCanvasView *view);
+NautilusCanvasContainer *nautilus_canvas_view_container_new (NautilusCanvasView *view);
diff --git a/src/nautilus-canvas-view.c b/src/nautilus-canvas-view.c
new file mode 100644
index 0000000..4b3197b
--- /dev/null
+++ b/src/nautilus-canvas-view.c
@@ -0,0 +1,1647 @@
+/* fm-canvas-view.c - implementation of canvas view of directory.
+ *
+ * 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: John Sullivan <sullivan@eazel.com>
+ */
+
+#include <config.h>
+
+#include "nautilus-canvas-view.h"
+
+#include "nautilus-canvas-view-container.h"
+#include "nautilus-error-reporting.h"
+#include "nautilus-files-view-dnd.h"
+#include "nautilus-toolbar.h"
+#include "nautilus-view.h"
+
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include "nautilus-directory.h"
+#include "nautilus-dnd.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-ui-utilities.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-canvas-container.h"
+#include "nautilus-canvas-dnd.h"
+#include "nautilus-metadata.h"
+#include "nautilus-clipboard.h"
+
+#define DEBUG_FLAG NAUTILUS_DEBUG_CANVAS_VIEW
+#include "nautilus-debug.h"
+
+#include <locale.h>
+#include <signal.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+typedef gboolean (*SortCriterionMatchFunc) (NautilusFile *file);
+
+typedef struct
+{
+ const NautilusFileSortType sort_type;
+ const char *metadata_text;
+ const char *action_target_name;
+ const gboolean reverse_order;
+ SortCriterionMatchFunc match_func;
+} SortCriterion;
+
+typedef enum
+{
+ MENU_ITEM_TYPE_STANDARD,
+ MENU_ITEM_TYPE_CHECK,
+ MENU_ITEM_TYPE_RADIO,
+ MENU_ITEM_TYPE_TREE
+} MenuItemType;
+
+struct _NautilusCanvasView
+{
+ NautilusFilesView parent_instance;
+
+ GList *icons_not_positioned;
+
+ guint react_to_canvas_change_idle_id;
+
+ const SortCriterion *sort;
+
+ GtkWidget *canvas_container;
+
+ /* FIXME: Needed for async operations. Suposedly we would use cancellable and gtask,
+ * sadly gtkclipboard doesn't support that.
+ * We follow this pattern for checking validity of the object in the views.
+ * Ideally we would connect to a weak reference and do a cancellable.
+ */
+ gboolean destroyed;
+};
+
+/* Note that the first item in this list is the default sort,
+ * and that the items show up in the menu in the order they
+ * appear in this list.
+ */
+static const SortCriterion sort_criteria[] =
+{
+ {
+ NAUTILUS_FILE_SORT_BY_DISPLAY_NAME,
+ "name",
+ "name",
+ FALSE
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_DISPLAY_NAME,
+ "name",
+ "name-desc",
+ TRUE
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_SIZE,
+ "size",
+ "size",
+ TRUE
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_TYPE,
+ "type",
+ "type",
+ FALSE
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_MTIME,
+ "modification date",
+ "modification-date",
+ FALSE
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_MTIME,
+ "modification date",
+ "modification-date-desc",
+ TRUE
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_ATIME,
+ "access date",
+ "access-date",
+ FALSE
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_ATIME,
+ "access date",
+ "access-date-desc",
+ TRUE
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_TRASHED_TIME,
+ "trashed",
+ "trash-time",
+ TRUE,
+ nautilus_file_is_in_trash
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_SEARCH_RELEVANCE,
+ NULL,
+ "search-relevance",
+ TRUE,
+ nautilus_file_is_in_search
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_RECENCY,
+ NULL,
+ "recency",
+ TRUE,
+ nautilus_file_is_in_recent
+ }
+};
+
+G_DEFINE_TYPE (NautilusCanvasView, nautilus_canvas_view, NAUTILUS_TYPE_FILES_VIEW);
+
+static void nautilus_canvas_view_set_directory_sort_by (NautilusCanvasView *canvas_view,
+ NautilusFile *file,
+ const SortCriterion *sort);
+static void nautilus_canvas_view_update_click_mode (NautilusCanvasView *canvas_view);
+static void nautilus_canvas_view_reveal_selection (NautilusFilesView *view);
+static const SortCriterion *get_sort_criterion_by_metadata_text (const char *metadata_text,
+ gboolean reversed);
+static const SortCriterion *get_sort_criterion_by_sort_type (NautilusFileSortType sort_type,
+ gboolean reversed);
+static const SortCriterion *get_default_sort_order (NautilusFile *file);
+static void nautilus_canvas_view_clear (NautilusFilesView *view);
+static void on_clipboard_owner_changed (GtkClipboard *clipboard,
+ GdkEvent *event,
+ gpointer user_data);
+
+static void
+nautilus_canvas_view_destroy (GtkWidget *object)
+{
+ NautilusCanvasView *canvas_view;
+ GtkClipboard *clipboard;
+
+ canvas_view = NAUTILUS_CANVAS_VIEW (object);
+
+ nautilus_canvas_view_clear (NAUTILUS_FILES_VIEW (object));
+
+ if (canvas_view->react_to_canvas_change_idle_id != 0)
+ {
+ g_source_remove (canvas_view->react_to_canvas_change_idle_id);
+ canvas_view->react_to_canvas_change_idle_id = 0;
+ }
+
+ clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+ g_signal_handlers_disconnect_by_func (clipboard,
+ on_clipboard_owner_changed,
+ canvas_view);
+
+ if (canvas_view->icons_not_positioned)
+ {
+ nautilus_file_list_free (canvas_view->icons_not_positioned);
+ canvas_view->icons_not_positioned = NULL;
+ }
+
+ GTK_WIDGET_CLASS (nautilus_canvas_view_parent_class)->destroy (object);
+}
+
+static NautilusCanvasContainer *
+get_canvas_container (NautilusCanvasView *canvas_view)
+{
+ return NAUTILUS_CANVAS_CONTAINER (canvas_view->canvas_container);
+}
+
+NautilusCanvasContainer *
+nautilus_canvas_view_get_canvas_container (NautilusCanvasView *canvas_view)
+{
+ return get_canvas_container (canvas_view);
+}
+
+static void
+update_sort_criterion (NautilusCanvasView *canvas_view,
+ const SortCriterion *sort,
+ gboolean set_metadata)
+{
+ NautilusFile *file;
+ const SortCriterion *overrided_sort_criterion;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (canvas_view));
+
+ /* Make sure we use the default one and not one that the user used previously
+ * of the change to not allow sorting on search and recent, or the
+ * case that the user or some app modified directly the metadata */
+ if (nautilus_file_is_in_search (file) || nautilus_file_is_in_recent (file))
+ {
+ overrided_sort_criterion = get_default_sort_order (file);
+ }
+ else if (sort != NULL && canvas_view->sort != sort)
+ {
+ overrided_sort_criterion = sort;
+ if (set_metadata)
+ {
+ /* Store the new sort setting. */
+ nautilus_canvas_view_set_directory_sort_by (canvas_view,
+ file,
+ sort);
+ }
+ }
+ else
+ {
+ return;
+ }
+
+ canvas_view->sort = overrided_sort_criterion;
+}
+
+static void
+list_covers (NautilusCanvasIconData *data,
+ gpointer callback_data)
+{
+ GSList **file_list;
+
+ file_list = callback_data;
+
+ *file_list = g_slist_prepend (*file_list, data);
+}
+
+static void
+unref_cover (NautilusCanvasIconData *data,
+ gpointer callback_data)
+{
+ nautilus_file_unref (NAUTILUS_FILE (data));
+}
+
+static void
+nautilus_canvas_view_clear (NautilusFilesView *view)
+{
+ NautilusCanvasContainer *canvas_container;
+ GSList *file_list;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_VIEW (view));
+
+ canvas_container = get_canvas_container (NAUTILUS_CANVAS_VIEW (view));
+ if (!canvas_container)
+ {
+ return;
+ }
+
+ /* Clear away the existing icons. */
+ file_list = NULL;
+ nautilus_canvas_container_for_each (canvas_container, list_covers, &file_list);
+ nautilus_canvas_container_clear (canvas_container);
+ g_slist_foreach (file_list, (GFunc) unref_cover, NULL);
+ g_slist_free (file_list);
+}
+
+static void
+nautilus_canvas_view_remove_file (NautilusFilesView *view,
+ NautilusFile *file,
+ NautilusDirectory *directory)
+{
+ NautilusCanvasView *canvas_view;
+
+ /* This used to assert that 'directory == nautilus_files_view_get_model (view)', but that
+ * resulted in a lot of crash reports (bug #352592). I don't see how that trace happens.
+ * It seems that somehow we get a files_changed event sent to the view from a directory
+ * that isn't the model, but the code disables the monitor and signal callback handlers when
+ * changing directories. Maybe we can get some more information when this happens.
+ * Further discussion in bug #368178.
+ */
+ if (directory != nautilus_files_view_get_model (view))
+ {
+ char *file_uri, *dir_uri, *model_uri;
+ file_uri = nautilus_file_get_uri (file);
+ dir_uri = nautilus_directory_get_uri (directory);
+ model_uri = nautilus_directory_get_uri (nautilus_files_view_get_model (view));
+ g_warning ("nautilus_canvas_view_remove_file() - directory not canvas view model, shouldn't happen.\n"
+ "file: %p:%s, dir: %p:%s, model: %p:%s, view loading: %d\n"
+ "If you see this, please add this info to http://bugzilla.gnome.org/show_bug.cgi?id=368178",
+ file, file_uri, directory, dir_uri, nautilus_files_view_get_model (view), model_uri, nautilus_files_view_get_loading (view));
+ g_free (file_uri);
+ g_free (dir_uri);
+ g_free (model_uri);
+ }
+
+ canvas_view = NAUTILUS_CANVAS_VIEW (view);
+
+ if (nautilus_canvas_container_remove (get_canvas_container (canvas_view),
+ NAUTILUS_CANVAS_ICON_DATA (file)))
+ {
+ nautilus_file_unref (file);
+ }
+}
+
+static void
+nautilus_canvas_view_add_files (NautilusFilesView *view,
+ GList *files)
+{
+ NautilusCanvasView *canvas_view;
+ NautilusCanvasContainer *canvas_container;
+ GList *l;
+
+ canvas_view = NAUTILUS_CANVAS_VIEW (view);
+ canvas_container = get_canvas_container (canvas_view);
+
+ for (l = files; l != NULL; l = l->next)
+ {
+ if (nautilus_canvas_container_add (canvas_container,
+ NAUTILUS_CANVAS_ICON_DATA (l->data)))
+ {
+ nautilus_file_ref (NAUTILUS_FILE (l->data));
+ }
+ }
+}
+
+static void
+nautilus_canvas_view_file_changed (NautilusFilesView *view,
+ NautilusFile *file,
+ NautilusDirectory *directory)
+{
+ NautilusCanvasView *canvas_view;
+
+ g_assert (directory == nautilus_files_view_get_model (view));
+
+ g_return_if_fail (view != NULL);
+ canvas_view = NAUTILUS_CANVAS_VIEW (view);
+
+ nautilus_canvas_container_request_update
+ (get_canvas_container (canvas_view),
+ NAUTILUS_CANVAS_ICON_DATA (file));
+}
+
+static const SortCriterion *
+nautilus_canvas_view_get_directory_sort_by (NautilusCanvasView *canvas_view,
+ NautilusFile *file)
+{
+ const SortCriterion *default_sort;
+ g_autofree char *sort_by = NULL;
+ gboolean reversed;
+
+ default_sort = get_default_sort_order (file);
+ g_return_val_if_fail (default_sort != NULL, NULL);
+
+ sort_by = nautilus_file_get_metadata (file,
+ NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_BY,
+ default_sort->metadata_text);
+
+ reversed = nautilus_file_get_boolean_metadata (file,
+ NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_REVERSED,
+ default_sort->reverse_order);
+
+ return get_sort_criterion_by_metadata_text (sort_by, reversed);
+}
+
+static const SortCriterion *
+get_default_sort_order (NautilusFile *file)
+{
+ NautilusFileSortType sort_type;
+ gboolean reversed;
+
+ sort_type = nautilus_file_get_default_sort_type (file, &reversed);
+
+ return get_sort_criterion_by_sort_type (sort_type, reversed);
+}
+
+static void
+nautilus_canvas_view_set_directory_sort_by (NautilusCanvasView *canvas_view,
+ NautilusFile *file,
+ const SortCriterion *sort)
+{
+ const SortCriterion *default_sort_criterion;
+
+ default_sort_criterion = get_default_sort_order (file);
+ g_return_if_fail (default_sort_criterion != NULL);
+
+ nautilus_file_set_metadata
+ (file, NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_BY,
+ default_sort_criterion->metadata_text,
+ sort->metadata_text);
+ nautilus_file_set_boolean_metadata (file,
+ NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_REVERSED,
+ default_sort_criterion->reverse_order,
+ sort->reverse_order);
+}
+
+static const SortCriterion *
+get_sort_criterion_by_metadata_text (const char *metadata_text,
+ gboolean reversed)
+{
+ guint i;
+
+ /* Figure out what the new sort setting should be. */
+ for (i = 0; i < G_N_ELEMENTS (sort_criteria); i++)
+ {
+ if (g_strcmp0 (sort_criteria[i].metadata_text, metadata_text) == 0
+ && reversed == sort_criteria[i].reverse_order)
+ {
+ return &sort_criteria[i];
+ }
+ }
+ return &sort_criteria[0];
+}
+
+static const SortCriterion *
+get_sort_criterion_by_action_target_name (const char *action_target_name)
+{
+ guint i;
+ /* Figure out what the new sort setting should be. */
+ for (i = 0; i < G_N_ELEMENTS (sort_criteria); i++)
+ {
+ if (g_strcmp0 (sort_criteria[i].action_target_name, action_target_name) == 0)
+ {
+ return &sort_criteria[i];
+ }
+ }
+ return NULL;
+}
+
+static const SortCriterion *
+get_sort_criterion_by_sort_type (NautilusFileSortType sort_type,
+ gboolean reversed)
+{
+ guint i;
+
+ /* Figure out what the new sort setting should be. */
+ for (i = 0; i < G_N_ELEMENTS (sort_criteria); i++)
+ {
+ if (sort_type == sort_criteria[i].sort_type
+ && reversed == sort_criteria[i].reverse_order)
+ {
+ return &sort_criteria[i];
+ }
+ }
+
+ return &sort_criteria[0];
+}
+
+static NautilusCanvasZoomLevel
+get_default_zoom_level (NautilusCanvasView *canvas_view)
+{
+ NautilusCanvasZoomLevel default_zoom_level;
+
+ default_zoom_level = g_settings_get_enum (nautilus_icon_view_preferences,
+ NAUTILUS_PREFERENCES_ICON_VIEW_DEFAULT_ZOOM_LEVEL);
+
+ return CLAMP (default_zoom_level, NAUTILUS_CANVAS_ZOOM_LEVEL_SMALL, NAUTILUS_CANVAS_ZOOM_LEVEL_LARGER);
+}
+
+static void
+nautilus_canvas_view_begin_loading (NautilusFilesView *view)
+{
+ NautilusCanvasView *canvas_view;
+ NautilusFile *file;
+ char *uri;
+ const SortCriterion *sort;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_VIEW (view));
+
+ canvas_view = NAUTILUS_CANVAS_VIEW (view);
+ file = nautilus_files_view_get_directory_as_file (view);
+ uri = nautilus_file_get_uri (file);
+
+ g_free (uri);
+
+ /* Set the sort mode.
+ * It's OK not to resort the icons because the
+ * container doesn't have any icons at this point.
+ */
+ sort = nautilus_canvas_view_get_directory_sort_by (canvas_view, file);
+ update_sort_criterion (canvas_view, sort, FALSE);
+
+ /* 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 (view);
+ nautilus_files_view_update_toolbar_menus (view);
+}
+
+static void
+on_clipboard_contents_received (GtkClipboard *clipboard,
+ const gchar *selection_data,
+ gpointer user_data)
+{
+ NautilusCanvasView *canvas_view;
+
+ canvas_view = NAUTILUS_CANVAS_VIEW (user_data);
+
+ if (canvas_view->destroyed)
+ {
+ /* We've been destroyed since call */
+ g_object_unref (canvas_view);
+ return;
+ }
+
+ if (nautilus_clipboard_is_cut_from_selection_data (selection_data))
+ {
+ GList *uris;
+ GList *files;
+
+ uris = nautilus_clipboard_get_uri_list_from_selection_data (selection_data);
+ files = nautilus_file_list_from_uri_list (uris);
+ nautilus_canvas_container_set_highlighted_for_clipboard (get_canvas_container (canvas_view),
+ files);
+
+ nautilus_file_list_free (files);
+ g_list_free_full (uris, g_free);
+ }
+ else
+ {
+ nautilus_canvas_container_set_highlighted_for_clipboard (get_canvas_container (canvas_view),
+ NULL);
+ }
+
+ g_object_unref (canvas_view);
+}
+
+static void
+update_clipboard_status (NautilusCanvasView *view)
+{
+ g_object_ref (view); /* Need to keep the object alive until we get the reply */
+ gtk_clipboard_request_text (nautilus_clipboard_get (GTK_WIDGET (view)),
+ on_clipboard_contents_received,
+ view);
+}
+
+static void
+on_clipboard_owner_changed (GtkClipboard *clipboard,
+ GdkEvent *event,
+ gpointer user_data)
+{
+ update_clipboard_status (NAUTILUS_CANVAS_VIEW (user_data));
+}
+
+static void
+nautilus_canvas_view_end_loading (NautilusFilesView *view,
+ gboolean all_files_seen)
+{
+ NautilusCanvasView *canvas_view;
+
+ canvas_view = NAUTILUS_CANVAS_VIEW (view);
+ update_clipboard_status (canvas_view);
+}
+
+static NautilusCanvasZoomLevel
+nautilus_canvas_view_get_zoom_level (NautilusFilesView *view)
+{
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_VIEW (view), NAUTILUS_CANVAS_ZOOM_LEVEL_LARGE);
+
+ return nautilus_canvas_container_get_zoom_level (get_canvas_container (NAUTILUS_CANVAS_VIEW (view)));
+}
+
+static void
+nautilus_canvas_view_zoom_to_level (NautilusFilesView *view,
+ gint new_level)
+{
+ NautilusCanvasView *canvas_view;
+ NautilusCanvasContainer *canvas_container;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_VIEW (view));
+ g_return_if_fail (new_level >= NAUTILUS_CANVAS_ZOOM_LEVEL_SMALL &&
+ new_level <= NAUTILUS_CANVAS_ZOOM_LEVEL_LARGER);
+
+ canvas_view = NAUTILUS_CANVAS_VIEW (view);
+ canvas_container = get_canvas_container (canvas_view);
+ if (nautilus_canvas_container_get_zoom_level (canvas_container) == new_level)
+ {
+ return;
+ }
+
+ nautilus_canvas_container_set_zoom_level (canvas_container, new_level);
+ g_action_group_change_action_state (nautilus_files_view_get_action_group (view),
+ "zoom-to-level", g_variant_new_int32 (new_level));
+
+ nautilus_files_view_update_toolbar_menus (view);
+}
+
+static void
+nautilus_canvas_view_bump_zoom_level (NautilusFilesView *view,
+ int zoom_increment)
+{
+ NautilusCanvasZoomLevel new_level;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_VIEW (view));
+ if (!nautilus_files_view_supports_zooming (view))
+ {
+ return;
+ }
+
+ new_level = nautilus_canvas_view_get_zoom_level (view) + zoom_increment;
+
+ if (new_level >= NAUTILUS_CANVAS_ZOOM_LEVEL_SMALL &&
+ new_level <= NAUTILUS_CANVAS_ZOOM_LEVEL_LARGER)
+ {
+ nautilus_canvas_view_zoom_to_level (view, new_level);
+ }
+}
+
+static void
+nautilus_canvas_view_restore_standard_zoom_level (NautilusFilesView *view)
+{
+ nautilus_canvas_view_zoom_to_level (view, NAUTILUS_CANVAS_ZOOM_LEVEL_LARGE);
+}
+
+static gboolean
+nautilus_canvas_view_can_zoom_in (NautilusFilesView *view)
+{
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_VIEW (view), FALSE);
+
+ return nautilus_canvas_view_get_zoom_level (view)
+ < NAUTILUS_CANVAS_ZOOM_LEVEL_LARGER;
+}
+
+static gboolean
+nautilus_canvas_view_can_zoom_out (NautilusFilesView *view)
+{
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_VIEW (view), FALSE);
+
+ return nautilus_canvas_view_get_zoom_level (view)
+ > NAUTILUS_CANVAS_ZOOM_LEVEL_SMALL;
+}
+
+static gfloat
+nautilus_canvas_view_get_zoom_level_percentage (NautilusFilesView *view)
+{
+ guint icon_size;
+ NautilusCanvasZoomLevel zoom_level;
+
+ zoom_level = nautilus_canvas_view_get_zoom_level (view);
+ icon_size = nautilus_canvas_container_get_icon_size_for_zoom_level (zoom_level);
+
+ return (gfloat) icon_size / NAUTILUS_CANVAS_ICON_SIZE_LARGE;
+}
+
+static gboolean
+nautilus_canvas_view_is_zoom_level_default (NautilusFilesView *view)
+{
+ guint icon_size;
+ NautilusCanvasZoomLevel zoom_level;
+
+ zoom_level = nautilus_canvas_view_get_zoom_level (view);
+ icon_size = nautilus_canvas_container_get_icon_size_for_zoom_level (zoom_level);
+
+ return icon_size == NAUTILUS_CANVAS_ICON_SIZE_LARGE;
+}
+
+static gboolean
+nautilus_canvas_view_is_empty (NautilusFilesView *view)
+{
+ g_assert (NAUTILUS_IS_CANVAS_VIEW (view));
+
+ return nautilus_canvas_container_is_empty
+ (get_canvas_container (NAUTILUS_CANVAS_VIEW (view)));
+}
+
+static GList *
+nautilus_canvas_view_get_selection (NautilusFilesView *view)
+{
+ GList *list;
+
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_VIEW (view), NULL);
+
+ list = nautilus_canvas_container_get_selection
+ (get_canvas_container (NAUTILUS_CANVAS_VIEW (view)));
+ nautilus_file_list_ref (list);
+ return list;
+}
+
+static void
+action_sort_order_changed (GSimpleAction *action,
+ GVariant *value,
+ gpointer user_data)
+{
+ const gchar *target_name;
+ const SortCriterion *sort_criterion;
+
+ g_assert (NAUTILUS_IS_CANVAS_VIEW (user_data));
+
+ target_name = g_variant_get_string (value, NULL);
+ sort_criterion = get_sort_criterion_by_action_target_name (target_name);
+
+ g_assert (sort_criterion != NULL);
+
+ update_sort_criterion (user_data, sort_criterion, TRUE);
+
+ nautilus_canvas_container_sort (get_canvas_container (user_data));
+ nautilus_canvas_view_reveal_selection (NAUTILUS_FILES_VIEW (user_data));
+
+ g_simple_action_set_state (action, value);
+}
+
+static void
+action_zoom_to_level (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusFilesView *view;
+ NautilusCanvasZoomLevel zoom_level;
+
+ g_assert (NAUTILUS_IS_FILES_VIEW (user_data));
+
+ view = NAUTILUS_FILES_VIEW (user_data);
+ zoom_level = g_variant_get_int32 (state);
+ nautilus_canvas_view_zoom_to_level (view, 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);
+ }
+}
+
+const GActionEntry canvas_view_entries[] =
+{
+ { "sort", NULL, "s", "'name'", action_sort_order_changed },
+ { "zoom-to-level", NULL, NULL, "1", action_zoom_to_level }
+};
+
+static void
+update_sort_action_state_hint (NautilusCanvasView *canvas_view)
+{
+ NautilusFile *file;
+ GVariantBuilder builder;
+ GActionGroup *action_group;
+ GAction *action;
+ GVariant *state_hint;
+ gint idx;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (canvas_view));
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("as"));
+
+ for (idx = 0; idx < G_N_ELEMENTS (sort_criteria); idx++)
+ {
+ if (sort_criteria[idx].match_func == NULL ||
+ (file != NULL && sort_criteria[idx].match_func (file)))
+ {
+ g_variant_builder_add (&builder, "s", sort_criteria[idx].action_target_name);
+ }
+ }
+
+ state_hint = g_variant_builder_end (&builder);
+
+ action_group = nautilus_files_view_get_action_group (NAUTILUS_FILES_VIEW (canvas_view));
+ action = g_action_map_lookup_action (G_ACTION_MAP (action_group), "sort");
+ g_simple_action_set_state_hint (G_SIMPLE_ACTION (action), state_hint);
+
+ g_variant_unref (state_hint);
+}
+
+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_search_directory (NautilusFilesView *view)
+{
+ NautilusFile *file;
+
+ file = nautilus_files_view_get_directory_as_file (view);
+ if (file != NULL)
+ {
+ return nautilus_file_is_in_search (file);
+ }
+ return FALSE;
+}
+
+static void
+nautilus_canvas_view_update_actions_state (NautilusFilesView *view)
+{
+ GActionGroup *view_action_group;
+ GVariant *sort_state;
+ GAction *action;
+ NautilusCanvasView *canvas_view;
+
+ canvas_view = NAUTILUS_CANVAS_VIEW (view);
+
+ NAUTILUS_FILES_VIEW_CLASS (nautilus_canvas_view_parent_class)->update_actions_state (view);
+
+ view_action_group = nautilus_files_view_get_action_group (view);
+
+ /* When we change the sort action state, even using the same value, it triggers
+ * the sort action changed handler, which reveals the selection, since we expect
+ * the selection to be visible when the user changes the sort order. But we may
+ * need to update the actions state for others reason than an actual sort change,
+ * so we need to prevent to trigger the sort action changed handler for those cases.
+ * To achieve this, check if the action state value actually changed before setting
+ * it
+ */
+ sort_state = g_action_group_get_action_state (view_action_group, "sort");
+
+ if (g_strcmp0 (g_variant_get_string (sort_state, NULL),
+ canvas_view->sort->action_target_name) != 0)
+ {
+ g_action_group_change_action_state (view_action_group,
+ "sort",
+ g_variant_new_string (canvas_view->sort->action_target_name));
+ }
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "sort");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
+ !showing_recent_directory (view) &&
+ !showing_search_directory (view));
+
+ update_sort_action_state_hint (canvas_view);
+
+ g_variant_unref (sort_state);
+}
+
+static void
+nautilus_canvas_view_select_all (NautilusFilesView *view)
+{
+ NautilusCanvasContainer *canvas_container;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_VIEW (view));
+
+ canvas_container = get_canvas_container (NAUTILUS_CANVAS_VIEW (view));
+ nautilus_canvas_container_select_all (canvas_container);
+}
+
+static void
+nautilus_canvas_view_select_first (NautilusFilesView *view)
+{
+ NautilusCanvasContainer *canvas_container;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_VIEW (view));
+
+ canvas_container = get_canvas_container (NAUTILUS_CANVAS_VIEW (view));
+ nautilus_canvas_container_select_first (canvas_container);
+}
+
+static void
+nautilus_canvas_view_reveal_selection (NautilusFilesView *view)
+{
+ g_autolist (NautilusFile) selection = NULL;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_VIEW (view));
+
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (view));
+
+ /* Make sure at least one of the selected items is scrolled into view */
+ if (selection != NULL)
+ {
+ /* Update the icon ordering to reveal the rigth selection */
+ nautilus_canvas_container_layout_now (get_canvas_container (NAUTILUS_CANVAS_VIEW (view)));
+ nautilus_canvas_container_reveal
+ (get_canvas_container (NAUTILUS_CANVAS_VIEW (view)),
+ selection->data);
+ }
+}
+
+static GdkRectangle *
+get_rectangle_for_data (NautilusFilesView *view,
+ NautilusCanvasIconData *data)
+{
+ NautilusCanvasContainer *container;
+ GdkRectangle *rectangle;
+
+ container = get_canvas_container (NAUTILUS_CANVAS_VIEW (view));
+ rectangle = nautilus_canvas_container_get_icon_bounding_box (container, data);
+ if (rectangle != NULL)
+ {
+ GtkWidget *context_widget;
+ GtkAdjustment *vadjustment;
+ GtkAdjustment *hadjustment;
+
+ context_widget = nautilus_files_view_get_content_widget (view);
+ vadjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (context_widget));
+ hadjustment = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (context_widget));
+
+ rectangle->x -= gtk_adjustment_get_value (hadjustment);
+ rectangle->y -= gtk_adjustment_get_value (vadjustment);
+ }
+ return rectangle;
+}
+
+static GdkRectangle *
+nautilus_canvas_view_compute_rename_popover_pointing_to (NautilusFilesView *view)
+{
+ g_autolist (NautilusFile) selection = NULL;
+ NautilusCanvasIconData *data;
+
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_VIEW (view), NULL);
+
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (view));
+ g_return_val_if_fail (selection != NULL, NULL);
+
+ /* We only allow renaming one item at once */
+ data = NAUTILUS_CANVAS_ICON_DATA (selection->data);
+
+ return get_rectangle_for_data (view, data);
+}
+
+static GdkRectangle *
+nautilus_canvas_view_reveal_for_selection_context_menu (NautilusFilesView *view)
+{
+ g_autolist (NautilusFile) selection = NULL;
+ NautilusCanvasContainer *container;
+ NautilusCanvasIconData *data;
+
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_VIEW (view), NULL);
+
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (view));
+ g_return_val_if_fail (selection != NULL, NULL);
+
+ container = get_canvas_container (NAUTILUS_CANVAS_VIEW (view));
+
+ /* Update the icon ordering to reveal the rigth selection */
+ nautilus_canvas_container_layout_now (container);
+
+ /* Get the data of the focused item, if selected. Otherwise, get the
+ * data of the last selected item.*/
+ data = nautilus_canvas_container_get_focused_icon (container);
+ if (data == NULL || g_list_find (selection, NAUTILUS_FILE (data)) == NULL)
+ {
+ selection = g_list_last (selection);
+ data = NAUTILUS_CANVAS_ICON_DATA (selection->data);
+ }
+
+ nautilus_canvas_container_reveal (container, data);
+
+ return get_rectangle_for_data (view, data);
+}
+
+static void
+nautilus_canvas_view_set_selection (NautilusFilesView *view,
+ GList *selection)
+{
+ g_return_if_fail (NAUTILUS_IS_CANVAS_VIEW (view));
+
+ nautilus_canvas_container_set_selection
+ (get_canvas_container (NAUTILUS_CANVAS_VIEW (view)), selection);
+}
+
+static void
+nautilus_canvas_view_invert_selection (NautilusFilesView *view)
+{
+ g_return_if_fail (NAUTILUS_IS_CANVAS_VIEW (view));
+
+ nautilus_canvas_container_invert_selection
+ (get_canvas_container (NAUTILUS_CANVAS_VIEW (view)));
+}
+
+static void
+nautilus_canvas_view_widget_to_file_operation_position (NautilusFilesView *view,
+ GdkPoint *position)
+{
+ g_assert (NAUTILUS_IS_CANVAS_VIEW (view));
+
+ nautilus_canvas_container_widget_to_file_operation_position
+ (get_canvas_container (NAUTILUS_CANVAS_VIEW (view)), position);
+}
+
+static void
+canvas_container_activate_callback (NautilusCanvasContainer *container,
+ GList *file_list,
+ NautilusCanvasView *canvas_view)
+{
+ g_assert (NAUTILUS_IS_CANVAS_VIEW (canvas_view));
+ g_assert (container == get_canvas_container (canvas_view));
+
+ nautilus_files_view_activate_files (NAUTILUS_FILES_VIEW (canvas_view),
+ file_list,
+ 0, TRUE);
+}
+
+static void
+canvas_container_activate_previewer_callback (NautilusCanvasContainer *container,
+ GList *file_list,
+ GArray *locations,
+ NautilusCanvasView *canvas_view)
+{
+ g_assert (NAUTILUS_IS_CANVAS_VIEW (canvas_view));
+ g_assert (container == get_canvas_container (canvas_view));
+
+ nautilus_files_view_preview_files (NAUTILUS_FILES_VIEW (canvas_view),
+ file_list, locations);
+}
+
+/* this is called in one of these cases:
+ * - we activate with enter holding shift
+ * - we activate with space holding shift
+ * - we double click an canvas holding shift
+ * - we middle click an canvas
+ *
+ * If we don't open in new windows by default, the behavior should be
+ * - middle click, shift + activate -> open in new tab
+ * - shift + double click -> open in new window
+ *
+ * If we open in new windows by default, the behaviour should be
+ * - middle click, or shift + activate, or shift + double-click -> close parent
+ */
+static void
+canvas_container_activate_alternate_callback (NautilusCanvasContainer *container,
+ GList *file_list,
+ NautilusCanvasView *canvas_view)
+{
+ GdkEvent *event;
+ GdkEventButton *button_event;
+ GdkEventKey *key_event;
+ gboolean open_in_tab, open_in_window;
+ NautilusWindowOpenFlags flags;
+
+ g_assert (NAUTILUS_IS_CANVAS_VIEW (canvas_view));
+ g_assert (container == get_canvas_container (canvas_view));
+
+ flags = 0;
+ event = gtk_get_current_event ();
+ open_in_tab = FALSE;
+ open_in_window = FALSE;
+
+ if (event->type == GDK_BUTTON_PRESS ||
+ event->type == GDK_BUTTON_RELEASE ||
+ event->type == GDK_2BUTTON_PRESS ||
+ event->type == GDK_3BUTTON_PRESS)
+ {
+ button_event = (GdkEventButton *) event;
+ open_in_window = ((button_event->state & GDK_SHIFT_MASK) != 0);
+ open_in_tab = !open_in_window;
+ }
+ else if (event->type == GDK_KEY_PRESS ||
+ event->type == GDK_KEY_RELEASE)
+ {
+ key_event = (GdkEventKey *) event;
+ open_in_tab = ((key_event->state & GDK_SHIFT_MASK) != 0);
+ }
+
+ if (open_in_tab)
+ {
+ flags |= NAUTILUS_WINDOW_OPEN_FLAG_NEW_TAB;
+ flags |= NAUTILUS_WINDOW_OPEN_FLAG_DONT_MAKE_ACTIVE;
+ }
+
+ if (open_in_window)
+ {
+ flags |= NAUTILUS_WINDOW_OPEN_FLAG_NEW_WINDOW;
+ }
+
+ DEBUG ("Activate alternate, open in tab %d, new window %d\n",
+ open_in_tab, open_in_window);
+
+ nautilus_files_view_activate_files (NAUTILUS_FILES_VIEW (canvas_view),
+ file_list,
+ flags,
+ TRUE);
+}
+
+static void
+band_select_started_callback (NautilusCanvasContainer *container,
+ NautilusCanvasView *canvas_view)
+{
+ g_assert (NAUTILUS_IS_CANVAS_VIEW (canvas_view));
+ g_assert (container == get_canvas_container (canvas_view));
+
+ nautilus_files_view_start_batching_selection_changes (NAUTILUS_FILES_VIEW (canvas_view));
+}
+
+static void
+band_select_ended_callback (NautilusCanvasContainer *container,
+ NautilusCanvasView *canvas_view)
+{
+ g_assert (NAUTILUS_IS_CANVAS_VIEW (canvas_view));
+ g_assert (container == get_canvas_container (canvas_view));
+
+ nautilus_files_view_stop_batching_selection_changes (NAUTILUS_FILES_VIEW (canvas_view));
+}
+
+int
+nautilus_canvas_view_compare_files (NautilusCanvasView *canvas_view,
+ NautilusFile *a,
+ NautilusFile *b)
+{
+ return nautilus_file_compare_for_sort
+ (a, b, canvas_view->sort->sort_type,
+ /* Use type-unsafe cast for performance */
+ nautilus_files_view_should_sort_directories_first ((NautilusFilesView *) canvas_view),
+ canvas_view->sort->reverse_order);
+}
+
+static int
+compare_files (NautilusFilesView *canvas_view,
+ NautilusFile *a,
+ NautilusFile *b)
+{
+ return nautilus_canvas_view_compare_files ((NautilusCanvasView *) canvas_view, a, b);
+}
+
+static void
+selection_changed_callback (NautilusCanvasContainer *container,
+ NautilusCanvasView *canvas_view)
+{
+ g_assert (NAUTILUS_IS_CANVAS_VIEW (canvas_view));
+ g_assert (container == get_canvas_container (canvas_view));
+
+ nautilus_files_view_notify_selection_changed (NAUTILUS_FILES_VIEW (canvas_view));
+}
+
+static void
+canvas_container_context_click_selection_callback (NautilusCanvasContainer *container,
+ const GdkEvent *event,
+ NautilusCanvasView *canvas_view)
+{
+ g_assert (NAUTILUS_IS_CANVAS_CONTAINER (container));
+ g_assert (NAUTILUS_IS_CANVAS_VIEW (canvas_view));
+
+ nautilus_files_view_pop_up_selection_context_menu (NAUTILUS_FILES_VIEW (canvas_view),
+ event);
+}
+
+static void
+canvas_container_context_click_background_callback (NautilusCanvasContainer *container,
+ const GdkEvent *event,
+ NautilusCanvasView *canvas_view)
+{
+ g_assert (NAUTILUS_IS_CANVAS_CONTAINER (container));
+ g_assert (NAUTILUS_IS_CANVAS_VIEW (canvas_view));
+
+ nautilus_files_view_pop_up_background_context_menu (NAUTILUS_FILES_VIEW (canvas_view),
+ event);
+}
+
+static char *
+get_icon_uri_callback (NautilusCanvasContainer *container,
+ NautilusFile *file,
+ NautilusCanvasView *canvas_view)
+{
+ g_assert (NAUTILUS_IS_CANVAS_CONTAINER (container));
+ g_assert (NAUTILUS_IS_FILE (file));
+ g_assert (NAUTILUS_IS_CANVAS_VIEW (canvas_view));
+
+ return nautilus_file_get_uri (file);
+}
+
+static char *
+get_icon_activation_uri_callback (NautilusCanvasContainer *container,
+ NautilusFile *file,
+ NautilusCanvasView *canvas_view)
+{
+ g_assert (NAUTILUS_IS_CANVAS_CONTAINER (container));
+ g_assert (NAUTILUS_IS_FILE (file));
+ g_assert (NAUTILUS_IS_CANVAS_VIEW (canvas_view));
+
+ return nautilus_file_get_activation_uri (file);
+}
+
+static char *
+get_icon_drop_target_uri_callback (NautilusCanvasContainer *container,
+ NautilusFile *file,
+ NautilusCanvasView *canvas_view)
+{
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container), NULL);
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL);
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_VIEW (canvas_view), NULL);
+
+ return nautilus_file_get_uri (file);
+}
+
+/* Preferences changed callbacks */
+static void
+nautilus_canvas_view_click_policy_changed (NautilusFilesView *directory_view)
+{
+ g_assert (NAUTILUS_IS_CANVAS_VIEW (directory_view));
+
+ nautilus_canvas_view_update_click_mode (NAUTILUS_CANVAS_VIEW (directory_view));
+}
+
+static void
+image_display_policy_changed_callback (gpointer callback_data)
+{
+ NautilusCanvasView *canvas_view;
+
+ canvas_view = NAUTILUS_CANVAS_VIEW (callback_data);
+
+ nautilus_canvas_container_request_update_all (get_canvas_container (canvas_view));
+}
+
+static void
+text_attribute_names_changed_callback (gpointer callback_data)
+{
+ NautilusCanvasView *canvas_view;
+
+ canvas_view = NAUTILUS_CANVAS_VIEW (callback_data);
+
+ nautilus_canvas_container_request_update_all (get_canvas_container (canvas_view));
+}
+
+static void
+default_sort_order_changed_callback (gpointer callback_data)
+{
+ NautilusCanvasView *canvas_view;
+ NautilusFile *file;
+ const SortCriterion *sort;
+ NautilusCanvasContainer *canvas_container;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_VIEW (callback_data));
+
+ canvas_view = NAUTILUS_CANVAS_VIEW (callback_data);
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (canvas_view));
+ sort = nautilus_canvas_view_get_directory_sort_by (canvas_view, file);
+ update_sort_criterion (canvas_view, sort, FALSE);
+
+ canvas_container = get_canvas_container (canvas_view);
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (canvas_container));
+
+ nautilus_canvas_container_request_update_all (canvas_container);
+}
+
+static void
+nautilus_canvas_view_sort_directories_first_changed (NautilusFilesView *directory_view)
+{
+ NautilusCanvasView *canvas_view;
+
+ canvas_view = NAUTILUS_CANVAS_VIEW (directory_view);
+
+ nautilus_canvas_container_sort (get_canvas_container (canvas_view));
+}
+
+static void
+nautilus_canvas_view_preview_selection_event (NautilusFilesView *directory_view,
+ GtkDirectionType direction)
+{
+ NautilusCanvasView *canvas_view;
+
+ canvas_view = NAUTILUS_CANVAS_VIEW (directory_view);
+
+ nautilus_canvas_container_preview_selection_event (get_canvas_container (canvas_view),
+ direction);
+}
+
+static char *
+canvas_view_get_container_uri (NautilusCanvasContainer *container,
+ NautilusFilesView *view)
+{
+ return nautilus_files_view_get_uri (view);
+}
+
+static void
+canvas_view_move_copy_items (NautilusCanvasContainer *container,
+ const GList *item_uris,
+ const char *target_dir,
+ int copy_action,
+ NautilusFilesView *view)
+{
+ nautilus_clipboard_clear_if_colliding_uris (GTK_WIDGET (view),
+ item_uris);
+ nautilus_files_view_move_copy_items (view, item_uris, target_dir,
+ copy_action);
+}
+
+static void
+nautilus_canvas_view_update_click_mode (NautilusCanvasView *canvas_view)
+{
+ NautilusCanvasContainer *canvas_container;
+ int click_mode;
+
+ canvas_container = get_canvas_container (canvas_view);
+ g_assert (canvas_container != NULL);
+
+ click_mode = g_settings_get_enum (nautilus_preferences, NAUTILUS_PREFERENCES_CLICK_POLICY);
+
+ nautilus_canvas_container_set_single_click_mode (canvas_container,
+ click_mode == NAUTILUS_CLICK_POLICY_SINGLE);
+}
+
+static void
+canvas_container_longpress_gesture_pressed_callback (GtkGestureLongPress *gesture,
+ gdouble x,
+ gdouble y,
+ gpointer user_data)
+{
+ GdkEventSequence *event_sequence;
+ const GdkEvent *event;
+ NautilusCanvasView *view = NAUTILUS_CANVAS_VIEW (user_data);
+
+ event_sequence = gtk_gesture_get_last_updated_sequence (GTK_GESTURE (gesture));
+ event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), event_sequence);
+
+ if (nautilus_view_get_selection (NAUTILUS_VIEW (view)))
+ {
+ nautilus_files_view_pop_up_selection_context_menu (NAUTILUS_FILES_VIEW (view),
+ event);
+ }
+ else
+ {
+ nautilus_files_view_pop_up_background_context_menu (NAUTILUS_FILES_VIEW (view),
+ event);
+ }
+}
+
+static void
+initialize_canvas_container (NautilusCanvasView *canvas_view,
+ NautilusCanvasContainer *canvas_container)
+{
+ GtkWidget *content_widget;
+ GtkGesture *longpress_gesture;
+
+ content_widget = nautilus_files_view_get_content_widget (NAUTILUS_FILES_VIEW (canvas_view));
+ canvas_view->canvas_container = GTK_WIDGET (canvas_container);
+ g_object_add_weak_pointer (G_OBJECT (canvas_container),
+ (gpointer *) &canvas_view->canvas_container);
+
+ longpress_gesture = gtk_gesture_long_press_new (GTK_WIDGET (content_widget));
+ gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (longpress_gesture),
+ GTK_PHASE_CAPTURE);
+ gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (longpress_gesture),
+ TRUE);
+ g_signal_connect (longpress_gesture, "pressed",
+ (GCallback) canvas_container_longpress_gesture_pressed_callback,
+ canvas_view);
+
+ gtk_widget_set_can_focus (GTK_WIDGET (canvas_container), TRUE);
+
+ g_signal_connect_object (canvas_container, "activate",
+ G_CALLBACK (canvas_container_activate_callback), canvas_view, 0);
+ g_signal_connect_object (canvas_container, "activate-alternate",
+ G_CALLBACK (canvas_container_activate_alternate_callback), canvas_view, 0);
+ g_signal_connect_object (canvas_container, "activate-previewer",
+ G_CALLBACK (canvas_container_activate_previewer_callback), canvas_view, 0);
+ g_signal_connect_object (canvas_container, "band-select-started",
+ G_CALLBACK (band_select_started_callback), canvas_view, 0);
+ g_signal_connect_object (canvas_container, "band-select-ended",
+ G_CALLBACK (band_select_ended_callback), canvas_view, 0);
+ g_signal_connect_object (canvas_container, "context-click-selection",
+ G_CALLBACK (canvas_container_context_click_selection_callback), canvas_view, 0);
+ g_signal_connect_object (canvas_container, "context-click-background",
+ G_CALLBACK (canvas_container_context_click_background_callback), canvas_view, 0);
+ g_signal_connect_object (canvas_container, "selection-changed",
+ G_CALLBACK (selection_changed_callback), canvas_view, 0);
+ /* FIXME: many of these should move into fm-canvas-container as virtual methods */
+ g_signal_connect_object (canvas_container, "get-icon-uri",
+ G_CALLBACK (get_icon_uri_callback), canvas_view, 0);
+ g_signal_connect_object (canvas_container, "get-icon-activation-uri",
+ G_CALLBACK (get_icon_activation_uri_callback), canvas_view, 0);
+ g_signal_connect_object (canvas_container, "get-icon-drop-target-uri",
+ G_CALLBACK (get_icon_drop_target_uri_callback), canvas_view, 0);
+ g_signal_connect_object (canvas_container, "move-copy-items",
+ G_CALLBACK (canvas_view_move_copy_items), canvas_view, 0);
+ g_signal_connect_object (canvas_container, "get-container-uri",
+ G_CALLBACK (canvas_view_get_container_uri), canvas_view, 0);
+
+ gtk_container_add (GTK_CONTAINER (content_widget),
+ GTK_WIDGET (canvas_container));
+
+ nautilus_canvas_view_update_click_mode (canvas_view);
+ nautilus_canvas_container_set_zoom_level (canvas_container,
+ get_default_zoom_level (canvas_view));
+
+ gtk_widget_show (GTK_WIDGET (canvas_container));
+}
+
+static void
+canvas_view_handle_uri_list (NautilusCanvasContainer *container,
+ const char *item_uris,
+ const char *target_uri,
+ GdkDragAction action,
+ NautilusCanvasView *view)
+{
+ nautilus_files_view_handle_uri_list_drop (NAUTILUS_FILES_VIEW (view),
+ item_uris, target_uri, action);
+}
+
+/* Handles an URL received from Mozilla */
+static void
+canvas_view_handle_netscape_url (NautilusCanvasContainer *container,
+ const char *encoded_url,
+ const char *target_uri,
+ GdkDragAction action,
+ NautilusCanvasView *view)
+{
+ nautilus_files_view_handle_netscape_url_drop (NAUTILUS_FILES_VIEW (view),
+ encoded_url, target_uri, action);
+}
+
+static void
+canvas_view_handle_text (NautilusCanvasContainer *container,
+ const char *text,
+ const char *target_uri,
+ GdkDragAction action,
+ NautilusCanvasView *view)
+{
+ nautilus_files_view_handle_text_drop (NAUTILUS_FILES_VIEW (view),
+ text, target_uri, action);
+}
+
+static void
+canvas_view_handle_raw (NautilusCanvasContainer *container,
+ const char *raw_data,
+ int length,
+ const char *target_uri,
+ const char *direct_save_uri,
+ GdkDragAction action,
+ NautilusCanvasView *view)
+{
+ nautilus_files_view_handle_raw_drop (NAUTILUS_FILES_VIEW (view),
+ raw_data, length, target_uri, direct_save_uri, action);
+}
+
+static void
+canvas_view_handle_hover (NautilusCanvasContainer *container,
+ const char *target_uri,
+ NautilusCanvasView *view)
+{
+ nautilus_files_view_handle_hover (NAUTILUS_FILES_VIEW (view), target_uri);
+}
+
+static char *
+canvas_view_get_first_visible_file (NautilusFilesView *view)
+{
+ NautilusFile *file;
+ NautilusCanvasView *canvas_view;
+
+ canvas_view = NAUTILUS_CANVAS_VIEW (view);
+
+ file = NAUTILUS_FILE (nautilus_canvas_container_get_first_visible_icon (get_canvas_container (canvas_view)));
+
+ if (file)
+ {
+ return nautilus_file_get_uri (file);
+ }
+
+ return NULL;
+}
+
+static void
+canvas_view_scroll_to_file (NautilusFilesView *view,
+ const char *uri)
+{
+ NautilusFile *file;
+ NautilusCanvasView *canvas_view;
+
+ canvas_view = NAUTILUS_CANVAS_VIEW (view);
+
+ if (uri != NULL)
+ {
+ /* Only if existing, since we don't want to add the file to
+ * the directory if it has been removed since then */
+ file = nautilus_file_get_existing_by_uri (uri);
+ if (file != NULL)
+ {
+ nautilus_canvas_container_scroll_to_canvas (get_canvas_container (canvas_view),
+ NAUTILUS_CANVAS_ICON_DATA (file));
+ nautilus_file_unref (file);
+ }
+ }
+}
+
+static guint
+nautilus_canvas_view_get_id (NautilusFilesView *view)
+{
+ return NAUTILUS_VIEW_GRID_ID;
+}
+
+static void
+nautilus_canvas_view_dispose (GObject *object)
+{
+ NautilusCanvasView *canvas_view;
+
+ canvas_view = NAUTILUS_CANVAS_VIEW (object);
+ canvas_view->destroyed = TRUE;
+
+ g_signal_handlers_disconnect_by_func (nautilus_preferences,
+ default_sort_order_changed_callback,
+ canvas_view);
+ g_signal_handlers_disconnect_by_func (nautilus_preferences,
+ image_display_policy_changed_callback,
+ canvas_view);
+
+ g_signal_handlers_disconnect_by_func (nautilus_icon_view_preferences,
+ text_attribute_names_changed_callback,
+ canvas_view);
+
+
+ G_OBJECT_CLASS (nautilus_canvas_view_parent_class)->dispose (object);
+}
+
+static void
+nautilus_canvas_view_class_init (NautilusCanvasViewClass *klass)
+{
+ NautilusFilesViewClass *nautilus_files_view_class;
+ GObjectClass *oclass;
+
+ nautilus_files_view_class = NAUTILUS_FILES_VIEW_CLASS (klass);
+ oclass = G_OBJECT_CLASS (klass);
+
+ oclass->dispose = nautilus_canvas_view_dispose;
+
+ GTK_WIDGET_CLASS (klass)->destroy = nautilus_canvas_view_destroy;
+
+ nautilus_files_view_class->add_files = nautilus_canvas_view_add_files;
+ nautilus_files_view_class->begin_loading = nautilus_canvas_view_begin_loading;
+ nautilus_files_view_class->bump_zoom_level = nautilus_canvas_view_bump_zoom_level;
+ nautilus_files_view_class->can_zoom_in = nautilus_canvas_view_can_zoom_in;
+ nautilus_files_view_class->can_zoom_out = nautilus_canvas_view_can_zoom_out;
+ nautilus_files_view_class->get_zoom_level_percentage = nautilus_canvas_view_get_zoom_level_percentage;
+ nautilus_files_view_class->is_zoom_level_default = nautilus_canvas_view_is_zoom_level_default;
+ nautilus_files_view_class->clear = nautilus_canvas_view_clear;
+ nautilus_files_view_class->end_loading = nautilus_canvas_view_end_loading;
+ nautilus_files_view_class->file_changed = nautilus_canvas_view_file_changed;
+ nautilus_files_view_class->compute_rename_popover_pointing_to = nautilus_canvas_view_compute_rename_popover_pointing_to;
+ nautilus_files_view_class->get_selection = nautilus_canvas_view_get_selection;
+ nautilus_files_view_class->get_selection_for_file_transfer = nautilus_canvas_view_get_selection;
+ nautilus_files_view_class->is_empty = nautilus_canvas_view_is_empty;
+ nautilus_files_view_class->remove_file = nautilus_canvas_view_remove_file;
+ nautilus_files_view_class->restore_standard_zoom_level = nautilus_canvas_view_restore_standard_zoom_level;
+ nautilus_files_view_class->reveal_selection = nautilus_canvas_view_reveal_selection;
+ nautilus_files_view_class->select_all = nautilus_canvas_view_select_all;
+ nautilus_files_view_class->select_first = nautilus_canvas_view_select_first;
+ nautilus_files_view_class->set_selection = nautilus_canvas_view_set_selection;
+ nautilus_files_view_class->invert_selection = nautilus_canvas_view_invert_selection;
+ nautilus_files_view_class->compare_files = compare_files;
+ nautilus_files_view_class->click_policy_changed = nautilus_canvas_view_click_policy_changed;
+ nautilus_files_view_class->update_actions_state = nautilus_canvas_view_update_actions_state;
+ nautilus_files_view_class->sort_directories_first_changed = nautilus_canvas_view_sort_directories_first_changed;
+ nautilus_files_view_class->widget_to_file_operation_position = nautilus_canvas_view_widget_to_file_operation_position;
+ nautilus_files_view_class->get_view_id = nautilus_canvas_view_get_id;
+ nautilus_files_view_class->get_first_visible_file = canvas_view_get_first_visible_file;
+ nautilus_files_view_class->scroll_to_file = canvas_view_scroll_to_file;
+ nautilus_files_view_class->reveal_for_selection_context_menu = nautilus_canvas_view_reveal_for_selection_context_menu;
+ nautilus_files_view_class->preview_selection_event = nautilus_canvas_view_preview_selection_event;
+}
+
+static void
+nautilus_canvas_view_init (NautilusCanvasView *canvas_view)
+{
+ NautilusCanvasContainer *canvas_container;
+ GActionGroup *view_action_group;
+ GtkClipboard *clipboard;
+
+ canvas_view->sort = &sort_criteria[0];
+ canvas_view->destroyed = FALSE;
+
+ canvas_container = nautilus_canvas_view_container_new (canvas_view);
+ initialize_canvas_container (canvas_view, canvas_container);
+
+ g_signal_connect_swapped (nautilus_preferences,
+ "changed::" NAUTILUS_PREFERENCES_DEFAULT_SORT_ORDER,
+ G_CALLBACK (default_sort_order_changed_callback),
+ canvas_view);
+ g_signal_connect_swapped (nautilus_preferences,
+ "changed::" NAUTILUS_PREFERENCES_DEFAULT_SORT_IN_REVERSE_ORDER,
+ G_CALLBACK (default_sort_order_changed_callback),
+ canvas_view);
+ g_signal_connect_swapped (nautilus_preferences,
+ "changed::" NAUTILUS_PREFERENCES_SHOW_FILE_THUMBNAILS,
+ G_CALLBACK (image_display_policy_changed_callback),
+ canvas_view);
+
+ g_signal_connect_swapped (nautilus_icon_view_preferences,
+ "changed::" NAUTILUS_PREFERENCES_ICON_VIEW_CAPTIONS,
+ G_CALLBACK (text_attribute_names_changed_callback),
+ canvas_view);
+
+ g_signal_connect_object (canvas_container, "handle-uri-list",
+ G_CALLBACK (canvas_view_handle_uri_list), canvas_view, 0);
+ g_signal_connect_object (canvas_container, "handle-netscape-url",
+ G_CALLBACK (canvas_view_handle_netscape_url), canvas_view, 0);
+ g_signal_connect_object (canvas_container, "handle-text",
+ G_CALLBACK (canvas_view_handle_text), canvas_view, 0);
+ g_signal_connect_object (canvas_container, "handle-raw",
+ G_CALLBACK (canvas_view_handle_raw), canvas_view, 0);
+ g_signal_connect_object (canvas_container, "handle-hover",
+ G_CALLBACK (canvas_view_handle_hover), canvas_view, 0);
+
+ /* React to clipboard changes */
+ clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+ g_signal_connect (clipboard, "owner-change",
+ G_CALLBACK (on_clipboard_owner_changed), canvas_view);
+
+ view_action_group = nautilus_files_view_get_action_group (NAUTILUS_FILES_VIEW (canvas_view));
+ g_action_map_add_action_entries (G_ACTION_MAP (view_action_group),
+ canvas_view_entries,
+ G_N_ELEMENTS (canvas_view_entries),
+ canvas_view);
+ /* 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 (canvas_view)),
+ "zoom-to-level", g_variant_new_int32 (get_default_zoom_level (canvas_view)));
+}
+
+NautilusFilesView *
+nautilus_canvas_view_new (NautilusWindowSlot *slot)
+{
+ return g_object_new (NAUTILUS_TYPE_CANVAS_VIEW,
+ "window-slot", slot,
+ NULL);
+}
diff --git a/src/nautilus-canvas-view.h b/src/nautilus-canvas-view.h
new file mode 100644
index 0000000..4e8f1ac
--- /dev/null
+++ b/src/nautilus-canvas-view.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* nautilus-canvas-view.h - interface for canvas view of directory.
+ *
+ * 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 "nautilus-files-view.h"
+
+#include "nautilus-types.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_CANVAS_VIEW nautilus_canvas_view_get_type()
+
+G_DECLARE_FINAL_TYPE (NautilusCanvasView, nautilus_canvas_view, NAUTILUS, CANVAS_VIEW, NautilusFilesView)
+
+int nautilus_canvas_view_compare_files (NautilusCanvasView *canvas_view,
+ NautilusFile *a,
+ NautilusFile *b);
+
+NautilusFilesView * nautilus_canvas_view_new (NautilusWindowSlot *slot);
+
+NautilusCanvasContainer * nautilus_canvas_view_get_canvas_container (NautilusCanvasView *view);
+
+G_END_DECLS
diff --git a/src/nautilus-clipboard.c b/src/nautilus-clipboard.c
new file mode 100644
index 0000000..fb7e96d
--- /dev/null
+++ b/src/nautilus-clipboard.c
@@ -0,0 +1,329 @@
+/* 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>
+
+typedef struct
+{
+ gboolean cut;
+ GList *files;
+} ClipboardInfo;
+
+static GList *
+convert_selection_data_to_str_list (const gchar *data)
+{
+ g_auto (GStrv) lines = NULL;
+ guint number_of_lines;
+ GList *result;
+
+ lines = g_strsplit (data, "\n", 0);
+ number_of_lines = g_strv_length (lines);
+ if (number_of_lines == 0)
+ {
+ /* An empty string will result in g_strsplit() returning an empty
+ * array, so, naturally, 0 - 1 = UINT_MAX and we read all sorts
+ * of invalid memory.
+ */
+ return NULL;
+ }
+ result = NULL;
+
+ /* Also, this skips the last line, since it would be an
+ * empty string from the split */
+ for (guint i = 0; i < number_of_lines - 1; i++)
+ {
+ result = g_list_prepend (result, g_strdup (lines[i]));
+ }
+
+ return g_list_reverse (result);
+}
+
+static char *
+convert_file_list_to_string (ClipboardInfo *info,
+ gboolean format_for_text,
+ gsize *len)
+{
+ GString *uris;
+ char *uri, *tmp;
+ GFile *f;
+ guint i;
+ GList *l;
+
+ if (format_for_text)
+ {
+ uris = g_string_new (NULL);
+ }
+ else
+ {
+ uris = g_string_new ("x-special/nautilus-clipboard\n");
+ g_string_append (uris, info->cut ? "cut\n" : "copy\n");
+ }
+
+ for (i = 0, l = info->files; l != NULL; l = l->next, i++)
+ {
+ uri = nautilus_file_get_uri (l->data);
+
+ if (format_for_text)
+ {
+ f = g_file_new_for_uri (uri);
+ tmp = g_file_get_parse_name (f);
+ g_object_unref (f);
+
+ if (tmp != NULL)
+ {
+ g_string_append (uris, tmp);
+ g_free (tmp);
+ }
+ else
+ {
+ g_string_append (uris, uri);
+ }
+
+ g_string_append_c (uris, '\n');
+ }
+ else
+ {
+ g_string_append (uris, uri);
+ g_string_append_c (uris, '\n');
+ }
+
+ g_free (uri);
+ }
+
+ *len = uris->len;
+ return g_string_free (uris, FALSE);
+}
+
+static GList *
+get_item_list_from_selection_data (const gchar *selection_data)
+{
+ GList *items = NULL;
+
+ if (selection_data != NULL)
+ {
+ gboolean valid_data = TRUE;
+ /* Not sure why it's legal to assume there's an extra byte
+ * past the end of the selection data that it's safe to write
+ * to. But gtk_editable_selection_received does this, so I
+ * think it is OK.
+ */
+ items = convert_selection_data_to_str_list (selection_data);
+ if (items == NULL || g_strcmp0 (items->data, "x-special/nautilus-clipboard") != 0)
+ {
+ valid_data = FALSE;
+ }
+ else if (items->next == NULL)
+ {
+ valid_data = FALSE;
+ }
+ else if (g_strcmp0 (items->next->data, "cut") != 0 &&
+ g_strcmp0 (items->next->data, "copy") != 0)
+ {
+ valid_data = FALSE;
+ }
+
+ if (!valid_data)
+ {
+ g_list_free_full (items, g_free);
+ items = NULL;
+ }
+ }
+
+ return items;
+}
+
+gboolean
+nautilus_clipboard_is_data_valid_from_selection_data (const gchar *selection_data)
+{
+ return nautilus_clipboard_get_uri_list_from_selection_data (selection_data) != NULL;
+}
+
+GList *
+nautilus_clipboard_get_uri_list_from_selection_data (const gchar *selection_data)
+{
+ GList *items;
+
+ items = get_item_list_from_selection_data (selection_data);
+ if (items)
+ {
+ /* Line 0 is x-special/nautilus-clipboard. */
+ items = g_list_remove (items, items->data);
+ /* Line 1 is "cut" or "copy", so uris start at line 2. */
+ items = g_list_remove (items, items->data);
+ }
+
+ return items;
+}
+
+GtkClipboard *
+nautilus_clipboard_get (GtkWidget *widget)
+{
+ return gtk_clipboard_get_for_display (gtk_widget_get_display (GTK_WIDGET (widget)),
+ GDK_SELECTION_CLIPBOARD);
+}
+
+void
+nautilus_clipboard_clear_if_colliding_uris (GtkWidget *widget,
+ const GList *item_uris)
+{
+ g_autofree gchar *data = NULL;
+ GList *clipboard_item_uris, *l;
+ gboolean collision;
+
+ collision = FALSE;
+ data = gtk_clipboard_wait_for_text (nautilus_clipboard_get (widget));
+ 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 (nautilus_clipboard_get (widget));
+ }
+
+ if (clipboard_item_uris)
+ {
+ g_list_free_full (clipboard_item_uris, g_free);
+ }
+}
+
+gboolean
+nautilus_clipboard_is_cut_from_selection_data (const gchar *selection_data)
+{
+ GList *items;
+ gboolean is_cut_from_selection_data;
+
+ items = get_item_list_from_selection_data (selection_data);
+ is_cut_from_selection_data = items != NULL &&
+ g_strcmp0 ((gchar *) items->next->data, "cut") == 0;
+
+ g_list_free_full (items, g_free);
+
+ return is_cut_from_selection_data;
+}
+
+static void
+on_get_clipboard (GtkClipboard *clipboard,
+ GtkSelectionData *selection_data,
+ guint info,
+ gpointer user_data)
+{
+ char **uris;
+ GList *l;
+ int i;
+ ClipboardInfo *clipboard_info;
+ GdkAtom target;
+
+ clipboard_info = (ClipboardInfo *) user_data;
+
+ target = gtk_selection_data_get_target (selection_data);
+
+ if (gtk_targets_include_uri (&target, 1))
+ {
+ uris = g_malloc ((g_list_length (clipboard_info->files) + 1) * sizeof (char *));
+ i = 0;
+
+ for (l = clipboard_info->files; l != NULL; l = l->next)
+ {
+ uris[i] = nautilus_file_get_uri (l->data);
+ i++;
+ }
+
+ uris[i] = NULL;
+
+ gtk_selection_data_set_uris (selection_data, uris);
+
+ g_strfreev (uris);
+ }
+ else if (gtk_targets_include_text (&target, 1))
+ {
+ char *str;
+ gsize len;
+
+ str = convert_file_list_to_string (clipboard_info, FALSE, &len);
+ gtk_selection_data_set_text (selection_data, str, len);
+ g_free (str);
+ }
+}
+
+static void
+on_clear_clipboard (GtkClipboard *clipboard,
+ gpointer user_data)
+{
+ ClipboardInfo *clipboard_info = (ClipboardInfo *) user_data;
+
+ nautilus_file_list_free (clipboard_info->files);
+
+ g_free (clipboard_info);
+}
+
+void
+nautilus_clipboard_prepare_for_files (GtkClipboard *clipboard,
+ GList *files,
+ gboolean cut)
+{
+ GtkTargetList *target_list;
+ GtkTargetEntry *targets;
+ int n_targets;
+ ClipboardInfo *clipboard_info;
+
+ clipboard_info = g_new (ClipboardInfo, 1);
+ clipboard_info->cut = cut;
+ clipboard_info->files = nautilus_file_list_copy (files);
+
+ target_list = gtk_target_list_new (NULL, 0);
+ gtk_target_list_add_uri_targets (target_list, 0);
+ gtk_target_list_add_text_targets (target_list, 0);
+
+ targets = gtk_target_table_new_from_list (target_list, &n_targets);
+ gtk_target_list_unref (target_list);
+
+ gtk_clipboard_set_with_data (clipboard,
+ targets, n_targets,
+ on_get_clipboard, on_clear_clipboard,
+ clipboard_info);
+ gtk_target_table_free (targets, n_targets);
+}
diff --git a/src/nautilus-clipboard.h b/src/nautilus-clipboard.h
new file mode 100644
index 0000000..61810c1
--- /dev/null
+++ b/src/nautilus-clipboard.h
@@ -0,0 +1,36 @@
+
+/* 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>
+
+void nautilus_clipboard_clear_if_colliding_uris (GtkWidget *widget,
+ const GList *item_uris);
+GtkClipboard* nautilus_clipboard_get (GtkWidget *widget);
+GList* nautilus_clipboard_get_uri_list_from_selection_data (const gchar *selection_data);
+gboolean nautilus_clipboard_is_cut_from_selection_data (const gchar *selection_data);
+void nautilus_clipboard_prepare_for_files (GtkClipboard *clipboard,
+ GList *files,
+ gboolean cut);
+GdkAtom nautilus_clipboard_get_atom (void);
+gboolean nautilus_clipboard_is_data_valid_from_selection_data (const gchar *selection_data);
diff --git a/src/nautilus-column-chooser.c b/src/nautilus-column-chooser.c
new file mode 100644
index 0000000..43d95c6
--- /dev/null
+++ b/src/nautilus-column-chooser.c
@@ -0,0 +1,714 @@
+/* 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;
+
+ GtkTreeView *view;
+ GtkListStore *store;
+
+ GtkWidget *main_box;
+ 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_constructed (GObject *object);
+
+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
+nautilus_column_chooser_class_init (NautilusColumnChooserClass *chooser_class)
+{
+ GObjectClass *oclass;
+
+ oclass = G_OBJECT_CLASS (chooser_class);
+
+ oclass->set_property = nautilus_column_chooser_set_property;
+ oclass->constructed = nautilus_column_chooser_constructed;
+
+ 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
+update_buttons (NautilusColumnChooser *chooser)
+{
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+
+ selection = gtk_tree_view_get_selection (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;
+
+ 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, -1);
+ 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);
+static void move_down_clicked_callback (GtkWidget *button,
+ gpointer user_data);
+
+static void
+add_tree_view (NautilusColumnChooser *chooser)
+{
+ GtkWidget *scrolled;
+ GtkWidget *view;
+ GtkListStore *store;
+ GtkCellRenderer *cell;
+ GtkTreeSelection *selection;
+
+ view = gtk_tree_view_new ();
+ gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (view), FALSE);
+
+ store = gtk_list_store_new (NUM_COLUMNS,
+ G_TYPE_BOOLEAN,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_BOOLEAN);
+
+ gtk_tree_view_set_model (GTK_TREE_VIEW (view),
+ GTK_TREE_MODEL (store));
+ g_object_unref (store);
+
+ gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view), TRUE);
+
+ g_signal_connect (view, "row-activated",
+ G_CALLBACK (view_row_activated_callback), chooser);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
+ g_signal_connect (selection, "changed",
+ G_CALLBACK (selection_changed_callback), chooser);
+
+ cell = gtk_cell_renderer_toggle_new ();
+
+ g_signal_connect (G_OBJECT (cell), "toggled",
+ G_CALLBACK (visible_toggled_callback), chooser);
+
+ gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view),
+ -1, NULL,
+ cell,
+ "active", COLUMN_VISIBLE,
+ "sensitive", COLUMN_SENSITIVE,
+ NULL);
+
+ cell = gtk_cell_renderer_text_new ();
+
+ gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view),
+ -1, NULL,
+ cell,
+ "text", COLUMN_LABEL,
+ "sensitive", COLUMN_SENSITIVE,
+ NULL);
+
+ chooser->view = GTK_TREE_VIEW (view);
+ chooser->store = store;
+
+ gtk_widget_show (view);
+
+ scrolled = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled),
+ GTK_SHADOW_IN);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_widget_show (GTK_WIDGET (scrolled));
+
+ gtk_container_add (GTK_CONTAINER (scrolled), view);
+ gtk_box_pack_start (GTK_BOX (chooser->main_box), scrolled, TRUE, TRUE, 0);
+}
+
+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 (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 (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
+add_buttons (NautilusColumnChooser *chooser)
+{
+ GtkWidget *inline_toolbar;
+ GtkStyleContext *style_context;
+ GtkToolItem *tool_item;
+ GtkWidget *box;
+
+ inline_toolbar = gtk_toolbar_new ();
+ gtk_widget_show (GTK_WIDGET (inline_toolbar));
+
+ style_context = gtk_widget_get_style_context (GTK_WIDGET (inline_toolbar));
+ gtk_style_context_add_class (style_context, GTK_STYLE_CLASS_INLINE_TOOLBAR);
+ gtk_box_pack_start (GTK_BOX (chooser->main_box), inline_toolbar,
+ FALSE, FALSE, 0);
+
+ box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ tool_item = gtk_tool_item_new ();
+ gtk_container_add (GTK_CONTAINER (tool_item), box);
+ gtk_container_add (GTK_CONTAINER (inline_toolbar), GTK_WIDGET (tool_item));
+
+ chooser->move_up_button = gtk_button_new_from_icon_name ("go-up-symbolic",
+ GTK_ICON_SIZE_SMALL_TOOLBAR);
+ g_signal_connect (chooser->move_up_button,
+ "clicked", G_CALLBACK (move_up_clicked_callback),
+ chooser);
+ gtk_widget_set_sensitive (chooser->move_up_button, FALSE);
+ gtk_container_add (GTK_CONTAINER (box), chooser->move_up_button);
+
+ chooser->move_down_button = gtk_button_new_from_icon_name ("go-down-symbolic",
+ GTK_ICON_SIZE_SMALL_TOOLBAR);
+ g_signal_connect (chooser->move_down_button,
+ "clicked", G_CALLBACK (move_down_clicked_callback),
+ chooser);
+ gtk_widget_set_sensitive (chooser->move_down_button, FALSE);
+ gtk_container_add (GTK_CONTAINER (box), chooser->move_down_button);
+
+ tool_item = gtk_separator_tool_item_new ();
+ gtk_separator_tool_item_set_draw (GTK_SEPARATOR_TOOL_ITEM (tool_item), FALSE);
+ gtk_tool_item_set_expand (tool_item, TRUE);
+ gtk_container_add (GTK_CONTAINER (inline_toolbar), GTK_WIDGET (tool_item));
+
+ box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ tool_item = gtk_tool_item_new ();
+ gtk_container_add (GTK_CONTAINER (tool_item), box);
+ gtk_container_add (GTK_CONTAINER (inline_toolbar), GTK_WIDGET (tool_item));
+
+ chooser->use_default_button = gtk_button_new_with_mnemonic (_("Reset to De_fault"));
+ gtk_widget_set_tooltip_text (chooser->use_default_button,
+ _("Replace the current List Columns settings with the default settings"));
+ g_signal_connect (chooser->use_default_button,
+ "clicked", G_CALLBACK (use_default_clicked_callback),
+ chooser);
+ gtk_container_add (GTK_CONTAINER (box), chooser->use_default_button);
+
+ gtk_widget_show_all (inline_toolbar);
+}
+
+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;
+ }
+
+ 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
+nautilus_column_chooser_init (NautilusColumnChooser *chooser)
+{
+ g_object_set (G_OBJECT (chooser),
+ "homogeneous", FALSE,
+ "spacing", 8,
+ "orientation", GTK_ORIENTATION_HORIZONTAL,
+ NULL);
+
+ chooser->main_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ gtk_widget_set_hexpand (chooser->main_box, TRUE);
+ gtk_widget_show (chooser->main_box);
+ gtk_container_add (GTK_CONTAINER (chooser), chooser->main_box);
+
+ add_tree_view (chooser);
+ add_buttons (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);
+}
+
+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..7f43b00
--- /dev/null
+++ b/src/nautilus-column-utilities.c
@@ -0,0 +1,406 @@
+/* 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",
+ "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", "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..d8aa792
--- /dev/null
+++ b/src/nautilus-compress-dialog-controller.c
@@ -0,0 +1,350 @@
+/* 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 <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 *description_stack;
+ GtkWidget *name_entry;
+ GtkWidget *zip_radio_button;
+ GtkWidget *tar_xz_radio_button;
+ GtkWidget *seven_zip_radio_button;
+
+ const char *extension;
+
+ gulong response_handler_id;
+};
+
+G_DEFINE_TYPE (NautilusCompressDialogController, nautilus_compress_dialog_controller, NAUTILUS_TYPE_FILE_NAME_WIDGET_CONTROLLER);
+
+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,
+ NautilusCompressionFormat format)
+{
+ const char *extension;
+ const char *description_label_name;
+ GtkWidget *active_button;
+
+ switch (format)
+ {
+ case NAUTILUS_COMPRESSION_ZIP:
+ {
+ extension = ".zip";
+ description_label_name = "zip-description-label";
+ active_button = self->zip_radio_button;
+ }
+ break;
+
+ case NAUTILUS_COMPRESSION_TAR_XZ:
+ {
+ extension = ".tar.xz";
+ description_label_name = "tar-xz-description-label";
+ active_button = self->tar_xz_radio_button;
+ }
+ break;
+
+ case NAUTILUS_COMPRESSION_7ZIP:
+ {
+ extension = ".7z";
+ description_label_name = "seven-zip-description-label";
+ active_button = self->seven_zip_radio_button;
+ }
+ break;
+
+ default:
+ {
+ g_assert_not_reached ();
+ }
+ break;
+ }
+
+ self->extension = extension;
+
+ gtk_stack_set_visible_child_name (GTK_STACK (self->description_stack),
+ description_label_name);
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (active_button),
+ TRUE);
+
+ g_settings_set_enum (nautilus_compression_preferences,
+ NAUTILUS_PREFERENCES_DEFAULT_COMPRESSION_FORMAT,
+ format);
+ /* Since the extension changes when the button is toggled, force a
+ * verification of the new file name by simulating an entry change
+ */
+ g_signal_emit_by_name (self->name_entry, "changed");
+}
+
+static void
+zip_radio_button_on_toggled (GtkToggleButton *toggle_button,
+ gpointer user_data)
+{
+ NautilusCompressDialogController *controller;
+
+ controller = NAUTILUS_COMPRESS_DIALOG_CONTROLLER (user_data);
+
+ if (!gtk_toggle_button_get_active (toggle_button))
+ {
+ return;
+ }
+
+ update_selected_format (controller,
+ NAUTILUS_COMPRESSION_ZIP);
+}
+
+static void
+tar_xz_radio_button_on_toggled (GtkToggleButton *toggle_button,
+ gpointer user_data)
+{
+ NautilusCompressDialogController *controller;
+
+ controller = NAUTILUS_COMPRESS_DIALOG_CONTROLLER (user_data);
+
+ if (!gtk_toggle_button_get_active (toggle_button))
+ {
+ return;
+ }
+
+ update_selected_format (controller,
+ NAUTILUS_COMPRESSION_TAR_XZ);
+}
+
+static void
+seven_zip_radio_button_on_toggled (GtkToggleButton *toggle_button,
+ gpointer user_data)
+{
+ NautilusCompressDialogController *controller;
+
+ controller = NAUTILUS_COMPRESS_DIALOG_CONTROLLER (user_data);
+
+ if (!gtk_toggle_button_get_active (toggle_button))
+ {
+ return;
+ }
+
+ update_selected_format (controller,
+ NAUTILUS_COMPRESSION_7ZIP);
+}
+
+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 *description_stack;
+ GtkWidget *zip_radio_button;
+ GtkWidget *tar_xz_radio_button;
+ GtkWidget *seven_zip_radio_button;
+ NautilusCompressionFormat format;
+
+ 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"));
+ zip_radio_button = GTK_WIDGET (gtk_builder_get_object (builder, "zip_radio_button"));
+ tar_xz_radio_button = GTK_WIDGET (gtk_builder_get_object (builder, "tar_xz_radio_button"));
+ seven_zip_radio_button = GTK_WIDGET (gtk_builder_get_object (builder, "seven_zip_radio_button"));
+ description_stack = GTK_WIDGET (gtk_builder_get_object (builder, "description_stack"));
+
+ 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->zip_radio_button = zip_radio_button;
+ self->tar_xz_radio_button = tar_xz_radio_button;
+ self->seven_zip_radio_button = seven_zip_radio_button;
+ self->description_stack = description_stack;
+ self->name_entry = name_entry;
+
+ self->response_handler_id = g_signal_connect (compress_dialog,
+ "response",
+ (GCallback) compress_dialog_controller_on_response,
+ self);
+
+ gtk_builder_add_callback_symbols (builder,
+ "zip_radio_button_on_toggled",
+ G_CALLBACK (zip_radio_button_on_toggled),
+ "tar_xz_radio_button_on_toggled",
+ G_CALLBACK (tar_xz_radio_button_on_toggled),
+ "seven_zip_radio_button_on_toggled",
+ G_CALLBACK (seven_zip_radio_button_on_toggled),
+ NULL);
+ gtk_builder_connect_signals (builder, self);
+
+ format = g_settings_get_enum (nautilus_compression_preferences,
+ NAUTILUS_PREFERENCES_DEFAULT_COMPRESSION_FORMAT);
+
+ update_selected_format (self, format);
+
+ if (initial_name != NULL)
+ {
+ gtk_entry_set_text (GTK_ENTRY (name_entry), initial_name);
+ }
+
+ gtk_widget_show_all (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_widget_destroy (self->compress_dialog);
+ self->compress_dialog = NULL;
+ }
+
+ 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;
+}
diff --git a/src/nautilus-compress-dialog-controller.h b/src/nautilus-compress-dialog-controller.h
new file mode 100644
index 0000000..2421b81
--- /dev/null
+++ b/src/nautilus-compress-dialog-controller.h
@@ -0,0 +1,33 @@
+/* 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);
diff --git a/src/nautilus-container-max-width.c b/src/nautilus-container-max-width.c
new file mode 100644
index 0000000..ef6fb87
--- /dev/null
+++ b/src/nautilus-container-max-width.c
@@ -0,0 +1,301 @@
+#include "nautilus-container-max-width.h"
+
+#define DEFAULT_MAX_SIZE 120
+
+struct _NautilusContainerMaxWidth
+{
+ GtkBin parent_instance;
+ guint max_width;
+
+ gboolean width_maximized;
+ guint change_width_maximized_idle_id;
+};
+
+G_DEFINE_TYPE (NautilusContainerMaxWidth, nautilus_container_max_width, GTK_TYPE_BIN)
+
+enum
+{
+ PROP_0,
+ PROP_MAX_WIDTH,
+ PROP_WIDTH_MAXIMIZED,
+ N_PROPS
+};
+
+void
+nautilus_container_max_width_set_max_width (NautilusContainerMaxWidth *self,
+ guint max_width)
+{
+ self->max_width = max_width;
+ gtk_widget_queue_allocate (GTK_WIDGET (self));
+}
+
+guint
+nautilus_container_max_width_get_max_width (NautilusContainerMaxWidth *self)
+{
+ return self->max_width;
+}
+
+gboolean
+nautilus_container_max_width_get_width_maximized (NautilusContainerMaxWidth *self)
+{
+ return self->width_maximized;
+}
+
+NautilusContainerMaxWidth *
+nautilus_container_max_width_new (void)
+{
+ return g_object_new (NAUTILUS_TYPE_CONTAINER_MAX_WIDTH, NULL);
+}
+
+static void
+nautilus_container_max_width_finalize (GObject *object)
+{
+ NautilusContainerMaxWidth *self = NAUTILUS_CONTAINER_MAX_WIDTH (object);
+
+ if (self->change_width_maximized_idle_id != 0)
+ {
+ g_source_remove (self->change_width_maximized_idle_id);
+ }
+
+ G_OBJECT_CLASS (nautilus_container_max_width_parent_class)->finalize (object);
+}
+
+static void
+nautilus_container_max_width_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusContainerMaxWidth *self = NAUTILUS_CONTAINER_MAX_WIDTH (object);
+
+ switch (prop_id)
+ {
+ case PROP_MAX_WIDTH:
+ {
+ g_value_set_int (value, self->max_width);
+ }
+ break;
+
+ case PROP_WIDTH_MAXIMIZED:
+ {
+ g_value_set_boolean (value, self->width_maximized);
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ break;
+ }
+}
+
+static void
+nautilus_container_max_width_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusContainerMaxWidth *self = NAUTILUS_CONTAINER_MAX_WIDTH (object);
+
+ switch (prop_id)
+ {
+ case PROP_MAX_WIDTH:
+ {
+ nautilus_container_max_width_set_max_width (self, g_value_get_int (value));
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ break;
+ }
+}
+
+static void
+get_preferred_width (GtkWidget *widget,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ GtkWidget *child;
+ NautilusContainerMaxWidth *self;
+ GtkStyleContext *style_context;
+ GtkBorder padding;
+
+ self = NAUTILUS_CONTAINER_MAX_WIDTH (widget);
+ child = gtk_bin_get_child (GTK_BIN (self));
+
+ *natural_size = 0;
+ *minimum_size = 0;
+ gtk_widget_get_preferred_width (child, minimum_size, natural_size);
+
+ if (self->max_width != -1 && *minimum_size > self->max_width)
+ {
+ g_critical ("NautilusContainerMaxWidth's child requested %d while set maximum width is %d",
+ *minimum_size, self->max_width);
+ }
+ *natural_size = self->max_width == -1 ? *natural_size :
+ MAX (*minimum_size, MIN (self->max_width, *natural_size));
+
+ style_context = gtk_widget_get_style_context (widget);
+ gtk_style_context_get_padding (style_context,
+ gtk_widget_get_state_flags (widget),
+ &padding);
+ *minimum_size += padding.left + padding.right;
+ *natural_size += padding.left + padding.right;
+}
+
+static void
+get_preferred_height (GtkWidget *widget,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ GtkWidget *child;
+ NautilusContainerMaxWidth *self;
+ gint minimum_width = 0;
+ gint natural_width = 0;
+ GtkStyleContext *style_context;
+ GtkBorder padding;
+
+ self = NAUTILUS_CONTAINER_MAX_WIDTH (widget);
+ child = gtk_bin_get_child (GTK_BIN (self));
+
+ get_preferred_width (widget, &minimum_width, &natural_width);
+ natural_width = self->max_width == -1 ? natural_width : MIN (self->max_width, natural_width);
+
+ gtk_widget_get_preferred_height_for_width (child, natural_width, minimum_size, natural_size);
+
+ style_context = gtk_widget_get_style_context (widget);
+ gtk_style_context_get_padding (style_context,
+ gtk_widget_get_state_flags (widget),
+ &padding);
+ *minimum_size += padding.top + padding.bottom;
+ *natural_size += padding.top + padding.bottom;
+}
+
+static void
+get_preferred_height_for_width (GtkWidget *widget,
+ gint width,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ get_preferred_height (widget, minimum_size, natural_size);
+}
+
+static void
+size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ GTK_WIDGET_CLASS (nautilus_container_max_width_parent_class)->size_allocate (widget, allocation);
+}
+
+static void
+get_preferred_width_for_height (GtkWidget *widget,
+ gint height,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ get_preferred_width (widget, minimum_size, natural_size);
+}
+
+static gboolean
+change_width_maximized_idle_callback (gpointer userdata)
+{
+ NautilusContainerMaxWidth *self = userdata;
+
+ self->change_width_maximized_idle_id = 0;
+
+ self->width_maximized = !self->width_maximized;
+ g_object_notify (G_OBJECT (self), "width-maximized");
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+on_size_allocate (GtkWidget *widget,
+ GdkRectangle *allocation,
+ gpointer userdata)
+{
+ NautilusContainerMaxWidth *self = NAUTILUS_CONTAINER_MAX_WIDTH (widget);
+ gboolean is_width_maximized;
+
+ is_width_maximized = self->max_width == -1 ? FALSE : allocation->width >= self->max_width;
+
+ if (self->width_maximized != is_width_maximized)
+ {
+ /* The handlers of the "notify::width-maximized" signal may trigger
+ * a reallocation, which shouldn't happen at this point because we are
+ * still in size_allocate phase. So, change the property on idle*/
+ if (self->change_width_maximized_idle_id == 0)
+ {
+ self->change_width_maximized_idle_id = g_idle_add (change_width_maximized_idle_callback, self);
+ }
+ }
+ else if (self->change_width_maximized_idle_id != 0)
+ {
+ /* This was going to change self->width_maximized, let's cancel it. */
+ g_source_remove (self->change_width_maximized_idle_id);
+ self->change_width_maximized_idle_id = 0;
+ }
+}
+
+static void
+constructed (GObject *obj)
+{
+ NautilusContainerMaxWidth *self = NAUTILUS_CONTAINER_MAX_WIDTH (obj);
+
+ G_OBJECT_CLASS (nautilus_container_max_width_parent_class)->constructed (obj);
+
+ /* We want our parent to gives our preferred width */
+ gtk_widget_set_halign (GTK_WIDGET (self), GTK_ALIGN_CENTER);
+ self->max_width = -1;
+
+ /* We want to know when the container has grown to its max width */
+ self->width_maximized = FALSE;
+ g_signal_connect (GTK_WIDGET (self),
+ "size-allocate",
+ G_CALLBACK (on_size_allocate),
+ NULL);
+}
+
+static void
+nautilus_container_max_width_class_init (NautilusContainerMaxWidthClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = nautilus_container_max_width_finalize;
+ object_class->get_property = nautilus_container_max_width_get_property;
+ object_class->set_property = nautilus_container_max_width_set_property;
+ object_class->constructed = constructed;
+
+ widget_class->get_preferred_width = get_preferred_width;
+ widget_class->get_preferred_width_for_height = get_preferred_width_for_height;
+ widget_class->get_preferred_height = get_preferred_height;
+ widget_class->get_preferred_height_for_width = get_preferred_height_for_width;
+ widget_class->size_allocate = size_allocate;
+
+ g_object_class_install_property (object_class,
+ PROP_MAX_WIDTH,
+ g_param_spec_int ("max-width",
+ "Max width",
+ "The max width of the container",
+ G_MININT,
+ G_MAXINT,
+ 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_WIDTH_MAXIMIZED,
+ g_param_spec_boolean ("width-maximized",
+ "Width maximized",
+ "Whether the container is at the max width",
+ FALSE,
+ G_PARAM_READABLE));
+}
+static void
+nautilus_container_max_width_init (NautilusContainerMaxWidth *self)
+{
+}
diff --git a/src/nautilus-container-max-width.h b/src/nautilus-container-max-width.h
new file mode 100644
index 0000000..3373416
--- /dev/null
+++ b/src/nautilus-container-max-width.h
@@ -0,0 +1,19 @@
+#pragma once
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_CONTAINER_MAX_WIDTH (nautilus_container_max_width_get_type())
+
+G_DECLARE_FINAL_TYPE (NautilusContainerMaxWidth, nautilus_container_max_width, NAUTILUS, CONTAINER_MAX_WIDTH, GtkBin)
+
+NautilusContainerMaxWidth *nautilus_container_max_width_new (void);
+
+void nautilus_container_max_width_set_max_width (NautilusContainerMaxWidth *self,
+ guint max_width);
+guint nautilus_container_max_width_get_max_width (NautilusContainerMaxWidth *self);
+gboolean nautilus_container_max_width_get_width_maximized (NautilusContainerMaxWidth *self);
+
+G_END_DECLS
diff --git a/src/nautilus-dbus-manager.c b/src/nautilus-dbus-manager.c
new file mode 100644
index 0000000..6736205
--- /dev/null
+++ b/src/nautilus-dbus-manager.c
@@ -0,0 +1,655 @@
+/*
+ * 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);
+ 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..59093f9
--- /dev/null
+++ b/src/nautilus-debug.c
@@ -0,0 +1,181 @@
+/*
+ * 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 },
+ { "CanvasContainer", NAUTILUS_DEBUG_CANVAS_CONTAINER },
+ { "IconView", NAUTILUS_DEBUG_CANVAS_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..4d890e6
--- /dev/null
+++ b/src/nautilus-debug.h
@@ -0,0 +1,79 @@
+/*
+ * 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_CANVAS_CONTAINER = 1 << 7,
+ NAUTILUS_DEBUG_CANVAS_VIEW = 1 << 8,
+ NAUTILUS_DEBUG_LIST_VIEW = 1 << 9,
+ NAUTILUS_DEBUG_MIME = 1 << 10,
+ NAUTILUS_DEBUG_PLACES = 1 << 11,
+ NAUTILUS_DEBUG_PREVIEWER = 1 << 12,
+ NAUTILUS_DEBUG_SMCLIENT = 1 << 13,
+ NAUTILUS_DEBUG_WINDOW = 1 << 14,
+ NAUTILUS_DEBUG_UNDO = 1 << 15,
+ NAUTILUS_DEBUG_SEARCH = 1 << 16,
+ NAUTILUS_DEBUG_SEARCH_HIT = 1 << 17,
+ NAUTILUS_DEBUG_THUMBNAILS = 1 << 18,
+ NAUTILUS_DEBUG_TAG_MANAGER = 1 << 19,
+} 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..d5b93b5
--- /dev/null
+++ b/src/nautilus-directory-async.c
@@ -0,0 +1,4740 @@
+/*
+ * 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 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;
+ }
+
+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 (file->details->scaled_thumbnail)
+ {
+ g_object_unref (file->details->scaled_thumbnail);
+ file->details->scaled_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_CANVAS_ICON_SIZE_LARGEST * NAUTILUS_CANVAS_ICON_SIZE_STANDARD / NAUTILUS_CANVAS_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..8f1a235
--- /dev/null
+++ b/src/nautilus-directory-private.h
@@ -0,0 +1,224 @@
+/*
+ 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 * */
+
+ 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..3b1556a
--- /dev/null
+++ b/src/nautilus-directory.c
@@ -0,0 +1,2066 @@
+/*
+ * 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_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;
+}
+
+gboolean
+nautilus_directory_is_local (NautilusDirectory *directory)
+{
+ g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), FALSE);
+
+ if (directory->details->location == NULL)
+ {
+ return TRUE;
+ }
+
+ return nautilus_directory_is_in_trash (directory) ||
+ nautilus_directory_is_in_recent (directory) ||
+ g_file_is_native (directory->details->location);
+}
+
+gboolean
+nautilus_directory_is_local_or_fuse (NautilusDirectory *directory)
+{
+ g_autofree char *path = NULL;
+
+ g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), FALSE);
+
+ if (directory->details->location == NULL)
+ {
+ return TRUE;
+ }
+
+ /* If the glib reports a path, then it can use FUSE to convert the uri
+ * to a local path
+ */
+ path = g_file_get_path (directory->details->location);
+
+ return nautilus_directory_is_in_trash (directory) ||
+ nautilus_directory_is_in_recent (directory) ||
+ g_file_is_native (directory->details->location) ||
+ 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);
+ nautilus_file_unref (file);
+ }
+ else
+ {
+ hash_table_list_prepend (added_lists,
+ directory,
+ g_object_ref (location));
+ }
+ 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;
+ 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);
+ }
+ }
+
+ /* 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);
+
+ while (g_hash_table_size (directories) != 0)
+ {
+ gtk_main_iteration ();
+ }
+
+ 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);
+
+ while (!got_files_flag)
+ {
+ gtk_main_iteration ();
+ }
+
+ 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..774514b
--- /dev/null
+++ b/src/nautilus-directory.h
@@ -0,0 +1,254 @@
+/*
+ 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);
+
+/* Return true if the directory is local. */
+gboolean nautilus_directory_is_local (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..8071d5b
--- /dev/null
+++ b/src/nautilus-dnd.c
@@ -0,0 +1,978 @@
+/* nautilus-dnd.c - Common Drag & drop handling code shared by the icon container
+ * and the list view.
+ *
+ * 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: Pavel Cisler <pavel@eazel.com>,
+ * Ettore Perazzoli <ettore@gnu.org>
+ */
+
+#include <config.h>
+#include "nautilus-dnd.h"
+
+#include "nautilus-program-choosing.h"
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-gtk-extensions.h>
+#include <eel/eel-string.h>
+#include <eel/eel-vfs-extensions.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include "nautilus-file-utilities.h"
+#include "nautilus-canvas-dnd.h"
+#include <src/nautilus-list-view-dnd.h>
+#include <stdio.h>
+#include <string.h>
+
+/* a set of defines stolen from the eel-icon-dnd.c file.
+ * These are in microseconds.
+ */
+#define AUTOSCROLL_TIMEOUT_INTERVAL 100
+#define AUTOSCROLL_INITIAL_DELAY 100000
+
+/* drag this close to the view edge to start auto scroll*/
+#define AUTO_SCROLL_MARGIN 30
+
+/* the smallest amount of auto scroll used when we just enter the autoscroll
+ * margin
+ */
+#define MIN_AUTOSCROLL_DELTA 5
+
+/* the largest amount of auto scroll used when we are right over the view
+ * edge
+ */
+#define MAX_AUTOSCROLL_DELTA 50
+
+void
+nautilus_drag_init (NautilusDragInfo *drag_info,
+ const GtkTargetEntry *drag_types,
+ int drag_type_count,
+ gboolean add_text_targets)
+{
+ drag_info->target_list = gtk_target_list_new (drag_types,
+ drag_type_count);
+
+ if (add_text_targets)
+ {
+ gtk_target_list_add_text_targets (drag_info->target_list,
+ NAUTILUS_ICON_DND_TEXT);
+ }
+
+ drag_info->drop_occurred = FALSE;
+ drag_info->need_to_destroy = FALSE;
+}
+
+void
+nautilus_drag_finalize (NautilusDragInfo *drag_info)
+{
+ gtk_target_list_unref (drag_info->target_list);
+ nautilus_drag_destroy_selection_list (drag_info->selection_list);
+ nautilus_drag_destroy_selection_list (drag_info->selection_cache);
+
+ g_free (drag_info);
+}
+
+
+/* Functions to deal with NautilusDragSelectionItems. */
+
+NautilusDragSelectionItem *
+nautilus_drag_selection_item_new (void)
+{
+ return g_new0 (NautilusDragSelectionItem, 1);
+}
+
+static void
+drag_selection_item_destroy (NautilusDragSelectionItem *item)
+{
+ g_clear_object (&item->file);
+ g_free (item->uri);
+ g_free (item);
+}
+
+void
+nautilus_drag_destroy_selection_list (GList *list)
+{
+ GList *p;
+
+ if (list == NULL)
+ {
+ return;
+ }
+
+ for (p = list; p != NULL; p = p->next)
+ {
+ drag_selection_item_destroy (p->data);
+ }
+
+ g_list_free (list);
+}
+
+GList *
+nautilus_drag_uri_list_from_selection_list (const GList *selection_list)
+{
+ NautilusDragSelectionItem *selection_item;
+ GList *uri_list;
+ const GList *l;
+
+ uri_list = NULL;
+ for (l = selection_list; l != NULL; l = l->next)
+ {
+ selection_item = (NautilusDragSelectionItem *) l->data;
+ if (selection_item->uri != NULL)
+ {
+ uri_list = g_list_prepend (uri_list, g_strdup (selection_item->uri));
+ }
+ }
+
+ return g_list_reverse (uri_list);
+}
+
+/*
+ * Transfer: Full. Free with g_list_free_full (list, g_object_unref);
+ */
+GList *
+nautilus_drag_file_list_from_selection_list (const GList *selection_list)
+{
+ NautilusDragSelectionItem *selection_item;
+ GList *file_list;
+ const GList *l;
+
+ file_list = NULL;
+ for (l = selection_list; l != NULL; l = l->next)
+ {
+ selection_item = (NautilusDragSelectionItem *) l->data;
+ if (selection_item->file != NULL)
+ {
+ file_list = g_list_prepend (file_list, g_object_ref (selection_item->file));
+ }
+ }
+
+ return g_list_reverse (file_list);
+}
+
+GList *
+nautilus_drag_uri_list_from_array (const char **uris)
+{
+ GList *uri_list;
+ int i;
+
+ if (uris == NULL)
+ {
+ return NULL;
+ }
+
+ uri_list = NULL;
+
+ for (i = 0; uris[i] != NULL; i++)
+ {
+ uri_list = g_list_prepend (uri_list, g_strdup (uris[i]));
+ }
+
+ return g_list_reverse (uri_list);
+}
+
+GList *
+nautilus_drag_build_selection_list (GtkSelectionData *data)
+{
+ GList *result;
+ const guchar *p, *oldp;
+ int size;
+
+ result = NULL;
+ oldp = gtk_selection_data_get_data (data);
+ size = gtk_selection_data_get_length (data);
+
+ while (size > 0)
+ {
+ NautilusDragSelectionItem *item;
+ guint len;
+
+ /* The list is in the form:
+ *
+ * name\rx:y:width:height\r\n
+ *
+ * The geometry information after the first \r is optional. */
+
+ /* 1: Decode name. */
+
+ p = memchr (oldp, '\r', size);
+ if (p == NULL)
+ {
+ break;
+ }
+
+ item = nautilus_drag_selection_item_new ();
+
+ len = p - oldp;
+
+ item->uri = g_malloc (len + 1);
+ memcpy (item->uri, oldp, len);
+ item->uri[len] = 0;
+ item->file = nautilus_file_get_by_uri (item->uri);
+
+ p++;
+ if (*p == '\n' || *p == '\0')
+ {
+ result = g_list_prepend (result, item);
+ if (p == 0)
+ {
+ g_warning ("Invalid x-special/gnome-icon-list data received: "
+ "missing newline character.");
+ break;
+ }
+ else
+ {
+ oldp = p + 1;
+ continue;
+ }
+ }
+
+ size -= p - oldp;
+ oldp = p;
+
+ /* 2: Decode geometry information. */
+
+ item->got_icon_position = sscanf ((const gchar *) p, "%d:%d:%d:%d%*s",
+ &item->icon_x, &item->icon_y,
+ &item->icon_width, &item->icon_height) == 4;
+ if (!item->got_icon_position)
+ {
+ g_warning ("Invalid x-special/gnome-icon-list data received: "
+ "invalid icon position specification.");
+ }
+
+ result = g_list_prepend (result, item);
+
+ p = memchr (p, '\r', size);
+ if (p == NULL || p[1] != '\n')
+ {
+ g_warning ("Invalid x-special/gnome-icon-list data received: "
+ "missing newline character.");
+ if (p == NULL)
+ {
+ break;
+ }
+ }
+ else
+ {
+ p += 2;
+ }
+
+ size -= p - oldp;
+ oldp = p;
+ }
+
+ return g_list_reverse (result);
+}
+
+static gboolean
+nautilus_drag_file_local_internal (const char *target_uri_string,
+ const char *first_source_uri)
+{
+ /* check if the first item on the list has target_uri_string as a parent
+ * FIXME:
+ * we should really test each item but that would be slow for large selections
+ * and currently dropped items can only be from the same container
+ */
+ GFile *target, *item, *parent;
+ gboolean result;
+
+ result = FALSE;
+
+ target = g_file_new_for_uri (target_uri_string);
+
+ /* get the parent URI of the first item in the selection */
+ item = g_file_new_for_uri (first_source_uri);
+ parent = g_file_get_parent (item);
+ g_object_unref (item);
+
+ if (parent != NULL)
+ {
+ result = g_file_equal (parent, target);
+ g_object_unref (parent);
+ }
+
+ g_object_unref (target);
+
+ return result;
+}
+
+gboolean
+nautilus_drag_uris_local (const char *target_uri,
+ const GList *source_uri_list)
+{
+ /* must have at least one item */
+ g_assert (source_uri_list);
+
+ return nautilus_drag_file_local_internal (target_uri, source_uri_list->data);
+}
+
+gboolean
+nautilus_drag_items_local (const char *target_uri_string,
+ const GList *selection_list)
+{
+ /* must have at least one item */
+ g_assert (selection_list);
+
+ return nautilus_drag_file_local_internal (target_uri_string,
+ ((NautilusDragSelectionItem *) selection_list->data)->uri);
+}
+
+GdkDragAction
+nautilus_drag_default_drop_action_for_netscape_url (GdkDragContext *context)
+{
+ /* Mozilla defaults to copy, but unless thats the
+ * only allowed thing (enforced by ctrl) we want to LINK */
+ if (gdk_drag_context_get_suggested_action (context) == GDK_ACTION_COPY &&
+ gdk_drag_context_get_actions (context) != GDK_ACTION_COPY)
+ {
+ return GDK_ACTION_LINK;
+ }
+ else if (gdk_drag_context_get_suggested_action (context) == GDK_ACTION_MOVE)
+ {
+ /* Don't support move */
+ return GDK_ACTION_COPY;
+ }
+
+ return gdk_drag_context_get_suggested_action (context);
+}
+
+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;
+}
+
+NautilusDragInfo *
+nautilus_drag_get_source_data (GdkDragContext *context)
+{
+ GtkWidget *source_widget;
+ NautilusDragInfo *source_data;
+
+ source_widget = gtk_drag_get_source_widget (context);
+ if (source_widget == NULL)
+ {
+ return NULL;
+ }
+
+ if (NAUTILUS_IS_CANVAS_CONTAINER (source_widget))
+ {
+ source_data = nautilus_canvas_dnd_get_drag_source_data (NAUTILUS_CANVAS_CONTAINER (source_widget),
+ context);
+ }
+ else if (GTK_IS_TREE_VIEW (source_widget))
+ {
+ NautilusWindow *window;
+ NautilusWindowSlot *active_slot;
+ NautilusView *view;
+
+ window = NAUTILUS_WINDOW (gtk_widget_get_toplevel (source_widget));
+ active_slot = nautilus_window_get_active_slot (window);
+ view = nautilus_window_slot_get_current_view (active_slot);
+ if (NAUTILUS_IS_LIST_VIEW (view))
+ {
+ source_data = nautilus_list_view_dnd_get_drag_source_data (NAUTILUS_LIST_VIEW (view),
+ context);
+ }
+ else
+ {
+ g_warning ("Got a drag context with a tree view source widget, but current view is not list view");
+ source_data = NULL;
+ }
+ }
+ else
+ {
+ /* it's a slot or something else */
+ g_warning ("Requested drag source data from a widget that doesn't support it");
+ source_data = NULL;
+ }
+
+ return source_data;
+}
+
+void
+nautilus_drag_default_drop_action_for_icons (GdkDragContext *context,
+ const char *target_uri_string,
+ const GList *items,
+ guint32 source_actions,
+ int *action)
+{
+ gboolean same_fs;
+ gboolean target_is_source_parent;
+ gboolean source_deletable;
+ const char *dropped_uri;
+ GFile *target, *dropped, *dropped_directory;
+ GdkDragAction actions;
+ NautilusFile *dropped_file, *target_file;
+
+ if (target_uri_string == NULL)
+ {
+ *action = 0;
+ return;
+ }
+
+ /* this is needed because of how dnd works. The actions at the time drag-begin
+ * is done are not set, because they are first set on drag-motion. However,
+ * for our use case, which is validation with the sidebar for dnd feedback
+ * when the dnd doesn't have as a destination the sidebar itself, we need
+ * a way to know the actions at drag-begin time. Either canvas view or
+ * list view know them when starting the drag, but asking for them here
+ * would be breaking the current model too much. So instead we rely on the
+ * caller, which will ask if appropiate to those objects about the actions
+ * available, instead of relying solely on the context here. */
+ if (source_actions)
+ {
+ actions = source_actions & (GDK_ACTION_MOVE | GDK_ACTION_COPY);
+ }
+ else
+ {
+ actions = gdk_drag_context_get_actions (context) & (GDK_ACTION_MOVE | GDK_ACTION_COPY);
+ }
+ if (actions == 0)
+ {
+ /* We can't use copy or move, just go with the suggested action. */
+ *action = gdk_drag_context_get_suggested_action (context);
+ return;
+ }
+
+ if (gdk_drag_context_get_suggested_action (context) == GDK_ACTION_ASK)
+ {
+ /* Don't override ask */
+ *action = gdk_drag_context_get_suggested_action (context);
+ return;
+ }
+
+ dropped_uri = ((NautilusDragSelectionItem *) items->data)->uri;
+ dropped_file = ((NautilusDragSelectionItem *) items->data)->file;
+ target_file = nautilus_file_get_by_uri (target_uri_string);
+
+ /*
+ * Check for trash URI. We do a find_directory for any Trash directory.
+ * Passing 0 permissions as gnome-vfs would override the permissions
+ * passed with 700 while creating .Trash directory
+ */
+ if (eel_uri_is_trash (target_uri_string))
+ {
+ /* Only move to Trash */
+ if (actions & GDK_ACTION_MOVE)
+ {
+ *action = GDK_ACTION_MOVE;
+ }
+ nautilus_file_unref (target_file);
+ return;
+ }
+ else if (target_file != NULL && nautilus_file_is_archive (target_file))
+ {
+ *action = GDK_ACTION_COPY;
+
+ nautilus_file_unref (target_file);
+ return;
+ }
+ else
+ {
+ target = g_file_new_for_uri (target_uri_string);
+ }
+
+ same_fs = check_same_fs (target_file, dropped_file);
+
+ nautilus_file_unref (target_file);
+
+ /* Compare the first dropped uri with the target uri for same fs match. */
+ dropped = g_file_new_for_uri (dropped_uri);
+ dropped_directory = g_file_get_parent (dropped);
+ target_is_source_parent = FALSE;
+ if (dropped_directory != NULL)
+ {
+ /* If the dropped file is already in the same directory but
+ * is in another filesystem we still want to move, not copy
+ * as this is then just a move of a mountpoint to another
+ * position in the dir */
+ target_is_source_parent = g_file_equal (dropped_directory, target);
+ g_object_unref (dropped_directory);
+ }
+ source_deletable = source_is_deletable (dropped);
+
+ if ((same_fs && source_deletable) || target_is_source_parent ||
+ g_file_has_uri_scheme (dropped, "trash"))
+ {
+ if (actions & GDK_ACTION_MOVE)
+ {
+ *action = GDK_ACTION_MOVE;
+ }
+ else
+ {
+ *action = gdk_drag_context_get_suggested_action (context);
+ }
+ }
+ else
+ {
+ if (actions & GDK_ACTION_COPY)
+ {
+ *action = GDK_ACTION_COPY;
+ }
+ else
+ {
+ *action = gdk_drag_context_get_suggested_action (context);
+ }
+ }
+
+ g_object_unref (target);
+ g_object_unref (dropped);
+}
+
+GdkDragAction
+nautilus_drag_default_drop_action_for_uri_list (GdkDragContext *context,
+ const char *target_uri_string)
+{
+ if (eel_uri_is_trash (target_uri_string) && (gdk_drag_context_get_actions (context) & GDK_ACTION_MOVE))
+ {
+ /* Only move to Trash */
+ return GDK_ACTION_MOVE;
+ }
+ else
+ {
+ return gdk_drag_context_get_suggested_action (context);
+ }
+}
+
+/* Encode a "x-special/gnome-icon-list" selection.
+ * Along with the URIs of the dragged files, this encodes
+ * the location and size of each icon relative to the cursor.
+ */
+static void
+add_one_gnome_icon (const char *uri,
+ int x,
+ int y,
+ int w,
+ int h,
+ gpointer data)
+{
+ GString *result;
+
+ result = (GString *) data;
+
+ g_string_append_printf (result, "%s\r%d:%d:%hu:%hu\r\n",
+ uri, x, y, w, h);
+}
+
+static void
+add_one_uri (const char *uri,
+ int x,
+ int y,
+ int w,
+ int h,
+ gpointer data)
+{
+ GString *result;
+
+ result = (GString *) data;
+
+ g_string_append (result, uri);
+ g_string_append (result, "\r\n");
+}
+
+static void
+cache_one_item (const char *uri,
+ int x,
+ int y,
+ int w,
+ int h,
+ gpointer data)
+{
+ GList **cache = data;
+ NautilusDragSelectionItem *item;
+
+ item = nautilus_drag_selection_item_new ();
+ item->uri = nautilus_uri_to_native_uri (uri);
+
+ if (item->uri == NULL)
+ {
+ item->uri = g_strdup (uri);
+ }
+
+ item->file = nautilus_file_get_by_uri (uri);
+ item->icon_x = x;
+ item->icon_y = y;
+ item->icon_width = w;
+ item->icon_height = h;
+ *cache = g_list_prepend (*cache, item);
+}
+
+GList *
+nautilus_drag_create_selection_cache (gpointer container_context,
+ NautilusDragEachSelectedItemIterator each_selected_item_iterator)
+{
+ GList *cache = NULL;
+
+ (*each_selected_item_iterator)(cache_one_item, container_context, &cache);
+ cache = g_list_reverse (cache);
+
+ return cache;
+}
+
+/* Common function for drag_data_get_callback calls.
+ * Returns FALSE if it doesn't handle drag data */
+gboolean
+nautilus_drag_drag_data_get_from_cache (GList *cache,
+ GdkDragContext *context,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint32 time)
+{
+ GList *l;
+ GString *result;
+ NautilusDragEachSelectedItemDataGet func;
+
+ if (cache == NULL)
+ {
+ return FALSE;
+ }
+
+ switch (info)
+ {
+ case NAUTILUS_ICON_DND_GNOME_ICON_LIST:
+ {
+ func = add_one_gnome_icon;
+ }
+ break;
+
+ case NAUTILUS_ICON_DND_URI_LIST:
+ case NAUTILUS_ICON_DND_TEXT:
+ {
+ func = add_one_uri;
+ }
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ result = g_string_new (NULL);
+
+ for (l = cache; l != NULL; l = l->next)
+ {
+ NautilusDragSelectionItem *item = l->data;
+ (*func)(item->uri, item->icon_x, item->icon_y, item->icon_width, item->icon_height, result);
+ }
+
+ gtk_selection_data_set (selection_data,
+ gtk_selection_data_get_target (selection_data),
+ 8, (guchar *) result->str, result->len);
+ g_string_free (result, TRUE);
+
+ return TRUE;
+}
+
+typedef struct
+{
+ GMainLoop *loop;
+ GdkDragAction chosen;
+} DropActionMenuData;
+
+static void
+menu_deactivate_callback (GtkWidget *menu,
+ gpointer data)
+{
+ DropActionMenuData *damd;
+
+ damd = data;
+
+ if (g_main_loop_is_running (damd->loop))
+ {
+ g_main_loop_quit (damd->loop);
+ }
+}
+
+static void
+drop_action_activated_callback (GtkWidget *menu_item,
+ gpointer data)
+{
+ DropActionMenuData *damd;
+
+ damd = data;
+
+ damd->chosen = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (menu_item),
+ "action"));
+
+ if (g_main_loop_is_running (damd->loop))
+ {
+ g_main_loop_quit (damd->loop);
+ }
+}
+
+static void
+append_drop_action_menu_item (GtkWidget *menu,
+ const char *text,
+ GdkDragAction action,
+ gboolean sensitive,
+ DropActionMenuData *damd)
+{
+ GtkWidget *menu_item;
+
+ menu_item = gtk_menu_item_new_with_mnemonic (text);
+ gtk_widget_set_sensitive (menu_item, sensitive);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
+
+ g_object_set_data (G_OBJECT (menu_item),
+ "action",
+ GINT_TO_POINTER (action));
+
+ g_signal_connect (menu_item, "activate",
+ G_CALLBACK (drop_action_activated_callback),
+ damd);
+
+ gtk_widget_show (menu_item);
+}
+
+/* Pops up a menu of actions to perform on dropped files */
+GdkDragAction
+nautilus_drag_drop_action_ask (GtkWidget *widget,
+ GdkDragAction actions)
+{
+ GtkWidget *menu;
+ GtkWidget *menu_item;
+ DropActionMenuData damd;
+
+ /* Create the menu and set the sensitivity of the items based on the
+ * allowed actions.
+ */
+ menu = gtk_menu_new ();
+ gtk_menu_set_screen (GTK_MENU (menu), gtk_widget_get_screen (widget));
+
+ 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);
+
+ eel_gtk_menu_append_separator (GTK_MENU (menu));
+
+ menu_item = gtk_menu_item_new_with_mnemonic (_("Cancel"));
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
+ gtk_widget_show (menu_item);
+
+ damd.chosen = 0;
+ damd.loop = g_main_loop_new (NULL, FALSE);
+
+ g_signal_connect (menu, "deactivate",
+ G_CALLBACK (menu_deactivate_callback),
+ &damd);
+
+ gtk_grab_add (menu);
+
+ gtk_menu_popup_at_pointer (GTK_MENU (menu),
+ NULL);
+
+ g_main_loop_run (damd.loop);
+
+ gtk_grab_remove (menu);
+
+ g_main_loop_unref (damd.loop);
+
+ g_object_ref_sink (menu);
+ g_object_unref (menu);
+
+ return damd.chosen;
+}
+
+gboolean
+nautilus_drag_autoscroll_in_scroll_region (GtkWidget *widget)
+{
+ float x_scroll_delta, y_scroll_delta;
+
+ nautilus_drag_autoscroll_calculate_delta (widget, &x_scroll_delta, &y_scroll_delta);
+
+ return x_scroll_delta != 0 || y_scroll_delta != 0;
+}
+
+
+void
+nautilus_drag_autoscroll_calculate_delta (GtkWidget *widget,
+ float *x_scroll_delta,
+ float *y_scroll_delta)
+{
+ GtkAllocation allocation;
+ GdkDisplay *display;
+ GdkSeat *seat;
+ GdkDevice *pointer;
+ int x, y;
+
+ g_assert (GTK_IS_WIDGET (widget));
+
+ display = gtk_widget_get_display (widget);
+ seat = gdk_display_get_default_seat (display);
+ pointer = gdk_seat_get_pointer (seat);
+ gdk_window_get_device_position (gtk_widget_get_window (widget), pointer,
+ &x, &y, NULL);
+
+ /* Find out if we are anywhere close to the tree view edges
+ * to see if we need to autoscroll.
+ */
+ *x_scroll_delta = 0;
+ *y_scroll_delta = 0;
+
+ if (x < AUTO_SCROLL_MARGIN)
+ {
+ *x_scroll_delta = (float) (x - AUTO_SCROLL_MARGIN);
+ }
+
+ gtk_widget_get_allocation (widget, &allocation);
+ if (x > allocation.width - AUTO_SCROLL_MARGIN)
+ {
+ if (*x_scroll_delta != 0)
+ {
+ /* Already trying to scroll because of being too close to
+ * the top edge -- must be the window is really short,
+ * don't autoscroll.
+ */
+ return;
+ }
+ *x_scroll_delta = (float) (x - (allocation.width - AUTO_SCROLL_MARGIN));
+ }
+
+ if (y < AUTO_SCROLL_MARGIN)
+ {
+ *y_scroll_delta = (float) (y - AUTO_SCROLL_MARGIN);
+ }
+
+ if (y > allocation.height - AUTO_SCROLL_MARGIN)
+ {
+ if (*y_scroll_delta != 0)
+ {
+ /* Already trying to scroll because of being too close to
+ * the top edge -- must be the window is really narrow,
+ * don't autoscroll.
+ */
+ return;
+ }
+ *y_scroll_delta = (float) (y - (allocation.height - AUTO_SCROLL_MARGIN));
+ }
+
+ if (*x_scroll_delta == 0 && *y_scroll_delta == 0)
+ {
+ /* no work */
+ return;
+ }
+
+ /* Adjust the scroll delta to the proper acceleration values depending on how far
+ * into the sroll margins we are.
+ * FIXME bugzilla.eazel.com 2486:
+ * we could use an exponential acceleration factor here for better feel
+ */
+ if (*x_scroll_delta != 0)
+ {
+ *x_scroll_delta /= AUTO_SCROLL_MARGIN;
+ *x_scroll_delta *= (MAX_AUTOSCROLL_DELTA - MIN_AUTOSCROLL_DELTA);
+ *x_scroll_delta += MIN_AUTOSCROLL_DELTA;
+ }
+
+ if (*y_scroll_delta != 0)
+ {
+ *y_scroll_delta /= AUTO_SCROLL_MARGIN;
+ *y_scroll_delta *= (MAX_AUTOSCROLL_DELTA - MIN_AUTOSCROLL_DELTA);
+ *y_scroll_delta += MIN_AUTOSCROLL_DELTA;
+ }
+}
+
+
+
+void
+nautilus_drag_autoscroll_start (NautilusDragInfo *drag_info,
+ GtkWidget *widget,
+ GSourceFunc callback,
+ gpointer user_data)
+{
+ if (nautilus_drag_autoscroll_in_scroll_region (widget))
+ {
+ if (drag_info->auto_scroll_timeout_id == 0)
+ {
+ drag_info->waiting_to_autoscroll = TRUE;
+ drag_info->start_auto_scroll_in = g_get_monotonic_time ()
+ + AUTOSCROLL_INITIAL_DELAY;
+
+ drag_info->auto_scroll_timeout_id = g_timeout_add
+ (AUTOSCROLL_TIMEOUT_INTERVAL,
+ callback,
+ user_data);
+ }
+ }
+ else
+ {
+ if (drag_info->auto_scroll_timeout_id != 0)
+ {
+ g_source_remove (drag_info->auto_scroll_timeout_id);
+ drag_info->auto_scroll_timeout_id = 0;
+ }
+ }
+}
+
+void
+nautilus_drag_autoscroll_stop (NautilusDragInfo *drag_info)
+{
+ if (drag_info->auto_scroll_timeout_id != 0)
+ {
+ g_source_remove (drag_info->auto_scroll_timeout_id);
+ drag_info->auto_scroll_timeout_id = 0;
+ }
+}
diff --git a/src/nautilus-dnd.h b/src/nautilus-dnd.h
new file mode 100644
index 0000000..6e101b5
--- /dev/null
+++ b/src/nautilus-dnd.h
@@ -0,0 +1,140 @@
+
+/* nautilus-dnd.h - Common Drag & drop handling code shared by the icon container
+ and the list view.
+
+ 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: Pavel Cisler <pavel@eazel.com>,
+ Ettore Perazzoli <ettore@gnu.org>
+*/
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include "nautilus-file.h"
+
+/* Drag & Drop target names. */
+#define NAUTILUS_ICON_DND_GNOME_ICON_LIST_TYPE "x-special/gnome-icon-list"
+#define NAUTILUS_ICON_DND_URI_LIST_TYPE "text/uri-list"
+#define NAUTILUS_ICON_DND_NETSCAPE_URL_TYPE "_NETSCAPE_URL"
+#define NAUTILUS_ICON_DND_ROOTWINDOW_DROP_TYPE "application/x-rootwindow-drop"
+#define NAUTILUS_ICON_DND_XDNDDIRECTSAVE_TYPE "XdndDirectSave0" /* XDS Protocol Type */
+#define NAUTILUS_ICON_DND_RAW_TYPE "application/octet-stream"
+
+/* drag&drop-related information. */
+typedef struct {
+ GtkTargetList *target_list;
+
+ /* Stuff saved at "receive data" time needed later in the drag. */
+ gboolean got_drop_data_type;
+ NautilusIconDndTargetType data_type;
+ GtkSelectionData *selection_data;
+ char *direct_save_uri;
+
+ /* Start of the drag, in window coordinates. */
+ int start_x, start_y;
+
+ /* List of NautilusDragSelectionItems, representing items being dragged, or NULL
+ * if data about them has not been received from the source yet.
+ */
+ GList *selection_list;
+
+ /* cache of selected URIs, representing items being dragged */
+ GList *selection_cache;
+
+ /* File selection list information request handler, for the call for
+ * information (mostly the file system info, in order to know if we want
+ * co copy or move the files) about the files being dragged, that can
+ * come from another nautilus process, like the desktop. */
+ NautilusFileListHandle *file_list_info_handler;
+
+ /* has the drop occurred ? */
+ gboolean drop_occurred;
+
+ /* whether or not need to clean up the previous dnd data */
+ gboolean need_to_destroy;
+
+ /* autoscrolling during dragging */
+ int auto_scroll_timeout_id;
+ gboolean waiting_to_autoscroll;
+ gint64 start_auto_scroll_in;
+
+ /* source context actions. Used for peek the actions using a GdkDragContext
+ * source at drag-begin time when they are not available yet (they become
+ * available at drag-motion time) */
+ guint32 source_actions;
+
+} NautilusDragInfo;
+
+typedef void (* NautilusDragEachSelectedItemDataGet) (const char *url,
+ int x, int y, int w, int h,
+ gpointer data);
+typedef void (* NautilusDragEachSelectedItemIterator) (NautilusDragEachSelectedItemDataGet iteratee,
+ gpointer iterator_context,
+ gpointer data);
+
+void nautilus_drag_init (NautilusDragInfo *drag_info,
+ const GtkTargetEntry *drag_types,
+ int drag_type_count,
+ gboolean add_text_targets);
+void nautilus_drag_finalize (NautilusDragInfo *drag_info);
+NautilusDragSelectionItem *nautilus_drag_selection_item_new (void);
+void nautilus_drag_destroy_selection_list (GList *selection_list);
+GList *nautilus_drag_build_selection_list (GtkSelectionData *data);
+
+GList * nautilus_drag_uri_list_from_selection_list (const GList *selection_list);
+
+GList * nautilus_drag_uri_list_from_array (const char **uris);
+
+gboolean nautilus_drag_items_local (const char *target_uri,
+ const GList *selection_list);
+gboolean nautilus_drag_uris_local (const char *target_uri,
+ const GList *source_uri_list);
+void nautilus_drag_default_drop_action_for_icons (GdkDragContext *context,
+ const char *target_uri,
+ const GList *items,
+ guint32 source_actions,
+ int *action);
+GdkDragAction nautilus_drag_default_drop_action_for_netscape_url (GdkDragContext *context);
+GdkDragAction nautilus_drag_default_drop_action_for_uri_list (GdkDragContext *context,
+ const char *target_uri_string);
+GList *nautilus_drag_create_selection_cache (gpointer container_context,
+ NautilusDragEachSelectedItemIterator each_selected_item_iterator);
+gboolean nautilus_drag_drag_data_get_from_cache (GList *cache,
+ GdkDragContext *context,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint32 time);
+int nautilus_drag_modifier_based_action (int default_action,
+ int non_default_action);
+
+GdkDragAction nautilus_drag_drop_action_ask (GtkWidget *widget,
+ GdkDragAction possible_actions);
+
+gboolean nautilus_drag_autoscroll_in_scroll_region (GtkWidget *widget);
+void nautilus_drag_autoscroll_calculate_delta (GtkWidget *widget,
+ float *x_scroll_delta,
+ float *y_scroll_delta);
+void nautilus_drag_autoscroll_start (NautilusDragInfo *drag_info,
+ GtkWidget *widget,
+ GSourceFunc callback,
+ gpointer user_data);
+void nautilus_drag_autoscroll_stop (NautilusDragInfo *drag_info);
+
+NautilusDragInfo * nautilus_drag_get_source_data (GdkDragContext *context);
+
+GList * nautilus_drag_file_list_from_selection_list (const GList *selection_list);
diff --git a/src/nautilus-enum-types.c.template b/src/nautilus-enum-types.c.template
new file mode 100644
index 0000000..00155ca
--- /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 volatile 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..edfd999
--- /dev/null
+++ b/src/nautilus-enums.h
@@ -0,0 +1,83 @@
+/* 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_CANVAS_ICON_SIZE_SMALL = 48,
+ NAUTILUS_CANVAS_ICON_SIZE_STANDARD = 64,
+ NAUTILUS_CANVAS_ICON_SIZE_LARGE = 96,
+ NAUTILUS_CANVAS_ICON_SIZE_LARGER = 128,
+ NAUTILUS_CANVAS_ICON_SIZE_LARGEST = 256,
+} NautilusCanvasIconSize;
+
+typedef enum
+{
+ NAUTILUS_CANVAS_ZOOM_LEVEL_SMALL,
+ NAUTILUS_CANVAS_ZOOM_LEVEL_STANDARD,
+ NAUTILUS_CANVAS_ZOOM_LEVEL_LARGE,
+ NAUTILUS_CANVAS_ZOOM_LEVEL_LARGER,
+ NAUTILUS_CANVAS_ZOOM_LEVEL_LARGEST,
+} NautilusCanvasZoomLevel;
+
+typedef enum
+{
+ NAUTILUS_LIST_ICON_SIZE_SMALL = 16,
+ NAUTILUS_LIST_ICON_SIZE_STANDARD = 32,
+ NAUTILUS_LIST_ICON_SIZE_LARGE = 48,
+ NAUTILUS_LIST_ICON_SIZE_LARGER = 64,
+} NautilusListIconSize;
+
+typedef enum
+{
+ NAUTILUS_LIST_ZOOM_LEVEL_SMALL,
+ NAUTILUS_LIST_ZOOM_LEVEL_STANDARD,
+ NAUTILUS_LIST_ZOOM_LEVEL_LARGE,
+ NAUTILUS_LIST_ZOOM_LEVEL_LARGER,
+} 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_WINDOW_OPEN_FLAG_NEW_WINDOW = 1 << 0,
+ NAUTILUS_WINDOW_OPEN_FLAG_NEW_TAB = 1 << 1,
+ NAUTILUS_WINDOW_OPEN_SLOT_APPEND = 1 << 2,
+ NAUTILUS_WINDOW_OPEN_FLAG_DONT_MAKE_ACTIVE = 1 << 3,
+} NautilusWindowOpenFlags;
diff --git a/src/nautilus-error-reporting.c b/src/nautilus-error-reporting.c
new file mode 100644
index 0000000..5ef9e6c
--- /dev/null
+++ b/src/nautilus-error-reporting.c
@@ -0,0 +1,443 @@
+/* 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))
+ {
+ /* If rename failed, notify the user. */
+ nautilus_report_error_renaming_file (file, data->name, error, NULL);
+ }
+ 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..d1bdc46
--- /dev/null
+++ b/src/nautilus-file-changes-queue.c
@@ -0,0 +1,346 @@
+/*
+ * 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;
+ GdkPoint point;
+ 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)
+{
+ g_autoptr (NautilusTagManager) tag_manager = nautilus_tag_manager_get ();
+ 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 (tag_manager,
+ 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..fdcaf2d
--- /dev/null
+++ b/src/nautilus-file-conflict-dialog.c
@@ -0,0 +1,390 @@
+/* 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;
+
+ /* UI objects */
+ GtkWidget *titles_vbox;
+ GtkWidget *first_hbox;
+ GtkWidget *second_hbox;
+ GtkWidget *expander;
+ GtkWidget *entry;
+ GtkWidget *checkbox;
+ GtkWidget *skip_button;
+ GtkWidget *rename_button;
+ GtkWidget *replace_button;
+ GtkWidget *dest_image;
+ GtkWidget *src_image;
+};
+
+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)
+{
+ GtkWidget *label;
+ PangoAttrList *attr_list;
+
+ label = gtk_label_new (primary_text);
+ gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+ gtk_label_set_line_wrap_mode (GTK_LABEL (label), PANGO_WRAP_WORD_CHAR);
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_box_pack_start (GTK_BOX (fcd->titles_vbox), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+
+ attr_list = pango_attr_list_new ();
+ pango_attr_list_insert (attr_list, pango_attr_weight_new (PANGO_WEIGHT_BOLD));
+ pango_attr_list_insert (attr_list, pango_attr_scale_new (PANGO_SCALE_LARGE));
+ g_object_set (label, "attributes", attr_list, NULL);
+
+ pango_attr_list_unref (attr_list);
+
+ label = gtk_label_new (secondary_text);
+ gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_box_pack_start (GTK_BOX (fcd->titles_vbox), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+}
+
+void
+nautilus_file_conflict_dialog_set_images (NautilusFileConflictDialog *fcd,
+ GdkPixbuf *destination_pixbuf,
+ GdkPixbuf *source_pixbuf)
+{
+ if (fcd->dest_image == NULL)
+ {
+ fcd->dest_image = gtk_image_new_from_pixbuf (destination_pixbuf);
+ gtk_box_pack_start (GTK_BOX (fcd->first_hbox), fcd->dest_image, FALSE, FALSE, 0);
+ gtk_widget_show (fcd->dest_image);
+ }
+ else
+ {
+ gtk_image_set_from_pixbuf (GTK_IMAGE (fcd->dest_image), destination_pixbuf);
+ }
+
+ if (fcd->src_image == NULL)
+ {
+ fcd->src_image = gtk_image_new_from_pixbuf (source_pixbuf);
+ gtk_box_pack_start (GTK_BOX (fcd->second_hbox), fcd->src_image, FALSE, FALSE, 0);
+ gtk_widget_show (fcd->src_image);
+ }
+ else
+ {
+ gtk_image_set_from_pixbuf (GTK_IMAGE (fcd->src_image), source_pixbuf);
+ }
+}
+
+void
+nautilus_file_conflict_dialog_set_file_labels (NautilusFileConflictDialog *fcd,
+ gchar *destination_label,
+ gchar *source_label)
+{
+ GtkWidget *label;
+
+ label = gtk_label_new (NULL);
+ gtk_label_set_markup (GTK_LABEL (label), destination_label);
+ gtk_box_pack_start (GTK_BOX (fcd->first_hbox), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+
+ label = gtk_label_new (NULL);
+ gtk_label_set_markup (GTK_LABEL (label), source_label);
+ gtk_box_pack_start (GTK_BOX (fcd->second_hbox), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+}
+
+void
+nautilus_file_conflict_dialog_set_conflict_name (NautilusFileConflictDialog *fcd,
+ gchar *conflict_name)
+{
+ fcd->conflict_name = g_strdup (conflict_name);
+
+ gtk_entry_set_text (GTK_ENTRY (fcd->entry), fcd->conflict_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)
+{
+ /* The rename button is visible only if there's text
+ * in the entry.
+ */
+ if (g_strcmp0 (gtk_entry_get_text (GTK_ENTRY (entry)), "") != 0 &&
+ g_strcmp0 (gtk_entry_get_text (GTK_ENTRY (entry)), dialog->conflict_name) != 0)
+ {
+ gtk_widget_hide (dialog->replace_button);
+ gtk_widget_show (dialog->rename_button);
+
+ gtk_widget_set_sensitive (dialog->checkbox, FALSE);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), CONFLICT_RESPONSE_RENAME);
+ }
+ else
+ {
+ gtk_widget_hide (dialog->rename_button);
+ gtk_widget_show (dialog->replace_button);
+
+ gtk_widget_set_sensitive (dialog->checkbox, TRUE);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), CONFLICT_RESPONSE_REPLACE);
+ }
+}
+
+static void
+expander_activated_cb (GtkExpander *w,
+ NautilusFileConflictDialog *dialog)
+{
+ int start_pos, end_pos;
+
+ if (!gtk_expander_get_expanded (w))
+ {
+ if (g_strcmp0 (gtk_entry_get_text (GTK_ENTRY (dialog->entry)), dialog->conflict_name) == 0)
+ {
+ 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
+checkbox_toggled_cb (GtkToggleButton *t,
+ NautilusFileConflictDialog *dialog)
+{
+ gtk_widget_set_sensitive (dialog->expander, !gtk_toggle_button_get_active (t));
+ gtk_widget_set_sensitive (dialog->rename_button, !gtk_toggle_button_get_active (t));
+
+ if (!gtk_toggle_button_get_active (t) &&
+ g_strcmp0 (gtk_entry_get_text (GTK_ENTRY (dialog->entry)), "") != 0 &&
+ g_strcmp0 (gtk_entry_get_text (GTK_ENTRY (dialog->entry)), dialog->conflict_name) != 0)
+ {
+ gtk_widget_hide (dialog->replace_button);
+ gtk_widget_show (dialog->rename_button);
+ }
+ else
+ {
+ gtk_widget_hide (dialog->rename_button);
+ gtk_widget_show (dialog->replace_button);
+ }
+}
+
+static void
+reset_button_clicked_cb (GtkButton *w,
+ NautilusFileConflictDialog *dialog)
+{
+ int start_pos, end_pos;
+
+ gtk_entry_set_text (GTK_ENTRY (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)
+{
+ GtkWidget *hbox, *vbox, *vbox2;
+ GtkWidget *widget, *dialog_area;
+ GtkDialog *dialog;
+
+ dialog = GTK_DIALOG (fcd);
+
+ /* Setup the main hbox */
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
+ dialog_area = gtk_dialog_get_content_area (dialog);
+ gtk_box_pack_start (GTK_BOX (dialog_area), hbox, FALSE, FALSE, 0);
+ gtk_container_set_border_width (GTK_CONTAINER (hbox), 6);
+
+ /* Setup the dialog image */
+ widget = gtk_image_new_from_icon_name ("dialog-warning",
+ GTK_ICON_SIZE_DIALOG);
+ gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
+ gtk_widget_set_valign (widget, GTK_ALIGN_START);
+
+ /* Setup the vbox containing the dialog body */
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
+ gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 0);
+
+ /* Setup the vbox for the dialog labels */
+ widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
+ gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0);
+ fcd->titles_vbox = widget;
+
+ /* Setup the hboxes to pack file infos into */
+ vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
+ gtk_widget_set_halign (vbox2, GTK_ALIGN_START);
+ gtk_widget_set_margin_start (vbox2, 12);
+ gtk_box_pack_start (GTK_BOX (vbox), vbox2, FALSE, FALSE, 0);
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
+ gtk_box_pack_start (GTK_BOX (vbox2), hbox, FALSE, FALSE, 0);
+ fcd->first_hbox = hbox;
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
+ gtk_box_pack_start (GTK_BOX (vbox2), hbox, FALSE, FALSE, 0);
+ fcd->second_hbox = hbox;
+
+ /* Setup the expander for the rename action */
+ fcd->expander = gtk_expander_new_with_mnemonic (_("_Select a new name for the destination"));
+ gtk_box_pack_start (GTK_BOX (vbox2), fcd->expander, FALSE, FALSE, 0);
+ g_signal_connect (fcd->expander, "activate",
+ G_CALLBACK (expander_activated_cb), dialog);
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_container_add (GTK_CONTAINER (fcd->expander), hbox);
+
+ widget = gtk_entry_new ();
+ gtk_box_pack_start (GTK_BOX (hbox), widget, TRUE, TRUE, 6);
+ fcd->entry = widget;
+ g_signal_connect (widget, "changed",
+ G_CALLBACK (entry_text_changed_cb), dialog);
+ gtk_entry_set_activates_default (GTK_ENTRY (widget), TRUE);
+
+ widget = gtk_button_new_with_label (_("Reset"));
+ gtk_button_set_image (GTK_BUTTON (widget),
+ gtk_image_new_from_icon_name ("edit-undo",
+ GTK_ICON_SIZE_MENU));
+ gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 6);
+ g_signal_connect (widget, "clicked",
+ G_CALLBACK (reset_button_clicked_cb), dialog);
+
+ gtk_widget_show_all (vbox2);
+
+ /* Setup the checkbox to apply the action to all files */
+ widget = gtk_check_button_new_with_mnemonic (_("Apply this action to all files and folders"));
+
+ gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0);
+ fcd->checkbox = widget;
+ g_signal_connect (widget, "toggled",
+ G_CALLBACK (checkbox_toggled_cb), dialog);
+
+ /* Add buttons */
+ gtk_dialog_add_button (dialog, _("_Cancel"), GTK_RESPONSE_CANCEL);
+
+ fcd->skip_button = gtk_dialog_add_button (dialog,
+ _("_Skip"),
+ CONFLICT_RESPONSE_SKIP);
+
+ fcd->rename_button = gtk_dialog_add_button (dialog,
+ _("Re_name"),
+ CONFLICT_RESPONSE_RENAME);
+ gtk_widget_hide (fcd->rename_button);
+
+ fcd->replace_button = gtk_dialog_add_button (dialog,
+ _("Re_place"),
+ CONFLICT_RESPONSE_REPLACE);
+ gtk_widget_grab_focus (fcd->replace_button);
+
+ /* Setup HIG properties */
+ gtk_container_set_border_width (GTK_CONTAINER (dialog), 5);
+ gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (dialog)), 14);
+ gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+
+ gtk_widget_show_all (dialog_area);
+}
+
+static void
+do_finalize (GObject *self)
+{
+ NautilusFileConflictDialog *dialog = NAUTILUS_FILE_CONFLICT_DIALOG (self);
+
+ g_free (dialog->conflict_name);
+
+ G_OBJECT_CLASS (nautilus_file_conflict_dialog_parent_class)->finalize (self);
+}
+
+static void
+nautilus_file_conflict_dialog_class_init (NautilusFileConflictDialogClass *klass)
+{
+ G_OBJECT_CLASS (klass)->finalize = do_finalize;
+}
+
+char *
+nautilus_file_conflict_dialog_get_new_name (NautilusFileConflictDialog *dialog)
+{
+ return g_strdup (gtk_entry_get_text (GTK_ENTRY (dialog->entry)));
+}
+
+gboolean
+nautilus_file_conflict_dialog_get_apply_to_all (NautilusFileConflictDialog *dialog)
+{
+ return gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->checkbox));
+}
+
+NautilusFileConflictDialog *
+nautilus_file_conflict_dialog_new (GtkWindow *parent)
+{
+ NautilusFileConflictDialog *dialog;
+
+ dialog = NAUTILUS_FILE_CONFLICT_DIALOG (g_object_new (NAUTILUS_TYPE_FILE_CONFLICT_DIALOG,
+ "use-header-bar", TRUE,
+ "modal", TRUE,
+ NULL));
+
+ gtk_window_set_transient_for (GTK_WINDOW (dialog), parent);
+
+ return dialog;
+}
diff --git a/src/nautilus-file-conflict-dialog.h b/src/nautilus-file-conflict-dialog.h
new file mode 100644
index 0000000..e54071b
--- /dev/null
+++ b/src/nautilus-file-conflict-dialog.h
@@ -0,0 +1,58 @@
+
+/* 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,
+ GdkPixbuf *source_pixbuf,
+ GdkPixbuf *destination_pixbuf);
+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_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);
+
+char* nautilus_file_conflict_dialog_get_new_name (NautilusFileConflictDialog *dialog);
+gboolean nautilus_file_conflict_dialog_get_apply_to_all (NautilusFileConflictDialog *dialog);
+
+G_END_DECLS \ No newline at end of file
diff --git a/src/nautilus-file-name-widget-controller.c b/src/nautilus-file-name-widget-controller.c
new file mode 100644
index 0000000..308ab51
--- /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_entry_get_text (GTK_ENTRY (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..3adf3b5
--- /dev/null
+++ b/src/nautilus-file-operations.c
@@ -0,0 +1,8894 @@
+/* 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 "nautilus-error-reporting.h"
+#include "nautilus-operations-ui-manager.h"
+#include "nautilus-file-changes-queue.h"
+#include "nautilus-file-private.h"
+#include "nautilus-global-preferences.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/gdkx.h>
+#endif
+
+#ifdef GDK_WINDOWING_WAYLAND
+#include <gdk/gdkwayland.h>
+#endif
+
+/* TODO: TESTING!!! */
+
+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;
+ goffset num_bytes;
+ int num_files_since_progress;
+ OpKind op;
+} SourceInfo;
+
+typedef struct
+{
+ int num_files;
+ goffset num_bytes;
+ OpKind op;
+ guint64 last_report_time;
+ int last_reported_files_left;
+} TransferInfo;
+
+typedef struct
+{
+ CommonJob common;
+ GList *source_files;
+ GFile *destination_directory;
+ GList *output_files;
+
+ gdouble base_progress;
+
+ guint64 archive_compressed_size;
+ guint64 total_compressed_size;
+
+ NautilusExtractCallback done_callback;
+ gpointer done_callback_data;
+} ExtractJob;
+
+typedef struct
+{
+ CommonJob common;
+ GList *source_files;
+ GFile *output_file;
+
+ AutoarFormat format;
+ AutoarFilter filter;
+
+ guint64 total_size;
+ guint total_files;
+
+ gboolean success;
+
+ NautilusCreateCallback done_callback;
+ gpointer done_callback_data;
+} CompressJob;
+
+#define SECONDS_NEEDED_FOR_RELIABLE_TRANSFER_RATE 8
+#define NSEC_PER_MICROSEC 1000
+#define PROGRESS_NOTIFY_INTERVAL 100 * NSEC_PER_MICROSEC
+
+#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 (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;
+ 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 (GdkWindow *child_window,
+ const char *parent_handle)
+{
+ GdkDisplay *display;
+ const char *prefix;
+
+ display = gdk_window_get_display (child_window);
+
+#ifdef GDK_WINDOWING_X11
+ if (GDK_IS_X11_DISPLAY (display))
+ {
+ prefix = "x11:";
+
+ if (g_str_has_prefix (parent_handle, prefix))
+ {
+ const char *handle;
+ GdkWindow *window;
+
+ handle = parent_handle + strlen (prefix);
+ window = gdk_x11_window_foreign_new_for_display (display, strtol (handle, NULL, 16));
+
+ if (window != NULL)
+ {
+ gdk_window_set_transient_for (child_window, window);
+ g_object_unref (window);
+ }
+ }
+ }
+#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_window_set_transient_for_exported (child_window, (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_widget_get_window (widget), parent_handle);
+}
+
+static gboolean
+do_run_simple_dialog (gpointer _data)
+{
+ RunSimpleDialogData *data = _data;
+ const char *button_title;
+ GtkWidget *dialog;
+ GtkWidget *button;
+ int result;
+ int response_id;
+
+ g_mutex_lock (&data->mutex);
+
+ /* Create the dialog. */
+ dialog = gtk_message_dialog_new (*data->parent_window,
+ 0,
+ 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->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_line_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_container_add (GTK_CONTAINER (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);
+
+ /* Assuming this is used for desktop implementations, we want the
+ * dialog to be centered on the screen rather than the parent window,
+ * which could extend to all monitors. This is the case for
+ * gnome-flashback.
+ */
+ gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER);
+
+ 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. */
+ result = gtk_dialog_run (GTK_DIALOG (dialog));
+
+ while ((result == GTK_RESPONSE_NONE || result == GTK_RESPONSE_DELETE_EVENT) && data->ignore_close_box)
+ {
+ result = gtk_dialog_run (GTK_DIALOG (dialog));
+ }
+
+ gtk_widget_destroy (dialog);
+
+ data->result = result;
+ data->completed = TRUE;
+
+ g_cond_signal (&data->cond);
+ g_mutex_unlock (&data->mutex);
+
+ 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);
+
+ 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);
+}
+
+/* Since this happens on a thread we can't use the global prefs object */
+static gboolean
+should_confirm_trash (void)
+{
+ GSettings *prefs;
+ gboolean confirm_trash;
+
+ prefs = g_settings_new ("org.gnome.nautilus.preferences");
+ confirm_trash = g_settings_get_boolean (prefs, NAUTILUS_PREFERENCES_CONFIRM_TRASH);
+ g_object_unref (prefs);
+ return confirm_trash;
+}
+
+static gboolean
+confirm_delete_from_trash (CommonJob *job,
+ GList *files)
+{
+ char *prompt;
+ int file_count;
+ int response;
+
+ /* Just Say Yes if the preference says not to confirm. */
+ if (!should_confirm_trash ())
+ {
+ return TRUE;
+ }
+
+ 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;
+
+ /* Just Say Yes if the preference says not to confirm. */
+ if (!should_confirm_trash ())
+ {
+ return TRUE;
+ }
+
+ 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;
+
+ /* Just Say Yes if the preference says not to confirm. */
+ if (!should_confirm_trash ())
+ {
+ return TRUE;
+ }
+
+ 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)
+ {
+ 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;
+ SourceInfo source_info;
+ 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)
+ {
+ 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_file_from_count (GFile *file,
+ CommonJob *job,
+ SourceInfo *source_info)
+{
+ g_autoptr (GFileInfo) file_info = NULL;
+
+ 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);
+ }
+}
+
+static void
+trash_files (CommonJob *job,
+ GList *files,
+ int *files_skipped)
+{
+ GList *l;
+ GFile *file;
+ GList *to_delete;
+ SourceInfo source_info;
+ 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 gint
+prompt_empty_trash (GtkWindow *parent_window)
+{
+ gint result;
+ GtkWidget *dialog;
+ GdkScreen *screen;
+
+ screen = NULL;
+ if (parent_window != NULL)
+ {
+ screen = gtk_widget_get_screen (GTK_WIDGET (parent_window));
+ }
+
+ /* Do we need to be modal ? */
+ dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL,
+ GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
+ _("Do you want to empty the trash before you unmount?"));
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ _("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."));
+ gtk_dialog_add_buttons (GTK_DIALOG (dialog),
+ _("Do _not Empty Trash"), GTK_RESPONSE_REJECT,
+ CANCEL, GTK_RESPONSE_CANCEL,
+ _("Empty _Trash"), GTK_RESPONSE_ACCEPT, NULL);
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
+ gtk_window_set_title (GTK_WINDOW (dialog), ""); /* as per HIG */
+ gtk_window_set_skip_taskbar_hint (GTK_WINDOW (dialog), TRUE);
+ if (screen)
+ {
+ gtk_window_set_screen (GTK_WINDOW (dialog), screen);
+ }
+ atk_object_set_role (gtk_widget_get_accessible (dialog), ATK_ROLE_ALERT);
+
+ /* Make transient for the window group */
+ gtk_widget_realize (dialog);
+ if (screen != NULL)
+ {
+ gdk_window_set_transient_for (gtk_widget_get_window (GTK_WIDGET (dialog)),
+ gdk_screen_get_root_window (screen));
+ }
+
+ result = gtk_dialog_run (GTK_DIALOG (dialog));
+ gtk_widget_destroy (dialog);
+ return result;
+}
+
+static void
+empty_trash_for_unmount_done (gboolean success,
+ gpointer user_data)
+{
+ UnmountData *data = user_data;
+ 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;
+ int response;
+
+ 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))
+ {
+ response = prompt_empty_trash (parent_window);
+
+ if (response == GTK_RESPONSE_ACCEPT)
+ {
+ GTask *task;
+ EmptyTrashJob *job;
+
+ job = op_job_new (EmptyTrashJob, parent_window, NULL);
+ job->should_confirm = FALSE;
+ job->trash_dirs = get_trash_dirs_for_mount (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);
+ return;
+ }
+ else if (response == GTK_RESPONSE_CANCEL)
+ {
+ if (callback)
+ {
+ callback (callback_data);
+ }
+
+ unmount_data_free (data);
+ 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)
+{
+ source_info->num_files += 1;
+ source_info->num_bytes += g_file_info_get_size (info);
+
+ 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,
+ GHashTable *scanned)
+{
+ GFileInfo *info;
+ GError *error;
+ GFile *subdir;
+ GFileEnumerator *enumerator;
+ char *primary, *secondary, *details;
+ int response;
+ SourceInfo saved_info;
+
+ saved_info = *source_info;
+
+retry:
+ 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)
+ {
+ g_autoptr (GFile) file = NULL;
+ g_autofree char *file_uri = NULL;
+
+ file = g_file_enumerator_get_child (enumerator, info);
+ file_uri = g_file_get_uri (file);
+
+ if (!g_hash_table_contains (scanned, file_uri))
+ {
+ g_hash_table_add (scanned, g_strdup (file_uri));
+
+ count_file (info, job, source_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));
+
+ /* Push to head, since we want depth-first */
+ g_queue_push_head (dirs, 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);
+ }
+ else if (response == 1)
+ {
+ *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);
+ }
+ 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);
+ }
+ else if (response == 1 || response == 2)
+ {
+ if (response == 1)
+ {
+ job->skip_all_error = TRUE;
+ }
+ skip_file (job, dir);
+ }
+ else if (response == 3)
+ {
+ goto retry;
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+ }
+}
+
+static void
+scan_file (GFile *file,
+ SourceInfo *source_info,
+ CommonJob *job,
+ GHashTable *scanned)
+{
+ 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)
+ {
+ g_autofree char *file_uri = NULL;
+
+ file_uri = g_file_get_uri (file);
+ if (!g_hash_table_contains (scanned, file_uri))
+ {
+ g_hash_table_add (scanned, g_strdup (file_uri));
+
+ count_file (info, job, source_info);
+
+ /* 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, scanned);
+ 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;
+ g_autoptr (GHashTable) scanned = NULL;
+
+ memset (source_info, 0, sizeof (SourceInfo));
+ source_info->op = kind;
+
+ scanned = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify) g_free,
+ NULL);
+
+ 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,
+ scanned);
+ }
+
+ /* 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;
+ 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,
+ 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;
+ }
+
+ if (required_size > 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)
+ {
+ 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 (!strcmp (dest_fs_type, "fat") ||
+ !strcmp (dest_fs_type, "vfat") ||
+ /* 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") ||
+ !strcmp (dest_fs_type, "msdos") ||
+ !strcmp (dest_fs_type, "msdosfs"))
+ {
+ 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);
+
+ 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)
+{
+ 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;
+
+ if (create_dest)
+ {
+ 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));
+ }
+ }
+
+ 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++;
+ 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 */
+ local_skipped_file = TRUE;
+ }
+ else if (response == 2)
+ {
+ goto retry;
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+ }
+
+ if (create_dest)
+ {
+ flags = (readonly_source_fs) ? G_FILE_COPY_NOFOLLOW_SYMLINKS | G_FILE_COPY_TARGET_DEFAULT_PERMS
+ : G_FILE_COPY_NOFOLLOW_SYMLINKS;
+ /* Ignore errors here. Failure to copy metadata is not a hard error */
+ g_file_copy_attributes (src, *dest,
+ flags,
+ job->cancellable, NULL);
+ }
+
+ if (!job_aborted (job) && copy_job->is_move &&
+ /* Don't delete source if there was a skipped file */
+ !local_skipped_file)
+ {
+ if (!g_file_delete (src, job->cancellable, &error))
+ {
+ g_autofree gchar *basename = NULL;
+
+ if (job->skip_all_error)
+ {
+ 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;
+ local_skipped_file = TRUE;
+ }
+ else if (response == 2) /* skip */
+ {
+ local_skipped_file = TRUE;
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+skip:
+ g_error_free (error);
+ }
+ }
+
+ if (local_skipped_file)
+ {
+ *skipped_file = TRUE;
+ }
+
+ 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;
+
+ 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_timer_stop (job->time);
+ nautilus_progress_info_pause (job->progress);
+
+ response = copy_move_conflict_ask_user_action (job->parent_window,
+ src,
+ dest,
+ dest_dir);
+
+ 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)
+ {
+ 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 (TRUE));
+ }
+ 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;
+ SourceInfo source_info;
+ 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;
+ }
+
+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;
+ SourceInfo source_info;
+ 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;
+ }
+
+ /* 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 (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;
+ }
+
+ extract_job->output_files = g_list_prepend (extract_job->output_files,
+ decided_destination);
+
+ 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;
+ gint response_id;
+ 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;
+ }
+
+ basename = get_basename (source_file);
+ nautilus_progress_info_take_status (extract_job->common.progress,
+ g_strdup_printf (_("Error extracting “%s”"),
+ basename));
+
+ response_id = run_warning ((CommonJob *) extract_job,
+ g_strdup_printf (_("There was an error while extracting “%s”."),
+ basename),
+ g_strdup (error->message),
+ NULL,
+ FALSE,
+ CANCEL,
+ SKIP,
+ NULL);
+
+ if (response_id == 0 || response_id == GTK_RESPONSE_DELETE_EVENT)
+ {
+ abort_job ((CommonJob *) extract_job);
+ }
+}
+
+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 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,
+ gint total_files)
+{
+ 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);
+
+ if (total_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”",
+ total_files),
+ 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));
+}
+
+static void
+extract_task_thread_func (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ ExtractJob *extract_job = task_data;
+ GList *l;
+ GList *existing_output_files = NULL;
+ gint total_files;
+ 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"));
+
+ total_files = g_list_length (extract_job->source_files);
+
+ archive_compressed_sizes = g_malloc0_n (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);
+
+ extract_job->archive_compressed_size = archive_compressed_sizes[i];
+
+ autoar_extractor_start (extractor,
+ extract_job->common.cancellable);
+
+ g_signal_handlers_disconnect_by_data (extractor,
+ extract_job);
+
+ extract_job->base_progress += (gdouble) extract_job->archive_compressed_size /
+ (gdouble) extract_job->total_compressed_size;
+ }
+
+ if (!job_aborted ((CommonJob *) extract_job))
+ {
+ report_extract_final_progress (extract_job, total_files);
+ }
+
+ for (l = extract_job->output_files; l != NULL; l = l->next)
+ {
+ GFile *output_file;
+
+ output_file = G_FILE (l->data);
+
+ if (g_file_query_exists (output_file, NULL))
+ {
+ existing_output_files = g_list_prepend (existing_output_files,
+ g_object_ref (output_file));
+ }
+ }
+
+ g_list_free_full (extract_job->output_files, g_object_unref);
+
+ extract_job->output_files = existing_output_files;
+
+ 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);
+
+ 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;
+ SourceInfo source_info;
+ 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);
+
+ 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,
+ 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->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);
+ }
+
+ 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..8236e0e
--- /dev/null
+++ b/src/nautilus-file-operations.h
@@ -0,0 +1,165 @@
+
+/* 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,
+ 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..ed9725b
--- /dev/null
+++ b/src/nautilus-file-private.h
@@ -0,0 +1,285 @@
+/*
+ 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 */
+
+ 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;
+
+ GdkPixbuf *scaled_thumbnail;
+ double thumbnail_scale;
+
+ 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..2a88617
--- /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 ()
+{
+ return undo_singleton->is_operating;
+}
+
+NautilusFileUndoManager *
+nautilus_file_undo_manager_get ()
+{
+ 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..b263a33
--- /dev/null
+++ b/src/nautilus-file-undo-operations.c
@@ -0,0 +1,2640 @@
+/* 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_parse_name (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);
+ NautilusTagManager *tag_manager;
+
+ tag_manager = nautilus_tag_manager_get ();
+
+ if (self->starred)
+ {
+ nautilus_tag_manager_star_files (tag_manager,
+ G_OBJECT (info),
+ self->files,
+ on_undo_starred_tags_updated,
+ NULL);
+ }
+ else
+ {
+ nautilus_tag_manager_unstar_files (tag_manager,
+ 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);
+ NautilusTagManager *tag_manager;
+
+ tag_manager = nautilus_tag_manager_get ();
+
+ if (self->starred)
+ {
+ nautilus_tag_manager_unstar_files (tag_manager,
+ G_OBJECT (info),
+ self->files,
+ on_undo_starred_tags_updated,
+ NULL);
+ }
+ else
+ {
+ nautilus_tag_manager_star_files (tag_manager,
+ 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;
+};
+
+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,
+ 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_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)
+{
+ 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;
+
+ 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..f96f2fe
--- /dev/null
+++ b/src/nautilus-file-undo-operations.h
@@ -0,0 +1,229 @@
+
+/* 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);
diff --git a/src/nautilus-file-utilities.c b/src/nautilus-file-utilities.c
new file mode 100644
index 0000000..2ee6e7c
--- /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-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)
+{
+ char *tmp_str;
+ 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;
+ }
+
+ g_free (tmp_str);
+
+ /* 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;
+ GFile *default_location = 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;
+ }
+
+ default_location = g_mount_get_default_location (mount);
+ if (!g_file_equal (default_location, root) &&
+ g_file_equal (location, default_location))
+ {
+ result = g_object_ref (mount);
+ break;
+ }
+ }
+
+ g_clear_object (&root);
+ g_clear_object (&default_location);
+ 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)
+{
+ GAppInfo *default_app;
+
+ 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;
+}
+
+gboolean
+nautilus_file_system_is_remote (const char *file_system)
+{
+ static const gchar * const remote_types[] =
+ {
+ "afp",
+ "google-drive",
+ "sftp",
+ "webdav",
+ "ftp",
+ "nfs",
+ "cifs",
+ NULL
+ };
+
+ return file_system != NULL && g_strv_contains (remote_types, file_system);
+}
diff --git a/src/nautilus-file-utilities.h b/src/nautilus-file-utilities.h
new file mode 100644
index 0000000..22d851c
--- /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 nautilus_file_system_is_remote (const char *file_system);
diff --git a/src/nautilus-file.c b/src/nautilus-file.c
new file mode 100644
index 0000000..3ba887e
--- /dev/null
+++ b/src/nautilus-file.c
@@ -0,0 +1,9811 @@
+/*
+ * 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-gtk-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_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->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->scaled_thumbnail)
+ {
+ g_object_unref (file->details->scaled_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://").
+ * If the parent is NULL, returns the empty string.
+ */
+char *
+nautilus_file_get_parent_uri_for_display (NautilusFile *file)
+{
+ GFile *parent;
+ char *result;
+
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ parent = nautilus_file_get_parent_location (file);
+ if (parent)
+ {
+ result = g_file_get_parse_name (parent);
+ g_object_unref (parent);
+ }
+ 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))
+ {
+ 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 (NULL, file->details->mount, NULL, 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))
+ {
+ 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 (NULL, file->details->mount, NULL, 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 (NautilusTagManager) tag_manager = nautilus_tag_manager_get ();
+ 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 (tag_manager, 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;
+}
+
+gboolean
+nautilus_file_is_local (NautilusFile *file)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE);
+
+ return nautilus_directory_is_local (file->details->directory);
+}
+
+gboolean
+nautilus_file_is_local_or_fuse (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;
+ 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);
+ 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;
+
+ 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_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);
+
+ 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;
+ g_autofree gchar *uri_1 = NULL;
+ g_autofree gchar *uri_2 = NULL;
+ gboolean file_1_is_starred;
+ gboolean file_2_is_starred;
+
+ tag_manager = nautilus_tag_manager_get ();
+
+ 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_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_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);
+}
+
+GList *
+nautilus_file_get_metadata_list (NautilusFile *file,
+ const char *key)
+{
+ GList *res;
+ guint id;
+ char **value;
+ int i;
+
+ 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));
+
+ if (value)
+ {
+ res = NULL;
+ for (i = 0; value[i] != NULL; i++)
+ {
+ res = g_list_prepend (res, g_strdup (value[i]));
+ }
+ return g_list_reverse (res);
+ }
+
+ return NULL;
+}
+
+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);
+}
+
+void
+nautilus_file_set_metadata_list (NautilusFile *file,
+ const char *key,
+ GList *list)
+{
+ char **val;
+ int len, i;
+ GList *l;
+
+ g_return_if_fail (NAUTILUS_IS_FILE (file));
+ g_return_if_fail (key != NULL);
+ g_return_if_fail (key[0] != '\0');
+
+ len = g_list_length (list);
+ val = g_new (char *, len + 1);
+ for (l = list, i = 0; l != NULL; l = l->next, i++)
+ {
+ val[i] = l->data;
+ }
+ val[i] = NULL;
+
+ NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->set_metadata_as_list (file, key, val);
+
+ g_free (val);
+}
+
+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);
+ }
+
+ 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))
+ {
+ 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;
+}
+
+gboolean
+nautilus_file_should_show_thumbnail (NautilusFile *file)
+{
+ const char *mime_type;
+ GFilesystemPreviewType use_preview;
+
+ use_preview = nautilus_file_get_filesystem_use_preview (file);
+
+ 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;
+ }
+
+ if (show_file_thumbs == NAUTILUS_SPEED_TRADEOFF_ALWAYS)
+ {
+ if (use_preview == G_FILESYSTEM_PREVIEW_TYPE_NEVER)
+ {
+ return FALSE;
+ }
+ else
+ {
+ return TRUE;
+ }
+ }
+ else if (show_file_thumbs == NAUTILUS_SPEED_TRADEOFF_NEVER)
+ {
+ return FALSE;
+ }
+ else
+ {
+ if (use_preview == G_FILESYSTEM_PREVIEW_TYPE_NEVER)
+ {
+ /* file system says to never thumbnail 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_local (file);
+ }
+ }
+
+ return FALSE;
+}
+
+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, *metadata_keywords;
+
+ 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_keywords = nautilus_file_get_metadata_list (file, NAUTILUS_METADATA_KEY_EMBLEMS);
+ 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: A list of emblem names.
+ *
+ **/
+static 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;
+}
+
+static void
+prepend_icon_name (const char *name,
+ GThemedIcon *icon)
+{
+ g_themed_icon_prepend_name (icon, name);
+}
+
+static void
+apply_emblems_to_icon (NautilusFile *file,
+ GIcon **icon,
+ NautilusFileIconFlags flags)
+{
+ GIcon *emblemed_icon = NULL;
+ g_autolist (GIcon) emblems = NULL;
+
+ emblems = nautilus_file_get_emblem_icons (file);
+
+ for (GList *l = emblems; l != NULL; l = l->next)
+ {
+ g_autoptr (GEmblem) emblem = NULL;
+
+ if (g_icon_equal (l->data, *icon))
+ {
+ continue;
+ }
+
+ emblem = g_emblem_new (l->data);
+
+ if (emblemed_icon == NULL)
+ {
+ emblemed_icon = g_emblemed_icon_new (*icon, emblem);
+ }
+ else
+ {
+ g_emblemed_icon_add_emblem (G_EMBLEMED_ICON (emblemed_icon), emblem);
+ }
+
+ if (emblemed_icon != NULL &&
+ (flags & NAUTILUS_FILE_ICON_FLAGS_USE_ONE_EMBLEM))
+ {
+ break;
+ }
+ }
+
+ if (emblemed_icon != NULL)
+ {
+ g_object_unref (*icon);
+ *icon = emblemed_icon;
+ }
+}
+
+GIcon *
+nautilus_file_get_gicon (NautilusFile *file,
+ NautilusFileIconFlags flags)
+{
+ const char * const *names;
+ const char *name;
+ GPtrArray *prepend_array;
+ GIcon *icon;
+ int i;
+ gboolean is_folder = FALSE, is_inode_directory = FALSE;
+
+ 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 = NULL;
+
+ if (((flags & NAUTILUS_FILE_ICON_FLAGS_FOR_DRAG_ACCEPT) ||
+ (flags & NAUTILUS_FILE_ICON_FLAGS_FOR_OPEN_FOLDER) ||
+ (flags & NAUTILUS_FILE_ICON_FLAGS_USE_MOUNT_ICON) ||
+ (flags & NAUTILUS_FILE_ICON_FLAGS_USE_EMBLEMS)) &&
+ G_IS_THEMED_ICON (file->details->icon))
+ {
+ names = g_themed_icon_get_names (G_THEMED_ICON (file->details->icon));
+ prepend_array = g_ptr_array_new ();
+
+ for (i = 0; names[i] != NULL; i++)
+ {
+ name = names[i];
+
+ if (strcmp (name, "folder") == 0)
+ {
+ is_folder = TRUE;
+ }
+ if (strcmp (name, "inode-directory") == 0)
+ {
+ is_inode_directory = TRUE;
+ }
+ }
+
+ /* Here, we add icons in reverse order of precedence,
+ * because they are later prepended */
+
+ /* "folder" should override "inode-directory", not the other way around */
+ if (is_inode_directory)
+ {
+ g_ptr_array_add (prepend_array, "folder");
+ }
+ if (is_folder && (flags & NAUTILUS_FILE_ICON_FLAGS_FOR_OPEN_FOLDER))
+ {
+ g_ptr_array_add (prepend_array, "folder-open");
+ }
+ if (is_folder &&
+ (flags & NAUTILUS_FILE_ICON_FLAGS_FOR_DRAG_ACCEPT))
+ {
+ g_ptr_array_add (prepend_array, "folder-drag-accept");
+ }
+
+ if (prepend_array->len)
+ {
+ /* When constructing GThemed Icon, pointers from the array
+ * are reused, but not the array itself, so the cast is safe */
+ icon = g_themed_icon_new_from_names ((char **) names, -1);
+ g_ptr_array_foreach (prepend_array, (GFunc) prepend_icon_name, icon);
+ }
+
+ g_ptr_array_free (prepend_array, TRUE);
+ }
+
+ if (icon == NULL)
+ {
+ icon = g_object_ref (file->details->icon);
+ }
+ }
+
+out:
+ if (icon == NULL)
+ {
+ icon = g_object_ref (get_default_file_icon ());
+ }
+
+ if (flags & NAUTILUS_FILE_ICON_FLAGS_USE_EMBLEMS)
+ {
+ apply_emblems_to_icon (file, &icon, flags);
+ }
+
+ 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)
+{
+ int modified_size;
+ GdkPixbuf *pixbuf;
+ int w, h, s;
+ double thumb_scale;
+ GIcon *gicon;
+ NautilusIconInfo *icon;
+
+ icon = NULL;
+ gicon = NULL;
+ pixbuf = NULL;
+
+ if (flags & NAUTILUS_FILE_ICON_FLAGS_FORCE_THUMBNAIL_SIZE)
+ {
+ modified_size = size * scale;
+ }
+ else
+ {
+ modified_size = size * scale * NAUTILUS_CANVAS_ICON_SIZE_STANDARD / NAUTILUS_CANVAS_ICON_SIZE_SMALL;
+ }
+
+ if (file->details->thumbnail)
+ {
+ w = gdk_pixbuf_get_width (file->details->thumbnail);
+ h = gdk_pixbuf_get_height (file->details->thumbnail);
+
+ s = MAX (w, h);
+ /* Don't scale up small thumbnails in the standard view */
+ if (s <= NAUTILUS_CANVAS_ICON_SIZE_STANDARD)
+ {
+ thumb_scale = (double) size / NAUTILUS_CANVAS_ICON_SIZE_SMALL;
+ }
+ else
+ {
+ thumb_scale = (double) modified_size / s;
+ }
+
+ /* Make sure that icons don't get smaller than NAUTILUS_LIST_ICON_SIZE_SMALL */
+ if (s * thumb_scale <= NAUTILUS_LIST_ICON_SIZE_SMALL)
+ {
+ thumb_scale = (double) NAUTILUS_LIST_ICON_SIZE_SMALL / s;
+ }
+
+ if (file->details->thumbnail_scale == thumb_scale &&
+ file->details->scaled_thumbnail != NULL)
+ {
+ pixbuf = file->details->scaled_thumbnail;
+ }
+ else
+ {
+ GdkPixbuf *bg_pixbuf;
+ int bg_size;
+
+ pixbuf = gdk_pixbuf_scale_simple (file->details->thumbnail,
+ MAX (w * thumb_scale, 1),
+ MAX (h * thumb_scale, 1),
+ GDK_INTERP_BILINEAR);
+
+ /* We don't want frames around small icons */
+ if (!gdk_pixbuf_get_has_alpha (file->details->thumbnail) || s >= 128 * scale)
+ {
+ gboolean use_experimental_views;
+
+ use_experimental_views = g_settings_get_boolean (nautilus_preferences,
+ NAUTILUS_PREFERENCES_USE_EXPERIMENTAL_VIEWS);
+ if (!use_experimental_views)
+ {
+ if (nautilus_is_video_file (file))
+ {
+ nautilus_ui_frame_video (&pixbuf);
+ }
+ else
+ {
+ nautilus_ui_frame_image (&pixbuf);
+ }
+ }
+ }
+
+ /* Copy to a transparent square pixbuf, aligned to the bottom edge */
+ bg_size = MAX (gdk_pixbuf_get_width (pixbuf), gdk_pixbuf_get_height (pixbuf));
+ bg_pixbuf = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (pixbuf),
+ TRUE,
+ gdk_pixbuf_get_bits_per_sample (pixbuf),
+ bg_size,
+ bg_size);
+ gdk_pixbuf_fill (bg_pixbuf, 0);
+ gdk_pixbuf_copy_area (pixbuf,
+ 0,
+ 0,
+ gdk_pixbuf_get_width (pixbuf),
+ gdk_pixbuf_get_height (pixbuf),
+ bg_pixbuf,
+ (bg_size - gdk_pixbuf_get_width (pixbuf)) / 2,
+ (bg_size - gdk_pixbuf_get_height (pixbuf)));
+ g_clear_object (&pixbuf);
+ pixbuf = bg_pixbuf;
+
+ g_clear_object (&file->details->scaled_thumbnail);
+ file->details->scaled_thumbnail = pixbuf;
+ file->details->thumbnail_scale = thumb_scale;
+ }
+
+ DEBUG ("Returning thumbnailed image, at size %d %d",
+ gdk_pixbuf_get_width (pixbuf), gdk_pixbuf_get_height (pixbuf));
+ }
+ 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 (pixbuf != NULL)
+ {
+ gicon = G_ICON (g_object_ref (pixbuf));
+ }
+ else if (file->details->is_thumbnailing)
+ {
+ gicon = g_themed_icon_new (ICON_NAME_THUMBNAIL_LOADING);
+ }
+
+ if (gicon != NULL)
+ {
+ apply_emblems_to_icon (file, &gicon, flags);
+
+ if (g_icon_equal (gicon, G_ICON (pixbuf)))
+ {
+ icon = nautilus_icon_info_new_for_pixbuf (pixbuf, scale);
+ }
+ else
+ {
+ icon = nautilus_icon_info_lookup (gicon, size, scale);
+ }
+
+ g_object_unref (gicon);
+ }
+
+ return icon;
+}
+
+static gboolean
+nautilus_thumbnail_is_limited_by_zoom (int size,
+ int scale)
+{
+ int zoom_level;
+
+ zoom_level = size * scale;
+
+ if (zoom_level <= NAUTILUS_LIST_ICON_SIZE_STANDARD)
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+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)
+ {
+ if (flags & NAUTILUS_FILE_ICON_FLAGS_USE_EMBLEMS)
+ {
+ apply_emblems_to_icon (file, &gicon, flags);
+ }
+
+ icon = nautilus_icon_info_lookup (gicon, size, scale);
+ g_object_unref (gicon);
+
+ goto out;
+ }
+
+ DEBUG ("Called file_get_icon(), at size %d, force thumbnail %d", size,
+ flags & NAUTILUS_FILE_ICON_FLAGS_FORCE_THUMBNAIL_SIZE);
+
+ if (flags & NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS &&
+ nautilus_file_should_show_thumbnail (file) &&
+ !nautilus_thumbnail_is_limited_by_zoom (size, scale))
+ {
+ 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;
+}
+
+GdkPixbuf *
+nautilus_file_get_icon_pixbuf (NautilusFile *file,
+ int size,
+ gboolean force_size,
+ int scale,
+ NautilusFileIconFlags flags)
+{
+ NautilusIconInfo *info;
+ GdkPixbuf *pixbuf;
+
+ info = nautilus_file_get_icon (file, size, scale, flags);
+ if (force_size)
+ {
+ pixbuf = nautilus_icon_info_get_pixbuf_at_size (info, size);
+ }
+ else
+ {
+ pixbuf = nautilus_icon_info_get_pixbuf (info);
+ }
+ g_object_unref (info);
+
+ return pixbuf;
+}
+
+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_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);
+}
+
+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;
+ }
+ }
+
+ if (value == NAUTILUS_SPEED_TRADEOFF_NEVER)
+ {
+ return FALSE;
+ }
+
+ g_assert (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_local (file);
+ }
+}
+
+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_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)
+{
+ uid_t user_id;
+
+ if (file->details->uid != -1 &&
+ nautilus_file_is_local (file))
+ {
+ /* 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_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 carriage return. 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)
+ {
+ name = g_strconcat (user->pw_name, "\n", 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_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: "Me" is used to indicate the file is owned by me (the current user) */
+ user_name = g_strdup (_("Me"));
+ }
+ 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;
+
+ 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_full (file->details->size, G_FORMAT_SIZE_LONG_FORMAT);
+}
+
+
+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_modified_full", "date_accessed_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_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)
+ {
+ return g_strdup (_("Unknown"));
+ }
+ 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 ("");
+ }
+
+ /* 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_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)
+ {
+ res = g_format_size (file->details->free_space);
+ }
+
+ 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;
+
+ 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_autofree char *filesystem_type = NULL;
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE);
+
+ if (nautilus_file_get_filesystem_remote (file))
+ {
+ return TRUE;
+ }
+
+ filesystem_type = nautilus_file_get_filesystem_type (file);
+
+ return nautilus_file_system_is_remote (filesystem_type);
+}
+
+/**
+ * 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_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);
+}
+
+/* DND */
+
+static gboolean
+nautilus_drag_can_accept_files (NautilusFile *drop_target_item)
+{
+ if (nautilus_file_is_directory (drop_target_item))
+ {
+ NautilusDirectory *directory;
+ gboolean res;
+
+ /* target is a directory, accept if editable */
+ directory = nautilus_directory_get_for_file (drop_target_item);
+ res = nautilus_directory_is_editable (directory) &&
+ nautilus_file_can_write (drop_target_item);
+ nautilus_directory_unref (directory);
+ return res;
+ }
+
+ if (nautilus_is_file_roller_installed () &&
+ nautilus_file_is_archive (drop_target_item))
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+gboolean
+nautilus_drag_can_accept_item (NautilusFile *drop_target_item,
+ const char *item_uri)
+{
+ if (nautilus_file_matches_uri (drop_target_item, item_uri))
+ {
+ /* can't accept itself */
+ return FALSE;
+ }
+
+ return nautilus_drag_can_accept_files (drop_target_item);
+}
+
+gboolean
+nautilus_drag_can_accept_items (NautilusFile *drop_target_item,
+ const GList *items)
+{
+ int max;
+
+ if (drop_target_item == NULL)
+ {
+ return FALSE;
+ }
+
+ g_assert (NAUTILUS_IS_FILE (drop_target_item));
+
+ /* Iterate through selection checking if item will get accepted by the
+ * drop target. If more than 100 items selected, return an over-optimisic
+ * result
+ */
+ for (max = 100; items != NULL && max >= 0; items = items->next, max--)
+ {
+ if (!nautilus_drag_can_accept_item (drop_target_item,
+ ((NautilusDragSelectionItem *) items->data)->uri))
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+gboolean
+nautilus_drag_can_accept_info (NautilusFile *drop_target_item,
+ NautilusIconDndTargetType drag_type,
+ const GList *items)
+{
+ switch (drag_type)
+ {
+ case NAUTILUS_ICON_DND_GNOME_ICON_LIST:
+ {
+ return nautilus_drag_can_accept_items (drop_target_item, items);
+ }
+
+ case NAUTILUS_ICON_DND_URI_LIST:
+ case NAUTILUS_ICON_DND_NETSCAPE_URL:
+ case NAUTILUS_ICON_DND_TEXT:
+ {
+ return nautilus_drag_can_accept_files (drop_target_item);
+ }
+
+ case NAUTILUS_ICON_DND_XDNDDIRECTSAVE:
+ case NAUTILUS_ICON_DND_RAW:
+ {
+ return nautilus_drag_can_accept_files (drop_target_item); /* Check if we can accept files at this location */
+ }
+
+ case NAUTILUS_ICON_DND_ROOTWINDOW_DROP:
+ {
+ return FALSE;
+ }
+
+ default:
+ g_assert_not_reached ();
+ return FALSE;
+ }
+}
+
+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..2164808
--- /dev/null
+++ b/src/nautilus-file.h
@@ -0,0 +1,618 @@
+/*
+ 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_STARRED = 5,
+
+ /* 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),
+ NAUTILUS_FILE_ICON_FLAGS_IGNORE_VISITING = (1<<1),
+ NAUTILUS_FILE_ICON_FLAGS_FOR_DRAG_ACCEPT = (1<<2),
+ NAUTILUS_FILE_ICON_FLAGS_FOR_OPEN_FOLDER = (1<<3),
+ /* whether the thumbnail size must match the display icon size */
+ NAUTILUS_FILE_ICON_FLAGS_FORCE_THUMBNAIL_SIZE = (1<<4),
+ /* uses the icon of the mount if present */
+ NAUTILUS_FILE_ICON_FLAGS_USE_MOUNT_ICON = (1<<5),
+ /* render emblems */
+ NAUTILUS_FILE_ICON_FLAGS_USE_EMBLEMS = (1<<6),
+ NAUTILUS_FILE_ICON_FLAGS_USE_ONE_EMBLEM = (1<<7)
+} NautilusFileIconFlags;
+
+/* Standard Drag & Drop types. */
+typedef enum {
+ NAUTILUS_ICON_DND_GNOME_ICON_LIST,
+ NAUTILUS_ICON_DND_URI_LIST,
+ NAUTILUS_ICON_DND_NETSCAPE_URL,
+ NAUTILUS_ICON_DND_TEXT,
+ NAUTILUS_ICON_DND_XDNDDIRECTSAVE,
+ NAUTILUS_ICON_DND_RAW,
+ NAUTILUS_ICON_DND_ROOTWINDOW_DROP
+} NautilusIconDndTargetType;
+
+/* Item of the drag selection list */
+typedef struct {
+ NautilusFile *file;
+ char *uri;
+ gboolean got_icon_position;
+ int icon_x, icon_y;
+ int icon_width, icon_height;
+} NautilusDragSelectionItem;
+
+/* 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_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);
+char * nautilus_file_get_owner_name (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);
+GList * 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,
+ GList *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);
+
+/* Is the file local? */
+gboolean nautilus_file_is_local (NautilusFile *file);
+gboolean nautilus_file_is_local_or_fuse (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);
+GdkPixbuf * nautilus_file_get_icon_pixbuf (NautilusFile *file,
+ int size,
+ gboolean force_size,
+ int scale,
+ NautilusFileIconFlags flags);
+
+/* 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);
+/* DND */
+gboolean nautilus_drag_can_accept_item (NautilusFile *drop_target_item,
+ const char *item_uri);
+
+gboolean nautilus_drag_can_accept_items (NautilusFile *drop_target_item,
+ const GList *items);
+
+gboolean nautilus_drag_can_accept_info (NautilusFile *drop_target_item,
+ NautilusIconDndTargetType drag_type,
+ const GList *items);
+
+/* 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_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..d5b4c21
--- /dev/null
+++ b/src/nautilus-files-view-dnd.c
@@ -0,0 +1,416 @@
+/*
+ * 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_netscape_url_drop (NautilusFilesView *view,
+ const char *encoded_url,
+ const char *target_uri,
+ GdkDragAction action)
+{
+ char *url;
+ char **bits;
+ GList *uri_list = NULL;
+ GFile *f;
+
+ f = g_file_new_for_uri (target_uri);
+
+ if (!g_file_is_native (f))
+ {
+ show_dialog (_("Drag and drop is not supported."),
+ _("Drag and drop is only supported on local file systems."),
+ GET_ANCESTOR (view),
+ GTK_MESSAGE_WARNING);
+ g_object_unref (f);
+ return;
+ }
+
+ g_object_unref (f);
+
+ /* _NETSCAPE_URL_ works like this: $URL\n$TITLE */
+ bits = g_strsplit (encoded_url, "\n", 0);
+ switch (g_strv_length (bits))
+ {
+ case 0:
+ {
+ g_strfreev (bits);
+ return;
+ }
+
+ default:
+ {
+ url = bits[0];
+ }
+ }
+
+ f = g_file_new_for_uri (url);
+
+ /* We don't support GDK_ACTION_ASK or GDK_ACTION_PRIVATE
+ * and we don't support combinations either. */
+ if ((action != GDK_ACTION_DEFAULT) &&
+ (action != GDK_ACTION_COPY) &&
+ (action != GDK_ACTION_MOVE))
+ {
+ show_dialog (_("Drag and drop is not supported."),
+ _("An invalid drag type was used."),
+ GET_ANCESTOR (view),
+ GTK_MESSAGE_WARNING);
+ return;
+ }
+
+ uri_list = g_list_append (uri_list, url);
+
+ nautilus_files_view_move_copy_items (view, uri_list,
+ target_uri,
+ action);
+
+ g_list_free (uri_list);
+
+ g_object_unref (f);
+ g_strfreev (bits);
+}
+
+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)
+ {
+ action = nautilus_drag_drop_action_ask
+ (GTK_WIDGET (view),
+ GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK);
+ if (action == 0)
+ {
+ g_free (container_uri);
+ return;
+ }
+ }
+
+ /* We don't support GDK_ACTION_ASK or GDK_ACTION_PRIVATE
+ * and we don't support combinations either. */
+ if ((action != GDK_ACTION_DEFAULT) &&
+ (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 128
+#define MIN_LEN_FILENAME 10
+
+static char *
+get_drop_filename (const char *text)
+{
+ char *filename;
+ char trimmed[MAX_LEN_FILENAME];
+ int i;
+ int last_word = -1;
+ int last_sentence = -1;
+ int last_nonspace = -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_white)
+ {
+ last_nonspace = i;
+ }
+ if (attrs[i].is_sentence_end)
+ {
+ last_sentence = last_nonspace;
+ }
+ if (attrs[i].is_word_boundary)
+ {
+ last_word = last_nonspace;
+ }
+ }
+ g_free (attrs);
+
+ if (last_sentence > 0)
+ {
+ i = last_sentence;
+ }
+ else
+ {
+ i = last_word;
+ }
+
+ if (i > MIN_LEN_FILENAME)
+ {
+ char basename[MAX_LEN_FILENAME];
+ g_utf8_strncpy (basename, trimmed, i);
+ filename = g_strdup_printf ("%s.txt", basename);
+ }
+ 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)
+{
+ char *container_uri;
+
+ 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)
+ {
+ action = nautilus_drag_drop_action_ask
+ (GTK_WIDGET (view),
+ GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK);
+ if (action == 0)
+ {
+ return;
+ }
+ }
+
+ nautilus_clipboard_clear_if_colliding_uris (GTK_WIDGET (view),
+ source_uri_list);
+
+ nautilus_files_view_move_copy_items (view, source_uri_list,
+ target_uri != NULL ? target_uri : container_uri,
+ action);
+
+ g_free (container_uri);
+}
+
+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_WINDOW_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..73b9263
--- /dev/null
+++ b/src/nautilus-files-view-dnd.h
@@ -0,0 +1,55 @@
+
+/*
+ * 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_handle_netscape_url_drop (NautilusFilesView *view,
+ const char *encoded_url,
+ const char *target_uri,
+ GdkDragAction action);
+
+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..b22bd84
--- /dev/null
+++ b/src/nautilus-files-view.c
@@ -0,0 +1,9928 @@
+/* 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-gtk-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 <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-batch-rename-dialog.h"
+#include "nautilus-batch-rename-utilities.h"
+#include "nautilus-canvas-view.h"
+#include "nautilus-clipboard.h"
+#include "nautilus-compress-dialog-controller.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-view-icon-controller.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;
+
+ 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 templates_present;
+ gboolean scripts_present;
+
+ gboolean in_destruction;
+
+ gboolean sort_directories_first;
+
+ gboolean show_hidden_files;
+ gboolean ignore_hidden_file_preferences;
+
+ gboolean batching_selection_level;
+ gboolean selection_changed_while_batched;
+
+ 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 *scrolled_window;
+
+ /* Empty states */
+ GtkWidget *folder_is_empty_widget;
+ GtkWidget *trash_is_empty_widget;
+ GtkWidget *no_search_results_widget;
+ GtkWidget *starred_is_empty_widget;
+
+ /* 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;
+ GtkWidget *sort_menu;
+ GtkWidget *sort_trash_time;
+ GtkWidget *visible_columns;
+ GtkWidget *stop;
+ GtkWidget *reload;
+ GtkWidget *zoom_controls_box;
+ GtkWidget *zoom_level_label;
+
+ /* Exposed menus, for the path bar etc. */
+ GMenuModel *extensions_background_menu;
+ GMenuModel *templates_menu;
+
+ gulong stop_signal_handler;
+ gulong reload_signal_handler;
+
+ GCancellable *starred_cancellable;
+ NautilusTagManager *tag_manager;
+
+ 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;
+
+/* 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 (GtkClipboard *clipboard,
+ GdkEvent *event,
+ gpointer user_data);
+static void open_one_in_new_window (gpointer data,
+ gpointer callback_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,
+ GTK_TYPE_GRID,
+ G_IMPLEMENT_INTERFACE (NAUTILUS_TYPE_VIEW, nautilus_files_view_iface_init)
+ G_ADD_PRIVATE (NautilusFilesView));
+
+static const struct
+{
+ unsigned int keyval;
+ const char *action;
+} extra_view_keybindings [] =
+{
+ /* View actions */
+ { GDK_KEY_ZoomIn, "zoom-in" },
+ { GDK_KEY_ZoomOut, "zoom-out" },
+};
+
+/*
+ * 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_cleanup_actions (NAUTILUS_FLOATING_BAR (priv->floating_bar));
+}
+
+static void
+real_setup_loading_floating_bar (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ nautilus_floating_bar_cleanup_actions (NAUTILUS_FLOATING_BAR (priv->floating_bar));
+ 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_add_action (NAUTILUS_FLOATING_BAR (priv->floating_bar),
+ "process-stop-symbolic",
+ NAUTILUS_FLOATING_BAR_ACTION_ID_STOP);
+
+ 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_action_cb (NautilusFloatingBar *floating_bar,
+ gint action,
+ NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ if (action == NAUTILUS_FLOATING_BAR_ACTION_ID_STOP)
+ {
+ 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_cleanup_actions (NAUTILUS_FLOATING_BAR (priv->floating_bar));
+ nautilus_floating_bar_set_show_spinner (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_overlay_set_overlay_pass_through (GTK_OVERLAY (priv->overlay),
+ priv->floating_bar, FALSE);
+ 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_screen (gtk_widget_get_screen (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_overlay_set_overlay_pass_through (GTK_OVERLAY (priv->overlay),
+ priv->floating_bar, TRUE);
+ 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 gfloat
+nautilus_files_view_get_zoom_level_percentage (NautilusFilesView *view)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), 1);
+
+ return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->get_zoom_level_percentage (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);
+}
+
+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 gboolean
+nautilus_files_view_confirm_multiple (GtkWindow *parent_window,
+ int count,
+ gboolean tabs)
+{
+ GtkDialog *dialog;
+ char *prompt;
+ char *detail;
+ int response;
+
+ if (count <= SILENT_WINDOW_OPEN_LIMIT)
+ {
+ return TRUE;
+ }
+
+ prompt = _("Are you sure you want to open all files?");
+ if (tabs)
+ {
+ detail = g_strdup_printf (ngettext ("This will open %'d separate tab.",
+ "This will open %'d separate tabs.", count), count);
+ }
+ else
+ {
+ detail = g_strdup_printf (ngettext ("This will open %'d separate window.",
+ "This will open %'d separate windows.", count), count);
+ }
+ dialog = eel_show_yes_no_dialog (prompt, detail,
+ _("_OK"), _("_Cancel"),
+ parent_window);
+ g_free (detail);
+
+ response = gtk_dialog_run (dialog);
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+
+ return response == GTK_RESPONSE_YES;
+}
+
+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);
+ }
+}
+
+void
+nautilus_files_view_preview_files (NautilusFilesView *view,
+ GList *files,
+ GArray *locations)
+{
+ PreviewExportData *data = g_new0 (PreviewExportData, 1);
+
+ data->uri = nautilus_file_get_uri (files->data);
+ data->is_update = FALSE;
+
+ nautilus_files_view_preview (view, data);
+}
+
+void
+nautilus_files_view_preview_update (NautilusFilesView *view,
+ GList *files)
+{
+ PreviewExportData *data;
+
+ if (!nautilus_previewer_is_visible ())
+ {
+ return;
+ }
+
+ data = g_new0 (PreviewExportData, 1);
+ data->uri = nautilus_file_get_uri (files->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,
+ NautilusWindowOpenFlags 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,
+ NautilusWindowOpenFlags 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;
+ GtkWindow *window;
+
+ view = NAUTILUS_FILES_VIEW (user_data);
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (view));
+
+ window = nautilus_files_view_get_containing_window (view);
+
+ if (nautilus_files_view_confirm_multiple (window, g_list_length (selection), TRUE))
+ {
+ nautilus_files_view_activate_files (view,
+ selection,
+ NAUTILUS_WINDOW_OPEN_FLAG_NEW_TAB |
+ NAUTILUS_WINDOW_OPEN_FLAG_DONT_MAKE_ACTIVE,
+ FALSE);
+ }
+}
+
+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 = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (dialog));
+
+ g_signal_emit_by_name (nautilus_signaller_get_current (), "mime-data-changed");
+
+ nautilus_launch_application (info, files, parent_window);
+
+ g_object_unref (info);
+out:
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+static void
+choose_program (NautilusFilesView *view,
+ GList *files)
+{
+ GtkWidget *dialog;
+ g_autofree gchar *mime_type = NULL;
+ GtkWindow *parent_window;
+
+ g_assert (NAUTILUS_IS_FILES_VIEW (view));
+
+ mime_type = nautilus_file_get_mime_type (files->data);
+ parent_window = nautilus_files_view_get_containing_window (view);
+
+ dialog = gtk_app_chooser_dialog_new_for_content_type (parent_window,
+ GTK_DIALOG_MODAL |
+ GTK_DIALOG_DESTROY_WITH_PARENT |
+ GTK_DIALOG_USE_HEADER_BAR,
+ mime_type);
+ 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
+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 (priv->tag_manager,
+ 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 (priv->tag_manager,
+ 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
+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_entry_get_text (GTK_ENTRY (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_widget_destroy (GTK_WIDGET (dialog));
+ }
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+select_pattern (NautilusFilesView *view)
+{
+ GtkWidget *dialog;
+ GtkWidget *label;
+ GtkWidget *example;
+ GtkWidget *grid;
+ GtkWidget *entry;
+ char *example_pattern;
+
+ dialog = gtk_dialog_new_with_buttons (_("Select Items Matching"),
+ nautilus_files_view_get_containing_window (view),
+ 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_container_set_border_width (GTK_CONTAINER (dialog), 5);
+ gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), 2);
+
+ label = gtk_label_new_with_mnemonic (_("_Pattern:"));
+ gtk_widget_set_halign (label, GTK_ALIGN_START);
+
+ example = gtk_label_new (NULL);
+ gtk_widget_set_halign (example, GTK_ALIGN_START);
+ example_pattern = g_strdup_printf ("%s<i>%s</i> ",
+ _("Examples: "),
+ "*.png, file\?\?.txt, pict*.\?\?\?");
+ gtk_label_set_markup (GTK_LABEL (example), example_pattern);
+ g_free (example_pattern);
+
+ entry = gtk_entry_new ();
+ gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE);
+ gtk_widget_set_hexpand (entry, TRUE);
+
+ grid = gtk_grid_new ();
+ g_object_set (grid,
+ "orientation", GTK_ORIENTATION_VERTICAL,
+ "border-width", 6,
+ "row-spacing", 6,
+ "column-spacing", 12,
+ NULL);
+
+ gtk_container_add (GTK_CONTAINER (grid), label);
+ gtk_grid_attach_next_to (GTK_GRID (grid), entry, label,
+ GTK_POS_RIGHT, 1, 1);
+ gtk_grid_attach_next_to (GTK_GRID (grid), example, entry,
+ GTK_POS_BOTTOM, 1, 1);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry);
+ gtk_widget_show_all (grid);
+ gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), grid);
+ g_object_set_data (G_OBJECT (dialog), "entry", entry);
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (pattern_select_response_cb),
+ view);
+ gtk_widget_show_all (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,
+ GTK_WIDGET (view));
+
+ 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;
+
+ 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));
+ }
+
+ 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 (data);
+}
+
+static void
+compress_dialog_controller_on_name_accepted (NautilusFileNameWidgetController *controller,
+ gpointer user_data)
+{
+ NautilusFilesView *view;
+ g_autofree gchar *name = NULL;
+ GList *selection;
+ 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;
+
+ view = NAUTILUS_FILES_VIEW (user_data);
+ priv = nautilus_files_view_get_instance_private (view);
+
+ selection = nautilus_files_view_get_selection_for_file_transfer (view);
+
+ for (l = 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_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,
+ nautilus_files_view_get_containing_window (view),
+ NULL,
+ compress_done,
+ data);
+
+ nautilus_file_list_free (selection);
+ 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
+nautilus_files_view_compress_dialog_new (NautilusFilesView *view)
+{
+ NautilusDirectory *containing_directory;
+ NautilusFilesViewPrivate *priv;
+ g_autolist (NautilusFile) selection = NULL;
+ g_autofree char *common_prefix = NULL;
+
+ 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);
+
+ g_signal_connect (priv->compress_controller,
+ "name-accepted",
+ (GCallback) compress_dialog_controller_on_name_accepted,
+ view);
+ 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;
+ }
+
+ g_return_if_fail (nautilus_file_is_local (source));
+
+ 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_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
+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;
+ GtkWindow *window;
+ GList *selection;
+
+ view = NAUTILUS_FILES_VIEW (user_data);
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (view));
+ window = GTK_WINDOW (nautilus_files_view_get_containing_window (view));
+
+ if (nautilus_files_view_confirm_multiple (window, g_list_length (selection), TRUE))
+ {
+ g_list_foreach (selection, open_one_in_new_window, view);
+ }
+
+ nautilus_file_list_free (selection);
+}
+
+static void
+handle_clipboard_data (NautilusFilesView *view,
+ const gchar *selection_data,
+ char *destination_uri,
+ GdkDragAction action)
+{
+ GList *item_uris;
+
+ item_uris = nautilus_clipboard_get_uri_list_from_selection_data (selection_data);
+
+ 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)
+ {
+ gtk_clipboard_clear (nautilus_clipboard_get (GTK_WIDGET (view)));
+ }
+
+ g_list_free_full (item_uris, g_free);
+ }
+}
+
+static void
+paste_clipboard_data (NautilusFilesView *view,
+ const gchar *selection_data,
+ char *destination_uri)
+{
+ GdkDragAction action;
+
+ if (nautilus_clipboard_is_cut_from_selection_data (selection_data))
+ {
+ action = GDK_ACTION_MOVE;
+ }
+ else
+ {
+ action = GDK_ACTION_COPY;
+ }
+
+ handle_clipboard_data (view, selection_data, destination_uri, action);
+}
+
+static void
+paste_clipboard_text_received_callback (GtkClipboard *clipboard,
+ const gchar *selection_data,
+ gpointer data)
+{
+ NautilusFilesView *view;
+ NautilusFilesViewPrivate *priv;
+ char *view_uri;
+
+ view = NAUTILUS_FILES_VIEW (data);
+ priv = nautilus_files_view_get_instance_private (view);
+
+ view_uri = nautilus_files_view_get_backing_uri (view);
+
+ if (priv->slot != NULL)
+ {
+ paste_clipboard_data (view, selection_data, view_uri);
+ }
+
+ g_free (view_uri);
+
+ g_object_unref (view);
+}
+
+static void
+paste_files (NautilusFilesView *view)
+{
+ GtkClipboard *clipboard;
+
+ clipboard = nautilus_clipboard_get (GTK_WIDGET (view));
+
+ /* Performing an async request of clipboard contents, corresponding unref
+ * is in the callback.
+ */
+ g_object_ref (view);
+
+ gtk_clipboard_request_text (clipboard,
+ paste_clipboard_text_received_callback,
+ view);
+}
+
+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 (GtkClipboard *clipboard,
+ const gchar *selection_data,
+ gpointer data)
+{
+ NautilusFilesView *view;
+ NautilusFilesViewPrivate *priv;
+ char *view_uri;
+
+ view = NAUTILUS_FILES_VIEW (data);
+ priv = nautilus_files_view_get_instance_private (view);
+
+ view_uri = nautilus_files_view_get_backing_uri (view);
+
+ if (priv->slot != NULL)
+ {
+ handle_clipboard_data (view, selection_data, view_uri, GDK_ACTION_LINK);
+ }
+
+ g_free (view_uri);
+
+ g_object_unref (view);
+}
+
+static void
+action_create_links (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusFilesView *view;
+
+ g_assert (NAUTILUS_IS_FILES_VIEW (user_data));
+
+ view = NAUTILUS_FILES_VIEW (user_data);
+
+ g_object_ref (view);
+ gtk_clipboard_request_text (nautilus_clipboard_get (GTK_WIDGET (view)),
+ create_links_clipboard_received_callback,
+ view);
+}
+
+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);
+
+ 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);
+
+ 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 void
+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_bin_get_child (GTK_BIN (priv->scrolled_window));
+
+ GTK_WIDGET_CLASS (nautilus_files_view_parent_class)->grab_focus (widget);
+
+ if (child)
+ {
+ gtk_widget_grab_focus (GTK_WIDGET (child));
+ }
+}
+
+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_destroy (GtkWidget *object)
+{
+ NautilusFilesView *view;
+ NautilusFilesViewPrivate *priv;
+ GtkClipboard *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);
+
+ 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 = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+ g_signal_handlers_disconnect_by_func (clipboard, on_clipboard_owner_changed, view);
+
+ nautilus_file_unref (priv->directory_as_file);
+ priv->directory_as_file = NULL;
+
+ g_clear_object (&priv->search_query);
+ g_clear_object (&priv->location);
+
+ /* We don't own the slot, so no unref */
+ priv->slot = NULL;
+
+ GTK_WIDGET_CLASS (nautilus_files_view_parent_class)->destroy (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->zoom_section);
+ g_clear_object (&priv->toolbar_menu_sections->extended_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);
+ g_free (priv->toolbar_menu_sections);
+
+ g_hash_table_destroy (priv->non_ready_files);
+ g_hash_table_destroy (priv->pending_reveal);
+
+ 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 *folder_count_str;
+ char *folder_item_count_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;
+ non_folder_count_str = NULL;
+ non_folder_item_count_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
+ {
+ /* 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, %s %s"),
+ folder_count_str,
+ folder_item_count_str,
+ non_folder_count_str,
+ non_folder_item_count_str);
+ detail_status = NULL;
+ }
+
+ g_free (first_item_name);
+ g_free (folder_count_str);
+ g_free (folder_item_count_str);
+ g_free (non_folder_count_str);
+ g_free (non_folder_item_count_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;
+ g_autofree gchar *uri = NULL;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ gtk_widget_hide (priv->no_search_results_widget);
+ gtk_widget_hide (priv->folder_is_empty_widget);
+ gtk_widget_hide (priv->trash_is_empty_widget);
+ gtk_widget_hide (priv->starred_is_empty_widget);
+
+ if (!priv->loading &&
+ nautilus_files_view_is_empty (view))
+ {
+ uri = g_file_get_uri (priv->location);
+
+ if (nautilus_view_is_searching (NAUTILUS_VIEW (view)))
+ {
+ gtk_widget_show (priv->no_search_results_widget);
+ }
+ else if (eel_uri_is_trash_root (uri))
+ {
+ gtk_widget_show (priv->trash_is_empty_widget);
+ }
+ else if (eel_uri_is_starred (uri))
+ {
+ gtk_widget_show (priv->starred_is_empty_widget);
+ }
+ else
+ {
+ gtk_widget_show (priv->folder_is_empty_widget);
+ }
+ }
+}
+
+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_VIEW_ICON_CONTROLLER (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);
+}
+
+static int
+compare_files_cover (gconstpointer a,
+ gconstpointer b,
+ gpointer callback_data)
+{
+ const FileAndDirectory *fad1, *fad2;
+ NautilusFilesView *view;
+
+ view = callback_data;
+ fad1 = a;
+ fad2 = b;
+
+ if (fad1->directory < fad2->directory)
+ {
+ return -1;
+ }
+ else if (fad1->directory > fad2->directory)
+ {
+ return 1;
+ }
+ else
+ {
+ return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->compare_files (view, fad1->file, fad2->file);
+ }
+}
+static void
+sort_files (NautilusFilesView *view,
+ GList **list)
+{
+ *list = g_list_sort_with_data (*list, compare_files_cover, view);
+}
+
+/* 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 any files were added to old_added_files, then resort it. */
+ if (old_added_files != priv->old_added_files)
+ {
+ priv->old_added_files = old_added_files;
+ sort_files (view, &priv->old_added_files);
+ }
+
+ /* Resort old_changed_files too, since file attributes
+ * relevant to sorting could have changed.
+ */
+ if (old_changed_files != priv->old_changed_files)
+ {
+ priv->old_changed_files = old_changed_files;
+ sort_files (view, &priv->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));
+ }
+ }
+
+ 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)
+{
+ 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_assert (!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_assert (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);
+}
+
+static void
+open_one_in_new_window (gpointer data,
+ gpointer callback_data)
+{
+ g_assert (NAUTILUS_IS_FILE (data));
+ g_assert (NAUTILUS_IS_FILES_VIEW (callback_data));
+
+ nautilus_files_view_activate_file (NAUTILUS_FILES_VIEW (callback_data),
+ NAUTILUS_FILE (data),
+ NAUTILUS_WINDOW_OPEN_FLAG_NEW_WINDOW);
+}
+
+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 GdkPixbuf *
+get_menu_icon_for_file (NautilusFile *file,
+ GtkWidget *widget)
+{
+ NautilusIconInfo *info;
+ GdkPixbuf *pixbuf;
+ int size, scale;
+
+ size = nautilus_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU);
+ scale = gtk_widget_get_scale_factor (widget);
+
+ info = nautilus_file_get_icon (file, size, scale, 0);
+ pixbuf = nautilus_icon_info_get_pixbuf_nodefault_at_size (info, size);
+ g_object_unref (info);
+
+ return pixbuf;
+}
+
+static GList *
+get_extension_selection_menu_items (NautilusFilesView *view)
+{
+ NautilusWindow *window;
+ GList *items;
+ GList *providers;
+ GList *l;
+ g_autolist (NautilusFile) selection = NULL;
+
+ window = nautilus_files_view_get_window (view);
+ 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,
+ GTK_WIDGET (window),
+ 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;
+ NautilusWindow *window;
+ GList *items;
+ GList *providers;
+ GList *l;
+
+ priv = nautilus_files_view_get_instance_private (view);
+ window = nautilus_files_view_get_window (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,
+ GTK_WIDGET (window),
+ 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)
+{
+ NautilusFile *file;
+ char **parameters;
+ GList *node;
+ GFile *file_location;
+ GFile *model_location;
+ int i;
+
+ if (model == NULL)
+ {
+ return NULL;
+ }
+
+ parameters = g_new (char *, g_list_length (selection) + 1);
+
+ model_location = nautilus_directory_get_location (model);
+
+ for (node = selection, i = 0; node != NULL; node = node->next, i++)
+ {
+ file = NAUTILUS_FILE (node->data);
+
+ if (!nautilus_file_is_local (file))
+ {
+ parameters[i] = NULL;
+ g_strfreev (parameters);
+ return NULL;
+ }
+
+ file_location = nautilus_file_get_location (NAUTILUS_FILE (node->data));
+ parameters[i] = g_file_get_relative_path (model_location, file_location);
+ if (parameters[i] == NULL)
+ {
+ parameters[i] = g_file_get_path (file_location);
+ }
+ g_object_unref (file_location);
+ }
+
+ g_object_unref (model_location);
+
+ parameters[i] = NULL;
+ return parameters;
+}
+
+static char *
+get_file_paths_or_uris_as_newline_delimited_string (NautilusFilesView *view,
+ GList *selection,
+ gboolean get_paths)
+{
+ char *path;
+ char *uri;
+ char *result;
+ GString *expanding_string;
+ GList *node;
+
+ expanding_string = g_string_new ("");
+ for (node = selection; node != NULL; node = node->next)
+ {
+ uri = nautilus_file_get_uri (NAUTILUS_FILE (node->data));
+ if (uri == NULL)
+ {
+ continue;
+ }
+
+ if (get_paths)
+ {
+ path = g_filename_from_uri (uri, NULL, NULL);
+ if (path != NULL)
+ {
+ g_string_append (expanding_string, path);
+ g_free (path);
+ g_string_append (expanding_string, "\n");
+ }
+ }
+ else
+ {
+ g_string_append (expanding_string, uri);
+ g_string_append (expanding_string, "\n");
+ }
+ g_free (uri);
+ }
+
+ result = expanding_string->str;
+ g_string_free (expanding_string, FALSE);
+
+ return result;
+}
+
+static char *
+get_file_paths_as_newline_delimited_string (NautilusFilesView *view,
+ GList *selection)
+{
+ return get_file_paths_or_uris_as_newline_delimited_string (view, selection, TRUE);
+}
+
+static char *
+get_file_uris_as_newline_delimited_string (NautilusFilesView *view,
+ GList *selection)
+{
+ return get_file_paths_or_uris_as_newline_delimited_string (view, selection, FALSE);
+}
+
+/* returns newly allocated strings for setting the environment variables */
+static void
+get_strings_for_environment_variables (NautilusFilesView *view,
+ GList *selected_files,
+ char **file_paths,
+ char **uris,
+ char **uri)
+{
+ NautilusFilesViewPrivate *priv;
+ char *directory_uri;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ /* We need to check that the directory uri starts with "file:" since
+ * nautilus_directory_is_local returns FALSE for nfs.
+ */
+ directory_uri = nautilus_directory_get_uri (priv->model);
+ if (g_str_has_prefix (directory_uri, "file:") ||
+ eel_uri_is_trash (directory_uri) ||
+ eel_uri_is_search (directory_uri))
+ {
+ *file_paths = get_file_paths_as_newline_delimited_string (view, selected_files);
+ }
+ else
+ {
+ *file_paths = g_strdup ("");
+ }
+ g_free (directory_uri);
+
+ *uris = get_file_uris_as_newline_delimited_string (view, selected_files);
+
+ *uri = nautilus_directory_get_uri (priv->model);
+}
+
+/*
+ * 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)
+{
+ char *file_paths;
+ char *uris;
+ char *uri;
+ char *geometry_string;
+
+ get_strings_for_environment_variables (view, selected_files,
+ &file_paths, &uris, &uri);
+
+ g_setenv ("NAUTILUS_SCRIPT_SELECTED_FILE_PATHS", file_paths, TRUE);
+ g_free (file_paths);
+
+ g_setenv ("NAUTILUS_SCRIPT_SELECTED_URIS", uris, TRUE);
+ g_free (uris);
+
+ g_setenv ("NAUTILUS_SCRIPT_CURRENT_URI", uri, TRUE);
+ g_free (uri);
+
+ geometry_string = eel_gtk_window_get_geometry_string
+ (GTK_WINDOW (nautilus_files_view_get_containing_window (view)));
+ g_setenv ("NAUTILUS_SCRIPT_WINDOW_GEOMETRY", geometry_string, TRUE);
+ g_free (geometry_string);
+}
+
+/* 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");
+ g_unsetenv ("NAUTILUS_SCRIPT_WINDOW_GEOMETRY");
+}
+
+static void
+run_script (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusFilesViewPrivate *priv;
+ ScriptLaunchParameters *launch_parameters;
+ GdkScreen *screen;
+ g_autolist (NautilusFile) selection = NULL;
+ char *file_uri;
+ g_autofree char *local_file_path = NULL;
+ char *quoted_path;
+ char *old_working_dir;
+ char **parameters;
+
+ 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);
+ g_free (file_uri);
+
+ 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);
+
+ screen = gtk_widget_get_screen (GTK_WIDGET (launch_parameters->directory_view));
+
+ DEBUG ("run_script, script_path=“%s” (omitting script parameters)",
+ local_file_path);
+
+ nautilus_launch_application_from_command_array (screen, quoted_path, FALSE,
+ (const char * const *) parameters);
+ g_strfreev (parameters);
+
+ unset_script_environment_variables ();
+ g_chdir (old_working_dir);
+ g_free (old_working_dir);
+ g_free (quoted_path);
+}
+
+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;
+ GdkPixbuf *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;
+ GList *sorted_copy, *node;
+ NautilusDirectory *directory;
+ GMenu *submenu;
+ char *uri;
+
+ 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 (node = sorted_copy; node != NULL; node = node->next)
+ {
+ directory = node->data;
+
+ uri = nautilus_directory_get_uri (directory);
+ if (!directory_belongs_in_scripts_menu (uri))
+ {
+ remove_directory_from_scripts_directory_list (view, directory);
+ }
+ g_free (uri);
+ }
+ nautilus_directory_list_free (sorted_copy);
+
+ directory = nautilus_directory_get_by_uri (scripts_directory_uri);
+ submenu = update_directory_in_scripts_menu (view, directory);
+ if (submenu != NULL)
+ {
+ GObject *object;
+
+ object = gtk_builder_get_object (builder, "scripts-submenu");
+ nautilus_gmenu_set_from_model (G_MENU (object),
+ G_MENU_MODEL (submenu));
+
+ g_object_unref (submenu);
+ }
+
+ nautilus_directory_unref (directory);
+
+ priv->scripts_present = submenu != NULL;
+}
+
+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 *tmp, *uri, *name;
+ g_autofree gchar *escaped_uri = NULL;
+ GdkPixbuf *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);
+ tmp = nautilus_file_get_display_name (file);
+ name = eel_filename_strip_extension (tmp);
+ g_free (tmp);
+
+ 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;
+ GList *sorted_copy, *node;
+ NautilusDirectory *directory;
+ g_autoptr (GMenuModel) submenu = NULL;
+ char *uri;
+ char *templates_directory_uri;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ if (nautilus_should_use_templates_directory ())
+ {
+ templates_directory_uri = nautilus_get_templates_directory_uri ();
+ }
+ else
+ {
+ priv->templates_present = FALSE;
+ return;
+ }
+
+
+ sorted_copy = nautilus_directory_list_sort_by_uri
+ (nautilus_directory_list_copy (priv->templates_directory_list));
+
+ for (node = sorted_copy; node != NULL; node = node->next)
+ {
+ directory = node->data;
+
+ uri = nautilus_directory_get_uri (directory);
+ if (!directory_belongs_in_templates_menu (templates_directory_uri, uri))
+ {
+ remove_directory_from_templates_directory_list (view, directory);
+ }
+ g_free (uri);
+ }
+ nautilus_directory_list_free (sorted_copy);
+
+ directory = nautilus_directory_get_by_uri (templates_directory_uri);
+ submenu = update_directory_in_templates_menu (view, directory);
+ if (submenu != NULL)
+ {
+ GObject *object;
+ object = gtk_builder_get_object (builder, "templates-submenu");
+ nautilus_gmenu_set_from_model (G_MENU (object), submenu);
+ }
+
+ nautilus_view_set_templates_menu (NAUTILUS_VIEW (view), submenu);
+
+ nautilus_directory_unref (directory);
+
+ priv->templates_present = submenu != NULL;
+
+ g_free (templates_directory_uri);
+}
+
+
+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 gboolean
+uri_is_parent_of_selection (GList *selection,
+ const char *uri)
+{
+ gboolean found;
+ GList *l;
+ GFile *file;
+
+ found = FALSE;
+
+ file = g_file_new_for_uri (uri);
+ for (l = selection; !found && l != NULL; l = l->next)
+ {
+ GFile *parent;
+ parent = nautilus_file_get_parent_location (l->data);
+ found = g_file_equal (file, parent);
+ g_object_unref (parent);
+ }
+ g_object_unref (file);
+ return found;
+}
+
+static void
+on_destination_dialog_folder_changed (GtkFileChooser *chooser,
+ gpointer user_data)
+{
+ CopyCallbackData *copy_data = user_data;
+ char *uri;
+ gboolean found;
+
+ uri = gtk_file_chooser_get_current_folder_uri (chooser);
+ found = uri_is_parent_of_selection (copy_data->selection, uri);
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (chooser), GTK_RESPONSE_OK, !found);
+ g_free (uri);
+}
+
+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)
+ {
+ char *target_uri;
+ GList *uris, *l;
+
+ target_uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog));
+
+ 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_widget_destroy (GTK_WIDGET (dialog));
+}
+
+static gboolean
+destination_dialog_filter_cb (const GtkFileFilterInfo *filter_info,
+ gpointer user_data)
+{
+ GList *selection = user_data;
+ GList *l;
+
+ for (l = selection; l != NULL; l = l->next)
+ {
+ char *uri;
+ uri = nautilus_file_get_uri (l->data);
+ if (strcmp (uri, filter_info->uri) == 0)
+ {
+ g_free (uri);
+ return FALSE;
+ }
+ g_free (uri);
+ }
+
+ return TRUE;
+}
+
+static GList *
+get_selected_folders (GList *selection)
+{
+ GList *folders;
+ GList *l;
+
+ folders = NULL;
+ for (l = selection; l != NULL; l = l->next)
+ {
+ if (nautilus_file_is_directory (l->data))
+ {
+ folders = g_list_prepend (folders, nautilus_file_ref (l->data));
+ }
+ }
+ return g_list_reverse (folders);
+}
+
+static void
+copy_or_move_selection (NautilusFilesView *view,
+ gboolean is_move)
+{
+ NautilusFilesViewPrivate *priv;
+ GtkWidget *dialog;
+ char *uri;
+ 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_file_chooser_set_local_only (GTK_FILE_CHOOSER (dialog), FALSE);
+
+ 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 (selection != NULL)
+ {
+ GtkFileFilter *filter;
+ GList *folders;
+
+ folders = get_selected_folders (selection);
+
+ filter = gtk_file_filter_new ();
+ gtk_file_filter_add_custom (filter,
+ GTK_FILE_FILTER_URI,
+ destination_dialog_filter_cb,
+ folders,
+ (GDestroyNotify) nautilus_file_list_free);
+ gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), filter);
+ }
+
+
+ if (nautilus_view_is_searching (NAUTILUS_VIEW (view)))
+ {
+ directory = nautilus_search_directory_get_base_model (NAUTILUS_SEARCH_DIRECTORY (priv->model));
+ uri = nautilus_directory_get_uri (directory);
+ }
+ else
+ {
+ uri = nautilus_directory_get_uri (priv->model);
+ }
+
+ gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (dialog), uri);
+ g_free (uri);
+ g_signal_connect (dialog, "current-folder-changed",
+ G_CALLBACK (on_destination_dialog_folder_changed),
+ copy_data);
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (on_destination_dialog_response),
+ copy_data);
+
+ gtk_widget_show_all (dialog);
+}
+
+static void
+action_copy (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusFilesView *view;
+ GtkClipboard *clipboard;
+ GList *selection;
+
+ view = NAUTILUS_FILES_VIEW (user_data);
+
+ selection = nautilus_files_view_get_selection_for_file_transfer (view);
+ clipboard = nautilus_clipboard_get (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;
+ GtkClipboard *clipboard;
+
+ view = NAUTILUS_FILES_VIEW (user_data);
+
+ selection = nautilus_files_view_get_selection_for_file_transfer (view);
+ clipboard = nautilus_clipboard_get (GTK_WIDGET (view));
+ nautilus_clipboard_prepare_for_files (clipboard, selection, TRUE);
+
+ nautilus_file_list_free (selection);
+}
+
+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);
+}
+
+typedef struct
+{
+ NautilusFilesView *view;
+ NautilusFile *target;
+} PasteIntoData;
+
+static void
+paste_into_clipboard_received_callback (GtkClipboard *clipboard,
+ const gchar *selection_data,
+ gpointer callback_data)
+{
+ NautilusFilesViewPrivate *priv;
+ PasteIntoData *data;
+ NautilusFilesView *view;
+ char *directory_uri;
+
+ data = (PasteIntoData *) callback_data;
+
+ view = NAUTILUS_FILES_VIEW (data->view);
+ priv = nautilus_files_view_get_instance_private (view);
+
+ if (priv->slot != NULL)
+ {
+ directory_uri = nautilus_file_get_activation_uri (data->target);
+
+ paste_clipboard_data (view, selection_data, directory_uri);
+
+ g_free (directory_uri);
+ }
+
+ g_object_unref (view);
+ nautilus_file_unref (data->target);
+ g_free (data);
+}
+
+static void
+paste_into (NautilusFilesView *view,
+ NautilusFile *target)
+{
+ PasteIntoData *data;
+
+ g_assert (NAUTILUS_IS_FILES_VIEW (view));
+ g_assert (NAUTILUS_IS_FILE (target));
+
+ data = g_new (PasteIntoData, 1);
+
+ data->view = g_object_ref (view);
+ data->target = nautilus_file_ref (target);
+
+ gtk_clipboard_request_text (nautilus_clipboard_get (GTK_WIDGET (view)),
+ paste_into_clipboard_received_callback,
+ data);
+}
+
+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)
+ {
+ GdkCursor *cursor;
+ GdkDisplay *display;
+
+ display = gtk_widget_get_display (GTK_WIDGET (nautilus_files_view_get_window (view)));
+ cursor = gdk_cursor_new_from_name (display, "progress");
+ gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (nautilus_files_view_get_window (view))),
+ cursor);
+ g_object_unref (cursor);
+
+ dialog = nautilus_batch_rename_dialog_new (selection,
+ nautilus_files_view_get_model (view),
+ nautilus_files_view_get_window (view));
+
+ 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_widget_destroy (GTK_WIDGET (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_autofree char *uri = 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_file_chooser_set_local_only (GTK_FILE_CHOOSER (dialog), FALSE);
+
+ 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);
+ uri = nautilus_directory_get_uri (directory);
+ }
+ else
+ {
+ uri = nautilus_directory_get_uri (priv->model);
+ }
+
+ gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (dialog), uri);
+
+ 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_all (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);
+}
+
+
+#define BG_KEY_PRIMARY_COLOR "primary-color"
+#define BG_KEY_SECONDARY_COLOR "secondary-color"
+#define BG_KEY_COLOR_TYPE "color-shading-type"
+#define BG_KEY_PICTURE_PLACEMENT "picture-options"
+#define BG_KEY_PICTURE_URI "picture-uri"
+
+static void
+set_uri_as_wallpaper (const char *uri)
+{
+ GSettings *settings;
+
+ settings = gnome_background_preferences;
+
+ g_settings_delay (settings);
+
+ if (uri == NULL)
+ {
+ uri = "";
+ }
+
+ g_settings_set_string (settings, BG_KEY_PICTURE_URI, uri);
+ g_settings_set_string (settings, BG_KEY_PRIMARY_COLOR, "#000000");
+ g_settings_set_string (settings, BG_KEY_SECONDARY_COLOR, "#000000");
+ g_settings_set_enum (settings, BG_KEY_COLOR_TYPE, G_DESKTOP_BACKGROUND_SHADING_SOLID);
+ g_settings_set_enum (settings, BG_KEY_PICTURE_PLACEMENT, G_DESKTOP_BACKGROUND_STYLE_ZOOM);
+
+ /* Apply changes atomically. */
+ g_settings_apply (settings);
+}
+
+static void
+wallpaper_copy_done_callback (GHashTable *debuting_files,
+ gboolean success,
+ gpointer data)
+{
+ GHashTableIter iter;
+ gpointer key, value;
+
+ g_hash_table_iter_init (&iter, debuting_files);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ char *uri;
+ uri = g_file_get_uri (G_FILE (key));
+ set_uri_as_wallpaper (uri);
+ g_free (uri);
+ break;
+ }
+}
+
+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
+action_set_as_wallpaper (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ g_autolist (NautilusFile) selection = NULL;
+
+ /* Copy the item to Pictures/Wallpaper (internationalized) since it may be
+ * remote. Then set it as the current wallpaper. */
+
+ g_assert (NAUTILUS_IS_FILES_VIEW (user_data));
+
+ selection = nautilus_view_get_selection (user_data);
+
+ if (can_set_wallpaper (selection))
+ {
+ NautilusFile *file;
+ char *target_uri;
+ GList *uris;
+ GFile *parent;
+ GFile *target;
+
+ file = NAUTILUS_FILE (selection->data);
+
+ parent = g_file_new_for_path (g_get_user_special_dir (G_USER_DIRECTORY_PICTURES));
+ target = g_file_get_child (parent, _("Wallpapers"));
+ g_object_unref (parent);
+ g_file_make_directory_with_parents (target, NULL, NULL);
+ target_uri = g_file_get_uri (target);
+ g_object_unref (target);
+ uris = g_list_prepend (NULL, nautilus_file_get_uri (file));
+ nautilus_file_operations_copy_move (uris,
+ target_uri,
+ GDK_ACTION_COPY,
+ GTK_WIDGET (user_data),
+ NULL,
+ wallpaper_copy_done_callback,
+ NULL);
+ g_free (target_uri);
+ g_list_free_full (uris, g_free);
+ }
+}
+
+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 */
+ { "new-folder", action_new_folder },
+ { "select-all", action_select_all },
+ { "paste", action_paste_files },
+ { "paste_accel", action_paste_files_accel },
+ { "create-link", action_create_links },
+ { "new-document" },
+ /* Selection menu */
+ { "scripts" },
+ { "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-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 },
+ { "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 },
+ { "properties", action_properties},
+ { "current-directory-properties", action_current_dir_properties},
+ { "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 },
+};
+
+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
+on_clipboard_contents_received (GtkClipboard *clipboard,
+ const gchar *selection_data,
+ gpointer user_data)
+{
+ NautilusFilesViewPrivate *priv;
+ NautilusFilesView *view;
+ 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;
+ gboolean is_data_valid;
+
+ view = NAUTILUS_FILES_VIEW (user_data);
+ priv = nautilus_files_view_get_instance_private (view);
+
+ if (priv->slot == NULL ||
+ !priv->active)
+ {
+ /* We've been destroyed or became inactive since call */
+ g_object_unref (view);
+ return;
+ }
+
+ is_data_valid = nautilus_clipboard_is_data_valid_from_selection_data (selection_data);
+ 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 = !nautilus_clipboard_is_cut_from_selection_data (selection_data) &&
+ !selection_contains_recent && !selection_contains_starred &&
+ !is_read_only && selection_data != NULL;
+
+ 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),
+ "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_valid && 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_valid && 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_valid && g_action_get_enabled (action));
+
+
+ g_object_unref (view);
+}
+
+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 (GtkClipboard *clipboard,
+ GdkEvent *event,
+ gpointer user_data)
+{
+ NautilusFilesView *self = NAUTILUS_FILES_VIEW (user_data);
+
+ /* Update paste menu item */
+ nautilus_files_view_update_context_menus (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;
+ 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;
+
+ 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);
+ 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_contains_recent &&
+ !selection_contains_starred &&
+ 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);
+ /* 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));
+
+ 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);
+
+ 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),
+ "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),
+ "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);
+
+ /* 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);
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
+ "scripts");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
+ priv->scripts_present);
+
+ /* Background menu actions */
+ 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),
+ "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),
+ !selection_is_read_only && !selection_contains_recent &&
+ can_paste_files_into && !selection_contains_starred);
+
+ 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),
+ "new-document");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
+ can_create_files &&
+ !selection_contains_recent &&
+ !selection_contains_starred &&
+ priv->templates_present);
+
+ g_object_ref (view); /* Need to keep the object alive until we get the reply */
+ gtk_clipboard_request_text (nautilus_clipboard_get (GTK_WIDGET (view)),
+ on_clipboard_contents_received,
+ view);
+
+ 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 (priv->tag_manager, 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 (priv->tag_manager, 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);
+}
+
+/* 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)
+{
+ 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;
+ 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 (!nautilus_mime_file_extracts (file))
+ {
+ show_extract = FALSE;
+ }
+
+ if (!nautilus_mime_file_opens_in_external_app (file))
+ {
+ show_app = FALSE;
+ }
+
+ if (!nautilus_mime_file_launches (file))
+ {
+ show_run = FALSE;
+ }
+
+ if (!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 Here")) :
+ g_strdup (_("Extract to…"));
+ }
+ else
+ {
+ item_label = g_strdup (_("Open"));
+ }
+
+ 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-default-application-section");
+ g_menu_append_item (G_MENU (object), menu_item);
+
+ g_free (item_label);
+ g_object_unref (menu_item);
+
+ /* 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);
+ }
+
+ update_scripts_menu (view, builder);
+}
+
+static void
+update_background_menu (NautilusFilesView *view,
+ GtkBuilder *builder)
+{
+ if (nautilus_files_view_supports_creating_files (view) &&
+ !showing_recent_directory (view) &&
+ !showing_starred_directory (view))
+ {
+ update_templates_menu (view, builder);
+ }
+}
+
+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;
+ GActionGroup *view_action_group;
+ gboolean sort_available;
+ g_autofree gchar *zoom_level_percent = NULL;
+ NautilusFile *file;
+
+ view_action_group = nautilus_files_view_get_action_group (view);
+ priv = nautilus_files_view_get_instance_private (view);
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (view));
+
+ gtk_widget_set_visible (priv->visible_columns,
+ g_action_group_has_action (view_action_group, "visible-columns"));
+
+ sort_available = g_action_group_get_action_enabled (view_action_group, "sort");
+ gtk_widget_set_visible (priv->sort_menu, sort_available);
+ gtk_widget_set_visible (priv->sort_trash_time,
+ nautilus_file_is_in_trash (file));
+
+ /* We want to make insensitive available actions but that are not current
+ * available due to the directory
+ */
+ gtk_widget_set_sensitive (priv->sort_menu,
+ !nautilus_files_view_is_empty (view));
+ gtk_widget_set_sensitive (priv->zoom_controls_box,
+ !nautilus_files_view_is_empty (view));
+
+ zoom_level_percent = g_strdup_printf ("%.0f%%", nautilus_files_view_get_zoom_level_percentage (view) * 100.0);
+ gtk_label_set_label (GTK_LABEL (priv->zoom_level_label), zoom_level_percent);
+}
+
+/* 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->slot == NULL ||
+ !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,
+ const GdkEvent *event)
+{
+ 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);
+
+ if (NULL == priv->selection_menu)
+ {
+ priv->selection_menu = gtk_menu_new ();
+
+ gtk_menu_attach_to_widget (GTK_MENU (priv->selection_menu),
+ GTK_WIDGET (view),
+ NULL);
+ }
+
+ gtk_menu_shell_bind_model (GTK_MENU_SHELL (priv->selection_menu),
+ G_MENU_MODEL (priv->selection_menu_model),
+ NULL,
+ TRUE);
+
+ if (event != NULL)
+ {
+ gtk_menu_popup_at_pointer (GTK_MENU (priv->selection_menu), event);
+ }
+ else
+ {
+ /* 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_menu_popup_at_rect (GTK_MENU (priv->selection_menu),
+ gtk_widget_get_window (GTK_WIDGET (view)),
+ rectangle,
+ GDK_GRAVITY_SOUTH_WEST,
+ GDK_GRAVITY_NORTH_WEST,
+ NULL);
+ }
+}
+
+/**
+ * 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,
+ const GdkEvent *event)
+{
+ 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);
+
+ if (NULL == priv->background_menu)
+ {
+ priv->background_menu = gtk_menu_new ();
+
+ gtk_menu_attach_to_widget (GTK_MENU (priv->background_menu),
+ GTK_WIDGET (view),
+ NULL);
+ }
+ gtk_menu_shell_bind_model (GTK_MENU_SHELL (priv->background_menu),
+ G_MENU_MODEL (priv->background_menu_model),
+ NULL,
+ TRUE);
+ if (event != NULL)
+ {
+ gtk_menu_popup_at_pointer (GTK_MENU (priv->background_menu), event);
+ }
+ else
+ {
+ /* It was triggered from the keyboard, so pop up from the center of view.
+ */
+ gtk_menu_popup_at_widget (GTK_MENU (priv->background_menu),
+ GTK_WIDGET (view),
+ GDK_GRAVITY_CENTER,
+ GDK_GRAVITY_CENTER,
+ NULL);
+ }
+}
+
+static gboolean
+popup_menu_callback (NautilusFilesView *view)
+{
+ g_autolist (NautilusFile) selection = NULL;
+
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (view));
+
+ if (selection != NULL)
+ {
+ nautilus_files_view_pop_up_selection_context_menu (view, NULL);
+ }
+ else
+ {
+ nautilus_files_view_pop_up_background_context_menu (view, NULL);
+ }
+
+ return TRUE;
+}
+
+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->slot == NULL ||
+ !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->slot == NULL)
+ {
+ 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);
+ }
+
+ if (priv->batching_selection_level != 0)
+ {
+ priv->selection_changed_while_batched = TRUE;
+ }
+ else
+ {
+ /* Here is the work we do only when we're not
+ * batching selection changes. In other words, it's the slower
+ * stuff that we don't want to slow down selection techniques
+ * such as rubberband-selecting in icon view.
+ */
+
+ /* Schedule an update of menu item states to match selection */
+ 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;
+ GdkScreen *screen;
+
+ /* 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);
+ }
+
+ screen = gtk_widget_get_screen (GTK_WIDGET (view));
+ if (screen == NULL)
+ {
+ screen = gdk_screen_get_default ();
+ }
+
+ nautilus_launch_application_from_command (screen, 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);
+}
+
+void
+nautilus_files_view_start_batching_selection_changes (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view));
+ priv = nautilus_files_view_get_instance_private (view);
+
+ ++priv->batching_selection_level;
+ priv->selection_changed_while_batched = FALSE;
+}
+
+void
+nautilus_files_view_stop_batching_selection_changes (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view));
+ priv = nautilus_files_view_get_instance_private (view);
+ g_return_if_fail (priv->batching_selection_level > 0);
+
+ if (--priv->batching_selection_level == 0)
+ {
+ if (priv->selection_changed_while_batched)
+ {
+ nautilus_files_view_notify_selection_changed (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_event (GtkWidget *widget,
+ GdkEvent *event,
+ gpointer user_data)
+{
+ NautilusFilesView *directory_view;
+ static gdouble total_delta_y = 0;
+ GdkModifierType state;
+ GdkScrollDirection direction;
+ gdouble delta_x, delta_y;
+
+ directory_view = NAUTILUS_FILES_VIEW (widget);
+
+ if (gdk_event_get_event_type (event) != GDK_SCROLL)
+ {
+ return GDK_EVENT_PROPAGATE;
+ }
+
+ if (!gdk_event_get_state (event, &state))
+ {
+ return GDK_EVENT_PROPAGATE;
+ }
+
+ if (!(state & GDK_CONTROL_MASK))
+ {
+ return GDK_EVENT_PROPAGATE;
+ }
+
+ if (gdk_event_get_scroll_direction (event, &direction))
+ {
+ if (direction == GDK_SCROLL_UP)
+ {
+ /* Zoom In */
+ nautilus_files_view_bump_zoom_level (directory_view, 1);
+ return GDK_EVENT_STOP;
+ }
+ else if (direction == GDK_SCROLL_DOWN)
+ {
+ /* Zoom Out */
+ nautilus_files_view_bump_zoom_level (directory_view, -1);
+ return GDK_EVENT_STOP;
+ }
+ }
+
+ if (gdk_event_get_scroll_deltas (event, &delta_x, &delta_y))
+ {
+ /* try to emulate a normal scrolling event by summing deltas */
+ total_delta_y += delta_y;
+
+ if (total_delta_y >= 1)
+ {
+ total_delta_y = 0;
+ /* emulate scroll down */
+ nautilus_files_view_bump_zoom_level (directory_view, -1);
+ return GDK_EVENT_STOP;
+ }
+ else if (total_delta_y <= -1)
+ {
+ total_delta_y = 0;
+ /* emulate scroll up */
+ nautilus_files_view_bump_zoom_level (directory_view, 1);
+ return GDK_EVENT_STOP;
+ }
+ else
+ {
+ /* eat event */
+ return GDK_EVENT_STOP;
+ }
+ }
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static void
+action_reload_enabled_changed (GActionGroup *action_group,
+ gchar *action_name,
+ gboolean enabled,
+ NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ gtk_widget_set_visible (priv->reload, enabled);
+}
+
+static void
+action_stop_enabled_changed (GActionGroup *action_group,
+ gchar *action_name,
+ gboolean enabled,
+ NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ gtk_widget_set_visible (priv->stop, enabled);
+}
+
+static void
+nautilus_files_view_parent_set (GtkWidget *widget,
+ GtkWidget *old_parent)
+{
+ NautilusWindow *window;
+ NautilusFilesView *view;
+ NautilusFilesViewPrivate *priv;
+ GtkWidget *parent;
+
+ view = NAUTILUS_FILES_VIEW (widget);
+ priv = nautilus_files_view_get_instance_private (view);
+
+ parent = gtk_widget_get_parent (widget);
+ window = nautilus_files_view_get_window (view);
+ g_assert (parent == NULL || old_parent == NULL);
+
+ if (GTK_WIDGET_CLASS (nautilus_files_view_parent_class)->parent_set != NULL)
+ {
+ GTK_WIDGET_CLASS (nautilus_files_view_parent_class)->parent_set (widget, old_parent);
+ }
+
+ if (priv->stop_signal_handler > 0)
+ {
+ g_signal_handler_disconnect (window, priv->stop_signal_handler);
+ priv->stop_signal_handler = 0;
+ }
+
+ if (priv->reload_signal_handler > 0)
+ {
+ g_signal_handler_disconnect (window, priv->reload_signal_handler);
+ priv->reload_signal_handler = 0;
+ }
+
+ if (parent != NULL)
+ {
+ g_assert (old_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));
+ }
+
+ priv->stop_signal_handler =
+ g_signal_connect (window,
+ "action-enabled-changed::stop",
+ G_CALLBACK (action_stop_enabled_changed),
+ view);
+ priv->reload_signal_handler =
+ g_signal_connect (window,
+ "action-enabled-changed::reload",
+ G_CALLBACK (action_reload_enabled_changed),
+ view);
+ }
+ else
+ {
+ remove_update_context_menus_timeout_callback (view);
+ /* Only remove the action group if it matchs the current view
+ * action group. If not, we can remove an action group set by
+ * a different view i.e. if the slot_active function is called
+ * before this one
+ */
+ if (gtk_widget_get_action_group (GTK_WIDGET (window), "view") ==
+ priv->view_action_group)
+ {
+ gtk_widget_insert_action_group (GTK_WIDGET (nautilus_files_view_get_window (view)),
+ "view",
+ NULL);
+ }
+ }
+}
+
+static gboolean
+nautilus_files_view_event (GtkWidget *widget,
+ GdkEvent *event)
+{
+ NautilusFilesView *view;
+ NautilusFilesViewPrivate *priv;
+ guint keyval;
+
+ if (gdk_event_get_event_type (event) != GDK_KEY_PRESS)
+ {
+ return GDK_EVENT_PROPAGATE;
+ }
+
+ view = NAUTILUS_FILES_VIEW (widget);
+ priv = nautilus_files_view_get_instance_private (view);
+
+ if (G_UNLIKELY (!gdk_event_get_keyval (event, &keyval)))
+ {
+ g_return_val_if_reached (GDK_EVENT_PROPAGATE);
+ }
+
+ for (gint i = 0; i < G_N_ELEMENTS (extra_view_keybindings); i++)
+ {
+ if (extra_view_keybindings[i].keyval == keyval)
+ {
+ GAction *action;
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (priv->view_action_group),
+ extra_view_keybindings[i].action);
+
+ if (g_action_get_enabled (action))
+ {
+ g_action_activate (action, NULL);
+ return GDK_EVENT_STOP;
+ }
+
+ break;
+ }
+ }
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+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->finalize = nautilus_files_view_finalize;
+ oclass->get_property = nautilus_files_view_get_property;
+ oclass->set_property = nautilus_files_view_set_property;
+
+ widget_class->destroy = nautilus_files_view_destroy;
+ widget_class->event = nautilus_files_view_event;
+ widget_class->parent_set = nautilus_files_view_parent_set;
+ 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");
+}
+
+static void
+nautilus_files_view_init (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+ GtkBuilder *builder;
+ AtkObject *atk_object;
+ NautilusDirectory *scripts_directory;
+ NautilusDirectory *templates_directory;
+ gchar *templates_uri;
+ GtkClipboard *clipboard;
+ GApplication *app;
+ const gchar *open_accels[] =
+ {
+ "Return",
+ "KP_Enter",
+ "<control>o",
+ "<alt>Down",
+ NULL
+ };
+ const gchar *open_properties[] =
+ {
+ "<control>i",
+ "<alt>Return",
+ NULL
+ };
+ 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
+ };
+ const gchar *move_to_trash_accels[] =
+ {
+ "Delete",
+ "KP_Delete",
+ NULL
+ };
+ const gchar *delete_permanently_accels[] =
+ {
+ "<shift>Delete",
+ "<shift>KP_Delete",
+ 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->supports_undo_redo = TRUE;
+ priv->toolbar_menu_sections->zoom_section = GTK_WIDGET (g_object_ref_sink (gtk_builder_get_object (builder, "zoom_section")));
+ priv->toolbar_menu_sections->extended_section = GTK_WIDGET (g_object_ref_sink (gtk_builder_get_object (builder, "extended_section")));
+ priv->zoom_controls_box = GTK_WIDGET (gtk_builder_get_object (builder, "zoom_controls_box"));
+ priv->zoom_level_label = GTK_WIDGET (gtk_builder_get_object (builder, "zoom_level_label"));
+
+ priv->sort_menu = GTK_WIDGET (gtk_builder_get_object (builder, "sort_menu"));
+ priv->sort_trash_time = GTK_WIDGET (gtk_builder_get_object (builder, "sort_trash_time"));
+ priv->visible_columns = GTK_WIDGET (gtk_builder_get_object (builder, "visible_columns"));
+ priv->reload = GTK_WIDGET (gtk_builder_get_object (builder, "reload"));
+ priv->stop = GTK_WIDGET (gtk_builder_get_object (builder, "stop"));
+
+ g_signal_connect (view,
+ "end-file-changes",
+ G_CALLBACK (on_end_file_changes),
+ view);
+
+ g_object_unref (builder);
+
+ /* Main widgets */
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (view), GTK_ORIENTATION_VERTICAL);
+ priv->overlay = gtk_overlay_new ();
+ gtk_widget_set_vexpand (priv->overlay, TRUE);
+ gtk_widget_set_hexpand (priv->overlay, TRUE);
+ gtk_container_add (GTK_CONTAINER (view), priv->overlay);
+ gtk_widget_show (priv->overlay);
+
+ /* NautilusFloatingBar listen to its parent's 'event' signal
+ * and GtkOverlay doesn't have it enabled by default, so we have to add them
+ * here.
+ */
+ gtk_widget_add_events (GTK_WIDGET (priv->overlay),
+ GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
+
+ /* Scrolled Window */
+ priv->scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolled_window),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_widget_show (priv->scrolled_window);
+
+ g_signal_connect_swapped (priv->scrolled_window,
+ "event",
+ G_CALLBACK (on_event),
+ view);
+ g_signal_connect_swapped (priv->scrolled_window,
+ "popup-menu",
+ G_CALLBACK (popup_menu_callback),
+ view);
+
+ gtk_container_add (GTK_CONTAINER (priv->overlay), priv->scrolled_window);
+
+ /* Empty states */
+ builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-no-search-results.ui");
+ priv->no_search_results_widget = GTK_WIDGET (gtk_builder_get_object (builder, "no_search_results"));
+ gtk_overlay_add_overlay (GTK_OVERLAY (priv->overlay), priv->no_search_results_widget);
+ gtk_overlay_set_overlay_pass_through (GTK_OVERLAY (priv->overlay),
+ priv->no_search_results_widget,
+ TRUE);
+ g_object_unref (builder);
+
+ builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-folder-is-empty.ui");
+ priv->folder_is_empty_widget = GTK_WIDGET (gtk_builder_get_object (builder, "folder_is_empty"));
+ gtk_overlay_add_overlay (GTK_OVERLAY (priv->overlay), priv->folder_is_empty_widget);
+ gtk_overlay_set_overlay_pass_through (GTK_OVERLAY (priv->overlay),
+ priv->folder_is_empty_widget,
+ TRUE);
+ g_object_unref (builder);
+
+ builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-starred-is-empty.ui");
+ priv->starred_is_empty_widget = GTK_WIDGET (gtk_builder_get_object (builder, "starred_is_empty"));
+ gtk_overlay_add_overlay (GTK_OVERLAY (priv->overlay), priv->starred_is_empty_widget);
+ gtk_overlay_set_overlay_pass_through (GTK_OVERLAY (priv->overlay),
+ priv->starred_is_empty_widget,
+ TRUE);
+ g_object_unref (builder);
+
+ builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-trash-is-empty.ui");
+ priv->trash_is_empty_widget = GTK_WIDGET (gtk_builder_get_object (builder, "trash_is_empty"));
+ gtk_overlay_add_overlay (GTK_OVERLAY (priv->overlay), priv->trash_is_empty_widget);
+ gtk_overlay_set_overlay_pass_through (GTK_OVERLAY (priv->overlay),
+ priv->trash_is_empty_widget,
+ TRUE);
+ g_object_unref (builder);
+
+ /* Floating bar */
+ priv->floating_bar = nautilus_floating_bar_new (NULL, NULL, FALSE);
+ gtk_widget_set_halign (priv->floating_bar, GTK_ALIGN_END);
+ gtk_widget_set_valign (priv->floating_bar, GTK_ALIGN_END);
+ gtk_overlay_add_overlay (GTK_OVERLAY (priv->overlay), priv->floating_bar);
+
+ g_signal_connect (priv->floating_bar,
+ "action",
+ G_CALLBACK (floating_bar_action_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);
+
+ gtk_style_context_set_junction_sides (gtk_widget_get_style_context (GTK_WIDGET (view)),
+ GTK_JUNCTION_TOP | GTK_JUNCTION_LEFT);
+
+ 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 = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+ g_signal_connect (clipboard, "owner-change",
+ 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;
+
+ /* Accessibility */
+ atk_object = gtk_widget_get_accessible (GTK_WIDGET (view));
+ atk_object_set_name (atk_object, _("Content View"));
+ atk_object_set_description (atk_object, _("View of the current folder"));
+
+ 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 ();
+
+ /* Toolbar menu */
+ 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");
+ /* Background menu */
+ nautilus_application_set_accelerator (app, "view.select-all", "<control>a");
+ nautilus_application_set_accelerator (app, "view.paste_accel", "<control>v");
+ nautilus_application_set_accelerator (app, "view.create-link", "<control>m");
+ /* Selection menu */
+ nautilus_application_set_accelerators (app, "view.open-with-default-application", open_accels);
+ nautilus_application_set_accelerator (app, "view.open-item-new-tab", "<control>Return");
+ nautilus_application_set_accelerator (app, "view.open-item-new-window", "<Shift>Return");
+ nautilus_application_set_accelerators (app, "view.move-to-trash", move_to_trash_accels);
+ nautilus_application_set_accelerators (app, "view.delete-from-trash", move_to_trash_accels);
+ nautilus_application_set_accelerators (app, "view.delete-permanently-shortcut", delete_permanently_accels);
+ /* 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 */
+ nautilus_application_set_accelerators (app, "view.delete-permanently-menu-item", move_to_trash_accels);
+ nautilus_application_set_accelerators (app, "view.permanent-delete-permanently-menu-item", delete_permanently_accels);
+ nautilus_application_set_accelerators (app, "view.properties", open_properties);
+ nautilus_application_set_accelerator (app, "view.open-item-location", "<control><alt>o");
+ nautilus_application_set_accelerator (app, "view.rename", "F2");
+ nautilus_application_set_accelerator (app, "view.cut", "<control>x");
+ nautilus_application_set_accelerator (app, "view.copy", "<control>c");
+ nautilus_application_set_accelerator (app, "view.create-link-in-place", "<control><shift>m");
+ nautilus_application_set_accelerator (app, "view.new-folder", "<control><shift>n");
+ /* Only accesible by shorcuts */
+ nautilus_application_set_accelerator (app, "view.select-pattern", "<control>s");
+ nautilus_application_set_accelerators (app, "view.zoom-standard", zoom_standard_accels);
+ nautilus_application_set_accelerator (app, "view.invert-selection", "<shift><control>i");
+
+ priv->starred_cancellable = g_cancellable_new ();
+ priv->tag_manager = nautilus_tag_manager_get ();
+
+ priv->rename_file_controller = nautilus_rename_file_popover_controller_new ();
+
+ nautilus_profile_end (NULL);
+}
+
+NautilusFilesView *
+nautilus_files_view_new (guint id,
+ NautilusWindowSlot *slot)
+{
+ NautilusFilesView *view = NULL;
+ gboolean use_experimental_views;
+
+ use_experimental_views = g_settings_get_boolean (nautilus_preferences,
+ NAUTILUS_PREFERENCES_USE_EXPERIMENTAL_VIEWS);
+ switch (id)
+ {
+ case NAUTILUS_VIEW_GRID_ID:
+ {
+ if (use_experimental_views)
+ {
+ view = NAUTILUS_FILES_VIEW (nautilus_view_icon_controller_new (slot));
+ }
+ else
+ {
+ view = nautilus_canvas_view_new (slot);
+ }
+ }
+ break;
+
+ case NAUTILUS_VIEW_LIST_ID:
+ {
+ view = nautilus_list_view_new (slot);
+ }
+ break;
+ }
+
+ if (view == NULL)
+ {
+ g_critical ("Unknown view type ID: %d", id);
+ }
+ 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..152f25a
--- /dev/null
+++ b/src/nautilus-files-view.h
@@ -0,0 +1,336 @@
+/* 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, GtkGrid)
+
+struct _NautilusFilesViewClass {
+ GtkGridClass 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);
+
+ /* The current zoom level as a percentage of the default. */
+ gfloat (* get_zoom_level_percentage) (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);
+
+ /* sort_files is a function pointer that subclasses can override
+ * to provide a sorting order to determine which files should be
+ * presented when only a partial list is provided.
+ */
+ int (* compare_files) (NautilusFilesView *view,
+ NautilusFile *a,
+ NautilusFile *b);
+
+ /* is_empty is a function pointer that subclasses must
+ * override to report whether the view contains any items.
+ */
+ gboolean (* is_empty) (NautilusFilesView *view);
+
+ /* convert *point from widget's coordinate system to a coordinate
+ * system used for specifying file operation positions, which is view-specific.
+ *
+ * This is used by the the icon view, which converts the screen position to a zoom
+ * level-independent coordinate system.
+ */
+ void (* widget_to_file_operation_position) (NautilusFilesView *view,
+ GdkPoint *position);
+
+ /* 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);
+
+ GIcon * (* get_icon) (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,
+ NautilusWindowOpenFlags flags,
+ gboolean confirm_multiple);
+void nautilus_files_view_activate_file (NautilusFilesView *view,
+ NautilusFile *file,
+ NautilusWindowOpenFlags flags);
+void nautilus_files_view_preview_files (NautilusFilesView *view,
+ GList *files,
+ GArray *locations);
+void nautilus_files_view_preview_update (NautilusFilesView *view,
+ GList *files);
+void nautilus_files_view_start_batching_selection_changes (NautilusFilesView *view);
+void nautilus_files_view_stop_batching_selection_changes (NautilusFilesView *view);
+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,
+ const GdkEvent *event);
+void nautilus_files_view_pop_up_selection_context_menu (NautilusFilesView *view,
+ const GdkEvent *event);
+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);
+
+guint nautilus_files_view_get_view_id (NautilusView *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);
+
+/* 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..30fbf57
--- /dev/null
+++ b/src/nautilus-floating-bar.c
@@ -0,0 +1,607 @@
+/* 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;
+ gboolean is_interactive;
+ guint hover_timeout_id;
+};
+
+enum
+{
+ PROP_PRIMARY_LABEL = 1,
+ PROP_DETAILS_LABEL,
+ PROP_SHOW_SPINNER,
+ NUM_PROPERTIES
+};
+
+enum
+{
+ ACTION,
+ 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
+action_button_clicked_cb (GtkButton *button,
+ NautilusFloatingBar *self)
+{
+ gint action_id;
+
+ action_id = GPOINTER_TO_INT
+ (g_object_get_data (G_OBJECT (button), "action-id"));
+
+ g_signal_emit (self, signals[ACTION], 0, action_id);
+}
+
+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_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;
+
+ 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;
+
+ 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
+{
+ GtkWidget *overlay;
+ GtkWidget *floating_bar;
+ GdkDevice *device;
+ 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;
+ gint pointer_y = -1;
+
+ gdk_window_get_device_position (gtk_widget_get_window (data->overlay), data->device,
+ NULL, &pointer_y, NULL);
+
+ if (pointer_y == -1 || pointer_y < data->y_down_limit || pointer_y > data->y_upper_limit)
+ {
+ gtk_widget_show (data->floating_bar);
+ NAUTILUS_FLOATING_BAR (data->floating_bar)->hover_timeout_id = 0;
+
+ return G_SOURCE_REMOVE;
+ }
+ else
+ {
+ gtk_widget_hide (data->floating_bar);
+ }
+
+ return G_SOURCE_CONTINUE;
+}
+
+static gboolean
+overlay_event_cb (GtkWidget *parent,
+ GdkEvent *event,
+ gpointer user_data)
+{
+ NautilusFloatingBar *self = NAUTILUS_FLOATING_BAR (user_data);
+ GtkWidget *widget = user_data;
+ CheckPointerData *data;
+ gint y_pos;
+
+ if (gdk_event_get_event_type (event) != GDK_ENTER_NOTIFY)
+ {
+ return GDK_EVENT_PROPAGATE;
+ }
+
+ if (self->hover_timeout_id != 0)
+ {
+ g_source_remove (self->hover_timeout_id);
+ }
+
+ if (gdk_event_get_window (event) != gtk_widget_get_window (widget))
+ {
+ return GDK_EVENT_PROPAGATE;
+ }
+
+ if (self->is_interactive)
+ {
+ return GDK_EVENT_PROPAGATE;
+ }
+
+ gdk_window_get_position (gtk_widget_get_window (widget), NULL, &y_pos);
+
+ data = g_slice_new (CheckPointerData);
+ data->overlay = parent;
+ data->floating_bar = widget;
+ data->device = gdk_event_get_device (event);
+ data->y_down_limit = y_pos;
+ data->y_upper_limit = y_pos + gtk_widget_get_allocated_height (widget);
+
+ 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] overlay_event_cb");
+
+ return GDK_EVENT_STOP;
+}
+
+static void
+nautilus_floating_bar_parent_set (GtkWidget *widget,
+ GtkWidget *old_parent)
+{
+ GtkWidget *parent;
+
+ parent = gtk_widget_get_parent (widget);
+
+ if (old_parent != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (old_parent,
+ overlay_event_cb, widget);
+ }
+
+ if (parent != NULL)
+ {
+ g_signal_connect (parent, "event",
+ G_CALLBACK (overlay_event_cb), widget);
+ }
+}
+
+static void
+get_padding_and_border (GtkWidget *widget,
+ GtkBorder *border)
+{
+ GtkStyleContext *context;
+ GtkStateFlags state;
+ GtkBorder tmp;
+
+ context = gtk_widget_get_style_context (widget);
+ state = gtk_widget_get_state_flags (widget);
+
+ gtk_style_context_get_padding (context, state, border);
+ gtk_style_context_get_border (context, state, &tmp);
+ border->top += tmp.top;
+ border->right += tmp.right;
+ border->bottom += tmp.bottom;
+ border->left += tmp.left;
+}
+
+static void
+nautilus_floating_bar_get_preferred_width (GtkWidget *widget,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ GtkBorder border;
+
+ get_padding_and_border (widget, &border);
+
+ GTK_WIDGET_CLASS (nautilus_floating_bar_parent_class)->get_preferred_width (widget,
+ minimum_size,
+ natural_size);
+
+ *minimum_size += border.left + border.right;
+ *natural_size += border.left + border.right;
+}
+
+static void
+nautilus_floating_bar_get_preferred_width_for_height (GtkWidget *widget,
+ gint height,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ GtkBorder border;
+
+ get_padding_and_border (widget, &border);
+
+ GTK_WIDGET_CLASS (nautilus_floating_bar_parent_class)->get_preferred_width_for_height (widget,
+ height,
+ minimum_size,
+ natural_size);
+
+ *minimum_size += border.left + border.right;
+ *natural_size += border.left + border.right;
+}
+
+static void
+nautilus_floating_bar_get_preferred_height (GtkWidget *widget,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ GtkBorder border;
+
+ get_padding_and_border (widget, &border);
+
+ GTK_WIDGET_CLASS (nautilus_floating_bar_parent_class)->get_preferred_height (widget,
+ minimum_size,
+ natural_size);
+
+ *minimum_size += border.top + border.bottom;
+ *natural_size += border.top + border.bottom;
+}
+
+static void
+nautilus_floating_bar_get_preferred_height_for_width (GtkWidget *widget,
+ gint width,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ GtkBorder border;
+
+ get_padding_and_border (widget, &border);
+
+ GTK_WIDGET_CLASS (nautilus_floating_bar_parent_class)->get_preferred_height_for_width (widget,
+ width,
+ minimum_size,
+ natural_size);
+
+ *minimum_size += border.top + border.bottom;
+ *natural_size += border.top + border.bottom;
+}
+
+static void
+nautilus_floating_bar_constructed (GObject *obj)
+{
+ NautilusFloatingBar *self = NAUTILUS_FLOATING_BAR (obj);
+ GtkWidget *w, *box, *labels_box;
+
+ G_OBJECT_CLASS (nautilus_floating_bar_parent_class)->constructed (obj);
+
+ box = GTK_WIDGET (obj);
+
+ w = gtk_spinner_new ();
+ gtk_box_pack_start (GTK_BOX (box), w, FALSE, FALSE, 0);
+ gtk_widget_set_visible (w, self->show_spinner);
+ gtk_spinner_start (GTK_SPINNER (w));
+ 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_pack_start (GTK_BOX (box), labels_box, TRUE, TRUE, 0);
+ g_object_set (labels_box,
+ "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_container_add (GTK_CONTAINER (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_container_add (GTK_CONTAINER (labels_box), w);
+ self->details_label_widget = w;
+ gtk_widget_show (w);
+}
+
+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");
+}
+
+static void
+nautilus_floating_bar_class_init (NautilusFloatingBarClass *klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *wclass = GTK_WIDGET_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;
+
+ wclass->get_preferred_width = nautilus_floating_bar_get_preferred_width;
+ wclass->get_preferred_width_for_height = nautilus_floating_bar_get_preferred_width_for_height;
+ wclass->get_preferred_height = nautilus_floating_bar_get_preferred_height;
+ wclass->get_preferred_height_for_width = nautilus_floating_bar_get_preferred_height_for_width;
+ wclass->parent_set = nautilus_floating_bar_parent_set;
+
+ 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);
+
+ signals[ACTION] =
+ g_signal_new ("action",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL,
+ g_cclosure_marshal_VOID__INT,
+ G_TYPE_NONE, 1,
+ G_TYPE_INT);
+
+ 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]);
+ }
+}
+
+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);
+}
+
+void
+nautilus_floating_bar_add_action (NautilusFloatingBar *self,
+ const gchar *icon_name,
+ gint action_id)
+{
+ GtkWidget *button;
+ GtkStyleContext *context;
+
+ button = gtk_button_new_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
+ context = gtk_widget_get_style_context (button);
+ gtk_style_context_add_class (context, "circular");
+ gtk_style_context_add_class (context, "flat");
+ gtk_widget_set_valign (button, GTK_ALIGN_CENTER);
+ gtk_box_pack_end (GTK_BOX (self), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ g_object_set_data (G_OBJECT (button), "action-id",
+ GINT_TO_POINTER (action_id));
+
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (action_button_clicked_cb), self);
+
+ self->is_interactive = TRUE;
+}
+
+void
+nautilus_floating_bar_cleanup_actions (NautilusFloatingBar *self)
+{
+ GtkWidget *widget;
+ GList *children, *l;
+ gpointer data;
+
+ children = gtk_container_get_children (GTK_CONTAINER (self));
+ l = children;
+
+ while (l != NULL)
+ {
+ widget = l->data;
+ data = g_object_get_data (G_OBJECT (widget), "action-id");
+ l = l->next;
+
+ if (data != NULL)
+ {
+ /* destroy this */
+ gtk_widget_destroy (widget);
+ }
+ }
+
+ g_list_free (children);
+
+ self->is_interactive = FALSE;
+}
diff --git a/src/nautilus-floating-bar.h b/src/nautilus-floating-bar.h
new file mode 100644
index 0000000..893e1dc
--- /dev/null
+++ b/src/nautilus-floating-bar.h
@@ -0,0 +1,51 @@
+
+/* 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_add_action (NautilusFloatingBar *self,
+ const gchar *icon_name,
+ gint action_id);
+void nautilus_floating_bar_cleanup_actions (NautilusFloatingBar *self);
+
+void nautilus_floating_bar_remove_hover_timeout (NautilusFloatingBar *self); \ No newline at end of file
diff --git a/src/nautilus-freedesktop-dbus.c b/src/nautilus-freedesktop-dbus.c
new file mode 100644
index 0000000..5cbbbad
--- /dev/null
+++ b/src/nautilus-freedesktop-dbus.c
@@ -0,0 +1,327 @@
+/*
+ * 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);
+}
+
+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..685ced4
--- /dev/null
+++ b/src/nautilus-global-preferences.c
@@ -0,0 +1,70 @@
+/* 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-gtk-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_background_preferences;
+GSettings *gnome_interface_preferences;
+GSettings *tracker_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.Settings.FileChooser",
+ "/org/gtk/settings/file-chooser/");
+ gnome_lockdown_preferences = g_settings_new ("org.gnome.desktop.lockdown");
+ gnome_background_preferences = g_settings_new ("org.gnome.desktop.background");
+ gnome_interface_preferences = g_settings_new ("org.gnome.desktop.interface");
+ tracker_preferences = g_settings_new ("org.freedesktop.Tracker3.Miner.Files");
+}
diff --git a/src/nautilus-global-preferences.h b/src/nautilus-global-preferences.h
new file mode 100644
index 0000000..e7e497e
--- /dev/null
+++ b/src/nautilus-global-preferences.h
@@ -0,0 +1,169 @@
+
+/* 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
+
+/* Trash options */
+#define NAUTILUS_PREFERENCES_CONFIRM_TRASH "confirm-trash"
+
+/* 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"
+
+typedef enum
+{
+ NAUTILUS_NEW_TAB_POSITION_AFTER_CURRENT_TAB,
+ NAUTILUS_NEW_TAB_POSITION_END,
+} NautilusNewTabPosition;
+
+/* 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"
+
+/* Activating executable text files */
+#define NAUTILUS_PREFERENCES_EXECUTABLE_TEXT_ACTIVATION "executable-text-activation"
+
+/* 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_START_WITH_SIDEBAR "start-with-sidebar"
+#define NAUTILUS_WINDOW_STATE_INITIAL_SIZE "initial-size"
+#define NAUTILUS_WINDOW_STATE_MAXIMIZED "maximized"
+#define NAUTILUS_WINDOW_STATE_SIDEBAR_WIDTH "sidebar-width"
+
+/* 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
+} NautilusCompressionFormat;
+
+/* Icon View */
+#define NAUTILUS_PREFERENCES_ICON_VIEW_DEFAULT_ZOOM_LEVEL "default-zoom-level"
+
+/* Experimental views */
+#define NAUTILUS_PREFERENCES_USE_EXPERIMENTAL_VIEWS "use-experimental-views"
+
+/* Which text attributes appear beneath icon names */
+#define NAUTILUS_PREFERENCES_ICON_VIEW_CAPTIONS "captions"
+
+/* ellipsization preferences */
+#define NAUTILUS_PREFERENCES_ICON_VIEW_TEXT_ELLIPSIS_LIMIT "text-ellipsis-limit"
+
+/* 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
+};
+
+enum
+{
+ NAUTILUS_EXECUTABLE_TEXT_LAUNCH,
+ NAUTILUS_EXECUTABLE_TEXT_DISPLAY,
+ NAUTILUS_EXECUTABLE_TEXT_ASK
+};
+
+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"
+
+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_background_preferences;
+extern GSettings *gnome_interface_preferences;
+extern GSettings *tracker_preferences;
+
+G_END_DECLS
diff --git a/src/nautilus-icon-info.c b/src/nautilus-icon-info.c
new file mode 100644
index 0000000..5bd8b01
--- /dev/null
+++ b/src/nautilus-icon-info.c
@@ -0,0 +1,625 @@
+/* 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;
+ GdkPixbuf *pixbuf;
+
+ char *icon_name;
+
+ gint orig_scale;
+};
+
+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->pixbuf == NULL;
+}
+
+static void
+pixbuf_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,
+ pixbuf_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->pixbuf)
+ {
+ g_object_remove_toggle_ref (G_OBJECT (icon->pixbuf),
+ pixbuf_toggle_notify,
+ icon);
+ }
+
+ if (icon->pixbuf)
+ {
+ g_object_unref (icon->pixbuf);
+ }
+ 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_pixbuf (GdkPixbuf *pixbuf,
+ gint scale)
+{
+ NautilusIconInfo *icon;
+
+ icon = g_object_new (NAUTILUS_TYPE_ICON_INFO, NULL);
+
+ if (pixbuf)
+ {
+ icon->pixbuf = g_object_ref (pixbuf);
+ }
+
+ icon->orig_scale = scale;
+
+ return icon;
+}
+
+static NautilusIconInfo *
+nautilus_icon_info_new_for_icon_info (GtkIconInfo *icon_info,
+ gint scale)
+{
+ NautilusIconInfo *icon;
+ const char *filename;
+ char *basename, *p;
+
+ icon = g_object_new (NAUTILUS_TYPE_ICON_INFO, NULL);
+
+ icon->pixbuf = gtk_icon_info_load_icon (icon_info, NULL);
+
+ filename = gtk_icon_info_get_filename (icon_info);
+ if (filename != NULL)
+ {
+ basename = g_path_get_basename (filename);
+ p = strrchr (basename, '.');
+ if (p)
+ {
+ *p = 0;
+ }
+ icon->icon_name = basename;
+ }
+
+ icon->orig_scale = scale;
+
+ return icon;
+}
+
+
+typedef struct
+{
+ GIcon *icon;
+ int scale;
+ int size;
+} LoadableIconKey;
+
+typedef struct
+{
+ char *filename;
+ 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->filename) ^ 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->filename, b->filename);
+}
+
+static ThemedIconKey *
+themed_icon_key_new (const char *filename,
+ int scale,
+ int size)
+{
+ ThemedIconKey *key;
+
+ key = g_slice_new (ThemedIconKey);
+ key->filename = g_strdup (filename);
+ key->scale = scale;
+ key->size = size;
+
+ return key;
+}
+
+static void
+themed_icon_key_free (ThemedIconKey *key)
+{
+ g_free (key->filename);
+ g_slice_free (ThemedIconKey, key);
+}
+
+NautilusIconInfo *
+nautilus_icon_info_lookup (GIcon *icon,
+ int size,
+ int scale)
+{
+ NautilusIconInfo *icon_info;
+
+ if (G_IS_LOADABLE_ICON (icon))
+ {
+ GdkPixbuf *pixbuf;
+ 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);
+ }
+
+ pixbuf = NULL;
+ 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);
+ }
+
+ icon_info = nautilus_icon_info_new_for_pixbuf (pixbuf, 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);
+ }
+ else if (G_IS_THEMED_ICON (icon))
+ {
+ const char * const *names;
+ ThemedIconKey lookup_key;
+ ThemedIconKey *key;
+ GtkIconTheme *icon_theme;
+ GtkIconInfo *gtkicon_info;
+ const char *filename;
+
+ 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);
+ }
+
+ names = g_themed_icon_get_names (G_THEMED_ICON (icon));
+
+ icon_theme = gtk_icon_theme_get_default ();
+ gtkicon_info = gtk_icon_theme_choose_icon_for_scale (icon_theme, (const char **) names,
+ size, scale, GTK_ICON_LOOKUP_FORCE_SIZE);
+
+ if (gtkicon_info == NULL)
+ {
+ return nautilus_icon_info_new_for_pixbuf (NULL, scale);
+ }
+
+ filename = gtk_icon_info_get_filename (gtkicon_info);
+ if (filename == NULL)
+ {
+ g_object_unref (gtkicon_info);
+ return nautilus_icon_info_new_for_pixbuf (NULL, scale);
+ }
+
+ lookup_key.filename = (char *) filename;
+ lookup_key.scale = scale;
+ lookup_key.size = size;
+
+ icon_info = g_hash_table_lookup (themed_icon_cache, &lookup_key);
+ if (icon_info)
+ {
+ g_object_unref (gtkicon_info);
+ return g_object_ref (icon_info);
+ }
+
+ icon_info = nautilus_icon_info_new_for_icon_info (gtkicon_info, scale);
+
+ key = themed_icon_key_new (filename, scale, size);
+ g_hash_table_insert (themed_icon_cache, key, icon_info);
+
+ g_object_unref (gtkicon_info);
+
+ return g_object_ref (icon_info);
+ }
+ else
+ {
+ GdkPixbuf *pixbuf;
+ GtkIconInfo *gtk_icon_info;
+
+ gtk_icon_info = gtk_icon_theme_lookup_by_gicon_for_scale (gtk_icon_theme_get_default (),
+ icon,
+ size,
+ scale,
+ GTK_ICON_LOOKUP_FORCE_SIZE);
+ if (gtk_icon_info != NULL)
+ {
+ pixbuf = gtk_icon_info_load_icon (gtk_icon_info, NULL);
+ g_object_unref (gtk_icon_info);
+ }
+ else
+ {
+ pixbuf = NULL;
+ }
+
+ icon_info = nautilus_icon_info_new_for_pixbuf (pixbuf, scale);
+
+ if (pixbuf != NULL)
+ {
+ g_object_unref (pixbuf);
+ }
+
+ return icon_info;
+ }
+}
+
+NautilusIconInfo *
+nautilus_icon_info_lookup_from_name (const char *name,
+ int size,
+ int scale)
+{
+ GIcon *icon;
+ NautilusIconInfo *info;
+
+ icon = g_themed_icon_new (name);
+ info = nautilus_icon_info_lookup (icon, size, scale);
+ g_object_unref (icon);
+ return info;
+}
+
+NautilusIconInfo *
+nautilus_icon_info_lookup_from_path (const char *path,
+ int size,
+ int scale)
+{
+ GFile *icon_file;
+ GIcon *icon;
+ NautilusIconInfo *info;
+
+ icon_file = g_file_new_for_path (path);
+ icon = g_file_icon_new (icon_file);
+ info = nautilus_icon_info_lookup (icon, size, scale);
+ g_object_unref (icon);
+ g_object_unref (icon_file);
+ return info;
+}
+
+GdkPixbuf *
+nautilus_icon_info_get_pixbuf_nodefault (NautilusIconInfo *icon)
+{
+ GdkPixbuf *res;
+
+ if (icon->pixbuf == NULL)
+ {
+ res = NULL;
+ }
+ else
+ {
+ res = g_object_ref (icon->pixbuf);
+
+ if (icon->sole_owner)
+ {
+ icon->sole_owner = FALSE;
+ g_object_add_toggle_ref (G_OBJECT (res),
+ pixbuf_toggle_notify,
+ icon);
+ }
+ }
+
+ return res;
+}
+
+
+GdkPixbuf *
+nautilus_icon_info_get_pixbuf (NautilusIconInfo *icon)
+{
+ GdkPixbuf *res;
+
+ res = nautilus_icon_info_get_pixbuf_nodefault (icon);
+ if (res == NULL)
+ {
+ res = gdk_pixbuf_new_from_resource ("/org/gnome/nautilus/text-x-preview.png",
+ NULL);
+ }
+
+ return res;
+}
+
+GdkPixbuf *
+nautilus_icon_info_get_pixbuf_nodefault_at_size (NautilusIconInfo *icon,
+ gsize forced_size)
+{
+ GdkPixbuf *pixbuf, *scaled_pixbuf;
+ int w, h, s;
+ double scale;
+
+ pixbuf = nautilus_icon_info_get_pixbuf_nodefault (icon);
+
+ if (pixbuf == NULL)
+ {
+ return NULL;
+ }
+
+ w = gdk_pixbuf_get_width (pixbuf) / icon->orig_scale;
+ h = gdk_pixbuf_get_height (pixbuf) / icon->orig_scale;
+ s = MAX (w, h);
+ if (s == forced_size)
+ {
+ return pixbuf;
+ }
+
+ scale = (double) forced_size / s;
+ scaled_pixbuf = gdk_pixbuf_scale_simple (pixbuf,
+ w * scale, h * scale,
+ GDK_INTERP_BILINEAR);
+ g_object_unref (pixbuf);
+ return scaled_pixbuf;
+}
+
+
+GdkPixbuf *
+nautilus_icon_info_get_pixbuf_at_size (NautilusIconInfo *icon,
+ gsize forced_size)
+{
+ GdkPixbuf *pixbuf, *scaled_pixbuf;
+ int w, h, s;
+ double scale;
+
+ pixbuf = nautilus_icon_info_get_pixbuf (icon);
+
+ w = gdk_pixbuf_get_width (pixbuf) / icon->orig_scale;
+ h = gdk_pixbuf_get_height (pixbuf) / icon->orig_scale;
+ s = MAX (w, h);
+ if (s == forced_size)
+ {
+ return pixbuf;
+ }
+
+ scale = (double) forced_size / s;
+
+ /* Neither of these can be 0. */
+ w = MAX (w * scale, 1);
+ h = MAX (h * scale, 1);
+
+ scaled_pixbuf = gdk_pixbuf_scale_simple (pixbuf,
+ w, h,
+ GDK_INTERP_BILINEAR);
+ g_object_unref (pixbuf);
+ return scaled_pixbuf;
+}
+
+const char *
+nautilus_icon_info_get_used_name (NautilusIconInfo *icon)
+{
+ return icon->icon_name;
+}
+
+gint
+nautilus_get_icon_size_for_stock_size (GtkIconSize size)
+{
+ gint w, h;
+
+ if (gtk_icon_size_lookup (size, &w, &h))
+ {
+ return MAX (w, h);
+ }
+ return NAUTILUS_CANVAS_ICON_SIZE_SMALL;
+}
diff --git a/src/nautilus-icon-info.h b/src/nautilus-icon-info.h
new file mode 100644
index 0000000..506acec
--- /dev/null
+++ b/src/nautilus-icon-info.h
@@ -0,0 +1,44 @@
+#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
+
+#define NAUTILUS_LIST_ZOOM_LEVEL_N_ENTRIES (NAUTILUS_LIST_ZOOM_LEVEL_LARGER + 1)
+#define NAUTILUS_CANVAS_ZOOM_LEVEL_N_ENTRIES (NAUTILUS_CANVAS_ZOOM_LEVEL_LARGEST + 1)
+
+/* 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_pixbuf (GdkPixbuf *pixbuf,
+ int scale);
+NautilusIconInfo * nautilus_icon_info_lookup (GIcon *icon,
+ int size,
+ int scale);
+NautilusIconInfo * nautilus_icon_info_lookup_from_name (const char *name,
+ int size,
+ int scale);
+NautilusIconInfo * nautilus_icon_info_lookup_from_path (const char *path,
+ int size,
+ int scale);
+gboolean nautilus_icon_info_is_fallback (NautilusIconInfo *icon);
+GdkPixbuf * nautilus_icon_info_get_pixbuf (NautilusIconInfo *icon);
+GdkPixbuf * nautilus_icon_info_get_pixbuf_nodefault (NautilusIconInfo *icon);
+GdkPixbuf * nautilus_icon_info_get_pixbuf_nodefault_at_size (NautilusIconInfo *icon,
+ gsize forced_size);
+GdkPixbuf * nautilus_icon_info_get_pixbuf_at_size (NautilusIconInfo *icon,
+ gsize forced_size);
+const char * nautilus_icon_info_get_used_name (NautilusIconInfo *icon);
+
+void nautilus_icon_info_clear_caches (void);
+
+gint nautilus_get_icon_size_for_stock_size (GtkIconSize size);
+
+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-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-model.c b/src/nautilus-list-model.c
new file mode 100644
index 0000000..e700669
--- /dev/null
+++ b/src/nautilus-list-model.c
@@ -0,0 +1,1866 @@
+/* fm-list-model.h - a GtkTreeModel for file lists.
+ *
+ * Copyright (C) 2001, 2002 Anders Carlsson
+ * Copyright (C) 2003, Soeren Sandmann
+ * 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 file COPYING.LIB. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Anders Carlsson <andersca@gnu.org>, Soeren Sandmann (sandmann@daimi.au.dk), Dave Camp <dave@ximian.com>
+ */
+
+#include <config.h>
+
+#include "nautilus-list-model.h"
+
+#include <string.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <cairo-gobject.h>
+
+#include <eel/eel-graphic-effects.h>
+#include "nautilus-dnd.h"
+
+enum
+{
+ SUBDIRECTORY_UNLOADED,
+ GET_ICON_SCALE,
+ LAST_SIGNAL
+};
+
+static GQuark attribute_name_q,
+ attribute_modification_date_q,
+ attribute_date_modified_q;
+
+/* msec delay after Loading... dummy row turns into (empty) */
+#define LOADING_TO_EMPTY_DELAY 100
+
+static guint list_model_signals[LAST_SIGNAL] = { 0 };
+
+static int nautilus_list_model_file_entry_compare_func (gconstpointer a,
+ gconstpointer b,
+ gpointer user_data);
+static void nautilus_list_model_tree_model_init (GtkTreeModelIface *iface);
+static void nautilus_list_model_sortable_init (GtkTreeSortableIface *iface);
+
+typedef struct
+{
+ GSequence *files;
+ GHashTable *directory_reverse_map; /* map from directory to GSequenceIter's */
+ GHashTable *top_reverse_map; /* map from files in top dir to GSequenceIter's */
+
+ int stamp;
+
+ GQuark sort_attribute;
+ GtkSortType order;
+
+ gboolean sort_directories_first;
+
+ GtkTreeView *drag_view;
+ int drag_begin_x;
+ int drag_begin_y;
+
+ GPtrArray *columns;
+
+ GList *highlight_files;
+} NautilusListModelPrivate;
+
+typedef struct
+{
+ NautilusListModel *model;
+
+ GList *path_list;
+} DragDataGetInfo;
+
+typedef struct FileEntry FileEntry;
+
+struct FileEntry
+{
+ NautilusFile *file;
+ GHashTable *reverse_map; /* map from files to GSequenceIter's */
+ NautilusDirectory *subdirectory;
+ FileEntry *parent;
+ GSequence *files;
+ GSequenceIter *ptr;
+ guint loaded : 1;
+};
+
+G_DEFINE_TYPE_WITH_CODE (NautilusListModel, nautilus_list_model, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL,
+ nautilus_list_model_tree_model_init)
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_SORTABLE,
+ nautilus_list_model_sortable_init)
+ G_ADD_PRIVATE (NautilusListModel));
+
+static const GtkTargetEntry drag_types [] =
+{
+ { NAUTILUS_ICON_DND_GNOME_ICON_LIST_TYPE, 0, NAUTILUS_ICON_DND_GNOME_ICON_LIST },
+ { NAUTILUS_ICON_DND_URI_LIST_TYPE, 0, NAUTILUS_ICON_DND_URI_LIST },
+};
+
+static void
+file_entry_free (FileEntry *file_entry)
+{
+ nautilus_file_unref (file_entry->file);
+ if (file_entry->reverse_map)
+ {
+ g_hash_table_destroy (file_entry->reverse_map);
+ file_entry->reverse_map = NULL;
+ }
+ if (file_entry->subdirectory != NULL)
+ {
+ nautilus_directory_unref (file_entry->subdirectory);
+ }
+ if (file_entry->files != NULL)
+ {
+ g_sequence_free (file_entry->files);
+ }
+ g_free (file_entry);
+}
+
+static GtkTreeModelFlags
+nautilus_list_model_get_flags (GtkTreeModel *tree_model)
+{
+ return GTK_TREE_MODEL_ITERS_PERSIST;
+}
+
+static int
+nautilus_list_model_get_n_columns (GtkTreeModel *tree_model)
+{
+ NautilusListModelPrivate *priv;
+
+ priv = nautilus_list_model_get_instance_private (NAUTILUS_LIST_MODEL (tree_model));
+
+ return NAUTILUS_LIST_MODEL_NUM_COLUMNS + priv->columns->len;
+}
+
+static GType
+nautilus_list_model_get_column_type (GtkTreeModel *tree_model,
+ int index)
+{
+ NautilusListModel *model;
+ NautilusListModelPrivate *priv;
+
+ model = NAUTILUS_LIST_MODEL (tree_model);
+ priv = nautilus_list_model_get_instance_private (model);
+
+ switch (index)
+ {
+ case NAUTILUS_LIST_MODEL_FILE_COLUMN:
+ {
+ return NAUTILUS_TYPE_FILE;
+ }
+
+ case NAUTILUS_LIST_MODEL_SUBDIRECTORY_COLUMN:
+ {
+ return NAUTILUS_TYPE_DIRECTORY;
+ }
+
+ case NAUTILUS_LIST_MODEL_SMALL_ICON_COLUMN:
+ case NAUTILUS_LIST_MODEL_STANDARD_ICON_COLUMN:
+ case NAUTILUS_LIST_MODEL_LARGE_ICON_COLUMN:
+ case NAUTILUS_LIST_MODEL_LARGER_ICON_COLUMN:
+ {
+ return CAIRO_GOBJECT_TYPE_SURFACE;
+ }
+
+ case NAUTILUS_LIST_MODEL_FILE_NAME_IS_EDITABLE_COLUMN:
+ {
+ return G_TYPE_BOOLEAN;
+ }
+
+ default:
+ if (index < NAUTILUS_LIST_MODEL_NUM_COLUMNS + priv->columns->len)
+ {
+ return G_TYPE_STRING;
+ }
+ else
+ {
+ return G_TYPE_INVALID;
+ }
+ }
+}
+
+static void
+nautilus_list_model_ptr_to_iter (NautilusListModel *model,
+ GSequenceIter *ptr,
+ GtkTreeIter *iter)
+{
+ NautilusListModelPrivate *priv;
+
+ priv = nautilus_list_model_get_instance_private (model);
+
+ g_assert (!g_sequence_iter_is_end (ptr));
+
+ if (iter != NULL)
+ {
+ iter->stamp = priv->stamp;
+ iter->user_data = ptr;
+ }
+}
+
+static gboolean
+nautilus_list_model_get_iter (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreePath *path)
+{
+ NautilusListModel *model;
+ NautilusListModelPrivate *priv;
+ GSequence *files;
+ GSequenceIter *ptr;
+ FileEntry *file_entry;
+ int i, d;
+
+ model = NAUTILUS_LIST_MODEL (tree_model);
+ priv = nautilus_list_model_get_instance_private (model);
+ ptr = NULL;
+
+ files = priv->files;
+ for (d = 0; d < gtk_tree_path_get_depth (path); d++)
+ {
+ i = gtk_tree_path_get_indices (path)[d];
+
+ if (files == NULL || i >= g_sequence_get_length (files))
+ {
+ return FALSE;
+ }
+
+ ptr = g_sequence_get_iter_at_pos (files, i);
+ file_entry = g_sequence_get (ptr);
+ files = file_entry->files;
+ }
+
+ nautilus_list_model_ptr_to_iter (model, ptr, iter);
+
+ return TRUE;
+}
+
+static GtkTreePath *
+nautilus_list_model_get_path (GtkTreeModel *tree_model,
+ GtkTreeIter *iter)
+{
+ GtkTreePath *path;
+ NautilusListModel *model;
+ NautilusListModelPrivate *priv;
+ GSequenceIter *ptr;
+ FileEntry *file_entry;
+
+ model = NAUTILUS_LIST_MODEL (tree_model);
+ priv = nautilus_list_model_get_instance_private (model);
+
+ g_return_val_if_fail (iter->stamp == priv->stamp, NULL);
+
+ if (g_sequence_iter_is_end (iter->user_data))
+ {
+ /* FIXME is this right? */
+ return NULL;
+ }
+
+ path = gtk_tree_path_new ();
+ ptr = iter->user_data;
+ while (ptr != NULL)
+ {
+ gtk_tree_path_prepend_index (path, g_sequence_iter_get_position (ptr));
+ file_entry = g_sequence_get (ptr);
+ if (file_entry->parent != NULL)
+ {
+ ptr = file_entry->parent->ptr;
+ }
+ else
+ {
+ ptr = NULL;
+ }
+ }
+
+ return path;
+}
+
+static gint
+nautilus_list_model_get_icon_scale (NautilusListModel *model)
+{
+ gint retval = -1;
+
+ g_signal_emit (model, list_model_signals[GET_ICON_SCALE], 0,
+ &retval);
+
+ if (retval == -1)
+ {
+ retval = gdk_monitor_get_scale_factor (gdk_display_get_monitor (gdk_display_get_default (), 0));
+ }
+
+ return retval;
+}
+
+guint
+nautilus_list_model_get_icon_size_for_zoom_level (NautilusListZoomLevel zoom_level)
+{
+ switch (zoom_level)
+ {
+ case NAUTILUS_LIST_ZOOM_LEVEL_SMALL:
+ {
+ return NAUTILUS_LIST_ICON_SIZE_SMALL;
+ }
+
+ case NAUTILUS_LIST_ZOOM_LEVEL_STANDARD:
+ {
+ return NAUTILUS_LIST_ICON_SIZE_STANDARD;
+ }
+
+ case NAUTILUS_LIST_ZOOM_LEVEL_LARGE:
+ {
+ return NAUTILUS_LIST_ICON_SIZE_LARGE;
+ }
+
+ case NAUTILUS_LIST_ZOOM_LEVEL_LARGER:
+ return NAUTILUS_LIST_ICON_SIZE_LARGER;
+ }
+ g_return_val_if_reached (NAUTILUS_LIST_ICON_SIZE_STANDARD);
+}
+
+static void
+nautilus_list_model_get_value (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ int column,
+ GValue *value)
+{
+ NautilusListModel *model;
+ NautilusListModelPrivate *priv;
+ FileEntry *file_entry;
+ NautilusFile *file;
+ char *str;
+ GdkPixbuf *icon, *rendered_icon;
+ int icon_size, icon_scale;
+ NautilusListZoomLevel zoom_level;
+ NautilusFileIconFlags flags;
+ cairo_surface_t *surface;
+
+ model = NAUTILUS_LIST_MODEL (tree_model);
+ priv = nautilus_list_model_get_instance_private (model);
+
+ g_return_if_fail (priv->stamp == iter->stamp);
+ g_return_if_fail (!g_sequence_iter_is_end (iter->user_data));
+
+ file_entry = g_sequence_get (iter->user_data);
+ file = file_entry->file;
+
+ switch (column)
+ {
+ case NAUTILUS_LIST_MODEL_FILE_COLUMN:
+ {
+ g_value_init (value, NAUTILUS_TYPE_FILE);
+
+ g_value_set_object (value, file);
+ }
+ break;
+
+ case NAUTILUS_LIST_MODEL_SUBDIRECTORY_COLUMN:
+ {
+ g_value_init (value, NAUTILUS_TYPE_DIRECTORY);
+
+ g_value_set_object (value, file_entry->subdirectory);
+ }
+ break;
+
+ case NAUTILUS_LIST_MODEL_SMALL_ICON_COLUMN:
+ case NAUTILUS_LIST_MODEL_STANDARD_ICON_COLUMN:
+ case NAUTILUS_LIST_MODEL_LARGE_ICON_COLUMN:
+ case NAUTILUS_LIST_MODEL_LARGER_ICON_COLUMN:
+ {
+ g_value_init (value, CAIRO_GOBJECT_TYPE_SURFACE);
+
+ if (file != NULL)
+ {
+ zoom_level = nautilus_list_model_get_zoom_level_from_column_id (column);
+ icon_size = nautilus_list_model_get_icon_size_for_zoom_level (zoom_level);
+ icon_scale = nautilus_list_model_get_icon_scale (model);
+
+ flags = NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS |
+ NAUTILUS_FILE_ICON_FLAGS_FORCE_THUMBNAIL_SIZE |
+ NAUTILUS_FILE_ICON_FLAGS_USE_EMBLEMS |
+ NAUTILUS_FILE_ICON_FLAGS_USE_ONE_EMBLEM;
+
+ if (priv->drag_view != NULL)
+ {
+ GtkTreePath *path_a, *path_b;
+
+ gtk_tree_view_get_drag_dest_row (priv->drag_view,
+ &path_a,
+ NULL);
+ if (path_a != NULL)
+ {
+ path_b = gtk_tree_model_get_path (tree_model, iter);
+
+ if (gtk_tree_path_compare (path_a, path_b) == 0)
+ {
+ flags |= NAUTILUS_FILE_ICON_FLAGS_FOR_DRAG_ACCEPT;
+ }
+
+ gtk_tree_path_free (path_a);
+ gtk_tree_path_free (path_b);
+ }
+ }
+
+ icon = nautilus_file_get_icon_pixbuf (file, icon_size, TRUE, icon_scale, flags);
+
+ if (priv->highlight_files != NULL &&
+ g_list_find_custom (priv->highlight_files,
+ file, (GCompareFunc) nautilus_file_compare_location))
+ {
+ rendered_icon = eel_create_spotlight_pixbuf (icon);
+
+ if (rendered_icon != NULL)
+ {
+ g_object_unref (icon);
+ icon = rendered_icon;
+ }
+ }
+
+ surface = gdk_cairo_surface_create_from_pixbuf (icon, icon_scale, NULL);
+ g_value_take_boxed (value, surface);
+ g_object_unref (icon);
+ }
+ }
+ break;
+
+ case NAUTILUS_LIST_MODEL_FILE_NAME_IS_EDITABLE_COLUMN:
+ {
+ g_value_init (value, G_TYPE_BOOLEAN);
+
+ g_value_set_boolean (value, file != NULL && nautilus_file_can_rename (file));
+ }
+ break;
+
+ default:
+ if (column >= NAUTILUS_LIST_MODEL_NUM_COLUMNS && column < NAUTILUS_LIST_MODEL_NUM_COLUMNS + priv->columns->len)
+ {
+ NautilusColumn *nautilus_column;
+ GQuark attribute;
+ nautilus_column = priv->columns->pdata[column - NAUTILUS_LIST_MODEL_NUM_COLUMNS];
+
+ g_value_init (value, G_TYPE_STRING);
+ g_object_get (nautilus_column,
+ "attribute_q", &attribute,
+ NULL);
+ if (file != NULL)
+ {
+ str = nautilus_file_get_string_attribute_with_default_q (file,
+ attribute);
+ g_value_take_string (value, str);
+ }
+ else if (attribute == attribute_name_q)
+ {
+ if (file_entry->parent->loaded)
+ {
+ g_value_set_string (value, _("(Empty)"));
+ }
+ else
+ {
+ g_value_set_string (value, _("Loading…"));
+ }
+ }
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+ }
+}
+
+static gboolean
+nautilus_list_model_iter_next (GtkTreeModel *tree_model,
+ GtkTreeIter *iter)
+{
+ NautilusListModel *model;
+ NautilusListModelPrivate *priv;
+
+ model = NAUTILUS_LIST_MODEL (tree_model);
+ priv = nautilus_list_model_get_instance_private (model);
+
+ g_return_val_if_fail (priv->stamp == iter->stamp, FALSE);
+
+ iter->user_data = g_sequence_iter_next (iter->user_data);
+
+ return !g_sequence_iter_is_end (iter->user_data);
+}
+
+static gboolean
+nautilus_list_model_iter_children (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent)
+{
+ NautilusListModel *model;
+ NautilusListModelPrivate *priv;
+ GSequence *files;
+ FileEntry *file_entry;
+
+ model = NAUTILUS_LIST_MODEL (tree_model);
+ priv = nautilus_list_model_get_instance_private (model);
+
+ if (parent == NULL)
+ {
+ files = priv->files;
+ }
+ else
+ {
+ file_entry = g_sequence_get (parent->user_data);
+ files = file_entry->files;
+ }
+
+ if (files == NULL || g_sequence_get_length (files) == 0)
+ {
+ return FALSE;
+ }
+
+ iter->stamp = priv->stamp;
+ iter->user_data = g_sequence_get_begin_iter (files);
+
+ return TRUE;
+}
+
+static gboolean
+nautilus_list_model_iter_has_child (GtkTreeModel *tree_model,
+ GtkTreeIter *iter)
+{
+ FileEntry *file_entry;
+
+ if (iter == NULL)
+ {
+ return !nautilus_list_model_is_empty (NAUTILUS_LIST_MODEL (tree_model));
+ }
+
+ file_entry = g_sequence_get (iter->user_data);
+
+ return (file_entry->files != NULL && g_sequence_get_length (file_entry->files) > 0);
+}
+
+static int
+nautilus_list_model_iter_n_children (GtkTreeModel *tree_model,
+ GtkTreeIter *iter)
+{
+ NautilusListModel *model;
+ NautilusListModelPrivate *priv;
+ GSequence *files;
+ FileEntry *file_entry;
+
+ model = NAUTILUS_LIST_MODEL (tree_model);
+ priv = nautilus_list_model_get_instance_private (model);
+
+ if (iter == NULL)
+ {
+ files = priv->files;
+ }
+ else
+ {
+ file_entry = g_sequence_get (iter->user_data);
+ files = file_entry->files;
+ }
+
+ return g_sequence_get_length (files);
+}
+
+static gboolean
+nautilus_list_model_iter_nth_child (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent,
+ int n)
+{
+ NautilusListModel *model;
+ NautilusListModelPrivate *priv;
+ GSequenceIter *child;
+ GSequence *files;
+ FileEntry *file_entry;
+
+ model = NAUTILUS_LIST_MODEL (tree_model);
+ priv = nautilus_list_model_get_instance_private (model);
+
+ if (parent != NULL)
+ {
+ file_entry = g_sequence_get (parent->user_data);
+ files = file_entry->files;
+ }
+ else
+ {
+ files = priv->files;
+ }
+
+ child = g_sequence_get_iter_at_pos (files, n);
+
+ if (g_sequence_iter_is_end (child))
+ {
+ return FALSE;
+ }
+
+ iter->stamp = priv->stamp;
+ iter->user_data = child;
+
+ return TRUE;
+}
+
+static gboolean
+nautilus_list_model_iter_parent (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *child)
+{
+ NautilusListModel *model;
+ NautilusListModelPrivate *priv;
+ FileEntry *file_entry;
+
+ model = NAUTILUS_LIST_MODEL (tree_model);
+ priv = nautilus_list_model_get_instance_private (model);
+
+ file_entry = g_sequence_get (child->user_data);
+
+ if (file_entry->parent == NULL)
+ {
+ return FALSE;
+ }
+
+ iter->stamp = priv->stamp;
+ iter->user_data = file_entry->parent->ptr;
+
+ return TRUE;
+}
+
+static GSequenceIter *
+lookup_file (NautilusListModel *model,
+ NautilusFile *file,
+ NautilusDirectory *directory)
+{
+ NautilusListModelPrivate *priv;
+ FileEntry *file_entry;
+ GSequenceIter *ptr, *parent_ptr;
+
+ priv = nautilus_list_model_get_instance_private (model);
+
+ parent_ptr = NULL;
+ if (directory)
+ {
+ parent_ptr = g_hash_table_lookup (priv->directory_reverse_map,
+ directory);
+ }
+
+ if (parent_ptr)
+ {
+ file_entry = g_sequence_get (parent_ptr);
+ ptr = g_hash_table_lookup (file_entry->reverse_map, file);
+ }
+ else
+ {
+ ptr = g_hash_table_lookup (priv->top_reverse_map, file);
+ }
+
+ if (ptr)
+ {
+ g_assert (((FileEntry *) g_sequence_get (ptr))->file == file);
+ }
+
+ return ptr;
+}
+
+
+struct GetIters
+{
+ NautilusListModel *model;
+ NautilusFile *file;
+ GList *iters;
+};
+
+static void
+dir_to_iters (struct GetIters *data,
+ GHashTable *reverse_map)
+{
+ GSequenceIter *ptr;
+
+ ptr = g_hash_table_lookup (reverse_map, data->file);
+ if (ptr)
+ {
+ GtkTreeIter *iter;
+ iter = g_new0 (GtkTreeIter, 1);
+ nautilus_list_model_ptr_to_iter (data->model, ptr, iter);
+ data->iters = g_list_prepend (data->iters, iter);
+ }
+}
+
+static void
+file_to_iter_cb (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ struct GetIters *data;
+ FileEntry *dir_file_entry;
+
+ data = user_data;
+ dir_file_entry = g_sequence_get ((GSequenceIter *) value);
+ dir_to_iters (data, dir_file_entry->reverse_map);
+}
+
+GList *
+nautilus_list_model_get_all_iters_for_file (NautilusListModel *model,
+ NautilusFile *file)
+{
+ struct GetIters data;
+ NautilusListModelPrivate *priv;
+ data.file = file;
+ data.model = model;
+ data.iters = NULL;
+
+ priv = nautilus_list_model_get_instance_private (model);
+
+ dir_to_iters (&data, priv->top_reverse_map);
+ g_hash_table_foreach (priv->directory_reverse_map,
+ file_to_iter_cb, &data);
+
+ return g_list_reverse (data.iters);
+}
+
+gboolean
+nautilus_list_model_get_first_iter_for_file (NautilusListModel *model,
+ NautilusFile *file,
+ GtkTreeIter *iter)
+{
+ GList *list;
+ gboolean res;
+
+ res = FALSE;
+
+ list = nautilus_list_model_get_all_iters_for_file (model, file);
+ if (list != NULL)
+ {
+ res = TRUE;
+ *iter = *(GtkTreeIter *) list->data;
+ }
+ g_list_free_full (list, g_free);
+
+ return res;
+}
+
+
+gboolean
+nautilus_list_model_get_tree_iter_from_file (NautilusListModel *model,
+ NautilusFile *file,
+ NautilusDirectory *directory,
+ GtkTreeIter *iter)
+{
+ GSequenceIter *ptr;
+
+ ptr = lookup_file (model, file, directory);
+ if (!ptr)
+ {
+ return FALSE;
+ }
+
+ nautilus_list_model_ptr_to_iter (model, ptr, iter);
+
+ return TRUE;
+}
+
+static int
+nautilus_list_model_file_entry_compare_func (gconstpointer a,
+ gconstpointer b,
+ gpointer user_data)
+{
+ FileEntry *file_entry1;
+ FileEntry *file_entry2;
+ NautilusListModel *model;
+ NautilusListModelPrivate *priv;
+ int result;
+
+ model = NAUTILUS_LIST_MODEL (user_data);
+ priv = nautilus_list_model_get_instance_private (model);
+
+ file_entry1 = (FileEntry *) a;
+ file_entry2 = (FileEntry *) b;
+
+ if (file_entry1->file != NULL && file_entry2->file != NULL)
+ {
+ result = nautilus_file_compare_for_sort_by_attribute_q (file_entry1->file, file_entry2->file,
+ priv->sort_attribute,
+ priv->sort_directories_first,
+ (priv->order == GTK_SORT_DESCENDING));
+ }
+ else if (file_entry1->file == NULL)
+ {
+ return -1;
+ }
+ else
+ {
+ return 1;
+ }
+
+ return result;
+}
+
+int
+nautilus_list_model_compare_func (NautilusListModel *model,
+ NautilusFile *file1,
+ NautilusFile *file2)
+{
+ NautilusListModelPrivate *priv;
+ int result;
+
+ priv = nautilus_list_model_get_instance_private (model);
+ result = nautilus_file_compare_for_sort_by_attribute_q (file1, file2,
+ priv->sort_attribute,
+ priv->sort_directories_first,
+ (priv->order == GTK_SORT_DESCENDING));
+
+ return result;
+}
+
+static void
+nautilus_list_model_sort_file_entries (NautilusListModel *model,
+ GSequence *files,
+ GtkTreePath *path)
+{
+ GSequenceIter **old_order;
+ GtkTreeIter iter;
+ int *new_order;
+ int length;
+ int i;
+ FileEntry *file_entry;
+ gboolean has_iter;
+
+ length = g_sequence_get_length (files);
+
+ if (length <= 1)
+ {
+ return;
+ }
+
+ /* generate old order of GSequenceIter's */
+ old_order = g_new (GSequenceIter *, length);
+ for (i = 0; i < length; ++i)
+ {
+ GSequenceIter *ptr = g_sequence_get_iter_at_pos (files, i);
+
+ file_entry = g_sequence_get (ptr);
+ if (file_entry->files != NULL)
+ {
+ gtk_tree_path_append_index (path, i);
+ nautilus_list_model_sort_file_entries (model, file_entry->files, path);
+ gtk_tree_path_up (path);
+ }
+
+ old_order[i] = ptr;
+ }
+
+ /* sort */
+ g_sequence_sort (files, nautilus_list_model_file_entry_compare_func, model);
+
+ /* generate new order */
+ new_order = g_new (int, length);
+ /* Note: new_order[newpos] = oldpos */
+ for (i = 0; i < length; ++i)
+ {
+ new_order[g_sequence_iter_get_position (old_order[i])] = i;
+ }
+
+ /* Let the world know about our new order */
+
+ g_assert (new_order != NULL);
+
+ has_iter = FALSE;
+ if (gtk_tree_path_get_depth (path) != 0)
+ {
+ gboolean get_iter_result;
+ has_iter = TRUE;
+ get_iter_result = gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path);
+ g_assert (get_iter_result);
+ }
+
+ gtk_tree_model_rows_reordered (GTK_TREE_MODEL (model),
+ path, has_iter ? &iter : NULL, new_order);
+
+ g_free (old_order);
+ g_free (new_order);
+}
+
+static void
+nautilus_list_model_sort (NautilusListModel *model)
+{
+ GtkTreePath *path;
+ NautilusListModelPrivate *priv;
+
+ path = gtk_tree_path_new ();
+ priv = nautilus_list_model_get_instance_private (model);
+
+ nautilus_list_model_sort_file_entries (model, priv->files, path);
+
+ gtk_tree_path_free (path);
+}
+
+static gboolean
+nautilus_list_model_get_sort_column_id (GtkTreeSortable *sortable,
+ gint *sort_column_id,
+ GtkSortType *order)
+{
+ NautilusListModel *model;
+ NautilusListModelPrivate *priv;
+ int id;
+
+ model = NAUTILUS_LIST_MODEL (sortable);
+ priv = nautilus_list_model_get_instance_private (model);
+ id = nautilus_list_model_get_sort_column_id_from_attribute (model, priv->sort_attribute);
+
+ if (id == -1)
+ {
+ return FALSE;
+ }
+
+ if (sort_column_id != NULL)
+ {
+ *sort_column_id = id;
+ }
+
+ if (order != NULL)
+ {
+ *order = priv->order;
+ }
+
+ return TRUE;
+}
+
+static void
+nautilus_list_model_set_sort_column_id (GtkTreeSortable *sortable,
+ gint sort_column_id,
+ GtkSortType order)
+{
+ NautilusListModel *model;
+ NautilusListModelPrivate *priv;
+
+ model = NAUTILUS_LIST_MODEL (sortable);
+ priv = nautilus_list_model_get_instance_private (model);
+
+ priv->sort_attribute = nautilus_list_model_get_attribute_from_sort_column_id (model, sort_column_id);
+
+ priv->order = order;
+
+ nautilus_list_model_sort (model);
+ gtk_tree_sortable_sort_column_changed (sortable);
+}
+
+static gboolean
+nautilus_list_model_has_default_sort_func (GtkTreeSortable *sortable)
+{
+ return FALSE;
+}
+
+static void
+add_dummy_row (NautilusListModel *model,
+ FileEntry *parent_entry)
+{
+ NautilusListModelPrivate *priv;
+ FileEntry *dummy_file_entry;
+ GtkTreeIter iter;
+ GtkTreePath *path;
+
+ priv = nautilus_list_model_get_instance_private (model);
+ dummy_file_entry = g_new0 (FileEntry, 1);
+ dummy_file_entry->parent = parent_entry;
+ dummy_file_entry->ptr = g_sequence_insert_sorted (parent_entry->files, dummy_file_entry,
+ nautilus_list_model_file_entry_compare_func, model);
+ iter.stamp = priv->stamp;
+ iter.user_data = dummy_file_entry->ptr;
+
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
+ gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter);
+ gtk_tree_path_free (path);
+}
+
+gboolean
+nautilus_list_model_add_file (NautilusListModel *model,
+ NautilusFile *file,
+ NautilusDirectory *directory)
+{
+ NautilusListModelPrivate *priv;
+ GtkTreeIter iter;
+ GtkTreePath *path;
+ FileEntry *file_entry;
+ GSequenceIter *ptr, *parent_ptr;
+ GSequence *files;
+ gboolean replace_dummy;
+ GHashTable *parent_hash;
+
+ priv = nautilus_list_model_get_instance_private (model);
+
+ parent_ptr = g_hash_table_lookup (priv->directory_reverse_map,
+ directory);
+ if (parent_ptr)
+ {
+ file_entry = g_sequence_get (parent_ptr);
+ ptr = g_hash_table_lookup (file_entry->reverse_map, file);
+ }
+ else
+ {
+ file_entry = NULL;
+ ptr = g_hash_table_lookup (priv->top_reverse_map, file);
+ }
+
+ if (ptr != NULL)
+ {
+ g_warning ("file already in tree (parent_ptr: %p)!!!\n", parent_ptr);
+ return FALSE;
+ }
+
+ file_entry = g_new0 (FileEntry, 1);
+ file_entry->file = nautilus_file_ref (file);
+ file_entry->parent = NULL;
+ file_entry->subdirectory = NULL;
+ file_entry->files = NULL;
+
+ files = priv->files;
+ parent_hash = priv->top_reverse_map;
+
+ replace_dummy = FALSE;
+
+ if (parent_ptr != NULL)
+ {
+ file_entry->parent = g_sequence_get (parent_ptr);
+ /* At this point we set loaded. Either we saw
+ * "done" and ignored it waiting for this, or we do this
+ * earlier, but then we replace the dummy row anyway,
+ * so it doesn't matter */
+ file_entry->parent->loaded = 1;
+ parent_hash = file_entry->parent->reverse_map;
+ files = file_entry->parent->files;
+ if (g_sequence_get_length (files) == 1)
+ {
+ GSequenceIter *dummy_ptr = g_sequence_get_iter_at_pos (files, 0);
+ FileEntry *dummy_entry = g_sequence_get (dummy_ptr);
+ if (dummy_entry->file == NULL)
+ {
+ /* replace the dummy loading entry */
+ priv->stamp++;
+ g_sequence_remove (dummy_ptr);
+
+ replace_dummy = TRUE;
+ }
+ }
+ }
+
+
+ file_entry->ptr = g_sequence_insert_sorted (files, file_entry,
+ nautilus_list_model_file_entry_compare_func, model);
+
+ g_hash_table_insert (parent_hash, file, file_entry->ptr);
+
+ iter.stamp = priv->stamp;
+ iter.user_data = file_entry->ptr;
+
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
+ if (replace_dummy)
+ {
+ gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter);
+ }
+ else
+ {
+ gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter);
+ }
+
+ if (nautilus_file_is_directory (file))
+ {
+ file_entry->files = g_sequence_new ((GDestroyNotify) file_entry_free);
+
+ add_dummy_row (model, file_entry);
+
+ gtk_tree_model_row_has_child_toggled (GTK_TREE_MODEL (model),
+ path, &iter);
+ }
+ gtk_tree_path_free (path);
+
+ return TRUE;
+}
+
+void
+nautilus_list_model_file_changed (NautilusListModel *model,
+ NautilusFile *file,
+ NautilusDirectory *directory)
+{
+ NautilusListModelPrivate *priv;
+ FileEntry *parent_file_entry;
+ GtkTreeIter iter;
+ GtkTreePath *path, *parent_path;
+ GSequenceIter *ptr;
+ int pos_before, pos_after, length, i, old;
+ int *new_order;
+ gboolean has_iter;
+ GSequence *files;
+
+ priv = nautilus_list_model_get_instance_private (model);
+
+ ptr = lookup_file (model, file, directory);
+ if (!ptr)
+ {
+ return;
+ }
+
+
+ pos_before = g_sequence_iter_get_position (ptr);
+
+ g_sequence_sort_changed (ptr, nautilus_list_model_file_entry_compare_func, model);
+
+ pos_after = g_sequence_iter_get_position (ptr);
+
+ if (pos_before != pos_after)
+ {
+ /* The file moved, we need to send rows_reordered */
+
+ parent_file_entry = ((FileEntry *) g_sequence_get (ptr))->parent;
+
+ if (parent_file_entry == NULL)
+ {
+ has_iter = FALSE;
+ parent_path = gtk_tree_path_new ();
+ files = priv->files;
+ }
+ else
+ {
+ has_iter = TRUE;
+ nautilus_list_model_ptr_to_iter (model, parent_file_entry->ptr, &iter);
+ parent_path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
+ files = parent_file_entry->files;
+ }
+
+ length = g_sequence_get_length (files);
+ new_order = g_new (int, length);
+ /* Note: new_order[newpos] = oldpos */
+ for (i = 0, old = 0; i < length; ++i)
+ {
+ if (i == pos_after)
+ {
+ new_order[i] = pos_before;
+ }
+ else
+ {
+ if (old == pos_before)
+ {
+ old++;
+ }
+ new_order[i] = old++;
+ }
+ }
+
+ gtk_tree_model_rows_reordered (GTK_TREE_MODEL (model),
+ parent_path, has_iter ? &iter : NULL, new_order);
+
+ gtk_tree_path_free (parent_path);
+ g_free (new_order);
+ }
+
+ nautilus_list_model_ptr_to_iter (model, ptr, &iter);
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
+ gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter);
+ gtk_tree_path_free (path);
+}
+
+gboolean
+nautilus_list_model_is_empty (NautilusListModel *model)
+{
+ NautilusListModelPrivate *priv;
+
+ priv = nautilus_list_model_get_instance_private (model);
+
+ return (g_sequence_get_length (priv->files) == 0);
+}
+
+static void
+nautilus_list_model_remove (NautilusListModel *model,
+ GtkTreeIter *iter)
+{
+ NautilusListModelPrivate *priv;
+ GSequenceIter *ptr, *child_ptr;
+ FileEntry *file_entry, *child_file_entry, *parent_file_entry;
+ GtkTreePath *path;
+ GtkTreeIter parent_iter;
+
+ priv = nautilus_list_model_get_instance_private (model);
+ ptr = iter->user_data;
+ file_entry = g_sequence_get (ptr);
+
+ if (file_entry->files != NULL)
+ {
+ while (g_sequence_get_length (file_entry->files) > 0)
+ {
+ child_ptr = g_sequence_get_begin_iter (file_entry->files);
+ child_file_entry = g_sequence_get (child_ptr);
+ if (child_file_entry->file != NULL)
+ {
+ nautilus_list_model_remove_file (model,
+ child_file_entry->file,
+ file_entry->subdirectory);
+ }
+ else
+ {
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter);
+ gtk_tree_path_append_index (path, 0);
+ priv->stamp++;
+ g_sequence_remove (child_ptr);
+ gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path);
+ gtk_tree_path_free (path);
+ }
+
+ /* the parent iter didn't actually change */
+ iter->stamp = priv->stamp;
+ }
+ }
+
+ if (file_entry->file != NULL) /* Don't try to remove dummy row */
+ {
+ if (file_entry->parent != NULL)
+ {
+ g_hash_table_remove (file_entry->parent->reverse_map, file_entry->file);
+ }
+ else
+ {
+ g_hash_table_remove (priv->top_reverse_map, file_entry->file);
+ }
+ }
+
+ parent_file_entry = file_entry->parent;
+ if (parent_file_entry && g_sequence_get_length (parent_file_entry->files) == 1 &&
+ file_entry->file != NULL)
+ {
+ /* this is the last non-dummy child, add a dummy node */
+ /* We need to do this before removing the last file to avoid
+ * collapsing the row.
+ */
+ add_dummy_row (model, parent_file_entry);
+ }
+
+ if (file_entry->subdirectory != NULL)
+ {
+ g_signal_emit (model,
+ list_model_signals[SUBDIRECTORY_UNLOADED], 0,
+ file_entry->subdirectory);
+ g_hash_table_remove (priv->directory_reverse_map,
+ file_entry->subdirectory);
+ }
+
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter);
+
+ g_sequence_remove (ptr);
+ priv->stamp++;
+ gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path);
+
+ gtk_tree_path_free (path);
+
+ if (parent_file_entry && g_sequence_get_length (parent_file_entry->files) == 0)
+ {
+ parent_iter.stamp = priv->stamp;
+ parent_iter.user_data = parent_file_entry->ptr;
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &parent_iter);
+ gtk_tree_model_row_has_child_toggled (GTK_TREE_MODEL (model),
+ path, &parent_iter);
+ gtk_tree_path_free (path);
+ }
+}
+
+void
+nautilus_list_model_remove_file (NautilusListModel *model,
+ NautilusFile *file,
+ NautilusDirectory *directory)
+{
+ GtkTreeIter iter;
+
+ if (nautilus_list_model_get_tree_iter_from_file (model, file, directory, &iter))
+ {
+ nautilus_list_model_remove (model, &iter);
+ }
+}
+
+static void
+nautilus_list_model_clear_directory (NautilusListModel *model,
+ GSequence *files)
+{
+ NautilusListModelPrivate *priv;
+ GtkTreeIter iter;
+ FileEntry *file_entry;
+
+ priv = nautilus_list_model_get_instance_private (model);
+
+ while (g_sequence_get_length (files) > 0)
+ {
+ iter.user_data = g_sequence_get_begin_iter (files);
+
+ file_entry = g_sequence_get (iter.user_data);
+ if (file_entry->files != NULL)
+ {
+ nautilus_list_model_clear_directory (model, file_entry->files);
+ }
+
+ iter.stamp = priv->stamp;
+ nautilus_list_model_remove (model, &iter);
+ }
+}
+
+void
+nautilus_list_model_clear (NautilusListModel *model)
+{
+ NautilusListModelPrivate *priv;
+
+ g_return_if_fail (model != NULL);
+
+ priv = nautilus_list_model_get_instance_private (model);
+
+ nautilus_list_model_clear_directory (model, priv->files);
+}
+
+NautilusFile *
+nautilus_list_model_file_for_path (NautilusListModel *model,
+ GtkTreePath *path)
+{
+ NautilusFile *file;
+ GtkTreeIter iter;
+
+ file = NULL;
+ if (gtk_tree_model_get_iter (GTK_TREE_MODEL (model),
+ &iter, path))
+ {
+ gtk_tree_model_get (GTK_TREE_MODEL (model),
+ &iter,
+ NAUTILUS_LIST_MODEL_FILE_COLUMN, &file,
+ -1);
+ }
+ return file;
+}
+
+gboolean
+nautilus_list_model_load_subdirectory (NautilusListModel *model,
+ GtkTreePath *path,
+ NautilusDirectory **directory)
+{
+ NautilusListModelPrivate *priv;
+ GtkTreeIter iter;
+ FileEntry *file_entry;
+ NautilusDirectory *subdirectory;
+
+ priv = nautilus_list_model_get_instance_private (model);
+
+ if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path))
+ {
+ return FALSE;
+ }
+
+ file_entry = g_sequence_get (iter.user_data);
+ if (file_entry->file == NULL ||
+ file_entry->subdirectory != NULL)
+ {
+ return FALSE;
+ }
+
+ subdirectory = nautilus_directory_get_for_file (file_entry->file);
+
+ if (g_hash_table_lookup (priv->directory_reverse_map, subdirectory) != NULL)
+ {
+ nautilus_directory_unref (subdirectory);
+ g_warning ("Already in directory_reverse_map, failing\n");
+ return FALSE;
+ }
+
+ file_entry->subdirectory = subdirectory,
+ g_hash_table_insert (priv->directory_reverse_map,
+ subdirectory, file_entry->ptr);
+ file_entry->reverse_map = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ /* Return a ref too */
+ nautilus_directory_ref (subdirectory);
+ *directory = subdirectory;
+
+ return TRUE;
+}
+
+/* removes all children of the subfolder and unloads the subdirectory */
+void
+nautilus_list_model_unload_subdirectory (NautilusListModel *model,
+ GtkTreeIter *iter)
+{
+ NautilusListModelPrivate *priv;
+ GSequenceIter *child_ptr;
+ FileEntry *file_entry, *child_file_entry;
+ GtkTreeIter child_iter;
+
+ priv = nautilus_list_model_get_instance_private (model);
+
+ file_entry = g_sequence_get (iter->user_data);
+ if (file_entry->file == NULL ||
+ file_entry->subdirectory == NULL)
+ {
+ return;
+ }
+
+ file_entry->loaded = 0;
+
+ /* Remove all children */
+ while (g_sequence_get_length (file_entry->files) > 0)
+ {
+ child_ptr = g_sequence_get_begin_iter (file_entry->files);
+ child_file_entry = g_sequence_get (child_ptr);
+ if (child_file_entry->file == NULL)
+ {
+ /* Don't delete the dummy node */
+ break;
+ }
+ else
+ {
+ nautilus_list_model_ptr_to_iter (model, child_ptr, &child_iter);
+ nautilus_list_model_remove (model, &child_iter);
+ }
+ }
+
+ /* Emit unload signal */
+ g_signal_emit (model,
+ list_model_signals[SUBDIRECTORY_UNLOADED], 0,
+ file_entry->subdirectory);
+
+ /* actually unload */
+ g_hash_table_remove (priv->directory_reverse_map,
+ file_entry->subdirectory);
+ nautilus_directory_unref (file_entry->subdirectory);
+ file_entry->subdirectory = NULL;
+
+ g_assert (g_hash_table_size (file_entry->reverse_map) == 0);
+ g_hash_table_destroy (file_entry->reverse_map);
+ file_entry->reverse_map = NULL;
+}
+
+
+
+void
+nautilus_list_model_set_should_sort_directories_first (NautilusListModel *model,
+ gboolean sort_directories_first)
+{
+ NautilusListModelPrivate *priv;
+
+ priv = nautilus_list_model_get_instance_private (model);
+
+ if (priv->sort_directories_first == sort_directories_first)
+ {
+ return;
+ }
+
+ priv->sort_directories_first = sort_directories_first;
+ nautilus_list_model_sort (model);
+}
+
+int
+nautilus_list_model_get_sort_column_id_from_attribute (NautilusListModel *model,
+ GQuark attribute)
+{
+ NautilusListModelPrivate *priv;
+ guint i;
+
+ if (attribute == 0)
+ {
+ return -1;
+ }
+
+ priv = nautilus_list_model_get_instance_private (model);
+
+ /* Hack - the preferences dialog sets modification_date for some
+ * rather than date_modified for some reason. Make sure that
+ * works. */
+ if (attribute == attribute_modification_date_q)
+ {
+ attribute = attribute_date_modified_q;
+ }
+
+ for (i = 0; i < priv->columns->len; i++)
+ {
+ NautilusColumn *column;
+ GQuark column_attribute;
+
+ column = NAUTILUS_COLUMN (priv->columns->pdata[i]);
+ g_object_get (G_OBJECT (column),
+ "attribute_q", &column_attribute,
+ NULL);
+ if (column_attribute == attribute)
+ {
+ return NAUTILUS_LIST_MODEL_NUM_COLUMNS + i;
+ }
+ }
+
+ return -1;
+}
+
+GQuark
+nautilus_list_model_get_attribute_from_sort_column_id (NautilusListModel *model,
+ int sort_column_id)
+{
+ NautilusListModelPrivate *priv;
+ NautilusColumn *column;
+ int index;
+ GQuark attribute;
+
+ priv = nautilus_list_model_get_instance_private (model);
+ index = sort_column_id - NAUTILUS_LIST_MODEL_NUM_COLUMNS;
+
+ if (index < 0 || index >= priv->columns->len)
+ {
+ g_warning ("unknown sort column id: %d", sort_column_id);
+ return 0;
+ }
+
+ column = NAUTILUS_COLUMN (priv->columns->pdata[index]);
+ g_object_get (G_OBJECT (column), "attribute_q", &attribute, NULL);
+
+ return attribute;
+}
+
+NautilusListZoomLevel
+nautilus_list_model_get_zoom_level_from_column_id (int column)
+{
+ switch (column)
+ {
+ case NAUTILUS_LIST_MODEL_SMALL_ICON_COLUMN:
+ {
+ return NAUTILUS_LIST_ZOOM_LEVEL_SMALL;
+ }
+
+ case NAUTILUS_LIST_MODEL_STANDARD_ICON_COLUMN:
+ {
+ return NAUTILUS_LIST_ZOOM_LEVEL_STANDARD;
+ }
+
+ case NAUTILUS_LIST_MODEL_LARGE_ICON_COLUMN:
+ {
+ return NAUTILUS_LIST_ZOOM_LEVEL_LARGE;
+ }
+
+ case NAUTILUS_LIST_MODEL_LARGER_ICON_COLUMN:
+ return NAUTILUS_LIST_ZOOM_LEVEL_LARGER;
+ }
+
+ g_return_val_if_reached (NAUTILUS_LIST_ZOOM_LEVEL_STANDARD);
+}
+
+int
+nautilus_list_model_get_column_id_from_zoom_level (NautilusListZoomLevel zoom_level)
+{
+ switch (zoom_level)
+ {
+ case NAUTILUS_LIST_ZOOM_LEVEL_SMALL:
+ {
+ return NAUTILUS_LIST_MODEL_SMALL_ICON_COLUMN;
+ }
+
+ case NAUTILUS_LIST_ZOOM_LEVEL_STANDARD:
+ {
+ return NAUTILUS_LIST_MODEL_STANDARD_ICON_COLUMN;
+ }
+
+ case NAUTILUS_LIST_ZOOM_LEVEL_LARGE:
+ {
+ return NAUTILUS_LIST_MODEL_LARGE_ICON_COLUMN;
+ }
+
+ case NAUTILUS_LIST_ZOOM_LEVEL_LARGER:
+ return NAUTILUS_LIST_MODEL_LARGER_ICON_COLUMN;
+ }
+
+ g_return_val_if_reached (NAUTILUS_LIST_MODEL_STANDARD_ICON_COLUMN);
+}
+
+void
+nautilus_list_model_set_drag_view (NautilusListModel *model,
+ GtkTreeView *view,
+ int drag_begin_x,
+ int drag_begin_y)
+{
+ NautilusListModelPrivate *priv;
+
+ g_return_if_fail (model != NULL);
+ g_return_if_fail (NAUTILUS_IS_LIST_MODEL (model));
+ g_return_if_fail (!view || GTK_IS_TREE_VIEW (view));
+
+ priv = nautilus_list_model_get_instance_private (model);
+
+ priv->drag_view = view;
+ priv->drag_begin_x = drag_begin_x;
+ priv->drag_begin_y = drag_begin_y;
+}
+
+GtkTreeView *
+nautilus_list_model_get_drag_view (NautilusListModel *model,
+ int *drag_begin_x,
+ int *drag_begin_y)
+{
+ NautilusListModelPrivate *priv;
+
+ priv = nautilus_list_model_get_instance_private (model);
+
+ if (drag_begin_x != NULL)
+ {
+ *drag_begin_x = priv->drag_begin_x;
+ }
+
+ if (drag_begin_y != NULL)
+ {
+ *drag_begin_y = priv->drag_begin_y;
+ }
+
+ return priv->drag_view;
+}
+
+GtkTargetList *
+nautilus_list_model_get_drag_target_list ()
+{
+ GtkTargetList *target_list;
+
+ target_list = gtk_target_list_new (drag_types, G_N_ELEMENTS (drag_types));
+ gtk_target_list_add_text_targets (target_list, NAUTILUS_ICON_DND_TEXT);
+
+ return target_list;
+}
+
+int
+nautilus_list_model_add_column (NautilusListModel *model,
+ NautilusColumn *column)
+{
+ NautilusListModelPrivate *priv;
+
+ priv = nautilus_list_model_get_instance_private (model);
+
+ g_ptr_array_add (priv->columns, column);
+ g_object_ref (column);
+
+ return NAUTILUS_LIST_MODEL_NUM_COLUMNS + (priv->columns->len - 1);
+}
+
+static void
+nautilus_list_model_dispose (GObject *object)
+{
+ NautilusListModel *model;
+ NautilusListModelPrivate *priv;
+ int i;
+
+ model = NAUTILUS_LIST_MODEL (object);
+ priv = nautilus_list_model_get_instance_private (model);
+
+ if (priv->columns)
+ {
+ for (i = 0; i < priv->columns->len; i++)
+ {
+ g_object_unref (priv->columns->pdata[i]);
+ }
+ g_ptr_array_free (priv->columns, TRUE);
+ priv->columns = NULL;
+ }
+
+ if (priv->files)
+ {
+ g_sequence_free (priv->files);
+ priv->files = NULL;
+ }
+
+ if (priv->top_reverse_map)
+ {
+ g_hash_table_destroy (priv->top_reverse_map);
+ priv->top_reverse_map = NULL;
+ }
+ if (priv->directory_reverse_map)
+ {
+ g_hash_table_destroy (priv->directory_reverse_map);
+ priv->directory_reverse_map = NULL;
+ }
+
+ G_OBJECT_CLASS (nautilus_list_model_parent_class)->dispose (object);
+}
+
+static void
+nautilus_list_model_finalize (GObject *object)
+{
+ NautilusListModel *model;
+ NautilusListModelPrivate *priv;
+
+ model = NAUTILUS_LIST_MODEL (object);
+ priv = nautilus_list_model_get_instance_private (model);
+
+ if (priv->highlight_files != NULL)
+ {
+ nautilus_file_list_free (priv->highlight_files);
+ priv->highlight_files = NULL;
+ }
+
+ G_OBJECT_CLASS (nautilus_list_model_parent_class)->finalize (object);
+}
+
+static void
+nautilus_list_model_init (NautilusListModel *model)
+{
+ NautilusListModelPrivate *priv;
+
+ priv = nautilus_list_model_get_instance_private (model);
+
+ priv->files = g_sequence_new ((GDestroyNotify) file_entry_free);
+ priv->top_reverse_map = g_hash_table_new (g_direct_hash, g_direct_equal);
+ priv->directory_reverse_map = g_hash_table_new (g_direct_hash, g_direct_equal);
+ priv->stamp = g_random_int ();
+ priv->sort_attribute = 0;
+ priv->columns = g_ptr_array_new ();
+}
+
+static void
+nautilus_list_model_class_init (NautilusListModelClass *klass)
+{
+ GObjectClass *object_class;
+
+ attribute_name_q = g_quark_from_static_string ("name");
+ attribute_modification_date_q = g_quark_from_static_string ("modification_date");
+ attribute_date_modified_q = g_quark_from_static_string ("date_modified");
+
+ object_class = (GObjectClass *) klass;
+ object_class->finalize = nautilus_list_model_finalize;
+ object_class->dispose = nautilus_list_model_dispose;
+
+ list_model_signals[SUBDIRECTORY_UNLOADED] =
+ g_signal_new ("subdirectory-unloaded",
+ NAUTILUS_TYPE_LIST_MODEL,
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (NautilusListModelClass, subdirectory_unloaded),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ NAUTILUS_TYPE_DIRECTORY);
+
+ list_model_signals[GET_ICON_SCALE] =
+ g_signal_new ("get-icon-scale",
+ NAUTILUS_TYPE_LIST_MODEL,
+ G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL,
+ NULL,
+ G_TYPE_INT, 0);
+}
+
+static void
+nautilus_list_model_tree_model_init (GtkTreeModelIface *iface)
+{
+ iface->get_flags = nautilus_list_model_get_flags;
+ iface->get_n_columns = nautilus_list_model_get_n_columns;
+ iface->get_column_type = nautilus_list_model_get_column_type;
+ iface->get_iter = nautilus_list_model_get_iter;
+ iface->get_path = nautilus_list_model_get_path;
+ iface->get_value = nautilus_list_model_get_value;
+ iface->iter_next = nautilus_list_model_iter_next;
+ iface->iter_children = nautilus_list_model_iter_children;
+ iface->iter_has_child = nautilus_list_model_iter_has_child;
+ iface->iter_n_children = nautilus_list_model_iter_n_children;
+ iface->iter_nth_child = nautilus_list_model_iter_nth_child;
+ iface->iter_parent = nautilus_list_model_iter_parent;
+}
+
+static void
+nautilus_list_model_sortable_init (GtkTreeSortableIface *iface)
+{
+ iface->get_sort_column_id = nautilus_list_model_get_sort_column_id;
+ iface->set_sort_column_id = nautilus_list_model_set_sort_column_id;
+ iface->has_default_sort_func = nautilus_list_model_has_default_sort_func;
+}
+
+void
+nautilus_list_model_subdirectory_done_loading (NautilusListModel *model,
+ NautilusDirectory *directory)
+{
+ NautilusListModelPrivate *priv;
+ GtkTreeIter iter;
+ GtkTreePath *path;
+ FileEntry *file_entry, *dummy_entry;
+ GSequenceIter *parent_ptr, *dummy_ptr;
+ GSequence *files;
+
+ priv = nautilus_list_model_get_instance_private (model);
+
+ if (model == NULL || priv->directory_reverse_map == NULL)
+ {
+ return;
+ }
+ parent_ptr = g_hash_table_lookup (priv->directory_reverse_map,
+ directory);
+ if (parent_ptr == NULL)
+ {
+ return;
+ }
+
+ file_entry = g_sequence_get (parent_ptr);
+ files = file_entry->files;
+
+ /* Only swap loading -> empty if we saw no files yet at "done",
+ * otherwise, toggle loading at first added file to the model.
+ */
+ if (!nautilus_directory_is_not_empty (directory) &&
+ g_sequence_get_length (files) == 1)
+ {
+ dummy_ptr = g_sequence_get_iter_at_pos (file_entry->files, 0);
+ dummy_entry = g_sequence_get (dummy_ptr);
+ if (dummy_entry->file == NULL)
+ {
+ /* was the dummy file */
+ file_entry->loaded = 1;
+
+ iter.stamp = priv->stamp;
+ iter.user_data = dummy_ptr;
+
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
+ gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter);
+ gtk_tree_path_free (path);
+ }
+ }
+}
+
+static void
+refresh_row (gpointer data,
+ gpointer user_data)
+{
+ NautilusFile *file;
+ NautilusListModel *model;
+ GList *iters, *l;
+ GtkTreePath *path;
+
+ model = user_data;
+ file = data;
+
+ iters = nautilus_list_model_get_all_iters_for_file (model, file);
+ for (l = iters; l != NULL; l = l->next)
+ {
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), l->data);
+ gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, l->data);
+
+ gtk_tree_path_free (path);
+ }
+
+ g_list_free_full (iters, g_free);
+}
+
+void
+nautilus_list_model_set_highlight_for_files (NautilusListModel *model,
+ GList *files)
+{
+ NautilusListModelPrivate *priv;
+
+ priv = nautilus_list_model_get_instance_private (model);
+
+ if (priv->highlight_files != NULL)
+ {
+ g_list_foreach (priv->highlight_files, refresh_row, model);
+ nautilus_file_list_free (priv->highlight_files);
+ priv->highlight_files = NULL;
+ }
+
+ if (files != NULL)
+ {
+ priv->highlight_files = nautilus_file_list_copy (files);
+ g_list_foreach (priv->highlight_files, refresh_row, model);
+ }
+}
diff --git a/src/nautilus-list-model.h b/src/nautilus-list-model.h
new file mode 100644
index 0000000..ba771b8
--- /dev/null
+++ b/src/nautilus-list-model.h
@@ -0,0 +1,113 @@
+
+/* fm-list-model.h - a GtkTreeModel for file lists.
+
+ Copyright (C) 2001, 2002 Anders Carlsson
+
+ 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: Anders Carlsson <andersca@gnu.org>
+*/
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+#include "nautilus-file.h"
+#include "nautilus-directory.h"
+#include <nautilus-extension.h>
+
+#define NAUTILUS_TYPE_LIST_MODEL nautilus_list_model_get_type()
+G_DECLARE_DERIVABLE_TYPE (NautilusListModel, nautilus_list_model, NAUTILUS, LIST_MODEL, GObject);
+
+enum {
+ NAUTILUS_LIST_MODEL_FILE_COLUMN,
+ NAUTILUS_LIST_MODEL_SUBDIRECTORY_COLUMN,
+ NAUTILUS_LIST_MODEL_SMALL_ICON_COLUMN,
+ NAUTILUS_LIST_MODEL_STANDARD_ICON_COLUMN,
+ NAUTILUS_LIST_MODEL_LARGE_ICON_COLUMN,
+ NAUTILUS_LIST_MODEL_LARGER_ICON_COLUMN,
+ NAUTILUS_LIST_MODEL_FILE_NAME_IS_EDITABLE_COLUMN,
+ NAUTILUS_LIST_MODEL_NUM_COLUMNS
+};
+
+struct _NautilusListModelClass
+{
+ GObjectClass parent_class;
+
+ void (* subdirectory_unloaded)(NautilusListModel *model,
+ NautilusDirectory *subdirectory);
+};
+
+gboolean nautilus_list_model_add_file (NautilusListModel *model,
+ NautilusFile *file,
+ NautilusDirectory *directory);
+void nautilus_list_model_file_changed (NautilusListModel *model,
+ NautilusFile *file,
+ NautilusDirectory *directory);
+gboolean nautilus_list_model_is_empty (NautilusListModel *model);
+void nautilus_list_model_remove_file (NautilusListModel *model,
+ NautilusFile *file,
+ NautilusDirectory *directory);
+void nautilus_list_model_clear (NautilusListModel *model);
+gboolean nautilus_list_model_get_tree_iter_from_file (NautilusListModel *model,
+ NautilusFile *file,
+ NautilusDirectory *directory,
+ GtkTreeIter *iter);
+GList * nautilus_list_model_get_all_iters_for_file (NautilusListModel *model,
+ NautilusFile *file);
+gboolean nautilus_list_model_get_first_iter_for_file (NautilusListModel *model,
+ NautilusFile *file,
+ GtkTreeIter *iter);
+void nautilus_list_model_set_should_sort_directories_first (NautilusListModel *model,
+ gboolean sort_directories_first);
+
+int nautilus_list_model_get_sort_column_id_from_attribute (NautilusListModel *model,
+ GQuark attribute);
+GQuark nautilus_list_model_get_attribute_from_sort_column_id (NautilusListModel *model,
+ int sort_column_id);
+void nautilus_list_model_sort_files (NautilusListModel *model,
+ GList **files);
+
+NautilusListZoomLevel nautilus_list_model_get_zoom_level_from_column_id (int column);
+int nautilus_list_model_get_column_id_from_zoom_level (NautilusListZoomLevel zoom_level);
+guint nautilus_list_model_get_icon_size_for_zoom_level (NautilusListZoomLevel zoom_level);
+
+NautilusFile * nautilus_list_model_file_for_path (NautilusListModel *model, GtkTreePath *path);
+gboolean nautilus_list_model_load_subdirectory (NautilusListModel *model, GtkTreePath *path, NautilusDirectory **directory);
+void nautilus_list_model_unload_subdirectory (NautilusListModel *model, GtkTreeIter *iter);
+
+void nautilus_list_model_set_drag_view (NautilusListModel *model,
+ GtkTreeView *view,
+ int begin_x,
+ int begin_y);
+GtkTreeView * nautilus_list_model_get_drag_view (NautilusListModel *model,
+ int *drag_begin_x,
+ int *drag_begin_y);
+
+GtkTargetList * nautilus_list_model_get_drag_target_list (void);
+
+int nautilus_list_model_compare_func (NautilusListModel *model,
+ NautilusFile *file1,
+ NautilusFile *file2);
+
+
+int nautilus_list_model_add_column (NautilusListModel *model,
+ NautilusColumn *column);
+
+void nautilus_list_model_subdirectory_done_loading (NautilusListModel *model,
+ NautilusDirectory *directory);
+
+void nautilus_list_model_set_highlight_for_files (NautilusListModel *model,
+ GList *files); \ No newline at end of file
diff --git a/src/nautilus-list-view-dnd.c b/src/nautilus-list-view-dnd.c
new file mode 100644
index 0000000..2191006
--- /dev/null
+++ b/src/nautilus-list-view-dnd.c
@@ -0,0 +1,305 @@
+/* nautilus-list-view-dnd.c
+ *
+ * Copyright (C) 2015 Carlos Soriano <csoriano@gnome.org>
+ *
+ * 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-list-view-dnd.h"
+#include "nautilus-list-view-private.h"
+
+static GtkTargetList *source_target_list = NULL;
+
+static void
+drag_info_data_free (NautilusListView *list_view);
+
+static void
+drag_data_get_callback (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint time,
+ gpointer user_data)
+{
+ GtkTreeView *tree_view;
+ GtkTreeModel *model;
+ NautilusListView *list_view;
+
+ tree_view = GTK_TREE_VIEW (widget);
+ list_view = NAUTILUS_LIST_VIEW (user_data);
+
+ model = gtk_tree_view_get_model (tree_view);
+
+ if (model == NULL)
+ {
+ return;
+ }
+
+ if (list_view->details->drag_source_info == NULL ||
+ list_view->details->drag_source_info->selection_cache == NULL)
+ {
+ return;
+ }
+
+ nautilus_drag_drag_data_get_from_cache (list_view->details->drag_source_info->selection_cache,
+ context, selection_data, info, time);
+}
+
+static cairo_surface_t *
+get_drag_surface (NautilusListView *view)
+{
+ GtkTreeModel *model;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ cairo_surface_t *ret;
+ GdkRectangle cell_area;
+
+ ret = NULL;
+
+ if (gtk_tree_view_get_path_at_pos (view->details->tree_view,
+ view->details->drag_x,
+ view->details->drag_y,
+ &path, NULL, NULL, NULL))
+ {
+ model = gtk_tree_view_get_model (view->details->tree_view);
+ gtk_tree_model_get_iter (model, &iter, path);
+ gtk_tree_model_get (model, &iter,
+ nautilus_list_model_get_column_id_from_zoom_level (view->details->zoom_level),
+ &ret,
+ -1);
+ }
+
+ gtk_tree_view_get_cell_area (view->details->tree_view,
+ path,
+ view->details->file_name_column,
+ &cell_area);
+
+ gtk_tree_path_free (path);
+
+ return ret;
+}
+
+/* iteration glue struct */
+typedef struct
+{
+ NautilusListView *view;
+ NautilusDragEachSelectedItemDataGet iteratee;
+ gpointer iteratee_data;
+} ListGetDataBinderContext;
+
+static void
+item_get_data_binder (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ ListGetDataBinderContext *context = data;
+ NautilusFile *file;
+ GtkTreeView *treeview;
+ GtkTreeViewColumn *column;
+ GdkRectangle cell_area;
+ int drag_begin_y = 0;
+ char *uri;
+
+ treeview = nautilus_list_model_get_drag_view (context->view->details->model,
+ NULL,
+ &drag_begin_y);
+ column = gtk_tree_view_get_column (treeview, 0);
+
+ file = nautilus_list_model_file_for_path (NAUTILUS_LIST_MODEL (model), path);
+ if (file == NULL)
+ {
+ return;
+ }
+
+ gtk_tree_view_get_cell_area (treeview,
+ path,
+ column,
+ &cell_area);
+
+ uri = nautilus_file_get_activation_uri (file);
+
+ nautilus_file_unref (file);
+
+ /* pass the uri, mouse-relative x/y and icon width/height */
+ context->iteratee (uri,
+ 0,
+ cell_area.y - drag_begin_y,
+ cell_area.width,
+ cell_area.height,
+ context->iteratee_data);
+
+ g_free (uri);
+}
+
+static void
+each_item_get_data_binder (NautilusDragEachSelectedItemDataGet iteratee,
+ gpointer iterator_context,
+ gpointer data)
+{
+ NautilusListView *view = NAUTILUS_LIST_VIEW (iterator_context);
+ ListGetDataBinderContext context;
+ GtkTreeSelection *selection;
+
+ context.view = view;
+ context.iteratee = iteratee;
+ context.iteratee_data = data;
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view->details->tree_view));
+ gtk_tree_selection_selected_foreach (selection, item_get_data_binder, &context);
+}
+
+static void
+drag_begin_callback (GtkWidget *widget,
+ GdkDragContext *context,
+ NautilusListView *view)
+{
+ cairo_surface_t *surface;
+ NautilusWindow *window;
+ GList *dragged_files;
+
+ window = nautilus_files_view_get_window (NAUTILUS_FILES_VIEW (view));
+ surface = get_drag_surface (view);
+ if (surface)
+ {
+ gtk_drag_set_icon_surface (context, surface);
+ cairo_surface_destroy (surface);
+ }
+ else
+ {
+ gtk_drag_set_icon_default (context);
+ }
+
+ view->details->drag_button = 0;
+ view->details->drag_started = TRUE;
+
+ view->details->drag_source_info->selection_cache = nautilus_drag_create_selection_cache (view,
+ each_item_get_data_binder);
+
+ dragged_files = nautilus_drag_file_list_from_selection_list (view->details->drag_source_info->selection_cache);
+ if (nautilus_file_list_are_all_folders (dragged_files))
+ {
+ nautilus_window_start_dnd (window, context);
+ }
+ g_list_free_full (dragged_files, g_object_unref);
+}
+
+static void
+drag_end_callback (GtkWidget *widget,
+ GdkDragContext *context,
+ NautilusListView *list_view)
+{
+ NautilusWindow *window;
+
+ window = nautilus_files_view_get_window (NAUTILUS_FILES_VIEW (list_view));
+
+ nautilus_window_end_dnd (window, context);
+
+ drag_info_data_free (list_view);
+}
+
+static void
+drag_info_data_free (NautilusListView *list_view)
+{
+ nautilus_drag_destroy_selection_list (list_view->details->drag_source_info->selection_cache);
+ list_view->details->drag_source_info->selection_cache = NULL;
+
+ g_free (list_view->details->drag_source_info);
+ list_view->details->drag_source_info = NULL;
+
+ g_signal_handlers_disconnect_by_func (list_view->details->tree_view, drag_begin_callback, list_view);
+ g_signal_handlers_disconnect_by_func (list_view->details->tree_view, drag_data_get_callback, list_view);
+ g_signal_handlers_disconnect_by_func (list_view->details->tree_view, drag_end_callback, list_view);
+}
+
+NautilusDragInfo *
+nautilus_list_view_dnd_get_drag_source_data (NautilusListView *list_view,
+ GdkDragContext *context)
+{
+ GtkTreeView *tree_view;
+ GtkTreeModel *model;
+
+ tree_view = GTK_TREE_VIEW (list_view->details->tree_view);
+
+ model = gtk_tree_view_get_model (tree_view);
+
+ if (model == NULL)
+ {
+ return NULL;
+ }
+
+ if (list_view->details->drag_source_info == NULL ||
+ list_view->details->drag_source_info->selection_cache == NULL)
+ {
+ return NULL;
+ }
+
+ return list_view->details->drag_source_info;
+}
+
+void
+nautilus_list_view_dnd_init (NautilusListView *list_view)
+{
+ if (list_view->details->drag_source_info != NULL)
+ {
+ return;
+ }
+
+ list_view->details->drag_source_info = g_new0 (NautilusDragInfo, 1);
+
+ g_signal_connect_object (list_view->details->tree_view, "drag-begin",
+ G_CALLBACK (drag_begin_callback), list_view, 0);
+ g_signal_connect_object (list_view->details->tree_view, "drag-end",
+ G_CALLBACK (drag_end_callback), list_view, 0);
+ g_signal_connect_object (list_view->details->tree_view, "drag-data-get",
+ G_CALLBACK (drag_data_get_callback), list_view, 0);
+}
+
+void
+nautilus_list_view_dnd_drag_begin (NautilusListView *list_view,
+ gdouble offset_x,
+ gdouble offset_y,
+ const GdkEvent *event)
+{
+ if (list_view->details->drag_button == 0)
+ {
+ return;
+ }
+
+ if (!source_target_list)
+ {
+ source_target_list = nautilus_list_model_get_drag_target_list ();
+ }
+
+ if (gtk_drag_check_threshold (GTK_WIDGET (list_view->details->tree_view),
+ list_view->details->drag_x,
+ list_view->details->drag_y,
+ list_view->details->drag_x + offset_x,
+ list_view->details->drag_y + offset_y))
+ {
+ guint32 actions;
+
+ actions = GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK | GDK_ACTION_ASK;
+ list_view->details->drag_source_info->source_actions = actions;
+ gtk_drag_begin_with_coordinates (GTK_WIDGET (list_view->details->tree_view),
+ source_target_list,
+ actions,
+ list_view->details->drag_button,
+ (GdkEvent *) event,
+ -1,
+ -1);
+ }
+}
diff --git a/src/nautilus-list-view-dnd.h b/src/nautilus-list-view-dnd.h
new file mode 100644
index 0000000..03e102e
--- /dev/null
+++ b/src/nautilus-list-view-dnd.h
@@ -0,0 +1,34 @@
+/* nautilus-list-view-dnd.h
+ *
+ * Copyright (C) 2015 Carlos Soriano <csoriano@gnome.org>
+ *
+ * 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 <gdk/gdk.h>
+
+#include "nautilus-list-view.h"
+
+#include "nautilus-dnd.h"
+
+void nautilus_list_view_dnd_init (NautilusListView *list_view);
+void nautilus_list_view_dnd_drag_begin (NautilusListView *list_view,
+ gdouble offset_x,
+ gdouble offset_y,
+ const GdkEvent *event);
+NautilusDragInfo *
+nautilus_list_view_dnd_get_drag_source_data (NautilusListView *list_view,
+ GdkDragContext *context);
diff --git a/src/nautilus-list-view-private.h b/src/nautilus-list-view-private.h
new file mode 100644
index 0000000..9d4cbad
--- /dev/null
+++ b/src/nautilus-list-view-private.h
@@ -0,0 +1,79 @@
+/* nautilus-list-view-private.h
+ *
+ * Copyright (C) 2015 Carlos Soriano <csoriano@gnome.org>
+ *
+ * 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/>.
+ */
+
+/* Data and functions shared between list view and list view dnd */
+
+#pragma once
+
+#include "nautilus-list-model.h"
+#include "nautilus-tree-view-drag-dest.h"
+#include "nautilus-dnd.h"
+#include "nautilus-tag-manager.h"
+
+struct NautilusListViewDetails {
+ GtkTreeView *tree_view;
+ NautilusListModel *model;
+
+ GtkTreeViewColumn *file_name_column;
+ int file_name_column_num;
+
+ GtkCellRendererPixbuf *pixbuf_cell;
+ GtkCellRendererText *file_name_cell;
+ GList *cells;
+
+ NautilusListZoomLevel zoom_level;
+
+ NautilusTreeViewDragDest *drag_dest;
+
+ GtkTreePath *first_click_path; /* Both clicks in a double click need to be on the same row */
+
+ GtkTreePath *new_selection_path; /* Path of the new selection after removing a file */
+
+ GtkTreePath *hover_path;
+
+ gint last_event_button_x;
+ gint last_event_button_y;
+
+ guint drag_button;
+ int drag_x;
+ int drag_y;
+
+ gboolean drag_started;
+ gboolean ignore_button_release;
+ gboolean row_selected_on_button_down;
+ gboolean active;
+ NautilusDragInfo *drag_source_info;
+
+ GHashTable *columns;
+ GtkWidget *column_editor;
+
+ char *original_name;
+
+ gulong clipboard_handler_id;
+
+ GQuark last_sort_attr;
+
+ GRegex *regex;
+
+ NautilusTagManager *tag_manager;
+ GCancellable *starred_cancellable;
+
+ GtkGesture *tree_view_drag_gesture;
+ GtkGesture *tree_view_multi_press_gesture;
+};
+
diff --git a/src/nautilus-list-view.c b/src/nautilus-list-view.c
new file mode 100644
index 0000000..66e3373
--- /dev/null
+++ b/src/nautilus-list-view.c
@@ -0,0 +1,4177 @@
+/* fm-list-view.c - implementation of list view of directory.
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ * Copyright (C) 2001, 2002 Anders Carlsson <andersca@gnu.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: John Sullivan <sullivan@eazel.com>
+ * Anders Carlsson <andersca@gnu.org>
+ * David Emory Watson <dwatson@cs.ucr.edu>
+ */
+
+#include "nautilus-list-view.h"
+#include "nautilus-list-view-private.h"
+
+#include <eel/eel-vfs-extensions.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkkeysyms.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <libgd/gd.h>
+#include <string.h>
+
+#define DEBUG_FLAG NAUTILUS_DEBUG_LIST_VIEW
+#include "nautilus-debug.h"
+
+#include "nautilus-clipboard.h"
+#include "nautilus-column-chooser.h"
+#include "nautilus-column-utilities.h"
+#include "nautilus-dnd.h"
+#include "nautilus-enums.h"
+#include "nautilus-error-reporting.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-files-view-dnd.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-list-model.h"
+#include "nautilus-list-view-dnd.h"
+#include "nautilus-metadata.h"
+#include "nautilus-search-directory.h"
+#include "nautilus-tag-manager.h"
+#include "nautilus-toolbar.h"
+#include "nautilus-tree-view-drag-dest.h"
+#include "nautilus-ui-utilities.h"
+#include "nautilus-view.h"
+#include "nautilus-tracker-utilities.h"
+
+struct SelectionForeachData
+{
+ GList *list;
+ GtkTreeSelection *selection;
+};
+
+/*
+ * The row height should be large enough to not clip emblems.
+ * Computing this would be costly, so we just choose a number
+ * that works well with the set of emblems we've designed.
+ */
+#define LIST_VIEW_MINIMUM_ROW_HEIGHT 28
+
+/* We wait two seconds after row is collapsed to unload the subdirectory */
+#define COLLAPSE_TO_UNLOAD_DELAY 2
+
+static GdkCursor *hand_cursor = NULL;
+
+static GList *nautilus_list_view_get_selection (NautilusFilesView *view);
+static GList *nautilus_list_view_get_selection_for_file_transfer (NautilusFilesView *view);
+static void nautilus_list_view_set_zoom_level (NautilusListView *view,
+ NautilusListZoomLevel new_level);
+static void nautilus_list_view_scroll_to_file (NautilusListView *view,
+ NautilusFile *file);
+static void nautilus_list_view_sort_directories_first_changed (NautilusFilesView *view);
+
+static void apply_columns_settings (NautilusListView *list_view,
+ char **column_order,
+ char **visible_columns);
+static char **get_visible_columns (NautilusListView *list_view);
+static char **get_default_visible_columns (NautilusListView *list_view);
+static char **get_column_order (NautilusListView *list_view);
+static char **get_default_column_order (NautilusListView *list_view);
+static void on_clipboard_owner_changed (GtkClipboard *clipboard,
+ GdkEvent *event,
+ gpointer user_data);
+
+
+G_DEFINE_TYPE (NautilusListView, nautilus_list_view, NAUTILUS_TYPE_FILES_VIEW);
+
+static const char *default_search_visible_columns[] =
+{
+ "name", "size", "where", NULL
+};
+
+static const char *default_search_columns_order[] =
+{
+ "name", "size", "where", NULL
+};
+
+static const char *default_recent_visible_columns[] =
+{
+ "name", "where", "recency", NULL
+};
+
+static const char *default_recent_columns_order[] =
+{
+ "name", "where", "recency", NULL
+};
+
+static const char *default_trash_visible_columns[] =
+{
+ "name", "size", "trash_orig_path", "trashed_on", NULL
+};
+
+static const char *default_trash_columns_order[] =
+{
+ "name", "size", "trash_orig_path", "trashed_on", NULL
+};
+
+static const gchar *
+get_default_sort_order (NautilusFile *file,
+ gboolean *reversed)
+{
+ NautilusFileSortType sort_type;
+
+ /* This array makes the #NautilusFileSortType values correspond to the
+ * respective column attribute.
+ */
+ const char *attributes[] =
+ {
+ "name",
+ "size",
+ "type",
+ "date_modified",
+ "date_accessed",
+ "starred",
+ "trashed_on",
+ "search_relevance",
+ "recency",
+ NULL
+ };
+
+ sort_type = nautilus_file_get_default_sort_type (file, reversed);
+
+ return attributes[sort_type];
+}
+
+static void
+list_selection_changed_callback (GtkTreeSelection *selection,
+ gpointer user_data)
+{
+ NautilusFilesView *view;
+
+ view = NAUTILUS_FILES_VIEW (user_data);
+
+ nautilus_files_view_notify_selection_changed (view);
+}
+
+static void
+preview_selected_items (NautilusListView *view)
+{
+ GList *file_list;
+
+ file_list = nautilus_list_view_get_selection (NAUTILUS_FILES_VIEW (view));
+
+ if (file_list != NULL)
+ {
+ nautilus_files_view_preview_files (NAUTILUS_FILES_VIEW (view),
+ file_list, NULL);
+ nautilus_file_list_free (file_list);
+ }
+}
+
+static void
+activate_selected_items (NautilusListView *view)
+{
+ GList *file_list;
+
+ file_list = nautilus_list_view_get_selection (NAUTILUS_FILES_VIEW (view));
+ if (file_list != NULL)
+ {
+ nautilus_files_view_activate_files (NAUTILUS_FILES_VIEW (view),
+ file_list,
+ 0, TRUE);
+ nautilus_file_list_free (file_list);
+ }
+}
+
+static void
+activate_selected_items_alternate (NautilusListView *view,
+ NautilusFile *file,
+ gboolean open_in_tab)
+{
+ GList *file_list;
+ NautilusWindowOpenFlags flags;
+
+ flags = 0;
+
+ if (open_in_tab)
+ {
+ flags |= NAUTILUS_WINDOW_OPEN_FLAG_NEW_TAB;
+ flags |= NAUTILUS_WINDOW_OPEN_FLAG_DONT_MAKE_ACTIVE;
+ }
+ else
+ {
+ flags |= NAUTILUS_WINDOW_OPEN_FLAG_NEW_WINDOW;
+ }
+
+ if (file != NULL)
+ {
+ nautilus_file_ref (file);
+ file_list = g_list_prepend (NULL, file);
+ }
+ else
+ {
+ file_list = nautilus_list_view_get_selection (NAUTILUS_FILES_VIEW (view));
+ }
+ nautilus_files_view_activate_files (NAUTILUS_FILES_VIEW (view),
+ file_list,
+ flags,
+ TRUE);
+ nautilus_file_list_free (file_list);
+}
+
+static gboolean
+button_event_modifies_selection (const GdkEvent *event)
+{
+ GdkModifierType state;
+
+ gdk_event_get_state (event, &state);
+
+ return (state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) != 0;
+}
+
+static int
+get_click_policy (void)
+{
+ return g_settings_get_enum (nautilus_preferences,
+ NAUTILUS_PREFERENCES_CLICK_POLICY);
+}
+
+static void
+nautilus_list_view_did_not_drag (NautilusListView *view,
+ const GdkEvent *event)
+{
+ GtkTreeView *tree_view;
+ GtkTreeSelection *selection;
+ gdouble x;
+ gdouble y;
+ GtkTreePath *path;
+ guint button;
+ GdkModifierType state;
+
+ tree_view = view->details->tree_view;
+ selection = gtk_tree_view_get_selection (tree_view);
+
+ if (!gdk_event_get_coords (event, &x, &y))
+ {
+ return;
+ }
+
+ if (!gtk_tree_view_get_path_at_pos (tree_view, x, y, &path, NULL, NULL, NULL))
+ {
+ return;
+ }
+
+ if (!gdk_event_get_button (event, &button))
+ {
+ return;
+ }
+
+ gdk_event_get_state (event, &state);
+
+ if ((button == GDK_BUTTON_PRIMARY || button == GDK_BUTTON_MIDDLE)
+ && ((state & GDK_CONTROL_MASK) != 0 ||
+ (state & GDK_SHIFT_MASK) == 0)
+ && view->details->row_selected_on_button_down)
+ {
+ if (!button_event_modifies_selection (event))
+ {
+ gtk_tree_selection_unselect_all (selection);
+ gtk_tree_selection_select_path (selection, path);
+ }
+ else
+ {
+ gtk_tree_selection_unselect_path (selection, path);
+ }
+ }
+
+ if ((get_click_policy () == NAUTILUS_CLICK_POLICY_SINGLE)
+ && !button_event_modifies_selection (event))
+ {
+ if (button == GDK_BUTTON_PRIMARY)
+ {
+ activate_selected_items (view);
+ }
+ else if (button == GDK_BUTTON_MIDDLE)
+ {
+ activate_selected_items_alternate (view, NULL, TRUE);
+ }
+ }
+ gtk_tree_path_free (path);
+}
+
+static gboolean
+on_motion_notify (GtkWidget *widget,
+ GdkEvent *event,
+ gpointer callback_data)
+{
+ NautilusListView *view;
+ gdouble x;
+ gdouble y;
+
+ view = NAUTILUS_LIST_VIEW (callback_data);
+
+ /* Remove after switching to GTK+ 4. */
+ if (gdk_event_get_window (event) != gtk_tree_view_get_bin_window (GTK_TREE_VIEW (widget)))
+ {
+ return GDK_EVENT_PROPAGATE;
+ }
+
+ g_assert (gdk_event_get_coords (event, &x, &y));
+
+ if (get_click_policy () == NAUTILUS_CLICK_POLICY_SINGLE)
+ {
+ GtkTreePath *old_hover_path;
+
+ old_hover_path = view->details->hover_path;
+ gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
+ x, y,
+ &view->details->hover_path,
+ NULL, NULL, NULL);
+
+ if ((old_hover_path != NULL) != (view->details->hover_path != NULL))
+ {
+ if (view->details->hover_path != NULL)
+ {
+ gdk_window_set_cursor (gtk_widget_get_window (widget), hand_cursor);
+ }
+ else
+ {
+ gdk_window_set_cursor (gtk_widget_get_window (widget), NULL);
+ }
+ }
+
+ if (old_hover_path != NULL)
+ {
+ gtk_tree_path_free (old_hover_path);
+ }
+ }
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static gboolean
+on_leave_notify (GtkWidget *widget,
+ GdkEvent *event,
+ gpointer callback_data)
+{
+ NautilusListView *view;
+
+ view = NAUTILUS_LIST_VIEW (callback_data);
+
+ if (get_click_policy () == NAUTILUS_CLICK_POLICY_SINGLE &&
+ view->details->hover_path != NULL)
+ {
+ gtk_tree_path_free (view->details->hover_path);
+ view->details->hover_path = NULL;
+ }
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static gboolean
+on_enter_notify (GtkWidget *widget,
+ GdkEvent *event,
+ gpointer callback_data)
+{
+ NautilusListView *view;
+
+ view = NAUTILUS_LIST_VIEW (callback_data);
+
+ if (get_click_policy () == NAUTILUS_CLICK_POLICY_SINGLE)
+ {
+ gdouble x;
+ gdouble y;
+
+ if (view->details->hover_path != NULL)
+ {
+ gtk_tree_path_free (view->details->hover_path);
+ }
+
+ g_assert (gdk_event_get_coords (event, &x, &y));
+
+ gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
+ x, y,
+ &view->details->hover_path,
+ NULL, NULL, NULL);
+
+ if (view->details->hover_path != NULL)
+ {
+ gdk_window_set_cursor (gtk_widget_get_window (widget), hand_cursor);
+ }
+ }
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static void
+row_activated_callback (GtkTreeView *treeview,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ NautilusListView *view)
+{
+ activate_selected_items (view);
+}
+
+static gboolean
+check_starred_status (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ NautilusFile *file;
+ GList *l;
+ GList *changed_files;
+
+ changed_files = data;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (model),
+ iter,
+ NAUTILUS_LIST_MODEL_FILE_COLUMN, &file,
+ -1);
+
+ if (!file)
+ {
+ return FALSE;
+ }
+
+ for (l = changed_files; l != NULL; l = l->next)
+ {
+ if (nautilus_file_compare_location (NAUTILUS_FILE (l->data), file) == 0)
+ {
+ gtk_tree_model_row_changed (model, path, iter);
+ }
+ }
+
+ nautilus_file_unref (file);
+
+ return FALSE;
+}
+
+static void
+on_starred_files_changed (NautilusTagManager *tag_manager,
+ GList *changed_files,
+ gpointer user_data)
+{
+ NautilusListView *list_view;
+
+ list_view = NAUTILUS_LIST_VIEW (user_data);
+
+ gtk_tree_model_foreach (GTK_TREE_MODEL (list_view->details->model),
+ check_starred_status,
+ changed_files);
+}
+
+static void
+on_star_cell_renderer_clicked (GtkTreePath *path,
+ NautilusListView *list_view)
+{
+ NautilusListModel *list_model;
+ NautilusFile *file;
+ g_autofree gchar *uri = NULL;
+ GList *selection;
+
+ list_model = list_view->details->model;
+
+ file = nautilus_list_model_file_for_path (list_model, path);
+
+ if (file == NULL)
+ {
+ /* This row is a label, not a file */
+ return;
+ }
+
+ uri = nautilus_file_get_uri (file);
+ selection = g_list_prepend (NULL, file);
+
+ if (nautilus_tag_manager_file_is_starred (list_view->details->tag_manager, uri))
+ {
+ nautilus_tag_manager_unstar_files (list_view->details->tag_manager,
+ G_OBJECT (list_view),
+ selection,
+ NULL,
+ list_view->details->starred_cancellable);
+ }
+ else
+ {
+ nautilus_tag_manager_star_files (list_view->details->tag_manager,
+ G_OBJECT (list_view),
+ selection,
+ NULL,
+ list_view->details->starred_cancellable);
+ }
+
+ nautilus_file_list_free (selection);
+}
+
+static void
+on_tree_view_multi_press_gesture_pressed (GtkGestureMultiPress *gesture,
+ gint n_press,
+ gdouble x,
+ gdouble y,
+ gpointer callback_data)
+{
+ NautilusListView *view;
+ GtkWidget *widget;
+ GtkTreeView *tree_view;
+ g_autoptr (GtkTreePath) path = NULL;
+ GtkTreeViewColumn *column;
+ GtkTreeSelection *selection;
+ GtkWidgetClass *tree_view_class;
+ guint button;
+ gint bin_x;
+ gint bin_y;
+ GdkEventSequence *sequence;
+ const GdkEvent *event;
+ gboolean call_parent, on_expander, show_expanders;
+ gboolean is_simple_click, path_selected;
+ NautilusFile *file;
+ gboolean on_star;
+
+ view = NAUTILUS_LIST_VIEW (callback_data);
+ widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
+ tree_view = GTK_TREE_VIEW (widget);
+ tree_view_class = GTK_WIDGET_GET_CLASS (tree_view);
+ selection = gtk_tree_view_get_selection (tree_view);
+ button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
+
+ gtk_tree_view_convert_widget_to_bin_window_coords (tree_view, x, y, &bin_x, &bin_y);
+
+ view->details->last_event_button_x = bin_x;
+ view->details->last_event_button_y = bin_y;
+
+ /* Don't handle extra mouse buttons here */
+ if (button > 5)
+ {
+ return;
+ }
+
+ sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
+ event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
+
+ /* Remove after switching to GTK+ 4. */
+ if (gdk_event_get_window (event) != gtk_tree_view_get_bin_window (tree_view))
+ {
+ return;
+ }
+
+ nautilus_list_model_set_drag_view
+ (NAUTILUS_LIST_MODEL (gtk_tree_view_get_model (tree_view)),
+ tree_view,
+ bin_x, bin_y);
+
+ /* Ignore double click if we are in single click mode */
+ if (get_click_policy () == NAUTILUS_CLICK_POLICY_SINGLE && n_press >= 2)
+ {
+ return;
+ }
+
+ view->details->ignore_button_release = FALSE;
+ is_simple_click = ((button == GDK_BUTTON_PRIMARY || button == GDK_BUTTON_MIDDLE) && (n_press == 1));
+
+ /* No item at this position */
+ if (!gtk_tree_view_get_path_at_pos (tree_view, bin_x, bin_y,
+ &path, &column, NULL, NULL))
+ {
+ if (is_simple_click)
+ {
+ g_clear_pointer (&view->details->first_click_path, gtk_tree_path_free);
+ }
+
+ gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (tree_view));
+
+ if (button == GDK_BUTTON_SECONDARY)
+ {
+ nautilus_files_view_pop_up_background_context_menu (NAUTILUS_FILES_VIEW (view),
+ event);
+ }
+
+ return;
+ }
+
+ call_parent = TRUE;
+ on_expander = FALSE;
+ path_selected = gtk_tree_selection_path_is_selected (selection, path);
+ show_expanders = g_settings_get_boolean (nautilus_list_view_preferences,
+ NAUTILUS_PREFERENCES_LIST_VIEW_USE_TREE);
+
+ if (show_expanders)
+ {
+ GdkRectangle cell_area;
+
+ gtk_tree_view_get_cell_area (tree_view, path, column, &cell_area);
+
+ /* We assume that the cell area excludes the expander itself.
+ * Explanatory link for future reference:
+ * https://gitlab.gnome.org/GNOME/nautilus/merge_requests/97#note_58649 */
+
+ if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
+ {
+ on_expander = bin_x > (cell_area.x + cell_area.width);
+ }
+ else
+ {
+ on_expander = bin_x < cell_area.x;
+ }
+ }
+
+ /* Keep track of path of last click so double clicks only happen
+ * on the same item */
+ if (is_simple_click)
+ {
+ g_clear_pointer (&view->details->first_click_path, gtk_tree_path_free);
+ view->details->first_click_path = gtk_tree_path_copy (path);
+ }
+
+ on_star = (g_strcmp0 (gtk_tree_view_column_get_title (column), "Star") == 0 &&
+ !gtk_tree_view_is_blank_at_pos (tree_view,
+ bin_x,
+ bin_y,
+ NULL,
+ NULL,
+ NULL,
+ NULL));
+
+ if (is_simple_click && on_star)
+ {
+ on_star_cell_renderer_clicked (path, view);
+ }
+ else if (n_press == 2 && !on_star)
+ {
+ /* Double clicking does not trigger a D&D action. */
+ view->details->drag_button = 0;
+
+ /* NOTE: Activation can actually destroy the view if we're switching */
+ if (!on_expander &&
+ view->details->first_click_path &&
+ gtk_tree_path_compare (path, view->details->first_click_path) == 0)
+ {
+ if ((button == GDK_BUTTON_PRIMARY) && button_event_modifies_selection (event))
+ {
+ file = nautilus_list_model_file_for_path (view->details->model, path);
+ if (file != NULL)
+ {
+ activate_selected_items_alternate (view, file, TRUE);
+ nautilus_file_unref (file);
+ }
+ }
+ else if ((button == GDK_BUTTON_PRIMARY || button == GDK_BUTTON_SECONDARY))
+ {
+ activate_selected_items (view);
+ }
+ }
+ else
+ {
+ return;
+ }
+ }
+ else
+ {
+ GdkModifierType state;
+ g_autoptr (GtkTreePath) cursor = NULL;
+ GList *selected_rows = NULL;
+
+ gdk_event_get_state (event, &state);
+
+ if (button == GDK_BUTTON_SECONDARY)
+ {
+ if (path_selected)
+ {
+ /* We're going to filter out some situations where
+ * we can't let the default code run because all
+ * but one row would be would be deselected. We don't
+ * want that; we want the right click menu or single
+ * click to apply to everything that's currently selected.
+ */
+ call_parent = FALSE;
+ }
+ else if ((state & GDK_CONTROL_MASK) != 0)
+ {
+ /* If CTRL is pressed, we don't allow the parent
+ * class to handle it, since GtkTreeView doesn't
+ * do it as intended currently.
+ */
+ call_parent = FALSE;
+ if ((state & GDK_SHIFT_MASK) != 0)
+ {
+ /* This is the CTRL+SHIFT selection mode which
+ * we handleourselves, as the parent class would
+ * otherwise do an unexpected selection.
+ */
+ gtk_tree_view_get_cursor (tree_view, &cursor, NULL);
+ if (cursor != NULL)
+ {
+ gtk_tree_selection_select_range (selection, cursor, path);
+ }
+ else
+ {
+ gtk_tree_selection_select_path (selection, path);
+ }
+ }
+ else
+ {
+ gtk_tree_selection_select_path (selection, path);
+ }
+ selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);
+
+ /* This unselects everything */
+ gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
+
+ /* So select it again */
+ for (GList *l = selected_rows; l != NULL; l = l->next)
+ {
+ gtk_tree_selection_select_path (selection, l->data);
+ }
+ g_list_free_full (selected_rows, (GDestroyNotify) gtk_tree_path_free);
+ }
+ else if (on_expander)
+ {
+ /* If the right click happened on an expander, we should
+ * fully change the selection on that row solely.
+ */
+ gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
+ }
+ }
+
+ if ((button == GDK_BUTTON_PRIMARY || button == GDK_BUTTON_MIDDLE) &&
+ ((state & GDK_CONTROL_MASK) != 0 || (state & GDK_SHIFT_MASK) == 0))
+ {
+ view->details->row_selected_on_button_down = path_selected;
+
+ if (path_selected)
+ {
+ call_parent = on_expander;
+ view->details->ignore_button_release = on_expander;
+ }
+ else if ((state & GDK_CONTROL_MASK) != 0)
+ {
+ call_parent = FALSE;
+ if ((state & GDK_SHIFT_MASK) != 0)
+ {
+ gtk_tree_view_get_cursor (tree_view, &cursor, NULL);
+ if (cursor != NULL)
+ {
+ gtk_tree_selection_select_range (selection, cursor, path);
+ }
+ else
+ {
+ gtk_tree_selection_select_path (selection, path);
+ }
+ }
+ else
+ {
+ gtk_tree_selection_select_path (selection, path);
+ }
+ selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);
+
+ /* This unselects everything */
+ gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
+
+ /* So select it again */
+ for (GList *l = selected_rows; l != NULL; l = l->next)
+ {
+ gtk_tree_selection_select_path (selection, l->data);
+ }
+ g_list_free_full (selected_rows, (GDestroyNotify) gtk_tree_path_free);
+ }
+ else
+ {
+ view->details->ignore_button_release = on_expander;
+ }
+ }
+
+ if (is_simple_click && on_expander)
+ {
+ /* Need to let the event propagate down, since propagating up
+ * by chaining up to button_press_event() doesn’t expand the
+ * expander.
+ */
+ return;
+ }
+
+ /* Needed to select an item before popping up a menu. */
+ if (call_parent)
+ {
+ g_signal_handlers_block_by_func (tree_view, row_activated_callback, view);
+ /* GTK+ 4 TODO: replace with event(), at the very least. */
+ tree_view_class->button_press_event (widget, (GdkEventButton *) event);
+ g_signal_handlers_unblock_by_func (tree_view, row_activated_callback, view);
+ }
+ else if (path_selected)
+ {
+ gtk_widget_grab_focus (widget);
+ }
+
+ if (is_simple_click && !on_expander)
+ {
+ view->details->drag_started = FALSE;
+ view->details->drag_button = button;
+ view->details->drag_x = bin_x;
+ view->details->drag_y = bin_y;
+ }
+
+ if (button == GDK_BUTTON_SECONDARY)
+ {
+ nautilus_files_view_pop_up_selection_context_menu (NAUTILUS_FILES_VIEW (view),
+ event);
+ }
+
+ /* Don't open a new tab if we are in single click mode (this would open 2 tabs),
+ * or if CTRL or SHIFT is pressed.
+ */
+ if (button == GDK_BUTTON_MIDDLE &&
+ get_click_policy () != NAUTILUS_CLICK_POLICY_SINGLE &&
+ !button_event_modifies_selection (event))
+ {
+ gtk_tree_selection_unselect_all (selection);
+ gtk_tree_selection_select_path (selection, path);
+
+ activate_selected_items_alternate (view, NULL, TRUE);
+ }
+ }
+
+ gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
+}
+
+static void
+on_tree_view_multi_press_gesture_released (GtkGestureMultiPress *gesture,
+ gint n_press,
+ gdouble x,
+ gdouble y,
+ gpointer callback_data)
+{
+ NautilusListView *view;
+ guint button;
+
+ view = NAUTILUS_LIST_VIEW (callback_data);
+ button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
+ if (button != view->details->drag_button)
+ {
+ return;
+ }
+
+ view->details->drag_button = 0;
+ if (!view->details->drag_started && !view->details->ignore_button_release)
+ {
+ GdkEventSequence *sequence;
+ const GdkEvent *event;
+
+ sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
+ event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
+ /* Typically will only happen with GTK+ <= 3.22.30 and <= 3.93.0,
+ * where ::released is emitted after ::cancel, but can’t hurt to guard
+ * against it anyway.
+ */
+ if (event == NULL)
+ {
+ return;
+ }
+
+ nautilus_list_view_did_not_drag (view, event);
+ }
+}
+
+static gboolean
+key_press_callback (GtkWidget *widget,
+ GdkEvent *event,
+ gpointer callback_data)
+{
+ NautilusFilesView *view;
+ GtkTreeView *tree_view;
+ guint keyval;
+ GdkModifierType state;
+
+ view = NAUTILUS_FILES_VIEW (callback_data);
+ tree_view = GTK_TREE_VIEW (widget);
+
+ NAUTILUS_LIST_VIEW (view)->details->last_event_button_x = -1;
+ NAUTILUS_LIST_VIEW (view)->details->last_event_button_y = -1;
+
+ if (G_UNLIKELY (!gdk_event_get_keyval (event, &keyval)))
+ {
+ g_return_val_if_reached (GDK_EVENT_PROPAGATE);
+ }
+ gdk_event_get_state (event, &state);
+
+ if (keyval == GDK_KEY_F10)
+ {
+ if ((state & GDK_CONTROL_MASK) != 0)
+ {
+ nautilus_files_view_pop_up_background_context_menu (view, NULL);
+
+ return GDK_EVENT_STOP;
+ }
+ }
+
+ if (keyval == GDK_KEY_Right)
+ {
+ g_autoptr (GtkTreePath) path = NULL;
+
+ gtk_tree_view_get_cursor (tree_view, &path, NULL);
+
+ if (path != NULL)
+ {
+ gtk_tree_view_expand_row (tree_view, path, FALSE);
+ }
+
+ return GDK_EVENT_STOP;
+ }
+
+ if (keyval == GDK_KEY_Left)
+ {
+ g_autoptr (GtkTreePath) path = NULL;
+
+ gtk_tree_view_get_cursor (tree_view, &path, NULL);
+
+ if (path != NULL && !gtk_tree_view_collapse_row (tree_view, path))
+ {
+ /* if the row is already collapsed or doesn't have any children,
+ * jump to the parent row instead.
+ */
+ if ((gtk_tree_path_get_depth (path) > 1) && gtk_tree_path_up (path))
+ {
+ gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
+ }
+ }
+
+ return GDK_EVENT_STOP;
+ }
+
+ if (keyval == GDK_KEY_space)
+ {
+ if ((state & GDK_CONTROL_MASK) != 0)
+ {
+ return GDK_EVENT_PROPAGATE;
+ }
+
+ if (!gtk_widget_has_focus (GTK_WIDGET (NAUTILUS_LIST_VIEW (view)->details->tree_view)))
+ {
+ return GDK_EVENT_PROPAGATE;
+ }
+
+ if ((state & GDK_SHIFT_MASK) != 0)
+ {
+ activate_selected_items_alternate (NAUTILUS_LIST_VIEW (view), NULL, TRUE);
+ }
+ else
+ {
+ preview_selected_items (NAUTILUS_LIST_VIEW (view));
+ }
+
+ return GDK_EVENT_STOP;
+ }
+
+ if (keyval == GDK_KEY_v)
+ {
+ /* Eat Control + v to not enable type ahead */
+ if ((state & GDK_CONTROL_MASK) != 0)
+ {
+ return GDK_EVENT_STOP;
+ }
+ }
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static gboolean
+on_event (GtkWidget *widget,
+ GdkEvent *event,
+ gpointer user_data)
+{
+ GdkEventType event_type;
+
+ event_type = gdk_event_get_event_type (event);
+
+ /* TODO: Replace motion events with motion controllers. */
+ if (event_type == GDK_MOTION_NOTIFY)
+ {
+ return on_motion_notify (widget, event, user_data);
+ }
+ else if (event_type == GDK_ENTER_NOTIFY)
+ {
+ return on_enter_notify (widget, event, user_data);
+ }
+ else if (event_type == GDK_LEAVE_NOTIFY)
+ {
+ return on_leave_notify (widget, event, user_data);
+ }
+ else if (event_type == GDK_KEY_PRESS)
+ {
+ return key_press_callback (widget, event, user_data);
+ }
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static void
+subdirectory_done_loading_callback (NautilusDirectory *directory,
+ NautilusListView *view)
+{
+ nautilus_list_model_subdirectory_done_loading (view->details->model, directory);
+}
+
+static void
+row_expanded_callback (GtkTreeView *treeview,
+ GtkTreeIter *iter,
+ GtkTreePath *path,
+ gpointer callback_data)
+{
+ NautilusListView *view;
+ NautilusDirectory *directory;
+ char *uri;
+
+ view = NAUTILUS_LIST_VIEW (callback_data);
+
+ if (!nautilus_list_model_load_subdirectory (view->details->model, path, &directory))
+ {
+ return;
+ }
+
+ uri = nautilus_directory_get_uri (directory);
+ DEBUG ("Row expanded callback for URI %s", uri);
+ g_free (uri);
+
+ nautilus_files_view_add_subdirectory (NAUTILUS_FILES_VIEW (view), directory);
+
+ if (nautilus_directory_are_all_files_seen (directory))
+ {
+ nautilus_list_model_subdirectory_done_loading (view->details->model,
+ directory);
+ }
+ else
+ {
+ g_signal_connect_object (directory, "done-loading",
+ G_CALLBACK (subdirectory_done_loading_callback),
+ view, 0);
+ }
+
+ nautilus_directory_unref (directory);
+}
+
+typedef struct
+{
+ NautilusFile *file;
+ NautilusDirectory *directory;
+ NautilusListView *view;
+} UnloadDelayData;
+
+static void
+unload_delay_data_free (UnloadDelayData *unload_data)
+{
+ if (unload_data->view != NULL)
+ {
+ g_object_remove_weak_pointer (G_OBJECT (unload_data->view),
+ (gpointer *) &unload_data->view);
+ }
+
+ nautilus_directory_unref (unload_data->directory);
+ nautilus_file_unref (unload_data->file);
+
+ g_slice_free (UnloadDelayData, unload_data);
+}
+
+static UnloadDelayData *
+unload_delay_data_new (NautilusFile *file,
+ NautilusDirectory *parent_directory,
+ NautilusListView *view)
+{
+ UnloadDelayData *unload_data;
+
+ unload_data = g_slice_new0 (UnloadDelayData);
+ unload_data->view = view;
+ unload_data->file = nautilus_file_ref (file);
+ unload_data->directory = nautilus_directory_ref (parent_directory);
+
+ g_object_add_weak_pointer (G_OBJECT (unload_data->view),
+ (gpointer *) &unload_data->view);
+
+ return unload_data;
+}
+
+static gboolean
+unload_file_timeout (gpointer data)
+{
+ UnloadDelayData *unload_data = data;
+ GtkTreeIter iter;
+ NautilusListModel *model;
+ GtkTreePath *path;
+
+ if (unload_data->view == NULL)
+ {
+ goto out;
+ }
+
+ model = unload_data->view->details->model;
+ if (nautilus_list_model_get_tree_iter_from_file (model,
+ unload_data->file,
+ unload_data->directory,
+ &iter))
+ {
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
+ if (!gtk_tree_view_row_expanded (unload_data->view->details->tree_view,
+ path))
+ {
+ nautilus_list_model_unload_subdirectory (model, &iter);
+ }
+ gtk_tree_path_free (path);
+ }
+
+out:
+ unload_delay_data_free (unload_data);
+ return FALSE;
+}
+
+static void
+row_collapsed_callback (GtkTreeView *treeview,
+ GtkTreeIter *iter,
+ GtkTreePath *path,
+ gpointer callback_data)
+{
+ NautilusListView *view;
+ NautilusFile *file;
+ NautilusDirectory *directory;
+ GtkTreeIter parent;
+ UnloadDelayData *unload_data;
+ GtkTreeModel *model;
+ char *uri;
+
+ view = NAUTILUS_LIST_VIEW (callback_data);
+ model = GTK_TREE_MODEL (view->details->model);
+
+ gtk_tree_model_get (model, iter,
+ NAUTILUS_LIST_MODEL_FILE_COLUMN, &file,
+ -1);
+
+ uri = nautilus_file_get_uri (file);
+ DEBUG ("Row collapsed callback for uri %s", uri);
+ g_free (uri);
+
+ directory = NULL;
+ if (gtk_tree_model_iter_parent (model, &parent, iter))
+ {
+ gtk_tree_model_get (model, &parent,
+ NAUTILUS_LIST_MODEL_SUBDIRECTORY_COLUMN, &directory,
+ -1);
+ }
+
+ unload_data = unload_delay_data_new (file, directory, view);
+ g_timeout_add_seconds (COLLAPSE_TO_UNLOAD_DELAY,
+ unload_file_timeout,
+ unload_data);
+
+ nautilus_file_unref (file);
+ nautilus_directory_unref (directory);
+}
+
+static void
+subdirectory_unloaded_callback (NautilusListModel *model,
+ NautilusDirectory *directory,
+ gpointer callback_data)
+{
+ NautilusListView *view;
+
+ g_return_if_fail (NAUTILUS_IS_LIST_MODEL (model));
+ g_return_if_fail (NAUTILUS_IS_DIRECTORY (directory));
+
+ view = NAUTILUS_LIST_VIEW (callback_data);
+
+ g_signal_handlers_disconnect_by_func (directory,
+ G_CALLBACK (subdirectory_done_loading_callback),
+ view);
+ nautilus_files_view_remove_subdirectory (NAUTILUS_FILES_VIEW (view), directory);
+}
+
+static gboolean
+test_expand_row_callback (GtkTreeView *tree_view,
+ GtkTreeIter *iter,
+ GtkTreePath *path,
+ gboolean user_data)
+{
+ return !g_settings_get_boolean (nautilus_list_view_preferences,
+ NAUTILUS_PREFERENCES_LIST_VIEW_USE_TREE);
+}
+
+static void
+nautilus_list_view_reveal_selection (NautilusFilesView *view)
+{
+ g_autolist (NautilusFile) selection = NULL;
+
+ g_return_if_fail (NAUTILUS_IS_LIST_VIEW (view));
+
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (view));
+
+ /* Make sure at least one of the selected items is scrolled into view */
+ if (selection != NULL)
+ {
+ NautilusListView *list_view;
+ NautilusFile *file;
+ GtkTreeIter iter;
+ GtkTreePath *path;
+
+ list_view = NAUTILUS_LIST_VIEW (view);
+ file = selection->data;
+ if (nautilus_list_model_get_first_iter_for_file (list_view->details->model, file, &iter))
+ {
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (list_view->details->model), &iter);
+
+ gtk_tree_view_scroll_to_cell (list_view->details->tree_view, path, NULL, FALSE, 0.0, 0.0);
+
+ gtk_tree_path_free (path);
+ }
+ }
+}
+
+static gboolean
+sort_criterion_changes_due_to_user (GtkTreeView *tree_view)
+{
+ GList *columns, *p;
+ GtkTreeViewColumn *column;
+ GSignalInvocationHint *ihint;
+ gboolean ret;
+
+ ret = FALSE;
+
+ columns = gtk_tree_view_get_columns (tree_view);
+ for (p = columns; p != NULL; p = p->next)
+ {
+ column = p->data;
+ ihint = g_signal_get_invocation_hint (column);
+ if (ihint != NULL)
+ {
+ ret = TRUE;
+ break;
+ }
+ }
+ g_list_free (columns);
+
+ return ret;
+}
+
+static void
+sort_column_changed_callback (GtkTreeSortable *sortable,
+ NautilusListView *view)
+{
+ NautilusFile *file;
+ gint sort_column_id, default_sort_column_id;
+ GtkSortType reversed;
+ GQuark sort_attr, default_sort_attr;
+ char *reversed_attr, *default_reversed_attr;
+ gboolean default_sort_reversed;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (view));
+
+ gtk_tree_sortable_get_sort_column_id (sortable, &sort_column_id, &reversed);
+ sort_attr = nautilus_list_model_get_attribute_from_sort_column_id (view->details->model, sort_column_id);
+
+ default_sort_column_id = nautilus_list_model_get_sort_column_id_from_attribute (view->details->model,
+ g_quark_from_string (get_default_sort_order (file, &default_sort_reversed)));
+ default_sort_attr = nautilus_list_model_get_attribute_from_sort_column_id (view->details->model, default_sort_column_id);
+ nautilus_file_set_metadata (file, NAUTILUS_METADATA_KEY_LIST_VIEW_SORT_COLUMN,
+ g_quark_to_string (default_sort_attr), g_quark_to_string (sort_attr));
+
+ default_reversed_attr = (default_sort_reversed ? "true" : "false");
+
+ if (view->details->last_sort_attr != sort_attr &&
+ sort_criterion_changes_due_to_user (view->details->tree_view))
+ {
+ /* at this point, the sort order is always GTK_SORT_ASCENDING, if the sort column ID
+ * switched. Invert the sort order, if it's the default criterion with a reversed preference,
+ * or if it makes sense for the attribute (i.e. date). */
+ if (sort_attr == default_sort_attr)
+ {
+ /* use value from preferences */
+ reversed = g_settings_get_boolean (nautilus_preferences,
+ NAUTILUS_PREFERENCES_DEFAULT_SORT_IN_REVERSE_ORDER);
+ }
+ else
+ {
+ reversed = nautilus_file_is_date_sort_attribute_q (sort_attr);
+ }
+
+ if (reversed)
+ {
+ g_signal_handlers_block_by_func (sortable, sort_column_changed_callback, view);
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (view->details->model),
+ sort_column_id,
+ GTK_SORT_DESCENDING);
+ g_signal_handlers_unblock_by_func (sortable, sort_column_changed_callback, view);
+ }
+ }
+
+
+ reversed_attr = (reversed ? "true" : "false");
+ nautilus_file_set_metadata (file, NAUTILUS_METADATA_KEY_LIST_VIEW_SORT_REVERSED,
+ default_reversed_attr, reversed_attr);
+
+ /* Make sure selected item(s) is visible after sort */
+ nautilus_list_view_reveal_selection (NAUTILUS_FILES_VIEW (view));
+
+ view->details->last_sort_attr = sort_attr;
+}
+
+static char *
+get_root_uri_callback (NautilusTreeViewDragDest *dest,
+ gpointer user_data)
+{
+ NautilusListView *view;
+
+ view = NAUTILUS_LIST_VIEW (user_data);
+
+ return nautilus_files_view_get_uri (NAUTILUS_FILES_VIEW (view));
+}
+
+static NautilusFile *
+get_file_for_path_callback (NautilusTreeViewDragDest *dest,
+ GtkTreePath *path,
+ gpointer user_data)
+{
+ NautilusListView *view;
+
+ view = NAUTILUS_LIST_VIEW (user_data);
+
+ return nautilus_list_model_file_for_path (view->details->model, path);
+}
+
+
+static void
+list_view_handle_uri_list (NautilusTreeViewDragDest *dest,
+ const char *item_uris,
+ const char *target_uri,
+ GdkDragAction action,
+ NautilusListView *view)
+{
+ nautilus_files_view_handle_uri_list_drop (NAUTILUS_FILES_VIEW (view),
+ item_uris, target_uri, action);
+}
+
+static void
+list_view_handle_text (NautilusTreeViewDragDest *dest,
+ const char *text,
+ const char *target_uri,
+ GdkDragAction action,
+ NautilusListView *view)
+{
+ nautilus_files_view_handle_text_drop (NAUTILUS_FILES_VIEW (view),
+ text, target_uri, action);
+}
+
+static void
+list_view_handle_raw (NautilusTreeViewDragDest *dest,
+ const char *raw_data,
+ int length,
+ const char *target_uri,
+ const char *direct_save_uri,
+ GdkDragAction action,
+ NautilusListView *view)
+{
+ nautilus_files_view_handle_raw_drop (NAUTILUS_FILES_VIEW (view),
+ raw_data, length, target_uri, direct_save_uri,
+ action);
+}
+
+static void
+list_view_handle_hover (NautilusTreeViewDragDest *dest,
+ const char *target_uri,
+ NautilusListView *view)
+{
+ nautilus_files_view_handle_hover (NAUTILUS_FILES_VIEW (view), target_uri);
+}
+
+static void
+move_copy_items_callback (NautilusTreeViewDragDest *dest,
+ const GList *item_uris,
+ const char *target_uri,
+ guint action,
+ gpointer user_data)
+{
+ NautilusFilesView *view = user_data;
+
+ nautilus_clipboard_clear_if_colliding_uris (GTK_WIDGET (view),
+ item_uris);
+ nautilus_files_view_move_copy_items (view,
+ item_uris,
+ target_uri,
+ action);
+}
+
+static void
+column_header_menu_toggled (GtkCheckMenuItem *menu_item,
+ NautilusListView *list_view)
+{
+ NautilusFile *file;
+ char **visible_columns;
+ char **column_order;
+ const char *column;
+ GList *list = NULL;
+ GList *l;
+ int i;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (list_view));
+ visible_columns = get_visible_columns (list_view);
+ column_order = get_column_order (list_view);
+ column = g_object_get_data (G_OBJECT (menu_item), "column-name");
+
+ for (i = 0; visible_columns[i] != NULL; ++i)
+ {
+ list = g_list_prepend (list, visible_columns[i]);
+ }
+
+ if (gtk_check_menu_item_get_active (menu_item))
+ {
+ list = g_list_prepend (list, g_strdup (column));
+ }
+ else
+ {
+ l = g_list_find_custom (list, column, (GCompareFunc) g_strcmp0);
+ list = g_list_delete_link (list, l);
+ }
+
+ list = g_list_reverse (list);
+ nautilus_file_set_metadata_list (file,
+ NAUTILUS_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS,
+ list);
+
+ g_free (visible_columns);
+
+ visible_columns = g_new0 (char *, g_list_length (list) + 1);
+ for (i = 0, l = list; l != NULL; ++i, l = l->next)
+ {
+ visible_columns[i] = l->data;
+ }
+
+ /* set view values ourselves, as new metadata could not have been
+ * updated yet.
+ */
+ apply_columns_settings (list_view, column_order, visible_columns);
+
+ g_list_free (list);
+ g_strfreev (column_order);
+ g_strfreev (visible_columns);
+}
+
+static void
+column_header_menu_use_default (GtkMenuItem *menu_item,
+ NautilusListView *list_view)
+{
+ NautilusFile *file;
+ char **default_columns;
+ char **default_order;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (list_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);
+
+ default_columns = get_default_visible_columns (list_view);
+ default_order = get_default_column_order (list_view);
+
+ /* set view values ourselves, as new metadata could not have been
+ * updated yet.
+ */
+ apply_columns_settings (list_view, default_order, default_columns);
+
+ g_strfreev (default_columns);
+ g_strfreev (default_order);
+}
+
+static gboolean
+on_column_header_event (GtkWidget *widget,
+ GdkEvent *event,
+ gpointer user_data)
+{
+ NautilusListView *list_view;
+ guint button;
+ NautilusFile *file;
+ char **visible_columns;
+ char **column_order;
+ GList *all_columns;
+ GHashTable *visible_columns_hash;
+ int i;
+ GList *l;
+ GtkWidget *menu;
+ GtkWidget *menu_item;
+
+ list_view = NAUTILUS_LIST_VIEW (user_data);
+
+ if (gdk_event_get_event_type (event) != GDK_BUTTON_PRESS)
+ {
+ return GDK_EVENT_PROPAGATE;
+ }
+
+ g_assert (gdk_event_get_button (event, &button));
+
+ if (button != GDK_BUTTON_SECONDARY)
+ {
+ return GDK_EVENT_PROPAGATE;
+ }
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (list_view));
+
+ visible_columns = get_visible_columns (list_view);
+ column_order = get_column_order (list_view);
+
+ 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"));
+ if (visible_columns != NULL)
+ {
+ for (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));
+ }
+ }
+
+ menu = gtk_menu_new ();
+
+ for (l = all_columns; l != NULL; l = l->next)
+ {
+ char *name;
+ char *label;
+ char *lowercase;
+
+ g_object_get (G_OBJECT (l->data),
+ "name", &name,
+ "label", &label,
+ NULL);
+ lowercase = g_ascii_strdown (name, -1);
+
+ menu_item = gtk_check_menu_item_new_with_label (label);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
+
+ g_object_set_data_full (G_OBJECT (menu_item),
+ "column-name", name, g_free);
+
+ /* name is always visible */
+ if (strcmp (lowercase, "name") == 0)
+ {
+ gtk_widget_set_sensitive (menu_item, FALSE);
+ }
+
+ if (g_hash_table_lookup (visible_columns_hash, lowercase) != NULL)
+ {
+ gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menu_item),
+ TRUE);
+ }
+
+ g_signal_connect (menu_item,
+ "toggled",
+ G_CALLBACK (column_header_menu_toggled),
+ list_view);
+
+ g_free (lowercase);
+ g_free (label);
+ }
+
+ menu_item = gtk_separator_menu_item_new ();
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
+
+ menu_item = gtk_menu_item_new_with_label (_("Use Default"));
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
+
+ g_signal_connect (menu_item,
+ "activate",
+ G_CALLBACK (column_header_menu_use_default),
+ list_view);
+
+ gtk_widget_show_all (menu);
+ gtk_menu_popup_at_pointer (GTK_MENU (menu), event);
+
+ g_hash_table_destroy (visible_columns_hash);
+ nautilus_column_list_free (all_columns);
+ g_strfreev (column_order);
+ g_strfreev (visible_columns);
+
+ return GDK_EVENT_STOP;
+}
+
+static void
+apply_columns_settings (NautilusListView *list_view,
+ char **column_order,
+ char **visible_columns)
+{
+ GList *all_columns;
+ NautilusFile *file;
+ GList *old_view_columns, *view_columns;
+ GHashTable *visible_columns_hash;
+ GtkTreeViewColumn *prev_view_column;
+ GList *l;
+ int i;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (list_view));
+
+ /* prepare ordered list of view columns using column_order and visible_columns */
+ view_columns = NULL;
+
+ 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"));
+ if (visible_columns != NULL)
+ {
+ for (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));
+ }
+ }
+
+ for (l = all_columns; l != NULL; l = l->next)
+ {
+ char *name;
+ char *lowercase;
+
+ 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)
+ {
+ GtkTreeViewColumn *view_column;
+
+ view_column = g_hash_table_lookup (list_view->details->columns, name);
+ if (view_column != NULL)
+ {
+ view_columns = g_list_prepend (view_columns, view_column);
+ }
+ }
+
+ g_free (name);
+ g_free (lowercase);
+ }
+
+ g_hash_table_destroy (visible_columns_hash);
+ nautilus_column_list_free (all_columns);
+
+ view_columns = g_list_reverse (view_columns);
+
+ /* hide columns that are not present in the configuration */
+ old_view_columns = gtk_tree_view_get_columns (list_view->details->tree_view);
+ for (l = old_view_columns; l != NULL; l = l->next)
+ {
+ if (g_list_find (view_columns, l->data) == NULL)
+ {
+ gtk_tree_view_column_set_visible (l->data, FALSE);
+ }
+ }
+ g_list_free (old_view_columns);
+
+ /* show new columns from the configuration */
+ for (l = view_columns; l != NULL; l = l->next)
+ {
+ gtk_tree_view_column_set_visible (l->data, TRUE);
+ }
+
+ /* place columns in the correct order */
+ prev_view_column = NULL;
+ for (l = view_columns; l != NULL; l = l->next)
+ {
+ gtk_tree_view_move_column_after (list_view->details->tree_view, l->data, prev_view_column);
+ prev_view_column = l->data;
+ }
+ g_list_free (view_columns);
+}
+
+static void
+starred_cell_data_func (GtkTreeViewColumn *column,
+ GtkCellRenderer *renderer,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ NautilusListView *view)
+{
+ g_autofree gchar *text = NULL;
+ g_autofree gchar *uri = NULL;
+ NautilusFile *file;
+
+ gtk_tree_model_get (model, iter,
+ view->details->file_name_column_num, &text,
+ -1);
+
+ gtk_tree_model_get (GTK_TREE_MODEL (model),
+ iter,
+ NAUTILUS_LIST_MODEL_FILE_COLUMN, &file,
+ -1);
+
+ if (file == NULL)
+ {
+ /* This row is a label, not a file */
+ g_object_set (renderer,
+ "icon-name", NULL,
+ "mode", GTK_CELL_RENDERER_MODE_INERT,
+ NULL);
+ return;
+ }
+
+ uri = nautilus_file_get_uri (file);
+
+ if (nautilus_tag_manager_file_is_starred (view->details->tag_manager, uri))
+ {
+ g_object_set (renderer,
+ "icon-name", "starred-symbolic",
+ NULL);
+ }
+ else
+ {
+ g_object_set (renderer,
+ "icon-name", "non-starred-symbolic",
+ NULL);
+ }
+
+ nautilus_file_unref (file);
+}
+
+static void
+filename_cell_data_func (GtkTreeViewColumn *column,
+ GtkCellRenderer *renderer,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ NautilusListView *view)
+{
+ char *text;
+ g_autofree gchar *escaped_text = NULL;
+ g_autofree gchar *escaped_name = NULL;
+ g_autofree gchar *replaced_text = NULL;
+ GtkTreePath *path;
+ PangoUnderline underline;
+ GString *display_text;
+ NautilusDirectory *directory;
+ NautilusQuery *query = NULL;
+ NautilusFile *file;
+ const gchar *snippet;
+
+ gtk_tree_model_get (model, iter,
+ view->details->file_name_column_num, &text,
+ -1);
+
+ escaped_name = g_markup_escape_text (text, -1);
+ display_text = g_string_new (escaped_name);
+
+ directory = nautilus_files_view_get_model (NAUTILUS_FILES_VIEW (view));
+
+ if (NAUTILUS_IS_SEARCH_DIRECTORY (directory))
+ {
+ query = nautilus_search_directory_get_query (NAUTILUS_SEARCH_DIRECTORY (directory));
+ }
+
+ if (get_click_policy () == NAUTILUS_CLICK_POLICY_SINGLE)
+ {
+ path = gtk_tree_model_get_path (model, iter);
+
+ if (view->details->hover_path == NULL ||
+ gtk_tree_path_compare (path, view->details->hover_path))
+ {
+ underline = PANGO_UNDERLINE_NONE;
+ }
+ else
+ {
+ underline = PANGO_UNDERLINE_SINGLE;
+ }
+
+ gtk_tree_path_free (path);
+ }
+ else
+ {
+ underline = PANGO_UNDERLINE_NONE;
+ }
+
+ if (query &&
+ nautilus_query_get_search_content (query) == NAUTILUS_QUERY_SEARCH_CONTENT_FULL_TEXT)
+ {
+ gtk_tree_model_get (model, iter,
+ NAUTILUS_LIST_MODEL_FILE_COLUMN, &file,
+ -1);
+
+ /* Rule out dummy row */
+ if (file != NULL)
+ {
+ snippet = nautilus_file_get_search_fts_snippet (file);
+ if (snippet)
+ {
+ replaced_text = g_regex_replace (view->details->regex,
+ snippet,
+ -1,
+ 0,
+ " ",
+ G_REGEX_MATCH_NEWLINE_ANY,
+ NULL);
+
+ escaped_text = g_markup_escape_text (replaced_text, -1);
+
+ g_string_append_printf (display_text,
+ " <small><span alpha='50%%'><b>%s</b></span></small>",
+ escaped_text);
+ }
+ }
+ nautilus_file_unref (file);
+ }
+
+ g_object_set (G_OBJECT (renderer),
+ "markup", display_text->str,
+ "underline", underline,
+ NULL);
+
+ g_free (text);
+ g_string_free (display_text, TRUE);
+}
+
+static void
+location_cell_data_func (GtkTreeViewColumn *column,
+ GtkCellRenderer *renderer,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ NautilusListView *view,
+ gboolean show_trash_orig)
+{
+ NautilusDirectory *directory;
+ GFile *home_location;
+ NautilusFile *file;
+ GFile *dir_location;
+ GFile *base_location;
+ gchar *where = NULL;
+
+ directory = nautilus_files_view_get_model (NAUTILUS_FILES_VIEW (view));
+
+ home_location = g_file_new_for_path (g_get_home_dir ());
+
+ gtk_tree_model_get (model, iter,
+ NAUTILUS_LIST_MODEL_FILE_COLUMN, &file,
+ -1);
+
+ /* The file might be NULL if we just toggled an expander
+ * and we're still loading the subdirectory.
+ */
+ if (file == NULL)
+ {
+ return;
+ }
+
+ if (show_trash_orig && nautilus_file_is_in_trash (file))
+ {
+ NautilusFile *orig_file;
+
+ orig_file = nautilus_file_get_trash_original_file (file);
+
+ if (orig_file != NULL)
+ {
+ nautilus_file_unref (file);
+ file = orig_file;
+ }
+ }
+
+ if (!nautilus_file_is_in_recent (file))
+ {
+ dir_location = nautilus_file_get_parent_location (file);
+ }
+ else
+ {
+ GFile *activation_location;
+
+ activation_location = nautilus_file_get_activation_location (file);
+ dir_location = g_file_get_parent (activation_location);
+
+ g_object_unref (activation_location);
+ }
+
+ if (!NAUTILUS_IS_SEARCH_DIRECTORY (directory))
+ {
+ base_location = g_object_ref (home_location);
+ }
+ else
+ {
+ NautilusQuery *query;
+ NautilusFile *base;
+ GFile *location;
+
+ query = nautilus_search_directory_get_query (NAUTILUS_SEARCH_DIRECTORY (directory));
+ location = nautilus_query_get_location (query);
+ base = nautilus_file_get (location);
+
+ if (!nautilus_file_is_in_recent (base))
+ {
+ base_location = nautilus_file_get_location (base);
+ }
+ else
+ {
+ base_location = g_object_ref (home_location);
+ }
+
+ nautilus_file_unref (base);
+ g_object_unref (location);
+ g_object_unref (query);
+ }
+
+ if (g_file_equal (base_location, dir_location))
+ {
+ /* Only occurs when search result is
+ * a direct child of the base location
+ */
+ where = g_strdup ("");
+ }
+ else if (g_file_equal (home_location, dir_location))
+ {
+ where = g_strdup (_("Home"));
+ }
+ else if (g_file_has_prefix (dir_location, base_location))
+ {
+ gchar *relative_path;
+
+ relative_path = g_file_get_relative_path (base_location, dir_location);
+ where = g_filename_display_name (relative_path);
+
+ g_free (relative_path);
+ }
+ else
+ {
+ where = g_file_get_path (dir_location);
+ }
+
+ g_object_set (G_OBJECT (renderer),
+ "text", where,
+ NULL);
+
+ g_free (where);
+
+ g_object_unref (base_location);
+ g_object_unref (dir_location);
+ nautilus_file_unref (file);
+ g_object_unref (home_location);
+}
+
+
+static void
+where_cell_data_func (GtkTreeViewColumn *column,
+ GtkCellRenderer *renderer,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ NautilusListView *view)
+{
+ location_cell_data_func (column, renderer, model, iter, view, FALSE);
+}
+
+static void
+trash_orig_path_cell_data_func (GtkTreeViewColumn *column,
+ GtkCellRenderer *renderer,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ NautilusListView *view)
+{
+ location_cell_data_func (column, renderer, model, iter, view, TRUE);
+}
+
+#define SMALL_ZOOM_ICON_PADDING 0
+#define STANDARD_ZOOM_ICON_PADDING 6
+#define LARGE_ZOOM_ICON_PADDING 6
+#define LARGER_ZOOM_ICON_PADDING 6
+
+static gint
+nautilus_list_view_get_icon_padding_for_zoom_level (NautilusListZoomLevel zoom_level)
+{
+ switch (zoom_level)
+ {
+ case NAUTILUS_LIST_ZOOM_LEVEL_SMALL:
+ {
+ return SMALL_ZOOM_ICON_PADDING;
+ }
+
+ case NAUTILUS_LIST_ZOOM_LEVEL_STANDARD:
+ {
+ return STANDARD_ZOOM_ICON_PADDING;
+ }
+
+ case NAUTILUS_LIST_ZOOM_LEVEL_LARGE:
+ {
+ return LARGE_ZOOM_ICON_PADDING;
+ }
+
+ case NAUTILUS_LIST_ZOOM_LEVEL_LARGER:
+ {
+ return LARGER_ZOOM_ICON_PADDING;
+ }
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+set_up_pixbuf_size (NautilusListView *view)
+{
+ int icon_size, icon_padding;
+
+ /* Make all rows the same size. */
+ icon_size = nautilus_list_model_get_icon_size_for_zoom_level (view->details->zoom_level);
+ icon_padding = nautilus_list_view_get_icon_padding_for_zoom_level (view->details->zoom_level);
+ gtk_cell_renderer_set_fixed_size (GTK_CELL_RENDERER (view->details->pixbuf_cell),
+ -1, icon_size + 2 * icon_padding);
+
+ /* FIXME: https://bugzilla.gnome.org/show_bug.cgi?id=641518 */
+ gtk_tree_view_columns_autosize (view->details->tree_view);
+}
+
+static gint
+get_icon_scale_callback (NautilusListModel *model,
+ NautilusListView *view)
+{
+ return gtk_widget_get_scale_factor (GTK_WIDGET (view->details->tree_view));
+}
+
+static void
+on_longpress_gesture_pressed_event (GtkGestureLongPress *gesture,
+ gdouble x,
+ gdouble y,
+ gpointer user_data)
+{
+ GdkEventSequence *event_sequence;
+ const GdkEvent *event;
+ NautilusListView *view = user_data;
+ g_autolist (NautilusFile) selection = NULL;
+
+ event_sequence = gtk_gesture_get_last_updated_sequence (GTK_GESTURE (gesture));
+ if (event_sequence == NULL)
+ {
+ return;
+ }
+
+ event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), event_sequence);
+
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (view));
+ if (selection != NULL)
+ {
+ nautilus_files_view_pop_up_selection_context_menu (NAUTILUS_FILES_VIEW (view), event);
+ }
+ else
+ {
+ nautilus_files_view_pop_up_background_context_menu (NAUTILUS_FILES_VIEW (view), event);
+ }
+}
+
+static void
+on_tree_view_drag_gesture_drag_begin (GtkGestureDrag *gesture,
+ gdouble start_x,
+ gdouble start_y,
+ gpointer user_data)
+{
+ nautilus_list_view_dnd_init (NAUTILUS_LIST_VIEW (user_data));
+}
+
+static void
+on_tree_view_drag_gesture_drag_update (GtkGestureDrag *gesture,
+ gdouble offset_x,
+ gdouble offset_y,
+ gpointer user_data)
+{
+ GdkEventSequence *sequence;
+ const GdkEvent *event;
+ NautilusListView *list_view;
+
+ sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
+ event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
+ list_view = NAUTILUS_LIST_VIEW (user_data);
+
+ nautilus_list_view_dnd_drag_begin (list_view, offset_x, offset_y, event);
+}
+
+static void
+list_view_use_tree_changed_callback (gpointer callback_data)
+{
+ GtkTreeView *tree_view;
+
+ tree_view = GTK_TREE_VIEW (callback_data);
+
+ gtk_tree_view_collapse_all (tree_view);
+}
+
+
+static void
+create_and_set_up_tree_view (NautilusListView *view)
+{
+ GtkCellRenderer *cell;
+ GtkTreeViewColumn *column;
+ AtkObject *atk_obj;
+ GList *nautilus_columns;
+ GList *l;
+ gchar **default_column_order, **default_visible_columns;
+ GtkWidget *content_widget;
+ GtkGesture *longpress_gesture;
+
+ content_widget = nautilus_files_view_get_content_widget (NAUTILUS_FILES_VIEW (view));
+ view->details->tree_view = GTK_TREE_VIEW (gtk_tree_view_new ());
+ view->details->columns = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify) g_free,
+ NULL);
+ gtk_tree_view_set_enable_search (view->details->tree_view, FALSE);
+
+ view->details->drag_dest =
+ nautilus_tree_view_drag_dest_new (view->details->tree_view);
+
+ /* Stop the tree view from performing select-all actions.
+ * It is desireable that the action is disabled while directory
+ * is loading.
+ */
+ g_signal_connect (view->details->tree_view, "select-all",
+ G_CALLBACK (g_signal_stop_emission_by_name), "select-all");
+
+ g_signal_connect_object (view->details->drag_dest,
+ "get-root-uri",
+ G_CALLBACK (get_root_uri_callback),
+ view, 0);
+ g_signal_connect_object (view->details->drag_dest,
+ "get-file-for-path",
+ G_CALLBACK (get_file_for_path_callback),
+ view, 0);
+ g_signal_connect_object (view->details->drag_dest,
+ "move-copy-items",
+ G_CALLBACK (move_copy_items_callback),
+ view, 0);
+ g_signal_connect_object (view->details->drag_dest, "handle-uri-list",
+ G_CALLBACK (list_view_handle_uri_list), view, 0);
+ g_signal_connect_object (view->details->drag_dest, "handle-text",
+ G_CALLBACK (list_view_handle_text), view, 0);
+ g_signal_connect_object (view->details->drag_dest, "handle-raw",
+ G_CALLBACK (list_view_handle_raw), view, 0);
+ g_signal_connect_object (view->details->drag_dest, "handle-hover",
+ G_CALLBACK (list_view_handle_hover), view, 0);
+
+ g_signal_connect_object (gtk_tree_view_get_selection (view->details->tree_view),
+ "changed",
+ G_CALLBACK (list_selection_changed_callback), view, 0);
+
+ view->details->tree_view_drag_gesture = gtk_gesture_drag_new (GTK_WIDGET (view->details->tree_view));
+
+ gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (view->details->tree_view_drag_gesture),
+ GTK_PHASE_CAPTURE);
+ gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (view->details->tree_view_drag_gesture), 0);
+
+ g_signal_connect (view->details->tree_view_drag_gesture, "drag-begin",
+ G_CALLBACK (on_tree_view_drag_gesture_drag_begin), view);
+ g_signal_connect (view->details->tree_view_drag_gesture, "drag-update",
+ G_CALLBACK (on_tree_view_drag_gesture_drag_update), view);
+
+ view->details->tree_view_multi_press_gesture = gtk_gesture_multi_press_new (GTK_WIDGET (view->details->tree_view));
+
+ gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (view->details->tree_view_multi_press_gesture),
+ GTK_PHASE_CAPTURE);
+ gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (view->details->tree_view_multi_press_gesture), 0);
+
+ g_signal_connect (view->details->tree_view_multi_press_gesture, "pressed",
+ G_CALLBACK (on_tree_view_multi_press_gesture_pressed), view);
+ g_signal_connect (view->details->tree_view_multi_press_gesture, "released",
+ G_CALLBACK (on_tree_view_multi_press_gesture_released), view);
+
+ g_signal_connect_object (view->details->tree_view, "event",
+ G_CALLBACK (on_event), view, 0);
+ g_signal_connect_object (view->details->tree_view, "test-expand-row",
+ G_CALLBACK (test_expand_row_callback), view, 0);
+ g_signal_connect_object (view->details->tree_view, "row-expanded",
+ G_CALLBACK (row_expanded_callback), view, 0);
+ g_signal_connect_object (view->details->tree_view, "row-collapsed",
+ G_CALLBACK (row_collapsed_callback), view, 0);
+ g_signal_connect_object (view->details->tree_view, "row-activated",
+ G_CALLBACK (row_activated_callback), view, 0);
+
+ g_signal_connect_object (nautilus_list_view_preferences,
+ "changed::" NAUTILUS_PREFERENCES_LIST_VIEW_USE_TREE,
+ G_CALLBACK (list_view_use_tree_changed_callback),
+ view->details->tree_view,
+ G_CONNECT_SWAPPED);
+
+ view->details->model = g_object_new (NAUTILUS_TYPE_LIST_MODEL, NULL);
+ gtk_tree_view_set_model (view->details->tree_view, GTK_TREE_MODEL (view->details->model));
+ /* Need the model for the dnd drop icon "accept" change */
+ nautilus_list_model_set_drag_view (NAUTILUS_LIST_MODEL (view->details->model),
+ view->details->tree_view, 0, 0);
+
+ g_signal_connect_object (view->details->model, "sort-column-changed",
+ G_CALLBACK (sort_column_changed_callback), view, 0);
+
+ g_signal_connect_object (view->details->model, "subdirectory-unloaded",
+ G_CALLBACK (subdirectory_unloaded_callback), view, 0);
+
+ g_signal_connect_object (view->details->model, "get-icon-scale",
+ G_CALLBACK (get_icon_scale_callback), view, 0);
+
+ longpress_gesture = gtk_gesture_long_press_new (GTK_WIDGET (content_widget));
+ gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (longpress_gesture),
+ GTK_PHASE_CAPTURE);
+ gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (longpress_gesture),
+ TRUE);
+ g_signal_connect (longpress_gesture,
+ "pressed",
+ (GCallback) on_longpress_gesture_pressed_event,
+ view);
+
+ gtk_tree_selection_set_mode (gtk_tree_view_get_selection (view->details->tree_view), GTK_SELECTION_MULTIPLE);
+
+ g_settings_bind (nautilus_list_view_preferences, NAUTILUS_PREFERENCES_LIST_VIEW_USE_TREE,
+ view->details->tree_view, "show-expanders",
+ G_SETTINGS_BIND_DEFAULT);
+
+ nautilus_columns = nautilus_get_all_columns ();
+
+ for (l = nautilus_columns; l != NULL; l = l->next)
+ {
+ NautilusColumn *nautilus_column;
+ int column_num;
+ char *name;
+ char *label;
+ float xalign;
+ GtkSortType sort_order;
+
+ nautilus_column = NAUTILUS_COLUMN (l->data);
+
+ g_object_get (nautilus_column,
+ "name", &name,
+ "label", &label,
+ "xalign", &xalign,
+ "default-sort-order", &sort_order,
+ NULL);
+
+ column_num = nautilus_list_model_add_column (view->details->model,
+ nautilus_column);
+
+ /* Created the name column specially, because it
+ * has the icon in it.*/
+ if (!strcmp (name, "name"))
+ {
+ /* Create the file name column */
+ view->details->file_name_column = gtk_tree_view_column_new ();
+ gtk_tree_view_append_column (view->details->tree_view,
+ view->details->file_name_column);
+ view->details->file_name_column_num = column_num;
+
+ g_hash_table_insert (view->details->columns,
+ g_strdup ("name"),
+ view->details->file_name_column);
+
+ g_signal_connect (gtk_tree_view_column_get_button (view->details->file_name_column),
+ "event",
+ G_CALLBACK (on_column_header_event),
+ view);
+
+ gtk_tree_view_set_search_column (view->details->tree_view, column_num);
+
+ gtk_tree_view_column_set_sort_column_id (view->details->file_name_column, column_num);
+ gtk_tree_view_column_set_title (view->details->file_name_column, _("Name"));
+ gtk_tree_view_column_set_resizable (view->details->file_name_column, TRUE);
+ gtk_tree_view_column_set_expand (view->details->file_name_column, TRUE);
+
+ /* Initial padding */
+ cell = gtk_cell_renderer_text_new ();
+ gtk_tree_view_column_pack_start (view->details->file_name_column, cell, FALSE);
+ g_object_set (cell, "xpad", 6, NULL);
+ g_settings_bind (nautilus_list_view_preferences, NAUTILUS_PREFERENCES_LIST_VIEW_USE_TREE,
+ cell, "visible",
+ G_SETTINGS_BIND_INVERT_BOOLEAN | G_SETTINGS_BIND_GET);
+
+ /* File icon */
+ cell = gtk_cell_renderer_pixbuf_new ();
+ view->details->pixbuf_cell = (GtkCellRendererPixbuf *) cell;
+ set_up_pixbuf_size (view);
+
+ gtk_tree_view_column_pack_start (view->details->file_name_column, cell, FALSE);
+ gtk_tree_view_column_set_attributes (view->details->file_name_column,
+ cell,
+ "surface", nautilus_list_model_get_column_id_from_zoom_level (view->details->zoom_level),
+ NULL);
+
+ cell = gtk_cell_renderer_text_new ();
+ view->details->file_name_cell = (GtkCellRendererText *) cell;
+ g_object_set (cell,
+ "ellipsize", PANGO_ELLIPSIZE_END,
+ "single-paragraph-mode", FALSE,
+ "width-chars", 30,
+ "xpad", 5,
+ NULL);
+
+ gtk_tree_view_column_pack_start (view->details->file_name_column, cell, TRUE);
+ gtk_tree_view_column_set_cell_data_func (view->details->file_name_column, cell,
+ (GtkTreeCellDataFunc) filename_cell_data_func,
+ view, NULL);
+ }
+ else
+ {
+ if (g_strcmp0 (name, "starred") == 0)
+ {
+ cell = gtk_cell_renderer_pixbuf_new ();
+ g_object_set (cell,
+ "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE,
+ NULL);
+
+ column = gtk_tree_view_column_new_with_attributes (label,
+ cell,
+ NULL);
+ }
+ else
+ {
+ /* We need to use libgd */
+ cell = gd_styled_text_renderer_new ();
+ /* FIXME: should be just dim-label.
+ * See https://bugzilla.gnome.org/show_bug.cgi?id=744397
+ */
+ gd_styled_text_renderer_add_class (GD_STYLED_TEXT_RENDERER (cell),
+ "nautilus-list-dim-label");
+
+ column = gtk_tree_view_column_new_with_attributes (label,
+ cell,
+ "text", column_num,
+ NULL);
+ }
+
+ gtk_tree_view_column_set_alignment (column, xalign);
+ g_object_set (cell,
+ "xalign", xalign,
+ "xpad", 5,
+ NULL);
+ if (!strcmp (name, "permissions"))
+ {
+ g_object_set (cell,
+ "family", "Monospace",
+ NULL);
+ }
+ view->details->cells = g_list_append (view->details->cells,
+ cell);
+
+ gtk_tree_view_append_column (view->details->tree_view, column);
+ gtk_tree_view_column_set_sort_column_id (column, column_num);
+ g_hash_table_insert (view->details->columns,
+ g_strdup (name),
+ column);
+
+ g_signal_connect (gtk_tree_view_column_get_button (column),
+ "event",
+ G_CALLBACK (on_column_header_event),
+ view);
+
+ gtk_tree_view_column_set_resizable (column, TRUE);
+ gtk_tree_view_column_set_sort_order (column, sort_order);
+
+ if (!strcmp (name, "where"))
+ {
+ gtk_tree_view_column_set_cell_data_func (column, cell,
+ (GtkTreeCellDataFunc) where_cell_data_func,
+ view, NULL);
+ }
+ else if (!strcmp (name, "trash_orig_path"))
+ {
+ gtk_tree_view_column_set_cell_data_func (column, cell,
+ (GtkTreeCellDataFunc) trash_orig_path_cell_data_func,
+ view, NULL);
+ }
+ else if (!strcmp (name, "starred"))
+ {
+ gtk_tree_view_column_set_cell_data_func (column, cell,
+ (GtkTreeCellDataFunc) starred_cell_data_func,
+ view, NULL);
+ }
+ }
+ g_free (name);
+ g_free (label);
+ }
+ nautilus_column_list_free (nautilus_columns);
+
+ default_visible_columns = g_settings_get_strv (nautilus_list_view_preferences,
+ NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS);
+ default_column_order = g_settings_get_strv (nautilus_list_view_preferences,
+ NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_COLUMN_ORDER);
+
+ /* Apply the default column order and visible columns, to get it
+ * right most of the time. The metadata will be checked when a
+ * folder is loaded */
+ apply_columns_settings (view,
+ default_column_order,
+ default_visible_columns);
+
+ gtk_widget_show (GTK_WIDGET (view->details->tree_view));
+ gtk_container_add (GTK_CONTAINER (content_widget), GTK_WIDGET (view->details->tree_view));
+
+ atk_obj = gtk_widget_get_accessible (GTK_WIDGET (view->details->tree_view));
+ atk_object_set_name (atk_obj, _("List View"));
+
+ g_strfreev (default_visible_columns);
+ g_strfreev (default_column_order);
+}
+
+static void
+nautilus_list_view_add_files (NautilusFilesView *view,
+ GList *files)
+{
+ NautilusListModel *model;
+ GList *l;
+
+ model = NAUTILUS_LIST_VIEW (view)->details->model;
+ for (l = files; l != NULL; l = l->next)
+ {
+ NautilusFile *parent;
+ NautilusDirectory *directory;
+
+ parent = nautilus_file_get_parent (NAUTILUS_FILE (l->data));
+ directory = nautilus_directory_get_for_file (parent);
+ nautilus_list_model_add_file (model, NAUTILUS_FILE (l->data), directory);
+
+ nautilus_file_unref (parent);
+ nautilus_directory_unref (directory);
+ }
+}
+
+static char **
+get_default_visible_columns (NautilusListView *list_view)
+{
+ NautilusFile *file;
+ NautilusDirectory *directory;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (list_view));
+
+ if (nautilus_file_is_in_trash (file))
+ {
+ return g_strdupv ((gchar **) default_trash_visible_columns);
+ }
+
+ if (nautilus_file_is_in_recent (file))
+ {
+ return g_strdupv ((gchar **) default_recent_visible_columns);
+ }
+
+ directory = nautilus_files_view_get_model (NAUTILUS_FILES_VIEW (list_view));
+ if (NAUTILUS_IS_SEARCH_DIRECTORY (directory))
+ {
+ return g_strdupv ((gchar **) default_search_visible_columns);
+ }
+
+ return g_settings_get_strv (nautilus_list_view_preferences,
+ NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS);
+}
+
+static GList *
+default_column_array_as_list (gchar **array)
+{
+ GList *res = NULL;
+ gint i = 0;
+
+ while (array[i] != NULL)
+ {
+ res = g_list_prepend (res, array[i]);
+ i++;
+ }
+
+ return res;
+}
+
+static char **
+get_visible_columns (NautilusListView *list_view)
+{
+ NautilusFile *file;
+ g_autoptr (GList) visible_columns = NULL;
+ g_autoptr (GFile) location = NULL;
+ GPtrArray *res;
+ GList *l;
+ g_autofree gchar *uri = NULL;
+ gboolean can_star_current_directory;
+ gboolean is_starred;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (list_view));
+ uri = nautilus_file_get_uri (file);
+
+ location = g_file_new_for_uri (uri);
+ can_star_current_directory = nautilus_tag_manager_can_star_contents (list_view->details->tag_manager,
+ location);
+ is_starred = eel_uri_is_starred (uri);
+
+ visible_columns = nautilus_file_get_metadata_list (file,
+ NAUTILUS_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS);
+ if (visible_columns == NULL)
+ {
+ visible_columns = default_column_array_as_list (get_default_visible_columns (list_view));
+ }
+
+ res = g_ptr_array_new ();
+ for (l = visible_columns; l != NULL; l = l->next)
+ {
+ if (g_strcmp0 (l->data, "starred") != 0 ||
+ (g_strcmp0 (l->data, "starred") == 0 && (can_star_current_directory || is_starred)))
+ {
+ g_ptr_array_add (res, l->data);
+ }
+ }
+
+ g_ptr_array_add (res, NULL);
+
+ return (char **) g_ptr_array_free (res, FALSE);
+}
+
+static char **
+get_default_column_order (NautilusListView *list_view)
+{
+ NautilusFile *file;
+ NautilusDirectory *directory;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (list_view));
+
+ if (nautilus_file_is_in_trash (file))
+ {
+ return g_strdupv ((gchar **) default_trash_columns_order);
+ }
+
+ if (nautilus_file_is_in_recent (file))
+ {
+ return g_strdupv ((gchar **) default_recent_columns_order);
+ }
+
+ directory = nautilus_files_view_get_model (NAUTILUS_FILES_VIEW (list_view));
+ if (NAUTILUS_IS_SEARCH_DIRECTORY (directory))
+ {
+ return g_strdupv ((gchar **) default_search_columns_order);
+ }
+
+ return g_settings_get_strv (nautilus_list_view_preferences,
+ NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_COLUMN_ORDER);
+}
+
+static char **
+get_column_order (NautilusListView *list_view)
+{
+ NautilusFile *file;
+ GList *column_order;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (list_view));
+
+ column_order = nautilus_file_get_metadata_list
+ (file,
+ NAUTILUS_METADATA_KEY_LIST_VIEW_COLUMN_ORDER);
+
+ if (column_order)
+ {
+ GPtrArray *res;
+ GList *l;
+
+ res = g_ptr_array_new ();
+ for (l = column_order; l != NULL; l = l->next)
+ {
+ g_ptr_array_add (res, l->data);
+ }
+ g_ptr_array_add (res, NULL);
+
+ g_list_free (column_order);
+
+ return (char **) g_ptr_array_free (res, FALSE);
+ }
+
+ return get_default_column_order (list_view);
+}
+
+static void
+check_allow_sort (NautilusListView *list_view)
+{
+ GList *column_names;
+ GList *l;
+ NautilusFile *file;
+ GtkTreeViewColumn *column;
+ gboolean allow_sorting;
+ int sort_column_id;
+
+ column_names = g_hash_table_get_keys (list_view->details->columns);
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (list_view));
+ allow_sorting = !(nautilus_file_is_in_recent (file) || nautilus_file_is_in_search (file));
+
+ for (l = column_names; l != NULL; l = l->next)
+ {
+ column = g_hash_table_lookup (list_view->details->columns, l->data);
+ if (allow_sorting)
+ {
+ sort_column_id = nautilus_list_model_get_sort_column_id_from_attribute (list_view->details->model,
+ g_quark_from_string (l->data));
+ /* Restore its original sorting id. We rely on that the keys of the hashmap
+ * use the same string than the sort criterias */
+ gtk_tree_view_column_set_sort_column_id (column, sort_column_id);
+ }
+ else
+ {
+ /* This disables the header and any sorting capability (like shortcuts),
+ * but leaving them interactionable so the user can still resize them */
+ gtk_tree_view_column_set_sort_column_id (column, -1);
+ }
+ }
+
+ g_list_free (column_names);
+}
+
+static void
+set_columns_settings_from_metadata_and_preferences (NautilusListView *list_view)
+{
+ char **column_order;
+ char **visible_columns;
+
+ column_order = get_column_order (list_view);
+ visible_columns = get_visible_columns (list_view);
+
+ apply_columns_settings (list_view, column_order, visible_columns);
+
+ g_strfreev (column_order);
+ g_strfreev (visible_columns);
+}
+
+static void
+set_sort_order_from_metadata_and_preferences (NautilusListView *list_view)
+{
+ char *sort_attribute;
+ int sort_column_id;
+ NautilusFile *file;
+ gboolean sort_reversed, default_sort_reversed;
+ const gchar *default_sort_order;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (list_view));
+ default_sort_order = get_default_sort_order (file, &default_sort_reversed);
+ if (!(nautilus_file_is_in_recent (file) || nautilus_file_is_in_search (file)))
+ {
+ sort_attribute = nautilus_file_get_metadata (file,
+ NAUTILUS_METADATA_KEY_LIST_VIEW_SORT_COLUMN,
+ NULL);
+ sort_column_id = nautilus_list_model_get_sort_column_id_from_attribute (list_view->details->model,
+ g_quark_from_string (sort_attribute));
+ g_free (sort_attribute);
+
+ if (sort_column_id == -1)
+ {
+ sort_column_id =
+ nautilus_list_model_get_sort_column_id_from_attribute (list_view->details->model,
+ g_quark_from_string (default_sort_order));
+ }
+
+ sort_reversed = nautilus_file_get_boolean_metadata (file,
+ NAUTILUS_METADATA_KEY_LIST_VIEW_SORT_REVERSED,
+ default_sort_reversed);
+ }
+ else
+ {
+ /* Make sure we use the default one and not one that the user used previously
+ * of the change to not allow sorting on search and recent, or the
+ * case that the user or some app modified directly the metadata */
+ sort_column_id = nautilus_list_model_get_sort_column_id_from_attribute (list_view->details->model,
+ g_quark_from_string (default_sort_order));
+ sort_reversed = default_sort_reversed;
+ }
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (list_view->details->model),
+ sort_column_id,
+ sort_reversed ? GTK_SORT_DESCENDING : GTK_SORT_ASCENDING);
+}
+
+static NautilusListZoomLevel
+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);
+
+ if (default_zoom_level < NAUTILUS_LIST_ZOOM_LEVEL_SMALL
+ || default_zoom_level > NAUTILUS_LIST_ZOOM_LEVEL_LARGER)
+ {
+ default_zoom_level = NAUTILUS_LIST_ZOOM_LEVEL_STANDARD;
+ }
+
+ return default_zoom_level;
+}
+
+static void
+nautilus_list_view_begin_loading (NautilusFilesView *view)
+{
+ NautilusListView *list_view;
+
+ list_view = NAUTILUS_LIST_VIEW (view);
+
+ nautilus_list_view_sort_directories_first_changed (NAUTILUS_FILES_VIEW (list_view));
+ set_sort_order_from_metadata_and_preferences (list_view);
+ set_columns_settings_from_metadata_and_preferences (list_view);
+ check_allow_sort (list_view);
+}
+
+static void
+nautilus_list_view_clear (NautilusFilesView *view)
+{
+ NautilusListView *list_view;
+ GtkTreeView *tree_view;
+ GtkTreeSelection *tree_selection;
+ GtkTreePath *path;
+
+ list_view = NAUTILUS_LIST_VIEW (view);
+
+ if (list_view->details->model != NULL)
+ {
+ tree_view = list_view->details->tree_view;
+
+ /* When the current cursor's row gets deleted, GTK will move the cursor to
+ * the next row, and when setting the cursor it also selects the new
+ * cursor's row, thereby triggering selection signals. The new cursor will
+ * soon be deleted again and the loop repeats.
+ *
+ * Since clear() removes all entries, those selections are useless but they
+ * take up most of the time in clear(). For example, when a search returns
+ * a large list, exiting from the search view would make nautilus hang.
+ *
+ * At the time the code is written simply removing the cursor solves the
+ * problem, but to be future-proof in case GTK does anything fancy with
+ * the current selection, we also remove the selection.
+ *
+ * Because GTK internally seeking the cursor takes time, only blocking the
+ * selection signal like everywhere else will not remove that overhead.
+ */
+
+ /* Clear the current selection */
+ tree_selection = gtk_tree_view_get_selection (tree_view);
+ gtk_tree_selection_unselect_all (tree_selection);
+
+ /* Clear the current cursor */
+ path = gtk_tree_path_new ();
+ gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
+ gtk_tree_path_free (path);
+
+ nautilus_list_model_clear (list_view->details->model);
+ }
+}
+
+static void
+nautilus_list_view_file_changed (NautilusFilesView *view,
+ NautilusFile *file,
+ NautilusDirectory *directory)
+{
+ NautilusListView *listview;
+
+ listview = NAUTILUS_LIST_VIEW (view);
+
+ nautilus_list_model_file_changed (listview->details->model, file, directory);
+}
+
+typedef struct
+{
+ GtkTreePath *path;
+ gboolean is_common;
+ gboolean is_root;
+} HasCommonParentData;
+
+static void
+tree_selection_has_common_parent_foreach_func (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer user_data)
+{
+ HasCommonParentData *data;
+ GtkTreePath *parent_path;
+ gboolean has_parent;
+
+ data = (HasCommonParentData *) user_data;
+
+ parent_path = gtk_tree_path_copy (path);
+ gtk_tree_path_up (parent_path);
+
+ has_parent = (gtk_tree_path_get_depth (parent_path) > 0) ? TRUE : FALSE;
+
+ if (!has_parent)
+ {
+ data->is_root = TRUE;
+ }
+
+ if (data->is_common && !data->is_root)
+ {
+ if (data->path == NULL)
+ {
+ data->path = gtk_tree_path_copy (parent_path);
+ }
+ else if (gtk_tree_path_compare (data->path, parent_path) != 0)
+ {
+ data->is_common = FALSE;
+ }
+ }
+
+ gtk_tree_path_free (parent_path);
+}
+
+static void
+tree_selection_has_common_parent (GtkTreeSelection *selection,
+ gboolean *is_common,
+ gboolean *is_root)
+{
+ HasCommonParentData data;
+
+ g_assert (is_common != NULL);
+ g_assert (is_root != NULL);
+
+ data.path = NULL;
+ data.is_common = *is_common = TRUE;
+ data.is_root = *is_root = FALSE;
+
+ gtk_tree_selection_selected_foreach (selection,
+ tree_selection_has_common_parent_foreach_func,
+ &data);
+
+ *is_common = data.is_common;
+ *is_root = data.is_root;
+
+ if (data.path != NULL)
+ {
+ gtk_tree_path_free (data.path);
+ }
+}
+
+static char *
+nautilus_list_view_get_backing_uri (NautilusFilesView *view)
+{
+ NautilusListView *list_view;
+ NautilusListModel *list_model;
+ NautilusFile *file;
+ GtkTreeView *tree_view;
+ GtkTreeSelection *selection;
+ GtkTreePath *path;
+ GList *paths;
+ guint length;
+ char *uri;
+
+ g_return_val_if_fail (NAUTILUS_IS_LIST_VIEW (view), NULL);
+
+ list_view = NAUTILUS_LIST_VIEW (view);
+ list_model = list_view->details->model;
+ tree_view = list_view->details->tree_view;
+
+ g_assert (list_model);
+
+ /* We currently handle three common cases here:
+ * (a) if the selection contains non-filesystem items (i.e., the
+ * "(Empty)" label), we return the uri of the parent.
+ * (b) if the selection consists of exactly one _expanded_ directory, we
+ * return its URI.
+ * (c) if the selection consists of either exactly one item which is not
+ * an expanded directory) or multiple items in the same directory,
+ * we return the URI of the common parent.
+ */
+
+ uri = NULL;
+
+ selection = gtk_tree_view_get_selection (tree_view);
+ length = gtk_tree_selection_count_selected_rows (selection);
+
+ if (length == 1)
+ {
+ paths = gtk_tree_selection_get_selected_rows (selection, NULL);
+ path = (GtkTreePath *) paths->data;
+
+ file = nautilus_list_model_file_for_path (list_model, path);
+ if (file == NULL)
+ {
+ /* The selected item is a label, not a file */
+ gtk_tree_path_up (path);
+ file = nautilus_list_model_file_for_path (list_model, path);
+ }
+
+ if (file != NULL)
+ {
+ if (nautilus_file_is_directory (file) &&
+ gtk_tree_view_row_expanded (tree_view, path))
+ {
+ uri = nautilus_file_get_uri (file);
+ }
+ nautilus_file_unref (file);
+ }
+
+ gtk_tree_path_free (path);
+ g_list_free (paths);
+ }
+
+ if (uri == NULL && length > 0)
+ {
+ gboolean is_common, is_root;
+
+ /* Check that all the selected items belong to the same
+ * directory and that directory is not the root directory (which
+ * is handled by NautilusFilesView::get_backing_directory.) */
+
+ tree_selection_has_common_parent (selection, &is_common, &is_root);
+
+ if (is_common && !is_root)
+ {
+ paths = gtk_tree_selection_get_selected_rows (selection, NULL);
+ path = (GtkTreePath *) paths->data;
+
+ file = nautilus_list_model_file_for_path (list_model, path);
+ g_assert (file != NULL);
+ uri = nautilus_file_get_parent_uri (file);
+ nautilus_file_unref (file);
+
+ g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free);
+ }
+ }
+
+ if (uri != NULL)
+ {
+ return uri;
+ }
+
+ return NAUTILUS_FILES_VIEW_CLASS (nautilus_list_view_parent_class)->get_backing_uri (view);
+}
+
+static void
+nautilus_list_view_get_selection_foreach_func (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ GList **list;
+ NautilusFile *file;
+
+ list = data;
+
+ gtk_tree_model_get (model, iter,
+ NAUTILUS_LIST_MODEL_FILE_COLUMN, &file,
+ -1);
+
+ if (file != NULL)
+ {
+ (*list) = g_list_prepend ((*list), file);
+ }
+}
+
+static GList *
+nautilus_list_view_get_selection (NautilusFilesView *view)
+{
+ GList *list;
+
+ list = NULL;
+
+ gtk_tree_selection_selected_foreach (gtk_tree_view_get_selection (NAUTILUS_LIST_VIEW (view)->details->tree_view),
+ nautilus_list_view_get_selection_foreach_func, &list);
+
+ return g_list_reverse (list);
+}
+
+static void
+nautilus_list_view_get_selection_for_file_transfer_foreach_func (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ NautilusFile *file;
+ struct SelectionForeachData *selection_data;
+ GtkTreeIter parent, child;
+
+ selection_data = data;
+
+ gtk_tree_model_get (model, iter,
+ NAUTILUS_LIST_MODEL_FILE_COLUMN, &file,
+ -1);
+
+ if (file != NULL)
+ {
+ /* If the parent folder is also selected, don't include this file in the
+ * file operation, since that would copy it to the toplevel target instead
+ * of keeping it as a child of the copied folder
+ */
+ child = *iter;
+ while (gtk_tree_model_iter_parent (model, &parent, &child))
+ {
+ if (gtk_tree_selection_iter_is_selected (selection_data->selection,
+ &parent))
+ {
+ return;
+ }
+ child = parent;
+ }
+
+ nautilus_file_ref (file);
+ selection_data->list = g_list_prepend (selection_data->list, file);
+ }
+}
+
+
+static GList *
+nautilus_list_view_get_selection_for_file_transfer (NautilusFilesView *view)
+{
+ struct SelectionForeachData selection_data;
+
+ selection_data.list = NULL;
+ selection_data.selection = gtk_tree_view_get_selection (NAUTILUS_LIST_VIEW (view)->details->tree_view);
+
+ gtk_tree_selection_selected_foreach (selection_data.selection,
+ nautilus_list_view_get_selection_for_file_transfer_foreach_func, &selection_data);
+
+ return g_list_reverse (selection_data.list);
+}
+
+static gboolean
+nautilus_list_view_is_empty (NautilusFilesView *view)
+{
+ return nautilus_list_model_is_empty (NAUTILUS_LIST_VIEW (view)->details->model);
+}
+
+static void
+nautilus_list_view_end_file_changes (NautilusFilesView *view)
+{
+ NautilusListView *list_view;
+
+ list_view = NAUTILUS_LIST_VIEW (view);
+
+ if (list_view->details->new_selection_path)
+ {
+ gtk_tree_view_set_cursor (list_view->details->tree_view,
+ list_view->details->new_selection_path,
+ NULL, FALSE);
+ gtk_tree_path_free (list_view->details->new_selection_path);
+ list_view->details->new_selection_path = NULL;
+ }
+}
+
+static void
+nautilus_list_view_remove_file (NautilusFilesView *view,
+ NautilusFile *file,
+ NautilusDirectory *directory)
+{
+ GtkTreePath *path;
+ GtkTreePath *file_path;
+ GtkTreeIter iter;
+ GtkTreeIter temp_iter;
+ GtkTreeRowReference *row_reference;
+ NautilusListView *list_view;
+ GtkTreeModel *tree_model;
+ GtkTreeSelection *selection;
+
+ path = NULL;
+ row_reference = NULL;
+ list_view = NAUTILUS_LIST_VIEW (view);
+ tree_model = GTK_TREE_MODEL (list_view->details->model);
+
+ if (nautilus_list_model_get_tree_iter_from_file (list_view->details->model, file, directory, &iter))
+ {
+ selection = gtk_tree_view_get_selection (list_view->details->tree_view);
+ file_path = gtk_tree_model_get_path (tree_model, &iter);
+
+ if (gtk_tree_selection_path_is_selected (selection, file_path))
+ {
+ /* get reference for next element in the list view. If the element to be deleted is the
+ * last one, get reference to previous element. If there is only one element in view
+ * no need to select anything.
+ */
+ temp_iter = iter;
+
+ if (gtk_tree_model_iter_next (tree_model, &iter))
+ {
+ path = gtk_tree_model_get_path (tree_model, &iter);
+ row_reference = gtk_tree_row_reference_new (tree_model, path);
+ }
+ else
+ {
+ path = gtk_tree_model_get_path (tree_model, &temp_iter);
+ if (gtk_tree_path_prev (path))
+ {
+ row_reference = gtk_tree_row_reference_new (tree_model, path);
+ }
+ }
+ gtk_tree_path_free (path);
+ }
+
+ gtk_tree_path_free (file_path);
+
+ nautilus_list_model_remove_file (list_view->details->model, file, directory);
+
+ if (gtk_tree_row_reference_valid (row_reference))
+ {
+ if (list_view->details->new_selection_path)
+ {
+ gtk_tree_path_free (list_view->details->new_selection_path);
+ }
+ list_view->details->new_selection_path = gtk_tree_row_reference_get_path (row_reference);
+ }
+
+ if (row_reference)
+ {
+ gtk_tree_row_reference_free (row_reference);
+ }
+ }
+}
+
+static void
+nautilus_list_view_set_selection (NautilusFilesView *view,
+ GList *selection)
+{
+ NautilusListView *list_view;
+ NautilusListModel *model;
+ GtkTreeView *tree_view;
+ GtkTreeSelection *tree_selection;
+ GList *node;
+ gboolean cursor_is_set_on_selection = FALSE;
+ GList *iters, *l;
+ NautilusFile *file;
+
+ list_view = NAUTILUS_LIST_VIEW (view);
+ model = list_view->details->model;
+ tree_view = list_view->details->tree_view;
+ tree_selection = gtk_tree_view_get_selection (tree_view);
+
+ g_signal_handlers_block_by_func (tree_selection, list_selection_changed_callback, view);
+
+ gtk_tree_selection_unselect_all (tree_selection);
+ for (node = selection; node != NULL; node = node->next)
+ {
+ file = node->data;
+ iters = nautilus_list_model_get_all_iters_for_file (model, file);
+
+ for (l = iters; l != NULL; l = l->next)
+ {
+ if (!cursor_is_set_on_selection)
+ {
+ GtkTreePath *path;
+
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (model),
+ (GtkTreeIter *) l->data);
+ gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
+ gtk_tree_path_free (path);
+
+ cursor_is_set_on_selection = TRUE;
+ continue;
+ }
+
+ gtk_tree_selection_select_iter (tree_selection,
+ (GtkTreeIter *) l->data);
+ }
+ g_list_free_full (iters, g_free);
+ }
+
+ g_signal_handlers_unblock_by_func (tree_selection, list_selection_changed_callback, view);
+ nautilus_files_view_notify_selection_changed (view);
+}
+
+static void
+nautilus_list_view_invert_selection (NautilusFilesView *view)
+{
+ NautilusListView *list_view;
+ GtkTreeSelection *tree_selection;
+ GList *node;
+ GList *iters, *l;
+ NautilusFile *file;
+ GList *selection = NULL;
+
+ list_view = NAUTILUS_LIST_VIEW (view);
+ tree_selection = gtk_tree_view_get_selection (list_view->details->tree_view);
+
+ g_signal_handlers_block_by_func (tree_selection, list_selection_changed_callback, view);
+
+ gtk_tree_selection_selected_foreach (tree_selection,
+ nautilus_list_view_get_selection_foreach_func, &selection);
+
+ gtk_tree_selection_select_all (tree_selection);
+
+ for (node = selection; node != NULL; node = node->next)
+ {
+ file = node->data;
+ iters = nautilus_list_model_get_all_iters_for_file (list_view->details->model, file);
+
+ for (l = iters; l != NULL; l = l->next)
+ {
+ gtk_tree_selection_unselect_iter (tree_selection,
+ (GtkTreeIter *) l->data);
+ }
+ g_list_free_full (iters, g_free);
+ }
+
+ g_list_free (selection);
+
+ g_signal_handlers_unblock_by_func (tree_selection, list_selection_changed_callback, view);
+ nautilus_files_view_notify_selection_changed (view);
+}
+
+static void
+nautilus_list_view_select_all (NautilusFilesView *view)
+{
+ gtk_tree_selection_select_all (gtk_tree_view_get_selection (NAUTILUS_LIST_VIEW (view)->details->tree_view));
+}
+
+static void
+nautilus_list_view_select_first (NautilusFilesView *view)
+{
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+
+ if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (NAUTILUS_LIST_VIEW (view)->details->model), &iter))
+ {
+ return;
+ }
+ selection = gtk_tree_view_get_selection (NAUTILUS_LIST_VIEW (view)->details->tree_view);
+ gtk_tree_selection_unselect_all (selection);
+ gtk_tree_selection_select_iter (selection, &iter);
+}
+
+static void
+nautilus_list_view_zoom_to_level (NautilusFilesView *view,
+ gint zoom_level)
+{
+ NautilusListView *list_view;
+
+ g_return_if_fail (NAUTILUS_IS_LIST_VIEW (view));
+
+ list_view = NAUTILUS_LIST_VIEW (view);
+
+ if (list_view->details->zoom_level == zoom_level)
+ {
+ return;
+ }
+
+ nautilus_list_view_set_zoom_level (list_view, zoom_level);
+ g_action_group_change_action_state (nautilus_files_view_get_action_group (view),
+ "zoom-to-level", g_variant_new_int32 (zoom_level));
+
+ nautilus_files_view_update_toolbar_menus (view);
+}
+
+static void
+action_zoom_to_level (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusFilesView *view;
+ NautilusListZoomLevel zoom_level;
+
+ g_assert (NAUTILUS_IS_FILES_VIEW (user_data));
+
+ view = NAUTILUS_FILES_VIEW (user_data);
+ zoom_level = g_variant_get_int32 (state);
+ nautilus_list_view_zoom_to_level (view, 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);
+ }
+}
+
+static void
+column_editor_response_callback (GtkWidget *dialog,
+ int response_id,
+ gpointer user_data)
+{
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+static void
+column_chooser_changed_callback (NautilusColumnChooser *chooser,
+ NautilusListView *view)
+{
+ NautilusFile *file;
+ char **visible_columns;
+ char **column_order;
+ GList *list;
+ int i;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (view));
+
+ nautilus_column_chooser_get_settings (chooser,
+ &visible_columns,
+ &column_order);
+
+ list = NULL;
+ for (i = 0; visible_columns[i] != NULL; ++i)
+ {
+ list = g_list_prepend (list, visible_columns[i]);
+ }
+ list = g_list_reverse (list);
+ nautilus_file_set_metadata_list (file,
+ NAUTILUS_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS,
+ list);
+ g_list_free (list);
+
+ list = NULL;
+ for (i = 0; column_order[i] != NULL; ++i)
+ {
+ list = g_list_prepend (list, column_order[i]);
+ }
+ list = g_list_reverse (list);
+ nautilus_file_set_metadata_list (file,
+ NAUTILUS_METADATA_KEY_LIST_VIEW_COLUMN_ORDER,
+ list);
+ g_list_free (list);
+
+ 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)
+{
+ GtkWidget *window;
+ GtkWidget *label;
+ GtkWidget *box;
+ GtkWidget *column_chooser;
+ NautilusFile *file;
+ char *str;
+ char *name;
+ const char *label_text;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (view));
+ name = nautilus_file_get_display_name (file);
+ str = g_strdup_printf (_("%s Visible Columns"), name);
+ g_free (name);
+
+ window = gtk_dialog_new_with_buttons (str,
+ GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (view))),
+ GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_USE_HEADER_BAR,
+ NULL, NULL);
+ g_free (str);
+ g_signal_connect (window, "response",
+ G_CALLBACK (column_editor_response_callback), NULL);
+
+ gtk_window_set_default_size (GTK_WINDOW (window), 300, 400);
+
+ box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
+ gtk_container_set_border_width (GTK_CONTAINER (box), 12);
+ gtk_widget_set_hexpand (box, TRUE);
+ gtk_widget_show (box);
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (window))), box,
+ TRUE, TRUE, 0);
+
+ label_text = _("Choose the order of information to appear in this folder:");
+ str = g_strconcat ("<b>", label_text, "</b>", NULL);
+ label = gtk_label_new (NULL);
+ gtk_label_set_markup (GTK_LABEL (label), str);
+ gtk_label_set_line_wrap (GTK_LABEL (label), FALSE);
+ gtk_label_set_xalign (GTK_LABEL (label), 0);
+ gtk_label_set_yalign (GTK_LABEL (label), 0);
+ gtk_widget_show (label);
+ gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
+
+ g_free (str);
+
+ column_chooser = nautilus_column_chooser_new (file);
+ gtk_widget_show (column_chooser);
+ gtk_box_pack_start (GTK_BOX (box), column_chooser, TRUE, TRUE, 0);
+
+ 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 *list_view;
+
+ list_view = NAUTILUS_LIST_VIEW (user_data);
+
+ if (list_view->details->column_editor)
+ {
+ gtk_window_present (GTK_WINDOW (list_view->details->column_editor));
+ }
+ else
+ {
+ list_view->details->column_editor = create_column_editor (list_view);
+ g_object_add_weak_pointer (G_OBJECT (list_view->details->column_editor),
+ (gpointer *) &list_view->details->column_editor);
+
+ gtk_widget_show (list_view->details->column_editor);
+ }
+}
+
+const GActionEntry list_view_entries[] =
+{
+ { "visible-columns", action_visible_columns },
+ { "zoom-to-level", NULL, NULL, "1", action_zoom_to_level }
+};
+
+static void
+nautilus_list_view_set_zoom_level (NautilusListView *view,
+ NautilusListZoomLevel new_level)
+{
+ int column;
+
+ g_return_if_fail (NAUTILUS_IS_LIST_VIEW (view));
+ g_return_if_fail (new_level >= NAUTILUS_LIST_ZOOM_LEVEL_SMALL &&
+ new_level <= NAUTILUS_LIST_ZOOM_LEVEL_LARGER);
+
+ if (view->details->zoom_level == new_level)
+ {
+ return;
+ }
+
+ view->details->zoom_level = new_level;
+
+ /* Select correctly scaled icons. */
+ column = nautilus_list_model_get_column_id_from_zoom_level (new_level);
+ gtk_tree_view_column_set_attributes (view->details->file_name_column,
+ GTK_CELL_RENDERER (view->details->pixbuf_cell),
+ "surface", column,
+ NULL);
+ set_up_pixbuf_size (view);
+}
+
+static void
+nautilus_list_view_bump_zoom_level (NautilusFilesView *view,
+ int zoom_increment)
+{
+ NautilusListView *list_view;
+ gint new_level;
+
+ g_return_if_fail (NAUTILUS_IS_LIST_VIEW (view));
+
+ list_view = NAUTILUS_LIST_VIEW (view);
+ new_level = list_view->details->zoom_level + zoom_increment;
+
+ if (new_level >= NAUTILUS_LIST_ZOOM_LEVEL_SMALL &&
+ new_level <= NAUTILUS_LIST_ZOOM_LEVEL_LARGER)
+ {
+ nautilus_list_view_zoom_to_level (view, new_level);
+ }
+}
+
+static void
+nautilus_list_view_restore_standard_zoom_level (NautilusFilesView *view)
+{
+ nautilus_list_view_zoom_to_level (view, NAUTILUS_LIST_ZOOM_LEVEL_STANDARD);
+}
+
+static gboolean
+nautilus_list_view_can_zoom_in (NautilusFilesView *view)
+{
+ g_return_val_if_fail (NAUTILUS_IS_LIST_VIEW (view), FALSE);
+
+ return NAUTILUS_LIST_VIEW (view)->details->zoom_level < NAUTILUS_LIST_ZOOM_LEVEL_LARGER;
+}
+
+static gboolean
+nautilus_list_view_can_zoom_out (NautilusFilesView *view)
+{
+ g_return_val_if_fail (NAUTILUS_IS_LIST_VIEW (view), FALSE);
+
+ return NAUTILUS_LIST_VIEW (view)->details->zoom_level > NAUTILUS_LIST_ZOOM_LEVEL_SMALL;
+}
+
+static gfloat
+nautilus_list_view_get_zoom_level_percentage (NautilusFilesView *view)
+{
+ NautilusListView *list_view;
+ guint icon_size;
+
+ g_return_val_if_fail (NAUTILUS_IS_LIST_VIEW (view), 1.0);
+
+ list_view = NAUTILUS_LIST_VIEW (view);
+ icon_size = nautilus_list_model_get_icon_size_for_zoom_level (list_view->details->zoom_level);
+
+ return (gfloat) icon_size / NAUTILUS_LIST_ICON_SIZE_STANDARD;
+}
+
+static gboolean
+nautilus_list_view_is_zoom_level_default (NautilusFilesView *view)
+{
+ NautilusListView *list_view;
+ guint icon_size;
+
+ list_view = NAUTILUS_LIST_VIEW (view);
+ icon_size = nautilus_list_model_get_icon_size_for_zoom_level (list_view->details->zoom_level);
+
+ return icon_size == NAUTILUS_LIST_ICON_SIZE_STANDARD;
+}
+
+static void
+nautilus_list_view_click_policy_changed (NautilusFilesView *directory_view)
+{
+ GdkWindow *win;
+ GdkDisplay *display;
+ NautilusListView *view;
+ GtkTreeIter iter;
+ GtkTreeView *tree;
+
+ view = NAUTILUS_LIST_VIEW (directory_view);
+ display = gtk_widget_get_display (GTK_WIDGET (view));
+
+ /* ensure that we unset the hand cursor and refresh underlined rows */
+ if (get_click_policy () == NAUTILUS_CLICK_POLICY_DOUBLE)
+ {
+ if (view->details->hover_path != NULL)
+ {
+ if (gtk_tree_model_get_iter (GTK_TREE_MODEL (view->details->model),
+ &iter, view->details->hover_path))
+ {
+ gtk_tree_model_row_changed (GTK_TREE_MODEL (view->details->model),
+ view->details->hover_path, &iter);
+ }
+
+ gtk_tree_path_free (view->details->hover_path);
+ view->details->hover_path = NULL;
+ }
+
+ tree = view->details->tree_view;
+ if (gtk_widget_get_realized (GTK_WIDGET (tree)))
+ {
+ win = gtk_widget_get_window (GTK_WIDGET (tree));
+ gdk_window_set_cursor (win, NULL);
+
+ if (display != NULL)
+ {
+ gdk_display_flush (display);
+ }
+ }
+
+ g_clear_object (&hand_cursor);
+ }
+ else if (get_click_policy () == NAUTILUS_CLICK_POLICY_SINGLE)
+ {
+ if (hand_cursor == NULL)
+ {
+ hand_cursor = gdk_cursor_new_for_display (display, GDK_HAND2);
+ }
+ }
+}
+
+static void
+default_sort_order_changed_callback (gpointer callback_data)
+{
+ NautilusListView *list_view;
+
+ list_view = NAUTILUS_LIST_VIEW (callback_data);
+
+ set_sort_order_from_metadata_and_preferences (list_view);
+}
+
+static void
+default_visible_columns_changed_callback (gpointer callback_data)
+{
+ NautilusListView *list_view;
+
+ list_view = NAUTILUS_LIST_VIEW (callback_data);
+
+ set_columns_settings_from_metadata_and_preferences (list_view);
+}
+
+static void
+default_column_order_changed_callback (gpointer callback_data)
+{
+ NautilusListView *list_view;
+
+ list_view = NAUTILUS_LIST_VIEW (callback_data);
+
+ set_columns_settings_from_metadata_and_preferences (list_view);
+}
+
+static void
+nautilus_list_view_sort_directories_first_changed (NautilusFilesView *view)
+{
+ NautilusListView *list_view;
+
+ list_view = NAUTILUS_LIST_VIEW (view);
+
+ nautilus_list_model_set_should_sort_directories_first (list_view->details->model,
+ nautilus_files_view_should_sort_directories_first (view));
+}
+
+static int
+nautilus_list_view_compare_files (NautilusFilesView *view,
+ NautilusFile *file1,
+ NautilusFile *file2)
+{
+ NautilusListView *list_view;
+
+ list_view = NAUTILUS_LIST_VIEW (view);
+ return nautilus_list_model_compare_func (list_view->details->model, file1, file2);
+}
+
+static void
+nautilus_list_view_dispose (GObject *object)
+{
+ NautilusListView *list_view;
+ GtkClipboard *clipboard;
+
+ list_view = NAUTILUS_LIST_VIEW (object);
+
+ if (list_view->details->model)
+ {
+ g_object_unref (list_view->details->model);
+ list_view->details->model = NULL;
+ }
+
+ if (list_view->details->drag_dest)
+ {
+ g_object_unref (list_view->details->drag_dest);
+ list_view->details->drag_dest = NULL;
+ }
+
+ clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+ g_signal_handlers_disconnect_by_func (clipboard, on_clipboard_owner_changed, list_view);
+ g_signal_handlers_disconnect_by_func (nautilus_preferences,
+ default_sort_order_changed_callback,
+ list_view);
+ g_signal_handlers_disconnect_by_func (nautilus_list_view_preferences,
+ default_visible_columns_changed_callback,
+ list_view);
+ g_signal_handlers_disconnect_by_func (nautilus_list_view_preferences,
+ default_column_order_changed_callback,
+ list_view);
+
+ g_clear_object (&list_view->details->tree_view_drag_gesture);
+ g_clear_object (&list_view->details->tree_view_multi_press_gesture);
+
+ G_OBJECT_CLASS (nautilus_list_view_parent_class)->dispose (object);
+}
+
+static void
+nautilus_list_view_finalize (GObject *object)
+{
+ NautilusListView *list_view;
+
+ list_view = NAUTILUS_LIST_VIEW (object);
+
+ g_free (list_view->details->original_name);
+ list_view->details->original_name = NULL;
+
+ if (list_view->details->first_click_path)
+ {
+ gtk_tree_path_free (list_view->details->first_click_path);
+ }
+ if (list_view->details->new_selection_path)
+ {
+ gtk_tree_path_free (list_view->details->new_selection_path);
+ }
+
+ g_list_free (list_view->details->cells);
+ g_hash_table_destroy (list_view->details->columns);
+
+ if (list_view->details->hover_path != NULL)
+ {
+ gtk_tree_path_free (list_view->details->hover_path);
+ }
+
+ if (list_view->details->column_editor != NULL)
+ {
+ gtk_widget_destroy (list_view->details->column_editor);
+ }
+
+ g_regex_unref (list_view->details->regex);
+
+ g_cancellable_cancel (list_view->details->starred_cancellable);
+ g_clear_object (&list_view->details->starred_cancellable);
+
+ g_signal_handlers_disconnect_by_func (list_view->details->tag_manager,
+ on_starred_files_changed,
+ list_view);
+
+ g_free (list_view->details);
+
+ G_OBJECT_CLASS (nautilus_list_view_parent_class)->finalize (object);
+}
+
+static char *
+nautilus_list_view_get_first_visible_file (NautilusFilesView *view)
+{
+ NautilusFile *file;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ NautilusListView *list_view;
+
+ list_view = NAUTILUS_LIST_VIEW (view);
+
+ if (gtk_tree_view_get_path_at_pos (list_view->details->tree_view,
+ 0, 0,
+ &path, NULL, NULL, NULL))
+ {
+ gtk_tree_model_get_iter (GTK_TREE_MODEL (list_view->details->model),
+ &iter, path);
+
+ gtk_tree_path_free (path);
+
+ gtk_tree_model_get (GTK_TREE_MODEL (list_view->details->model),
+ &iter,
+ NAUTILUS_LIST_MODEL_FILE_COLUMN, &file,
+ -1);
+ if (file)
+ {
+ char *uri;
+
+ uri = nautilus_file_get_uri (file);
+
+ nautilus_file_unref (file);
+
+ return uri;
+ }
+ }
+
+ return NULL;
+}
+
+static void
+nautilus_list_view_scroll_to_file (NautilusListView *view,
+ NautilusFile *file)
+{
+ GtkTreePath *path;
+ GtkTreeIter iter;
+
+ if (!nautilus_list_model_get_first_iter_for_file (view->details->model, file, &iter))
+ {
+ return;
+ }
+
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (view->details->model), &iter);
+
+ gtk_tree_view_scroll_to_cell (view->details->tree_view,
+ path, NULL,
+ TRUE, 0.0, 0.0);
+
+ gtk_tree_path_free (path);
+}
+
+static void
+list_view_scroll_to_file (NautilusFilesView *view,
+ const char *uri)
+{
+ NautilusFile *file;
+
+ if (uri != NULL)
+ {
+ /* Only if existing, since we don't want to add the file to
+ * the directory if it has been removed since then */
+ file = nautilus_file_get_existing_by_uri (uri);
+ if (file != NULL)
+ {
+ nautilus_list_view_scroll_to_file (NAUTILUS_LIST_VIEW (view), file);
+ nautilus_file_unref (file);
+ }
+ }
+}
+
+static void
+on_clipboard_contents_received (GtkClipboard *clipboard,
+ const gchar *selection_data,
+ gpointer user_data)
+{
+ NautilusListView *view = NAUTILUS_LIST_VIEW (user_data);
+
+ if (!view->details->model)
+ {
+ /* We've been destroyed since call */
+ g_object_unref (view);
+ return;
+ }
+
+ if (nautilus_clipboard_is_cut_from_selection_data (selection_data))
+ {
+ GList *uris;
+ GList *files;
+
+ uris = nautilus_clipboard_get_uri_list_from_selection_data (selection_data);
+ files = nautilus_file_list_from_uri_list (uris);
+ nautilus_list_model_set_highlight_for_files (view->details->model, files);
+
+ nautilus_file_list_free (files);
+ g_list_free_full (uris, g_free);
+ }
+ else
+ {
+ nautilus_list_model_set_highlight_for_files (view->details->model, NULL);
+ }
+
+ g_object_unref (view);
+}
+
+static void
+update_clipboard_status (NautilusListView *view)
+{
+ g_object_ref (view); /* Need to keep the object alive until we get the reply */
+ gtk_clipboard_request_text (nautilus_clipboard_get (GTK_WIDGET (view)),
+ on_clipboard_contents_received,
+ view);
+}
+
+static void
+on_clipboard_owner_changed (GtkClipboard *clipboard,
+ GdkEvent *event,
+ gpointer user_data)
+{
+ update_clipboard_status (NAUTILUS_LIST_VIEW (user_data));
+}
+
+static void
+nautilus_list_view_end_loading (NautilusFilesView *view,
+ gboolean all_files_seen)
+{
+ update_clipboard_status (NAUTILUS_LIST_VIEW (view));
+}
+
+static guint
+nautilus_list_view_get_id (NautilusFilesView *view)
+{
+ return NAUTILUS_VIEW_LIST_ID;
+}
+
+static GdkRectangle *
+get_rectangle_for_path (NautilusListView *list_view,
+ GtkTreePath *path)
+{
+ GtkTreeView *tree_view = list_view->details->tree_view;
+ GdkRectangle *rect = g_malloc0 (sizeof (GdkRectangle));
+ int header_height;
+
+ gtk_tree_view_get_cell_area (tree_view,
+ path,
+ list_view->details->file_name_column,
+ rect);
+ gtk_tree_view_convert_bin_window_to_widget_coords (tree_view,
+ rect->x, rect->y,
+ &rect->x, &rect->y);
+
+ /* FIXME Due to smooth scrolling, we may get the cell area while the view is
+ * still scrolling (and still outside the view), not at the final position
+ * of the cell after scrolling.
+ * https://bugzilla.gnome.org/show_bug.cgi?id=746773
+ * The following workaround guesses the final "y" coordinate by clamping it
+ * to the widget edge. Note that the top edge has got columns header, which
+ * is private, so first guess the header height from the difference between
+ * widget coordinates and bin cooridinates.
+ */
+ gtk_tree_view_convert_bin_window_to_widget_coords (tree_view,
+ 0, 0,
+ NULL, &header_height);
+
+ rect->y = CLAMP (rect->y,
+ header_height,
+ gtk_widget_get_allocated_height (GTK_WIDGET (list_view)) - rect->height);
+ /* End of workaround */
+
+ return rect;
+}
+
+static GdkRectangle *
+nautilus_list_view_compute_rename_popover_pointing_to (NautilusFilesView *view)
+{
+ NautilusListView *list_view;
+ GtkTreeView *tree_view;
+ GtkTreeSelection *selection;
+ GList *list;
+ GtkTreePath *path;
+ GdkRectangle *rect;
+
+ list_view = NAUTILUS_LIST_VIEW (view);
+ tree_view = list_view->details->tree_view;
+ selection = gtk_tree_view_get_selection (tree_view);
+ list = gtk_tree_selection_get_selected_rows (selection, NULL);
+ path = list->data;
+ rect = get_rectangle_for_path (list_view, path);
+
+ if (list_view->details->last_event_button_x > 0)
+ {
+ /* Point to the position in the row where it was clicked. */
+ rect->x = list_view->details->last_event_button_x;
+ /* Make it zero width to point exactly at rect->x.*/
+ rect->width = 0;
+ }
+
+ g_list_free_full (list, (GDestroyNotify) gtk_tree_path_free);
+
+ return rect;
+}
+
+static GdkRectangle *
+nautilus_list_view_reveal_for_selection_context_menu (NautilusFilesView *view)
+{
+ NautilusListView *list_view;
+ GtkTreeView *tree_view;
+ GtkTreeSelection *tree_selection;
+ GtkTreePath *path;
+ GdkRectangle *rect;
+
+ g_return_val_if_fail (NAUTILUS_IS_LIST_VIEW (view), NULL);
+
+ list_view = NAUTILUS_LIST_VIEW (view);
+ tree_view = list_view->details->tree_view;
+ tree_selection = gtk_tree_view_get_selection (tree_view);
+ g_return_val_if_fail (tree_selection != NULL, NULL);
+
+ /* Get the path to the last focused item, if selected. Otherwise, get
+ * the path to the selected item which is sorted the lowest.
+ */
+ gtk_tree_view_get_cursor (tree_view, &path, NULL);
+ if (path == NULL || !gtk_tree_selection_path_is_selected (tree_selection, path))
+ {
+ GList *list;
+
+ list = gtk_tree_selection_get_selected_rows (tree_selection, NULL);
+ list = g_list_last (list);
+ path = g_steal_pointer (&list->data);
+
+ g_list_free_full (list, (GDestroyNotify) gtk_tree_path_free);
+ }
+
+ gtk_tree_view_scroll_to_cell (tree_view, path, NULL, FALSE, 0.0, 0.0);
+
+ rect = get_rectangle_for_path (list_view, path);
+
+ gtk_tree_path_free (path);
+
+ return rect;
+}
+
+static void
+nautilus_list_view_preview_selection_event (NautilusFilesView *view,
+ GtkDirectionType direction)
+{
+ NautilusListView *list_view;
+ GtkTreeView *tree_view;
+ GtkTreeSelection *selection;
+ GList *list;
+ GtkTreeIter iter;
+ GtkTreePath *path;
+ GtkTreeModel *tree_model;
+ gboolean moved;
+
+ /* We only support up and down movements for the list view */
+ if (direction != GTK_DIR_UP && direction != GTK_DIR_DOWN)
+ {
+ return;
+ }
+
+ list_view = NAUTILUS_LIST_VIEW (view);
+ tree_view = list_view->details->tree_view;
+ selection = gtk_tree_view_get_selection (tree_view);
+ list = gtk_tree_selection_get_selected_rows (selection, &tree_model);
+
+ if (list == NULL)
+ {
+ return;
+ }
+
+ /* Advance the first selected item, since that's what we use for
+ * the previewer */
+ path = list->data;
+ moved = FALSE;
+ if (gtk_tree_model_get_iter (tree_model, &iter, path))
+ {
+ if (direction == GTK_DIR_UP)
+ {
+ moved = gtk_tree_model_iter_previous (tree_model, &iter);
+ }
+ else
+ {
+ moved = gtk_tree_model_iter_next (tree_model, &iter);
+ }
+ }
+
+ if (moved)
+ {
+ g_signal_handlers_block_by_func (selection, list_selection_changed_callback, view);
+
+ gtk_tree_selection_unselect_all (selection);
+ gtk_tree_selection_select_iter (selection, &iter);
+
+ g_signal_handlers_unblock_by_func (selection, list_selection_changed_callback, view);
+ nautilus_files_view_notify_selection_changed (view);
+ }
+
+ g_list_free_full (list, (GDestroyNotify) gtk_tree_path_free);
+}
+
+static void
+nautilus_list_view_class_init (NautilusListViewClass *class)
+{
+ NautilusFilesViewClass *nautilus_files_view_class;
+
+ nautilus_files_view_class = NAUTILUS_FILES_VIEW_CLASS (class);
+
+ G_OBJECT_CLASS (class)->dispose = nautilus_list_view_dispose;
+ G_OBJECT_CLASS (class)->finalize = nautilus_list_view_finalize;
+
+ nautilus_files_view_class->add_files = nautilus_list_view_add_files;
+ nautilus_files_view_class->begin_loading = nautilus_list_view_begin_loading;
+ nautilus_files_view_class->end_loading = nautilus_list_view_end_loading;
+ nautilus_files_view_class->bump_zoom_level = nautilus_list_view_bump_zoom_level;
+ nautilus_files_view_class->can_zoom_in = nautilus_list_view_can_zoom_in;
+ nautilus_files_view_class->can_zoom_out = nautilus_list_view_can_zoom_out;
+ nautilus_files_view_class->get_zoom_level_percentage = nautilus_list_view_get_zoom_level_percentage;
+ nautilus_files_view_class->is_zoom_level_default = nautilus_list_view_is_zoom_level_default;
+ nautilus_files_view_class->click_policy_changed = nautilus_list_view_click_policy_changed;
+ nautilus_files_view_class->clear = nautilus_list_view_clear;
+ nautilus_files_view_class->file_changed = nautilus_list_view_file_changed;
+ nautilus_files_view_class->get_backing_uri = nautilus_list_view_get_backing_uri;
+ nautilus_files_view_class->get_selection = nautilus_list_view_get_selection;
+ nautilus_files_view_class->get_selection_for_file_transfer = nautilus_list_view_get_selection_for_file_transfer;
+ nautilus_files_view_class->is_empty = nautilus_list_view_is_empty;
+ nautilus_files_view_class->remove_file = nautilus_list_view_remove_file;
+ nautilus_files_view_class->restore_standard_zoom_level = nautilus_list_view_restore_standard_zoom_level;
+ nautilus_files_view_class->reveal_selection = nautilus_list_view_reveal_selection;
+ nautilus_files_view_class->select_all = nautilus_list_view_select_all;
+ nautilus_files_view_class->select_first = nautilus_list_view_select_first;
+ nautilus_files_view_class->set_selection = nautilus_list_view_set_selection;
+ nautilus_files_view_class->invert_selection = nautilus_list_view_invert_selection;
+ nautilus_files_view_class->compare_files = nautilus_list_view_compare_files;
+ nautilus_files_view_class->sort_directories_first_changed = nautilus_list_view_sort_directories_first_changed;
+ nautilus_files_view_class->end_file_changes = nautilus_list_view_end_file_changes;
+ nautilus_files_view_class->get_view_id = nautilus_list_view_get_id;
+ nautilus_files_view_class->get_first_visible_file = nautilus_list_view_get_first_visible_file;
+ nautilus_files_view_class->scroll_to_file = list_view_scroll_to_file;
+ nautilus_files_view_class->compute_rename_popover_pointing_to = nautilus_list_view_compute_rename_popover_pointing_to;
+ nautilus_files_view_class->reveal_for_selection_context_menu = nautilus_list_view_reveal_for_selection_context_menu;
+ nautilus_files_view_class->preview_selection_event = nautilus_list_view_preview_selection_event;
+}
+
+static void
+nautilus_list_view_init (NautilusListView *list_view)
+{
+ GActionGroup *view_action_group;
+ GtkClipboard *clipboard;
+
+ list_view->details = g_new0 (NautilusListViewDetails, 1);
+
+ /* ensure that the zoom level is always set before settings up the tree view columns */
+ list_view->details->zoom_level = get_default_zoom_level ();
+
+ create_and_set_up_tree_view (list_view);
+
+ gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (list_view)),
+ "nautilus-list-view");
+
+ g_signal_connect_swapped (nautilus_preferences,
+ "changed::" NAUTILUS_PREFERENCES_DEFAULT_SORT_ORDER,
+ G_CALLBACK (default_sort_order_changed_callback),
+ list_view);
+ g_signal_connect_swapped (nautilus_preferences,
+ "changed::" NAUTILUS_PREFERENCES_DEFAULT_SORT_IN_REVERSE_ORDER,
+ G_CALLBACK (default_sort_order_changed_callback),
+ list_view);
+ g_signal_connect_swapped (nautilus_list_view_preferences,
+ "changed::" NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS,
+ G_CALLBACK (default_visible_columns_changed_callback),
+ list_view);
+ g_signal_connect_swapped (nautilus_list_view_preferences,
+ "changed::" NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_COLUMN_ORDER,
+ G_CALLBACK (default_column_order_changed_callback),
+ list_view);
+
+ /* React to clipboard changes */
+ clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+ g_signal_connect (clipboard, "owner-change",
+ G_CALLBACK (on_clipboard_owner_changed), list_view);
+
+ nautilus_list_view_click_policy_changed (NAUTILUS_FILES_VIEW (list_view));
+
+ nautilus_list_view_set_zoom_level (list_view, get_default_zoom_level ());
+
+ list_view->details->hover_path = NULL;
+
+ view_action_group = nautilus_files_view_get_action_group (NAUTILUS_FILES_VIEW (list_view));
+ g_action_map_add_action_entries (G_ACTION_MAP (view_action_group),
+ list_view_entries,
+ G_N_ELEMENTS (list_view_entries),
+ list_view);
+ /* 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 (list_view)),
+ "zoom-to-level", g_variant_new_int32 (get_default_zoom_level ()));
+
+ list_view->details->regex = g_regex_new ("\\R+", 0, G_REGEX_MATCH_NEWLINE_ANY, NULL);
+
+ list_view->details->tag_manager = nautilus_tag_manager_get ();
+ list_view->details->starred_cancellable = g_cancellable_new ();
+
+ g_signal_connect (list_view->details->tag_manager,
+ "starred-changed",
+ (GCallback) on_starred_files_changed,
+ list_view);
+}
+
+NautilusFilesView *
+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..7e19621
--- /dev/null
+++ b/src/nautilus-list-view.h
@@ -0,0 +1,40 @@
+
+/* fm-list-view.h - interface for list view of directory.
+
+ Copyright (C) 2000 Eazel, Inc.
+ Copyright (C) 2001 Anders Carlsson <andersca@gnu.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: John Sullivan <sullivan@eazel.com>
+ Anders Carlsson <andersca@gnu.org>
+*/
+
+#pragma once
+
+#include "nautilus-files-view.h"
+
+#define NAUTILUS_TYPE_LIST_VIEW (nautilus_list_view_get_type ())
+G_DECLARE_FINAL_TYPE (NautilusListView, nautilus_list_view, NAUTILUS, LIST_VIEW, NautilusFilesView)
+
+typedef struct NautilusListViewDetails NautilusListViewDetails;
+
+struct _NautilusListView
+{
+ NautilusFilesView parent_instance;
+ NautilusListViewDetails *details;
+};
+
+NautilusFilesView * nautilus_list_view_new (NautilusWindowSlot *slot); \ No newline at end of file
diff --git a/src/nautilus-location-entry.c b/src/nautilus-location-entry.c
new file mode 100644
index 0000000..05bc6fb
--- /dev/null
+++ b/src/nautilus-location-entry.c
@@ -0,0 +1,938 @@
+/*
+ * 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>
+
+#define NAUTILUS_DND_URI_LIST_TYPE "text/uri-list"
+#define NAUTILUS_DND_TEXT_PLAIN_TYPE "text/plain"
+
+enum
+{
+ NAUTILUS_DND_URI_LIST,
+ NAUTILUS_DND_TEXT_PLAIN,
+ NAUTILUS_DND_NTARGETS
+};
+
+static const GtkTargetEntry drag_types [] =
+{
+ { NAUTILUS_DND_URI_LIST_TYPE, 0, NAUTILUS_DND_URI_LIST },
+ { NAUTILUS_DND_TEXT_PLAIN_TYPE, 0, NAUTILUS_DND_TEXT_PLAIN },
+};
+
+static const GtkTargetEntry drop_types [] =
+{
+ { NAUTILUS_DND_URI_LIST_TYPE, 0, NAUTILUS_DND_URI_LIST },
+ { NAUTILUS_DND_TEXT_PLAIN_TYPE, 0, NAUTILUS_DND_TEXT_PLAIN },
+};
+
+typedef struct _NautilusLocationEntryPrivate
+{
+ char *current_directory;
+ GFilenameCompleter *completer;
+
+ guint idle_id;
+
+ GFile *last_location;
+
+ gboolean has_special_text;
+ gboolean setting_special_text;
+ gchar *special_text;
+ NautilusLocationEntryAction secondary_action;
+} 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 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
+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_entry_get_text (GTK_ENTRY (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);
+
+ gtk_entry_set_text (GTK_ENTRY (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);
+
+ g_free (uri);
+ g_free (formatted_uri);
+}
+
+static void
+drag_data_received_callback (GtkWidget *widget,
+ GdkDragContext *context,
+ int x,
+ int y,
+ GtkSelectionData *data,
+ guint info,
+ guint32 time,
+ gpointer callback_data)
+{
+ char **names;
+ int name_count;
+ GtkWidget *window;
+ gboolean new_windows_for_extras;
+ char *prompt;
+ char *detail;
+ GFile *location;
+ NautilusLocationEntry *self = NAUTILUS_LOCATION_ENTRY (widget);
+
+ g_assert (data != NULL);
+ g_assert (callback_data == NULL);
+
+ names = g_uri_list_extract_uris ((const gchar *) gtk_selection_data_get_data (data));
+
+ if (names == NULL || *names == NULL)
+ {
+ g_warning ("No D&D URI's");
+ gtk_drag_finish (context, FALSE, FALSE, time);
+ return;
+ }
+
+ window = gtk_widget_get_toplevel (widget);
+ new_windows_for_extras = FALSE;
+ /* Ask user if they really want to open multiple windows
+ * for multiple dropped URIs. This is likely to have been
+ * a mistake.
+ */
+ name_count = g_strv_length (names);
+ if (name_count > 1)
+ {
+ prompt = g_strdup_printf (ngettext ("Do you want to view %d location?",
+ "Do you want to view %d locations?",
+ name_count),
+ name_count);
+ detail = g_strdup_printf (ngettext ("This will open %d separate window.",
+ "This will open %d separate windows.",
+ name_count),
+ name_count);
+ /* eel_run_simple_dialog should really take in pairs
+ * like gtk_dialog_new_with_buttons() does. */
+ new_windows_for_extras = eel_run_simple_dialog (GTK_WIDGET (window),
+ TRUE,
+ GTK_MESSAGE_QUESTION,
+ prompt,
+ detail,
+ _("_Cancel"), _("_OK"),
+ NULL) != 0 /* GNOME_OK */;
+
+ g_free (prompt);
+ g_free (detail);
+
+ if (!new_windows_for_extras)
+ {
+ gtk_drag_finish (context, FALSE, FALSE, time);
+ return;
+ }
+ }
+
+ location = g_file_new_for_uri (names[0]);
+ nautilus_location_entry_set_location (self, location);
+ emit_location_changed (self);
+ g_object_unref (location);
+
+ if (new_windows_for_extras)
+ {
+ int i;
+
+ for (i = 1; names[i] != NULL; ++i)
+ {
+ location = g_file_new_for_uri (names[i]);
+ nautilus_application_open_location_full (NAUTILUS_APPLICATION (g_application_get_default ()),
+ location, NAUTILUS_WINDOW_OPEN_FLAG_NEW_WINDOW, NULL, NULL, NULL);
+ g_object_unref (location);
+ }
+ }
+
+ g_strfreev (names);
+
+ gtk_drag_finish (context, TRUE, FALSE, time);
+}
+
+static void
+drag_data_get_callback (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint32 time,
+ gpointer callback_data)
+{
+ NautilusLocationEntry *self;
+ GFile *location;
+ gchar *uri;
+
+ g_assert (selection_data != NULL);
+ self = callback_data;
+
+ location = nautilus_location_entry_get_location (self);
+ uri = g_file_get_uri (location);
+
+ switch (info)
+ {
+ case NAUTILUS_DND_URI_LIST:
+ case NAUTILUS_DND_TEXT_PLAIN:
+ {
+ gtk_selection_data_set (selection_data,
+ gtk_selection_data_get_target (selection_data),
+ 8, (guchar *) uri,
+ strlen (uri));
+ }
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+ g_free (uri);
+ g_object_unref (location);
+}
+
+/* routine that performs the tab expansion. Extract the directory name and
+ * incomplete basename, then iterate through the directory trying to complete it. If we
+ * find something, add it to the entry */
+
+static gboolean
+try_to_expand_path (gpointer callback_data)
+{
+ NautilusLocationEntry *entry;
+ NautilusLocationEntryPrivate *priv;
+ GtkEditable *editable;
+ char *suffix, *user_location, *absolute_location, *uri_scheme;
+ int user_location_length, pos;
+
+ entry = NAUTILUS_LOCATION_ENTRY (callback_data);
+ priv = nautilus_location_entry_get_instance_private (entry);
+ editable = GTK_EDITABLE (entry);
+ user_location = gtk_editable_get_chars (editable, 0, -1);
+ user_location_length = g_utf8_strlen (user_location, -1);
+ user_location = g_strchug (user_location);
+ user_location = g_strchomp (user_location);
+ priv->idle_id = 0;
+
+ uri_scheme = g_uri_parse_scheme (user_location);
+
+ if (!g_path_is_absolute (user_location) && uri_scheme == NULL && user_location[0] != '~')
+ {
+ absolute_location = g_build_filename (priv->current_directory, user_location, NULL);
+ suffix = g_filename_completer_get_completion_suffix (priv->completer,
+ absolute_location);
+ g_free (absolute_location);
+ }
+ else
+ {
+ suffix = g_filename_completer_get_completion_suffix (priv->completer,
+ user_location);
+ }
+
+ g_free (user_location);
+ g_free (uri_scheme);
+
+ /* if we've got something, add it to the entry */
+ if (suffix != NULL)
+ {
+ pos = user_location_length;
+ gtk_editable_insert_text (editable,
+ suffix, -1, &pos);
+ pos = user_location_length;
+ gtk_editable_select_region (editable, pos, -1);
+
+ g_free (suffix);
+ }
+
+ return FALSE;
+}
+
+/* Until we have a more elegant solution, this is how we figure out if
+ * the GtkEntry inserted characters, assuming that the return value is
+ * TRUE indicating that the GtkEntry consumed the key event for some
+ * reason. This is a clone of code from GtkEntry.
+ */
+static gboolean
+entry_would_have_inserted_characters (const GdkEvent *event)
+{
+ guint keyval;
+ GdkModifierType state;
+
+ if (G_UNLIKELY (!gdk_event_get_keyval (event, &keyval)))
+ {
+ g_return_val_if_reached (GDK_EVENT_PROPAGATE);
+ }
+ if (G_UNLIKELY (!gdk_event_get_state (event, &state)))
+ {
+ g_return_val_if_reached (GDK_EVENT_PROPAGATE);
+ }
+
+ switch (keyval)
+ {
+ case GDK_KEY_BackSpace:
+ case GDK_KEY_Clear:
+ case GDK_KEY_Insert:
+ case GDK_KEY_Delete:
+ case GDK_KEY_Home:
+ case GDK_KEY_End:
+ case GDK_KEY_KP_Home:
+ case GDK_KEY_KP_End:
+ case GDK_KEY_Left:
+ case GDK_KEY_Right:
+ case GDK_KEY_KP_Left:
+ case GDK_KEY_KP_Right:
+ case GDK_KEY_Return:
+ /* For when the entry is set to be always visible.
+ */
+ case GDK_KEY_Escape:
+ {
+ return FALSE;
+ }
+
+ default:
+ if (keyval >= 0x20 && keyval <= 0xFF)
+ {
+ if ((state & GDK_CONTROL_MASK) != 0)
+ {
+ return FALSE;
+ }
+ if ((state & GDK_MOD1_MASK) != 0)
+ {
+ return FALSE;
+ }
+ }
+ }
+
+ /* GTK+ 4 TODO: gdk_event_get_string () and check if length > 0. */
+ return ((const GdkEventKey *) event)->length;
+}
+
+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;
+}
+
+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;
+ }
+ try_to_expand_path (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_free (priv->special_text);
+
+ g_clear_object (&priv->last_location);
+
+ G_OBJECT_CLASS (nautilus_location_entry_parent_class)->finalize (object);
+}
+
+static void
+destroy (GtkWidget *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_free (priv->current_directory);
+ priv->current_directory = NULL;
+
+ GTK_WIDGET_CLASS (nautilus_location_entry_parent_class)->destroy (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);
+
+ if (priv->has_special_text)
+ {
+ priv->setting_special_text = TRUE;
+ gtk_entry_set_text (GTK_ENTRY (entry), "");
+ priv->setting_special_text = FALSE;
+ }
+}
+
+static void
+nautilus_location_entry_text_changed (NautilusLocationEntry *entry,
+ GParamSpec *pspec)
+{
+ NautilusLocationEntryPrivate *priv;
+
+ priv = nautilus_location_entry_get_instance_private (entry);
+
+ if (priv->setting_special_text)
+ {
+ return;
+ }
+
+ priv->has_special_text = FALSE;
+}
+
+static void
+nautilus_location_entry_icon_release (GtkEntry *gentry,
+ GtkEntryIconPosition position,
+ GdkEvent *event,
+ 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:
+ {
+ gtk_entry_set_text (gentry, "");
+ }
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static gboolean
+nautilus_location_entry_on_event (GtkWidget *widget,
+ GdkEvent *event)
+{
+ GtkWidgetClass *parent_widget_class;
+ NautilusLocationEntry *entry;
+ NautilusLocationEntryPrivate *priv;
+ GtkEditable *editable;
+ gboolean selected;
+ guint keyval;
+ GdkModifierType state;
+ gboolean handled;
+
+ parent_widget_class = GTK_WIDGET_CLASS (nautilus_location_entry_parent_class);
+
+ if (gdk_event_get_event_type (event) != GDK_KEY_PRESS)
+ {
+ return parent_widget_class->event (widget, event);
+ }
+
+ entry = NAUTILUS_LOCATION_ENTRY (widget);
+ priv = nautilus_location_entry_get_instance_private (entry);
+ editable = GTK_EDITABLE (widget);
+ selected = gtk_editable_get_selection_bounds (editable, NULL, NULL);
+
+ if (!gtk_editable_get_editable (editable))
+ {
+ return GDK_EVENT_PROPAGATE;
+ }
+
+ if (G_UNLIKELY (!gdk_event_get_keyval (event, &keyval)))
+ {
+ g_return_val_if_reached (GDK_EVENT_PROPAGATE);
+ }
+ if (G_UNLIKELY (!gdk_event_get_state (event, &state)))
+ {
+ g_return_val_if_reached (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 && selected)
+ {
+ int position;
+
+ position = strlen (gtk_entry_get_text (GTK_ENTRY (editable)));
+ gtk_editable_select_region (editable, position, position);
+
+ 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);
+ }
+
+ /* GTK+ 4 TODO: Calling the event vfunc is not enough, we need the entry
+ * to handle the key press and insert the text first.
+ *
+ * Chaining up here is required either way, since the code below
+ * used to be in the handler for ::event-after, which is no longer a thing.
+ */
+ handled = parent_widget_class->key_press_event (widget, (GdkEventKey *) event);
+
+ /* Only do expanding when we are typing at the end of the
+ * text. Do the expand at idle time to avoid slowing down
+ * typing when the directory is large. Only trigger the expand
+ * when we type a key that would have inserted characters.
+ */
+ if (position_and_selection_are_at_end (editable))
+ {
+ if (entry_would_have_inserted_characters (event))
+ {
+ if (priv->idle_id == 0)
+ {
+ priv->idle_id = g_idle_add (try_to_expand_path, widget);
+ }
+ }
+ }
+ else
+ {
+ /* FIXME: Also might be good to do this when you click
+ * to change the position or selection.
+ */
+ if (priv->idle_id != 0)
+ {
+ g_source_remove (priv->idle_id);
+ priv->idle_id = 0;
+ }
+ }
+
+ return handled;
+}
+
+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_entry_get_text (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);
+ gtk_entry_set_text (entry, full_path);
+ g_free (full_path);
+ }
+
+ g_free (uri_scheme);
+ }
+
+ GTK_ENTRY_CLASS (nautilus_location_entry_parent_class)->activate (entry);
+}
+
+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)
+{
+ GtkWidgetClass *widget_class;
+ GObjectClass *gobject_class;
+ GtkEntryClass *entry_class;
+ GtkBindingSet *binding_set;
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->destroy = destroy;
+ widget_class->event = nautilus_location_entry_on_event;
+
+ gobject_class = G_OBJECT_CLASS (class);
+ 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);
+
+ binding_set = gtk_binding_set_by_class (class);
+ gtk_binding_entry_add_signal (binding_set, GDK_KEY_Escape, 0, "cancel", 0);
+}
+
+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_entry_get_text (entry);
+ path = g_strdup (entry_text);
+ path = g_strchug (path);
+ path = g_strchomp (path);
+
+ if (path != NULL && *path != '\0')
+ {
+ gtk_entry_set_text (entry, 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;
+ GtkTargetList *targetlist;
+
+ priv = nautilus_location_entry_get_instance_private (entry);
+
+ priv->completer = g_filename_completer_new ();
+ g_filename_completer_set_dirs_only (priv->completer, TRUE);
+
+ gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry), GTK_ENTRY_ICON_PRIMARY, "folder-symbolic");
+ gtk_entry_set_icon_activatable (GTK_ENTRY (entry), GTK_ENTRY_ICON_PRIMARY, FALSE);
+ targetlist = gtk_target_list_new (drag_types, G_N_ELEMENTS (drag_types));
+ gtk_entry_set_icon_drag_source (GTK_ENTRY (entry), GTK_ENTRY_ICON_PRIMARY, targetlist, GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
+ gtk_target_list_unref (targetlist);
+
+ 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);
+
+ /* Drag source */
+ g_signal_connect_object (entry, "drag-data-get",
+ G_CALLBACK (drag_data_get_callback), entry, 0);
+
+ /* Drag dest. */
+ gtk_drag_dest_set (GTK_WIDGET (entry),
+ GTK_DEST_DEFAULT_ALL,
+ drop_types, G_N_ELEMENTS (drop_types),
+ GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
+ g_signal_connect (entry, "drag-data-received",
+ G_CALLBACK (drag_data_received_callback), NULL);
+
+ 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);
+}
+
+GtkWidget *
+nautilus_location_entry_new (void)
+{
+ GtkWidget *entry;
+
+ entry = gtk_widget_new (NAUTILUS_TYPE_LOCATION_ENTRY, "max-width-chars", 350, 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);
+
+ priv->has_special_text = TRUE;
+
+ g_free (priv->special_text);
+ priv->special_text = g_strdup (special_text);
+
+ priv->setting_special_text = TRUE;
+ gtk_entry_set_text (GTK_ENTRY (entry), special_text);
+ priv->setting_special_text = FALSE;
+}
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..26468c5
--- /dev/null
+++ b/src/nautilus-mime-actions.c
@@ -0,0 +1,2245 @@
+/* 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_ASK,
+ 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
+{
+ NautilusWindowSlot *slot;
+ gpointer window;
+ GtkWindow *parent_window;
+ GCancellable *cancellable;
+ GList *locations;
+ GList *mountables;
+ GList *start_mountables;
+ GList *not_mounted;
+ NautilusWindowOpenFlags flags;
+ char *timed_wait_prompt;
+ gboolean timed_wait_active;
+ NautilusFileListHandle *files_handle;
+ gboolean tried_mounting;
+ char *activation_directory;
+ gboolean user_confirmation;
+} ActivateParameters;
+
+typedef struct
+{
+ ActivateParameters *activation_params;
+ GQueue *uris;
+ GQueue *unhandled_uris;
+} ApplicationLaunchParameters;
+
+/* 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 RESPONSE_RUN 1000
+#define RESPONSE_DISPLAY 1001
+#define RESPONSE_RUN_IN_TERMINAL 1002
+
+#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 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 (ActivateParameters *activation_params,
+ GQueue *uris)
+{
+ ApplicationLaunchParameters *result;
+
+ result = g_new0 (ApplicationLaunchParameters, 1);
+ result->activation_params = activation_params;
+ result->uris = uris;
+ result->unhandled_uris = g_queue_new ();
+
+ return result;
+}
+
+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_is_local_or_fuse (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);
+}
+
+static void
+report_broken_symbolic_link (GtkWindow *parent_window,
+ NautilusFile *file)
+{
+ char *target_path;
+ char *display_name;
+ char *prompt;
+ char *detail;
+ GtkDialog *dialog;
+ GList file_as_list;
+ int response;
+ 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);
+
+ if (can_trash)
+ {
+ prompt = g_strdup_printf (_("The link “%s” is broken. Move it to Trash?"), display_name);
+ }
+ else
+ {
+ prompt = g_strdup_printf (_("The link “%s” is broken."), display_name);
+ }
+ g_free (display_name);
+
+ 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)
+ {
+ eel_run_simple_dialog (GTK_WIDGET (parent_window), FALSE, GTK_MESSAGE_WARNING,
+ prompt, detail, _("_Cancel"), NULL);
+ goto out;
+ }
+
+ dialog = eel_show_yes_no_dialog (prompt, detail, _("Mo_ve to Trash"), _("_Cancel"),
+ parent_window);
+
+ gtk_dialog_set_default_response (dialog, GTK_RESPONSE_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.
+ */
+
+ response = gtk_dialog_run (dialog);
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+
+ if (response == GTK_RESPONSE_YES)
+ {
+ file_as_list.data = file;
+ file_as_list.next = NULL;
+ file_as_list.prev = NULL;
+ trash_or_delete_files (parent_window, &file_as_list, TRUE);
+ }
+
+out:
+ g_free (prompt);
+ g_free (target_path);
+ g_free (detail);
+}
+
+static ActivationAction
+get_executable_text_file_action (GtkWindow *parent_window,
+ NautilusFile *file)
+{
+ GtkDialog *dialog;
+ char *file_name;
+ char *prompt;
+ char *detail;
+ int preferences_value;
+ int response;
+
+ g_assert (nautilus_file_contains_text (file));
+
+ preferences_value = g_settings_get_enum (nautilus_preferences,
+ NAUTILUS_PREFERENCES_EXECUTABLE_TEXT_ACTIVATION);
+ switch (preferences_value)
+ {
+ case NAUTILUS_EXECUTABLE_TEXT_LAUNCH:
+ {
+ return ACTIVATION_ACTION_LAUNCH;
+ }
+
+ case NAUTILUS_EXECUTABLE_TEXT_DISPLAY:
+ {
+ return ACTIVATION_ACTION_OPEN_IN_APPLICATION;
+ }
+
+ case NAUTILUS_EXECUTABLE_TEXT_ASK:
+ {
+ }
+ break;
+
+ default:
+ /* Complain non-fatally, since preference data can't be trusted */
+ g_warning ("Unknown value %d for NAUTILUS_PREFERENCES_EXECUTABLE_TEXT_ACTIVATION",
+ preferences_value);
+ }
+
+
+ file_name = nautilus_file_get_display_name (file);
+ prompt = g_strdup_printf (_("Do you want to run “%s”, or display its contents?"),
+ file_name);
+ detail = g_strdup_printf (_("“%s” is an executable text file."),
+ file_name);
+ g_free (file_name);
+
+ dialog = eel_create_question_dialog (prompt,
+ detail,
+ _("Run in _Terminal"), RESPONSE_RUN_IN_TERMINAL,
+ _("_Display"), RESPONSE_DISPLAY,
+ parent_window);
+ gtk_dialog_add_button (dialog, _("_Cancel"), GTK_RESPONSE_CANCEL);
+ gtk_dialog_add_button (dialog, _("_Run"), RESPONSE_RUN);
+ gtk_dialog_set_default_response (dialog, GTK_RESPONSE_CANCEL);
+ gtk_widget_show (GTK_WIDGET (dialog));
+
+ g_free (prompt);
+ g_free (detail);
+
+ response = gtk_dialog_run (dialog);
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+
+ switch (response)
+ {
+ case RESPONSE_RUN:
+ {
+ return ACTIVATION_ACTION_LAUNCH;
+ }
+
+ case RESPONSE_RUN_IN_TERMINAL:
+ {
+ return ACTIVATION_ACTION_LAUNCH_IN_TERMINAL;
+ }
+
+ case RESPONSE_DISPLAY:
+ {
+ return ACTIVATION_ACTION_OPEN_IN_APPLICATION;
+ }
+
+ default:
+ return ACTIVATION_ACTION_DO_NOTHING;
+ }
+}
+
+static ActivationAction
+get_default_executable_text_file_action (void)
+{
+ int preferences_value;
+
+ preferences_value = g_settings_get_enum (nautilus_preferences,
+ NAUTILUS_PREFERENCES_EXECUTABLE_TEXT_ACTIVATION);
+ switch (preferences_value)
+ {
+ case NAUTILUS_EXECUTABLE_TEXT_LAUNCH:
+ {
+ return ACTIVATION_ACTION_LAUNCH;
+ }
+
+ case NAUTILUS_EXECUTABLE_TEXT_DISPLAY:
+ {
+ return ACTIVATION_ACTION_OPEN_IN_APPLICATION;
+ }
+
+ case NAUTILUS_EXECUTABLE_TEXT_ASK:
+ default:
+ return ACTIVATION_ACTION_ASK;
+ }
+}
+
+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 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_free (parameters);
+}
+
+static void
+application_launch_parameters_free (ApplicationLaunchParameters *parameters)
+{
+ g_queue_free (parameters->unhandled_uris);
+ 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 gboolean
+confirm_multiple_windows (GtkWindow *parent_window,
+ int count,
+ gboolean use_tabs)
+{
+ GtkDialog *dialog;
+ char *prompt;
+ char *detail;
+ int response;
+
+ if (count <= SILENT_WINDOW_OPEN_LIMIT)
+ {
+ return TRUE;
+ }
+
+ prompt = _("Are you sure you want to open all files?");
+ if (use_tabs)
+ {
+ detail = g_strdup_printf (ngettext ("This will open %d separate tab.",
+ "This will open %d separate tabs.", count), count);
+ }
+ else
+ {
+ detail = g_strdup_printf (ngettext ("This will open %d separate window.",
+ "This will open %d separate windows.", count), count);
+ }
+ dialog = eel_show_yes_no_dialog (prompt, detail,
+ _("_OK"), _("_Cancel"),
+ parent_window);
+ g_free (detail);
+
+ response = gtk_dialog_run (dialog);
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+
+ return response == GTK_RESPONSE_YES;
+}
+
+typedef struct
+{
+ NautilusWindowSlot *slot;
+ GtkWindow *parent_window;
+ NautilusFile *file;
+ GList *files;
+ NautilusWindowOpenFlags 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_widget_destroy (GTK_WIDGET (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_widget_destroy (GTK_WIDGET (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,
+ int response,
+ gpointer callback_data)
+{
+ GtkWidget *dialog;
+ NautilusFile *file;
+ GFile *location;
+ ActivateParametersInstall *parameters = callback_data;
+
+ if (response != GTK_RESPONSE_ACCEPT)
+ {
+ gtk_widget_destroy (GTK_WIDGET (message_dialog));
+ 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_widget_destroy (GTK_WIDGET (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;
+
+ 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))
+ {
+ dialog = gtk_message_dialog_new (parameters->parent_window,
+ GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
+ GTK_MESSAGE_ERROR,
+ 0,
+ "%s", error_message);
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ _("The file is of an unknown type"));
+ }
+ else
+ {
+ char *text;
+ text = g_strdup_printf (_("There is no application installed for “%s” files"), g_content_type_get_description (mime_type));
+
+ dialog = gtk_message_dialog_new (parameters->parent_window,
+ GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
+ GTK_MESSAGE_ERROR,
+ 0,
+ "%s", error_message);
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ "%s", text);
+
+ g_free (text);
+ }
+
+ gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Select Application"), GTK_RESPONSE_ACCEPT);
+
+ gtk_dialog_add_button (GTK_DIALOG (dialog), _("_OK"), GTK_RESPONSE_OK);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+
+ g_object_set_data_full (G_OBJECT (dialog),
+ "mime-action:file",
+ nautilus_file_ref (parameters->file),
+ (GDestroyNotify) nautilus_file_unref);
+
+ gtk_widget_show (GTK_WIDGET (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", gtk_get_current_event_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,
+ gint response_id,
+ ActivateParametersInstall *parameters_install)
+{
+ char *mime_type;
+
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+ parameters_install->dialog = NULL;
+
+ if (response_id == GTK_RESPONSE_YES)
+ {
+ 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;
+
+ 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);
+ 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 = gtk_message_dialog_new (parameters_install->parent_window,
+ GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_NONE,
+ "%s", error_message);
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ _("There is no application installed for “%s” files. "
+ "Do you want to search for an application to open this file?"),
+ g_content_type_get_description (mime_type));
+ gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+
+ gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Cancel"), GTK_RESPONSE_CANCEL);
+ gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Search in Software"), GTK_RESPONSE_YES);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_YES);
+
+ parameters_install->dialog = dialog;
+ parameters_install->proxy = proxy;
+
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (application_unhandled_file_install),
+ parameters_install);
+ gtk_widget_show_all (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
+on_launch_default_for_uri (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ ApplicationLaunchParameters *params;
+ ActivateParameters *activation_params;
+ char *uri;
+ gboolean sandboxed;
+ GError *error = NULL;
+
+ params = user_data;
+ activation_params = params->activation_params;
+ uri = g_queue_pop_head (params->uris);
+ sandboxed = g_file_test ("/.flatpak-info", G_FILE_TEST_EXISTS);
+
+ nautilus_launch_default_for_uri_finish (res, &error);
+ if (!sandboxed && error != NULL && error->code != G_IO_ERROR_CANCELLED)
+ {
+ g_queue_push_tail (params->unhandled_uris, 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,
+ on_launch_default_for_uri,
+ params);
+ }
+ else
+ {
+ while ((uri = g_queue_pop_head (params->unhandled_uris)) != NULL)
+ {
+ application_unhandled_uri (activation_params, uri);
+ }
+
+ application_launch_parameters_free (params);
+ }
+}
+
+static void
+activate_files (ActivateParameters *parameters)
+{
+ NautilusFile *file;
+ NautilusWindowOpenFlags flags;
+ int count;
+ g_autofree char *old_working_dir = NULL;
+ GdkScreen *screen;
+ g_autoptr (GQueue) launch_files = NULL;
+ g_autoptr (GQueue) launch_in_terminal_files = NULL;
+ g_autoptr (GQueue) open_in_app_uris = NULL;
+ g_autoptr (GQueue) open_in_view_files = NULL;
+ GList *l;
+ ActivationAction action;
+
+ launch_files = g_queue_new ();
+ launch_in_terminal_files = g_queue_new ();
+ open_in_view_files = g_queue_new ();
+ 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);
+ if (action == ACTIVATION_ACTION_ASK)
+ {
+ /* Special case for executable text files, since it might be
+ * dangerous & unexpected to launch these.
+ */
+ pause_activation_timed_cancel (parameters);
+ action = get_executable_text_file_action (parameters->parent_window, file);
+ unpause_activation_timed_cancel (parameters);
+ }
+
+ switch (action)
+ {
+ case ACTIVATION_ACTION_LAUNCH:
+ {
+ g_queue_push_tail (launch_files, file);
+ }
+ break;
+
+ case ACTIVATION_ACTION_LAUNCH_IN_TERMINAL:
+ {
+ g_queue_push_tail (launch_in_terminal_files, file);
+ }
+ break;
+
+ case ACTIVATION_ACTION_OPEN_IN_VIEW:
+ {
+ g_queue_push_tail (open_in_view_files, file);
+ }
+ break;
+
+ case ACTIVATION_ACTION_OPEN_IN_APPLICATION:
+ {
+ g_queue_push_tail (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;
+
+ case ACTIVATION_ACTION_ASK:
+ {
+ g_assert_not_reached ();
+ }
+ break;
+ }
+ }
+
+ if (parameters->activation_directory &&
+ (!g_queue_is_empty (launch_files) ||
+ !g_queue_is_empty (launch_in_terminal_files)))
+ {
+ old_working_dir = g_get_current_dir ();
+ g_chdir (parameters->activation_directory);
+ }
+
+ screen = gtk_widget_get_screen (GTK_WIDGET (parameters->parent_window));
+ for (l = g_queue_peek_head_link (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 (screen, quoted_path, FALSE, NULL);
+ }
+
+ for (l = g_queue_peek_head_link (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 (screen, quoted_path, TRUE, NULL);
+ }
+
+ if (old_working_dir != NULL)
+ {
+ g_chdir (old_working_dir);
+ }
+
+ count = g_queue_get_length (open_in_view_files);
+
+ flags = parameters->flags;
+ if (count > 1)
+ {
+ if ((parameters->flags & NAUTILUS_WINDOW_OPEN_FLAG_NEW_WINDOW) == 0)
+ {
+ flags |= NAUTILUS_WINDOW_OPEN_FLAG_NEW_TAB;
+ }
+ else
+ {
+ flags |= NAUTILUS_WINDOW_OPEN_FLAG_NEW_WINDOW;
+ }
+ }
+
+ if (parameters->slot != NULL &&
+ (!parameters->user_confirmation ||
+ confirm_multiple_windows (parameters->parent_window, count,
+ (flags & NAUTILUS_WINDOW_OPEN_FLAG_NEW_TAB) != 0)))
+ {
+ if ((flags & NAUTILUS_WINDOW_OPEN_FLAG_NEW_TAB) != 0 &&
+ g_settings_get_enum (nautilus_preferences, NAUTILUS_PREFERENCES_NEW_TAB_POSITION) ==
+ NAUTILUS_NEW_TAB_POSITION_AFTER_CURRENT_TAB)
+ {
+ /* 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 (open_in_view_files);
+ }
+
+ for (l = g_queue_peek_head_link (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, flags, NULL, NULL, parameters->slot);
+ }
+ }
+
+ if (g_queue_is_empty (open_in_app_uris))
+ {
+ activation_parameters_free (parameters);
+ }
+ else
+ {
+ const char *uri;
+ ApplicationLaunchParameters *params;
+
+ uri = g_queue_peek_head (open_in_app_uris);
+ params = application_launch_parameters_new (parameters,
+ g_queue_copy (open_in_app_uris));
+
+ gtk_recent_manager_add_item (gtk_recent_manager_get_default (), uri);
+ nautilus_launch_default_for_uri_async (uri,
+ parameters->parent_window,
+ parameters->cancellable,
+ on_launch_default_for_uri,
+ params);
+ }
+}
+
+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,
+ NautilusWindowOpenFlags 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,
+ NautilusWindowOpenFlags 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..9765894
--- /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,
+ NautilusWindowOpenFlags flags,
+ gboolean user_confirmation);
+void nautilus_mime_activate_file (GtkWindow *parent_window,
+ NautilusWindowSlot *slot_info,
+ NautilusFile *file,
+ const char *launch_directory,
+ NautilusWindowOpenFlags 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..bf474bd
--- /dev/null
+++ b/src/nautilus-module.c
@@ -0,0 +1,311 @@
+/*
+ * 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 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)
+{
+ 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));
+ return module;
+ }
+ else
+ {
+ g_object_unref (module);
+ return NULL;
+ }
+}
+
+static void
+load_module_dir (const char *dirname)
+{
+ GDir *dir;
+
+ 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);
+ g_free (filename);
+ }
+ }
+
+ g_dir_close (dir);
+ }
+}
+
+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);
+}
+
+void
+nautilus_module_setup (void)
+{
+ static gboolean initialized = FALSE;
+
+ 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..1f70a91
--- /dev/null
+++ b/src/nautilus-module.h
@@ -0,0 +1,38 @@
+/*
+ * 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);
+
+
+/* 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-new-folder-dialog-controller.c b/src/nautilus-new-folder-dialog-controller.c
new file mode 100644
index 0000000..17406a0
--- /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_entry_set_text (GTK_ENTRY (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_all (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_widget_destroy (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-notebook.c b/src/nautilus-notebook.c
new file mode 100644
index 0000000..9d2fb1d
--- /dev/null
+++ b/src/nautilus-notebook.c
@@ -0,0 +1,572 @@
+/*
+ * Copyright © 2002 Christophe Fergeau
+ * Copyright © 2003, 2004 Marco Pesenti Gritti
+ * Copyright © 2003, 2004, 2005 Christian Persch
+ * (ephy-notebook.c)
+ *
+ * Copyright © 2008 Free Software Foundation, Inc.
+ * (nautilus-notebook.c)
+ *
+ * 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, 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-notebook.h"
+
+#include "nautilus-window.h"
+#include "nautilus-window-slot.h"
+#include "nautilus-window-slot-dnd.h"
+
+#include <eel/eel-vfs-extensions.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+#define AFTER_ALL_TABS -1
+
+static int nautilus_notebook_insert_page (GtkNotebook *notebook,
+ GtkWidget *child,
+ GtkWidget *tab_label,
+ GtkWidget *menu_label,
+ int position);
+static void nautilus_notebook_remove (GtkContainer *container,
+ GtkWidget *tab_widget);
+
+enum
+{
+ TAB_CLOSE_REQUEST,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+struct _NautilusNotebook
+{
+ GtkNotebook parent_instance;
+
+ GtkGesture *multi_press_gesture;
+};
+
+G_DEFINE_TYPE (NautilusNotebook, nautilus_notebook, GTK_TYPE_NOTEBOOK);
+
+static void
+nautilus_notebook_dispose (GObject *object)
+{
+ NautilusNotebook *notebook;
+
+ notebook = NAUTILUS_NOTEBOOK (object);
+
+ g_clear_object (&notebook->multi_press_gesture);
+
+ G_OBJECT_CLASS (nautilus_notebook_parent_class)->dispose (object);
+}
+
+static void
+nautilus_notebook_class_init (NautilusNotebookClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+ GtkNotebookClass *notebook_class = GTK_NOTEBOOK_CLASS (klass);
+
+ object_class->dispose = nautilus_notebook_dispose;
+
+ container_class->remove = nautilus_notebook_remove;
+
+ notebook_class->insert_page = nautilus_notebook_insert_page;
+
+ signals[TAB_CLOSE_REQUEST] =
+ g_signal_new ("tab-close-request",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1,
+ NAUTILUS_TYPE_WINDOW_SLOT);
+}
+
+static gint
+find_tab_num_at_pos (NautilusNotebook *notebook,
+ gint abs_x,
+ gint abs_y)
+{
+ int page_num = 0;
+ GtkNotebook *nb = GTK_NOTEBOOK (notebook);
+ GtkWidget *page;
+ GtkAllocation allocation;
+
+ while ((page = gtk_notebook_get_nth_page (nb, page_num)))
+ {
+ GtkWidget *tab;
+ gint max_x, max_y;
+
+ tab = gtk_notebook_get_tab_label (nb, page);
+ g_return_val_if_fail (tab != NULL, -1);
+
+ if (!gtk_widget_get_mapped (GTK_WIDGET (tab)))
+ {
+ page_num++;
+ continue;
+ }
+
+ gtk_widget_get_allocation (tab, &allocation);
+
+ max_x = allocation.x + allocation.width;
+ max_y = allocation.y + allocation.height;
+
+ if (abs_x <= max_x && abs_y <= max_y)
+ {
+ return page_num;
+ }
+
+ page_num++;
+ }
+ return AFTER_ALL_TABS;
+}
+
+static void
+button_press_cb (GtkGestureMultiPress *gesture,
+ gint n_press,
+ gdouble x,
+ gdouble y,
+ gpointer user_data)
+{
+ guint button;
+ GdkEventSequence *sequence;
+ const GdkEvent *event;
+ GtkWidget *widget;
+ NautilusNotebook *notebook;
+ int tab_clicked;
+ GdkModifierType state;
+
+ button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
+ sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
+ event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
+ widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
+ notebook = NAUTILUS_NOTEBOOK (widget);
+ tab_clicked = find_tab_num_at_pos (notebook, x, y);
+
+ gdk_event_get_state (event, &state);
+
+ if (n_press != 1)
+ {
+ return;
+ }
+
+ if (tab_clicked == -1)
+ {
+ return;
+ }
+
+ if (button == GDK_BUTTON_SECONDARY &&
+ (state & gtk_accelerator_get_default_mod_mask ()) == 0)
+ {
+ /* switch to the page the mouse is over, but don't consume the event */
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), tab_clicked);
+ }
+ else if (button == GDK_BUTTON_MIDDLE)
+ {
+ GtkWidget *slot;
+
+ slot = gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook), tab_clicked);
+ g_signal_emit (notebook, signals[TAB_CLOSE_REQUEST], 0, slot);
+ }
+}
+
+static void
+nautilus_notebook_init (NautilusNotebook *notebook)
+{
+ gtk_notebook_set_scrollable (GTK_NOTEBOOK (notebook), TRUE);
+ gtk_notebook_set_show_border (GTK_NOTEBOOK (notebook), FALSE);
+ gtk_notebook_set_show_tabs (GTK_NOTEBOOK (notebook), FALSE);
+
+ notebook->multi_press_gesture = gtk_gesture_multi_press_new (GTK_WIDGET (notebook));
+
+ gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (notebook->multi_press_gesture),
+ GTK_PHASE_CAPTURE);
+ gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (notebook->multi_press_gesture), 0);
+
+ g_signal_connect (notebook->multi_press_gesture, "pressed", G_CALLBACK (button_press_cb), NULL);
+}
+
+gboolean
+nautilus_notebook_contains_slot (NautilusNotebook *notebook,
+ NautilusWindowSlot *slot)
+{
+ GList *children;
+ GList *l;
+ gboolean found = FALSE;
+
+ children = gtk_container_get_children (GTK_CONTAINER (notebook));
+ for (l = children; l != NULL && !found; l = l->next)
+ {
+ found = l->data == slot;
+ }
+
+ g_list_free (children);
+
+ return found;
+}
+
+gboolean
+nautilus_notebook_content_area_hit (NautilusNotebook *notebook,
+ gint x,
+ gint y)
+{
+ return find_tab_num_at_pos (notebook, x, y) == -1;
+}
+
+void
+nautilus_notebook_sync_loading (NautilusNotebook *notebook,
+ NautilusWindowSlot *slot)
+{
+ GtkWidget *tab_label, *spinner, *icon;
+ gboolean active, allow_stop;
+
+ g_return_if_fail (NAUTILUS_IS_NOTEBOOK (notebook));
+ g_return_if_fail (NAUTILUS_IS_WINDOW_SLOT (slot));
+
+ tab_label = gtk_notebook_get_tab_label (GTK_NOTEBOOK (notebook),
+ GTK_WIDGET (slot));
+ g_return_if_fail (GTK_IS_WIDGET (tab_label));
+
+ spinner = GTK_WIDGET (g_object_get_data (G_OBJECT (tab_label), "spinner"));
+ icon = GTK_WIDGET (g_object_get_data (G_OBJECT (tab_label), "icon"));
+ g_return_if_fail (spinner != NULL && icon != NULL);
+
+ active = FALSE;
+ g_object_get (spinner, "active", &active, NULL);
+ allow_stop = nautilus_window_slot_get_allow_stop (slot);
+
+ if (active == allow_stop)
+ {
+ return;
+ }
+
+ if (allow_stop)
+ {
+ gtk_widget_hide (icon);
+ gtk_widget_show (spinner);
+ gtk_spinner_start (GTK_SPINNER (spinner));
+ }
+ else
+ {
+ gtk_spinner_stop (GTK_SPINNER (spinner));
+ gtk_widget_hide (spinner);
+ gtk_widget_show (icon);
+ }
+}
+
+void
+nautilus_notebook_sync_tab_label (NautilusNotebook *notebook,
+ NautilusWindowSlot *slot)
+{
+ GtkWidget *hbox, *label;
+ char *location_name;
+ GFile *location;
+ const gchar *title_name;
+
+ g_return_if_fail (NAUTILUS_IS_NOTEBOOK (notebook));
+ g_return_if_fail (NAUTILUS_IS_WINDOW_SLOT (slot));
+
+ hbox = gtk_notebook_get_tab_label (GTK_NOTEBOOK (notebook), GTK_WIDGET (slot));
+ g_return_if_fail (GTK_IS_WIDGET (hbox));
+
+ label = GTK_WIDGET (g_object_get_data (G_OBJECT (hbox), "label"));
+ g_return_if_fail (GTK_IS_WIDGET (label));
+
+ gtk_label_set_text (GTK_LABEL (label), nautilus_window_slot_get_title (slot));
+ location = nautilus_window_slot_get_location (slot);
+
+ if (location != NULL)
+ {
+ /* 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);
+ title_name = nautilus_window_slot_get_title (slot);
+ if (eel_uri_is_search (location_name))
+ {
+ gtk_widget_set_tooltip_text (gtk_widget_get_parent (label), title_name);
+ }
+ else
+ {
+ gtk_widget_set_tooltip_text (gtk_widget_get_parent (label), location_name);
+ }
+ g_free (location_name);
+ }
+ else
+ {
+ gtk_widget_set_tooltip_text (gtk_widget_get_parent (label), NULL);
+ }
+}
+
+static void
+close_button_clicked_cb (GtkWidget *widget,
+ NautilusWindowSlot *slot)
+{
+ GtkWidget *notebook;
+
+ notebook = gtk_widget_get_ancestor (GTK_WIDGET (slot), NAUTILUS_TYPE_NOTEBOOK);
+ if (notebook != NULL)
+ {
+ g_signal_emit (notebook, signals[TAB_CLOSE_REQUEST], 0, slot);
+ }
+}
+
+static GtkWidget *
+build_tab_label (NautilusNotebook *notebook,
+ NautilusWindowSlot *slot)
+{
+ GtkWidget *box;
+ GtkWidget *label;
+ GtkWidget *close_button;
+ GtkWidget *image;
+ GtkWidget *spinner;
+ GtkWidget *icon;
+
+ /* When porting to Gtk+4, use GtkCenterBox instead */
+ box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4);
+ gtk_widget_show (box);
+
+ /* Spinner to be shown as load feedback */
+ spinner = gtk_spinner_new ();
+ gtk_box_pack_start (GTK_BOX (box), spinner, FALSE, FALSE, 0);
+
+ /* Dummy icon to allocate space for spinner */
+ icon = gtk_image_new ();
+ gtk_box_pack_start (GTK_BOX (box), icon, FALSE, FALSE, 0);
+ /* don't show the icon */
+
+ /* Tab title */
+ label = gtk_label_new (NULL);
+ gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
+ gtk_label_set_single_line_mode (GTK_LABEL (label), TRUE);
+ gtk_label_set_width_chars (GTK_LABEL (label), 6);
+ gtk_box_set_center_widget (GTK_BOX (box), label);
+ gtk_widget_show (label);
+
+ /* Tab close button */
+ close_button = gtk_button_new ();
+ gtk_button_set_relief (GTK_BUTTON (close_button),
+ GTK_RELIEF_NONE);
+ /* don't allow focus on the close button */
+ gtk_widget_set_focus_on_click (close_button, FALSE);
+
+ gtk_widget_set_name (close_button, "nautilus-tab-close-button");
+
+ image = gtk_image_new_from_icon_name ("window-close-symbolic", GTK_ICON_SIZE_MENU);
+ gtk_widget_set_tooltip_text (close_button, _("Close tab"));
+ g_signal_connect_object (close_button, "clicked",
+ G_CALLBACK (close_button_clicked_cb), slot, 0);
+
+ gtk_container_add (GTK_CONTAINER (close_button), image);
+ gtk_widget_show (image);
+
+ gtk_box_pack_end (GTK_BOX (box), close_button, FALSE, FALSE, 0);
+ gtk_widget_show (close_button);
+
+ g_object_set_data (G_OBJECT (box), "nautilus-notebook-tab", GINT_TO_POINTER (1));
+ nautilus_drag_slot_proxy_init (box, NULL, slot);
+
+ g_object_set_data (G_OBJECT (box), "label", label);
+ g_object_set_data (G_OBJECT (box), "spinner", spinner);
+ g_object_set_data (G_OBJECT (box), "icon", icon);
+ g_object_set_data (G_OBJECT (box), "close-button", close_button);
+
+ return box;
+}
+
+static int
+nautilus_notebook_insert_page (GtkNotebook *gnotebook,
+ GtkWidget *tab_widget,
+ GtkWidget *tab_label,
+ GtkWidget *menu_label,
+ int position)
+{
+ g_assert (GTK_IS_WIDGET (tab_widget));
+
+ position = GTK_NOTEBOOK_CLASS (nautilus_notebook_parent_class)->insert_page (gnotebook,
+ tab_widget,
+ tab_label,
+ menu_label,
+ position);
+
+ gtk_notebook_set_show_tabs (gnotebook,
+ gtk_notebook_get_n_pages (gnotebook) > 1);
+ gtk_notebook_set_tab_reorderable (gnotebook, tab_widget, TRUE);
+ gtk_notebook_set_tab_detachable (gnotebook, tab_widget, TRUE);
+
+ return position;
+}
+
+int
+nautilus_notebook_add_tab (NautilusNotebook *notebook,
+ NautilusWindowSlot *slot,
+ int position,
+ gboolean jump_to)
+{
+ GtkNotebook *gnotebook = GTK_NOTEBOOK (notebook);
+ GtkWidget *tab_label;
+
+ g_return_val_if_fail (NAUTILUS_IS_NOTEBOOK (notebook), -1);
+ g_return_val_if_fail (NAUTILUS_IS_WINDOW_SLOT (slot), -1);
+
+ tab_label = build_tab_label (notebook, slot);
+
+ position = gtk_notebook_insert_page (GTK_NOTEBOOK (notebook),
+ GTK_WIDGET (slot),
+ tab_label,
+ position);
+
+ gtk_container_child_set (GTK_CONTAINER (notebook),
+ GTK_WIDGET (slot),
+ "tab-expand", TRUE,
+ "detachable", FALSE,
+ NULL);
+
+ nautilus_notebook_sync_tab_label (notebook, slot);
+ nautilus_notebook_sync_loading (notebook, slot);
+
+ if (jump_to)
+ {
+ gtk_notebook_set_current_page (gnotebook, position);
+ }
+
+ return position;
+}
+
+static void
+nautilus_notebook_remove (GtkContainer *container,
+ GtkWidget *tab_widget)
+{
+ GtkNotebook *gnotebook = GTK_NOTEBOOK (container);
+ GTK_CONTAINER_CLASS (nautilus_notebook_parent_class)->remove (container, tab_widget);
+
+ gtk_notebook_set_show_tabs (gnotebook,
+ gtk_notebook_get_n_pages (gnotebook) > 1);
+}
+
+void
+nautilus_notebook_reorder_current_child_relative (NautilusNotebook *notebook,
+ int offset)
+{
+ GtkNotebook *gnotebook;
+ GtkWidget *child;
+ int page;
+
+ g_return_if_fail (NAUTILUS_IS_NOTEBOOK (notebook));
+
+ if (!nautilus_notebook_can_reorder_current_child_relative (notebook, offset))
+ {
+ return;
+ }
+
+ gnotebook = GTK_NOTEBOOK (notebook);
+
+ page = gtk_notebook_get_current_page (gnotebook);
+ child = gtk_notebook_get_nth_page (gnotebook, page);
+ gtk_notebook_reorder_child (gnotebook, child, page + offset);
+}
+
+static gboolean
+nautilus_notebook_is_valid_relative_position (NautilusNotebook *notebook,
+ int offset)
+{
+ GtkNotebook *gnotebook;
+ int page;
+ int n_pages;
+
+ gnotebook = GTK_NOTEBOOK (notebook);
+
+ page = gtk_notebook_get_current_page (gnotebook);
+ n_pages = gtk_notebook_get_n_pages (gnotebook) - 1;
+ if (page < 0 ||
+ (offset < 0 && page < -offset) ||
+ (offset > 0 && page > n_pages - offset))
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+nautilus_notebook_can_reorder_current_child_relative (NautilusNotebook *notebook,
+ int offset)
+{
+ g_return_val_if_fail (NAUTILUS_IS_NOTEBOOK (notebook), FALSE);
+
+ return nautilus_notebook_is_valid_relative_position (notebook, offset);
+}
+
+void
+nautilus_notebook_next_page (NautilusNotebook *notebook)
+{
+ gint current_page, n_pages;
+
+ g_return_if_fail (NAUTILUS_IS_NOTEBOOK (notebook));
+
+ current_page = gtk_notebook_get_current_page (GTK_NOTEBOOK (notebook));
+ n_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (notebook));
+
+ if (current_page < n_pages - 1)
+ {
+ gtk_notebook_next_page (GTK_NOTEBOOK (notebook));
+ }
+ else
+ {
+ gboolean wrap_around;
+
+ g_object_get (gtk_widget_get_settings (GTK_WIDGET (notebook)),
+ "gtk-keynav-wrap-around", &wrap_around,
+ NULL);
+
+ if (wrap_around)
+ {
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), 0);
+ }
+ }
+}
+
+void
+nautilus_notebook_prev_page (NautilusNotebook *notebook)
+{
+ gint current_page;
+
+ g_return_if_fail (NAUTILUS_IS_NOTEBOOK (notebook));
+
+ current_page = gtk_notebook_get_current_page (GTK_NOTEBOOK (notebook));
+
+ if (current_page > 0)
+ {
+ gtk_notebook_prev_page (GTK_NOTEBOOK (notebook));
+ }
+ else
+ {
+ gboolean wrap_around;
+
+ g_object_get (gtk_widget_get_settings (GTK_WIDGET (notebook)),
+ "gtk-keynav-wrap-around", &wrap_around,
+ NULL);
+
+ if (wrap_around)
+ {
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), -1);
+ }
+ }
+}
diff --git a/src/nautilus-notebook.h b/src/nautilus-notebook.h
new file mode 100644
index 0000000..1590e11
--- /dev/null
+++ b/src/nautilus-notebook.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright © 2002 Christophe Fergeau
+ * Copyright © 2003 Marco Pesenti Gritti
+ * Copyright © 2003, 2004 Christian Persch
+ * (ephy-notebook.c)
+ *
+ * Copyright © 2008 Free Software Foundation, Inc.
+ * (nautilus-notebook.c)
+ *
+ * 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, 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-window-slot.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_NOTEBOOK (nautilus_notebook_get_type ())
+G_DECLARE_FINAL_TYPE (NautilusNotebook, nautilus_notebook, NAUTILUS, NOTEBOOK, GtkNotebook)
+
+int nautilus_notebook_add_tab (NautilusNotebook *nb,
+ NautilusWindowSlot *slot,
+ int position,
+ gboolean jump_to);
+
+void nautilus_notebook_sync_tab_label (NautilusNotebook *nb,
+ NautilusWindowSlot *slot);
+void nautilus_notebook_sync_loading (NautilusNotebook *nb,
+ NautilusWindowSlot *slot);
+
+void nautilus_notebook_reorder_current_child_relative (NautilusNotebook *notebook,
+ int offset);
+gboolean nautilus_notebook_can_reorder_current_child_relative (NautilusNotebook *notebook,
+ int offset);
+void nautilus_notebook_prev_page (NautilusNotebook *notebook);
+void nautilus_notebook_next_page (NautilusNotebook *notebook);
+
+gboolean nautilus_notebook_contains_slot (NautilusNotebook *notebook,
+ NautilusWindowSlot *slot);
+
+gboolean nautilus_notebook_content_area_hit (NautilusNotebook *notebook,
+ gint x,
+ gint y);
+
+G_END_DECLS
diff --git a/src/nautilus-operations-ui-manager.c b/src/nautilus-operations-ui-manager.c
new file mode 100644
index 0000000..a932a2d
--- /dev/null
+++ b/src/nautilus-operations-ui-manager.c
@@ -0,0 +1,576 @@
+#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;
+ gpointer user_data;
+ 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 = user_data;
+
+ g_mutex_lock (&data->mutex);
+
+ while (data->source_func (data->user_data))
+ {
+ }
+
+ data->completed = TRUE;
+
+ g_cond_signal (&data->cond);
+ g_mutex_unlock (&data->mutex);
+
+ return G_SOURCE_REMOVE;
+}
+
+/* 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 the condition is set in the UI thread.
+ */
+static void
+invoke_main_context_sync (GMainContext *main_context,
+ GSourceFunc source_func,
+ gpointer user_data)
+{
+ ContextInvokeData 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;
+ data.user_data = user_data;
+
+ 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,
+ &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
+{
+ GFile *source_name;
+ GFile *destination_name;
+ GFile *destination_directory_name;
+
+ GtkWindow *parent;
+
+ FileConflictResponse *response;
+
+ NautilusFile *source;
+ NautilusFile *destination;
+ NautilusFile *destination_directory;
+
+ 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);
+
+ 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)
+{
+ GdkPixbuf *source_pixbuf;
+ GdkPixbuf *destination_pixbuf;
+
+ destination_pixbuf = nautilus_file_get_icon_pixbuf (data->destination,
+ NAUTILUS_CANVAS_ICON_SIZE_SMALL,
+ TRUE,
+ 1,
+ NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS);
+
+ source_pixbuf = nautilus_file_get_icon_pixbuf (data->source,
+ NAUTILUS_CANVAS_ICON_SIZE_SMALL,
+ TRUE,
+ 1,
+ NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS);
+
+ nautilus_file_conflict_dialog_set_images (data->dialog,
+ destination_pixbuf,
+ source_pixbuf);
+
+ g_object_unref (destination_pixbuf);
+ g_object_unref (source_pixbuf);
+}
+
+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_name (FileConflictDialogData *data)
+{
+ g_autofree gchar *edit_name = NULL;
+
+ edit_name = nautilus_file_get_edit_name (data->destination);
+
+ nautilus_file_conflict_dialog_set_conflict_name (data->dialog,
+ edit_name);
+}
+
+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_name (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 gboolean
+run_file_conflict_dialog (gpointer user_data)
+{
+ FileConflictDialogData *data = user_data;
+ int response_id;
+ GList *files = NULL;
+
+ data->source = nautilus_file_get (data->source_name);
+ data->destination = nautilus_file_get (data->destination_name);
+ data->destination_directory = nautilus_file_get (data->destination_directory_name);
+
+ data->dialog = nautilus_file_conflict_dialog_new (data->parent);
+
+ files = g_list_prepend (files, data->source);
+ files = g_list_prepend (files, data->destination);
+ files = g_list_prepend (files, data->destination_directory);
+
+ 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);
+
+ response_id = gtk_dialog_run (GTK_DIALOG (data->dialog));
+
+ 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_widget_destroy (GTK_WIDGET (data->dialog));
+
+ nautilus_file_unref (data->source);
+ nautilus_file_unref (data->destination);
+ nautilus_file_unref (data->destination_directory);
+ g_list_free (files);
+
+ return G_SOURCE_REMOVE;
+}
+
+FileConflictResponse *
+copy_move_conflict_ask_user_action (GtkWindow *parent_window,
+ GFile *source_name,
+ GFile *destination_name,
+ GFile *destination_directory_name)
+{
+ FileConflictDialogData *data;
+ FileConflictResponse *response;
+
+ data = g_slice_new0 (FileConflictDialogData);
+ data->parent = parent_window;
+ data->source_name = source_name;
+ data->destination_name = destination_name;
+ data->destination_directory_name = destination_directory_name;
+
+ 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
+{
+ GtkWindow *parent_window;
+ NautilusFile *file;
+} HandleUnsupportedFileData;
+
+static gboolean
+open_file_in_application (gpointer user_data)
+{
+ HandleUnsupportedFileData *data;
+ g_autofree gchar *mime_type = NULL;
+ GtkWidget *dialog;
+ const char *heading;
+ g_autoptr (GAppInfo) application = NULL;
+
+ 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);
+
+ if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK)
+ {
+ application = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (dialog));
+ }
+
+ gtk_widget_destroy (dialog);
+
+ if (application != NULL)
+ {
+ g_autoptr (GList) files = NULL;
+
+ files = g_list_append (NULL, data->file);
+
+ nautilus_launch_application (application, files, data->parent_window);
+ }
+
+ 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;
+}
diff --git a/src/nautilus-operations-ui-manager.h b/src/nautilus-operations-ui-manager.h
new file mode 100644
index 0000000..032ffb5
--- /dev/null
+++ b/src/nautilus-operations-ui-manager.h
@@ -0,0 +1,27 @@
+#pragma once
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+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,
+ GFile *src,
+ GFile *dest,
+ GFile *dest_dir);
+
+enum
+{
+ CONFLICT_RESPONSE_SKIP = 1,
+ CONFLICT_RESPONSE_REPLACE = 2,
+ CONFLICT_RESPONSE_RENAME = 3,
+};
+
+void handle_unsupported_compressed_file (GtkWindow *parent_window,
+ GFile *compressed_file); \ No newline at end of file
diff --git a/src/nautilus-other-locations-window-slot.c b/src/nautilus-other-locations-window-slot.c
new file mode 100644
index 0000000..0c6bf2f
--- /dev/null
+++ b/src/nautilus-other-locations-window-slot.c
@@ -0,0 +1,83 @@
+/* nautilus-other-locations-window-slot.c
+ *
+ * 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 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-other-locations-window-slot.h"
+
+#include "nautilus-file.h"
+#include "nautilus-places-view.h"
+#include "nautilus-view.h"
+
+struct _NautilusOtherLocationsWindowSlot
+{
+ NautilusWindowSlot parent_instance;
+};
+
+G_DEFINE_TYPE (NautilusOtherLocationsWindowSlot, nautilus_other_locations_window_slot, NAUTILUS_TYPE_WINDOW_SLOT)
+
+static gboolean
+real_handles_location (NautilusWindowSlot *self,
+ GFile *location)
+{
+ NautilusFile *file;
+ gboolean handles_location;
+
+ file = nautilus_file_get (location);
+ handles_location = nautilus_file_is_other_locations (file);
+ nautilus_file_unref (file);
+
+ return handles_location;
+}
+
+static NautilusView *
+real_get_view_for_location (NautilusWindowSlot *self,
+ GFile *location)
+{
+ return NAUTILUS_VIEW (nautilus_places_view_new ());
+}
+
+NautilusOtherLocationsWindowSlot *
+nautilus_other_locations_window_slot_new (NautilusWindow *window)
+{
+ return g_object_new (NAUTILUS_TYPE_OTHER_LOCATIONS_WINDOW_SLOT,
+ "window", window,
+ NULL);
+}
+
+static void
+nautilus_other_locations_window_slot_class_init (NautilusOtherLocationsWindowSlotClass *klass)
+{
+ NautilusWindowSlotClass *parent_class = NAUTILUS_WINDOW_SLOT_CLASS (klass);
+
+ parent_class->get_view_for_location = real_get_view_for_location;
+ parent_class->handles_location = real_handles_location;
+}
+
+static void
+nautilus_other_locations_window_slot_init (NautilusOtherLocationsWindowSlot *self)
+{
+ GAction *action;
+ GActionGroup *action_group;
+
+ /* Disable the ability to change between types of views */
+ action_group = gtk_widget_get_action_group (GTK_WIDGET (self), "slot");
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (action_group), "files-view-mode");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
+ action = g_action_map_lookup_action (G_ACTION_MAP (action_group), "files-view-mode-toggle");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
+}
diff --git a/src/nautilus-other-locations-window-slot.h b/src/nautilus-other-locations-window-slot.h
new file mode 100644
index 0000000..70136d1
--- /dev/null
+++ b/src/nautilus-other-locations-window-slot.h
@@ -0,0 +1,33 @@
+/* nautilus-other-locations-window-slot.h
+ *
+ * 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 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 "nautilus-window-slot.h"
+
+#include "nautilus-types.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_OTHER_LOCATIONS_WINDOW_SLOT (nautilus_other_locations_window_slot_get_type())
+
+G_DECLARE_FINAL_TYPE (NautilusOtherLocationsWindowSlot, nautilus_other_locations_window_slot, NAUTILUS, OTHER_LOCATIONS_WINDOW_SLOT, NautilusWindowSlot)
+
+NautilusOtherLocationsWindowSlot *nautilus_other_locations_window_slot_new (NautilusWindow *window);
+
+G_END_DECLS
diff --git a/src/nautilus-pathbar.c b/src/nautilus-pathbar.c
new file mode 100644
index 0000000..edc0fff
--- /dev/null
+++ b/src/nautilus-pathbar.c
@@ -0,0 +1,1699 @@
+/* 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-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,
+ PATH_CLICKED,
+ 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_MAX_WIDTH 175
+
+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 *disclosure_arrow;
+ GtkWidget *container;
+
+ NautilusPathBar *path_bar;
+
+ GtkGesture *multi_press_gesture;
+
+ guint ignore_changes : 1;
+ guint is_root : 1;
+} ButtonData;
+
+struct _NautilusPathBar
+{
+ GtkContainer parent_instance;
+
+ GdkWindow *event_window;
+
+ GFile *current_path;
+ gpointer current_button_data;
+
+ GList *button_list;
+ gulong settings_signal_id;
+
+ GActionGroup *action_group;
+
+ NautilusFile *context_menu_file;
+ GtkPopover *current_view_menu_popover;
+ GtkPopover *button_menu_popover;
+ GMenu *current_view_menu;
+ GMenu *extensions_section;
+ GMenu *templates_submenu;
+ GMenu *button_menu;
+};
+
+G_DEFINE_TYPE (NautilusPathBar, nautilus_path_bar, GTK_TYPE_CONTAINER);
+
+static void nautilus_path_bar_check_icon_theme (NautilusPathBar *self);
+static void nautilus_path_bar_update_button_appearance (ButtonData *button_data,
+ gboolean current_dir);
+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);
+
+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, GTK_PLACES_OPEN_NEW_TAB);
+ 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, GTK_PLACES_OPEN_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
+nautilus_path_bar_init (NautilusPathBar *self)
+{
+ GtkBuilder *builder;
+ g_autoptr (GError) error = NULL;
+
+ 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 = g_object_ref_sink (GTK_POPOVER (gtk_popover_new_from_model (NULL,
+ G_MENU_MODEL (self->button_menu))));
+
+ /* Add current location menu, which matches the view's background context menu */
+ gtk_builder_add_from_resource (builder,
+ "/org/gnome/nautilus/ui/nautilus-files-view-context-menus.ui",
+ &error);
+ if (error != NULL)
+ {
+ g_error ("failed to add files-view-context-menus.ui: %s", error->message);
+ }
+ self->current_view_menu = g_object_ref_sink (G_MENU (gtk_builder_get_object (builder, "background-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 (gtk_popover_new_from_model (NULL,
+ G_MENU_MODEL (self->current_view_menu))));
+
+ g_object_unref (builder);
+
+ gtk_widget_set_has_window (GTK_WIDGET (self), FALSE);
+ gtk_widget_set_redraw_on_allocate (GTK_WIDGET (self), FALSE);
+
+ gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (self)),
+ GTK_STYLE_CLASS_LINKED);
+ gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (self)),
+ "nautilus-path-bar");
+
+ /* 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_list_free (self->button_list);
+ 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->button_menu_popover);
+ g_clear_object (&self->current_view_menu_popover);
+
+ unschedule_pop_up_context_menu (NAUTILUS_PATH_BAR (object));
+
+ G_OBJECT_CLASS (nautilus_path_bar_parent_class)->finalize (object);
+}
+
+/* Removes the settings signal handler. It's safe to call multiple times */
+static void
+remove_settings_signal (NautilusPathBar *self,
+ GdkScreen *screen)
+{
+ GtkSettings *settings;
+
+ settings = gtk_settings_get_for_screen (screen);
+
+ g_clear_signal_handler (&self->settings_signal_id, settings);
+}
+
+static void
+nautilus_path_bar_dispose (GObject *object)
+{
+ NautilusPathBar *self;
+
+ self = NAUTILUS_PATH_BAR (object);
+
+ remove_settings_signal (self, gtk_widget_get_screen (GTK_WIDGET (object)));
+
+ 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:
+ {
+ /* Translators: This is the label used in the pathbar when seeing
+ * the root directory (also known as /) */
+ return _("Computer");
+ }
+
+ 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;
+ }
+ }
+}
+
+/* We always want to request the same size for the label, whether
+ * or not the contents are bold
+ */
+static void
+set_label_size_request (ButtonData *button_data)
+{
+ gint width;
+ GtkRequisition nat_req;
+
+ if (button_data->label == NULL)
+ {
+ return;
+ }
+
+ gtk_widget_get_preferred_size (button_data->label, NULL, &nat_req);
+
+ width = MIN (nat_req.width, NAUTILUS_PATH_BAR_BUTTON_MAX_WIDTH);
+
+ gtk_widget_set_size_request (button_data->label, width, nat_req.height);
+}
+
+/* Size requisition:
+ *
+ * Ideally, our size is determined by another widget, and we are just filling
+ * available space.
+ */
+static void
+nautilus_path_bar_get_preferred_width (GtkWidget *widget,
+ gint *minimum,
+ gint *natural)
+{
+ ButtonData *button_data;
+ NautilusPathBar *self;
+ GList *list;
+ gint child_height;
+ gint height;
+ gint child_min, child_nat;
+
+ self = NAUTILUS_PATH_BAR (widget);
+
+ *minimum = *natural = 0;
+ height = 0;
+
+ for (list = self->button_list; list; list = list->next)
+ {
+ button_data = BUTTON_DATA (list->data);
+ set_label_size_request (button_data);
+
+ gtk_widget_get_preferred_width (button_data->button, &child_min, &child_nat);
+ gtk_widget_get_preferred_height (button_data->button, &child_height, NULL);
+ height = MAX (height, child_height);
+
+ if (button_data->type == NORMAL_BUTTON)
+ {
+ /* Use 2*Height as button width because of ellipsized label. */
+ child_min = MAX (child_min, child_height * 2);
+ child_nat = MAX (child_min, child_height * 2);
+ }
+
+ *minimum = MAX (*minimum, child_min);
+ *natural = *natural + child_nat;
+ }
+}
+
+static void
+nautilus_path_bar_get_preferred_height (GtkWidget *widget,
+ gint *minimum,
+ gint *natural)
+{
+ ButtonData *button_data;
+ NautilusPathBar *self;
+ GList *list;
+ gint child_min, child_nat;
+
+ self = NAUTILUS_PATH_BAR (widget);
+
+ *minimum = *natural = 0;
+
+ for (list = self->button_list; list; list = list->next)
+ {
+ button_data = BUTTON_DATA (list->data);
+ set_label_size_request (button_data);
+
+ gtk_widget_get_preferred_height (button_data->button, &child_min, &child_nat);
+
+ *minimum = MAX (*minimum, child_min);
+ *natural = MAX (*natural, child_nat);
+ }
+}
+
+static void
+nautilus_path_bar_unmap (GtkWidget *widget)
+{
+ NautilusPathBar *self;
+
+ self = NAUTILUS_PATH_BAR (widget);
+
+ gdk_window_hide (self->event_window);
+
+ GTK_WIDGET_CLASS (nautilus_path_bar_parent_class)->unmap (widget);
+}
+
+static void
+nautilus_path_bar_map (GtkWidget *widget)
+{
+ NautilusPathBar *self;
+
+ self = NAUTILUS_PATH_BAR (widget);
+
+ gdk_window_show (self->event_window);
+
+ GTK_WIDGET_CLASS (nautilus_path_bar_parent_class)->map (widget);
+}
+
+#define BUTTON_BOTTOM_SHADOW 1
+
+static void
+union_with_clip (GtkWidget *widget,
+ gpointer clip)
+{
+ GtkAllocation widget_clip;
+
+ if (!gtk_widget_is_drawable (widget))
+ {
+ return;
+ }
+
+ gtk_widget_get_clip (widget, &widget_clip);
+
+ gdk_rectangle_union (&widget_clip, clip, clip);
+}
+
+static void
+_set_simple_bottom_clip (GtkWidget *widget,
+ gint pixels)
+{
+ GtkAllocation clip;
+
+ gtk_widget_get_allocation (widget, &clip);
+ clip.height += pixels;
+
+ gtk_container_forall (GTK_CONTAINER (widget), union_with_clip, &clip);
+ gtk_widget_set_clip (widget, &clip);
+}
+
+/* This is a tad complicated */
+static void
+nautilus_path_bar_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ NautilusPathBar *self;
+ GtkWidget *child;
+ GtkTextDirection direction;
+ GtkAllocation child_allocation;
+ GList *list, *first_button;
+ gint width;
+ gint largest_width;
+ GtkRequisition child_requisition;
+
+ self = NAUTILUS_PATH_BAR (widget);
+
+ gtk_widget_set_allocation (widget, allocation);
+
+ if (gtk_widget_get_realized (widget))
+ {
+ gdk_window_move_resize (self->event_window,
+ allocation->x, allocation->y,
+ allocation->width, allocation->height);
+ }
+
+ /* No path is set so we don't have to allocate anything. */
+ if (self->button_list == NULL)
+ {
+ _set_simple_bottom_clip (widget, BUTTON_BOTTOM_SHADOW);
+ return;
+ }
+ direction = gtk_widget_get_direction (widget);
+
+ width = 0;
+
+ gtk_widget_get_preferred_size (BUTTON_DATA (self->button_list->data)->button,
+ &child_requisition, NULL);
+ width += child_requisition.width;
+
+ for (list = self->button_list->next; list; list = list->next)
+ {
+ child = BUTTON_DATA (list->data)->button;
+ gtk_widget_get_preferred_size (child, &child_requisition, NULL);
+ width += child_requisition.width;
+ }
+
+ if (width <= allocation->width)
+ {
+ first_button = g_list_last (self->button_list);
+ }
+ else
+ {
+ gboolean reached_end;
+ reached_end = FALSE;
+
+ first_button = self->button_list;
+
+ /* To see how much space we have, and how many buttons we can display.
+ * We start at the first button, count forward until hit the new
+ * button, then count backwards.
+ */
+ /* Count down the path chain towards the end. */
+ gtk_widget_get_preferred_size (BUTTON_DATA (first_button->data)->button,
+ &child_requisition, NULL);
+ width = child_requisition.width;
+ list = first_button->prev;
+ while (list && !reached_end)
+ {
+ child = BUTTON_DATA (list->data)->button;
+ gtk_widget_get_preferred_size (child, &child_requisition, NULL);
+
+ if (width + child_requisition.width > allocation->width)
+ {
+ reached_end = TRUE;
+ }
+ else
+ {
+ width += child_requisition.width;
+ }
+
+ list = list->prev;
+ }
+
+ /* Finally, we walk up, seeing how many of the previous buttons we can add*/
+
+ while (first_button->next && !reached_end)
+ {
+ child = BUTTON_DATA (first_button->next->data)->button;
+ gtk_widget_get_preferred_size (child, &child_requisition, NULL);
+
+ if (width + child_requisition.width > allocation->width)
+ {
+ reached_end = TRUE;
+ }
+ else
+ {
+ width += child_requisition.width;
+ first_button = first_button->next;
+ }
+ }
+ }
+
+ /* Now, we allocate space to the buttons */
+ child_allocation.y = allocation->y;
+ child_allocation.height = allocation->height;
+
+ if (direction == GTK_TEXT_DIR_RTL)
+ {
+ child_allocation.x = allocation->x + allocation->width;
+ }
+ else
+ {
+ child_allocation.x = allocation->x;
+ }
+
+ /* Determine the largest possible allocation size */
+ largest_width = allocation->width;
+ for (list = first_button; list; list = list->prev)
+ {
+ child = BUTTON_DATA (list->data)->button;
+ gtk_widget_get_preferred_size (child, &child_requisition, NULL);
+
+ child_allocation.width = MIN (child_requisition.width, largest_width);
+ if (direction == GTK_TEXT_DIR_RTL)
+ {
+ child_allocation.x -= child_allocation.width;
+ }
+ /* Check to see if we've don't have any more space to allocate buttons */
+
+ gtk_widget_set_child_visible (child, TRUE);
+ gtk_widget_size_allocate (child, &child_allocation);
+
+ if (direction == GTK_TEXT_DIR_LTR)
+ {
+ child_allocation.x += child_allocation.width;
+ }
+ }
+ /* Now we go hide all the widgets that don't fit */
+ while (list)
+ {
+ child = BUTTON_DATA (list->data)->button;
+ gtk_widget_set_child_visible (child, FALSE);
+ list = list->prev;
+ }
+ for (list = first_button->next; list; list = list->next)
+ {
+ child = BUTTON_DATA (list->data)->button;
+ gtk_widget_set_child_visible (child, FALSE);
+ }
+
+ _set_simple_bottom_clip (widget, BUTTON_BOTTOM_SHADOW);
+}
+
+static void
+nautilus_path_bar_style_updated (GtkWidget *widget)
+{
+ GTK_WIDGET_CLASS (nautilus_path_bar_parent_class)->style_updated (widget);
+
+ nautilus_path_bar_check_icon_theme (NAUTILUS_PATH_BAR (widget));
+}
+
+static void
+nautilus_path_bar_screen_changed (GtkWidget *widget,
+ GdkScreen *previous_screen)
+{
+ if (GTK_WIDGET_CLASS (nautilus_path_bar_parent_class)->screen_changed)
+ {
+ GTK_WIDGET_CLASS (nautilus_path_bar_parent_class)->screen_changed (widget, previous_screen);
+ }
+ /* We might nave a new settings, so we remove the old one */
+ if (previous_screen)
+ {
+ remove_settings_signal (NAUTILUS_PATH_BAR (widget), previous_screen);
+ }
+ nautilus_path_bar_check_icon_theme (NAUTILUS_PATH_BAR (widget));
+}
+
+static void
+nautilus_path_bar_realize (GtkWidget *widget)
+{
+ NautilusPathBar *self;
+ GtkAllocation allocation;
+ GdkWindow *window;
+ GdkWindowAttr attributes;
+ gint attributes_mask;
+
+ gtk_widget_set_realized (widget, TRUE);
+
+ self = NAUTILUS_PATH_BAR (widget);
+
+ window = gtk_widget_get_parent_window (widget);
+ gtk_widget_set_window (widget, window);
+ g_object_ref (window);
+
+ gtk_widget_get_allocation (widget, &allocation);
+
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.x = allocation.x;
+ attributes.y = allocation.y;
+ attributes.width = allocation.width;
+ attributes.height = allocation.height;
+ attributes.wclass = GDK_INPUT_ONLY;
+ attributes.event_mask = gtk_widget_get_events (widget);
+ attributes.event_mask |=
+ GDK_BUTTON_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK |
+ GDK_POINTER_MOTION_MASK;
+ attributes_mask = GDK_WA_X | GDK_WA_Y;
+
+ self->event_window = gdk_window_new (gtk_widget_get_parent_window (widget),
+ &attributes, attributes_mask);
+ gdk_window_set_user_data (self->event_window, widget);
+}
+
+static void
+nautilus_path_bar_unrealize (GtkWidget *widget)
+{
+ NautilusPathBar *self;
+
+ self = NAUTILUS_PATH_BAR (widget);
+
+ gdk_window_set_user_data (self->event_window, NULL);
+ gdk_window_destroy (self->event_window);
+ self->event_window = NULL;
+
+ GTK_WIDGET_CLASS (nautilus_path_bar_parent_class)->unrealize (widget);
+}
+
+static void
+nautilus_path_bar_add (GtkContainer *container,
+ GtkWidget *widget)
+{
+ gtk_widget_set_parent (widget, GTK_WIDGET (container));
+}
+
+static void
+nautilus_path_bar_remove_1 (GtkContainer *container,
+ GtkWidget *widget)
+{
+ gboolean was_visible = gtk_widget_get_visible (widget);
+ gtk_widget_unparent (widget);
+ if (was_visible)
+ {
+ gtk_widget_queue_resize (GTK_WIDGET (container));
+ }
+}
+
+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_clear_object (&button_data->multi_press_gesture);
+
+ g_free (button_data);
+}
+
+static void
+nautilus_path_bar_remove (GtkContainer *container,
+ GtkWidget *widget)
+{
+ NautilusPathBar *self;
+ GList *children;
+
+ self = NAUTILUS_PATH_BAR (container);
+
+ children = self->button_list;
+ while (children != NULL)
+ {
+ if (widget == BUTTON_DATA (children->data)->button)
+ {
+ nautilus_path_bar_remove_1 (container, widget);
+ self->button_list = g_list_remove_link (self->button_list, children);
+ button_data_free (children->data);
+ g_list_free_1 (children);
+ return;
+ }
+ children = children->next;
+ }
+}
+
+static void
+nautilus_path_bar_forall (GtkContainer *container,
+ gboolean include_internals,
+ GtkCallback callback,
+ gpointer callback_data)
+{
+ NautilusPathBar *self;
+ GList *children;
+
+ g_return_if_fail (callback != NULL);
+ self = NAUTILUS_PATH_BAR (container);
+
+ children = self->button_list;
+ while (children != NULL)
+ {
+ GtkWidget *child;
+ child = BUTTON_DATA (children->data)->button;
+ children = children->next;
+ (*callback)(child, callback_data);
+ }
+}
+
+static GtkWidgetPath *
+nautilus_path_bar_get_path_for_child (GtkContainer *container,
+ GtkWidget *child)
+{
+ NautilusPathBar *self;
+ GtkWidgetPath *path;
+
+ self = NAUTILUS_PATH_BAR (container);
+ path = gtk_widget_path_copy (gtk_widget_get_path (GTK_WIDGET (self)));
+
+ if (gtk_widget_get_visible (child) &&
+ gtk_widget_get_child_visible (child))
+ {
+ GtkWidgetPath *sibling_path;
+ GList *visible_children;
+ GList *l;
+ int pos;
+
+ /* 1. Build the list of visible children, in visually left-to-right order
+ * (i.e. independently of the widget's direction). Note that our
+ * button_list is stored in innermost-to-outermost path order!
+ */
+
+ visible_children = NULL;
+
+ for (l = self->button_list; l; l = l->next)
+ {
+ ButtonData *data = l->data;
+
+ if (gtk_widget_get_visible (data->button) &&
+ gtk_widget_get_child_visible (data->button))
+ {
+ visible_children = g_list_prepend (visible_children, data->button);
+ }
+ }
+
+ if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL)
+ {
+ visible_children = g_list_reverse (visible_children);
+ }
+
+ /* 2. Find the index of the child within that list */
+
+ pos = 0;
+
+ for (l = visible_children; l; l = l->next)
+ {
+ GtkWidget *button = l->data;
+
+ if (button == child)
+ {
+ break;
+ }
+
+ pos++;
+ }
+
+ /* 3. Build the path */
+
+ sibling_path = gtk_widget_path_new ();
+
+ for (l = visible_children; l; l = l->next)
+ {
+ gtk_widget_path_append_for_widget (sibling_path, l->data);
+ }
+
+ gtk_widget_path_append_with_siblings (path, sibling_path, pos);
+
+ g_list_free (visible_children);
+ gtk_widget_path_unref (sibling_path);
+ }
+ else
+ {
+ gtk_widget_path_append_for_widget (path, child);
+ }
+
+ return path;
+}
+
+static void
+nautilus_path_bar_class_init (NautilusPathBarClass *path_bar_class)
+{
+ GObjectClass *gobject_class;
+ GtkWidgetClass *widget_class;
+ GtkContainerClass *container_class;
+
+ gobject_class = (GObjectClass *) path_bar_class;
+ widget_class = (GtkWidgetClass *) path_bar_class;
+ container_class = (GtkContainerClass *) path_bar_class;
+
+ gobject_class->finalize = nautilus_path_bar_finalize;
+ gobject_class->dispose = nautilus_path_bar_dispose;
+
+ widget_class->get_preferred_height = nautilus_path_bar_get_preferred_height;
+ widget_class->get_preferred_width = nautilus_path_bar_get_preferred_width;
+ widget_class->realize = nautilus_path_bar_realize;
+ widget_class->unrealize = nautilus_path_bar_unrealize;
+ widget_class->unmap = nautilus_path_bar_unmap;
+ widget_class->map = nautilus_path_bar_map;
+ widget_class->size_allocate = nautilus_path_bar_size_allocate;
+ widget_class->style_updated = nautilus_path_bar_style_updated;
+ widget_class->screen_changed = nautilus_path_bar_screen_changed;
+
+ container_class->add = nautilus_path_bar_add;
+ container_class->forall = nautilus_path_bar_forall;
+ container_class->remove = nautilus_path_bar_remove;
+ container_class->get_path_for_child = nautilus_path_bar_get_path_for_child;
+
+ 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,
+ GTK_TYPE_PLACES_OPEN_FLAGS);
+ path_bar_signals [PATH_CLICKED] =
+ g_signal_new ("path-clicked",
+ G_OBJECT_CLASS_TYPE (path_bar_class),
+ G_SIGNAL_RUN_FIRST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ G_TYPE_FILE);
+
+ gtk_container_class_handle_border_width (container_class);
+}
+
+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)
+{
+ g_return_if_fail (NAUTILUS_IS_PATH_BAR (self));
+
+ nautilus_gmenu_set_from_model (self->templates_submenu, menu);
+}
+
+/* Changes the icons wherever it is needed */
+static void
+reload_icons (NautilusPathBar *self)
+{
+ GList *list;
+
+ for (list = self->button_list; list; list = list->next)
+ {
+ ButtonData *button_data;
+
+ button_data = BUTTON_DATA (list->data);
+ if (button_data->type != NORMAL_BUTTON || button_data->is_root)
+ {
+ nautilus_path_bar_update_button_appearance (button_data,
+ list->next == NULL);
+ }
+ }
+}
+
+/* Callback used when a GtkSettings value changes */
+static void
+settings_notify_cb (GObject *object,
+ GParamSpec *pspec,
+ NautilusPathBar *self)
+{
+ const char *name;
+
+ name = g_param_spec_get_name (pspec);
+
+ if (!strcmp (name, "gtk-icon-theme-name") || !strcmp (name, "gtk-icon-sizes"))
+ {
+ reload_icons (self);
+ }
+}
+
+static void
+nautilus_path_bar_check_icon_theme (NautilusPathBar *self)
+{
+ GtkSettings *settings;
+
+ if (self->settings_signal_id)
+ {
+ return;
+ }
+
+ settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (self)));
+ self->settings_signal_id = g_signal_connect (settings, "notify", G_CALLBACK (settings_notify_cb), self);
+
+ reload_icons (self);
+}
+
+/* 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_container_remove (GTK_CONTAINER (self), button_data->button);
+ }
+}
+
+static void
+button_clicked_cb (GtkButton *button,
+ gpointer data)
+{
+ ButtonData *button_data;
+ NautilusPathBar *self;
+ GdkModifierType state;
+
+ button_data = BUTTON_DATA (data);
+ if (button_data->ignore_changes)
+ {
+ return;
+ }
+
+ self = button_data->path_bar;
+
+ gtk_get_current_event_state (&state);
+
+ if ((state & GDK_CONTROL_MASK) != 0)
+ {
+ g_signal_emit (button_data->path_bar, path_bar_signals[OPEN_LOCATION], 0,
+ button_data->path,
+ GTK_PLACES_OPEN_NEW_WINDOW);
+ }
+ else
+ {
+ if (g_file_equal (button_data->path, self->current_path))
+ {
+ gtk_popover_popup (self->current_view_menu_popover);
+ }
+ 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 (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_multi_press_gesture_pressed (GtkGestureMultiPress *gesture,
+ gint n_press,
+ gdouble x,
+ gdouble y,
+ gpointer user_data)
+{
+ ButtonData *button_data;
+ NautilusPathBar *self;
+ guint current_button;
+
+ 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));
+
+ switch (current_button)
+ {
+ case GDK_BUTTON_MIDDLE:
+ {
+ GdkModifierType state;
+
+ gtk_get_current_event_state (&state);
+ state &= gtk_accelerator_get_default_mod_mask ();
+ if (state == 0)
+ {
+ g_signal_emit (self, path_bar_signals[OPEN_LOCATION], 0,
+ button_data->path,
+ GTK_PLACES_OPEN_NEW_TAB);
+ }
+ }
+ break;
+
+ case GDK_BUTTON_SECONDARY:
+ {
+ if (g_file_equal (button_data->path, self->current_path))
+ {
+ gtk_popover_popup (self->current_view_menu_popover);
+ }
+ else
+ {
+ gtk_popover_set_relative_to (self->button_menu_popover,
+ button_data->button);
+ pop_up_pathbar_context_menu (self, button_data->file);
+ }
+ }
+ break;
+
+ default:
+ {
+ /* Ignore other buttons in this gesture. GtkButton will claim the
+ * primary button presses and emit the "clicked" signal.
+ */
+ 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);
+ GIcon *icon;
+
+ if (button_data->label != NULL)
+ {
+ gtk_label_set_text (GTK_LABEL (button_data->label), dir_name);
+ }
+
+ icon = get_gicon (button_data);
+ if (icon != NULL)
+ {
+ gtk_image_set_from_gicon (GTK_IMAGE (button_data->image), icon, GTK_ICON_SIZE_MENU);
+ gtk_style_context_add_class (gtk_widget_get_style_context (button_data->button),
+ "image-button");
+ gtk_widget_show (GTK_WIDGET (button_data->image));
+ g_object_unref (icon);
+ }
+ else
+ {
+ gtk_widget_hide (GTK_WIDGET (button_data->image));
+ if (!current_dir)
+ {
+ gtk_style_context_remove_class (gtk_widget_get_style_context (button_data->button),
+ "image-button");
+ }
+ }
+}
+
+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);
+ gtk_label_set_use_markup (GTK_LABEL (button_data->label), current_dir);
+ }
+
+ 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_container_remove (GTK_CONTAINER (self), data->button);
+ }
+ }
+ }
+
+ 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;
+ ButtonData *button_data;
+ GtkStyleContext *style_context;
+
+ 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);
+
+ style_context = gtk_widget_get_style_context (button_data->button);
+ gtk_style_context_add_class (style_context, "text-button");
+ /* 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);
+ button_data->disclosure_arrow = gtk_image_new_from_icon_name ("pan-down-symbolic",
+ GTK_ICON_SIZE_MENU);
+ gtk_widget_set_margin_start (button_data->disclosure_arrow, 0);
+ button_data->container = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_container_add (GTK_CONTAINER (button_data->button), button_data->container);
+
+ gtk_box_pack_start (GTK_BOX (button_data->container), button_data->image, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (button_data->container), button_data->label, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (button_data->container), button_data->disclosure_arrow, FALSE, FALSE, 0);
+ }
+ break;
+
+ case NORMAL_BUTTON:
+ /* Fall through */
+ default:
+ {
+ button_data->label = gtk_label_new (NULL);
+ button_data->disclosure_arrow = gtk_image_new_from_icon_name ("pan-down-symbolic",
+ GTK_ICON_SIZE_MENU);
+ gtk_widget_set_margin_start (button_data->disclosure_arrow, 0);
+ button_data->container = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_container_add (GTK_CONTAINER (button_data->button), button_data->container);
+
+ gtk_box_pack_start (GTK_BOX (button_data->container), button_data->label, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (button_data->container), button_data->disclosure_arrow, FALSE, FALSE, 0);
+ }
+ break;
+ }
+
+ gtk_widget_set_no_show_all (button_data->disclosure_arrow, TRUE);
+ if (current_dir)
+ {
+ gtk_widget_show (button_data->disclosure_arrow);
+ gtk_popover_set_relative_to (self->current_view_menu_popover, button_data->button);
+ gtk_style_context_add_class (gtk_widget_get_style_context (button_data->button),
+ "image-button");
+ }
+
+ if (button_data->label != NULL)
+ {
+ gtk_label_set_ellipsize (GTK_LABEL (button_data->label), PANGO_ELLIPSIZE_MIDDLE);
+ gtk_label_set_single_line_mode (GTK_LABEL (button_data->label), TRUE);
+ }
+
+ 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_widget_show_all (button_data->button);
+
+ nautilus_path_bar_update_button_state (button_data, current_dir);
+
+ button_data->path_bar = self;
+
+ 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.
+ */
+ button_data->multi_press_gesture = gtk_gesture_multi_press_new (button_data->button);
+
+ gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (button_data->multi_press_gesture), 0);
+
+ g_signal_connect (button_data->multi_press_gesture, "pressed",
+ G_CALLBACK (on_multi_press_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);
+ self->button_list = g_list_reverse (new_buttons);
+
+ for (l = self->button_list; l; l = l->next)
+ {
+ GtkWidget *button;
+ button = BUTTON_DATA (l->data)->button;
+ gtk_container_add (GTK_CONTAINER (self), button);
+ }
+}
+
+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);
+
+ /* Check whether the new path is already present in the pathbar as buttons.
+ * This could be a parent directory or a previous selected subdirectory. */
+ 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..2e91e29
--- /dev/null
+++ b/src/nautilus-pathbar.h
@@ -0,0 +1,33 @@
+/* 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, GtkContainer)
+
+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);
diff --git a/src/nautilus-places-view.c b/src/nautilus-places-view.c
new file mode 100644
index 0000000..9776118
--- /dev/null
+++ b/src/nautilus-places-view.c
@@ -0,0 +1,430 @@
+/* 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 "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;
+ NautilusToolbarMenuSections *toolbar_menu_sections;
+
+ 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,
+ GtkPlacesOpenFlags open_flags)
+{
+ NautilusWindowOpenFlags flags;
+ GtkWidget *slot;
+
+ slot = gtk_widget_get_ancestor (GTK_WIDGET (view), NAUTILUS_TYPE_WINDOW_SLOT);
+
+ switch (open_flags)
+ {
+ case GTK_PLACES_OPEN_NEW_TAB:
+ {
+ flags = NAUTILUS_WINDOW_OPEN_FLAG_NEW_TAB |
+ NAUTILUS_WINDOW_OPEN_FLAG_DONT_MAKE_ACTIVE;
+ }
+ break;
+
+ case GTK_PLACES_OPEN_NEW_WINDOW:
+ {
+ flags = NAUTILUS_WINDOW_OPEN_FLAG_NEW_WINDOW;
+ }
+ break;
+
+ case GTK_PLACES_OPEN_NORMAL: /* fall-through */
+ default:
+ {
+ flags = 0;
+ }
+ break;
+ }
+
+ if (slot)
+ {
+ NautilusFile *file;
+ GtkWidget *window;
+ char *path;
+
+ path = "other-locations:///";
+ file = nautilus_file_get (location);
+ window = gtk_widget_get_toplevel (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;
+ GtkWidget *window;
+
+ window = gtk_widget_get_toplevel (GTK_WIDGET (view));
+
+ dialog = gtk_message_dialog_new (GTK_WINDOW (window),
+ GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ "%s", primary);
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ "%s", secondary);
+
+ gtk_dialog_run (GTK_DIALOG (dialog));
+
+ gtk_widget_destroy (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_free (priv->toolbar_menu_sections);
+
+ 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)
+{
+ NautilusPlacesViewPrivate *priv;
+
+ priv = nautilus_places_view_get_instance_private (NAUTILUS_PLACES_VIEW (view));
+
+ return priv->toolbar_menu_sections;
+}
+
+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),
+ GTK_PLACES_OPEN_NEW_TAB | GTK_PLACES_OPEN_NEW_WINDOW | GTK_PLACES_OPEN_NORMAL);
+ gtk_widget_set_hexpand (priv->places_view, TRUE);
+ gtk_widget_set_vexpand (priv->places_view, TRUE);
+ gtk_widget_show (priv->places_view);
+ gtk_container_add (GTK_CONTAINER (self), priv->places_view);
+
+ g_signal_connect_swapped (priv->places_view,
+ "notify::loading",
+ G_CALLBACK (loading_cb),
+ self);
+
+ g_signal_connect_swapped (priv->places_view,
+ "open-location",
+ G_CALLBACK (open_location_cb),
+ self);
+
+ g_signal_connect_swapped (priv->places_view,
+ "show-error-message",
+ G_CALLBACK (show_error_message_cb),
+ self);
+
+ /* Toolbar menu */
+ priv->toolbar_menu_sections = g_new0 (NautilusToolbarMenuSections, 1);
+ priv->toolbar_menu_sections->supports_undo_redo = FALSE;
+}
+
+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..d75d9dc
--- /dev/null
+++ b/src/nautilus-preferences-window.c
@@ -0,0 +1,546 @@
+/* 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 <string.h>
+#include <time.h>
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+
+#include <glib/gi18n.h>
+
+#include <eel/eel-glib-extensions.h>
+
+#include <nautilus-extension.h>
+
+#include "nautilus-column-chooser.h"
+#include "nautilus-column-utilities.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-module.h"
+
+/* string enum preferences */
+#define NAUTILUS_PREFERENCES_DIALOG_DEFAULT_VIEW_WIDGET "default_view_combobox"
+#define NAUTILUS_PREFERENCES_DIALOG_PREVIEW_FILES_WIDGET \
+ "preview_image_combobox"
+#define NAUTILUS_PREFERENCES_DIALOG_PREVIEW_FOLDER_WIDGET \
+ "preview_folder_combobox"
+
+/* bool preferences */
+#define NAUTILUS_PREFERENCES_DIALOG_FOLDERS_FIRST_WIDGET \
+ "sort_folders_first_checkbutton"
+#define NAUTILUS_PREFERENCS_DIALOG_START_WITH_SIDEBAR \
+ "show_sidebar_checkbutton"
+#define NAUTILUS_PREFERENCES_DIALOG_DELETE_PERMANENTLY_WIDGET \
+ "show_delete_permanently_checkbutton"
+#define NAUTILUS_PREFERENCES_DIALOG_CREATE_LINK_WIDGET \
+ "show_create_link_checkbutton"
+#define NAUTILUS_PREFERENCES_DIALOG_LIST_VIEW_USE_TREE_WIDGET \
+ "use_tree_view_checkbutton"
+#define NAUTILUS_PREFERENCES_DIALOG_TRASH_CONFIRM_WIDGET \
+ "trash_confirm_checkbutton"
+#define NAUTILUS_PREFERENCES_DIALOG_USE_NEW_VIEWS_WIDGET \
+ "use_new_views_checkbutton"
+
+/* int enums */
+#define NAUTILUS_PREFERENCES_DIALOG_THUMBNAIL_LIMIT_WIDGET \
+ "preview_image_size_spinbutton"
+
+static const char * const speed_tradeoff_values[] =
+{
+ "local-only", "always", "never",
+ NULL
+};
+
+static const char * const click_behavior_components[] =
+{
+ "single_click_radiobutton", "double_click_radiobutton", NULL
+};
+
+static const char * const click_behavior_values[] = {"single", "double", NULL};
+
+static const char * const executable_text_components[] =
+{
+ "scripts_execute_radiobutton", "scripts_view_radiobutton",
+ "scripts_confirm_radiobutton", NULL
+};
+
+static const char * const executable_text_values[] =
+{
+ "launch", "display", "ask",
+ NULL
+};
+
+static const char * const recursive_search_components[] =
+{
+ "search_recursive_only_this_computer_radiobutton", "search_recursive_all_locations_radiobutton", "search_recursive_never_radiobutton", NULL
+};
+
+static const char * const thumbnails_components[] =
+{
+ "thumbnails_only_this_computer_radiobutton", "thumbnails_all_files_radiobutton", "thumbnails_never_radiobutton", NULL
+};
+
+static const char * const count_components[] =
+{
+ "count_only_this_computer_radiobutton", "count_all_files_radiobutton", "count_never_radiobutton", NULL
+};
+
+static const char * const icon_captions_components[] =
+{
+ "captions_0_combobox", "captions_1_combobox", "captions_2_combobox", NULL
+};
+
+static GtkWidget *preferences_window = NULL;
+
+static void columns_changed_callback(NautilusColumnChooser *chooser,
+ gpointer callback_data)
+{
+ char **visible_columns;
+ char **column_order;
+
+ nautilus_column_chooser_get_settings (NAUTILUS_COLUMN_CHOOSER (chooser),
+ &visible_columns, &column_order);
+
+ g_settings_set_strv (nautilus_list_view_preferences,
+ NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS,
+ (const char * const *) visible_columns);
+ g_settings_set_strv (nautilus_list_view_preferences,
+ NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_COLUMN_ORDER,
+ (const char * const *) column_order);
+
+ g_strfreev (visible_columns);
+ g_strfreev (column_order);
+}
+
+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_box_items(GtkComboBoxText *combo_box,
+ GList *columns)
+{
+ GList *l;
+ GPtrArray *column_names;
+
+ column_names = g_ptr_array_new ();
+
+ /* Translators: this is referred to captions under icons. */
+ gtk_combo_box_text_append_text (combo_box, _("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;
+ }
+
+ gtk_combo_box_text_append_text (combo_box, label);
+ g_ptr_array_add (column_names, name);
+
+ g_free (label);
+ }
+ g_object_set_data_full (G_OBJECT (combo_box), "column_names", column_names,
+ (GDestroyNotify) free_column_names_array);
+}
+
+static void icon_captions_changed_callback(GtkComboBox *widget,
+ 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_box;
+ int active;
+ GPtrArray *column_names;
+ char *name;
+
+ combo_box = GTK_WIDGET (
+ gtk_builder_get_object (builder, icon_captions_components[i]));
+ active = gtk_combo_box_get_active (GTK_COMBO_BOX (combo_box));
+
+ column_names = g_object_get_data (G_OBJECT (combo_box), "column_names");
+
+ name = g_ptr_array_index (column_names, active);
+ 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_box(GtkBuilder *builder,
+ const char *combo_box_name,
+ const char *name)
+{
+ GtkWidget *combo_box;
+ int i;
+ GPtrArray *column_names;
+
+ combo_box = GTK_WIDGET (gtk_builder_get_object (builder, combo_box_name));
+
+ g_signal_handlers_block_by_func (
+ combo_box, G_CALLBACK (icon_captions_changed_callback), builder);
+
+ column_names = g_object_get_data (G_OBJECT (combo_box), "column_names");
+
+ for (i = 0; i < column_names->len; ++i)
+ {
+ if (!strcmp (name, g_ptr_array_index (column_names, i)))
+ {
+ gtk_combo_box_set_active (GTK_COMBO_BOX (combo_box), i);
+ break;
+ }
+ }
+
+ g_signal_handlers_unblock_by_func (
+ combo_box, 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_box (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_box;
+
+ combo_box = GTK_WIDGET (
+ gtk_builder_get_object (builder, icon_captions_components[i]));
+
+ create_icon_caption_combo_box_items (GTK_COMBO_BOX_TEXT (combo_box), columns);
+ gtk_widget_set_sensitive (combo_box, writable);
+
+ g_signal_connect_data (
+ combo_box, "changed", 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 set_columns_from_settings(NautilusColumnChooser *chooser)
+{
+ char **visible_columns;
+ char **column_order;
+
+ visible_columns = g_settings_get_strv (
+ nautilus_list_view_preferences,
+ NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS);
+ column_order =
+ g_settings_get_strv (nautilus_list_view_preferences,
+ NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_COLUMN_ORDER);
+
+ nautilus_column_chooser_set_settings (NAUTILUS_COLUMN_CHOOSER (chooser),
+ visible_columns, column_order);
+
+ g_strfreev (visible_columns);
+ g_strfreev (column_order);
+}
+
+static void use_default_callback(NautilusColumnChooser *chooser,
+ gpointer user_data)
+{
+ g_settings_reset (nautilus_list_view_preferences,
+ NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS);
+ g_settings_reset (nautilus_list_view_preferences,
+ NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_COLUMN_ORDER);
+ set_columns_from_settings (chooser);
+}
+
+static void
+nautilus_preferences_window_setup_list_column_page (GtkBuilder *builder)
+{
+ GtkWidget *chooser;
+ GtkWidget *box;
+
+ chooser = nautilus_column_chooser_new (NULL);
+ g_signal_connect (chooser, "changed", G_CALLBACK (columns_changed_callback),
+ chooser);
+ g_signal_connect (chooser, "use-default", G_CALLBACK (use_default_callback),
+ chooser);
+
+ set_columns_from_settings (NAUTILUS_COLUMN_CHOOSER (chooser));
+
+ gtk_widget_show (chooser);
+ box = GTK_WIDGET (gtk_builder_get_object (builder, "list_columns_vbox"));
+
+ gtk_box_pack_start (GTK_BOX (box), chooser, TRUE, TRUE, 0);
+}
+
+static gboolean
+format_spin_button (GtkSpinButton *spin_button,
+ gpointer user_data)
+{
+ gint value;
+ gchar *text;
+
+ value = gtk_spin_button_get_value_as_int (spin_button);
+ text = g_strdup_printf (_("%d MB"), value);
+ gtk_entry_set_text (GTK_ENTRY (spin_button), text);
+
+ return TRUE;
+}
+
+static void nautilus_preferences_window_setup_thumbnail_limit_formatting (GtkBuilder *builder)
+{
+ GtkSpinButton *spin;
+
+ spin = GTK_SPIN_BUTTON (gtk_builder_get_object (builder, "preview_image_size_spinbutton"));
+
+ g_signal_connect (spin, "output", G_CALLBACK (format_spin_button),
+ spin);
+}
+
+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 void bind_builder_uint_spin(GtkBuilder *builder,
+ GSettings *settings,
+ const char *widget_name,
+ const char *prefs)
+{
+ g_settings_bind (settings, prefs, gtk_builder_get_object (builder, widget_name),
+ "value", G_SETTINGS_BIND_DEFAULT);
+}
+
+static GVariant *radio_mapping_set(const GValue *gvalue,
+ const GVariantType *expected_type,
+ gpointer user_data)
+{
+ const gchar *widget_value = user_data;
+ GVariant *retval = NULL;
+
+ if (g_value_get_boolean (gvalue))
+ {
+ retval = g_variant_new_string (widget_value);
+ }
+
+ return retval;
+}
+
+static gboolean radio_mapping_get(GValue *gvalue,
+ GVariant *variant,
+ gpointer user_data)
+{
+ const gchar *widget_value = user_data;
+ const gchar *value;
+
+ value = g_variant_get_string (variant, NULL);
+
+ if (g_strcmp0 (value, widget_value) == 0)
+ {
+ g_value_set_boolean (gvalue, TRUE);
+ }
+ else
+ {
+ g_value_set_boolean (gvalue, FALSE);
+ }
+
+ return TRUE;
+}
+
+static void bind_builder_radio(GtkBuilder *builder,
+ GSettings *settings,
+ const char **widget_names,
+ const char *prefs,
+ const char **values)
+{
+ GtkWidget *button;
+ int i;
+
+ for (i = 0; widget_names[i] != NULL; i++)
+ {
+ button = GTK_WIDGET (gtk_builder_get_object (builder, widget_names[i]));
+
+ g_settings_bind_with_mapping (settings, prefs, button, "active",
+ G_SETTINGS_BIND_DEFAULT, radio_mapping_get,
+ radio_mapping_set, (gpointer) values[i], NULL);
+ }
+}
+
+static void nautilus_preferences_window_setup(GtkBuilder *builder,
+ GtkWindow *parent_window)
+{
+ GtkWidget *window;
+
+ /* 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_window_state,
+ NAUTILUS_PREFERENCS_DIALOG_START_WITH_SIDEBAR,
+ NAUTILUS_WINDOW_STATE_START_WITH_SIDEBAR);
+ bind_builder_bool (builder, nautilus_preferences,
+ NAUTILUS_PREFERENCES_DIALOG_TRASH_CONFIRM_WIDGET,
+ NAUTILUS_PREFERENCES_CONFIRM_TRASH);
+ 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_radio (
+ builder, nautilus_preferences, (const char **) click_behavior_components,
+ NAUTILUS_PREFERENCES_CLICK_POLICY, (const char **) click_behavior_values);
+ bind_builder_radio (builder, nautilus_preferences,
+ (const char **) executable_text_components,
+ NAUTILUS_PREFERENCES_EXECUTABLE_TEXT_ACTIVATION,
+ (const char **) executable_text_values);
+ bind_builder_radio (builder, nautilus_preferences,
+ (const char **) recursive_search_components,
+ NAUTILUS_PREFERENCES_RECURSIVE_SEARCH,
+ (const char **) speed_tradeoff_values);
+ bind_builder_radio (builder, nautilus_preferences,
+ (const char **) thumbnails_components,
+ NAUTILUS_PREFERENCES_SHOW_FILE_THUMBNAILS,
+ (const char **) speed_tradeoff_values);
+ bind_builder_radio (builder, nautilus_preferences,
+ (const char **) count_components,
+ NAUTILUS_PREFERENCES_SHOW_DIRECTORY_ITEM_COUNTS,
+ (const char **) speed_tradeoff_values);
+
+ bind_builder_uint_spin (builder, nautilus_preferences,
+ NAUTILUS_PREFERENCES_DIALOG_THUMBNAIL_LIMIT_WIDGET,
+ NAUTILUS_PREFERENCES_FILE_THUMBNAIL_LIMIT);
+
+ nautilus_preferences_window_setup_thumbnail_limit_formatting (builder);
+ nautilus_preferences_window_setup_icon_caption_page (builder);
+ nautilus_preferences_window_setup_list_column_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);
+
+ /* This statement is necessary because GtkDialog defaults to 2px. Will not
+ * be necessary in GTK+4.
+ */
+ gtk_container_set_border_width (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (preferences_window))),
+ 0);
+
+ gtk_widget_show (preferences_window);
+}
+
+void nautilus_preferences_window_show(GtkWindow *window)
+{
+ GtkBuilder *builder;
+
+ 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", NULL);
+
+ 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..47362a3
--- /dev/null
+++ b/src/nautilus-program-choosing.c
@@ -0,0 +1,623 @@
+/* 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);
+
+ if (parent_window != NULL)
+ {
+ gdk_app_launch_context_set_screen (launch_context,
+ gtk_window_get_screen (parent_window));
+ }
+
+ 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,
+ GdkScreen *screen,
+ 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 && screen == NULL))
+ {
+ GdkDisplay *display;
+ g_autoptr (GdkAppLaunchContext) context = NULL;
+
+ display = gdk_screen_get_display (screen);
+ context = gdk_display_get_app_launch_context (display);
+ gdk_app_launch_context_set_screen (context, screen);
+
+ 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 (GdkScreen *screen,
+ 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, screen, 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 (GdkScreen *screen,
+ 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, screen, use_terminal);
+
+ g_free (full_command);
+}
+
+void
+nautilus_launch_desktop_file (GdkScreen *screen,
+ 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);
+ gdk_app_launch_context_set_screen (context,
+ gtk_window_get_screen (parent_window));
+ 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..51881ff
--- /dev/null
+++ b/src/nautilus-program-choosing.h
@@ -0,0 +1,60 @@
+
+/* 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 (GdkScreen *screen,
+ const char *command_string,
+ gboolean use_terminal,
+ ...) G_GNUC_NULL_TERMINATED;
+void nautilus_launch_application_from_command_array (GdkScreen *screen,
+ const char *command_string,
+ gboolean use_terminal,
+ const char * const * parameters);
+void nautilus_launch_desktop_file (GdkScreen *screen,
+ 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); \ No newline at end of file
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..3d24133
--- /dev/null
+++ b/src/nautilus-progress-info-widget.c
@@ -0,0 +1,225 @@
+/*
+ * 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;
+ GtkWidget *done_image;
+};
+
+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_image (GTK_BUTTON (self->priv->button), self->priv->done_image);
+ 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_image (GTK_BUTTON (self->priv->button), self->priv->done_image);
+ }
+
+ 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);
+ gtk_widget_class_bind_template_child_private (widget_class, NautilusProgressInfoWidget, done_image);
+}
+
+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..64f45e9
--- /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 successfully 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_has_toplevel_focus (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..c83ef30
--- /dev/null
+++ b/src/nautilus-properties-window.c
@@ -0,0 +1,5667 @@
+/* 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-gtk-extensions.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 <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-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-signaller.h"
+#include "nautilus-ui-utilities.h"
+#include "nautilus-signaller.h"
+
+#define PREVIEW_IMAGE_WIDTH 96
+
+#define ROW_PAD 6
+
+static GHashTable *windows;
+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
+{
+ GtkWindow parent_instance;
+
+ GList *original_files;
+ GList *target_files;
+
+ GtkNotebook *notebook;
+
+ /* Basic tab widgets */
+
+ GtkStack *icon_stack;
+ GtkWidget *icon_image;
+ GtkWidget *icon_button;
+ GtkWidget *icon_button_image;
+ GtkWidget *icon_chooser;
+
+ GtkGrid *basic_grid;
+
+ GtkLabel *name_title_label;
+ GtkStack *name_stack;
+ GtkWidget *name_field;
+ char *pending_name;
+
+ guint select_idle_id;
+
+ GtkWidget *type_title_label;
+ GtkWidget *type_value_label;
+
+ GtkWidget *link_target_title_label;
+ GtkWidget *link_target_value_label;
+
+ GtkWidget *contents_title_label;
+ GtkWidget *contents_value_label;
+ GtkWidget *contents_spinner;
+ guint update_directory_contents_timeout_id;
+ guint update_files_timeout_id;
+
+ GtkWidget *size_title_label;
+ GtkWidget *size_value_label;
+
+ GtkWidget *parent_folder_title_label;
+ GtkWidget *parent_folder_value_label;
+
+ GtkWidget *original_folder_title_label;
+ GtkWidget *original_folder_value_label;
+
+ GtkWidget *volume_title_label;
+ GtkWidget *volume_value_label;
+
+ GtkWidget *trashed_on_title_label;
+ GtkWidget *trashed_on_value_label;
+
+ GtkWidget *spacer_2;
+
+ GtkWidget *accessed_title_label;
+ GtkWidget *accessed_value_label;
+
+ GtkWidget *modified_title_label;
+ GtkWidget *modified_value_label;
+
+ GtkWidget *spacer_3;
+
+ GtkWidget *free_space_title_label;
+ GtkWidget *free_space_value_label;
+
+ GtkWidget *volume_widget_box;
+ GtkWidget *open_in_disks_button;
+
+ GtkWidget *pie_chart;
+ GtkWidget *used_color;
+ GtkWidget *used_value;
+ GtkWidget *free_color;
+ GtkWidget *free_value;
+ GtkWidget *total_capacity_value;
+ GtkWidget *file_system_value;
+
+ /* Permissions tab Widgets */
+
+ GtkWidget *permissions_box;
+ GtkWidget *permissions_grid;
+
+ GtkWidget *bottom_prompt_seperator;
+ GtkWidget *not_the_owner_label;
+
+ GtkWidget *permission_indeterminable_label;
+
+ GtkWidget *owner_value_stack;
+ GtkWidget *owner_access_label;
+ GtkWidget *owner_access_combo;
+ GtkWidget *owner_folder_access_label;
+ GtkWidget *owner_folder_access_combo;
+ GtkWidget *owner_file_access_label;
+ GtkWidget *owner_file_access_combo;
+
+ GtkWidget *group_value_stack;
+ GtkWidget *group_access_label;
+ GtkWidget *group_access_combo;
+ GtkWidget *group_folder_access_label;
+ GtkWidget *group_folder_access_combo;
+ GtkWidget *group_file_access_label;
+ GtkWidget *group_file_access_combo;
+
+ GtkWidget *others_access_label;
+ GtkWidget *others_access_combo;
+ GtkWidget *others_folder_access_label;
+ GtkWidget *others_folder_access_combo;
+ GtkWidget *others_file_access_label;
+ GtkWidget *others_file_access_combo;
+
+ GtkWidget *execute_label;
+ GtkWidget *execute_checkbox;
+
+ GtkWidget *security_context_title_label;
+ GtkWidget *security_context_value_label;
+
+ GtkWidget *change_permissions_button_box;
+ GtkWidget *change_permissions_button;
+
+ /* Open With tab Widgets */
+
+ GtkWidget *open_with_box;
+ GtkWidget *open_with_label;
+ GtkWidget *app_chooser_widget_box;
+ GtkWidget *app_chooser_widget;
+ GtkWidget *reset_button;
+ GtkWidget *add_button;
+ GtkWidget *set_as_default_button;
+ char *content_type;
+ GList *open_with_files;
+
+ GroupChange *group_change;
+ OwnerChange *owner_change;
+
+ GList *permission_buttons;
+ GList *permission_combos;
+ 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;
+};
+
+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;
+
+/* drag and drop definitions */
+
+enum
+{
+ TARGET_URI_LIST,
+ TARGET_GNOME_URI_LIST,
+};
+
+static const GtkTargetEntry target_table[] =
+{
+ { "text/uri-list", 0, TARGET_URI_LIST },
+ { "x-special/gnome-icon-list", 0, TARGET_GNOME_URI_LIST },
+};
+
+#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 *window);
+static void directory_contents_value_field_update (NautilusPropertiesWindow *window);
+static void file_changed_callback (NautilusFile *file,
+ gpointer user_data);
+static void permission_button_update (NautilusPropertiesWindow *window,
+ GtkToggleButton *button);
+static void permission_combo_update (NautilusPropertiesWindow *window,
+ GtkComboBox *combo);
+static void value_field_update (NautilusPropertiesWindow *window,
+ GtkLabel *field);
+static void properties_window_update (NautilusPropertiesWindow *window,
+ 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 select_image_button_callback (GtkWidget *widget,
+ NautilusPropertiesWindow *properties_window);
+static void set_icon (const char *icon_path,
+ NautilusPropertiesWindow *properties_window);
+static void remove_pending (StartupData *data,
+ gboolean cancel_call_when_ready,
+ gboolean cancel_timed_wait);
+static void append_extension_pages (NautilusPropertiesWindow *window);
+
+static void name_field_focus_changed (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data);
+static void name_field_activate (GtkWidget *name_field,
+ gpointer user_data);
+static void setup_pie_widget (NautilusPropertiesWindow *window);
+
+G_DEFINE_TYPE (NautilusPropertiesWindow, nautilus_properties_window, GTK_TYPE_WINDOW);
+
+static gboolean
+is_multi_file_window (NautilusPropertiesWindow *window)
+{
+ GList *l;
+ int count;
+
+ count = 0;
+
+ for (l = window->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 int
+get_not_gone_original_file_count (NautilusPropertiesWindow *window)
+{
+ GList *l;
+ int count;
+
+ count = 0;
+
+ for (l = window->original_files; l != NULL; l = l->next)
+ {
+ if (!nautilus_file_is_gone (NAUTILUS_FILE (l->data)))
+ {
+ count++;
+ }
+ }
+
+ return count;
+}
+
+static NautilusFile *
+get_original_file (NautilusPropertiesWindow *window)
+{
+ g_return_val_if_fail (!is_multi_file_window (window), NULL);
+
+ if (window->original_files == NULL)
+ {
+ return NULL;
+ }
+
+ return NAUTILUS_FILE (window->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 *window)
+{
+ return NAUTILUS_FILE (window->target_files->data);
+}
+
+static void
+get_image_for_properties_window (NautilusPropertiesWindow *window,
+ char **icon_name,
+ GdkPixbuf **icon_pixbuf)
+{
+ NautilusIconInfo *icon, *new_icon;
+ GList *l;
+ gint icon_scale;
+
+ icon = NULL;
+ icon_scale = gtk_widget_get_scale_factor (GTK_WIDGET (window->notebook));
+
+ for (l = window->original_files; l != NULL; l = l->next)
+ {
+ NautilusFile *file;
+
+ file = NAUTILUS_FILE (l->data);
+
+ if (!icon)
+ {
+ icon = nautilus_file_get_icon (file, NAUTILUS_CANVAS_ICON_SIZE_STANDARD, icon_scale,
+ NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS |
+ NAUTILUS_FILE_ICON_FLAGS_IGNORE_VISITING);
+ }
+ else
+ {
+ new_icon = nautilus_file_get_icon (file, NAUTILUS_CANVAS_ICON_SIZE_STANDARD, icon_scale,
+ NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS |
+ NAUTILUS_FILE_ICON_FLAGS_IGNORE_VISITING);
+ if (!new_icon || new_icon != icon)
+ {
+ g_object_unref (icon);
+ g_object_unref (new_icon);
+ icon = NULL;
+ break;
+ }
+ g_object_unref (new_icon);
+ }
+ }
+
+ if (!icon)
+ {
+ icon = nautilus_icon_info_lookup_from_name ("text-x-generic",
+ NAUTILUS_CANVAS_ICON_SIZE_STANDARD,
+ icon_scale);
+ }
+
+ if (icon_name != NULL)
+ {
+ *icon_name = g_strdup (nautilus_icon_info_get_used_name (icon));
+ }
+
+ if (icon_pixbuf != NULL)
+ {
+ *icon_pixbuf = nautilus_icon_info_get_pixbuf_at_size (icon, NAUTILUS_CANVAS_ICON_SIZE_STANDARD);
+ }
+
+ g_object_unref (icon);
+}
+
+
+static void
+update_properties_window_icon (NautilusPropertiesWindow *window)
+{
+ GdkPixbuf *pixbuf;
+ cairo_surface_t *surface;
+ char *name;
+
+ get_image_for_properties_window (window, &name, &pixbuf);
+
+ if (name != NULL)
+ {
+ gtk_window_set_icon_name (GTK_WINDOW (window), name);
+ }
+ else
+ {
+ gtk_window_set_icon (GTK_WINDOW (window), pixbuf);
+ }
+
+ surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, gtk_widget_get_scale_factor (GTK_WIDGET (window)),
+ gtk_widget_get_window (GTK_WIDGET (window)));
+ gtk_image_set_from_surface (GTK_IMAGE (window->icon_image), surface);
+ gtk_image_set_from_surface (GTK_IMAGE (window->icon_button_image), surface);
+
+ g_free (name);
+ g_object_unref (pixbuf);
+ cairo_surface_destroy (surface);
+}
+
+/* utility to test if a uri refers to a local image */
+static gboolean
+uri_is_local_image (const char *uri)
+{
+ GdkPixbuf *pixbuf;
+ char *image_path;
+
+ image_path = g_filename_from_uri (uri, NULL, NULL);
+ if (image_path == NULL)
+ {
+ return FALSE;
+ }
+
+ pixbuf = gdk_pixbuf_new_from_file (image_path, NULL);
+ g_free (image_path);
+
+ if (pixbuf == NULL)
+ {
+ return FALSE;
+ }
+ g_object_unref (pixbuf);
+ return TRUE;
+}
+
+
+static void
+reset_icon (NautilusPropertiesWindow *properties_window)
+{
+ GList *l;
+
+ for (l = properties_window->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_data_received (GtkWidget *widget,
+ GdkDragContext *context,
+ int x,
+ int y,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint time)
+{
+ char **uris;
+ gboolean exactly_one;
+ GtkImage *image;
+ GtkWindow *window;
+
+ image = GTK_IMAGE (widget);
+ window = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (image)));
+
+ uris = g_strsplit ((const gchar *) gtk_selection_data_get_data (selection_data), "\r\n", 0);
+ exactly_one = uris[0] != NULL && (uris[1] == NULL || uris[1][0] == '\0');
+
+
+ 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
+ {
+ if (uri_is_local_image (uris[0]))
+ {
+ set_icon (uris[0], NAUTILUS_PROPERTIES_WINDOW (window));
+ }
+ else
+ {
+ GFile *f;
+
+ f = g_file_new_for_uri (uris[0]);
+ if (!g_file_is_native (f))
+ {
+ 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);
+ }
+ g_object_unref (f);
+ }
+ }
+ g_strfreev (uris);
+}
+
+static void
+setup_image_widget (NautilusPropertiesWindow *window,
+ gboolean is_customizable)
+{
+ update_properties_window_icon (window);
+
+ if (is_customizable)
+ {
+ /* prepare the image to receive dropped objects to assign custom images */
+ gtk_drag_dest_set (window->icon_button_image,
+ GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP,
+ target_table, G_N_ELEMENTS (target_table),
+ GDK_ACTION_COPY | GDK_ACTION_MOVE);
+
+ g_signal_connect (window->icon_button_image, "drag-data-received",
+ G_CALLBACK (nautilus_properties_window_drag_data_received), NULL);
+ g_signal_connect (window->icon_button, "clicked",
+ G_CALLBACK (select_image_button_callback), window);
+ gtk_stack_set_visible_child (window->icon_stack, window->icon_button);
+ }
+ else
+ {
+ gtk_stack_set_visible_child (window->icon_stack, window->icon_image);
+ }
+}
+
+static void
+set_name_field (NautilusPropertiesWindow *window,
+ const gchar *original_name,
+ const gchar *name)
+{
+ GtkWidget *stack_child_label;
+ GtkWidget *stack_child_entry;
+ gboolean use_label;
+
+ stack_child_label = gtk_stack_get_child_by_name (window->name_stack, "name_value_label");
+ stack_child_entry = gtk_stack_get_child_by_name (window->name_stack, "name_value_entry");
+
+ use_label = is_multi_file_window (window) || !nautilus_file_can_rename (get_original_file (window));
+
+ if (use_label)
+ {
+ gtk_label_set_text (GTK_LABEL (stack_child_label), name);
+ gtk_stack_set_visible_child (window->name_stack, stack_child_label);
+ }
+ else
+ {
+ gtk_stack_set_visible_child (window->name_stack, stack_child_entry);
+ }
+
+ /* Only replace text if the file's name has changed. */
+ if (original_name == NULL || strcmp (original_name, name) != 0)
+ {
+ if (!use_label)
+ {
+ /* Only reset the text if it's different from what is
+ * currently showing. This causes minimal ripples (e.g.
+ * selection change).
+ */
+ gchar *displayed_name = gtk_editable_get_chars (GTK_EDITABLE (window->name_field), 0, -1);
+ if (strcmp (displayed_name, name) != 0)
+ {
+ gtk_entry_set_text (GTK_ENTRY (window->name_field), name);
+ }
+ g_free (displayed_name);
+ }
+ }
+}
+
+static void
+update_name_field (NautilusPropertiesWindow *window)
+{
+ NautilusFile *file;
+
+ gtk_label_set_text_with_mnemonic (window->name_title_label,
+ ngettext ("_Name", "_Names",
+ get_not_gone_original_file_count (window)));
+
+ if (is_multi_file_window (window))
+ {
+ /* Multifile property dialog, show all names */
+ GString *str;
+ char *name;
+ gboolean first;
+ GList *l;
+
+ str = g_string_new ("");
+
+ first = TRUE;
+
+ for (l = window->target_files; l != NULL; l = l->next)
+ {
+ file = NAUTILUS_FILE (l->data);
+
+ if (!nautilus_file_is_gone (file))
+ {
+ if (!first)
+ {
+ g_string_append (str, ", ");
+ }
+ first = FALSE;
+
+ name = nautilus_file_get_display_name (file);
+ g_string_append (str, name);
+ g_free (name);
+ }
+ }
+ set_name_field (window, NULL, str->str);
+ g_string_free (str, TRUE);
+ }
+ else
+ {
+ const char *original_name = NULL;
+ char *current_name;
+
+ file = get_original_file (window);
+
+ if (file == NULL || nautilus_file_is_gone (file))
+ {
+ current_name = g_strdup ("");
+ }
+ else
+ {
+ current_name = nautilus_file_get_display_name (file);
+ }
+
+ /* If the file name has changed since the original name was stored,
+ * update the text in the text field, possibly (deliberately) clobbering
+ * an edit in progress. If the name hasn't changed (but some other
+ * aspect of the file might have), then don't clobber changes.
+ */
+ original_name = (const char *) g_object_get_data (G_OBJECT (window->name_field), "original_name");
+
+ set_name_field (window, original_name, current_name);
+
+ if (original_name == NULL ||
+ g_strcmp0 (original_name, current_name) != 0)
+ {
+ g_object_set_data_full (G_OBJECT (window->name_field),
+ "original_name",
+ current_name,
+ g_free);
+ }
+ else
+ {
+ g_free (current_name);
+ }
+ }
+}
+
+static void
+name_field_restore_original_name (GtkWidget *name_field)
+{
+ const char *original_name;
+ char *displayed_name;
+
+ original_name = (const char *) g_object_get_data (G_OBJECT (name_field),
+ "original_name");
+
+ if (!original_name)
+ {
+ return;
+ }
+
+ displayed_name = gtk_editable_get_chars (GTK_EDITABLE (name_field), 0, -1);
+
+ if (strcmp (original_name, displayed_name) != 0)
+ {
+ gtk_entry_set_text (GTK_ENTRY (name_field), original_name);
+ }
+ gtk_editable_select_region (GTK_EDITABLE (name_field), 0, -1);
+
+ g_free (displayed_name);
+}
+
+static void
+rename_callback (NautilusFile *file,
+ GFile *res_loc,
+ GError *error,
+ gpointer callback_data)
+{
+ NautilusPropertiesWindow *window;
+
+ window = NAUTILUS_PROPERTIES_WINDOW (callback_data);
+
+ /* Complain to user if rename failed. */
+ if (error != NULL)
+ {
+ nautilus_report_error_renaming_file (file,
+ window->pending_name,
+ error,
+ GTK_WINDOW (window));
+ name_field_restore_original_name (window->name_field);
+ }
+
+ g_object_unref (window);
+}
+
+static void
+set_pending_name (NautilusPropertiesWindow *window,
+ const char *name)
+{
+ g_free (window->pending_name);
+ window->pending_name = g_strdup (name);
+}
+
+static void
+name_field_done_editing (GtkWidget *name_field,
+ NautilusPropertiesWindow *window)
+{
+ NautilusFile *file;
+ char *new_name;
+ const char *original_name;
+
+ g_return_if_fail (GTK_IS_ENTRY (name_field));
+
+ /* Don't apply if the dialog has more than one file */
+ if (is_multi_file_window (window))
+ {
+ return;
+ }
+
+ file = get_original_file (window);
+
+ /* This gets called when the window is closed, which might be
+ * caused by the file having been deleted.
+ */
+ if (file == NULL || nautilus_file_is_gone (file))
+ {
+ return;
+ }
+
+ new_name = gtk_editable_get_chars (GTK_EDITABLE (name_field), 0, -1);
+
+ /* Special case: silently revert text if new text is empty. */
+ if (strlen (new_name) == 0)
+ {
+ name_field_restore_original_name (name_field);
+ }
+ else
+ {
+ original_name = (const char *) g_object_get_data (G_OBJECT (window->name_field),
+ "original_name");
+ /* Don't rename if not changed since we read the display name.
+ * This is needed so that we don't save the display name to the
+ * file when nothing is changed */
+ if (strcmp (new_name, original_name) != 0)
+ {
+ set_pending_name (window, new_name);
+ g_object_ref (window);
+ nautilus_file_rename (file, new_name,
+ rename_callback, window);
+ }
+ }
+
+ g_free (new_name);
+}
+
+static void
+name_field_focus_changed (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ GtkWidget *widget;
+
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (user_data));
+
+ widget = GTK_WIDGET (object);
+
+ if (!gtk_widget_has_focus (widget) && gtk_widget_get_sensitive (widget))
+ {
+ name_field_done_editing (widget, NAUTILUS_PROPERTIES_WINDOW (user_data));
+ }
+}
+
+static gboolean
+select_all_at_idle (gpointer user_data)
+{
+ NautilusPropertiesWindow *window;
+
+ window = NAUTILUS_PROPERTIES_WINDOW (user_data);
+
+ gtk_editable_select_region (GTK_EDITABLE (window->name_field),
+ 0, -1);
+
+ window->select_idle_id = 0;
+
+ return FALSE;
+}
+
+static void
+name_field_activate (GtkWidget *name_field,
+ gpointer user_data)
+{
+ NautilusPropertiesWindow *window;
+
+ g_assert (GTK_IS_ENTRY (name_field));
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (user_data));
+
+ window = NAUTILUS_PROPERTIES_WINDOW (user_data);
+
+ /* Accept changes. */
+ name_field_done_editing (name_field, window);
+
+ if (window->select_idle_id == 0)
+ {
+ window->select_idle_id = g_idle_add (select_all_at_idle,
+ window);
+ }
+}
+
+static void
+update_properties_window_title (NautilusPropertiesWindow *window)
+{
+ char *name, *title;
+ NautilusFile *file;
+
+ g_return_if_fail (GTK_IS_WINDOW (window));
+
+ title = g_strdup_printf (_("Properties"));
+
+ if (!is_multi_file_window (window))
+ {
+ file = get_original_file (window);
+
+ if (file != NULL)
+ {
+ g_free (title);
+ name = nautilus_file_get_display_name (file);
+ if (nautilus_file_is_directory (file))
+ {
+ /* To translators: %s is the name of the folder. */
+ title = g_strdup_printf (C_("folder", "%s Properties"), name);
+ }
+ else
+ {
+ /* To translators: %s is the name of the file. */
+ title = g_strdup_printf (C_("file", "%s Properties"), name);
+ }
+
+ g_free (name);
+ }
+ }
+
+ gtk_window_set_title (GTK_WINDOW (window), title);
+
+ g_free (title);
+}
+
+static void
+clear_extension_pages (NautilusPropertiesWindow *window)
+{
+ int i;
+ int num_pages;
+ GtkWidget *page;
+
+ num_pages = gtk_notebook_get_n_pages
+ (GTK_NOTEBOOK (window->notebook));
+
+ for (i = 0; i < num_pages; i++)
+ {
+ page = gtk_notebook_get_nth_page
+ (GTK_NOTEBOOK (window->notebook), i);
+
+ if (g_object_get_data (G_OBJECT (page), "is-extension-page"))
+ {
+ gtk_notebook_remove_page
+ (GTK_NOTEBOOK (window->notebook), i);
+ num_pages--;
+ i--;
+ }
+ }
+}
+
+static void
+refresh_extension_pages (NautilusPropertiesWindow *window)
+{
+ clear_extension_pages (window);
+ append_extension_pages (window);
+}
+
+static void
+remove_from_dialog (NautilusPropertiesWindow *window,
+ NautilusFile *file)
+{
+ int index;
+ GList *original_link;
+ GList *target_link;
+ NautilusFile *original_file;
+ NautilusFile *target_file;
+
+ index = g_list_index (window->target_files, file);
+ if (index == -1)
+ {
+ index = g_list_index (window->original_files, file);
+ g_return_if_fail (index != -1);
+ }
+
+ original_link = g_list_nth (window->original_files, index);
+ target_link = g_list_nth (window->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);
+
+ window->original_files = g_list_remove_link (window->original_files, original_link);
+ g_list_free (original_link);
+
+ window->target_files = g_list_remove_link (window->target_files, target_link);
+ g_list_free (target_link);
+
+ g_hash_table_remove (window->initial_permissions, target_file);
+
+ g_signal_handlers_disconnect_by_func (original_file,
+ G_CALLBACK (file_changed_callback),
+ window);
+ g_signal_handlers_disconnect_by_func (target_file,
+ G_CALLBACK (file_changed_callback),
+ window);
+
+ nautilus_file_monitor_remove (original_file, &window->original_files);
+ nautilus_file_monitor_remove (target_file, &window->target_files);
+
+ nautilus_file_unref (original_file);
+ nautilus_file_unref (target_file);
+}
+
+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 *window)
+{
+ GList *ret;
+ GList *l;
+
+ ret = NULL;
+ for (l = window->target_files; l != NULL; l = l->next)
+ {
+ ret = g_list_append (ret, nautilus_file_get_mime_type (NAUTILUS_FILE (l->data)));
+ }
+ ret = g_list_reverse (ret);
+ return ret;
+}
+
+static gboolean
+start_spinner_callback (NautilusPropertiesWindow *window)
+{
+ gtk_widget_show (window->contents_spinner);
+ gtk_spinner_start (GTK_SPINNER (window->contents_spinner));
+ window->deep_count_spinner_timeout_id = 0;
+
+ return FALSE;
+}
+
+static void
+schedule_start_spinner (NautilusPropertiesWindow *window)
+{
+ if (window->deep_count_spinner_timeout_id == 0)
+ {
+ window->deep_count_spinner_timeout_id
+ = g_timeout_add_seconds (1,
+ (GSourceFunc) start_spinner_callback,
+ window);
+ }
+}
+
+static void
+stop_spinner (NautilusPropertiesWindow *window)
+{
+ gtk_spinner_stop (GTK_SPINNER (window->contents_spinner));
+ gtk_widget_hide (window->contents_spinner);
+ if (window->deep_count_spinner_timeout_id > 0)
+ {
+ g_source_remove (window->deep_count_spinner_timeout_id);
+ window->deep_count_spinner_timeout_id = 0;
+ }
+}
+
+static void
+stop_deep_count_for_file (NautilusPropertiesWindow *window,
+ NautilusFile *file)
+{
+ if (g_list_find (window->deep_count_files, file))
+ {
+ g_signal_handlers_disconnect_by_func (file,
+ G_CALLBACK (schedule_directory_contents_update),
+ window);
+ nautilus_file_unref (file);
+ window->deep_count_files = g_list_remove (window->deep_count_files, file);
+ }
+}
+
+static void
+start_deep_count_for_file (NautilusPropertiesWindow *window,
+ NautilusFile *file)
+{
+ if (!nautilus_file_is_directory (file))
+ {
+ return;
+ }
+
+ if (!g_list_find (window->deep_count_files, file))
+ {
+ nautilus_file_ref (file);
+ window->deep_count_files = g_list_prepend (window->deep_count_files, file);
+
+ nautilus_file_recompute_deep_counts (file);
+ if (!window->deep_count_finished)
+ {
+ g_signal_connect_object (file,
+ "updated-deep-count-in-progress",
+ G_CALLBACK (schedule_directory_contents_update),
+ window, G_CONNECT_SWAPPED);
+ schedule_start_spinner (window);
+ }
+ }
+}
+
+static void
+properties_window_update (NautilusPropertiesWindow *window,
+ GList *files)
+{
+ GList *l;
+ GList *mime_list;
+ GList *tmp;
+ NautilusFile *changed_file;
+ gboolean dirty_original = FALSE;
+ gboolean dirty_target = FALSE;
+
+ if (files == NULL)
+ {
+ dirty_original = TRUE;
+ dirty_target = TRUE;
+ }
+
+ for (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 (window, changed_file);
+ changed_file = NULL;
+
+ if (window->original_files == NULL)
+ {
+ return;
+ }
+ }
+ if (changed_file == NULL ||
+ g_list_find (window->original_files, changed_file))
+ {
+ dirty_original = TRUE;
+ }
+ if (changed_file == NULL ||
+ g_list_find (window->target_files, changed_file))
+ {
+ dirty_target = TRUE;
+ }
+ }
+
+ if (dirty_original)
+ {
+ update_properties_window_title (window);
+ update_properties_window_icon (window);
+ update_name_field (window);
+
+ /* If any of the value fields start to depend on the original
+ * value, value_field_updates should be added here */
+ }
+
+ if (dirty_target)
+ {
+ for (l = window->permission_buttons; l != NULL; l = l->next)
+ {
+ permission_button_update (window, GTK_TOGGLE_BUTTON (l->data));
+ }
+
+ for (l = window->permission_combos; l != NULL; l = l->next)
+ {
+ permission_combo_update (window, GTK_COMBO_BOX (l->data));
+ }
+
+ for (l = window->value_fields; l != NULL; l = l->next)
+ {
+ value_field_update (window, GTK_LABEL (l->data));
+ }
+ }
+
+ mime_list = get_mime_list (window);
+
+ if (!window->mime_list)
+ {
+ window->mime_list = mime_list;
+ }
+ else
+ {
+ if (!mime_list_equal (window->mime_list, mime_list))
+ {
+ refresh_extension_pages (window);
+ }
+
+ g_list_free_full (window->mime_list, g_free);
+ window->mime_list = mime_list;
+ }
+}
+
+static gboolean
+update_files_callback (gpointer data)
+{
+ NautilusPropertiesWindow *window;
+
+ window = NAUTILUS_PROPERTIES_WINDOW (data);
+
+ window->update_files_timeout_id = 0;
+
+ properties_window_update (window, window->changed_files);
+
+ if (window->original_files == NULL)
+ {
+ /* Close the window if no files are left */
+ gtk_widget_destroy (GTK_WIDGET (window));
+ }
+ else
+ {
+ nautilus_file_list_free (window->changed_files);
+ window->changed_files = NULL;
+ }
+
+ return FALSE;
+}
+
+static void
+schedule_files_update (NautilusPropertiesWindow *window)
+{
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (window));
+
+ if (window->update_files_timeout_id == 0)
+ {
+ window->update_files_timeout_id
+ = g_timeout_add (FILES_UPDATE_INTERVAL,
+ update_files_callback,
+ window);
+ }
+}
+
+static gboolean
+file_list_attributes_identical (GList *file_list,
+ const char *attribute_name)
+{
+ gboolean identical;
+ char *first_attr;
+ GList *l;
+
+ first_attr = NULL;
+ identical = TRUE;
+
+ for (l = file_list; l != NULL; l = l->next)
+ {
+ NautilusFile *file;
+
+ 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
+ {
+ char *attr;
+ attr = nautilus_file_get_string_attribute_with_default (file, attribute_name);
+ if (strcmp (attr, first_attr))
+ {
+ identical = FALSE;
+ g_free (attr);
+ break;
+ }
+ g_free (attr);
+ }
+ }
+
+ g_free (first_attr);
+ return identical;
+}
+
+static char *
+file_list_get_string_attribute (GList *file_list,
+ const char *attribute_name,
+ const char *inconsistent_value)
+{
+ if (file_list_attributes_identical (file_list, attribute_name))
+ {
+ GList *l;
+
+ for (l = file_list; l != NULL; l = l->next)
+ {
+ NautilusFile *file;
+
+ file = NAUTILUS_FILE (l->data);
+ if (!nautilus_file_is_gone (file))
+ {
+ return nautilus_file_get_string_attribute_with_default
+ (file,
+ attribute_name);
+ }
+ }
+ return g_strdup (_("unknown"));
+ }
+ else
+ {
+ return g_strdup (inconsistent_value);
+ }
+}
+
+
+static gboolean
+file_list_all_directories (GList *file_list)
+{
+ GList *l;
+ for (l = file_list; l != NULL; l = l->next)
+ {
+ if (!nautilus_file_is_directory (NAUTILUS_FILE (l->data)))
+ {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+#define INCONSISTENT_STATE_STRING \
+ "\xE2\x80\x92"
+
+static gboolean
+location_show_original (NautilusPropertiesWindow *window)
+{
+ 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 (window->original_files, 0));
+ return (file != NULL && !nautilus_file_is_in_recent (file));
+}
+
+static void
+value_field_update (NautilusPropertiesWindow *window,
+ GtkLabel *label)
+{
+ GList *file_list;
+ const char *attribute_name;
+ char *attribute_value;
+ char *inconsistent_string;
+ char *mime_type, *tmp;
+ 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 (window))
+ {
+ file_list = window->original_files;
+ }
+ else
+ {
+ file_list = window->target_files;
+ }
+
+ inconsistent_string = INCONSISTENT_STATE_STRING;
+ attribute_value = file_list_get_string_attribute (file_list,
+ attribute_name,
+ inconsistent_string);
+ if (!strcmp (attribute_name, "detailed_type") && strcmp (attribute_value, inconsistent_string))
+ {
+ mime_type = file_list_get_string_attribute (file_list,
+ "mime_type",
+ inconsistent_string);
+ if (strcmp (mime_type, inconsistent_string))
+ {
+ tmp = attribute_value;
+ attribute_value = g_strdup_printf (C_("MIME type description (MIME type)", "%s (%s)"), attribute_value, mime_type);
+ g_free (tmp);
+ }
+ g_free (mime_type);
+ }
+
+ gtk_label_set_text (label, attribute_value);
+ g_free (attribute_value);
+}
+
+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 *window;
+
+ 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);
+ }
+
+ window = NAUTILUS_PROPERTIES_WINDOW (change->window);
+ if (window->group_change == change)
+ {
+ window->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 *window,
+ NautilusFile *file,
+ const char *group)
+{
+ GroupChange *change;
+
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (window));
+ g_assert (window->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 (window));
+ change->timeout =
+ g_timeout_add (CHOWN_CHGRP_TIMEOUT,
+ (GSourceFunc) schedule_group_change_timeout,
+ change);
+
+ window->group_change = change;
+}
+
+static void
+unschedule_or_cancel_group_change (NautilusPropertiesWindow *window)
+{
+ GroupChange *change;
+
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (window));
+
+ change = window->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);
+ }
+
+ window->group_change = NULL;
+ }
+}
+
+static void
+changed_group_callback (GtkComboBox *combo_box,
+ NautilusFile *file)
+{
+ NautilusPropertiesWindow *window;
+ char *group;
+ char *cur_group;
+
+ g_assert (GTK_IS_COMBO_BOX (combo_box));
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ group = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (combo_box));
+ cur_group = nautilus_file_get_group_name (file);
+
+ if (group != NULL && strcmp (group, cur_group) != 0)
+ {
+ /* Try to change file group. If this fails, complain to user. */
+ window = NAUTILUS_PROPERTIES_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (combo_box), GTK_TYPE_WINDOW));
+
+ unschedule_or_cancel_group_change (window);
+ schedule_group_change (window, file, group);
+ }
+ g_free (group);
+ g_free (cur_group);
+}
+
+/* checks whether the given column at the first level
+ * of model has the specified entries in the given order. */
+static gboolean
+tree_model_entries_equal (GtkTreeModel *model,
+ unsigned int column,
+ GList *entries)
+{
+ GtkTreeIter iter;
+ gboolean empty_model;
+
+ g_assert (GTK_IS_TREE_MODEL (model));
+ g_assert (gtk_tree_model_get_column_type (model, column) == G_TYPE_STRING);
+
+ empty_model = !gtk_tree_model_get_iter_first (model, &iter);
+
+ if (!empty_model && entries != NULL)
+ {
+ GList *l;
+
+ l = entries;
+
+ do
+ {
+ char *val;
+
+ gtk_tree_model_get (model, &iter,
+ column, &val,
+ -1);
+ if ((val == NULL && l->data != NULL) ||
+ (val != NULL && l->data == NULL) ||
+ (val != NULL && strcmp (val, l->data)))
+ {
+ g_free (val);
+ return FALSE;
+ }
+
+ g_free (val);
+ l = l->next;
+ }
+ while (gtk_tree_model_iter_next (model, &iter));
+
+ return l == NULL;
+ }
+ else
+ {
+ return (empty_model && entries == NULL) ||
+ (!empty_model && entries != NULL);
+ }
+}
+
+static char *
+combo_box_get_active_entry (GtkComboBox *combo_box,
+ unsigned int column)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ char *val;
+
+ g_assert (GTK_IS_COMBO_BOX (combo_box));
+
+ if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo_box), &iter))
+ {
+ model = gtk_combo_box_get_model (combo_box);
+ g_assert (GTK_IS_TREE_MODEL (model));
+
+ gtk_tree_model_get (model, &iter,
+ column, &val,
+ -1);
+ return val;
+ }
+
+ return NULL;
+}
+
+/* returns the index of the given entry in the the given column
+ * at the first level of model. Returns -1 if entry can't be found
+ * or entry is NULL.
+ * */
+static int
+tree_model_get_entry_index (GtkTreeModel *model,
+ unsigned int column,
+ const char *entry)
+{
+ GtkTreeIter iter;
+ int index;
+ gboolean empty_model;
+
+ g_assert (GTK_IS_TREE_MODEL (model));
+ g_assert (gtk_tree_model_get_column_type (model, column) == G_TYPE_STRING);
+
+ empty_model = !gtk_tree_model_get_iter_first (model, &iter);
+ if (!empty_model && entry != NULL)
+ {
+ index = 0;
+
+ do
+ {
+ char *val;
+
+ gtk_tree_model_get (model, &iter,
+ column, &val,
+ -1);
+ if (val != NULL && !strcmp (val, entry))
+ {
+ g_free (val);
+ return index;
+ }
+
+ g_free (val);
+ index++;
+ }
+ while (gtk_tree_model_iter_next (model, &iter));
+ }
+
+ return -1;
+}
+
+
+static void
+synch_groups_combo_box (GtkComboBox *combo_box,
+ NautilusFile *file)
+{
+ GList *groups;
+ GList *node;
+ GtkTreeModel *model;
+ GtkListStore *store;
+ const char *group_name;
+ char *current_group_name;
+ int group_index;
+ int current_group_index;
+
+ g_assert (GTK_IS_COMBO_BOX (combo_box));
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ if (nautilus_file_is_gone (file))
+ {
+ return;
+ }
+
+ groups = nautilus_file_get_settable_group_names (file);
+
+ model = gtk_combo_box_get_model (combo_box);
+ store = GTK_LIST_STORE (model);
+ g_assert (GTK_IS_LIST_STORE (model));
+
+ if (!tree_model_entries_equal (model, 0, groups))
+ {
+ /* Clear the contents of ComboBox in a wacky way because there
+ * is no function to clear all items and also no function to obtain
+ * the number of items in a combobox.
+ */
+ gtk_list_store_clear (store);
+
+ for (node = groups, group_index = 0; node != NULL; node = node->next, ++group_index)
+ {
+ group_name = (const char *) node->data;
+ gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo_box), group_name);
+ }
+ }
+
+ current_group_name = nautilus_file_get_group_name (file);
+ current_group_index = tree_model_get_entry_index (model, 0, current_group_name);
+
+ /* If current group wasn't in list, we prepend it (with a separator).
+ * This can happen if the current group is an id with no matching
+ * group in the groups file.
+ */
+ if (current_group_index < 0 && current_group_name != NULL)
+ {
+ if (groups != NULL)
+ {
+ /* add separator */
+ gtk_combo_box_text_prepend_text (GTK_COMBO_BOX_TEXT (combo_box), "-");
+ }
+
+ gtk_combo_box_text_prepend_text (GTK_COMBO_BOX_TEXT (combo_box), current_group_name);
+ current_group_index = 0;
+ }
+ gtk_combo_box_set_active (combo_box, current_group_index);
+
+ g_free (current_group_name);
+ g_list_free_full (groups, g_free);
+}
+
+static gboolean
+combo_box_row_separator_func (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ gchar *text;
+ gboolean ret;
+
+ gtk_tree_model_get (model, iter, 0, &text, -1);
+
+ if (text == NULL)
+ {
+ return FALSE;
+ }
+
+ if (strcmp (text, "-") == 0)
+ {
+ ret = TRUE;
+ }
+ else
+ {
+ ret = FALSE;
+ }
+
+ g_free (text);
+ return ret;
+}
+
+static void
+setup_group_combo_box (GtkWidget *combo_box,
+ NautilusFile *file)
+{
+ gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (combo_box),
+ combo_box_row_separator_func,
+ NULL,
+ NULL);
+
+ synch_groups_combo_box (GTK_COMBO_BOX (combo_box), file);
+
+ /* Connect to signal to update menu when file changes. */
+ g_signal_connect_object (file, "changed",
+ G_CALLBACK (synch_groups_combo_box),
+ combo_box, G_CONNECT_SWAPPED);
+ g_signal_connect_data (combo_box, "changed",
+ G_CALLBACK (changed_group_callback),
+ nautilus_file_ref (file),
+ (GClosureNotify) nautilus_file_unref, 0);
+}
+
+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 *window;
+
+ 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);
+ }
+
+ window = NAUTILUS_PROPERTIES_WINDOW (change->window);
+ if (window->owner_change == change)
+ {
+ window->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 *window,
+ NautilusFile *file,
+ const char *owner)
+{
+ OwnerChange *change;
+
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (window));
+ g_assert (window->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 (window));
+ change->timeout =
+ g_timeout_add (CHOWN_CHGRP_TIMEOUT,
+ (GSourceFunc) schedule_owner_change_timeout,
+ change);
+
+ window->owner_change = change;
+}
+
+static void
+unschedule_or_cancel_owner_change (NautilusPropertiesWindow *window)
+{
+ OwnerChange *change;
+
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (window));
+
+ change = window->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);
+ }
+
+ window->owner_change = NULL;
+ }
+}
+
+static void
+changed_owner_callback (GtkComboBox *combo_box,
+ NautilusFile *file)
+{
+ NautilusPropertiesWindow *window;
+ char *new_owner;
+ char *cur_owner;
+
+ g_assert (GTK_IS_COMBO_BOX (combo_box));
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ new_owner = combo_box_get_active_entry (combo_box, 2);
+ if (!new_owner)
+ {
+ return;
+ }
+ cur_owner = nautilus_file_get_owner_name (file);
+
+ if (strcmp (new_owner, cur_owner) != 0)
+ {
+ /* Try to change file owner. If this fails, complain to user. */
+ window = NAUTILUS_PROPERTIES_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (combo_box), GTK_TYPE_WINDOW));
+
+ unschedule_or_cancel_owner_change (window);
+ schedule_owner_change (window, file, new_owner);
+ }
+ g_free (new_owner);
+ g_free (cur_owner);
+}
+
+static void
+synch_user_menu (GtkComboBox *combo_box,
+ NautilusFile *file)
+{
+ GList *users;
+ GList *node;
+ GtkTreeModel *model;
+ GtkListStore *store;
+ GtkTreeIter iter;
+ char *user_name;
+ char *owner_name;
+ char *nice_owner_name;
+ int user_index;
+ int owner_index;
+ char **name_array;
+ char *combo_text;
+
+ g_assert (GTK_IS_COMBO_BOX (combo_box));
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ if (nautilus_file_is_gone (file))
+ {
+ return;
+ }
+
+ users = nautilus_get_user_names ();
+
+ model = gtk_combo_box_get_model (combo_box);
+ store = GTK_LIST_STORE (model);
+ g_assert (GTK_IS_LIST_STORE (model));
+
+ if (!tree_model_entries_equal (model, 1, users))
+ {
+ /* Clear the contents of ComboBox in a wacky way because there
+ * is no function to clear all items and also no function to obtain
+ * the number of items in a combobox.
+ */
+ gtk_list_store_clear (store);
+
+ for (node = users, user_index = 0; node != NULL; node = node->next, ++user_index)
+ {
+ user_name = (char *) node->data;
+
+ name_array = g_strsplit (user_name, "\n", 2);
+ if (name_array[1] != NULL && *name_array[1] != 0)
+ {
+ combo_text = g_strdup_printf ("%s - %s", name_array[0], name_array[1]);
+ }
+ else
+ {
+ combo_text = g_strdup (name_array[0]);
+ }
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ 0, combo_text,
+ 1, user_name,
+ 2, name_array[0],
+ -1);
+
+ g_strfreev (name_array);
+ g_free (combo_text);
+ }
+ }
+
+ owner_name = nautilus_file_get_owner_name (file);
+ owner_index = tree_model_get_entry_index (model, 2, owner_name);
+ nice_owner_name = nautilus_file_get_string_attribute (file, "owner");
+
+ /* If owner wasn't in list, we prepend it (with a separator).
+ * This can happen if the owner is an id with no matching
+ * identifier in the passwords file.
+ */
+ if (owner_index < 0 && owner_name != NULL)
+ {
+ if (users != NULL)
+ {
+ /* add separator */
+ gtk_list_store_prepend (store, &iter);
+ gtk_list_store_set (store, &iter,
+ 0, "-",
+ 1, NULL,
+ 2, NULL,
+ -1);
+ }
+
+ owner_index = 0;
+
+ gtk_list_store_prepend (store, &iter);
+ gtk_list_store_set (store, &iter,
+ 0, nice_owner_name,
+ 1, owner_name,
+ 2, owner_name,
+ -1);
+ }
+
+ gtk_combo_box_set_active (combo_box, owner_index);
+
+ g_free (owner_name);
+ g_free (nice_owner_name);
+ g_list_free_full (users, g_free);
+}
+
+static void
+setup_owner_combo_box (GtkWidget *combo_box,
+ NautilusFile *file)
+{
+ GtkTreeModel *model;
+ GtkCellRenderer *renderer;
+
+ model = GTK_TREE_MODEL (gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING));
+ gtk_combo_box_set_model (GTK_COMBO_BOX (combo_box), model);
+ g_object_unref (G_OBJECT (model));
+
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box), renderer, TRUE);
+ gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (combo_box), renderer,
+ "text", 0);
+
+ gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (combo_box),
+ combo_box_row_separator_func,
+ NULL,
+ NULL);
+
+ synch_user_menu (GTK_COMBO_BOX (combo_box), file);
+
+ /* Connect to signal to update menu when file changes. */
+ g_signal_connect_object (file, "changed",
+ G_CALLBACK (synch_user_menu),
+ combo_box, G_CONNECT_SWAPPED);
+ g_signal_connect_data (combo_box, "changed",
+ G_CALLBACK (changed_owner_callback),
+ nautilus_file_ref (file),
+ (GClosureNotify) nautilus_file_unref, 0);
+}
+
+static gboolean
+file_has_prefix (NautilusFile *file,
+ GList *prefix_candidates)
+{
+ GList *p;
+ GFile *location, *candidate_location;
+
+ location = nautilus_file_get_location (file);
+
+ for (p = prefix_candidates; p != NULL; p = p->next)
+ {
+ if (file == p->data)
+ {
+ continue;
+ }
+
+ candidate_location = nautilus_file_get_location (NAUTILUS_FILE (p->data));
+ if (g_file_has_prefix (location, candidate_location))
+ {
+ g_object_unref (location);
+ g_object_unref (candidate_location);
+ return TRUE;
+ }
+ g_object_unref (candidate_location);
+ }
+
+ g_object_unref (location);
+
+ return FALSE;
+}
+
+static void
+directory_contents_value_field_update (NautilusPropertiesWindow *window)
+{
+ NautilusRequestStatus file_status;
+ char *text, *temp;
+ 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 (window));
+
+ total_count = 0;
+ total_size = 0;
+ unreadable_directory_count = FALSE;
+
+ for (l = window->target_files; l; l = l->next)
+ {
+ file = NAUTILUS_FILE (l->data);
+
+ if (file_has_prefix (file, window->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 (window, file);
+ }
+ }
+ else
+ {
+ ++total_count;
+ total_size += nautilus_file_get_size (file);
+ }
+ }
+
+ deep_count_active = (window->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 (window->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 (_("nothing"));
+ }
+ else
+ {
+ text = g_strdup (_("unreadable"));
+ }
+ }
+ else
+ {
+ text = g_strdup ("…");
+ }
+ }
+ else
+ {
+ char *size_str;
+ 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);
+ g_free (size_str);
+
+ if (unreadable_directory_count != 0)
+ {
+ temp = text;
+ text = g_strconcat (temp, "\n",
+ _("(some contents unreadable)"),
+ NULL);
+ g_free (temp);
+ }
+ }
+
+ gtk_label_set_text (GTK_LABEL (window->contents_value_label),
+ text);
+ g_free (text);
+
+ if (!deep_count_active)
+ {
+ window->deep_count_finished = TRUE;
+ stop_spinner (window);
+ }
+}
+
+static gboolean
+update_directory_contents_callback (gpointer data)
+{
+ NautilusPropertiesWindow *window;
+
+ window = NAUTILUS_PROPERTIES_WINDOW (data);
+
+ window->update_directory_contents_timeout_id = 0;
+ directory_contents_value_field_update (window);
+
+ return FALSE;
+}
+
+static void
+schedule_directory_contents_update (NautilusPropertiesWindow *window)
+{
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (window));
+
+ if (window->update_directory_contents_timeout_id == 0)
+ {
+ window->update_directory_contents_timeout_id
+ = g_timeout_add (DIRECTORY_CONTENTS_UPDATE_INTERVAL,
+ update_directory_contents_callback,
+ window);
+ }
+}
+
+static void
+setup_contents_field (NautilusPropertiesWindow *window,
+ GtkGrid *grid)
+{
+ GList *l;
+
+ for (l = window->target_files; l; l = l->next)
+ {
+ NautilusFile *file;
+
+ file = NAUTILUS_FILE (l->data);
+ start_deep_count_for_file (window, file);
+ }
+
+ /* Fill in the initial value. */
+ directory_contents_value_field_update (window);
+}
+
+static gboolean
+is_root_directory (NautilusFile *file)
+{
+ GFile *location;
+ gboolean result;
+
+ location = nautilus_file_get_location (file);
+ result = nautilus_is_root_directory (location);
+ g_object_unref (location);
+
+ return result;
+}
+
+static gboolean
+is_network_directory (NautilusFile *file)
+{
+ char *file_uri;
+ gboolean result;
+
+ file_uri = nautilus_file_get_uri (file);
+ result = strcmp (file_uri, "network:///") == 0;
+ g_free (file_uri);
+
+ return result;
+}
+
+static gboolean
+is_burn_directory (NautilusFile *file)
+{
+ char *file_uri;
+ gboolean result;
+
+ file_uri = nautilus_file_get_uri (file);
+ result = strcmp (file_uri, "burn:///") == 0;
+ g_free (file_uri);
+
+ return result;
+}
+
+static gboolean
+should_show_custom_icon_buttons (NautilusPropertiesWindow *window)
+{
+ if (is_multi_file_window (window))
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+should_show_file_type (NautilusPropertiesWindow *window)
+{
+ if (!is_multi_file_window (window)
+ && (nautilus_file_is_in_trash (get_target_file (window)) ||
+ is_network_directory (get_target_file (window)) ||
+ is_burn_directory (get_target_file (window))))
+ {
+ return FALSE;
+ }
+
+
+ return TRUE;
+}
+
+static gboolean
+should_show_location_info (NautilusPropertiesWindow *window)
+{
+ GList *l;
+
+ for (l = window->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_trash_orig_path (NautilusPropertiesWindow *window)
+{
+ GList *l;
+
+ for (l = window->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 *window)
+{
+ /* 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 (file_list_all_directories (window->target_files)
+ || is_multi_file_window (window))
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+should_show_modified_date (NautilusPropertiesWindow *window)
+{
+ return !is_multi_file_window (window);
+}
+
+static gboolean
+should_show_trashed_on (NautilusPropertiesWindow *window)
+{
+ GList *l;
+
+ for (l = window->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_link_target (NautilusPropertiesWindow *window)
+{
+ if (!is_multi_file_window (window)
+ && nautilus_file_is_symbolic_link (get_target_file (window)))
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+should_show_free_space (NautilusPropertiesWindow *window)
+{
+ if (!is_multi_file_window (window)
+ && (nautilus_file_is_in_trash (get_target_file (window)) ||
+ is_network_directory (get_target_file (window)) ||
+ nautilus_file_is_in_recent (get_target_file (window)) ||
+ is_burn_directory (get_target_file (window))))
+ {
+ return FALSE;
+ }
+
+ if (file_list_all_directories (window->target_files))
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+should_show_volume_info (NautilusPropertiesWindow *window)
+{
+ NautilusFile *file;
+
+ if (is_multi_file_window (window))
+ {
+ return FALSE;
+ }
+
+ file = get_original_file (window);
+
+ if (file == NULL)
+ {
+ return FALSE;
+ }
+
+ if (nautilus_file_can_unmount (file))
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+should_show_volume_usage (NautilusPropertiesWindow *window)
+{
+ NautilusFile *file;
+ gboolean success = FALSE;
+
+ if (is_multi_file_window (window))
+ {
+ return FALSE;
+ }
+
+ file = get_original_file (window);
+
+ if (file == NULL)
+ {
+ 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 void
+paint_legend (GtkWidget *widget,
+ cairo_t *cr,
+ gpointer data)
+{
+ GtkStyleContext *context;
+ GtkAllocation allocation;
+
+ gtk_widget_get_allocation (widget, &allocation);
+ context = gtk_widget_get_style_context (widget);
+
+ gtk_render_background (context, cr, 0, 0, allocation.width, allocation.height);
+ gtk_render_frame (context, cr, 0, 0, allocation.width, allocation.height);
+}
+
+static void
+paint_slice (GtkWidget *widget,
+ cairo_t *cr,
+ double percent_start,
+ double percent_width,
+ const gchar *style_class)
+{
+ double angle1;
+ double angle2;
+ gboolean full;
+ double offset = G_PI / 2.0;
+ GdkRGBA fill;
+ GdkRGBA stroke;
+ GtkStateFlags state;
+ GtkBorder border;
+ GtkStyleContext *context;
+ double x, y, radius;
+ gint width, height;
+
+ if (percent_width < .01)
+ {
+ return;
+ }
+
+ context = gtk_widget_get_style_context (widget);
+ state = gtk_style_context_get_state (context);
+ gtk_style_context_get_border (context, state, &border);
+
+ gtk_style_context_save (context);
+ gtk_style_context_add_class (context, style_class);
+ gtk_style_context_get_color (context, state, &fill);
+ gtk_style_context_add_class (context, "border");
+ gtk_style_context_get_color (context, state, &stroke);
+ gtk_style_context_restore (context);
+
+ width = gtk_widget_get_allocated_width (widget);
+ height = gtk_widget_get_allocated_height (widget);
+ x = width / 2;
+ y = height / 2;
+
+ if (width < height)
+ {
+ radius = (width - border.left) / 2;
+ }
+ else
+ {
+ radius = (height - border.top) / 2;
+ }
+
+ angle1 = (percent_start * 2 * G_PI) - offset;
+ angle2 = angle1 + (percent_width * 2 * G_PI);
+
+ full = (percent_width > .99);
+
+ if (!full)
+ {
+ cairo_move_to (cr, x, y);
+ }
+ cairo_arc (cr, x, y, radius, angle1, angle2);
+
+ if (!full)
+ {
+ cairo_line_to (cr, x, y);
+ }
+
+ cairo_set_line_width (cr, border.top);
+ gdk_cairo_set_source_rgba (cr, &fill);
+ cairo_fill_preserve (cr);
+
+ gdk_cairo_set_source_rgba (cr, &stroke);
+ cairo_stroke (cr);
+}
+
+static void
+paint_pie_chart (GtkWidget *widget,
+ cairo_t *cr,
+ gpointer data)
+{
+ NautilusPropertiesWindow *window;
+ double free, used, reserved;
+
+ window = NAUTILUS_PROPERTIES_WINDOW (data);
+
+ free = (double) window->volume_free / (double) window->volume_capacity;
+ used = (double) window->volume_used / (double) window->volume_capacity;
+ reserved = 1.0 - (used + free);
+
+ paint_slice (widget, cr,
+ 0, free, "free");
+ paint_slice (widget, cr,
+ free + used, reserved, "unknown");
+ /* paint the used last so its slice strokes are on top */
+ paint_slice (widget, cr,
+ free, used, "used");
+}
+
+static void
+setup_pie_widget (NautilusPropertiesWindow *window)
+{
+ NautilusFile *file;
+ gchar *capacity;
+ gchar *used;
+ gchar *free;
+ const char *fs_type;
+ gchar *uri;
+ GFile *location;
+ GFileInfo *info;
+
+ capacity = g_format_size (window->volume_capacity);
+ free = g_format_size (window->volume_free);
+ used = g_format_size (window->volume_used);
+
+ file = get_original_file (window);
+
+ uri = nautilus_file_get_activation_uri (file);
+
+ /* Translators: "used" refers to the capacity of the filesystem */
+ gtk_label_set_text (GTK_LABEL (window->used_value), used);
+
+ /* Translators: "free" refers to the capacity of the filesystem */
+ gtk_label_set_text (GTK_LABEL (window->free_value), free);
+
+ gtk_label_set_text (GTK_LABEL (window->total_capacity_value), capacity);
+
+ gtk_label_set_text (GTK_LABEL (window->file_system_value), NULL);
+
+ 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);
+ if (fs_type != NULL)
+ {
+ gtk_label_set_text (GTK_LABEL (window->file_system_value), fs_type);
+ }
+
+ g_object_unref (info);
+ }
+ g_object_unref (location);
+
+ g_free (uri);
+ g_free (capacity);
+ g_free (used);
+ g_free (free);
+
+ g_signal_connect (window->pie_chart, "draw",
+ G_CALLBACK (paint_pie_chart), window);
+ g_signal_connect (window->used_color, "draw",
+ G_CALLBACK (paint_legend), window);
+ g_signal_connect (window->free_color, "draw",
+ G_CALLBACK (paint_legend), window);
+}
+
+static void
+setup_volume_usage_widget (NautilusPropertiesWindow *window)
+{
+ gchar *uri;
+ NautilusFile *file;
+ GFile *location;
+ GFileInfo *info;
+
+ file = get_original_file (window);
+
+ 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)
+ {
+ window->volume_capacity = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE);
+ window->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))
+ {
+ window->volume_used = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_USED);
+ }
+ else
+ {
+ window->volume_used = window->volume_capacity - window->volume_free;
+ }
+
+ g_object_unref (info);
+ }
+ else
+ {
+ window->volume_capacity = 0;
+ window->volume_free = 0;
+ window->volume_used = 0;
+ }
+
+ g_object_unref (location);
+
+ if (window->volume_capacity > 0)
+ {
+ setup_pie_widget (window);
+ }
+}
+
+static void
+open_in_disks (GtkButton *button,
+ NautilusPropertiesWindow *self)
+{
+ g_autofree char *message = NULL;
+ g_autoptr (GError) error = NULL;
+ g_autoptr (GAppInfo) app_info = NULL;
+
+ app_info = g_app_info_create_from_commandline ("gnome-disks",
+ NULL,
+ G_APP_INFO_CREATE_NONE,
+ NULL);
+
+ g_app_info_launch (app_info, NULL, NULL, &error);
+
+ if (error != NULL)
+ {
+ message = g_strdup_printf (_("Details: %s"), error->message);
+ show_dialog (_("There was an error launching the application."),
+ message,
+ GTK_WINDOW (self),
+ GTK_MESSAGE_ERROR);
+ }
+}
+
+static void
+setup_basic_page (NautilusPropertiesWindow *window)
+{
+ GtkGrid *grid;
+
+ /* Icon pixmap */
+
+ setup_image_widget (window, should_show_custom_icon_buttons (window));
+
+ window->icon_chooser = NULL;
+
+ /* Grid */
+
+ grid = window->basic_grid;
+
+ update_name_field (window);
+
+ g_signal_connect_object (window->name_field, "notify::has-focus",
+ G_CALLBACK (name_field_focus_changed), window, 0);
+ g_signal_connect_object (window->name_field, "activate",
+ G_CALLBACK (name_field_activate), window, 0);
+
+ /* Start with name field selected, if it's an entry. */
+ if (GTK_IS_ENTRY (gtk_stack_get_visible_child (window->name_stack)))
+ {
+ gtk_widget_grab_focus (GTK_WIDGET (window->name_field));
+ }
+
+ if (should_show_file_type (window))
+ {
+ gtk_widget_show (window->type_title_label);
+ gtk_widget_show (window->type_value_label);
+ g_object_set_data_full (G_OBJECT (window->type_value_label), "file_attribute",
+ g_strdup ("detailed_type"), g_free);
+
+ window->value_fields = g_list_prepend (window->value_fields,
+ window->type_value_label);
+ }
+
+ if (should_show_link_target (window))
+ {
+ gtk_widget_show (window->link_target_title_label);
+ gtk_widget_show (window->link_target_value_label);
+ g_object_set_data_full (G_OBJECT (window->link_target_value_label), "file_attribute",
+ g_strdup ("link_target"), g_free);
+
+ window->value_fields = g_list_prepend (window->value_fields,
+ window->link_target_value_label);
+ }
+
+ if (is_multi_file_window (window) ||
+ nautilus_file_is_directory (get_target_file (window)))
+ {
+ gtk_widget_show (window->contents_title_label);
+ gtk_widget_show (window->contents_value_label);
+ setup_contents_field (window, grid);
+ }
+ else
+ {
+ gtk_widget_show (window->size_title_label);
+ gtk_widget_show (window->size_value_label);
+
+ /* Stash a copy of the file attribute name in this field for the callback's sake. */
+ g_object_set_data_full (G_OBJECT (window->size_value_label), "file_attribute",
+ g_strdup ("size_detail"), g_free);
+
+ window->value_fields = g_list_prepend (window->value_fields,
+ window->size_value_label);
+ }
+
+ if (should_show_location_info (window))
+ {
+ gtk_widget_show (window->parent_folder_title_label);
+ gtk_widget_show (window->parent_folder_value_label);
+
+ g_object_set_data_full (G_OBJECT (window->parent_folder_value_label), "file_attribute",
+ g_strdup ("where"), g_free);
+
+ window->value_fields = g_list_prepend (window->value_fields,
+ window->parent_folder_value_label);
+ }
+
+ if (should_show_trash_orig_path (window))
+ {
+ gtk_widget_show (window->original_folder_title_label);
+ gtk_widget_show (window->original_folder_value_label);
+ g_object_set_data_full (G_OBJECT (window->original_folder_value_label), "file_attribute",
+ g_strdup ("trash_orig_path"), g_free);
+
+ window->value_fields = g_list_prepend (window->value_fields,
+ window->original_folder_value_label);
+ }
+
+ if (should_show_volume_info (window))
+ {
+ gtk_widget_show (window->volume_title_label);
+ gtk_widget_show (window->volume_value_label);
+ g_object_set_data_full (G_OBJECT (window->volume_value_label), "file_attribute",
+ g_strdup ("volume"), g_free);
+
+ window->value_fields = g_list_prepend (window->value_fields,
+ window->volume_value_label);
+ }
+
+ if (should_show_trashed_on (window))
+ {
+ gtk_widget_show (window->trashed_on_title_label);
+ gtk_widget_show (window->trashed_on_value_label);
+ g_object_set_data_full (G_OBJECT (window->trashed_on_value_label), "file_attribute",
+ g_strdup ("trashed_on_full"), g_free);
+
+ window->value_fields = g_list_prepend (window->value_fields,
+ window->trashed_on_value_label);
+ }
+
+ if (should_show_accessed_date (window)
+ || should_show_modified_date (window))
+ {
+ gtk_widget_show (window->spacer_2);
+ }
+
+ if (should_show_accessed_date (window))
+ {
+ gtk_widget_show (window->accessed_title_label);
+ gtk_widget_show (window->accessed_value_label);
+ /* Stash a copy of the file attribute name in this field for the callback's sake. */
+ g_object_set_data_full (G_OBJECT (window->accessed_value_label), "file_attribute",
+ g_strdup ("date_accessed_full"), g_free);
+
+ window->value_fields = g_list_prepend (window->value_fields,
+ window->accessed_value_label);
+ }
+
+ if (should_show_modified_date (window))
+ {
+ gtk_widget_show (window->modified_title_label);
+ gtk_widget_show (window->modified_value_label);
+ /* Stash a copy of the file attribute name in this field for the callback's sake. */
+ g_object_set_data_full (G_OBJECT (window->modified_value_label), "file_attribute",
+ g_strdup ("date_modified_full"), g_free);
+
+ window->value_fields = g_list_prepend (window->value_fields,
+ window->modified_value_label);
+ }
+
+ if (should_show_free_space (window)
+ && !should_show_volume_usage (window))
+ {
+ gtk_widget_show (window->spacer_3);
+ gtk_widget_show (window->free_space_title_label);
+ gtk_widget_show (window->free_space_value_label);
+
+ /* Stash a copy of the file attribute name in this field for the callback's sake. */
+ g_object_set_data_full (G_OBJECT (window->free_space_value_label), "file_attribute",
+ g_strdup ("free_space"), g_free);
+
+ window->value_fields = g_list_prepend (window->value_fields,
+ window->free_space_value_label);
+ }
+
+ if (should_show_volume_usage (window))
+ {
+ gtk_widget_show (window->volume_widget_box);
+ gtk_widget_show (window->open_in_disks_button);
+ setup_volume_usage_widget (window);
+ /*Translators: Here Disks mean the name of application GNOME Disks.*/
+ g_signal_connect (window->open_in_disks_button, "clicked", G_CALLBACK (open_in_disks), NULL);
+ }
+}
+
+static gboolean
+files_has_directory (NautilusPropertiesWindow *window)
+{
+ GList *l;
+
+ for (l = window->target_files; l != NULL; l = l->next)
+ {
+ NautilusFile *file;
+ file = NAUTILUS_FILE (l->data);
+ if (nautilus_file_is_directory (file))
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static gboolean
+files_has_changable_permissions_directory (NautilusPropertiesWindow *window)
+{
+ GList *l;
+ gboolean changable = FALSE;
+
+ for (l = window->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 gboolean
+files_has_file (NautilusPropertiesWindow *window)
+{
+ GList *l;
+
+ for (l = window->target_files; l != NULL; l = l->next)
+ {
+ NautilusFile *file;
+ file = NAUTILUS_FILE (l->data);
+ if (!nautilus_file_is_directory (file))
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+start_long_operation (NautilusPropertiesWindow *window)
+{
+ if (window->long_operation_underway == 0)
+ {
+ /* start long operation */
+ GdkDisplay *display;
+ GdkCursor *cursor;
+
+ display = gtk_widget_get_display (GTK_WIDGET (window));
+ cursor = gdk_cursor_new_for_display (display, GDK_WATCH);
+ gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (window)), cursor);
+ g_object_unref (cursor);
+ }
+ window->long_operation_underway++;
+}
+
+static void
+end_long_operation (NautilusPropertiesWindow *window)
+{
+ if (gtk_widget_get_window (GTK_WIDGET (window)) != NULL &&
+ window->long_operation_underway == 1)
+ {
+ /* finished !! */
+ gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (window)), NULL);
+ }
+ window->long_operation_underway--;
+}
+
+static void
+permission_change_callback (NautilusFile *file,
+ GFile *res_loc,
+ GError *error,
+ gpointer callback_data)
+{
+ NautilusPropertiesWindow *window;
+ g_assert (callback_data != NULL);
+
+ window = NAUTILUS_PROPERTIES_WINDOW (callback_data);
+ end_long_operation (window);
+
+ /* Report the error if it's an error. */
+ nautilus_report_error_setting_permissions (file, error, GTK_WINDOW (window));
+
+ g_object_unref (window);
+}
+
+static void
+update_permissions (NautilusPropertiesWindow *window,
+ guint32 vfs_new_perm,
+ guint32 vfs_mask,
+ gboolean is_folder,
+ gboolean apply_to_both_folder_and_dir,
+ gboolean use_original)
+{
+ GList *l;
+
+ for (l = window->target_files; l != NULL; l = l->next)
+ {
+ NautilusFile *file;
+ guint32 permissions;
+
+ file = NAUTILUS_FILE (l->data);
+
+ if (!nautilus_file_can_get_permissions (file))
+ {
+ continue;
+ }
+
+ if (!apply_to_both_folder_and_dir &&
+ ((nautilus_file_is_directory (file) && !is_folder) ||
+ (!nautilus_file_is_directory (file) && is_folder)))
+ {
+ continue;
+ }
+
+ permissions = nautilus_file_get_permissions (file);
+ if (use_original)
+ {
+ gpointer ptr;
+ if (g_hash_table_lookup_extended (window->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 (window);
+ g_object_ref (window);
+ nautilus_file_set_permissions
+ (file, permissions,
+ permission_change_callback,
+ window);
+ }
+}
+
+static gboolean
+initial_permission_state_consistent (NautilusPropertiesWindow *window,
+ guint32 mask,
+ gboolean is_folder,
+ gboolean both_folder_and_dir)
+{
+ GList *l;
+ gboolean first;
+ guint32 first_permissions;
+
+ first = TRUE;
+ first_permissions = 0;
+ for (l = window->target_files; l != NULL; l = l->next)
+ {
+ NautilusFile *file;
+ guint32 permissions;
+
+ file = l->data;
+
+ if (!both_folder_and_dir &&
+ ((nautilus_file_is_directory (file) && !is_folder) ||
+ (!nautilus_file_is_directory (file) && is_folder)))
+ {
+ continue;
+ }
+
+ permissions = GPOINTER_TO_INT (g_hash_table_lookup (window->initial_permissions,
+ file));
+
+ if (first)
+ {
+ if ((permissions & mask) != mask &&
+ (permissions & mask) != 0)
+ {
+ /* Not fully on or off -> inconsistent */
+ return FALSE;
+ }
+
+ first_permissions = permissions;
+ first = FALSE;
+ }
+ else if ((permissions & mask) != (first_permissions & mask))
+ {
+ /* Not same permissions as first -> inconsistent */
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static void
+permission_button_toggled (GtkToggleButton *button,
+ NautilusPropertiesWindow *window)
+{
+ gboolean is_folder, is_special;
+ guint32 permission_mask;
+ gboolean inconsistent;
+ gboolean on;
+
+ permission_mask = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
+ "permission"));
+ is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
+ "is-folder"));
+ is_special = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
+ "is-special"));
+
+ if (gtk_toggle_button_get_active (button)
+ && !gtk_toggle_button_get_inconsistent (button))
+ {
+ /* Go to the initial state unless the initial state was
+ * consistent, or we support recursive apply */
+ inconsistent = TRUE;
+ on = TRUE;
+
+ if (initial_permission_state_consistent (window, permission_mask, is_folder, is_special))
+ {
+ inconsistent = FALSE;
+ on = TRUE;
+ }
+ }
+ else if (gtk_toggle_button_get_inconsistent (button)
+ && !gtk_toggle_button_get_active (button))
+ {
+ inconsistent = FALSE;
+ on = TRUE;
+ }
+ else
+ {
+ inconsistent = FALSE;
+ on = FALSE;
+ }
+
+ g_signal_handlers_block_by_func (G_OBJECT (button),
+ G_CALLBACK (permission_button_toggled),
+ window);
+
+ gtk_toggle_button_set_active (button, on);
+ gtk_toggle_button_set_inconsistent (button, inconsistent);
+
+ g_signal_handlers_unblock_by_func (G_OBJECT (button),
+ G_CALLBACK (permission_button_toggled),
+ window);
+
+ update_permissions (window,
+ on ? permission_mask : 0,
+ permission_mask,
+ is_folder,
+ is_special,
+ inconsistent);
+}
+
+static void
+permission_button_update (NautilusPropertiesWindow *window,
+ GtkToggleButton *button)
+{
+ GList *l;
+ gboolean all_set;
+ gboolean all_unset;
+ gboolean all_cannot_set;
+ gboolean is_folder, is_special;
+ gboolean no_match;
+ gboolean sensitive;
+ guint32 button_permission;
+
+ button_permission = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
+ "permission"));
+ is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
+ "is-folder"));
+ is_special = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
+ "is-special"));
+
+ all_set = TRUE;
+ all_unset = TRUE;
+ all_cannot_set = TRUE;
+ no_match = TRUE;
+ for (l = window->target_files; l != NULL; l = l->next)
+ {
+ NautilusFile *file;
+ guint32 file_permissions;
+
+ file = NAUTILUS_FILE (l->data);
+
+ if (!nautilus_file_can_get_permissions (file))
+ {
+ continue;
+ }
+
+ if (!is_special &&
+ ((nautilus_file_is_directory (file) && !is_folder) ||
+ (!nautilus_file_is_directory (file) && is_folder)))
+ {
+ continue;
+ }
+
+ no_match = FALSE;
+
+ file_permissions = nautilus_file_get_permissions (file);
+
+ if ((file_permissions & button_permission) == button_permission)
+ {
+ all_unset = FALSE;
+ }
+ else if ((file_permissions & button_permission) == 0)
+ {
+ all_set = FALSE;
+ }
+ else
+ {
+ all_unset = FALSE;
+ all_set = FALSE;
+ }
+
+ if (nautilus_file_can_set_permissions (file))
+ {
+ all_cannot_set = FALSE;
+ }
+ }
+
+ sensitive = !all_cannot_set;
+
+ g_signal_handlers_block_by_func (G_OBJECT (button),
+ G_CALLBACK (permission_button_toggled),
+ window);
+
+ gtk_toggle_button_set_active (button, !all_unset);
+ /* if actually inconsistent, or default value for file buttons
+ * if no files are selected. (useful for recursive apply) */
+ gtk_toggle_button_set_inconsistent (button,
+ (!all_unset && !all_set) ||
+ (!is_folder && no_match));
+ gtk_widget_set_sensitive (GTK_WIDGET (button), sensitive);
+
+ g_signal_handlers_unblock_by_func (G_OBJECT (button),
+ G_CALLBACK (permission_button_toggled),
+ window);
+}
+
+static void
+setup_execute_checkbox_with_label (NautilusPropertiesWindow *window,
+ guint32 permission_to_check)
+{
+ gboolean a11y_enabled;
+ GtkLabel *label_for;
+
+ label_for = GTK_LABEL (window->execute_label);
+ gtk_widget_show (window->execute_label);
+ gtk_widget_show (window->execute_checkbox);
+
+ /* Load up the check_button with data we'll need when updating its state. */
+ g_object_set_data (G_OBJECT (window->execute_checkbox), "permission",
+ GINT_TO_POINTER (permission_to_check));
+ g_object_set_data (G_OBJECT (window->execute_checkbox), "properties_window",
+ window);
+ g_object_set_data (G_OBJECT (window->execute_checkbox), "is-folder",
+ GINT_TO_POINTER (FALSE));
+
+ window->permission_buttons =
+ g_list_prepend (window->permission_buttons,
+ window->execute_checkbox);
+
+ g_signal_connect_object (window->execute_checkbox, "toggled",
+ G_CALLBACK (permission_button_toggled),
+ window,
+ 0);
+
+ a11y_enabled = GTK_IS_ACCESSIBLE (gtk_widget_get_accessible (window->execute_checkbox));
+ if (a11y_enabled && label_for != NULL)
+ {
+ AtkObject *atk_widget;
+ AtkObject *atk_label;
+
+ atk_label = gtk_widget_get_accessible (GTK_WIDGET (label_for));
+ atk_widget = gtk_widget_get_accessible (window->execute_checkbox);
+
+ /* Create the label -> widget relation */
+ atk_object_add_relationship (atk_label, ATK_RELATION_LABEL_FOR, atk_widget);
+
+ /* Create the widget -> label relation */
+ atk_object_add_relationship (atk_widget, ATK_RELATION_LABELLED_BY, atk_label);
+ }
+}
+
+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_READ = (1 << 0),
+ PERMISSION_WRITE = (1 << 1),
+ PERMISSION_EXEC = (1 << 2)
+} PermissionValue;
+
+typedef enum
+{
+ PERMISSION_USER,
+ PERMISSION_GROUP,
+ PERMISSION_OTHER
+} PermissionType;
+
+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;
+ g_assert (type >= 0 && type < 3);
+
+ perm = 0;
+ 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 void
+permission_combo_changed (GtkWidget *combo,
+ NautilusPropertiesWindow *window)
+{
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ gboolean is_folder, use_original;
+ PermissionType type;
+ int new_perm, mask;
+ guint32 vfs_new_perm, vfs_mask;
+
+ is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "is-folder"));
+ type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "permission-type"));
+
+ if (is_folder)
+ {
+ mask = PERMISSION_READ | PERMISSION_WRITE | PERMISSION_EXEC;
+ }
+ else
+ {
+ mask = PERMISSION_READ | PERMISSION_WRITE;
+ }
+
+ vfs_mask = permission_to_vfs (type, mask);
+
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));
+
+ if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter))
+ {
+ return;
+ }
+ gtk_tree_model_get (model, &iter, COLUMN_VALUE, &new_perm,
+ COLUMN_USE_ORIGINAL, &use_original, -1);
+ vfs_new_perm = permission_to_vfs (type, new_perm);
+
+ update_permissions (window, vfs_new_perm, vfs_mask,
+ is_folder, FALSE, use_original);
+}
+
+static void
+permission_combo_add_multiple_choice (GtkComboBox *combo,
+ GtkTreeIter *iter)
+{
+ GtkTreeModel *model;
+ GtkListStore *store;
+ gboolean found;
+
+ model = gtk_combo_box_get_model (combo);
+ store = GTK_LIST_STORE (model);
+
+ found = FALSE;
+ gtk_tree_model_get_iter_first (model, iter);
+ do
+ {
+ gboolean multi;
+ gtk_tree_model_get (model, iter, COLUMN_USE_ORIGINAL, &multi, -1);
+
+ if (multi)
+ {
+ found = TRUE;
+ break;
+ }
+ }
+ while (gtk_tree_model_iter_next (model, iter));
+
+ if (!found)
+ {
+ gtk_list_store_append (store, iter);
+ gtk_list_store_set (store, iter,
+ COLUMN_NAME, "---",
+ COLUMN_VALUE, 0,
+ COLUMN_USE_ORIGINAL, TRUE, -1);
+ }
+}
+
+static void
+permission_combo_update (NautilusPropertiesWindow *window,
+ GtkComboBox *combo)
+{
+ PermissionType type;
+ PermissionValue perm, all_dir_perm, all_file_perm, all_perm;
+ gboolean is_folder, no_files, no_dirs, all_file_same, all_dir_same, all_same;
+ gboolean all_dir_cannot_set, all_file_cannot_set, sensitive;
+ GtkTreeIter iter;
+ int mask;
+ GtkTreeModel *model;
+ GtkListStore *store;
+ GList *l;
+ gboolean is_multi;
+
+ model = gtk_combo_box_get_model (combo);
+
+ is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "is-folder"));
+ type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "permission-type"));
+
+ is_multi = FALSE;
+ if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter))
+ {
+ gtk_tree_model_get (model, &iter, COLUMN_USE_ORIGINAL, &is_multi, -1);
+ }
+
+ no_files = TRUE;
+ no_dirs = TRUE;
+ all_dir_same = TRUE;
+ all_file_same = TRUE;
+ all_dir_perm = 0;
+ all_file_perm = 0;
+ all_dir_cannot_set = TRUE;
+ all_file_cannot_set = TRUE;
+
+ for (l = window->target_files; l != NULL; l = l->next)
+ {
+ NautilusFile *file;
+ guint32 file_permissions;
+
+ file = NAUTILUS_FILE (l->data);
+
+ if (!nautilus_file_can_get_permissions (file))
+ {
+ continue;
+ }
+
+ if (nautilus_file_is_directory (file))
+ {
+ mask = PERMISSION_READ | PERMISSION_WRITE | PERMISSION_EXEC;
+ }
+ else
+ {
+ mask = PERMISSION_READ | PERMISSION_WRITE;
+ }
+
+ file_permissions = nautilus_file_get_permissions (file);
+
+ perm = permission_from_vfs (type, file_permissions) & mask;
+
+ if (nautilus_file_is_directory (file))
+ {
+ if (no_dirs)
+ {
+ all_dir_perm = perm;
+ no_dirs = FALSE;
+ }
+ else if (perm != all_dir_perm)
+ {
+ all_dir_same = FALSE;
+ }
+
+ if (nautilus_file_can_set_permissions (file))
+ {
+ all_dir_cannot_set = FALSE;
+ }
+ }
+ else
+ {
+ if (no_files)
+ {
+ all_file_perm = perm;
+ no_files = FALSE;
+ }
+ else if (perm != all_file_perm)
+ {
+ all_file_same = FALSE;
+ }
+
+ if (nautilus_file_can_set_permissions (file))
+ {
+ all_file_cannot_set = FALSE;
+ }
+ }
+ }
+
+ if (is_folder)
+ {
+ all_same = all_dir_same;
+ all_perm = all_dir_perm;
+ }
+ else
+ {
+ all_same = all_file_same && !no_files;
+ all_perm = all_file_perm;
+ }
+
+ store = GTK_LIST_STORE (model);
+ if (all_same)
+ {
+ gboolean found;
+
+ found = FALSE;
+ gtk_tree_model_get_iter_first (model, &iter);
+ do
+ {
+ int current_perm;
+ gtk_tree_model_get (model, &iter, 1, &current_perm, -1);
+
+ if (current_perm == all_perm)
+ {
+ found = TRUE;
+ break;
+ }
+ }
+ while (gtk_tree_model_iter_next (model, &iter));
+
+ if (!found)
+ {
+ GString *str;
+ str = g_string_new ("");
+
+ if (!(all_perm & PERMISSION_READ))
+ {
+ /* translators: this gets concatenated to "no read",
+ * "no access", etc. (see following strings)
+ */
+ g_string_append (str, _("no "));
+ }
+ if (is_folder)
+ {
+ g_string_append (str, _("list"));
+ }
+ else
+ {
+ g_string_append (str, _("read"));
+ }
+
+ g_string_append (str, ", ");
+
+ if (!(all_perm & PERMISSION_WRITE))
+ {
+ g_string_append (str, _("no "));
+ }
+ if (is_folder)
+ {
+ g_string_append (str, _("create/delete"));
+ }
+ else
+ {
+ g_string_append (str, _("write"));
+ }
+
+ if (is_folder)
+ {
+ g_string_append (str, ", ");
+
+ if (!(all_perm & PERMISSION_EXEC))
+ {
+ g_string_append (str, _("no "));
+ }
+ g_string_append (str, _("access"));
+ }
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ 0, str->str,
+ 1, all_perm, -1);
+
+ g_string_free (str, TRUE);
+ }
+ }
+ else
+ {
+ permission_combo_add_multiple_choice (combo, &iter);
+ }
+
+ g_signal_handlers_block_by_func (G_OBJECT (combo),
+ G_CALLBACK (permission_combo_changed),
+ window);
+
+ gtk_combo_box_set_active_iter (combo, &iter);
+
+ /* Also enable if no files found (for recursive
+ * file changes when only selecting folders) */
+ if (is_folder)
+ {
+ sensitive = !all_dir_cannot_set;
+ }
+ else
+ {
+ sensitive = !all_file_cannot_set;
+ }
+ gtk_widget_set_sensitive (GTK_WIDGET (combo), sensitive);
+
+ g_signal_handlers_unblock_by_func (G_OBJECT (combo),
+ G_CALLBACK (permission_combo_changed),
+ window);
+}
+
+static void
+setup_permissions_combo_box (GtkComboBox *combo,
+ PermissionType type,
+ gboolean is_folder)
+{
+ GtkListStore *store;
+ 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), "is-folder", GINT_TO_POINTER (is_folder));
+ g_object_set_data (G_OBJECT (combo), "permission-type", GINT_TO_POINTER (type));
+
+ if (is_folder)
+ {
+ 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, 0,
+ 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, 0,
+ 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);
+ }
+ g_object_unref (store);
+
+ 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 void
+create_simple_permissions (NautilusPropertiesWindow *window,
+ GtkGrid *page_grid)
+{
+ gboolean has_directory;
+ gboolean has_file;
+ GtkWidget *owner_combo_box;
+ GtkWidget *owner_value_label;
+ GtkWidget *group_combo_box;
+ GtkWidget *group_value_label;
+
+ has_directory = files_has_directory (window);
+ has_file = files_has_file (window);
+
+ if (!is_multi_file_window (window) && nautilus_file_can_set_owner (get_target_file (window)))
+ {
+ /* Combo box in this case. */
+ owner_combo_box = gtk_stack_get_child_by_name (GTK_STACK (window->owner_value_stack), "combo_box");
+ gtk_stack_set_visible_child (GTK_STACK (window->owner_value_stack), owner_combo_box);
+ setup_owner_combo_box (owner_combo_box, get_target_file (window));
+ }
+ else
+ {
+ /* Static text in this case. */
+ owner_value_label = gtk_stack_get_child_by_name (GTK_STACK (window->owner_value_stack), "label");
+ gtk_stack_set_visible_child (GTK_STACK (window->owner_value_stack), owner_value_label);
+
+ /* Stash a copy of the file attribute name in this field for the callback's sake. */
+ g_object_set_data_full (G_OBJECT (owner_value_label), "file_attribute",
+ g_strdup ("owner"), g_free);
+
+ window->value_fields = g_list_prepend (window->value_fields,
+ owner_value_label);
+ }
+ if (has_directory && has_file)
+ {
+ gtk_widget_show (window->owner_folder_access_label);
+ gtk_widget_show (window->owner_folder_access_combo);
+ setup_permissions_combo_box (GTK_COMBO_BOX (window->owner_folder_access_combo),
+ PERMISSION_USER, TRUE);
+ window->permission_combos = g_list_prepend (window->permission_combos,
+ window->owner_folder_access_combo);
+ g_signal_connect (window->owner_folder_access_combo, "changed", G_CALLBACK (permission_combo_changed), window);
+
+ gtk_widget_show (window->owner_file_access_label);
+ gtk_widget_show (window->owner_file_access_combo);
+ setup_permissions_combo_box (GTK_COMBO_BOX (window->owner_file_access_combo),
+ PERMISSION_USER, FALSE);
+ window->permission_combos = g_list_prepend (window->permission_combos,
+ window->owner_file_access_combo);
+ g_signal_connect (window->owner_file_access_combo, "changed", G_CALLBACK (permission_combo_changed), window);
+ }
+ else
+ {
+ gtk_widget_show (window->owner_access_label);
+ gtk_widget_show (window->owner_access_combo);
+ setup_permissions_combo_box (GTK_COMBO_BOX (window->owner_access_combo),
+ PERMISSION_USER, has_directory);
+ window->permission_combos = g_list_prepend (window->permission_combos,
+ window->owner_access_combo);
+ g_signal_connect (window->owner_access_combo, "changed", G_CALLBACK (permission_combo_changed), window);
+ }
+
+ if (!is_multi_file_window (window) && nautilus_file_can_set_group (get_target_file (window)))
+ {
+ /* Combo box in this case. */
+ group_combo_box = gtk_stack_get_child_by_name (GTK_STACK (window->group_value_stack), "combo_box");
+ gtk_stack_set_visible_child (GTK_STACK (window->group_value_stack), group_combo_box);
+ setup_group_combo_box (group_combo_box, get_target_file (window));
+ }
+ else
+ {
+ group_value_label = gtk_stack_get_child_by_name (GTK_STACK (window->group_value_stack), "label");
+ gtk_stack_set_visible_child (GTK_STACK (window->group_value_stack), group_value_label);
+
+ /* Stash a copy of the file attribute name in this field for the callback's sake. */
+ g_object_set_data_full (G_OBJECT (group_value_label), "file_attribute",
+ g_strdup ("group"), g_free);
+
+ window->value_fields = g_list_prepend (window->value_fields,
+ group_value_label);
+ }
+
+ if (has_directory && has_file)
+ {
+ gtk_widget_show (window->group_folder_access_label);
+ gtk_widget_show (window->group_folder_access_combo);
+ setup_permissions_combo_box (GTK_COMBO_BOX (window->group_folder_access_combo),
+ PERMISSION_GROUP, TRUE);
+ window->permission_combos = g_list_prepend (window->permission_combos,
+ window->group_folder_access_combo);
+ g_signal_connect (window->group_folder_access_combo, "changed", G_CALLBACK (permission_combo_changed), window);
+
+ gtk_widget_show (window->group_file_access_label);
+ gtk_widget_show (window->group_file_access_combo);
+ setup_permissions_combo_box (GTK_COMBO_BOX (window->group_file_access_combo),
+ PERMISSION_GROUP, FALSE);
+ window->permission_combos = g_list_prepend (window->permission_combos,
+ window->group_file_access_combo);
+ g_signal_connect (window->group_file_access_combo, "changed", G_CALLBACK (permission_combo_changed), window);
+ }
+ else
+ {
+ gtk_widget_show (window->group_access_label);
+ gtk_widget_show (window->group_access_combo);
+ setup_permissions_combo_box (GTK_COMBO_BOX (window->group_access_combo),
+ PERMISSION_GROUP, has_directory);
+ window->permission_combos = g_list_prepend (window->permission_combos,
+ window->group_access_combo);
+ g_signal_connect (window->group_access_combo, "changed", G_CALLBACK (permission_combo_changed), window);
+ }
+
+ /* Others Row */
+ if (has_directory && has_file)
+ {
+ gtk_widget_show (window->others_folder_access_label);
+ gtk_widget_show (window->others_folder_access_combo);
+ setup_permissions_combo_box (GTK_COMBO_BOX (window->others_folder_access_combo),
+ PERMISSION_OTHER, TRUE);
+ window->permission_combos = g_list_prepend (window->permission_combos,
+ window->others_folder_access_combo);
+ g_signal_connect (window->others_folder_access_combo, "changed", G_CALLBACK (permission_combo_changed), window);
+
+ gtk_widget_show (window->others_file_access_label);
+ gtk_widget_show (window->others_file_access_combo);
+ setup_permissions_combo_box (GTK_COMBO_BOX (window->others_file_access_combo),
+ PERMISSION_OTHER, FALSE);
+ window->permission_combos = g_list_prepend (window->permission_combos,
+ window->others_file_access_combo);
+ g_signal_connect (window->others_file_access_combo, "changed", G_CALLBACK (permission_combo_changed), window);
+ }
+ else
+ {
+ gtk_widget_show (window->others_access_label);
+ gtk_widget_show (window->others_access_combo);
+ setup_permissions_combo_box (GTK_COMBO_BOX (window->others_access_combo),
+ PERMISSION_OTHER, has_directory);
+ window->permission_combos = g_list_prepend (window->permission_combos,
+ window->others_access_combo);
+ g_signal_connect (window->others_access_combo, "changed", G_CALLBACK (permission_combo_changed), window);
+ }
+
+ if (!has_directory)
+ {
+ setup_execute_checkbox_with_label (window,
+ UNIX_PERM_USER_EXEC | UNIX_PERM_GROUP_EXEC | UNIX_PERM_OTHER_EXEC);
+ }
+}
+
+static void
+set_recursive_permissions_done (gboolean success,
+ gpointer callback_data)
+{
+ NautilusPropertiesWindow *window;
+
+ window = NAUTILUS_PROPERTIES_WINDOW (callback_data);
+ end_long_operation (window);
+
+ g_object_unref (window);
+}
+
+static void
+on_change_permissions_response (GtkDialog *dialog,
+ int response,
+ NautilusPropertiesWindow *window)
+{
+ guint32 file_permission, file_permission_mask;
+ guint32 dir_permission, dir_permission_mask;
+ guint32 vfs_mask, vfs_new_perm;
+ GtkWidget *combo;
+ gboolean is_folder, use_original;
+ GList *l;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ PermissionType type;
+ int new_perm, mask;
+
+ if (response != GTK_RESPONSE_OK)
+ {
+ g_clear_pointer (&window->change_permission_combos, g_list_free);
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+ return;
+ }
+
+ file_permission = 0;
+ file_permission_mask = 0;
+ dir_permission = 0;
+ dir_permission_mask = 0;
+
+ /* Simple mode, minus exec checkbox */
+ for (l = window->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"));
+ is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "is-folder"));
+
+ 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);
+
+ if (is_folder)
+ {
+ mask = PERMISSION_READ | PERMISSION_WRITE | PERMISSION_EXEC;
+ }
+ else
+ {
+ mask = PERMISSION_READ | PERMISSION_WRITE;
+ }
+ vfs_mask = permission_to_vfs (type, mask);
+
+ if (is_folder)
+ {
+ dir_permission_mask |= vfs_mask;
+ dir_permission |= vfs_new_perm;
+ }
+ else
+ {
+ file_permission_mask |= vfs_mask;
+ file_permission |= vfs_new_perm;
+ }
+ }
+
+ for (l = window->target_files; l != NULL; l = l->next)
+ {
+ NautilusFile *file;
+ char *uri;
+
+ file = NAUTILUS_FILE (l->data);
+
+ if (nautilus_file_is_directory (file) &&
+ nautilus_file_can_set_permissions (file))
+ {
+ uri = nautilus_file_get_uri (file);
+ start_long_operation (window);
+ g_object_ref (window);
+ nautilus_file_set_permissions_recursive (uri,
+ file_permission,
+ file_permission_mask,
+ dir_permission,
+ dir_permission_mask,
+ set_recursive_permissions_done,
+ window);
+ g_free (uri);
+ }
+ }
+ g_clear_pointer (&window->change_permission_combos, g_list_free);
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+static void
+set_active_from_umask (GtkComboBox *combo,
+ PermissionType type,
+ gboolean is_folder)
+{
+ mode_t initial;
+ mode_t mask;
+ mode_t p;
+ const char *id;
+
+ if (is_folder)
+ {
+ 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 *window)
+{
+ GtkWidget *dialog;
+ GtkComboBox *combo;
+ GtkBuilder *change_permissions_builder;
+
+ 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 (window));
+
+ /* Owner Permissions */
+ combo = GTK_COMBO_BOX (gtk_builder_get_object (change_permissions_builder, "file_owner_combo"));
+ setup_permissions_combo_box (combo, PERMISSION_USER, FALSE);
+ window->change_permission_combos = g_list_prepend (window->change_permission_combos,
+ combo);
+ set_active_from_umask (combo, PERMISSION_USER, FALSE);
+
+ combo = GTK_COMBO_BOX (gtk_builder_get_object (change_permissions_builder, "folder_owner_combo"));
+ setup_permissions_combo_box (combo, PERMISSION_USER, TRUE);
+ window->change_permission_combos = g_list_prepend (window->change_permission_combos,
+ combo);
+ set_active_from_umask (combo, PERMISSION_USER, TRUE);
+
+ /* Group Permissions */
+ combo = GTK_COMBO_BOX (gtk_builder_get_object (change_permissions_builder, "file_group_combo"));
+ setup_permissions_combo_box (combo, PERMISSION_GROUP, FALSE);
+ window->change_permission_combos = g_list_prepend (window->change_permission_combos,
+ combo);
+ set_active_from_umask (combo, PERMISSION_GROUP, FALSE);
+
+ combo = GTK_COMBO_BOX (gtk_builder_get_object (change_permissions_builder, "folder_group_combo"));
+ setup_permissions_combo_box (combo, PERMISSION_GROUP, TRUE);
+ window->change_permission_combos = g_list_prepend (window->change_permission_combos,
+ combo);
+ set_active_from_umask (combo, PERMISSION_GROUP, TRUE);
+
+ /* Others Permissions */
+ combo = GTK_COMBO_BOX (gtk_builder_get_object (change_permissions_builder, "file_other_combo"));
+ setup_permissions_combo_box (combo, PERMISSION_OTHER, FALSE);
+ window->change_permission_combos = g_list_prepend (window->change_permission_combos,
+ combo);
+ set_active_from_umask (combo, PERMISSION_OTHER, FALSE);
+
+ combo = GTK_COMBO_BOX (gtk_builder_get_object (change_permissions_builder, "folder_other_combo"));
+ setup_permissions_combo_box (combo, PERMISSION_OTHER, TRUE);
+ window->change_permission_combos = g_list_prepend (window->change_permission_combos,
+ combo);
+ set_active_from_umask (combo, PERMISSION_OTHER, TRUE);
+
+ g_signal_connect (dialog, "response", G_CALLBACK (on_change_permissions_response), window);
+ gtk_widget_show_all (dialog);
+ g_object_unref (change_permissions_builder);
+}
+
+static void
+setup_permissions_page (NautilusPropertiesWindow *window)
+{
+ char *file_name, *prompt_text;
+ GList *file_list;
+
+ file_list = window->original_files;
+
+ window->initial_permissions = NULL;
+
+ if (all_can_get_permissions (file_list) && all_can_get_permissions (window->target_files))
+ {
+ window->initial_permissions = get_initial_permissions (window->target_files);
+ window->has_recursive_apply = files_has_changable_permissions_directory (window);
+
+ if (!all_can_set_permissions (file_list))
+ {
+ gtk_widget_show (window->not_the_owner_label);
+ gtk_widget_show (window->bottom_prompt_seperator);
+ }
+
+ gtk_widget_show (window->permissions_grid);
+ create_simple_permissions (window, GTK_GRID (window->permissions_grid));
+
+#ifdef HAVE_SELINUX
+ gtk_widget_show (window->security_context_title_label);
+ gtk_widget_show (window->security_context_value_label);
+
+ /* Stash a copy of the file attribute name in this field for the callback's sake. */
+ g_object_set_data_full (G_OBJECT (window->security_context_value_label), "file_attribute",
+ g_strdup ("selinux_context"), g_free);
+
+ window->value_fields = g_list_prepend (window->value_fields,
+ window->security_context_value_label);
+#endif
+
+ if (window->has_recursive_apply)
+ {
+ gtk_widget_show_all (window->change_permissions_button_box);
+ g_signal_connect (window->change_permissions_button, "clicked",
+ G_CALLBACK (on_change_permissions_clicked),
+ window);
+ }
+ }
+ 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 (window))
+ {
+ file_name = nautilus_file_get_display_name (get_target_file (window));
+ prompt_text = g_strdup_printf (_("The permissions of “%s” could not be determined."), file_name);
+ gtk_label_set_text (GTK_LABEL (window->permission_indeterminable_label), prompt_text);
+ g_free (file_name);
+ g_free (prompt_text);
+ }
+
+ gtk_widget_show (window->permission_indeterminable_label);
+ }
+}
+
+static void
+append_extension_pages (NautilusPropertiesWindow *window)
+{
+ GList *providers;
+ GList *p;
+
+ providers = nautilus_module_get_extensions_for_type (NAUTILUS_TYPE_PROPERTY_PAGE_PROVIDER);
+
+ for (p = providers; p != NULL; p = p->next)
+ {
+ NautilusPropertyPageProvider *provider;
+ GList *pages;
+ GList *l;
+
+ provider = NAUTILUS_PROPERTY_PAGE_PROVIDER (p->data);
+
+ pages = nautilus_property_page_provider_get_pages
+ (provider, window->original_files);
+
+ for (l = pages; l != NULL; l = l->next)
+ {
+ NautilusPropertyPage *page;
+ GtkWidget *page_widget;
+ GtkWidget *label;
+
+ page = NAUTILUS_PROPERTY_PAGE (l->data);
+
+ g_object_get (G_OBJECT (page),
+ "page", &page_widget, "label", &label,
+ NULL);
+
+ gtk_notebook_append_page (window->notebook,
+ page_widget, label);
+ gtk_container_child_set (GTK_CONTAINER (window->notebook),
+ page_widget,
+ "tab-expand", TRUE,
+ NULL);
+
+ g_object_set_data (G_OBJECT (page_widget),
+ "is-extension-page",
+ page);
+
+ g_object_unref (page_widget);
+ g_object_unref (label);
+
+ g_object_unref (page);
+ }
+
+ g_list_free (pages);
+ }
+
+ nautilus_module_extension_list_free (providers);
+}
+
+static gboolean
+should_show_permissions (NautilusPropertiesWindow *window)
+{
+ GList *l;
+
+ /* Don't show permissions for Trash and Computer since they're not
+ * really file system objects.
+ */
+ for (l = window->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 *l;
+ GList *uris;
+ 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 *window = NAUTILUS_PROPERTIES_WINDOW (user_data);
+
+ if (!g_list_find (window->changed_files, file))
+ {
+ nautilus_file_ref (file);
+ window->changed_files = g_list_prepend (window->changed_files, file);
+ schedule_files_update (window);
+ }
+}
+
+static gboolean
+should_show_open_with (NautilusPropertiesWindow *window)
+{
+ NautilusFile *file;
+ char *mime_type;
+ char *extension;
+ gboolean hide;
+ g_autoptr (GAppInfo) app_info = NULL;
+
+ /* Don't show open with tab for desktop special icons (trash, etc)
+ * or desktop files. We don't get the open-with menu for these anyway.
+ *
+ * Also don't show it for folders. Changing the default app for folders
+ * leads to all sort of hard to understand errors.
+ */
+
+ if (is_multi_file_window (window))
+ {
+ GList *l;
+
+ if (!file_list_attributes_identical (window->target_files,
+ "mime_type"))
+ {
+ return FALSE;
+ }
+
+ for (l = window->target_files; l; l = l->next)
+ {
+ file = NAUTILUS_FILE (l->data);
+ app_info = nautilus_mime_get_default_application_for_file (file);
+ if (nautilus_file_is_directory (file) || !app_info || file == NULL)
+ {
+ return FALSE;
+ }
+ }
+
+ /* since we just confirmed all the mime types are the
+ * same we only need to test one file */
+ file = window->target_files->data;
+ }
+ else
+ {
+ file = get_target_file (window);
+
+ app_info = nautilus_mime_get_default_application_for_file (file);
+ if (nautilus_file_is_directory (file) || !app_info || file == NULL)
+ {
+ return FALSE;
+ }
+ }
+
+ mime_type = nautilus_file_get_mime_type (file);
+ extension = nautilus_file_get_extension (file);
+ hide = (g_content_type_is_unknown (mime_type) && extension == NULL);
+ g_free (mime_type);
+ g_free (extension);
+
+ return !hide;
+}
+
+static void
+add_clicked_cb (GtkButton *button,
+ gpointer user_data)
+{
+ NautilusPropertiesWindow *window = NAUTILUS_PROPERTIES_WINDOW (user_data);
+ GAppInfo *info;
+ gchar *message;
+ GError *error = NULL;
+
+ info = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (window->app_chooser_widget));
+
+ if (info == NULL)
+ {
+ return;
+ }
+
+ g_app_info_set_as_last_used_for_type (info, window->content_type, &error);
+
+ if (error != NULL)
+ {
+ message = g_strdup_printf (_("Error while adding “%s”: %s"),
+ g_app_info_get_display_name (info), error->message);
+ show_dialog (_("Could not add application"),
+ message,
+ GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (window))),
+ GTK_MESSAGE_ERROR);
+ g_error_free (error);
+ g_free (message);
+ }
+ else
+ {
+ gtk_app_chooser_refresh (GTK_APP_CHOOSER (window->app_chooser_widget));
+ g_signal_emit_by_name (nautilus_signaller_get_current (), "mime-data-changed");
+ }
+
+ g_object_unref (info);
+}
+
+static void
+remove_clicked_cb (GtkMenuItem *item,
+ gpointer user_data)
+{
+ NautilusPropertiesWindow *window = NAUTILUS_PROPERTIES_WINDOW (user_data);
+ GError *error;
+ GAppInfo *info;
+
+ info = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (window->app_chooser_widget));
+
+ if (info)
+ {
+ error = NULL;
+ if (!g_app_info_remove_supports_type (info,
+ window->content_type,
+ &error))
+ {
+ show_dialog (_("Could not forget association"),
+ error->message,
+ GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (window))),
+ GTK_MESSAGE_ERROR);
+ g_error_free (error);
+ }
+
+ gtk_app_chooser_refresh (GTK_APP_CHOOSER (window->app_chooser_widget));
+ g_object_unref (info);
+ }
+
+ g_signal_emit_by_name (nautilus_signaller_get_current (), "mime-data-changed");
+}
+
+static void
+populate_popup_cb (GtkAppChooserWidget *widget,
+ GtkMenu *menu,
+ GAppInfo *app,
+ gpointer user_data)
+{
+ GtkWidget *item;
+ NautilusPropertiesWindow *window = NAUTILUS_PROPERTIES_WINDOW (user_data);
+
+ if (g_app_info_can_remove_supports_type (app))
+ {
+ item = gtk_menu_item_new_with_label (_("Forget association"));
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+ gtk_widget_show (item);
+
+ g_signal_connect (item, "activate",
+ G_CALLBACK (remove_clicked_cb), window);
+ }
+}
+
+static void
+reset_clicked_cb (GtkButton *button,
+ gpointer user_data)
+{
+ NautilusPropertiesWindow *window;
+
+ window = NAUTILUS_PROPERTIES_WINDOW (user_data);
+
+ g_app_info_reset_type_associations (window->content_type);
+ gtk_app_chooser_refresh (GTK_APP_CHOOSER (window->app_chooser_widget));
+
+ g_signal_emit_by_name (nautilus_signaller_get_current (), "mime-data-changed");
+}
+
+static void
+set_as_default_clicked_cb (GtkButton *button,
+ gpointer user_data)
+{
+ NautilusPropertiesWindow *window = NAUTILUS_PROPERTIES_WINDOW (user_data);
+ GAppInfo *info;
+ GError *error = NULL;
+ gchar *message = NULL;
+
+ info = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (window->app_chooser_widget));
+
+ g_app_info_set_as_default_for_type (info, window->content_type,
+ &error);
+
+ if (error != NULL)
+ {
+ message = g_strdup_printf (_("Error while setting “%s” as default application: %s"),
+ g_app_info_get_display_name (info), error->message);
+ show_dialog (_("Could not set as default"),
+ message,
+ GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (window))),
+ GTK_MESSAGE_ERROR);
+ }
+
+ g_object_unref (info);
+
+ gtk_app_chooser_refresh (GTK_APP_CHOOSER (window->app_chooser_widget));
+ g_signal_emit_by_name (nautilus_signaller_get_current (), "mime-data-changed");
+}
+
+static gint
+app_compare (gconstpointer a,
+ gconstpointer b)
+{
+ return !g_app_info_equal (G_APP_INFO (a), G_APP_INFO (b));
+}
+
+static gboolean
+app_info_can_add (GAppInfo *info,
+ const gchar *content_type)
+{
+ GList *recommended, *fallback;
+ gboolean retval = FALSE;
+
+ recommended = g_app_info_get_recommended_for_type (content_type);
+ fallback = g_app_info_get_fallback_for_type (content_type);
+
+ if (g_list_find_custom (recommended, info, app_compare))
+ {
+ goto out;
+ }
+
+ if (g_list_find_custom (fallback, info, app_compare))
+ {
+ goto out;
+ }
+
+ retval = TRUE;
+
+out:
+ g_list_free_full (recommended, g_object_unref);
+ g_list_free_full (fallback, g_object_unref);
+
+ return retval;
+}
+
+static void
+application_selected_cb (GtkAppChooserWidget *widget,
+ GAppInfo *info,
+ gpointer user_data)
+{
+ NautilusPropertiesWindow *window = NAUTILUS_PROPERTIES_WINDOW (user_data);
+ GAppInfo *default_app;
+
+ default_app = g_app_info_get_default_for_type (window->content_type, FALSE);
+ if (default_app != NULL)
+ {
+ gtk_widget_set_sensitive (window->set_as_default_button,
+ !g_app_info_equal (info, default_app));
+ g_object_unref (default_app);
+ }
+ gtk_widget_set_sensitive (window->add_button,
+ app_info_can_add (info, window->content_type));
+}
+
+static void
+application_chooser_apply_labels (NautilusPropertiesWindow *window)
+{
+ gchar *label, *extension = NULL, *description = NULL;
+ gint num_files;
+ NautilusFile *file;
+
+ num_files = g_list_length (window->open_with_files);
+ file = window->open_with_files->data;
+
+ /* here we assume all files are of the same content type */
+ if (g_content_type_is_unknown (window->content_type))
+ {
+ extension = nautilus_file_get_extension (file);
+
+ /* Translators: the %s here is a file extension */
+ description = g_strdup_printf (_("%s document"), extension);
+ }
+ else
+ {
+ description = g_content_type_get_description (window->content_type);
+ }
+
+ if (num_files > 1)
+ {
+ /* Translators; %s here is a mime-type description */
+ label = g_strdup_printf (_("Open all files of type “%s” with"),
+ description);
+ }
+ else
+ {
+ gchar *display_name;
+ display_name = nautilus_file_get_display_name (file);
+
+ /* Translators: first %s is filename, second %s is mime-type description */
+ label = g_strdup_printf (_("Select an application to open “%s” and other files of type “%s”"),
+ display_name, description);
+
+ g_free (display_name);
+ }
+
+ gtk_label_set_markup (GTK_LABEL (window->open_with_label), label);
+
+ g_free (label);
+ g_free (extension);
+ g_free (description);
+}
+
+static void
+setup_app_chooser_area (NautilusPropertiesWindow *window)
+{
+ GAppInfo *info;
+
+ window->app_chooser_widget = gtk_app_chooser_widget_new (window->content_type);
+ gtk_box_pack_start (GTK_BOX (window->app_chooser_widget_box), window->app_chooser_widget, TRUE, TRUE, 0);
+
+ gtk_app_chooser_widget_set_show_default (GTK_APP_CHOOSER_WIDGET (window->app_chooser_widget), TRUE);
+ gtk_app_chooser_widget_set_show_fallback (GTK_APP_CHOOSER_WIDGET (window->app_chooser_widget), TRUE);
+ gtk_app_chooser_widget_set_show_other (GTK_APP_CHOOSER_WIDGET (window->app_chooser_widget), TRUE);
+ gtk_widget_show (window->app_chooser_widget);
+ g_signal_connect (window->reset_button, "clicked",
+ G_CALLBACK (reset_clicked_cb),
+ window);
+ g_signal_connect (window->add_button, "clicked",
+ G_CALLBACK (add_clicked_cb),
+ window);
+ g_signal_connect (window->set_as_default_button, "clicked",
+ G_CALLBACK (set_as_default_clicked_cb),
+ window);
+
+ /* initialize sensitivity */
+ info = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (window->app_chooser_widget));
+ if (info != NULL)
+ {
+ application_selected_cb (GTK_APP_CHOOSER_WIDGET (window->app_chooser_widget),
+ info, window);
+ g_object_unref (info);
+ }
+
+ g_signal_connect (window->app_chooser_widget,
+ "application-selected",
+ G_CALLBACK (application_selected_cb),
+ window);
+ g_signal_connect (window->app_chooser_widget,
+ "populate-popup",
+ G_CALLBACK (populate_popup_cb),
+ window);
+
+ application_chooser_apply_labels (window);
+}
+
+static void
+setup_open_with_page (NautilusPropertiesWindow *window)
+{
+ GList *files = NULL;
+ NautilusFile *target_file;
+
+ target_file = get_target_file (window);
+ window->content_type = nautilus_file_get_mime_type (target_file);
+
+ if (!is_multi_file_window (window))
+ {
+ files = g_list_prepend (NULL, target_file);
+ }
+ else
+ {
+ files = g_list_copy (window->original_files);
+ if (files == NULL)
+ {
+ return;
+ }
+ }
+
+ window->open_with_files = files;
+ setup_app_chooser_area (window);
+}
+
+
+static NautilusPropertiesWindow *
+create_properties_window (StartupData *startup_data)
+{
+ NautilusPropertiesWindow *window;
+ GList *l;
+
+ window = NAUTILUS_PROPERTIES_WINDOW (gtk_widget_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_screen (GTK_WINDOW (window),
+ gtk_widget_get_screen (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);
+ }
+
+ /* Set initial window title */
+ update_properties_window_title (window);
+
+ /* 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_box);
+ }
+
+ if (should_show_open_with (window))
+ {
+ setup_open_with_page (window);
+ gtk_widget_show (window->open_with_box);
+ }
+
+ /* append pages from available views */
+ append_extension_pages (window);
+
+ /* Update from initial state */
+ properties_window_update (window, NULL);
+
+ return window;
+}
+
+static GList *
+get_target_file_list (GList *original_files)
+{
+ GList *ret;
+ GList *l;
+
+ ret = NULL;
+
+ for (l = original_files; l != NULL; l = l->next)
+ {
+ NautilusFile *target;
+
+ target = get_target_file_for_original_file (NAUTILUS_FILE (l->data));
+
+ ret = g_list_prepend (ret, target);
+ }
+
+ ret = g_list_reverse (ret);
+
+ return ret;
+}
+
+static void
+add_window (NautilusPropertiesWindow *window)
+{
+ if (!is_multi_file_window (window))
+ {
+ g_hash_table_insert (windows,
+ get_original_file (window),
+ window);
+ g_object_set_data (G_OBJECT (window), "window_key",
+ get_original_file (window));
+ }
+}
+
+static void
+remove_window (NautilusPropertiesWindow *window)
+{
+ gpointer key;
+
+ key = g_object_get_data (G_OBJECT (window), "window_key");
+ if (key)
+ {
+ g_hash_table_remove (windows, key);
+ }
+}
+
+static NautilusPropertiesWindow *
+get_existing_window (GList *file_list)
+{
+ if (!file_list->next)
+ {
+ return g_hash_table_lookup (windows, file_list->data);
+ }
+
+ return 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);
+
+ add_window (new_window);
+ 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);
+ }
+}
+
+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;
+ GList *target_files;
+ NautilusPropertiesWindow *existing_window;
+ char *pending_key;
+
+ g_return_if_fail (original_files != NULL);
+ g_return_if_fail (parent_widget == NULL || GTK_IS_WIDGET (parent_widget));
+
+
+ /* Create the hash tables first time through. */
+ if (windows == NULL)
+ {
+ windows = g_hash_table_new (NULL, NULL);
+ }
+
+ if (pending_lists == NULL)
+ {
+ pending_lists = g_hash_table_new (g_str_hash, g_str_equal);
+ }
+
+ /* Look to see if there's already a window for this file. */
+ existing_window = get_existing_window (original_files);
+ if (existing_window != NULL)
+ {
+ if (parent_widget)
+ {
+ gtk_window_set_screen (GTK_WINDOW (existing_window),
+ gtk_widget_get_screen (parent_widget));
+ }
+ else if (startup_id)
+ {
+ gtk_window_set_startup_id (GTK_WINDOW (existing_window), startup_id);
+ }
+
+ gtk_window_present (GTK_WINDOW (existing_window));
+ startup_data = startup_data_new (NULL, NULL, NULL, NULL, NULL, NULL,
+ callback, callback_data, existing_window);
+ g_signal_connect (GTK_WIDGET (existing_window), "destroy",
+ G_CALLBACK (widget_on_destroy), startup_data);
+ return;
+ }
+
+
+ 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);
+
+ nautilus_file_list_free (target_files);
+ g_free (pending_key);
+
+ /* 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_destroy (GtkWidget *object)
+{
+ NautilusPropertiesWindow *window;
+ GList *l;
+
+ window = NAUTILUS_PROPERTIES_WINDOW (object);
+
+ remove_window (window);
+
+ unschedule_or_cancel_group_change (window);
+ unschedule_or_cancel_owner_change (window);
+
+ for (l = window->original_files; l != NULL; l = l->next)
+ {
+ nautilus_file_monitor_remove (NAUTILUS_FILE (l->data), &window->original_files);
+ }
+ nautilus_file_list_free (window->original_files);
+ window->original_files = NULL;
+
+ for (l = window->target_files; l != NULL; l = l->next)
+ {
+ nautilus_file_monitor_remove (NAUTILUS_FILE (l->data), &window->target_files);
+ }
+ nautilus_file_list_free (window->target_files);
+ window->target_files = NULL;
+
+ nautilus_file_list_free (window->changed_files);
+ window->changed_files = NULL;
+
+ if (window->deep_count_spinner_timeout_id > 0)
+ {
+ g_source_remove (window->deep_count_spinner_timeout_id);
+ }
+
+ while (window->deep_count_files)
+ {
+ stop_deep_count_for_file (window, window->deep_count_files->data);
+ }
+
+ g_list_free (window->permission_buttons);
+ window->permission_buttons = NULL;
+
+ g_list_free (window->permission_combos);
+ window->permission_combos = NULL;
+
+ g_list_free (window->change_permission_combos);
+ window->change_permission_combos = NULL;
+
+ if (window->initial_permissions)
+ {
+ g_hash_table_destroy (window->initial_permissions);
+ window->initial_permissions = NULL;
+ }
+
+ g_list_free (window->value_fields);
+ window->value_fields = NULL;
+
+ if (window->update_directory_contents_timeout_id != 0)
+ {
+ g_source_remove (window->update_directory_contents_timeout_id);
+ window->update_directory_contents_timeout_id = 0;
+ }
+
+ if (window->update_files_timeout_id != 0)
+ {
+ g_source_remove (window->update_files_timeout_id);
+ window->update_files_timeout_id = 0;
+ }
+
+ GTK_WIDGET_CLASS (nautilus_properties_window_parent_class)->destroy (object);
+}
+
+static void
+real_finalize (GObject *object)
+{
+ NautilusPropertiesWindow *window;
+
+ window = NAUTILUS_PROPERTIES_WINDOW (object);
+
+ g_list_free_full (window->mime_list, g_free);
+
+ g_free (window->pending_name);
+ g_free (window->content_type);
+ g_list_free (window->open_with_files);
+
+ if (window->select_idle_id != 0)
+ {
+ g_source_remove (window->select_idle_id);
+ }
+
+ 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 *properties_window)
+{
+ NautilusFile *file;
+ char *file_uri;
+ char *icon_path;
+
+ g_assert (icon_uri != NULL);
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (properties_window));
+
+ icon_path = g_filename_from_uri (icon_uri, NULL, NULL);
+ /* we don't allow remote URIs */
+ if (icon_path != NULL)
+ {
+ GList *l;
+
+ for (l = properties_window->original_files; l != NULL; l = l->next)
+ {
+ 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);
+
+ g_free (file_uri);
+ }
+
+ g_free (icon_path);
+ }
+}
+
+static void
+update_preview_callback (GtkFileChooser *icon_chooser,
+ NautilusPropertiesWindow *window)
+{
+ GtkWidget *preview_widget;
+ GdkPixbuf *pixbuf, *scaled_pixbuf;
+ char *filename;
+ double scale;
+
+ pixbuf = NULL;
+
+ filename = gtk_file_chooser_get_filename (icon_chooser);
+ if (filename != NULL)
+ {
+ pixbuf = gdk_pixbuf_new_from_file (filename, NULL);
+ }
+
+ if (pixbuf != NULL)
+ {
+ preview_widget = gtk_file_chooser_get_preview_widget (icon_chooser);
+ gtk_file_chooser_set_preview_widget_active (icon_chooser, TRUE);
+
+ if (gdk_pixbuf_get_width (pixbuf) > PREVIEW_IMAGE_WIDTH)
+ {
+ scale = (double) gdk_pixbuf_get_height (pixbuf) /
+ gdk_pixbuf_get_width (pixbuf);
+
+ scaled_pixbuf = gdk_pixbuf_scale_simple (pixbuf,
+ PREVIEW_IMAGE_WIDTH,
+ scale * PREVIEW_IMAGE_WIDTH,
+ GDK_INTERP_BILINEAR);
+ g_object_unref (pixbuf);
+ pixbuf = scaled_pixbuf;
+ }
+
+ gtk_image_set_from_pixbuf (GTK_IMAGE (preview_widget), pixbuf);
+ }
+ else
+ {
+ gtk_file_chooser_set_preview_widget_active (icon_chooser, FALSE);
+ }
+
+ g_free (filename);
+
+ if (pixbuf != NULL)
+ {
+ g_object_unref (pixbuf);
+ }
+}
+
+static void
+custom_icon_file_chooser_response_cb (GtkDialog *dialog,
+ gint response,
+ NautilusPropertiesWindow *window)
+{
+ char *uri;
+
+ switch (response)
+ {
+ case GTK_RESPONSE_NO:
+ {
+ reset_icon (window);
+ }
+ break;
+
+ case GTK_RESPONSE_OK:
+ {
+ uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog));
+ if (uri != NULL)
+ {
+ set_icon (uri, window);
+ }
+ else
+ {
+ reset_icon (window);
+ }
+ g_free (uri);
+ }
+ break;
+
+ default:
+ {
+ }
+ break;
+ }
+
+ gtk_widget_hide (GTK_WIDGET (dialog));
+}
+
+static void
+select_image_button_callback (GtkWidget *widget,
+ NautilusPropertiesWindow *window)
+{
+ GtkWidget *dialog, *preview;
+ GtkFileFilter *filter;
+ GList *l;
+ NautilusFile *file;
+ char *uri;
+ char *image_path;
+ gboolean revert_is_sensitive;
+
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (window));
+
+ dialog = window->icon_chooser;
+
+ if (dialog == NULL)
+ {
+ dialog = gtk_file_chooser_dialog_new (_("Select Custom Icon"), GTK_WINDOW (window),
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ _("_Revert"), GTK_RESPONSE_NO,
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ _("_Open"), GTK_RESPONSE_OK,
+ NULL);
+ gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (dialog),
+ g_get_user_special_dir (G_USER_DIRECTORY_PICTURES),
+ 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);
+
+ preview = gtk_image_new ();
+ gtk_widget_set_size_request (preview, PREVIEW_IMAGE_WIDTH, -1);
+ gtk_file_chooser_set_preview_widget (GTK_FILE_CHOOSER (dialog), preview);
+ gtk_file_chooser_set_use_preview_label (GTK_FILE_CHOOSER (dialog), FALSE);
+ gtk_file_chooser_set_preview_widget_active (GTK_FILE_CHOOSER (dialog), FALSE);
+
+ g_signal_connect (dialog, "update-preview",
+ G_CALLBACK (update_preview_callback), window);
+
+ window->icon_chooser = dialog;
+
+ g_object_add_weak_pointer (G_OBJECT (dialog),
+ (gpointer *) &window->icon_chooser);
+ }
+
+ /* it's likely that the user wants to pick an icon that is inside a local directory */
+ if (g_list_length (window->original_files) == 1)
+ {
+ file = NAUTILUS_FILE (window->original_files->data);
+
+ if (nautilus_file_is_directory (file))
+ {
+ uri = nautilus_file_get_uri (file);
+
+ image_path = g_filename_from_uri (uri, NULL, NULL);
+ if (image_path != NULL)
+ {
+ gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), image_path);
+ g_free (image_path);
+ }
+
+ g_free (uri);
+ }
+ }
+
+ revert_is_sensitive = FALSE;
+ for (l = window->original_files; l != NULL; l = l->next)
+ {
+ file = NAUTILUS_FILE (l->data);
+ image_path = nautilus_file_get_metadata (file, NAUTILUS_METADATA_KEY_CUSTOM_ICON, NULL);
+ revert_is_sensitive = (image_path != NULL);
+ g_free (image_path);
+
+ 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), window);
+ gtk_widget_show (dialog);
+}
+
+static void
+nautilus_properties_window_class_init (NautilusPropertiesWindowClass *klass)
+{
+ GtkBindingSet *binding_set;
+ GtkWidgetClass *widget_class;
+ GObjectClass *oclass;
+
+ widget_class = GTK_WIDGET_CLASS (klass);
+ oclass = G_OBJECT_CLASS (klass);
+ oclass->finalize = real_finalize;
+ widget_class->destroy = real_destroy;
+
+ binding_set = gtk_binding_set_by_class (klass);
+ g_signal_new ("close",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ 0, NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ gtk_binding_entry_add_signal (binding_set, GDK_KEY_Escape, 0,
+ "close", 0);
+
+ 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, notebook);
+ 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, basic_grid);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, name_title_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, name_stack);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, name_field);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, type_title_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, type_value_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, link_target_title_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, link_target_value_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, contents_title_label);
+ 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, size_title_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, size_value_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, parent_folder_title_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, parent_folder_value_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, original_folder_title_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, original_folder_value_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, volume_title_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, volume_value_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, trashed_on_title_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, trashed_on_value_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, accessed_title_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, accessed_value_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, spacer_2);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, modified_title_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, modified_value_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, spacer_3);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, free_space_title_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, free_space_value_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, volume_widget_box);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, open_in_disks_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, pie_chart);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, used_color);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, used_value);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, free_color);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, free_value);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, total_capacity_value);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, file_system_value);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, permissions_box);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, permissions_grid);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, bottom_prompt_seperator);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, not_the_owner_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, permission_indeterminable_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, owner_value_stack);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, owner_access_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, owner_folder_access_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, owner_file_access_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, owner_access_combo);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, owner_folder_access_combo);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, owner_file_access_combo);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, group_value_stack);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, group_access_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, group_folder_access_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, group_file_access_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, group_access_combo);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, group_folder_access_combo);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, group_file_access_combo);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, others_access_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, others_folder_access_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, others_file_access_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, others_access_combo);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, others_folder_access_combo);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, others_file_access_combo);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, execute_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, execute_checkbox);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, security_context_title_label);
+ 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_child (widget_class, NautilusPropertiesWindow, open_with_box);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, open_with_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, app_chooser_widget_box);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, reset_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, add_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, set_as_default_button);
+}
+
+static void
+nautilus_properties_window_init (NautilusPropertiesWindow *window)
+{
+ gtk_widget_init_template (GTK_WIDGET (window));
+ g_signal_connect (window, "close", G_CALLBACK (gtk_window_close), NULL);
+}
diff --git a/src/nautilus-properties-window.h b/src/nautilus-properties-window.h
new file mode 100644
index 0000000..c1b44a1
--- /dev/null
+++ b/src/nautilus-properties-window.h
@@ -0,0 +1,40 @@
+
+/* 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>
+
+#define NAUTILUS_TYPE_PROPERTIES_WINDOW (nautilus_properties_window_get_type ())
+
+G_DECLARE_FINAL_TYPE (NautilusPropertiesWindow, nautilus_properties_window,
+ NAUTILUS, PROPERTIES_WINDOW,
+ GtkWindow)
+
+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..0f284b5
--- /dev/null
+++ b/src/nautilus-query-editor.c
@@ -0,0 +1,751 @@
+/*
+ * 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 <libgd/gd.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
+{
+ GtkBox parent_instance;
+
+ GtkWidget *entry;
+ GtkWidget *popover;
+ GtkWidget *dropdown_button;
+
+ GdTaggedEntryTag *mime_types_tag;
+ GdTaggedEntryTag *date_range_tag;
+
+ 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_BOX);
+
+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_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 void
+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->entry))
+ {
+ /* avoid selecting the entry text */
+ gtk_widget_grab_focus (editor->entry);
+ gtk_editable_set_position (GTK_EDITABLE (editor->entry), -1);
+ }
+}
+
+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)
+{
+ NautilusQueryEditor *editor;
+
+ editor = NAUTILUS_QUERY_EDITOR (object);
+
+ g_clear_object (&editor->date_range_tag);
+ g_clear_object (&editor->mime_types_tag);
+
+ 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;
+
+ 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);
+
+ gtk_binding_entry_add_signal (gtk_binding_set_by_class (class),
+ GDK_KEY_Down,
+ 0,
+ "focus-view",
+ 0);
+
+ /**
+ * 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));
+}
+
+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_entry_get_text (GTK_ENTRY (editor->entry)));
+ 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 void
+entry_changed_cb (GtkWidget *entry,
+ NautilusQueryEditor *editor)
+{
+ if (editor->change_frozen)
+ {
+ return;
+ }
+
+ if (editor->query == NULL)
+ {
+ create_query (editor);
+ }
+ else
+ {
+ g_autofree gchar *text = NULL;
+
+ text = g_strdup (gtk_entry_get_text (GTK_ENTRY (editor->entry)));
+ text = g_strstrip (text);
+
+ nautilus_query_set_text (editor->query, text);
+ }
+
+ nautilus_query_editor_changed (editor);
+}
+
+static void
+nautilus_query_editor_on_stop_search (GtkWidget *entry,
+ NautilusQueryEditor *editor)
+{
+ g_signal_emit (editor, signals[CANCEL], 0);
+}
+
+/* Type */
+
+static void
+nautilus_query_editor_init (NautilusQueryEditor *editor)
+{
+ g_signal_connect (nautilus_preferences,
+ "changed::recursive-search",
+ G_CALLBACK (recursive_search_preferences_changed),
+ editor);
+}
+
+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);
+ }
+
+ gd_tagged_entry_remove_tag (GD_TAGGED_ENTRY (editor->entry),
+ editor->date_range_tag);
+ if (date_range)
+ {
+ g_autofree gchar *text_for_date_range = NULL;
+
+ text_for_date_range = get_text_for_date_range (date_range, TRUE);
+ gd_tagged_entry_tag_set_label (editor->date_range_tag,
+ text_for_date_range);
+ gd_tagged_entry_add_tag (GD_TAGGED_ENTRY (editor->entry),
+ GD_TAGGED_ENTRY_TAG (editor->date_range_tag));
+ }
+
+ nautilus_query_set_date_range (editor->query, date_range);
+
+ nautilus_query_editor_changed (editor);
+}
+
+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);
+ }
+
+ gd_tagged_entry_remove_tag (GD_TAGGED_ENTRY (editor->entry),
+ editor->mime_types_tag);
+ /* 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);
+ gd_tagged_entry_tag_set_label (editor->mime_types_tag,
+ nautilus_mime_types_group_get_name (mimetype_group));
+ gd_tagged_entry_add_tag (GD_TAGGED_ENTRY (editor->entry),
+ GD_TAGGED_ENTRY_TAG (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);
+ gd_tagged_entry_tag_set_label (editor->mime_types_tag, display_name);
+ gd_tagged_entry_add_tag (GD_TAGGED_ENTRY (editor->entry),
+ GD_TAGGED_ENTRY_TAG (editor->mime_types_tag));
+ }
+ nautilus_query_set_mime_types (editor->query, mimetypes);
+
+ nautilus_query_editor_changed (editor);
+}
+
+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
+entry_tag_clicked (NautilusQueryEditor *editor)
+{
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (editor->dropdown_button),
+ TRUE);
+}
+
+static void
+entry_tag_close_button_clicked (NautilusQueryEditor *editor,
+ GdTaggedEntryTag *tag)
+{
+ if (tag == editor->mime_types_tag)
+ {
+ nautilus_search_popover_reset_mime_types (NAUTILUS_SEARCH_POPOVER (editor->popover));
+ }
+ else
+ {
+ nautilus_search_popover_reset_date_range (NAUTILUS_SEARCH_POPOVER (editor->popover));
+ }
+}
+
+static void
+setup_widgets (NautilusQueryEditor *editor)
+{
+ GtkWidget *hbox;
+ GtkWidget *vbox;
+
+ /* vertical box that holds the search entry and the label below */
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+ gtk_container_add (GTK_CONTAINER (editor), vbox);
+
+ /* horizontal box */
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_style_context_add_class (gtk_widget_get_style_context (hbox), "linked");
+ gtk_container_add (GTK_CONTAINER (vbox), hbox);
+
+ /* create the search entry */
+ editor->entry = GTK_WIDGET (gd_tagged_entry_new ());
+ gtk_widget_set_hexpand (editor->entry, TRUE);
+
+ gtk_container_add (GTK_CONTAINER (hbox), editor->entry);
+
+ editor->mime_types_tag = gd_tagged_entry_tag_new (NULL);
+ editor->date_range_tag = gd_tagged_entry_tag_new (NULL);
+
+ g_signal_connect_swapped (editor->entry,
+ "tag-clicked",
+ G_CALLBACK (entry_tag_clicked),
+ editor);
+ g_signal_connect_swapped (editor->entry,
+ "tag-button-clicked",
+ G_CALLBACK (entry_tag_close_button_clicked),
+ editor);
+
+ /* 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_popover (GTK_MENU_BUTTON (editor->dropdown_button), editor->popover);
+ gtk_container_add (GTK_CONTAINER (hbox), editor->dropdown_button);
+
+ g_signal_connect (editor->entry, "activate",
+ G_CALLBACK (entry_activate_cb), editor);
+ g_signal_connect (editor->entry, "search-changed",
+ G_CALLBACK (entry_changed_cb), editor);
+ g_signal_connect (editor->entry, "stop-search",
+ G_CALLBACK (nautilus_query_editor_on_stop_search), 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);
+
+ /* show everything */
+ gtk_widget_show_all (vbox);
+}
+
+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->entry == NULL)
+ {
+ return NULL;
+ }
+
+ return editor->query;
+}
+
+GtkWidget *
+nautilus_query_editor_new (void)
+{
+ NautilusQueryEditor *editor;
+
+ editor = g_object_new (NAUTILUS_TYPE_QUERY_EDITOR, NULL);
+
+ setup_widgets (editor);
+
+ return GTK_WIDGET (editor);
+}
+
+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_entry_get_text (GTK_ENTRY (self->entry)));
+ current_text = g_strstrip (current_text);
+ if (!g_str_equal (current_text, text))
+ {
+ gtk_entry_set_text (GTK_ENTRY (self->entry), text);
+ }
+
+ 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_entry_set_text (GTK_ENTRY (self->entry), text);
+}
+
+gboolean
+nautilus_query_editor_handle_event (NautilusQueryEditor *self,
+ GdkEvent *event)
+{
+ g_return_val_if_fail (NAUTILUS_IS_QUERY_EDITOR (self), GDK_EVENT_PROPAGATE);
+ g_return_val_if_fail (event != NULL, GDK_EVENT_PROPAGATE);
+
+ return gtk_search_entry_handle_event (GTK_SEARCH_ENTRY (self->entry), event);
+}
diff --git a/src/nautilus-query-editor.h b/src/nautilus-query-editor.h
new file mode 100644
index 0000000..c09de18
--- /dev/null
+++ b/src/nautilus-query-editor.h
@@ -0,0 +1,77 @@
+/*
+ * 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, GtkBox)
+
+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,
+ GdkEvent *event);
diff --git a/src/nautilus-query.c b/src/nautilus-query.c
new file mode 100644
index 0000000..9c43743
--- /dev/null
+++ b/src/nautilus-query.c
@@ -0,0 +1,688 @@
+/*
+ * 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..6d6f662
--- /dev/null
+++ b/src/nautilus-query.h
@@ -0,0 +1,88 @@
+/*
+ * 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
+} 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..903b54f
--- /dev/null
+++ b/src/nautilus-rename-file-popover-controller.c
@@ -0,0 +1,451 @@
+/* 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 20
+#define RENAME_ENTRY_MAX_CHARS 35
+
+struct _NautilusRenameFilePopoverController
+{
+ NautilusFileNameWidgetController parent_instance;
+
+ NautilusFile *target_file;
+ gboolean target_is_folder;
+
+ GtkWidget *rename_file_popover;
+ GtkWidget *name_entry;
+ GtkWidget *name_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_entry_get_text (GTK_ENTRY (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 *display_name = NULL;
+
+ display_name = nautilus_file_get_display_name (self->target_file);
+
+ gtk_entry_set_text (GTK_ENTRY (widget), display_name);
+
+ gtk_editable_select_region (GTK_EDITABLE (widget), 0, -1);
+
+ return GDK_EVENT_STOP;
+}
+
+static gboolean
+name_entry_on_event (GtkWidget *widget,
+ GdkEvent *event,
+ gpointer user_data)
+{
+ NautilusRenameFilePopoverController *self;
+ guint keyval;
+ GdkModifierType state;
+
+ if (gdk_event_get_event_type (event) != GDK_KEY_PRESS)
+ {
+ return GDK_EVENT_PROPAGATE;
+ }
+
+ self = NAUTILUS_RENAME_FILE_POPOVER_CONTROLLER (user_data);
+
+ if (G_UNLIKELY (!gdk_event_get_keyval (event, &keyval)))
+ {
+ g_return_val_if_reached (GDK_EVENT_PROPAGATE);
+ }
+ if (G_UNLIKELY (!gdk_event_get_state (event, &state)))
+ {
+ g_return_val_if_reached (GDK_EVENT_PROPAGATE);
+ }
+
+ 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 (void)
+{
+ NautilusRenameFilePopoverController *self;
+ g_autoptr (GtkBuilder) builder = NULL;
+ GtkWidget *rename_file_popover;
+ 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-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"));
+ name_label = GTK_WIDGET (gtk_builder_get_object (builder, "name_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->name_label = name_label;
+
+ gtk_popover_set_default_widget (GTK_POPOVER (rename_file_popover), name_entry);
+
+ 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,
+ GtkWidget *relative_to)
+{
+ g_autoptr (NautilusDirectory) containing_directory = NULL;
+ g_autofree gchar *display_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);
+
+ self->key_press_event_handler_id = g_signal_connect (self->name_entry,
+ "event",
+ G_CALLBACK (name_entry_on_event),
+ self);
+
+ gtk_label_set_text (GTK_LABEL (self->name_label),
+ self->target_is_folder ? _("Folder name") :
+ _("File name"));
+
+ display_name = nautilus_file_get_display_name (self->target_file);
+
+ gtk_entry_set_text (GTK_ENTRY (self->name_entry), display_name);
+
+ gtk_popover_set_pointing_to (GTK_POPOVER (self->rename_file_popover), pointing_to);
+ gtk_popover_set_relative_to (GTK_POPOVER (self->rename_file_popover), relative_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 (display_name,
+ &start_offset, &end_offset);
+ gtk_editable_select_region (GTK_EDITABLE (self->name_entry),
+ start_offset, end_offset);
+ }
+
+ n_chars = g_utf8_strlen (display_name, -1);
+ gtk_entry_set_width_chars (GTK_ENTRY (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);
+
+ gtk_widget_destroy (self->rename_file_popover);
+ g_clear_object (&self->rename_file_popover);
+
+ 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..c4627bc
--- /dev/null
+++ b/src/nautilus-rename-file-popover-controller.h
@@ -0,0 +1,38 @@
+/* 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 (void);
+
+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,
+ GtkWidget *relative_to);
diff --git a/src/nautilus-search-directory-file.c b/src/nautilus-search-directory-file.c
new file mode 100644
index 0000000..1a87a59
--- /dev/null
+++ b/src/nautilus-search-directory-file.c
@@ -0,0 +1,310 @@
+/*
+ * 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));
+
+ /* All data for directory-as-file is always uptodate */
+ (*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..509010e
--- /dev/null
+++ b/src/nautilus-search-engine-model.c
@@ -0,0 +1,360 @@
+/*
+ * 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)
+ {
+ 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;
+ }
+ }
+ }
+
+ date_range = nautilus_query_get_date_range (model->query);
+ if (found && date_range != NULL)
+ {
+ NautilusQuerySearchType type;
+ guint64 current_file_unix_time;
+
+ 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);
+
+ if (type == NAUTILUS_QUERY_SEARCH_TYPE_LAST_ACCESS)
+ {
+ current_file_unix_time = nautilus_file_get_atime (file);
+ }
+ else
+ {
+ current_file_unix_time = nautilus_file_get_mtime (file);
+ }
+
+ found = nautilus_file_date_in_between (current_file_unix_time,
+ 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);
+ 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..dd53587
--- /dev/null
+++ b/src/nautilus-search-engine-recent.c
@@ -0,0 +1,430 @@
+/*
+ * 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 ","
+
+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,
+ GError **error)
+{
+ g_autofree gchar *path = NULL;
+ 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;
+ }
+
+ path = g_file_get_path (file);
+
+ 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, 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;
+ time_t modified, visited;
+ g_autoptr (GDateTime) gmodified = NULL;
+ g_autoptr (GDateTime) gvisited = NULL;
+
+ if (gtk_recent_info_is_local (info))
+ {
+ g_autoptr (GError) error = NULL;
+
+ if (!is_file_valid_recursive (self, file, &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;
+ }
+ }
+
+ modified = gtk_recent_info_get_modified (info);
+ visited = gtk_recent_info_get_visited (info);
+
+ gmodified = g_date_time_new_from_unix_local (modified);
+ gvisited = g_date_time_new_from_unix_local (visited);
+
+ if (date_range != NULL)
+ {
+ NautilusQuerySearchType type;
+ guint64 target_time;
+ 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);
+ target_time = (type == NAUTILUS_QUERY_SEARCH_TYPE_LAST_ACCESS) ?
+ visited : modified;
+
+ if (!nautilus_file_date_in_between (target_time,
+ 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, gmodified);
+ nautilus_search_hit_set_access_time (hit, gvisited);
+
+ 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..4264cfe
--- /dev/null
+++ b/src/nautilus-search-engine-simple.c
@@ -0,0 +1,593 @@
+/*
+ * 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_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;
+ guint64 atime;
+ guint64 mtime;
+ 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)
+ {
+ 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_attribute_uint64 (info, "time::modified");
+ atime = g_file_info_get_attribute_uint64 (info, "time::access");
+
+ if (found && date_range != NULL)
+ {
+ guint64 current_file_time;
+
+ initial_date = g_ptr_array_index (date_range, 0);
+ end_date = g_ptr_array_index (date_range, 1);
+
+ if (type == NAUTILUS_QUERY_SEARCH_TYPE_LAST_ACCESS)
+ {
+ current_file_time = atime;
+ }
+ else
+ {
+ current_file_time = mtime;
+ }
+ found = nautilus_file_date_in_between (current_file_time,
+ initial_date,
+ end_date);
+ }
+
+ if (found)
+ {
+ NautilusSearchHit *hit;
+ GDateTime *date;
+
+ uri = g_file_get_uri (child);
+ hit = nautilus_search_hit_new (uri);
+ g_free (uri);
+ nautilus_search_hit_set_fts_rank (hit, match);
+ date = g_date_time_new_from_unix_local (mtime);
+ nautilus_search_hit_set_modification_time (hit, date);
+ g_date_time_unref (date);
+
+ 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..9ebac0b
--- /dev/null
+++ b/src/nautilus-search-engine-tracker.c
@@ -0,0 +1,617 @@
+/*
+ * 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 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);
+ atime_str = tracker_sparql_cursor_get_string (cursor, 3, 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, 4, NULL);
+ nautilus_search_hit_set_fts_snippet (hit, snippet);
+ }
+
+ 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);
+ }
+
+ 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 gchar *
+filter_alnum_strdup (gchar *string)
+{
+ GString *filtered;
+ gchar *c;
+
+ filtered = g_string_new ("");
+ for (c = string; *c; c = g_utf8_next_char (c))
+ {
+ gunichar uc;
+
+ uc = g_utf8_get_char (c);
+ if (g_unichar_isalnum (uc))
+ {
+ g_string_append_unichar (filtered, uc);
+ }
+ else
+ {
+ g_string_append_c (filtered, ' ');
+ }
+ }
+
+ return g_string_free (filtered, FALSE);
+}
+
+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:fileLastAccessed(?file)");
+
+ if (tracker->fts_enabled && *search_text)
+ {
+ g_string_append (sparql, " fts:snippet(?content)");
+ }
+
+ 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.");
+
+ 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_autofree gchar *filtered_search_text;
+
+ filtered_search_text = filter_alnum_strdup (search_text);
+ g_string_append_printf (sparql,
+ " { "
+ " ?content nie:isStoredAs ?file ."
+ " ?content fts:match \"%s*\" ."
+ " BIND(fts:rank(?content) AS ?rank1) ."
+ " } UNION",
+ filtered_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 (initial_date, "%Y-%m-%dT%H:%M:%S");
+ end_date_format = g_date_time_format (shifted_end_date, "%Y-%m-%dT%H:%M:%S");
+
+ 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
+ {
+ g_string_append_printf (sparql, "?mtime >= \"%s\"^^xsd:dateTime", initial_date_format);
+ g_string_append_printf (sparql, " && ?mtime <= \"%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..c8505e5
--- /dev/null
+++ b/src/nautilus-search-engine.c
@@ -0,0 +1,590 @@
+/*
+ * 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_TYPE,
+ NULL, NULL);
+ if (file_system_info != NULL)
+ {
+ const char *file_system;
+
+ file_system = g_file_info_get_attribute_string (file_system_info,
+ G_FILE_ATTRIBUTE_FILESYSTEM_TYPE);
+
+ return !nautilus_file_system_is_remote (file_system);
+ }
+ }
+ }
+
+ 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..4eb2e39
--- /dev/null
+++ b/src/nautilus-search-hit.c
@@ -0,0 +1,438 @@
+/*
+ * 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;
+ gdouble fts_rank;
+ gchar *fts_snippet;
+
+ gdouble relevance;
+};
+
+enum
+{
+ PROP_URI = 1,
+ PROP_RELEVANCE,
+ PROP_MODIFICATION_TIME,
+ PROP_ACCESS_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_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_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_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);
+ }
+
+ 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_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..33c7990
--- /dev/null
+++ b/src/nautilus-search-hit.h
@@ -0,0 +1,48 @@
+/*
+ * 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_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..fe4aae0
--- /dev/null
+++ b/src/nautilus-search-popover.c
@@ -0,0 +1,1023 @@
+/* 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 *full_text_search_button;
+ GtkWidget *filename_search_button;
+
+ NautilusQuery *query;
+
+ 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;
+ guint year, month, day;
+ GPtrArray *date_range;
+
+ gtk_calendar_get_date (calendar, &year, &month, &day);
+
+ date = g_date_time_new_local (year, month + 1, day, 0, 0, 0);
+
+ 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_month (GTK_CALENDAR (popover->calendar),
+ g_date_time_get_month (date_initial) - 1,
+ g_date_time_get_year (date_initial));
+
+ gtk_calendar_select_day (GTK_CALENDAR (popover->calendar),
+ g_date_time_get_day_of_month (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_entry_get_text (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,
+ GdkEvent *event,
+ 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 (GtkToggleButton *button,
+ NautilusSearchPopover *popover)
+{
+ NautilusQuerySearchType type = -1;
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (popover->last_modified_button)))
+ {
+ type = NAUTILUS_QUERY_SEARCH_TYPE_LAST_MODIFIED;
+ }
+ else
+ {
+ type = NAUTILUS_QUERY_SEARCH_TYPE_LAST_ACCESS;
+ }
+
+ 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_container_add (GTK_CONTAINER (row), label);
+ gtk_widget_show_all (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_container_add (GTK_CONTAINER (popover->dates_listbox), row);
+
+ /* 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_container_add (GTK_CONTAINER (popover->dates_listbox), row);
+
+ 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_container_add (GTK_CONTAINER (popover->type_listbox), row);
+ }
+
+ /* Other types */
+ row = create_row_for_label (_("Other Type…"), TRUE);
+ g_object_set_data (G_OBJECT (row), "mimetype-group", GINT_TO_POINTER (-1));
+ gtk_container_add (GTK_CONTAINER (popover->type_listbox), row);
+}
+
+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
+show_other_types_dialog (NautilusSearchPopover *popover)
+{
+ GList *mime_infos, *l;
+ GtkWidget *dialog;
+ GtkWidget *scrolled, *treeview;
+ GtkListStore *store;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *renderer;
+ GtkWidget *toplevel;
+ GtkTreeSelection *selection;
+
+ mime_infos = g_content_types_get_registered ();
+
+ store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
+ for (l = mime_infos; l != NULL; l = l->next)
+ {
+ GtkTreeIter iter;
+ char *mime_type = l->data;
+ char *description;
+
+ description = g_content_type_get_description (mime_type);
+ if (description == NULL)
+ {
+ description = g_strdup (mime_type);
+ }
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ 0, description,
+ 1, mime_type,
+ -1);
+
+ g_free (mime_type);
+ g_free (description);
+ }
+ g_list_free (mime_infos);
+
+ toplevel = gtk_widget_get_toplevel (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_window_set_default_size (GTK_WINDOW (dialog), 400, 600);
+
+ scrolled = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+
+ gtk_widget_show (scrolled);
+ gtk_container_set_border_width (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), 0);
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), scrolled, TRUE, TRUE, 0);
+
+ treeview = gtk_tree_view_new ();
+ gtk_tree_view_set_model (GTK_TREE_VIEW (treeview), GTK_TREE_MODEL (store));
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store), 0, GTK_SORT_ASCENDING);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
+
+
+ renderer = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes ("Name",
+ renderer,
+ "text",
+ 0,
+ NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
+ gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE);
+
+ gtk_widget_show (treeview);
+ gtk_container_add (GTK_CONTAINER (scrolled), treeview);
+
+ if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK)
+ {
+ GtkTreeIter iter;
+ char *mimetype;
+ char *description;
+
+ gtk_tree_selection_get_selected (selection, NULL, &iter);
+ gtk_tree_model_get (GTK_TREE_MODEL (store), &iter,
+ 0, &description,
+ 1, &mimetype,
+ -1);
+
+ 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");
+ }
+
+ gtk_widget_destroy (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_entry_set_text (GTK_ENTRY (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_entry_set_text (GTK_ENTRY (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_month (GTK_CALENDAR (self->calendar),
+ g_date_time_get_month (now) - 1,
+ g_date_time_get_year (now));
+
+ gtk_calendar_select_day (GTK_CALENDAR (self->calendar),
+ g_date_time_get_day_of_month (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, 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_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->last_modified_button), TRUE);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->last_used_button), FALSE);
+ }
+ else
+ {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->last_modified_button), FALSE);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->last_used_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-selection-canvas-item.c b/src/nautilus-selection-canvas-item.c
new file mode 100644
index 0000000..b54cb68
--- /dev/null
+++ b/src/nautilus-selection-canvas-item.c
@@ -0,0 +1,554 @@
+/* Nautilus - Canvas item for floating selection.
+ *
+ * Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation
+ * 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: Federico Mena <federico@nuclecu.unam.mx>
+ * Cosimo Cecchi <cosimoc@redhat.com>
+ */
+
+#include <config.h>
+
+#include "nautilus-selection-canvas-item.h"
+
+#include <math.h>
+
+enum
+{
+ PROP_X1 = 1,
+ PROP_Y1,
+ PROP_X2,
+ PROP_Y2,
+ NUM_PROPERTIES
+};
+
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL };
+
+typedef struct
+{
+ /*< public >*/
+ int x0, y0, x1, y1;
+} Rect;
+
+struct _NautilusSelectionCanvasItemDetails
+{
+ Rect last_update_rect;
+ Rect last_outline_update_rect;
+ int last_outline_update_width;
+
+ double x1, y1, x2, y2; /* Corners of item */
+};
+
+G_DEFINE_TYPE (NautilusSelectionCanvasItem, nautilus_selection_canvas_item, EEL_TYPE_CANVAS_ITEM);
+
+static void
+nautilus_selection_canvas_item_draw (EelCanvasItem *item,
+ cairo_t *cr,
+ cairo_region_t *region)
+{
+ NautilusSelectionCanvasItem *self;
+ double x1, y1, x2, y2;
+ int cx1, cy1, cx2, cy2;
+ double i2w_dx, i2w_dy;
+ GtkStyleContext *context;
+
+ self = NAUTILUS_SELECTION_CANVAS_ITEM (item);
+
+ /* Get canvas pixel coordinates */
+ i2w_dx = 0.0;
+ i2w_dy = 0.0;
+ eel_canvas_item_i2w (item, &i2w_dx, &i2w_dy);
+
+ x1 = self->priv->x1 + i2w_dx;
+ y1 = self->priv->y1 + i2w_dy;
+ x2 = self->priv->x2 + i2w_dx;
+ y2 = self->priv->y2 + i2w_dy;
+
+ eel_canvas_w2c (item->canvas, x1, y1, &cx1, &cy1);
+ eel_canvas_w2c (item->canvas, x2, y2, &cx2, &cy2);
+
+ if (cx2 <= cx1 || cy2 <= cy1)
+ {
+ return;
+ }
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (item->canvas));
+
+ gtk_style_context_save (context);
+
+ gtk_style_context_add_class (context, GTK_STYLE_CLASS_RUBBERBAND);
+
+ cairo_save (cr);
+
+ gtk_render_background (context, cr,
+ cx1, cy1,
+ cx2 - cx1,
+ cy2 - cy1);
+ gtk_render_frame (context, cr,
+ cx1, cy1,
+ cx2 - cx1,
+ cy2 - cy1);
+
+ cairo_restore (cr);
+
+ gtk_style_context_restore (context);
+}
+
+static double
+nautilus_selection_canvas_item_point (EelCanvasItem *item,
+ double x,
+ double y,
+ int cx,
+ int cy,
+ EelCanvasItem **actual_item)
+{
+ NautilusSelectionCanvasItem *self;
+ double x1, y1, x2, y2;
+ double hwidth;
+ double dx, dy;
+
+ self = NAUTILUS_SELECTION_CANVAS_ITEM (item);
+ *actual_item = item;
+
+ /* Find the bounds for the rectangle plus its outline width */
+
+ x1 = self->priv->x1;
+ y1 = self->priv->y1;
+ x2 = self->priv->x2;
+ y2 = self->priv->y2;
+
+ hwidth = (1.0 / item->canvas->pixels_per_unit) / 2.0;
+
+ x1 -= hwidth;
+ y1 -= hwidth;
+ x2 += hwidth;
+ y2 += hwidth;
+
+ /* Is point inside rectangle (which can be hollow if it has no fill set)? */
+
+ if ((x >= x1) && (y >= y1) && (x <= x2) && (y <= y2))
+ {
+ return 0.0;
+ }
+
+ /* Point is outside rectangle */
+
+ if (x < x1)
+ {
+ dx = x1 - x;
+ }
+ else if (x > x2)
+ {
+ dx = x - x2;
+ }
+ else
+ {
+ dx = 0.0;
+ }
+
+ if (y < y1)
+ {
+ dy = y1 - y;
+ }
+ else if (y > y2)
+ {
+ dy = y - y2;
+ }
+ else
+ {
+ dy = 0.0;
+ }
+
+ return sqrt (dx * dx + dy * dy);
+}
+
+static void
+request_redraw_borders (EelCanvas *canvas,
+ Rect *update_rect,
+ int width)
+{
+ /* Top */
+ eel_canvas_request_redraw (canvas,
+ update_rect->x0, update_rect->y0,
+ update_rect->x1, update_rect->y0 + width);
+ /* Bottom */
+ eel_canvas_request_redraw (canvas,
+ update_rect->x0, update_rect->y1 - width,
+ update_rect->x1, update_rect->y1);
+ /* Left */
+ eel_canvas_request_redraw (canvas,
+ update_rect->x0, update_rect->y0,
+ update_rect->x0 + width, update_rect->y1);
+ /* Right */
+ eel_canvas_request_redraw (canvas,
+ update_rect->x1 - width, update_rect->y0,
+ update_rect->x1, update_rect->y1);
+}
+
+static Rect make_rect (int x0,
+ int y0,
+ int x1,
+ int y1);
+
+static int
+rect_empty (const Rect *src)
+{
+ return (src->x1 <= src->x0 || src->y1 <= src->y0);
+}
+
+static gboolean
+rects_intersect (Rect r1,
+ Rect r2)
+{
+ if (r1.x0 >= r2.x1)
+ {
+ return FALSE;
+ }
+ if (r2.x0 >= r1.x1)
+ {
+ return FALSE;
+ }
+ if (r1.y0 >= r2.y1)
+ {
+ return FALSE;
+ }
+ if (r2.y0 >= r1.y1)
+ {
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static void
+diff_rects_guts (Rect ra,
+ Rect rb,
+ int *count,
+ Rect result[4])
+{
+ if (ra.x0 < rb.x0)
+ {
+ result[(*count)++] = make_rect (ra.x0, ra.y0, rb.x0, ra.y1);
+ }
+ if (ra.y0 < rb.y0)
+ {
+ result[(*count)++] = make_rect (ra.x0, ra.y0, ra.x1, rb.y0);
+ }
+ if (ra.x1 < rb.x1)
+ {
+ result[(*count)++] = make_rect (ra.x1, rb.y0, rb.x1, rb.y1);
+ }
+ if (ra.y1 < rb.y1)
+ {
+ result[(*count)++] = make_rect (rb.x0, ra.y1, rb.x1, rb.y1);
+ }
+}
+
+static void
+diff_rects (Rect r1,
+ Rect r2,
+ int *count,
+ Rect result[4])
+{
+ g_assert (count != NULL);
+ g_assert (result != NULL);
+
+ *count = 0;
+
+ if (rects_intersect (r1, r2))
+ {
+ diff_rects_guts (r1, r2, count, result);
+ diff_rects_guts (r2, r1, count, result);
+ }
+ else
+ {
+ if (!rect_empty (&r1))
+ {
+ result[(*count)++] = r1;
+ }
+ if (!rect_empty (&r2))
+ {
+ result[(*count)++] = r2;
+ }
+ }
+}
+
+static Rect
+make_rect (int x0,
+ int y0,
+ int x1,
+ int y1)
+{
+ Rect r;
+
+ r.x0 = x0;
+ r.y0 = y0;
+ r.x1 = x1;
+ r.y1 = y1;
+ return r;
+}
+
+static void
+nautilus_selection_canvas_item_update (EelCanvasItem *item,
+ double i2w_dx,
+ double i2w_dy,
+ gint flags)
+{
+ NautilusSelectionCanvasItem *self;
+ NautilusSelectionCanvasItemDetails *priv;
+ double x1, y1, x2, y2;
+ int cx1, cy1, cx2, cy2;
+ int repaint_rects_count, i;
+ GtkStyleContext *context;
+ GtkBorder border;
+ Rect update_rect, repaint_rects[4];
+
+ if (EEL_CANVAS_ITEM_CLASS (nautilus_selection_canvas_item_parent_class)->update)
+ {
+ (*EEL_CANVAS_ITEM_CLASS (nautilus_selection_canvas_item_parent_class)->update)(item, i2w_dx, i2w_dy, flags);
+ }
+
+ self = NAUTILUS_SELECTION_CANVAS_ITEM (item);
+ priv = self->priv;
+
+ x1 = priv->x1 + i2w_dx;
+ y1 = priv->y1 + i2w_dy;
+ x2 = priv->x2 + i2w_dx;
+ y2 = priv->y2 + i2w_dy;
+
+ eel_canvas_w2c (item->canvas, x1, y1, &cx1, &cy1);
+ eel_canvas_w2c (item->canvas, x2, y2, &cx2, &cy2);
+
+ update_rect = make_rect (cx1, cy1, cx2 + 1, cy2 + 1);
+ diff_rects (update_rect, priv->last_update_rect,
+ &repaint_rects_count, repaint_rects);
+ for (i = 0; i < repaint_rects_count; i++)
+ {
+ eel_canvas_request_redraw (item->canvas,
+ repaint_rects[i].x0, repaint_rects[i].y0,
+ repaint_rects[i].x1, repaint_rects[i].y1);
+ }
+
+ priv->last_update_rect = update_rect;
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (item->canvas));
+
+ gtk_style_context_save (context);
+ gtk_style_context_add_class (context, GTK_STYLE_CLASS_RUBBERBAND);
+ gtk_style_context_get_border (context, GTK_STATE_FLAG_NORMAL, &border);
+ gtk_style_context_restore (context);
+
+ cx1 -= border.left;
+ cy1 -= border.top;
+ cx2 += border.right;
+ cy2 += border.bottom;
+
+ update_rect = make_rect (cx1, cy1, cx2, cy2);
+ request_redraw_borders (item->canvas, &update_rect,
+ border.left + border.top + border.right + border.bottom);
+ request_redraw_borders (item->canvas, &priv->last_outline_update_rect,
+ priv->last_outline_update_width);
+ priv->last_outline_update_rect = update_rect;
+ priv->last_outline_update_width = border.left + border.top + border.right + border.bottom;
+
+ item->x1 = cx1;
+ item->y1 = cy1;
+ item->x2 = cx2;
+ item->y2 = cy2;
+}
+
+static void
+nautilus_selection_canvas_item_translate (EelCanvasItem *item,
+ double dx,
+ double dy)
+{
+ NautilusSelectionCanvasItem *self;
+
+ self = NAUTILUS_SELECTION_CANVAS_ITEM (item);
+
+ self->priv->x1 += dx;
+ self->priv->y1 += dy;
+ self->priv->x2 += dx;
+ self->priv->y2 += dy;
+}
+
+static void
+nautilus_selection_canvas_item_bounds (EelCanvasItem *item,
+ double *x1,
+ double *y1,
+ double *x2,
+ double *y2)
+{
+ NautilusSelectionCanvasItem *self;
+ GtkStyleContext *context;
+ GtkBorder border;
+
+ self = NAUTILUS_SELECTION_CANVAS_ITEM (item);
+ context = gtk_widget_get_style_context (GTK_WIDGET (item->canvas));
+
+ gtk_style_context_save (context);
+ gtk_style_context_add_class (context, GTK_STYLE_CLASS_RUBBERBAND);
+ gtk_style_context_get_border (context, GTK_STATE_FLAG_NORMAL, &border);
+ gtk_style_context_restore (context);
+
+ *x1 = self->priv->x1 - (border.left / item->canvas->pixels_per_unit) / 2.0;
+ *y1 = self->priv->y1 - (border.top / item->canvas->pixels_per_unit) / 2.0;
+ *x2 = self->priv->x2 + (border.right / item->canvas->pixels_per_unit) / 2.0;
+ *y2 = self->priv->y2 + (border.bottom / item->canvas->pixels_per_unit) / 2.0;
+}
+
+static void
+nautilus_selection_canvas_item_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ EelCanvasItem *item;
+ NautilusSelectionCanvasItem *self;
+
+ self = NAUTILUS_SELECTION_CANVAS_ITEM (object);
+ item = EEL_CANVAS_ITEM (object);
+
+ switch (param_id)
+ {
+ case PROP_X1:
+ {
+ self->priv->x1 = g_value_get_double (value);
+
+ eel_canvas_item_request_update (item);
+ }
+ break;
+
+ case PROP_Y1:
+ {
+ self->priv->y1 = g_value_get_double (value);
+
+ eel_canvas_item_request_update (item);
+ }
+ break;
+
+ case PROP_X2:
+ {
+ self->priv->x2 = g_value_get_double (value);
+
+ eel_canvas_item_request_update (item);
+ }
+ break;
+
+ case PROP_Y2:
+ {
+ self->priv->y2 = g_value_get_double (value);
+
+ eel_canvas_item_request_update (item);
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ }
+ break;
+ }
+}
+
+static void
+nautilus_selection_canvas_item_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusSelectionCanvasItem *self;
+
+ self = NAUTILUS_SELECTION_CANVAS_ITEM (object);
+
+ switch (param_id)
+ {
+ case PROP_X1:
+ {
+ g_value_set_double (value, self->priv->x1);
+ }
+ break;
+
+ case PROP_Y1:
+ {
+ g_value_set_double (value, self->priv->y1);
+ }
+ break;
+
+ case PROP_X2:
+ {
+ g_value_set_double (value, self->priv->x2);
+ }
+ break;
+
+ case PROP_Y2:
+ {
+ g_value_set_double (value, self->priv->y2);
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ }
+ break;
+ }
+}
+
+static void
+nautilus_selection_canvas_item_class_init (NautilusSelectionCanvasItemClass *klass)
+{
+ EelCanvasItemClass *item_class;
+ GObjectClass *gobject_class;
+
+ gobject_class = G_OBJECT_CLASS (klass);
+ item_class = EEL_CANVAS_ITEM_CLASS (klass);
+
+ gobject_class->set_property = nautilus_selection_canvas_item_set_property;
+ gobject_class->get_property = nautilus_selection_canvas_item_get_property;
+
+ item_class->draw = nautilus_selection_canvas_item_draw;
+ item_class->point = nautilus_selection_canvas_item_point;
+ item_class->update = nautilus_selection_canvas_item_update;
+ item_class->bounds = nautilus_selection_canvas_item_bounds;
+ item_class->translate = nautilus_selection_canvas_item_translate;
+
+ properties[PROP_X1] =
+ g_param_spec_double ("x1", NULL, NULL,
+ -G_MAXDOUBLE, G_MAXDOUBLE, 0,
+ G_PARAM_READWRITE);
+ properties[PROP_Y1] =
+ g_param_spec_double ("y1", NULL, NULL,
+ -G_MAXDOUBLE, G_MAXDOUBLE, 0,
+ G_PARAM_READWRITE);
+ properties[PROP_X2] =
+ g_param_spec_double ("x2", NULL, NULL,
+ -G_MAXDOUBLE, G_MAXDOUBLE, 0,
+ G_PARAM_READWRITE);
+ properties[PROP_Y2] =
+ g_param_spec_double ("y2", NULL, NULL,
+ -G_MAXDOUBLE, G_MAXDOUBLE, 0,
+ G_PARAM_READWRITE);
+
+ g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties);
+ g_type_class_add_private (klass, sizeof (NautilusSelectionCanvasItemDetails));
+}
+
+static void
+nautilus_selection_canvas_item_init (NautilusSelectionCanvasItem *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NAUTILUS_TYPE_SELECTION_CANVAS_ITEM,
+ NautilusSelectionCanvasItemDetails);
+}
diff --git a/src/nautilus-selection-canvas-item.h b/src/nautilus-selection-canvas-item.h
new file mode 100644
index 0000000..6c13fe2
--- /dev/null
+++ b/src/nautilus-selection-canvas-item.h
@@ -0,0 +1,62 @@
+
+/* Nautilus - Canvas item for floating selection.
+ *
+ * Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation
+ * 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: Federico Mena <federico@nuclecu.unam.mx>
+ * Cosimo Cecchi <cosimoc@redhat.com>
+ */
+
+#pragma once
+
+#include <eel/eel-canvas.h>
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_SELECTION_CANVAS_ITEM nautilus_selection_canvas_item_get_type()
+#define NAUTILUS_SELECTION_CANVAS_ITEM(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_SELECTION_CANVAS_ITEM, NautilusSelectionCanvasItem))
+#define NAUTILUS_SELECTION_CANVAS_ITEM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_SELECTION_CANVAS_ITEM, NautilusSelectionCanvasItemClass))
+#define NAUTILUS_IS_SELECTION_CANVAS_ITEM(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_SELECTION_CANVAS_ITEM))
+#define NAUTILUS_IS_SELECTION_CANVAS_ITEM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_SELECTION_CANVAS_ITEM))
+#define NAUTILUS_SELECTION_CANVAS_ITEM_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_SELECTION_CANVAS_ITEM, NautilusSelectionCanvasItemClass))
+
+typedef struct _NautilusSelectionCanvasItem NautilusSelectionCanvasItem;
+typedef struct _NautilusSelectionCanvasItemClass NautilusSelectionCanvasItemClass;
+typedef struct _NautilusSelectionCanvasItemDetails NautilusSelectionCanvasItemDetails;
+
+struct _NautilusSelectionCanvasItem {
+ EelCanvasItem item;
+ NautilusSelectionCanvasItemDetails *priv;
+ gpointer user_data;
+};
+
+struct _NautilusSelectionCanvasItemClass {
+ EelCanvasItemClass parent_class;
+};
+
+/* GObject */
+GType nautilus_selection_canvas_item_get_type (void);
+
+void nautilus_selection_canvas_item_fade_out (NautilusSelectionCanvasItem *self,
+ guint transition_time);
+
+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..c0da929
--- /dev/null
+++ b/src/nautilus-shell-search-provider.c
@@ -0,0 +1,811 @@
+/*
+ * 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;
+
+ 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)
+ {
+ nautilus_search_provider_stop (NAUTILUS_SEARCH_PROVIDER (self->current_search->engine));
+ }
+}
+
+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;
+ GDBusMethodInvocation *invocation;
+
+ gchar **uris;
+} ResultMetasData;
+
+static void
+result_metas_data_free (ResultMetasData *data)
+{
+ 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}"));
+
+ 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_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 (gdk_display_get_monitor (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_pixbuf (file, 128, TRUE,
+ 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);
+ }
+
+ 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,
+ NULL,
+ result_list_attributes_ready_cb,
+ data);
+ nautilus_file_list_free (missing_files);
+ return TRUE;
+}
+
+static gboolean
+handle_activate_result (NautilusShellSearchProvider2 *skeleton,
+ GDBusMethodInvocation *invocation,
+ gchar *result,
+ gchar **terms,
+ guint32 timestamp,
+ gpointer user_data)
+{
+ gboolean res;
+ GFile *file;
+
+ res = gtk_show_uri_on_window (NULL, result, timestamp, NULL);
+
+ if (!res)
+ {
+ file = g_file_new_for_uri (result);
+ g_application_open (g_application_get_default (), &file, 1, "");
+ g_object_unref (file);
+ }
+
+ nautilus_shell_search_provider2_complete_activate_result (skeleton, invocation);
+ 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 (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..dc10a82
--- /dev/null
+++ b/src/nautilus-special-location-bar.c
@@ -0,0 +1,194 @@
+/*
+ * 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-special-location-bar.h"
+#include "nautilus-enum-types.h"
+
+struct _NautilusSpecialLocationBar
+{
+ GtkInfoBar parent_instance;
+
+ GtkWidget *label;
+ GtkWidget *learn_more_label;
+ NautilusSpecialLocation special_location;
+};
+
+enum
+{
+ PROP_0,
+ PROP_SPECIAL_LOCATION,
+};
+
+G_DEFINE_TYPE (NautilusSpecialLocationBar, nautilus_special_location_bar, GTK_TYPE_INFO_BAR)
+
+static void
+set_special_location (NautilusSpecialLocationBar *bar,
+ NautilusSpecialLocation location)
+{
+ char *message;
+ char *learn_more_markup = 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;
+
+ 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);
+ }
+}
+
+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 *location_area;
+ GtkWidget *action_area;
+ PangoAttrList *attrs;
+
+ location_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (bar));
+ action_area = gtk_info_bar_get_action_area (GTK_INFO_BAR (bar));
+
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (action_area), GTK_ORIENTATION_HORIZONTAL);
+
+ 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_container_add (GTK_CONTAINER (location_area), bar->label);
+
+ 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_container_add (GTK_CONTAINER (location_area), bar->learn_more_label);
+}
+
+GtkWidget *
+nautilus_special_location_bar_new (NautilusSpecialLocation location)
+{
+ return g_object_new (NAUTILUS_TYPE_SPECIAL_LOCATION_BAR,
+ "message-type", GTK_MESSAGE_QUESTION,
+ "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..d5772c1
--- /dev/null
+++ b/src/nautilus-special-location-bar.h
@@ -0,0 +1,36 @@
+/*
+ * 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>
+
+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, GtkInfoBar)
+
+typedef enum {
+ NAUTILUS_SPECIAL_LOCATION_TEMPLATES,
+ NAUTILUS_SPECIAL_LOCATION_SCRIPTS,
+} NautilusSpecialLocation;
+
+GtkWidget *nautilus_special_location_bar_new (NautilusSpecialLocation location);
+
+G_END_DECLS \ No newline at end of file
diff --git a/src/nautilus-starred-directory.c b/src/nautilus-starred-directory.c
new file mode 100644
index 0000000..c1a3fe5
--- /dev/null
+++ b/src/nautilus-starred-directory.c
@@ -0,0 +1,583 @@
+/* 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;
+
+ NautilusTagManager *tag_manager;
+ 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)
+{
+ 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 (self->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 (self->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)
+{
+ NautilusFavoriteDirectory *self;
+ g_autofree gchar *uri = NULL;
+
+ self = NAUTILUS_STARRED_DIRECTORY (directory);
+
+ uri = nautilus_file_get_uri (file);
+
+ return nautilus_tag_manager_file_is_starred (self->tag_manager, 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 (self->tag_manager);
+
+ 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 (self->tag_manager,
+ on_starred_files_changed,
+ self);
+
+ g_object_unref (self->tag_manager);
+ 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 ()
+{
+ NautilusFavoriteDirectory *self;
+
+ self = g_object_new (NAUTILUS_TYPE_STARRED_DIRECTORY, NULL);
+
+ return self;
+}
+
+static void
+nautilus_starred_directory_init (NautilusFavoriteDirectory *self)
+{
+ NautilusTagManager *tag_manager;
+
+ tag_manager = nautilus_tag_manager_get ();
+
+ g_signal_connect (tag_manager,
+ "starred-changed",
+ (GCallback) on_starred_files_changed,
+ self);
+
+ self->tag_manager = tag_manager;
+
+ 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..24c2de6
--- /dev/null
+++ b/src/nautilus-tag-manager.c
@@ -0,0 +1,1016 @@
+/* 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);
+
+typedef enum
+{
+ GET_STARRED_FILES,
+ GET_IDS_FOR_URLS
+} OperationType;
+
+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 const 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);
+}
+
+static gboolean
+get_query_status (TrackerSparqlCursor *cursor,
+ GAsyncResult *result,
+ OperationType op_type,
+ gpointer user_data)
+{
+ gboolean success;
+ GTask *task;
+ g_autoptr (GError) error = NULL;
+
+ task = user_data;
+
+ success = tracker_sparql_cursor_next_finish (cursor, result, &error);
+
+ if (!success)
+ {
+ if (error)
+ {
+ g_warning ("Error on getting all tags cursor callback: %s", error->message);
+ }
+
+ g_clear_object (&cursor);
+
+ if (error == NULL ||
+ (error != NULL && error->code == G_IO_ERROR_CANCELLED))
+ {
+ if (op_type == GET_IDS_FOR_URLS)
+ {
+ g_task_return_pointer (task, g_task_get_task_data (task), NULL);
+ g_object_unref (task);
+ }
+ }
+ }
+
+ return success;
+}
+
+/**
+ * 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;
+ const gchar *url;
+ gboolean success;
+ NautilusTagManager *self;
+ GList *changed_files;
+ NautilusFile *file;
+
+ cursor = TRACKER_SPARQL_CURSOR (object);
+
+ self = NAUTILUS_TAG_MANAGER (user_data);
+
+ success = get_query_status (cursor, result, GET_STARRED_FILES, NULL);
+ if (!success)
+ {
+ 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;
+ }
+
+ self->cancellable = cancellable;
+
+ 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_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_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);
+}
+
+NautilusTagManager *
+nautilus_tag_manager_get (void)
+{
+ static NautilusTagManager *tag_manager = NULL;
+
+ 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;
+}
+
+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;
+}
+
+/* Initialize the tag mananger. */
+void
+nautilus_tag_manager_set_cancellable (NautilusTagManager *self,
+ GCancellable *cancellable)
+{
+ g_autoptr (GError) error = NULL;
+
+ self->database_ok = setup_database (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, cancellable);
+
+ g_signal_connect (self->notifier,
+ "events",
+ G_CALLBACK (on_tracker_notifier_events),
+ self);
+}
+
+static void
+nautilus_tag_manager_init (NautilusTagManager *self)
+{
+ 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 ());
+}
+
+gboolean
+nautilus_tag_manager_can_star_contents (NautilusTagManager *tag_manager,
+ 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, tag_manager->home) || g_file_equal (directory, tag_manager->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;
+ g_autoptr (NautilusTagManager) tag_manager = 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));
+ }
+
+ tag_manager = nautilus_tag_manager_get ();
+ 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);
+ const 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)
+{
+ if (g_file_test (tracker2_migration_stamp (), 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..1ba8a48
--- /dev/null
+++ b/src/nautilus-tag-manager.h
@@ -0,0 +1,62 @@
+/* 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_get (void);
+
+void nautilus_tag_manager_set_cancellable (NautilusTagManager *tag_manager,
+ GCancellable *cancellable);
+
+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..8b532f2
--- /dev/null
+++ b/src/nautilus-thumbnails.c
@@ -0,0 +1,560 @@
+/*
+ * 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)
+ {
+ thumbnail_factory = gnome_desktop_thumbnail_factory_new (GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE);
+ }
+
+ 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;
+
+ 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);
+
+ 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);
+ g_object_unref (pixbuf);
+ }
+ else
+ {
+ DEBUG ("(Thumbnail Thread) Thumbnail failed: %s\n",
+ info->image_uri);
+
+ gnome_desktop_thumbnail_factory_create_failed_thumbnail (thumbnail_factory,
+ info->image_uri,
+ current_orig_mtime);
+ }
+ /* 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..c7864ea
--- /dev/null
+++ b/src/nautilus-toolbar-menu-sections.h
@@ -0,0 +1,32 @@
+/*
+ * 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 {
+ GtkWidget *zoom_section;
+ GtkWidget *extended_section;
+ gboolean supports_undo_redo;
+};
+
+G_END_DECLS \ No newline at end of file
diff --git a/src/nautilus-toolbar.c b/src/nautilus-toolbar.c
new file mode 100644
index 0000000..eb86054
--- /dev/null
+++ b/src/nautilus-toolbar.c
@@ -0,0 +1,1512 @@
+/*
+ * 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 "animation/ide-box-theatric.h"
+#include "animation/egg-animation.h"
+
+#include "nautilus-application.h"
+#include "nautilus-bookmark.h"
+#include "nautilus-file-operations.h"
+#include "nautilus-file-undo-manager.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-location-entry.h"
+#include "nautilus-pathbar.h"
+#include "nautilus-progress-info-manager.h"
+#include "nautilus-progress-info-widget.h"
+#include "nautilus-toolbar-menu-sections.h"
+#include "nautilus-ui-utilities.h"
+#include "nautilus-window.h"
+#include "nautilus-container-max-width.h"
+
+#define OPERATION_MINIMUM_TIME 2 /*s */
+#define NEEDS_ATTENTION_ANIMATION_TIMEOUT 2000 /*ms */
+#define REMOVE_FINISHED_OPERATIONS_TIEMOUT 3 /*s */
+
+#define ANIMATION_X_GROW 30
+#define ANIMATION_Y_GROW 30
+
+/* Just design, context at https://gitlab.gnome.org/GNOME/nautilus/issues/548#note_274131 */
+#define SWITCHER_MAX_WIDTH 840
+
+typedef enum
+{
+ NAUTILUS_NAVIGATION_DIRECTION_NONE,
+ NAUTILUS_NAVIGATION_DIRECTION_BACK,
+ NAUTILUS_NAVIGATION_DIRECTION_FORWARD
+} NautilusNavigationDirection;
+
+struct _NautilusToolbar
+{
+ GtkHeaderBar parent_instance;
+
+ NautilusWindow *window;
+
+ GtkWidget *path_bar_container;
+ GtkWidget *location_entry_container;
+ GtkWidget *search_container;
+ GtkWidget *toolbar_switcher;
+ GtkWidget *toolbar_switcher_container;
+ NautilusContainerMaxWidth *toolbar_switcher_container_max_width;
+ GtkWidget *path_bar;
+ GtkWidget *location_entry;
+
+ gboolean show_location_entry;
+ gboolean location_entry_should_auto_hide;
+
+ guint start_operations_timeout_id;
+ guint remove_finished_operations_timeout_id;
+ guint operations_button_attention_timeout_id;
+
+ GtkWidget *operations_button;
+ GtkWidget *view_button;
+ GtkWidget *view_menu_zoom_section;
+ GtkWidget *view_menu_undo_redo_section;
+ GtkWidget *view_menu_extended_section;
+ GtkWidget *undo_button;
+ GtkWidget *redo_button;
+ GtkWidget *view_toggle_button;
+ GtkWidget *view_toggle_icon;
+
+ GtkWidget *operations_popover;
+ GtkWidget *operations_container;
+ GtkWidget *operations_revealer;
+ GtkWidget *operations_icon;
+
+ GtkWidget *forward_button;
+ GtkGesture *forward_button_longpress_gesture;
+ GtkGesture *forward_button_multi_press_gesture;
+
+ GtkWidget *back_button;
+ GtkGesture *back_button_longpress_gesture;
+ GtkGesture *back_button_multi_press_gesture;
+
+ GtkWidget *search_button;
+
+ GtkWidget *location_entry_close_button;
+
+ NautilusProgressInfoManager *progress_manager;
+
+ /* active slot & bindings */
+ NautilusWindowSlot *window_slot;
+ GBinding *icon_binding;
+ GBinding *search_binding;
+ GBinding *tooltip_binding;
+};
+
+enum
+{
+ PROP_WINDOW = 1,
+ PROP_SHOW_LOCATION_ENTRY,
+ PROP_WINDOW_SLOT,
+ PROP_SEARCHING,
+ NUM_PROPERTIES
+};
+
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
+
+G_DEFINE_TYPE (NautilusToolbar, nautilus_toolbar, GTK_TYPE_HEADER_BAR);
+
+static void nautilus_toolbar_set_window_slot_real (NautilusToolbar *self,
+ NautilusWindowSlot *slot);
+static void update_operations (NautilusToolbar *self);
+
+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
+activate_back_or_forward_menu_item (GtkMenuItem *menu_item,
+ NautilusWindow *window,
+ gboolean back)
+{
+ int index;
+
+ g_assert (GTK_IS_MENU_ITEM (menu_item));
+
+ index = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (menu_item), "user_data"));
+
+ nautilus_window_back_or_forward (window, back, index, nautilus_event_get_window_open_flags ());
+}
+
+static void
+activate_back_menu_item_callback (GtkMenuItem *menu_item,
+ NautilusToolbar *self)
+{
+ activate_back_or_forward_menu_item (menu_item, self->window, TRUE);
+}
+
+static void
+activate_forward_menu_item_callback (GtkMenuItem *menu_item,
+ NautilusToolbar *self)
+{
+ activate_back_or_forward_menu_item (menu_item, self->window, FALSE);
+}
+
+static void
+fill_menu (NautilusToolbar *self,
+ GtkWidget *menu,
+ gboolean back)
+{
+ GtkWidget *menu_item;
+ int index;
+ GList *list;
+
+ 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)
+ {
+ menu_item = nautilus_bookmark_menu_item_new (NAUTILUS_BOOKMARK (list->data));
+ g_object_set_data (G_OBJECT (menu_item), "user_data", GINT_TO_POINTER (index));
+ gtk_widget_show (GTK_WIDGET (menu_item));
+ g_signal_connect_object (menu_item, "activate",
+ back
+ ? G_CALLBACK (activate_back_menu_item_callback)
+ : G_CALLBACK (activate_forward_menu_item_callback),
+ self, 0);
+
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
+ list = g_list_next (list);
+ ++index;
+ }
+}
+
+static void
+show_menu (NautilusToolbar *self,
+ GtkWidget *widget,
+ const GdkEvent *event)
+{
+ GtkWidget *menu;
+ NautilusNavigationDirection direction;
+
+ menu = gtk_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);
+ }
+ break;
+
+ case NAUTILUS_NAVIGATION_DIRECTION_BACK:
+ {
+ fill_menu (self, menu, TRUE);
+ }
+ break;
+
+ default:
+ {
+ g_assert_not_reached ();
+ }
+ break;
+ }
+
+ gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (self->window), NULL);
+ gtk_menu_popup_at_widget (GTK_MENU (menu), widget,
+ GDK_GRAVITY_SOUTH_WEST,
+ GDK_GRAVITY_NORTH_WEST,
+ event);
+}
+
+static void
+navigation_button_press_cb (GtkGestureMultiPress *gesture,
+ gint n_press,
+ gdouble x,
+ gdouble y,
+ gpointer user_data)
+{
+ NautilusToolbar *self;
+ GtkWidget *widget;
+ GdkEventSequence *sequence;
+ const GdkEvent *event;
+
+ self = NAUTILUS_TOOLBAR (user_data);
+ widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
+ sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
+ event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
+
+ show_menu (self, widget, event);
+}
+
+static void
+back_button_longpress_cb (GtkGestureLongPress *gesture,
+ double x,
+ double y,
+ gpointer user_data)
+{
+ NautilusToolbar *self = user_data;
+ GdkEventSequence *sequence;
+ const GdkEvent *event;
+
+ sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
+ event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
+
+ show_menu (self, self->back_button, event);
+}
+
+static void
+forward_button_longpress_cb (GtkGestureLongPress *gesture,
+ double x,
+ double y,
+ gpointer user_data)
+{
+ NautilusToolbar *self = user_data;
+ GdkEventSequence *sequence;
+ const GdkEvent *event;
+
+ sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
+ event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
+
+ show_menu (self, self->forward_button, event);
+}
+
+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 (NautilusToolbar *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 (NautilusToolbar *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 (NautilusToolbar *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 (NautilusToolbar *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 (NautilusToolbar *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 (NautilusToolbar *self)
+{
+ GtkStyleContext *style_context;
+
+ style_context = gtk_widget_get_style_context (self->operations_button);
+ gtk_style_context_remove_class (style_context,
+ "nautilus-operations-button-needs-attention");
+}
+
+static gboolean
+on_remove_operations_button_attention_style_timeout (NautilusToolbar *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 (NautilusToolbar *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 (NautilusToolbar *self)
+{
+ GtkStyleContext *style_context;
+
+ style_context = gtk_widget_get_style_context (self->operations_button);
+
+ unschedule_operations_button_attention_style (self);
+ remove_operations_button_attention_style (self);
+
+ gtk_style_context_add_class (style_context,
+ "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 (NautilusToolbar *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 (NautilusToolbar *self)
+{
+ /* Update the pie chart progress */
+ gtk_widget_queue_draw (self->operations_icon);
+}
+
+static void
+on_progress_info_finished (NautilusToolbar *self,
+ NautilusProgressInfo *info)
+{
+ gchar *main_label;
+ GFile *folder_to_open;
+
+ /* 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_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->operations_button)) &&
+ folder_to_open != NULL)
+ {
+ add_operations_button_attention_style (self);
+ main_label = nautilus_progress_info_get_status (info);
+ nautilus_window_show_operation_notification (self->window,
+ main_label,
+ folder_to_open);
+ g_free (main_label);
+ }
+
+ g_clear_object (&folder_to_open);
+}
+
+static void
+disconnect_progress_infos (NautilusToolbar *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 (NautilusToolbar *self)
+{
+ GList *progress_infos;
+ GList *l;
+ GtkWidget *progress;
+ gboolean should_show_progress_button = FALSE;
+
+ gtk_container_foreach (GTK_CONTAINER (self->operations_container),
+ (GtkCallback) gtk_widget_destroy,
+ NULL);
+
+ disconnect_progress_infos (self);
+
+ 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_swapped (l->data, "finished",
+ G_CALLBACK (on_progress_info_finished), self);
+ g_signal_connect_swapped (l->data, "cancelled",
+ G_CALLBACK (on_progress_info_cancelled), self);
+ g_signal_connect_swapped (l->data, "progress-changed",
+ G_CALLBACK (on_progress_info_progress_changed), self);
+ progress = nautilus_progress_info_widget_new (l->data);
+ gtk_box_pack_start (GTK_BOX (self->operations_container),
+ progress,
+ FALSE, FALSE, 0);
+ }
+
+ 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);
+
+ /* Show the popover at start to increase visibility.
+ * Check whether the toolbar is visible or not before showing the
+ * popover. This can happens if the window has the disables-chrome
+ * property set. */
+ if (gtk_widget_is_visible (GTK_WIDGET (self)))
+ {
+ GtkAllocation rect;
+ IdeBoxTheatric *theatric;
+
+ gtk_widget_get_allocation (GTK_WIDGET (self->operations_button), &rect);
+ theatric = g_object_new (IDE_TYPE_BOX_THEATRIC,
+ "alpha", 0.9,
+ "background", "#fdfdfd",
+ "target", self->operations_button,
+ "height", rect.height,
+ "width", rect.width,
+ "x", rect.x,
+ "y", rect.y,
+ NULL);
+
+ egg_object_animate_full (theatric,
+ EGG_ANIMATION_EASE_IN_CUBIC,
+ 250,
+ gtk_widget_get_frame_clock (GTK_WIDGET (self->operations_button)),
+ g_object_unref,
+ theatric,
+ "x", rect.x - ANIMATION_X_GROW,
+ "width", rect.width + (ANIMATION_X_GROW * 2),
+ "y", rect.y - ANIMATION_Y_GROW,
+ "height", rect.height + (ANIMATION_Y_GROW * 2),
+ "alpha", 0.0,
+ NULL);
+ }
+ }
+
+ /* 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 (NautilusToolbar *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 (NautilusToolbar *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 (NautilusToolbar *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,
+ NautilusToolbar *self)
+{
+ g_signal_handlers_disconnect_by_data (info, self);
+ schedule_operations_start (self);
+}
+
+static void
+on_new_progress_info (NautilusProgressInfoManager *manager,
+ NautilusProgressInfo *info,
+ NautilusToolbar *self)
+{
+ g_signal_connect (info, "started",
+ G_CALLBACK (on_progress_info_started), self);
+}
+
+static void
+on_operations_icon_draw (GtkWidget *widget,
+ cairo_t *cr,
+ NautilusToolbar *self)
+{
+ gfloat elapsed_progress = 0;
+ gint remaining_progress = 0;
+ gint total_progress;
+ gdouble ratio;
+ GList *progress_infos;
+ GList *l;
+ guint width;
+ guint height;
+ 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, gtk_widget_get_state_flags (widget), &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_button_toggled (NautilusToolbar *self,
+ GtkToggleButton *button)
+{
+ if (gtk_toggle_button_get_active (button))
+ {
+ 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,
+ NautilusToolbar *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 void
+update_menu_item (GtkWidget *menu_item,
+ NautilusToolbar *self,
+ const char *action_name,
+ gboolean enabled,
+ char *label)
+{
+ GAction *action;
+ GValue val = G_VALUE_INIT;
+
+ /* Activate/deactivate */
+ action = g_action_map_lookup_action (G_ACTION_MAP (self->window), action_name);
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), enabled);
+
+ /* Set the text of the menu item. Can't use gtk_button_set_label here as
+ * we need to set the text property, not the label. There's no equivalent
+ * gtk_model_button_set_text function (refer to #766083 for discussion
+ * on adding a set_text function)
+ */
+ g_value_init (&val, G_TYPE_STRING);
+ g_value_set_string (&val, label);
+ g_object_set_property (G_OBJECT (menu_item), "text", &val);
+ g_value_unset (&val);
+}
+
+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;
+
+ /* 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"));
+ }
+ update_menu_item (self->undo_button, self, "undo", undo_active, undo_label);
+
+ if (!redo_active || redo_label == NULL)
+ {
+ g_free (redo_label);
+ redo_label = g_strdup (_("_Redo"));
+ }
+ update_menu_item (self->redo_button, self, "redo", redo_active, redo_label);
+}
+
+static void
+on_location_entry_close (GtkWidget *close_button,
+ NautilusToolbar *self)
+{
+ nautilus_toolbar_set_show_location_entry (self, FALSE);
+}
+
+static gboolean
+on_location_entry_populate_popup (GtkEntry *entry,
+ GtkWidget *widget,
+ gpointer user_data)
+{
+ NautilusToolbar *toolbar;
+
+ toolbar = user_data;
+
+ toolbar->location_entry_should_auto_hide = FALSE;
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static void
+on_location_entry_focus_changed (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ NautilusToolbar *toolbar;
+
+ toolbar = NAUTILUS_TOOLBAR (user_data);
+
+ if (gtk_widget_has_focus (GTK_WIDGET (object)))
+ {
+ toolbar->location_entry_should_auto_hide = TRUE;
+ }
+ else if (toolbar->location_entry_should_auto_hide)
+ {
+ nautilus_toolbar_set_show_location_entry (toolbar, FALSE);
+ }
+}
+
+static void
+nautilus_toolbar_constructed (GObject *object)
+{
+ GtkBuilder *builder;
+ NautilusToolbar *self = NAUTILUS_TOOLBAR (object);
+
+ builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-toolbar-switcher.ui");
+ self->toolbar_switcher = GTK_WIDGET (gtk_builder_get_object (builder, "toolbar_switcher"));
+ self->search_container = GTK_WIDGET (gtk_builder_get_object (builder, "search_container"));
+ self->path_bar_container = GTK_WIDGET (gtk_builder_get_object (builder, "path_bar_container"));
+ self->location_entry_container = GTK_WIDGET (gtk_builder_get_object (builder, "location_entry_container"));
+
+ self->toolbar_switcher_container_max_width = nautilus_container_max_width_new ();
+ nautilus_container_max_width_set_max_width (self->toolbar_switcher_container_max_width,
+ SWITCHER_MAX_WIDTH);
+ gtk_container_add (GTK_CONTAINER (self->toolbar_switcher_container_max_width),
+ self->toolbar_switcher);
+ gtk_container_add (GTK_CONTAINER (self->toolbar_switcher_container),
+ GTK_WIDGET (self->toolbar_switcher_container_max_width));
+
+ self->path_bar = g_object_new (NAUTILUS_TYPE_PATH_BAR, NULL);
+ gtk_container_add (GTK_CONTAINER (self->path_bar_container),
+ self->path_bar);
+
+ self->location_entry = nautilus_location_entry_new ();
+ gtk_container_add (GTK_CONTAINER (self->location_entry_container),
+ self->location_entry);
+ self->location_entry_close_button = gtk_button_new_from_icon_name ("window-close-symbolic",
+ GTK_ICON_SIZE_BUTTON);
+ gtk_container_add (GTK_CONTAINER (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);
+
+ 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);
+
+ update_operations (self);
+
+ self->back_button_longpress_gesture = gtk_gesture_long_press_new (self->back_button);
+ g_signal_connect (self->back_button_longpress_gesture, "pressed",
+ G_CALLBACK (back_button_longpress_cb), self);
+
+ self->forward_button_longpress_gesture = gtk_gesture_long_press_new (self->forward_button);
+ g_signal_connect (self->forward_button_longpress_gesture, "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));
+
+
+ self->back_button_multi_press_gesture = gtk_gesture_multi_press_new (self->back_button);
+ gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (self->back_button_multi_press_gesture),
+ GDK_BUTTON_SECONDARY);
+ g_signal_connect (self->back_button_multi_press_gesture, "pressed",
+ G_CALLBACK (navigation_button_press_cb), self);
+
+ self->forward_button_multi_press_gesture = gtk_gesture_multi_press_new (self->forward_button);
+ gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (self->forward_button_multi_press_gesture),
+ GDK_BUTTON_SECONDARY);
+ g_signal_connect (self->forward_button_multi_press_gesture, "pressed",
+ G_CALLBACK (navigation_button_press_cb), 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);
+ g_signal_connect (self->location_entry, "populate-popup",
+ G_CALLBACK (on_location_entry_populate_popup), self);
+ g_signal_connect (self->location_entry, "notify::has-focus",
+ G_CALLBACK (on_location_entry_focus_changed), self);
+
+ gtk_widget_show_all (GTK_WIDGET (self));
+ toolbar_update_appearance (self);
+}
+
+static void
+nautilus_toolbar_init (NautilusToolbar *self)
+{
+ 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_SEARCHING:
+ {
+ g_value_set_boolean (value, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->search_button)));
+ }
+ 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->icon_binding = NULL;
+ self->search_binding = NULL;
+
+ nautilus_toolbar_set_window_slot_real (self, NULL);
+}
+
+static void
+on_window_focus_changed (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ GtkWidget *widget;
+ NautilusToolbar *toolbar;
+
+ widget = GTK_WIDGET (object);
+ toolbar = NAUTILUS_TOOLBAR (user_data);
+
+ if (g_settings_get_boolean (nautilus_preferences,
+ NAUTILUS_PREFERENCES_ALWAYS_USE_LOCATION_ENTRY))
+ {
+ return;
+ }
+
+ /* The working assumption being made here is, if the location entry is visible,
+ * the user must have switched windows while having keyboard focus on the entry
+ * (because otherwise it would be invisible),
+ * so we focus the entry explicitly to reset the “should auto-hide” flag.
+ */
+ if (gtk_widget_has_focus (widget) && toolbar->show_location_entry)
+ {
+ gtk_widget_grab_focus (toolbar->location_entry);
+ }
+ /* The location entry in general is hidden when it loses focus,
+ * but hiding it when switching windows could be undesirable, as the user
+ * might want to copy a path from somewhere. This here prevents that from happening.
+ */
+ else
+ {
+ toolbar->location_entry_should_auto_hide = FALSE;
+ }
+}
+
+static void
+nautilus_toolbar_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusToolbar *self = NAUTILUS_TOOLBAR (object);
+
+ switch (property_id)
+ {
+ case PROP_WINDOW:
+ {
+ if (self->window != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (self->window,
+ on_window_focus_changed, self);
+ }
+ self->window = g_value_get_object (value);
+ if (self->window != NULL)
+ {
+ g_signal_connect (self->window, "notify::has-focus",
+ G_CALLBACK (on_window_focus_changed), self);
+ }
+ }
+ break;
+
+ 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;
+
+ 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_object (&self->forward_button_multi_press_gesture);
+ g_clear_object (&self->back_button_multi_press_gesture);
+ g_clear_pointer (&self->icon_binding, g_binding_unbind);
+ 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;
+ }
+ disconnect_progress_infos (self);
+ unschedule_remove_finished_operations (self);
+ unschedule_operations_start (self);
+ unschedule_operations_button_attention_style (self);
+
+ g_signal_handlers_disconnect_by_data (self->progress_manager, self);
+ g_clear_object (&self->progress_manager);
+
+ g_signal_handlers_disconnect_by_func (self->window,
+ on_window_focus_changed, self);
+
+ g_clear_object (&self->back_button_longpress_gesture);
+ g_clear_object (&self->forward_button_longpress_gesture);
+
+ 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_WINDOW] =
+ g_param_spec_object ("window",
+ "The NautilusWindow",
+ "The NautilusWindow this toolbar is part of",
+ NAUTILUS_TYPE_WINDOW,
+ G_PARAM_WRITABLE |
+ G_PARAM_STATIC_STRINGS);
+ 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);
+
+ 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, operations_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, operations_icon);
+ gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, operations_popover);
+ gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, operations_container);
+ gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, operations_revealer);
+ gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, view_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, view_toggle_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, view_toggle_icon);
+ gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, back_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, forward_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, toolbar_switcher_container);
+
+ gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, view_menu_zoom_section);
+ gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, view_menu_undo_redo_section);
+ gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, view_menu_extended_section);
+ gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, undo_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, redo_button);
+
+ gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, search_button);
+
+ gtk_widget_class_bind_template_callback (widget_class, on_operations_icon_draw);
+ gtk_widget_class_bind_template_callback (widget_class, on_operations_button_toggled);
+}
+
+GtkWidget *
+nautilus_toolbar_new ()
+{
+ 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
+container_remove_all_children (GtkContainer *container)
+{
+ GList *children;
+ GList *child;
+
+ children = gtk_container_get_children (container);
+ for (child = children; child != NULL; child = g_list_next (child))
+ {
+ gtk_container_remove (container, GTK_WIDGET (child->data));
+ }
+ g_list_free (children);
+}
+
+static void
+slot_on_extensions_background_menu_changed (NautilusToolbar *self,
+ GParamSpec *param,
+ NautilusWindowSlot *slot)
+{
+ nautilus_path_bar_set_extensions_background_menu (NAUTILUS_PATH_BAR (self->path_bar),
+ nautilus_window_slot_get_extensions_background_menu (slot));
+}
+
+static void
+slot_on_templates_menu_changed (NautilusToolbar *self,
+ GParamSpec *param,
+ NautilusWindowSlot *slot)
+{
+ nautilus_path_bar_set_templates_menu (NAUTILUS_PATH_BAR (self->path_bar),
+ nautilus_window_slot_get_templates_menu (slot));
+}
+
+static void
+on_slot_toolbar_menu_sections_changed (NautilusToolbar *self,
+ GParamSpec *param,
+ NautilusWindowSlot *slot)
+{
+ NautilusToolbarMenuSections *new_sections;
+
+ container_remove_all_children (GTK_CONTAINER (self->view_menu_zoom_section));
+ container_remove_all_children (GTK_CONTAINER (self->view_menu_extended_section));
+
+ new_sections = nautilus_window_slot_get_toolbar_menu_sections (slot);
+ if (new_sections == NULL)
+ {
+ return;
+ }
+
+ gtk_widget_set_visible (self->view_menu_undo_redo_section,
+ new_sections->supports_undo_redo);
+
+ if (new_sections->zoom_section != NULL)
+ {
+ gtk_box_pack_start (GTK_BOX (self->view_menu_zoom_section),
+ new_sections->zoom_section, FALSE, FALSE, 0);
+ }
+
+ if (new_sections->extended_section != NULL)
+ {
+ gtk_box_pack_start (GTK_BOX (self->view_menu_extended_section),
+ new_sections->extended_section, FALSE, FALSE, 0);
+ }
+
+ gtk_widget_set_sensitive (self->view_button, (new_sections->extended_section != NULL ||
+ new_sections->zoom_section != NULL ||
+ new_sections->supports_undo_redo));
+}
+
+
+static void
+disconnect_toolbar_menu_sections_change_handler (NautilusToolbar *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 gboolean
+nautilus_toolbar_view_toggle_icon_transform_to (GBinding *binding,
+ const GValue *from_value,
+ GValue *to_value,
+ gpointer user_data)
+{
+ GIcon *icon;
+
+ icon = g_value_get_object (from_value);
+
+ /* As per design decision, we let the previous used icon if no
+ * view menu is available */
+ if (icon)
+ {
+ g_value_set_object (to_value, icon);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+nautilus_toolbar_view_toggle_tooltip_transform_to (GBinding *binding,
+ const GValue *from_value,
+ GValue *to_value,
+ gpointer user_data)
+{
+ const gchar *tooltip;
+
+ tooltip = g_value_get_string (from_value);
+
+ /* As per design decision, we let the previous used tooltip if no
+ * view menu is available */
+ if (tooltip)
+ {
+ g_value_set_string (to_value, tooltip);
+ }
+
+ return TRUE;
+}
+
+/* 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->icon_binding = g_object_bind_property_full (self->window_slot, "icon",
+ self->view_toggle_icon, "gicon",
+ G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE,
+ (GBindingTransformFunc) nautilus_toolbar_view_toggle_icon_transform_to,
+ NULL,
+ self,
+ NULL);
+
+ self->tooltip_binding = g_object_bind_property_full (self->window_slot, "tooltip",
+ self->view_toggle_button, "tooltip-text",
+ G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE,
+ (GBindingTransformFunc) nautilus_toolbar_view_toggle_tooltip_transform_to,
+ NULL,
+ self,
+ NULL);
+
+ self->search_binding = g_object_bind_property (self->window_slot, "searching",
+ self, "searching",
+ G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
+
+ 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_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);
+ }
+
+ children = gtk_container_get_children (GTK_CONTAINER (self->search_container));
+ if (children != NULL)
+ {
+ gtk_container_remove (GTK_CONTAINER (self->search_container),
+ children->data);
+ }
+
+ if (self->window_slot != NULL)
+ {
+ gtk_container_add (GTK_CONTAINER (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->icon_binding, g_binding_unbind);
+ g_clear_pointer (&self->search_binding, g_binding_unbind);
+
+ disconnect_toolbar_menu_sections_change_handler (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);
+ }
+
+ nautilus_toolbar_set_window_slot_real (self, window_slot);
+}
+
+gboolean
+nautilus_toolbar_is_menu_visible (NautilusToolbar *self)
+{
+ GtkPopover *popover;
+
+ g_return_val_if_fail (NAUTILUS_IS_TOOLBAR (self), FALSE);
+
+ popover = GTK_POPOVER (gtk_menu_button_get_popover (GTK_MENU_BUTTON (self->view_button)));
+
+ return gtk_widget_is_visible (GTK_WIDGET (popover));
+}
+
+gboolean
+nautilus_toolbar_is_operations_button_active (NautilusToolbar *self)
+{
+ return gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->operations_button));
+}
diff --git a/src/nautilus-toolbar.h b/src/nautilus-toolbar.h
new file mode 100644
index 0000000..13f0072
--- /dev/null
+++ b/src/nautilus-toolbar.h
@@ -0,0 +1,55 @@
+
+/*
+ * 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 "nautilus-window-slot.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_TOOLBAR nautilus_toolbar_get_type()
+
+G_DECLARE_FINAL_TYPE (NautilusToolbar, nautilus_toolbar, NAUTILUS, TOOLBAR, GtkHeaderBar)
+
+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);
+
+gboolean nautilus_toolbar_is_operations_button_active (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..6230a50
--- /dev/null
+++ b/src/nautilus-tracker-utilities.c
@@ -0,0 +1,145 @@
+/* nautilus-tracker-utilities.c
+ *
+ * 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
+ */
+
+#include "config.h"
+#include "nautilus-tracker-utilities.h"
+#include "nautilus-global-preferences.h"
+
+#define TRACKER_KEY_RECURSIVE_DIRECTORIES "index-recursive-directories"
+#define TRACKER_KEY_SINGLE_DIRECTORIES "index-single-directories"
+
+/* 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 gboolean
+get_host_tracker_miner_fs (GError **error)
+{
+ const gchar *busname = "org.freedesktop.Tracker3.Miner.Files";
+
+ g_message ("Connecting to %s", busname);
+ tracker_miner_fs_connection = tracker_sparql_connection_bus_new (busname, NULL, NULL, error);
+ if (*error)
+ {
+ g_warning ("Unable to create connection for session-wide Tracker indexer: %s", (*error)->message);
+ return FALSE;
+ }
+
+ tracker_miner_fs_busname = busname;
+ return TRUE;
+}
+
+static gboolean
+start_local_tracker_miner_fs (GError **error)
+{
+ const gchar *busname = APPLICATION_ID ".Tracker3.Miner.Files";
+
+ g_message ("Starting %s", busname);
+ tracker_miner_fs_connection = tracker_sparql_connection_bus_new (busname, NULL, NULL, error);
+ if (*error)
+ {
+ g_critical ("Could not start local Tracker indexer at %s: %s", busname, (*error)->message);
+ return FALSE;
+ }
+
+ tracker_miner_fs_busname = busname;
+ return TRUE;
+}
+
+static gboolean
+inside_flatpak (void)
+{
+ return g_file_test ("/.flatpak-info", G_FILE_TEST_EXISTS);
+}
+
+static void
+setup_tracker_miner_fs_connection (void)
+{
+ static gsize tried_tracker_init = FALSE;
+
+ if (g_once_init_enter (&tried_tracker_init))
+ {
+ gboolean success;
+
+ success = get_host_tracker_miner_fs (&tracker_miner_fs_error);
+
+ if (!success && inside_flatpak ())
+ {
+ g_clear_error (&tracker_miner_fs_error);
+ success = start_local_tracker_miner_fs (&tracker_miner_fs_error);
+ }
+
+ g_once_init_leave (&tried_tracker_init, TRUE);
+ }
+}
+
+/**
+ * nautilus_tracker_get_miner_fs_connection:
+ * @error: return location for a #GError
+ *
+ * This function returns a global singleton #TrackerSparqlConnection, or %NULL
+ * if we couldn't connect to Tracker Miner FS.
+ *
+ * The first time you call it, this function will block while trying to connect.
+ * This may take some time if starting Tracker Miners from a Flatpak bundle.
+ *
+ * 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)
+{
+ setup_tracker_miner_fs_connection ();
+
+ if (tracker_miner_fs_error && error)
+ {
+ *error = g_error_copy (tracker_miner_fs_error);
+ }
+
+ return tracker_miner_fs_connection;
+}
+
+/**
+ * nautilus_tracker_get_miner_fs_busname:
+ * @error: return location for a #GError
+ *
+ * This function returns a DBus name that can be used to talk to
+ * tracker-miner-fs, or %NULL if there is no Tracker Miner FS available.
+ *
+ * The first time you call it, this function will block while trying to connect.
+ * This may take some time if starting Tracker Miners from a Flatpak bundle.
+ *
+ * Returns: a string
+ */
+const gchar *
+nautilus_tracker_get_miner_fs_busname (GError **error)
+{
+ setup_tracker_miner_fs_connection ();
+
+ if (tracker_miner_fs_error && error)
+ {
+ *error = g_error_copy (tracker_miner_fs_error);
+ }
+
+ return tracker_miner_fs_busname;
+}
diff --git a/src/nautilus-tracker-utilities.h b/src/nautilus-tracker-utilities.h
new file mode 100644
index 0000000..f541655
--- /dev/null
+++ b/src/nautilus-tracker-utilities.h
@@ -0,0 +1,28 @@
+/* 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);
+const gchar * nautilus_tracker_get_miner_fs_busname (GError **error);
diff --git a/src/nautilus-trash-bar.c b/src/nautilus-trash-bar.c
new file mode 100644
index 0000000..fefb05c
--- /dev/null
+++ b/src/nautilus-trash-bar.c
@@ -0,0 +1,240 @@
+/*
+ * 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: Paolo Borelli <pborelli@katamail.com>
+ *
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "nautilus-trash-bar.h"
+
+#include "nautilus-files-view.h"
+#include "nautilus-file-operations.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-file.h"
+#include "nautilus-trash-monitor.h"
+
+enum
+{
+ PROP_VIEW = 1,
+ NUM_PROPERTIES
+};
+
+enum
+{
+ TRASH_BAR_RESPONSE_EMPTY = 1,
+ TRASH_BAR_RESPONSE_RESTORE
+};
+
+struct _NautilusTrashBar
+{
+ GtkInfoBar parent_instance;
+
+ NautilusFilesView *view;
+ gulong selection_handler_id;
+};
+
+G_DEFINE_TYPE (NautilusTrashBar, nautilus_trash_bar, GTK_TYPE_INFO_BAR)
+
+static void
+selection_changed_cb (NautilusFilesView *view,
+ NautilusTrashBar *bar)
+{
+ g_autolist (NautilusFile) selection = NULL;
+ int count;
+
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (view));
+ count = g_list_length (selection);
+
+ gtk_info_bar_set_response_sensitive (GTK_INFO_BAR (bar),
+ TRASH_BAR_RESPONSE_RESTORE,
+ (count > 0));
+}
+
+static void
+connect_view_and_update_button (NautilusTrashBar *bar)
+{
+ bar->selection_handler_id = g_signal_connect (bar->view,
+ "selection-changed",
+ G_CALLBACK (selection_changed_cb),
+ bar);
+
+ selection_changed_cb (bar->view, bar);
+}
+
+static void
+nautilus_trash_bar_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusTrashBar *bar = NAUTILUS_TRASH_BAR (object);
+
+ switch (prop_id)
+ {
+ case PROP_VIEW:
+ {
+ bar->view = g_value_get_object (value);
+ connect_view_and_update_button (NAUTILUS_TRASH_BAR (object));
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ break;
+ }
+}
+
+static void
+nautilus_trash_bar_dispose (GObject *obj)
+{
+ NautilusTrashBar *bar = NAUTILUS_TRASH_BAR (obj);
+
+ g_clear_signal_handler (&bar->selection_handler_id, bar->view);
+
+ G_OBJECT_CLASS (nautilus_trash_bar_parent_class)->dispose (obj);
+}
+
+static void
+nautilus_trash_bar_trash_state_changed (NautilusTrashMonitor *trash_monitor,
+ gboolean state,
+ gpointer data)
+{
+ NautilusTrashBar *bar;
+
+ bar = NAUTILUS_TRASH_BAR (data);
+
+ gtk_info_bar_set_response_sensitive (GTK_INFO_BAR (bar),
+ TRASH_BAR_RESPONSE_EMPTY,
+ !nautilus_trash_monitor_is_empty ());
+}
+
+static void
+nautilus_trash_bar_class_init (NautilusTrashBarClass *klass)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = nautilus_trash_bar_set_property;
+ object_class->dispose = nautilus_trash_bar_dispose;
+
+ g_object_class_install_property (object_class,
+ PROP_VIEW,
+ g_param_spec_object ("view",
+ "view",
+ "the NautilusFilesView",
+ NAUTILUS_TYPE_FILES_VIEW,
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+trash_bar_response_cb (GtkInfoBar *infobar,
+ gint response_id,
+ gpointer user_data)
+{
+ NautilusTrashBar *bar;
+ GtkWidget *window;
+
+ bar = NAUTILUS_TRASH_BAR (infobar);
+ window = gtk_widget_get_toplevel (GTK_WIDGET (bar));
+
+ switch (response_id)
+ {
+ case TRASH_BAR_RESPONSE_EMPTY:
+ {
+ nautilus_file_operations_empty_trash (window, TRUE, NULL);
+ }
+ break;
+
+ case TRASH_BAR_RESPONSE_RESTORE:
+ {
+ g_autolist (NautilusFile) selection = NULL;
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (bar->view));
+ nautilus_restore_files_from_trash (selection, GTK_WINDOW (window));
+ }
+ break;
+
+ default:
+ {
+ }
+ break;
+ }
+}
+
+static void
+nautilus_trash_bar_init (NautilusTrashBar *bar)
+{
+ GtkWidget *content_area, *action_area, *w;
+ GtkWidget *label;
+ PangoAttrList *attrs;
+
+ content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (bar));
+ action_area = gtk_info_bar_get_action_area (GTK_INFO_BAR (bar));
+
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (action_area),
+ GTK_ORIENTATION_HORIZONTAL);
+
+ attrs = pango_attr_list_new ();
+ pango_attr_list_insert (attrs, pango_attr_weight_new (PANGO_WEIGHT_BOLD));
+ label = gtk_label_new (_("Trash"));
+ gtk_label_set_attributes (GTK_LABEL (label), attrs);
+ pango_attr_list_unref (attrs);
+
+ gtk_widget_show (label);
+ gtk_container_add (GTK_CONTAINER (content_area), label);
+
+ w = gtk_info_bar_add_button (GTK_INFO_BAR (bar),
+ _("_Restore"),
+ TRASH_BAR_RESPONSE_RESTORE);
+ gtk_widget_set_tooltip_text (w,
+ _("Restore selected items to their original position"));
+
+ w = gtk_info_bar_add_button (GTK_INFO_BAR (bar),
+ /* Translators: "Empty" is an action (for the trash) , not a state */
+ _("_Empty"),
+ TRASH_BAR_RESPONSE_EMPTY);
+ gtk_widget_set_tooltip_text (w,
+ _("Delete all items in the Trash"));
+
+ g_signal_connect_object (nautilus_trash_monitor_get (),
+ "trash-state-changed",
+ G_CALLBACK (nautilus_trash_bar_trash_state_changed),
+ bar,
+ 0);
+ nautilus_trash_bar_trash_state_changed (nautilus_trash_monitor_get (),
+ FALSE, bar);
+
+ g_signal_connect (bar, "response",
+ G_CALLBACK (trash_bar_response_cb), bar);
+}
+
+GtkWidget *
+nautilus_trash_bar_new (NautilusFilesView *view)
+{
+ return g_object_new (NAUTILUS_TYPE_TRASH_BAR,
+ "view", view,
+ "message-type", GTK_MESSAGE_QUESTION,
+ NULL);
+}
diff --git a/src/nautilus-trash-bar.h b/src/nautilus-trash-bar.h
new file mode 100644
index 0000000..f838cbd
--- /dev/null
+++ b/src/nautilus-trash-bar.h
@@ -0,0 +1,35 @@
+/*
+ * 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: Paolo Borelli <pborelli@katamail.com>
+ *
+ */
+
+#pragma once
+
+#include "nautilus-files-view.h"
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_TRASH_BAR (nautilus_trash_bar_get_type ())
+
+G_DECLARE_FINAL_TYPE (NautilusTrashBar, nautilus_trash_bar, NAUTILUS, TRASH_BAR, GtkInfoBar)
+
+GtkWidget *nautilus_trash_bar_new (NautilusFilesView *view);
+
+G_END_DECLS \ No newline at end of file
diff --git a/src/nautilus-trash-monitor.c b/src/nautilus-trash-monitor.c
new file mode 100644
index 0000000..2e2beaa
--- /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
+unref_trash_monitor (void)
+{
+ g_object_unref (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 (unref_trash_monitor);
+ }
+
+ 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-tree-view-drag-dest.c b/src/nautilus-tree-view-drag-dest.c
new file mode 100644
index 0000000..379693f
--- /dev/null
+++ b/src/nautilus-tree-view-drag-dest.c
@@ -0,0 +1,1370 @@
+/*
+ * Nautilus
+ *
+ * Copyright (C) 2002 Sun Microsystems, 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: Dave Camp <dave@ximian.com>
+ * XDS support: Benedikt Meurer <benny@xfce.org> (adapted by Amos Brocco <amos.brocco@unifr.ch>)
+ */
+
+/* nautilus-tree-view-drag-dest.c: Handles drag and drop for treeviews which
+ * contain a hierarchy of files
+ */
+
+#include <config.h>
+
+#include "nautilus-tree-view-drag-dest.h"
+
+#include "nautilus-dnd.h"
+#include "nautilus-file-changes-queue.h"
+#include "nautilus-global-preferences.h"
+
+#include <gtk/gtk.h>
+
+#include <stdio.h>
+#include <string.h>
+
+#define DEBUG_FLAG NAUTILUS_DEBUG_LIST_VIEW
+#include "nautilus-debug.h"
+
+#define AUTO_SCROLL_MARGIN 20
+#define HOVER_EXPAND_TIMEOUT 1
+
+struct _NautilusTreeViewDragDestDetails
+{
+ GtkTreeView *tree_view;
+
+ gboolean drop_occurred;
+
+ gboolean have_drag_data;
+ guint drag_type;
+ GtkSelectionData *drag_data;
+ GList *drag_list;
+
+ guint hover_id;
+ gulong highlight_id;
+ guint scroll_id;
+ guint expand_id;
+
+ char *direct_save_uri;
+ char *target_uri;
+};
+
+enum
+{
+ GET_ROOT_URI,
+ GET_FILE_FOR_PATH,
+ MOVE_COPY_ITEMS,
+ HANDLE_NETSCAPE_URL,
+ HANDLE_URI_LIST,
+ HANDLE_TEXT,
+ HANDLE_RAW,
+ HANDLE_HOVER,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (NautilusTreeViewDragDest, nautilus_tree_view_drag_dest,
+ G_TYPE_OBJECT);
+
+static const GtkTargetEntry drag_types [] =
+{
+ { NAUTILUS_ICON_DND_GNOME_ICON_LIST_TYPE, 0, NAUTILUS_ICON_DND_GNOME_ICON_LIST },
+ /* prefer "_NETSCAPE_URL" over "text/uri-list" to satisfy web browsers. */
+ { NAUTILUS_ICON_DND_NETSCAPE_URL_TYPE, 0, NAUTILUS_ICON_DND_NETSCAPE_URL },
+ { NAUTILUS_ICON_DND_URI_LIST_TYPE, 0, NAUTILUS_ICON_DND_URI_LIST },
+ { NAUTILUS_ICON_DND_XDNDDIRECTSAVE_TYPE, 0, NAUTILUS_ICON_DND_XDNDDIRECTSAVE }, /* XDS Protocol Type */
+ { NAUTILUS_ICON_DND_RAW_TYPE, 0, NAUTILUS_ICON_DND_RAW }
+};
+
+
+static void
+gtk_tree_view_vertical_autoscroll (GtkTreeView *tree_view)
+{
+ GdkRectangle visible_rect;
+ GtkAdjustment *vadjustment;
+ GdkDisplay *display;
+ GdkSeat *seat;
+ GdkDevice *pointer;
+ GdkWindow *window;
+ int y;
+ int offset;
+ float value;
+
+ window = gtk_tree_view_get_bin_window (tree_view);
+ vadjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (tree_view));
+
+ display = gtk_widget_get_display (GTK_WIDGET (tree_view));
+ seat = gdk_display_get_default_seat (display);
+ pointer = gdk_seat_get_pointer (seat);
+ gdk_window_get_device_position (window, pointer,
+ NULL, &y, NULL);
+
+ y += gtk_adjustment_get_value (vadjustment);
+
+ gtk_tree_view_get_visible_rect (tree_view, &visible_rect);
+
+ offset = y - (visible_rect.y + 2 * AUTO_SCROLL_MARGIN);
+ if (offset > 0)
+ {
+ offset = y - (visible_rect.y + visible_rect.height - 2 * AUTO_SCROLL_MARGIN);
+ if (offset < 0)
+ {
+ return;
+ }
+ }
+
+ value = CLAMP (gtk_adjustment_get_value (vadjustment) + offset, 0.0,
+ gtk_adjustment_get_upper (vadjustment) - gtk_adjustment_get_page_size (vadjustment));
+ gtk_adjustment_set_value (vadjustment, value);
+}
+
+static int
+scroll_timeout (gpointer data)
+{
+ GtkTreeView *tree_view = GTK_TREE_VIEW (data);
+
+ gtk_tree_view_vertical_autoscroll (tree_view);
+
+ return TRUE;
+}
+
+static void
+remove_scroll_timeout (NautilusTreeViewDragDest *dest)
+{
+ if (dest->details->scroll_id)
+ {
+ g_source_remove (dest->details->scroll_id);
+ dest->details->scroll_id = 0;
+ }
+}
+
+static int
+expand_timeout (gpointer data)
+{
+ GtkTreeView *tree_view;
+ GtkTreePath *drop_path;
+
+ tree_view = GTK_TREE_VIEW (data);
+
+ gtk_tree_view_get_drag_dest_row (tree_view, &drop_path, NULL);
+
+ if (drop_path)
+ {
+ gtk_tree_view_expand_row (tree_view, drop_path, FALSE);
+ gtk_tree_path_free (drop_path);
+ }
+
+ return FALSE;
+}
+
+static void
+remove_expand_timer (NautilusTreeViewDragDest *dest)
+{
+ if (dest->details->expand_id)
+ {
+ g_source_remove (dest->details->expand_id);
+ dest->details->expand_id = 0;
+ }
+}
+
+static gboolean
+highlight_draw (GtkWidget *widget,
+ cairo_t *cr,
+ gpointer data)
+{
+ GdkWindow *bin_window;
+ int width;
+ int height;
+ GtkStyleContext *style;
+
+ /* FIXMEchpe: is bin window right here??? */
+ bin_window = gtk_tree_view_get_bin_window (GTK_TREE_VIEW (widget));
+
+ width = gdk_window_get_width (bin_window);
+ height = gdk_window_get_height (bin_window);
+
+ style = gtk_widget_get_style_context (widget);
+
+ gtk_style_context_save (style);
+ gtk_style_context_add_class (style, "treeview-drop-indicator");
+
+ gtk_render_focus (style,
+ cr,
+ 0, 0, width, height);
+
+ gtk_style_context_restore (style);
+
+ return FALSE;
+}
+
+static void
+set_widget_highlight (NautilusTreeViewDragDest *dest,
+ gboolean highlight)
+{
+ if (!highlight)
+ {
+ g_clear_signal_handler (&dest->details->highlight_id, dest->details->tree_view);
+ gtk_widget_queue_draw (GTK_WIDGET (dest->details->tree_view));
+ }
+
+ if (highlight && !dest->details->highlight_id)
+ {
+ dest->details->highlight_id =
+ g_signal_connect_object (dest->details->tree_view,
+ "draw",
+ G_CALLBACK (highlight_draw),
+ dest, 0);
+ gtk_widget_queue_draw (GTK_WIDGET (dest->details->tree_view));
+ }
+}
+
+static void
+set_drag_dest_row (NautilusTreeViewDragDest *dest,
+ GtkTreePath *path)
+{
+ if (path)
+ {
+ set_widget_highlight (dest, FALSE);
+ gtk_tree_view_set_drag_dest_row
+ (dest->details->tree_view,
+ path,
+ GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
+ }
+ else
+ {
+ set_widget_highlight (dest, TRUE);
+ gtk_tree_view_set_drag_dest_row (dest->details->tree_view,
+ NULL,
+ 0);
+ }
+}
+
+static void
+clear_drag_dest_row (NautilusTreeViewDragDest *dest)
+{
+ gtk_tree_view_set_drag_dest_row (dest->details->tree_view, NULL, 0);
+ set_widget_highlight (dest, FALSE);
+}
+
+static gboolean
+get_drag_data (NautilusTreeViewDragDest *dest,
+ GdkDragContext *context,
+ guint32 time)
+{
+ GdkAtom target;
+
+ target = gtk_drag_dest_find_target (GTK_WIDGET (dest->details->tree_view),
+ context,
+ NULL);
+
+ if (target == GDK_NONE)
+ {
+ return FALSE;
+ }
+
+ if (target == gdk_atom_intern (NAUTILUS_ICON_DND_XDNDDIRECTSAVE_TYPE, FALSE) &&
+ !dest->details->drop_occurred)
+ {
+ dest->details->drag_type = NAUTILUS_ICON_DND_XDNDDIRECTSAVE;
+ dest->details->have_drag_data = TRUE;
+ return TRUE;
+ }
+
+ gtk_drag_get_data (GTK_WIDGET (dest->details->tree_view),
+ context, target, time);
+
+ return TRUE;
+}
+
+static void
+remove_hover_timer (NautilusTreeViewDragDest *dest)
+{
+ if (dest->details->hover_id != 0)
+ {
+ g_source_remove (dest->details->hover_id);
+ dest->details->hover_id = 0;
+ }
+}
+
+static void
+free_drag_data (NautilusTreeViewDragDest *dest)
+{
+ dest->details->have_drag_data = FALSE;
+
+ if (dest->details->drag_data)
+ {
+ gtk_selection_data_free (dest->details->drag_data);
+ dest->details->drag_data = NULL;
+ }
+
+ if (dest->details->drag_list)
+ {
+ nautilus_drag_destroy_selection_list (dest->details->drag_list);
+ dest->details->drag_list = NULL;
+ }
+
+ g_free (dest->details->direct_save_uri);
+ dest->details->direct_save_uri = NULL;
+
+ g_free (dest->details->target_uri);
+ dest->details->target_uri = NULL;
+
+ remove_hover_timer (dest);
+ remove_expand_timer (dest);
+}
+
+static gboolean
+hover_timer (gpointer user_data)
+{
+ NautilusTreeViewDragDest *dest = user_data;
+
+ dest->details->hover_id = 0;
+
+ g_signal_emit (dest, signals[HANDLE_HOVER], 0, dest->details->target_uri);
+
+ return FALSE;
+}
+
+static void
+check_hover_timer (NautilusTreeViewDragDest *dest,
+ const char *uri)
+{
+ GtkSettings *settings;
+ guint timeout;
+
+ if (g_strcmp0 (uri, dest->details->target_uri) == 0)
+ {
+ return;
+ }
+ remove_hover_timer (dest);
+
+ settings = gtk_widget_get_settings (GTK_WIDGET (dest->details->tree_view));
+ g_object_get (settings, "gtk-timeout-expand", &timeout, NULL);
+
+ g_free (dest->details->target_uri);
+ dest->details->target_uri = NULL;
+
+ if (uri != NULL)
+ {
+ dest->details->target_uri = g_strdup (uri);
+ dest->details->hover_id =
+ gdk_threads_add_timeout (timeout,
+ hover_timer,
+ dest);
+ }
+}
+
+static void
+check_expand_timer (NautilusTreeViewDragDest *dest,
+ GtkTreePath *drop_path,
+ GtkTreePath *old_drop_path)
+{
+ GtkTreeModel *model;
+ GtkTreeIter drop_iter;
+
+ model = gtk_tree_view_get_model (dest->details->tree_view);
+
+ if (drop_path == NULL ||
+ (old_drop_path != NULL && gtk_tree_path_compare (old_drop_path, drop_path) != 0))
+ {
+ remove_expand_timer (dest);
+ }
+
+ if (dest->details->expand_id == 0 &&
+ drop_path != NULL)
+ {
+ gtk_tree_model_get_iter (model, &drop_iter, drop_path);
+ if (gtk_tree_model_iter_has_child (model, &drop_iter))
+ {
+ dest->details->expand_id =
+ g_timeout_add_seconds (HOVER_EXPAND_TIMEOUT,
+ expand_timeout,
+ dest->details->tree_view);
+ }
+ }
+}
+
+static char *
+get_root_uri (NautilusTreeViewDragDest *dest)
+{
+ char *uri;
+
+ g_signal_emit (dest, signals[GET_ROOT_URI], 0, &uri);
+
+ return uri;
+}
+
+static NautilusFile *
+file_for_path (NautilusTreeViewDragDest *dest,
+ GtkTreePath *path)
+{
+ NautilusFile *file;
+ char *uri;
+
+ if (path)
+ {
+ g_signal_emit (dest, signals[GET_FILE_FOR_PATH], 0, path, &file);
+ }
+ else
+ {
+ uri = get_root_uri (dest);
+
+ file = NULL;
+ if (uri != NULL)
+ {
+ file = nautilus_file_get_by_uri (uri);
+ }
+
+ g_free (uri);
+ }
+
+ return file;
+}
+
+static char *
+get_drop_target_uri_for_path (NautilusTreeViewDragDest *dest,
+ GtkTreePath *path)
+{
+ NautilusFile *file;
+ char *target = NULL;
+ gboolean can;
+
+ file = file_for_path (dest, path);
+ if (file == NULL)
+ {
+ return NULL;
+ }
+ can = nautilus_drag_can_accept_info (file,
+ dest->details->drag_type,
+ dest->details->drag_list);
+ if (can)
+ {
+ target = nautilus_file_get_uri (file);
+ }
+ nautilus_file_unref (file);
+
+ return target;
+}
+
+static void
+check_hover_expand_timer (NautilusTreeViewDragDest *dest,
+ GtkTreePath *path,
+ GtkTreePath *drop_path,
+ GtkTreePath *old_drop_path)
+{
+ gboolean use_tree = g_settings_get_boolean (nautilus_list_view_preferences,
+ NAUTILUS_PREFERENCES_LIST_VIEW_USE_TREE);
+
+ if (use_tree)
+ {
+ check_expand_timer (dest, drop_path, old_drop_path);
+ }
+ else
+ {
+ char *uri;
+ uri = get_drop_target_uri_for_path (dest, path);
+ check_hover_timer (dest, uri);
+ g_free (uri);
+ }
+}
+
+static GtkTreePath *
+get_drop_path (NautilusTreeViewDragDest *dest,
+ GtkTreePath *path)
+{
+ NautilusFile *file;
+ GtkTreePath *ret;
+
+ if (!path || !dest->details->have_drag_data)
+ {
+ return NULL;
+ }
+
+ ret = gtk_tree_path_copy (path);
+ file = file_for_path (dest, ret);
+
+ /* Go up the tree until we find a file that can accept a drop */
+ while (file == NULL /* dummy row */ ||
+ !nautilus_drag_can_accept_info (file,
+ dest->details->drag_type,
+ dest->details->drag_list))
+ {
+ if (gtk_tree_path_get_depth (ret) == 1)
+ {
+ gtk_tree_path_free (ret);
+ ret = NULL;
+ break;
+ }
+ else
+ {
+ gtk_tree_path_up (ret);
+
+ nautilus_file_unref (file);
+ file = file_for_path (dest, ret);
+ }
+ }
+ nautilus_file_unref (file);
+
+ return ret;
+}
+
+static guint
+get_drop_action (NautilusTreeViewDragDest *dest,
+ GdkDragContext *context,
+ GtkTreePath *path)
+{
+ char *drop_target;
+ int action;
+
+ if (!dest->details->have_drag_data ||
+ (dest->details->drag_type == NAUTILUS_ICON_DND_GNOME_ICON_LIST &&
+ dest->details->drag_list == NULL))
+ {
+ return 0;
+ }
+
+ drop_target = get_drop_target_uri_for_path (dest, path);
+ if (drop_target == NULL)
+ {
+ return 0;
+ }
+
+ action = 0;
+ switch (dest->details->drag_type)
+ {
+ case NAUTILUS_ICON_DND_GNOME_ICON_LIST:
+ {
+ nautilus_drag_default_drop_action_for_icons
+ (context,
+ drop_target,
+ dest->details->drag_list,
+ 0,
+ &action);
+ }
+ break;
+
+ case NAUTILUS_ICON_DND_NETSCAPE_URL:
+ {
+ action = nautilus_drag_default_drop_action_for_netscape_url (context);
+ }
+ break;
+
+ case NAUTILUS_ICON_DND_URI_LIST:
+ {
+ action = gdk_drag_context_get_suggested_action (context);
+ }
+ break;
+
+ case NAUTILUS_ICON_DND_TEXT:
+ case NAUTILUS_ICON_DND_RAW:
+ case NAUTILUS_ICON_DND_XDNDDIRECTSAVE:
+ {
+ action = GDK_ACTION_COPY;
+ }
+ break;
+ }
+
+ g_free (drop_target);
+
+ return action;
+}
+
+static gboolean
+drag_motion_callback (GtkWidget *widget,
+ GdkDragContext *context,
+ int x,
+ int y,
+ guint32 time,
+ gpointer data)
+{
+ NautilusTreeViewDragDest *dest;
+ GtkTreePath *path;
+ GtkTreePath *drop_path, *old_drop_path;
+ GtkTreeViewDropPosition pos;
+ GdkWindow *bin_window;
+ guint action;
+ gboolean res = TRUE;
+
+ dest = NAUTILUS_TREE_VIEW_DRAG_DEST (data);
+
+ gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
+ x, y, &path, &pos);
+ if (pos == GTK_TREE_VIEW_DROP_BEFORE ||
+ pos == GTK_TREE_VIEW_DROP_AFTER)
+ {
+ gtk_tree_path_free (path);
+ path = NULL;
+ }
+
+ if (!dest->details->have_drag_data)
+ {
+ res = get_drag_data (dest, context, time);
+ }
+
+ if (!res)
+ {
+ return FALSE;
+ }
+
+ drop_path = get_drop_path (dest, path);
+
+ action = 0;
+ bin_window = gtk_tree_view_get_bin_window (GTK_TREE_VIEW (widget));
+ if (bin_window != NULL)
+ {
+ int bin_x, bin_y;
+ gdk_window_get_position (bin_window, &bin_x, &bin_y);
+ if (bin_y <= y)
+ {
+ /* ignore drags on the header */
+ action = get_drop_action (dest, context, drop_path);
+ }
+ }
+
+ gtk_tree_view_get_drag_dest_row (GTK_TREE_VIEW (widget), &old_drop_path,
+ NULL);
+
+ if (action)
+ {
+ set_drag_dest_row (dest, drop_path);
+ check_hover_expand_timer (dest, path, drop_path, old_drop_path);
+ }
+ else
+ {
+ clear_drag_dest_row (dest);
+ remove_hover_timer (dest);
+ remove_expand_timer (dest);
+ }
+
+ if (path)
+ {
+ gtk_tree_path_free (path);
+ }
+
+ if (drop_path)
+ {
+ gtk_tree_path_free (drop_path);
+ }
+
+ if (old_drop_path)
+ {
+ gtk_tree_path_free (old_drop_path);
+ }
+
+ if (dest->details->scroll_id == 0)
+ {
+ dest->details->scroll_id =
+ g_timeout_add (150,
+ scroll_timeout,
+ dest->details->tree_view);
+ }
+
+ gdk_drag_status (context, action, time);
+
+ return TRUE;
+}
+
+static void
+drag_leave_callback (GtkWidget *widget,
+ GdkDragContext *context,
+ guint32 time,
+ gpointer data)
+{
+ NautilusTreeViewDragDest *dest;
+
+ dest = NAUTILUS_TREE_VIEW_DRAG_DEST (data);
+
+ clear_drag_dest_row (dest);
+
+ free_drag_data (dest);
+
+ remove_scroll_timeout (dest);
+}
+
+static char *
+get_drop_target_uri_at_pos (NautilusTreeViewDragDest *dest,
+ int x,
+ int y)
+{
+ char *drop_target = NULL;
+ GtkTreePath *path;
+ GtkTreePath *drop_path;
+ GtkTreeViewDropPosition pos;
+
+ gtk_tree_view_get_dest_row_at_pos (dest->details->tree_view, x, y,
+ &path, &pos);
+ if (pos == GTK_TREE_VIEW_DROP_BEFORE ||
+ pos == GTK_TREE_VIEW_DROP_AFTER)
+ {
+ gtk_tree_path_free (path);
+ path = NULL;
+ }
+
+ drop_path = get_drop_path (dest, path);
+
+ drop_target = get_drop_target_uri_for_path (dest, drop_path);
+
+ if (path != NULL)
+ {
+ gtk_tree_path_free (path);
+ }
+
+ if (drop_path != NULL)
+ {
+ gtk_tree_path_free (drop_path);
+ }
+
+ return drop_target;
+}
+
+static void
+receive_uris (NautilusTreeViewDragDest *dest,
+ GdkDragContext *context,
+ GList *source_uris,
+ int x,
+ int y)
+{
+ char *drop_target;
+ GdkDragAction action, real_action;
+
+ drop_target = get_drop_target_uri_at_pos (dest, x, y);
+ g_assert (drop_target != NULL);
+
+ real_action = gdk_drag_context_get_selected_action (context);
+
+ if (real_action == GDK_ACTION_ASK)
+ {
+ action = GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK;
+ real_action = nautilus_drag_drop_action_ask (GTK_WIDGET (dest->details->tree_view), action);
+ }
+
+ /* We only want to copy external uris */
+ if (dest->details->drag_type == NAUTILUS_ICON_DND_URI_LIST)
+ {
+ real_action = GDK_ACTION_COPY;
+ }
+
+ if (real_action > 0)
+ {
+ if (!nautilus_drag_uris_local (drop_target, source_uris)
+ || real_action != GDK_ACTION_MOVE)
+ {
+ g_signal_emit (dest, signals[MOVE_COPY_ITEMS], 0,
+ source_uris,
+ drop_target,
+ real_action,
+ x, y);
+ }
+ }
+
+ g_free (drop_target);
+}
+
+static void
+receive_dropped_icons (NautilusTreeViewDragDest *dest,
+ GdkDragContext *context,
+ int x,
+ int y)
+{
+ GList *source_uris;
+ GList *l;
+
+ /* FIXME: ignore local only moves */
+
+ if (!dest->details->drag_list)
+ {
+ return;
+ }
+
+ source_uris = NULL;
+ for (l = dest->details->drag_list; l != NULL; l = l->next)
+ {
+ source_uris = g_list_prepend (source_uris,
+ ((NautilusDragSelectionItem *) l->data)->uri);
+ }
+
+ source_uris = g_list_reverse (source_uris);
+
+ receive_uris (dest, context, source_uris, x, y);
+
+ g_list_free (source_uris);
+}
+
+static void
+receive_dropped_uri_list (NautilusTreeViewDragDest *dest,
+ GdkDragContext *context,
+ int x,
+ int y)
+{
+ char *drop_target;
+
+ if (!dest->details->drag_data)
+ {
+ return;
+ }
+
+ drop_target = get_drop_target_uri_at_pos (dest, x, y);
+ g_assert (drop_target != NULL);
+
+ g_signal_emit (dest, signals[HANDLE_URI_LIST], 0,
+ (char *) gtk_selection_data_get_data (dest->details->drag_data),
+ drop_target,
+ gdk_drag_context_get_selected_action (context),
+ x, y);
+
+ g_free (drop_target);
+}
+
+static void
+receive_dropped_text (NautilusTreeViewDragDest *dest,
+ GdkDragContext *context,
+ int x,
+ int y)
+{
+ char *drop_target;
+ guchar *text;
+
+ if (!dest->details->drag_data)
+ {
+ return;
+ }
+
+ drop_target = get_drop_target_uri_at_pos (dest, x, y);
+ g_assert (drop_target != NULL);
+
+ text = gtk_selection_data_get_text (dest->details->drag_data);
+ g_signal_emit (dest, signals[HANDLE_TEXT], 0,
+ (char *) text, drop_target,
+ gdk_drag_context_get_selected_action (context),
+ x, y);
+
+ g_free (text);
+ g_free (drop_target);
+}
+
+static void
+receive_dropped_raw (NautilusTreeViewDragDest *dest,
+ const char *raw_data,
+ int length,
+ GdkDragContext *context,
+ int x,
+ int y)
+{
+ char *drop_target;
+
+ if (!dest->details->drag_data)
+ {
+ return;
+ }
+
+ drop_target = get_drop_target_uri_at_pos (dest, x, y);
+ g_assert (drop_target != NULL);
+
+ g_signal_emit (dest, signals[HANDLE_RAW], 0,
+ raw_data, length, drop_target,
+ dest->details->direct_save_uri,
+ gdk_drag_context_get_selected_action (context));
+
+ g_free (drop_target);
+}
+
+static void
+receive_dropped_netscape_url (NautilusTreeViewDragDest *dest,
+ GdkDragContext *context,
+ int x,
+ int y)
+{
+ char *drop_target;
+
+ if (!dest->details->drag_data)
+ {
+ return;
+ }
+
+ drop_target = get_drop_target_uri_at_pos (dest, x, y);
+ g_assert (drop_target != NULL);
+
+ g_signal_emit (dest, signals[HANDLE_NETSCAPE_URL], 0,
+ (char *) gtk_selection_data_get_data (dest->details->drag_data),
+ drop_target,
+ gdk_drag_context_get_selected_action (context),
+ x, y);
+
+ g_free (drop_target);
+}
+
+static gboolean
+receive_xds (NautilusTreeViewDragDest *dest,
+ GtkWidget *widget,
+ guint32 time,
+ GdkDragContext *context,
+ int x,
+ int y)
+{
+ GFile *location;
+ const guchar *selection_data;
+ gint selection_format;
+ gint selection_length;
+
+ selection_data = gtk_selection_data_get_data (dest->details->drag_data);
+ selection_format = gtk_selection_data_get_format (dest->details->drag_data);
+ selection_length = gtk_selection_data_get_length (dest->details->drag_data);
+
+ if (selection_format == 8
+ && selection_length == 1
+ && selection_data[0] == 'F')
+ {
+ gtk_drag_get_data (widget, context,
+ gdk_atom_intern (NAUTILUS_ICON_DND_RAW_TYPE,
+ FALSE),
+ time);
+ return FALSE;
+ }
+ else if (selection_format == 8
+ && selection_length == 1
+ && selection_data[0] == 'S')
+ {
+ g_assert (dest->details->direct_save_uri != NULL);
+ location = g_file_new_for_uri (dest->details->direct_save_uri);
+
+ nautilus_file_changes_queue_file_added (location);
+ nautilus_file_changes_consume_changes (TRUE);
+
+ g_object_unref (location);
+ }
+ return TRUE;
+}
+
+
+static gboolean
+drag_data_received_callback (GtkWidget *widget,
+ GdkDragContext *context,
+ int x,
+ int y,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint32 time,
+ gpointer data)
+{
+ NautilusTreeViewDragDest *dest;
+ const gchar *tmp;
+ int length;
+ gboolean success, finished;
+
+ dest = NAUTILUS_TREE_VIEW_DRAG_DEST (data);
+
+ if (!dest->details->have_drag_data)
+ {
+ dest->details->have_drag_data = TRUE;
+ dest->details->drag_type = info;
+ dest->details->drag_data =
+ gtk_selection_data_copy (selection_data);
+ if (info == NAUTILUS_ICON_DND_GNOME_ICON_LIST)
+ {
+ dest->details->drag_list =
+ nautilus_drag_build_selection_list (selection_data);
+ }
+ }
+
+ if (dest->details->drop_occurred)
+ {
+ success = FALSE;
+ finished = TRUE;
+ switch (info)
+ {
+ case NAUTILUS_ICON_DND_GNOME_ICON_LIST:
+ {
+ receive_dropped_icons (dest, context, x, y);
+ success = TRUE;
+ }
+ break;
+
+ case NAUTILUS_ICON_DND_NETSCAPE_URL:
+ {
+ receive_dropped_netscape_url (dest, context, x, y);
+ success = TRUE;
+ }
+ break;
+
+ case NAUTILUS_ICON_DND_URI_LIST:
+ {
+ receive_dropped_uri_list (dest, context, x, y);
+ success = TRUE;
+ }
+ break;
+
+ case NAUTILUS_ICON_DND_TEXT:
+ {
+ receive_dropped_text (dest, context, x, y);
+ success = TRUE;
+ }
+ break;
+
+ case NAUTILUS_ICON_DND_RAW:
+ {
+ length = gtk_selection_data_get_length (selection_data);
+ tmp = (const gchar *) gtk_selection_data_get_data (selection_data);
+ receive_dropped_raw (dest, tmp, length, context, x, y);
+ success = TRUE;
+ }
+ break;
+
+ case NAUTILUS_ICON_DND_XDNDDIRECTSAVE:
+ {
+ finished = receive_xds (dest, widget, time, context, x, y);
+ success = TRUE;
+ }
+ break;
+ }
+
+ if (finished)
+ {
+ dest->details->drop_occurred = FALSE;
+ free_drag_data (dest);
+ gtk_drag_finish (context, success, FALSE, time);
+ }
+ }
+
+ /* appease GtkTreeView by preventing its drag_data_receive
+ * from being called */
+ g_signal_stop_emission_by_name (dest->details->tree_view,
+ "drag-data-received");
+
+ return TRUE;
+}
+
+static char *
+get_direct_save_filename (GdkDragContext *context)
+{
+ guchar *prop_text;
+ gint prop_len;
+
+ if (!gdk_property_get (gdk_drag_context_get_source_window (context), gdk_atom_intern (NAUTILUS_ICON_DND_XDNDDIRECTSAVE_TYPE, FALSE),
+ gdk_atom_intern ("text/plain", FALSE), 0, 1024, FALSE, NULL, NULL,
+ &prop_len, &prop_text))
+ {
+ return NULL;
+ }
+
+ /* Zero-terminate the string */
+ prop_text = g_realloc (prop_text, prop_len + 1);
+ prop_text[prop_len] = '\0';
+
+ /* Verify that the file name provided by the source is valid */
+ if (*prop_text == '\0' ||
+ strchr ((const gchar *) prop_text, G_DIR_SEPARATOR) != NULL)
+ {
+ DEBUG ("Invalid filename provided by XDS drag site");
+ g_free (prop_text);
+ return NULL;
+ }
+
+ return (gchar *) prop_text;
+}
+
+static gboolean
+set_direct_save_uri (NautilusTreeViewDragDest *dest,
+ GdkDragContext *context,
+ int x,
+ int y)
+{
+ GFile *base, *child;
+ char *drop_uri;
+ char *filename, *uri;
+
+ g_assert (dest->details->direct_save_uri == NULL);
+
+ uri = NULL;
+
+ drop_uri = get_drop_target_uri_at_pos (dest, x, y);
+ if (drop_uri != NULL)
+ {
+ filename = get_direct_save_filename (context);
+ if (filename != NULL)
+ {
+ /* Resolve relative path */
+ base = g_file_new_for_uri (drop_uri);
+ child = g_file_get_child (base, filename);
+ uri = g_file_get_uri (child);
+
+ g_object_unref (base);
+ g_object_unref (child);
+
+ /* Change the property */
+ gdk_property_change (gdk_drag_context_get_source_window (context),
+ gdk_atom_intern (NAUTILUS_ICON_DND_XDNDDIRECTSAVE_TYPE, FALSE),
+ gdk_atom_intern ("text/plain", FALSE), 8,
+ GDK_PROP_MODE_REPLACE, (const guchar *) uri,
+ strlen (uri));
+
+ dest->details->direct_save_uri = uri;
+ }
+ else
+ {
+ DEBUG ("Invalid filename provided by XDS drag site");
+ }
+ }
+ else
+ {
+ DEBUG ("Could not retrieve XDS drop destination");
+ }
+
+ return uri != NULL;
+}
+
+static gboolean
+drag_drop_callback (GtkWidget *widget,
+ GdkDragContext *context,
+ int x,
+ int y,
+ guint32 time,
+ gpointer data)
+{
+ NautilusTreeViewDragDest *dest;
+ guint info;
+ GdkAtom target;
+
+ dest = NAUTILUS_TREE_VIEW_DRAG_DEST (data);
+
+ target = gtk_drag_dest_find_target (GTK_WIDGET (dest->details->tree_view),
+ context,
+ NULL);
+ if (target == GDK_NONE)
+ {
+ return FALSE;
+ }
+
+ info = dest->details->drag_type;
+
+ if (info == NAUTILUS_ICON_DND_XDNDDIRECTSAVE)
+ {
+ /* We need to set this or get_drop_path will fail, and it
+ * was unset by drag_leave_callback */
+ dest->details->have_drag_data = TRUE;
+ if (!set_direct_save_uri (dest, context, x, y))
+ {
+ return FALSE;
+ }
+ dest->details->have_drag_data = FALSE;
+ }
+
+ dest->details->drop_occurred = TRUE;
+
+ get_drag_data (dest, context, time);
+ remove_scroll_timeout (dest);
+ clear_drag_dest_row (dest);
+
+ return TRUE;
+}
+
+static void
+tree_view_weak_notify (gpointer user_data,
+ GObject *object)
+{
+ NautilusTreeViewDragDest *dest;
+
+ dest = NAUTILUS_TREE_VIEW_DRAG_DEST (user_data);
+
+ remove_scroll_timeout (dest);
+
+ dest->details->tree_view = NULL;
+}
+
+static void
+nautilus_tree_view_drag_dest_dispose (GObject *object)
+{
+ NautilusTreeViewDragDest *dest;
+
+ dest = NAUTILUS_TREE_VIEW_DRAG_DEST (object);
+
+ if (dest->details->tree_view)
+ {
+ g_object_weak_unref (G_OBJECT (dest->details->tree_view),
+ tree_view_weak_notify,
+ dest);
+ }
+
+ remove_scroll_timeout (dest);
+
+ G_OBJECT_CLASS (nautilus_tree_view_drag_dest_parent_class)->dispose (object);
+}
+
+static void
+nautilus_tree_view_drag_dest_finalize (GObject *object)
+{
+ NautilusTreeViewDragDest *dest;
+
+ dest = NAUTILUS_TREE_VIEW_DRAG_DEST (object);
+ free_drag_data (dest);
+
+ G_OBJECT_CLASS (nautilus_tree_view_drag_dest_parent_class)->finalize (object);
+}
+
+static void
+nautilus_tree_view_drag_dest_init (NautilusTreeViewDragDest *dest)
+{
+ dest->details = G_TYPE_INSTANCE_GET_PRIVATE (dest, NAUTILUS_TYPE_TREE_VIEW_DRAG_DEST,
+ NautilusTreeViewDragDestDetails);
+}
+
+static void
+nautilus_tree_view_drag_dest_class_init (NautilusTreeViewDragDestClass *class)
+{
+ GObjectClass *gobject_class;
+
+ gobject_class = G_OBJECT_CLASS (class);
+
+ gobject_class->dispose = nautilus_tree_view_drag_dest_dispose;
+ gobject_class->finalize = nautilus_tree_view_drag_dest_finalize;
+
+ g_type_class_add_private (class, sizeof (NautilusTreeViewDragDestDetails));
+
+ signals[GET_ROOT_URI] =
+ g_signal_new ("get-root-uri",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusTreeViewDragDestClass,
+ get_root_uri),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_STRING, 0);
+ signals[GET_FILE_FOR_PATH] =
+ g_signal_new ("get-file-for-path",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusTreeViewDragDestClass,
+ get_file_for_path),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ NAUTILUS_TYPE_FILE, 1,
+ GTK_TYPE_TREE_PATH);
+ signals[MOVE_COPY_ITEMS] =
+ g_signal_new ("move-copy-items",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusTreeViewDragDestClass,
+ move_copy_items),
+ NULL, NULL,
+
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 3,
+ G_TYPE_POINTER,
+ G_TYPE_STRING,
+ GDK_TYPE_DRAG_ACTION);
+ signals[HANDLE_NETSCAPE_URL] =
+ g_signal_new ("handle-netscape-url",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusTreeViewDragDestClass,
+ handle_netscape_url),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 3,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ GDK_TYPE_DRAG_ACTION);
+ signals[HANDLE_URI_LIST] =
+ g_signal_new ("handle-uri-list",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusTreeViewDragDestClass,
+ handle_uri_list),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 3,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ GDK_TYPE_DRAG_ACTION);
+ signals[HANDLE_TEXT] =
+ g_signal_new ("handle-text",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusTreeViewDragDestClass,
+ handle_text),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 3,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ GDK_TYPE_DRAG_ACTION);
+ signals[HANDLE_RAW] =
+ g_signal_new ("handle-raw",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusTreeViewDragDestClass,
+ handle_raw),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 5,
+ G_TYPE_POINTER,
+ G_TYPE_INT,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ GDK_TYPE_DRAG_ACTION);
+ signals[HANDLE_HOVER] =
+ g_signal_new ("handle-hover",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusTreeViewDragDestClass,
+ handle_hover),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 1,
+ G_TYPE_STRING);
+}
+
+
+
+NautilusTreeViewDragDest *
+nautilus_tree_view_drag_dest_new (GtkTreeView *tree_view)
+{
+ NautilusTreeViewDragDest *dest;
+ GtkTargetList *targets;
+
+ dest = g_object_new (NAUTILUS_TYPE_TREE_VIEW_DRAG_DEST, NULL);
+
+ dest->details->tree_view = tree_view;
+ g_object_weak_ref (G_OBJECT (dest->details->tree_view),
+ tree_view_weak_notify, dest);
+
+ gtk_drag_dest_set (GTK_WIDGET (tree_view),
+ 0, drag_types, G_N_ELEMENTS (drag_types),
+ GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK | GDK_ACTION_ASK);
+
+ targets = gtk_drag_dest_get_target_list (GTK_WIDGET (tree_view));
+ gtk_target_list_add_text_targets (targets, NAUTILUS_ICON_DND_TEXT);
+
+ g_signal_connect_object (tree_view,
+ "drag-motion",
+ G_CALLBACK (drag_motion_callback),
+ dest, 0);
+ g_signal_connect_object (tree_view,
+ "drag-leave",
+ G_CALLBACK (drag_leave_callback),
+ dest, 0);
+ g_signal_connect_object (tree_view,
+ "drag-drop",
+ G_CALLBACK (drag_drop_callback),
+ dest, 0);
+ g_signal_connect_object (tree_view,
+ "drag-data-received",
+ G_CALLBACK (drag_data_received_callback),
+ dest, 0);
+
+ return dest;
+}
diff --git a/src/nautilus-tree-view-drag-dest.h b/src/nautilus-tree-view-drag-dest.h
new file mode 100644
index 0000000..b2134c6
--- /dev/null
+++ b/src/nautilus-tree-view-drag-dest.h
@@ -0,0 +1,96 @@
+
+/*
+ * Nautilus
+ *
+ * Copyright (C) 2002 Sun Microsystems, 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: Dave Camp <dave@ximian.com>
+ */
+
+/* nautilus-tree-view-drag-dest.h: Handles drag and drop for treeviews which
+ * contain a hierarchy of files
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+#include "nautilus-file.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_TREE_VIEW_DRAG_DEST (nautilus_tree_view_drag_dest_get_type ())
+#define NAUTILUS_TREE_VIEW_DRAG_DEST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_TREE_VIEW_DRAG_DEST, NautilusTreeViewDragDest))
+#define NAUTILUS_TREE_VIEW_DRAG_DEST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_TREE_VIEW_DRAG_DEST, NautilusTreeViewDragDestClass))
+#define NAUTILUS_IS_TREE_VIEW_DRAG_DEST(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_TREE_VIEW_DRAG_DEST))
+#define NAUTILUS_IS_TREE_VIEW_DRAG_DEST_CLASS(klass) (G_TYPE_CLASS_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_TREE_VIEW_DRAG_DEST))
+
+typedef struct _NautilusTreeViewDragDest NautilusTreeViewDragDest;
+typedef struct _NautilusTreeViewDragDestClass NautilusTreeViewDragDestClass;
+typedef struct _NautilusTreeViewDragDestDetails NautilusTreeViewDragDestDetails;
+
+struct _NautilusTreeViewDragDest {
+ GObject parent;
+
+ NautilusTreeViewDragDestDetails *details;
+};
+
+struct _NautilusTreeViewDragDestClass {
+ GObjectClass parent;
+
+ char *(*get_root_uri) (NautilusTreeViewDragDest *dest);
+ NautilusFile *(*get_file_for_path) (NautilusTreeViewDragDest *dest,
+ GtkTreePath *path);
+ void (*move_copy_items) (NautilusTreeViewDragDest *dest,
+ const GList *item_uris,
+ const char *target_uri,
+ GdkDragAction action,
+ int x,
+ int y);
+ void (* handle_netscape_url) (NautilusTreeViewDragDest *dest,
+ const char *url,
+ const char *target_uri,
+ GdkDragAction action,
+ int x,
+ int y);
+ void (* handle_uri_list) (NautilusTreeViewDragDest *dest,
+ const char *uri_list,
+ const char *target_uri,
+ GdkDragAction action,
+ int x,
+ int y);
+ void (* handle_text) (NautilusTreeViewDragDest *dest,
+ const char *text,
+ const char *target_uri,
+ GdkDragAction action,
+ int x,
+ int y);
+ void (* handle_raw) (NautilusTreeViewDragDest *dest,
+ char *raw_data,
+ int length,
+ const char *target_uri,
+ const char *direct_save_uri,
+ GdkDragAction action,
+ int x,
+ int y);
+ void (* handle_hover) (NautilusTreeViewDragDest *dest,
+ const char *target_uri);
+};
+
+GType nautilus_tree_view_drag_dest_get_type (void);
+NautilusTreeViewDragDest *nautilus_tree_view_drag_dest_new (GtkTreeView *tree_view);
+
+G_END_DECLS \ No newline at end of file
diff --git a/src/nautilus-types.h b/src/nautilus-types.h
new file mode 100644
index 0000000..3c2979a
--- /dev/null
+++ b/src/nautilus-types.h
@@ -0,0 +1,48 @@
+/* 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 _NautilusCanvasContainer NautilusCanvasContainer;
+typedef struct _NautilusCanvasView NautilusCanvasView;
+typedef struct _NautilusDirectory NautilusDirectory;
+typedef struct NautilusFile NautilusFile;
+typedef struct NautilusFileQueue NautilusFileQueue;
+typedef struct _NautilusFilesView NautilusFilesView;
+typedef struct _NautilusIconInfo NautilusIconInfo;
+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..195f765
--- /dev/null
+++ b/src/nautilus-ui-utilities.c
@@ -0,0 +1,381 @@
+/* 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 <libgd/gd.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);
+ }
+ }
+}
+
+#define NAUTILUS_THUMBNAIL_FRAME_LEFT 3
+#define NAUTILUS_THUMBNAIL_FRAME_TOP 3
+#define NAUTILUS_THUMBNAIL_FRAME_RIGHT 3
+#define NAUTILUS_THUMBNAIL_FRAME_BOTTOM 3
+
+void
+nautilus_ui_frame_image (GdkPixbuf **pixbuf)
+{
+ GtkBorder border;
+ GdkPixbuf *pixbuf_with_frame;
+
+ border.left = NAUTILUS_THUMBNAIL_FRAME_LEFT;
+ border.top = NAUTILUS_THUMBNAIL_FRAME_TOP;
+ border.right = NAUTILUS_THUMBNAIL_FRAME_RIGHT;
+ border.bottom = NAUTILUS_THUMBNAIL_FRAME_BOTTOM;
+
+ pixbuf_with_frame = gd_embed_image_in_frame (*pixbuf,
+ "resource:///org/gnome/nautilus/icons/thumbnail_frame.png",
+ &border, &border);
+ g_object_unref (*pixbuf);
+
+ *pixbuf = pixbuf_with_frame;
+}
+
+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 (GdkPixbuf **pixbuf)
+{
+ int width, height;
+ int holes_width, holes_height;
+ int i;
+
+ if (!ensure_filmholes ())
+ {
+ return;
+ }
+
+ width = gdk_pixbuf_get_width (*pixbuf);
+ height = gdk_pixbuf_get_height (*pixbuf);
+ holes_width = gdk_pixbuf_get_width (filmholes_left);
+ holes_height = gdk_pixbuf_get_height (filmholes_left);
+
+ for (i = 0; i < height; i += holes_height)
+ {
+ gdk_pixbuf_composite (filmholes_left, *pixbuf, 0, i,
+ MIN (width, holes_width),
+ MIN (height - i, holes_height),
+ 0, i, 1, 1, GDK_INTERP_NEAREST, 255);
+ }
+
+ for (i = 0; i < height; i += holes_height)
+ {
+ gdk_pixbuf_composite (filmholes_right, *pixbuf,
+ width - holes_width, i,
+ MIN (width, holes_width),
+ MIN (height - i, holes_height),
+ width - holes_width, i,
+ 1, 1, GDK_INTERP_NEAREST, 255);
+ }
+}
+
+gboolean
+nautilus_file_date_in_between (guint64 unix_file_time,
+ GDateTime *initial_date,
+ GDateTime *end_date)
+{
+ GDateTime *date;
+ gboolean in_between;
+
+ /* Silently ignore errors */
+ if (unix_file_time == 0)
+ {
+ return FALSE;
+ }
+
+ date = g_date_time_new_from_unix_local (unix_file_time);
+
+ /* 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;
+
+ g_date_time_unref (date);
+
+ 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;
+}
+
+GtkDialog *
+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 = gtk_message_dialog_new (parent,
+ GTK_DIALOG_MODAL,
+ type,
+ GTK_BUTTONS_OK,
+ "%s", primary_text);
+
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ "%s", secondary_text);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+
+ gtk_widget_show (dialog);
+
+ g_signal_connect (GTK_DIALOG (dialog), "response",
+ G_CALLBACK (gtk_widget_destroy), NULL);
+
+ return GTK_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..1136e1d
--- /dev/null
+++ b/src/nautilus-ui-utilities.h
@@ -0,0 +1,51 @@
+
+/* 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>
+
+
+void nautilus_gmenu_set_from_model (GMenu *target_menu,
+ GMenuModel *source_model);
+
+void nautilus_ui_frame_image (GdkPixbuf **pixbuf);
+void nautilus_ui_frame_video (GdkPixbuf **pixbuf);
+
+gboolean nautilus_file_date_in_between (guint64 file_unix_time,
+ GDateTime *initial_date,
+ GDateTime *end_date);
+gchar * get_text_for_date_range (GPtrArray *date_range,
+ gboolean prefix_with_since);
+
+GtkDialog * 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..66dff20
--- /dev/null
+++ b/src/nautilus-vfs-file.c
@@ -0,0 +1,709 @@
+/*
+ * 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);
+ 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 recency;
+ time_t trash_time;
+
+ atime = nautilus_file_get_atime (file);
+ mtime = nautilus_file_get_mtime (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_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-icon-controller.c b/src/nautilus-view-icon-controller.c
new file mode 100644
index 0000000..ab8b824
--- /dev/null
+++ b/src/nautilus-view-icon-controller.c
@@ -0,0 +1,1099 @@
+#include "nautilus-view-icon-controller.h"
+#include "nautilus-view-icon-ui.h"
+#include "nautilus-view-item-model.h"
+#include "nautilus-view-icon-item-ui.h"
+#include "nautilus-view-model.h"
+#include "nautilus-files-view.h"
+#include "nautilus-file.h"
+#include "nautilus-metadata.h"
+#include "nautilus-window-slot.h"
+#include "nautilus-directory.h"
+#include "nautilus-global-preferences.h"
+
+struct _NautilusViewIconController
+{
+ NautilusFilesView parent_instance;
+
+ NautilusViewIconUi *view_ui;
+ NautilusViewModel *model;
+
+ GIcon *view_icon;
+ GActionGroup *action_group;
+ gint zoom_level;
+
+ GtkGesture *multi_press_gesture;
+};
+
+G_DEFINE_TYPE (NautilusViewIconController, nautilus_view_icon_controller, NAUTILUS_TYPE_FILES_VIEW)
+
+typedef struct
+{
+ const NautilusFileSortType sort_type;
+ const gchar *metadata_name;
+ const gchar *action_target_name;
+ gboolean reversed;
+} SortConstants;
+
+static const SortConstants sorts_constants[] =
+{
+ {
+ NAUTILUS_FILE_SORT_BY_DISPLAY_NAME,
+ "name",
+ "name",
+ FALSE,
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_DISPLAY_NAME,
+ "name",
+ "name-desc",
+ TRUE,
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_SIZE,
+ "size",
+ "size",
+ TRUE,
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_TYPE,
+ "type",
+ "type",
+ FALSE,
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_MTIME,
+ "modification date",
+ "modification-date",
+ FALSE,
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_MTIME,
+ "modification date",
+ "modification-date-desc",
+ TRUE,
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_ATIME,
+ "access date",
+ "access-date",
+ FALSE,
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_ATIME,
+ "access date",
+ "access-date-desc",
+ TRUE,
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_TRASHED_TIME,
+ "trashed",
+ "trash-time",
+ TRUE,
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_SEARCH_RELEVANCE,
+ NULL,
+ "search-relevance",
+ TRUE,
+ }
+};
+
+static guint get_icon_size_for_zoom_level (NautilusCanvasZoomLevel zoom_level);
+
+static const SortConstants *
+get_sorts_constants_from_action_target_name (const gchar *action_target_name)
+{
+ int i;
+
+ for (i = 0; i < G_N_ELEMENTS (sorts_constants); i++)
+ {
+ if (g_strcmp0 (sorts_constants[i].action_target_name, action_target_name) == 0)
+ {
+ return &sorts_constants[i];
+ }
+ }
+
+ return &sorts_constants[0];
+}
+
+static const SortConstants *
+get_sorts_constants_from_sort_type (NautilusFileSortType sort_type,
+ gboolean reversed)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (sorts_constants); i++)
+ {
+ if (sort_type == sorts_constants[i].sort_type
+ && reversed == sorts_constants[i].reversed)
+ {
+ return &sorts_constants[i];
+ }
+ }
+
+ return &sorts_constants[0];
+}
+
+static const SortConstants *
+get_sorts_constants_from_metadata_text (const char *metadata_name,
+ gboolean reversed)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (sorts_constants); i++)
+ {
+ if (g_strcmp0 (sorts_constants[i].metadata_name, metadata_name) == 0
+ && reversed == sorts_constants[i].reversed)
+ {
+ return &sorts_constants[i];
+ }
+ }
+
+ return &sorts_constants[0];
+}
+
+static const SortConstants *
+get_default_sort_order (NautilusFile *file)
+{
+ NautilusFileSortType sort_type;
+ gboolean reversed;
+
+ sort_type = nautilus_file_get_default_sort_type (file, &reversed);
+
+ return get_sorts_constants_from_sort_type (sort_type, reversed);
+}
+
+static const SortConstants *
+get_directory_sort_by (NautilusFile *file)
+{
+ const SortConstants *default_sort;
+ g_autofree char *sort_by = NULL;
+ gboolean reversed;
+
+ default_sort = get_default_sort_order (file);
+ g_return_val_if_fail (default_sort != NULL, NULL);
+
+ 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,
+ default_sort->reversed);
+
+ return get_sorts_constants_from_metadata_text (sort_by, reversed);
+}
+
+static void
+set_directory_sort_metadata (NautilusFile *file,
+ const SortConstants *sort)
+{
+ const SortConstants *default_sort;
+
+ default_sort = get_default_sort_order (file);
+
+ nautilus_file_set_metadata (file,
+ NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_BY,
+ default_sort->metadata_name,
+ sort->metadata_name);
+ nautilus_file_set_boolean_metadata (file,
+ NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_REVERSED,
+ default_sort->reversed,
+ sort->reversed);
+}
+
+static void
+update_sort_order_from_metadata_and_preferences (NautilusViewIconController *self)
+{
+ const SortConstants *default_directory_sort;
+ GActionGroup *view_action_group;
+
+ default_directory_sort = get_directory_sort_by (nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (self)));
+ 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_string (get_sorts_constants_from_sort_type (default_directory_sort->sort_type, default_directory_sort->reversed)->action_target_name));
+}
+
+static void
+real_begin_loading (NautilusFilesView *files_view)
+{
+ NautilusViewIconController *self = NAUTILUS_VIEW_ICON_CONTROLLER (files_view);
+
+ /* TODO: This calls sort once, and update_context_menus calls update_actions which calls
+ * the action again
+ */
+ update_sort_order_from_metadata_and_preferences (self);
+
+ /*TODO move this to the files view class begin_loading and hook up? */
+
+ /* 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);
+}
+
+static void
+real_clear (NautilusFilesView *files_view)
+{
+ NautilusViewIconController *self = NAUTILUS_VIEW_ICON_CONTROLLER (files_view);
+
+ nautilus_view_model_remove_all_items (self->model);
+}
+
+
+/* FIXME: ideally this should go into the model so there is not need to
+ * recreate the model with the new data */
+static void
+real_file_changed (NautilusFilesView *files_view,
+ NautilusFile *file,
+ NautilusDirectory *directory)
+{
+ NautilusViewIconController *self;
+ NautilusViewItemModel *item_model;
+ NautilusViewItemModel *new_item_model;
+
+ self = NAUTILUS_VIEW_ICON_CONTROLLER (files_view);
+ item_model = nautilus_view_model_get_item_from_file (self->model, file);
+ nautilus_view_model_remove_item (self->model, item_model);
+ new_item_model = nautilus_view_item_model_new (file,
+ get_icon_size_for_zoom_level (self->zoom_level));
+ nautilus_view_model_add_item (self->model, new_item_model);
+}
+
+static GList *
+real_get_selection (NautilusFilesView *files_view)
+{
+ NautilusViewIconController *self;
+ GList *selected_files = NULL;
+ GList *l;
+ g_autoptr (GList) selected_items = NULL;
+
+ self = NAUTILUS_VIEW_ICON_CONTROLLER (files_view);
+ selected_items = gtk_flow_box_get_selected_children (GTK_FLOW_BOX (self->view_ui));
+ for (l = selected_items; l != NULL; l = l->next)
+ {
+ NautilusViewItemModel *item_model;
+
+ item_model = nautilus_view_icon_item_ui_get_model (NAUTILUS_VIEW_ICON_ITEM_UI (l->data));
+ selected_files = g_list_prepend (selected_files,
+ g_object_ref (nautilus_view_item_model_get_file (item_model)));
+ }
+
+ return selected_files;
+}
+
+static gboolean
+real_is_empty (NautilusFilesView *files_view)
+{
+ NautilusViewIconController *self = NAUTILUS_VIEW_ICON_CONTROLLER (files_view);
+
+ return g_list_model_get_n_items (G_LIST_MODEL (nautilus_view_model_get_g_model (self->model))) == 0;
+}
+
+static void
+real_end_file_changes (NautilusFilesView *files_view)
+{
+}
+
+static void
+real_remove_file (NautilusFilesView *files_view,
+ NautilusFile *file,
+ NautilusDirectory *directory)
+{
+ NautilusViewIconController *self = NAUTILUS_VIEW_ICON_CONTROLLER (files_view);
+ NautilusFile *current_file;
+ NautilusViewItemModel *current_item_model;
+ guint i = 0;
+
+ while ((current_item_model = NAUTILUS_VIEW_ITEM_MODEL (g_list_model_get_item (G_LIST_MODEL (nautilus_view_model_get_g_model (self->model)), i))))
+ {
+ current_file = nautilus_view_item_model_get_file (current_item_model);
+ if (current_file == file)
+ {
+ g_list_store_remove (nautilus_view_model_get_g_model (self->model), i);
+ break;
+ }
+ i++;
+ }
+}
+
+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_item_models (NautilusViewIconController *self,
+ GQueue *files)
+{
+ GList *l;
+ GQueue *models;
+
+ models = g_queue_new ();
+ for (l = g_queue_peek_head_link (files); l != NULL; l = l->next)
+ {
+ NautilusViewItemModel *item_model;
+
+ item_model = nautilus_view_item_model_new (NAUTILUS_FILE (l->data),
+ get_icon_size_for_zoom_level (self->zoom_level));
+ g_queue_push_tail (models, item_model);
+ }
+
+ return models;
+}
+
+static void
+real_set_selection (NautilusFilesView *files_view,
+ GList *selection)
+{
+ NautilusViewIconController *self = NAUTILUS_VIEW_ICON_CONTROLLER (files_view);
+ g_autoptr (GQueue) selection_files = NULL;
+ g_autoptr (GQueue) selection_item_models = NULL;
+
+ selection_files = convert_glist_to_queue (selection);
+ selection_item_models = nautilus_view_model_get_items_from_files (self->model, selection_files);
+ nautilus_view_icon_ui_set_selection (self->view_ui, selection_item_models);
+ nautilus_files_view_notify_selection_changed (files_view);
+}
+
+static void
+real_select_all (NautilusFilesView *files_view)
+{
+ NautilusViewIconController *self = NAUTILUS_VIEW_ICON_CONTROLLER (files_view);
+ gtk_flow_box_select_all (GTK_FLOW_BOX (self->view_ui));
+}
+
+static GtkWidget *
+get_first_selected_item_ui (NautilusViewIconController *self)
+{
+ g_autolist (NautilusFile) selection = NULL;
+ NautilusFile *file;
+ NautilusViewItemModel *item_model;
+
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (self));
+ if (selection == NULL)
+ {
+ return NULL;
+ }
+
+ file = NAUTILUS_FILE (selection->data);
+ item_model = nautilus_view_model_get_item_from_file (self->model, file);
+
+ return nautilus_view_item_model_get_item_ui (item_model);
+}
+
+static void
+reveal_item_ui (NautilusViewIconController *self,
+ GtkWidget *item_ui)
+{
+ GtkAllocation allocation;
+ GtkWidget *content_widget;
+ GtkAdjustment *vadjustment;
+ int view_height;
+
+ gtk_widget_get_allocation (item_ui, &allocation);
+ content_widget = nautilus_files_view_get_content_widget (NAUTILUS_FILES_VIEW (self));
+ view_height = gtk_widget_get_allocated_height (content_widget);
+ vadjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (content_widget));
+
+ /* Scroll only as necessary. TODO: Would be nice to have this as part of
+ * GtkFlowBox. GtkTreeView has something similar. */
+ if (allocation.y < gtk_adjustment_get_value (vadjustment))
+ {
+ gtk_adjustment_set_value (vadjustment, allocation.y);
+ }
+ else if (allocation.y + allocation.height >
+ gtk_adjustment_get_value (vadjustment) + view_height)
+ {
+ gtk_adjustment_set_value (vadjustment,
+ allocation.y + allocation.height - view_height);
+ }
+}
+
+static void
+real_reveal_selection (NautilusFilesView *files_view)
+{
+ NautilusViewIconController *self = NAUTILUS_VIEW_ICON_CONTROLLER (files_view);
+ GtkWidget *item_ui;
+
+ item_ui = get_first_selected_item_ui (self);
+
+ if (item_ui != NULL)
+ {
+ reveal_item_ui (self, item_ui);
+ }
+}
+
+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_search_directory (NautilusFilesView *view)
+{
+ NautilusFile *file;
+
+ file = nautilus_files_view_get_directory_as_file (view);
+ if (file != NULL)
+ {
+ return nautilus_file_is_in_search (file);
+ }
+ return FALSE;
+}
+
+static void
+real_update_actions_state (NautilusFilesView *files_view)
+{
+ GAction *action;
+ GActionGroup *view_action_group;
+
+ NAUTILUS_FILES_VIEW_CLASS (nautilus_view_icon_controller_parent_class)->update_actions_state (files_view);
+
+ view_action_group = nautilus_files_view_get_action_group (files_view);
+ action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "sort");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
+ !showing_recent_directory (files_view) &&
+ !showing_search_directory (files_view));
+}
+
+static void
+real_bump_zoom_level (NautilusFilesView *files_view,
+ int zoom_increment)
+{
+ NautilusViewIconController *self = NAUTILUS_VIEW_ICON_CONTROLLER (files_view);
+ NautilusCanvasZoomLevel new_level;
+
+ new_level = self->zoom_level + zoom_increment;
+
+ if (new_level >= NAUTILUS_CANVAS_ZOOM_LEVEL_SMALL &&
+ new_level <= NAUTILUS_CANVAS_ZOOM_LEVEL_LARGEST)
+ {
+ 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 (NautilusCanvasZoomLevel zoom_level)
+{
+ switch (zoom_level)
+ {
+ case NAUTILUS_CANVAS_ZOOM_LEVEL_SMALL:
+ {
+ return NAUTILUS_CANVAS_ICON_SIZE_SMALL;
+ }
+ break;
+
+ case NAUTILUS_CANVAS_ZOOM_LEVEL_STANDARD:
+ {
+ return NAUTILUS_CANVAS_ICON_SIZE_STANDARD;
+ }
+ break;
+
+ case NAUTILUS_CANVAS_ZOOM_LEVEL_LARGE:
+ {
+ return NAUTILUS_CANVAS_ICON_SIZE_LARGE;
+ }
+ break;
+
+ case NAUTILUS_CANVAS_ZOOM_LEVEL_LARGER:
+ {
+ return NAUTILUS_CANVAS_ICON_SIZE_LARGER;
+ }
+ break;
+
+ case NAUTILUS_CANVAS_ZOOM_LEVEL_LARGEST:
+ {
+ return NAUTILUS_CANVAS_ICON_SIZE_LARGEST;
+ }
+ break;
+ }
+ g_return_val_if_reached (NAUTILUS_CANVAS_ICON_SIZE_STANDARD);
+}
+
+static gint
+get_default_zoom_level (void)
+{
+ NautilusCanvasZoomLevel default_zoom_level;
+
+ default_zoom_level = g_settings_get_enum (nautilus_icon_view_preferences,
+ NAUTILUS_PREFERENCES_ICON_VIEW_DEFAULT_ZOOM_LEVEL);
+
+ return default_zoom_level;
+}
+
+static void
+set_icon_size (NautilusViewIconController *self,
+ gint icon_size)
+{
+ NautilusViewItemModel *current_item_model;
+ guint i = 0;
+
+ while ((current_item_model = NAUTILUS_VIEW_ITEM_MODEL (g_list_model_get_item (G_LIST_MODEL (nautilus_view_model_get_g_model (self->model)), i))))
+ {
+ nautilus_view_item_model_set_icon_size (current_item_model,
+ get_icon_size_for_zoom_level (self->zoom_level));
+ i++;
+ }
+}
+
+static void
+set_zoom_level (NautilusViewIconController *self,
+ guint new_level)
+{
+ self->zoom_level = new_level;
+
+ set_icon_size (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)
+{
+ NautilusViewIconController *self;
+
+ self = NAUTILUS_VIEW_ICON_CONTROLLER (files_view);
+ g_action_group_change_action_state (self->action_group,
+ "zoom-to-level",
+ g_variant_new_int32 (NAUTILUS_CANVAS_ZOOM_LEVEL_LARGE));
+}
+
+static gfloat
+real_get_zoom_level_percentage (NautilusFilesView *files_view)
+{
+ NautilusViewIconController *self = NAUTILUS_VIEW_ICON_CONTROLLER (files_view);
+
+ return (gfloat) get_icon_size_for_zoom_level (self->zoom_level) /
+ NAUTILUS_CANVAS_ICON_SIZE_LARGE;
+}
+
+static gboolean
+real_is_zoom_level_default (NautilusFilesView *files_view)
+{
+ NautilusViewIconController *self;
+ guint icon_size;
+
+ self = NAUTILUS_VIEW_ICON_CONTROLLER (files_view);
+ icon_size = get_icon_size_for_zoom_level (self->zoom_level);
+
+ return icon_size == NAUTILUS_CANVAS_ICON_SIZE_LARGE;
+}
+
+static gboolean
+real_can_zoom_in (NautilusFilesView *files_view)
+{
+ return TRUE;
+}
+
+static gboolean
+real_can_zoom_out (NautilusFilesView *files_view)
+{
+ return TRUE;
+}
+
+static GdkRectangle *
+get_rectangle_for_item_ui (NautilusViewIconController *self,
+ GtkWidget *item_ui)
+{
+ GdkRectangle *rectangle;
+ GtkWidget *content_widget;
+ GtkAdjustment *vadjustment;
+ GtkAdjustment *hadjustment;
+
+ rectangle = g_new0 (GdkRectangle, 1);
+ gtk_widget_get_allocation (item_ui, rectangle);
+
+ content_widget = nautilus_files_view_get_content_widget (NAUTILUS_FILES_VIEW (self));
+ vadjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (content_widget));
+ hadjustment = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (content_widget));
+
+ rectangle->x -= gtk_adjustment_get_value (hadjustment);
+ rectangle->y -= gtk_adjustment_get_value (vadjustment);
+
+ return rectangle;
+}
+
+static GdkRectangle *
+real_compute_rename_popover_pointing_to (NautilusFilesView *files_view)
+{
+ NautilusViewIconController *self = NAUTILUS_VIEW_ICON_CONTROLLER (files_view);
+ GtkWidget *item_ui;
+
+ /* We only allow one item to be renamed with a popover */
+ item_ui = get_first_selected_item_ui (self);
+ 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)
+{
+ g_autolist (NautilusFile) selection = NULL;
+ NautilusViewIconController *self = NAUTILUS_VIEW_ICON_CONTROLLER (files_view);
+ GtkWidget *item_ui;
+
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (files_view));
+ g_return_val_if_fail (selection != NULL, NULL);
+
+ /* Get the focused item_ui, if selected.
+ * Otherwise, get the selected item_ui which is sorted the lowest.*/
+ item_ui = gtk_container_get_focus_child (GTK_CONTAINER (self->view_ui));
+ if (item_ui == NULL || !gtk_flow_box_child_is_selected (GTK_FLOW_BOX_CHILD (item_ui)))
+ {
+ g_autoptr (GList) list = gtk_flow_box_get_selected_children (GTK_FLOW_BOX (self->view_ui));
+
+ list = g_list_last (list);
+ item_ui = GTK_WIDGET (list->data);
+ }
+
+ reveal_item_ui (self, item_ui);
+
+ return get_rectangle_for_item_ui (self, item_ui);
+}
+
+static void
+real_click_policy_changed (NautilusFilesView *files_view)
+{
+}
+
+static void
+on_button_press_event (GtkGestureMultiPress *gesture,
+ gint n_press,
+ gdouble x,
+ gdouble y,
+ gpointer user_data)
+{
+ NautilusViewIconController *self;
+ guint button;
+ GdkEventSequence *sequence;
+ const GdkEvent *event;
+ g_autolist (NautilusFile) selection = NULL;
+ GtkWidget *child_at_pos;
+
+ self = NAUTILUS_VIEW_ICON_CONTROLLER (user_data);
+ button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
+ sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
+ event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
+
+ /* Need to update the selection so the popup has the right actions enabled */
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (self));
+ child_at_pos = GTK_WIDGET (gtk_flow_box_get_child_at_pos (GTK_FLOW_BOX (self->view_ui),
+ x, y));
+ if (child_at_pos != NULL)
+ {
+ NautilusFile *selected_file;
+ NautilusViewItemModel *item_model;
+
+ item_model = nautilus_view_icon_item_ui_get_model (NAUTILUS_VIEW_ICON_ITEM_UI (child_at_pos));
+ selected_file = nautilus_view_item_model_get_file (item_model);
+ if (g_list_find (selection, selected_file) == NULL)
+ {
+ g_list_foreach (selection, (GFunc) g_object_unref, NULL);
+ selection = g_list_append (NULL, g_object_ref (selected_file));
+ }
+ else
+ {
+ selection = g_list_prepend (selection, g_object_ref (selected_file));
+ }
+
+ nautilus_view_set_selection (NAUTILUS_VIEW (self), selection);
+
+ if (button == GDK_BUTTON_SECONDARY)
+ {
+ nautilus_files_view_pop_up_selection_context_menu (NAUTILUS_FILES_VIEW (self),
+ event);
+ }
+ }
+ else
+ {
+ nautilus_view_set_selection (NAUTILUS_VIEW (self), NULL);
+ if (button == GDK_BUTTON_SECONDARY)
+ {
+ nautilus_files_view_pop_up_background_context_menu (NAUTILUS_FILES_VIEW (self),
+ event);
+ }
+ }
+}
+
+static void
+on_longpress_gesture_pressed_callback (GtkGestureLongPress *gesture,
+ gdouble x,
+ gdouble y,
+ gpointer user_data)
+{
+ NautilusViewIconController *self;
+ g_autoptr (GList) selection = NULL;
+ GtkWidget *child_at_pos;
+ GdkEventButton *event_button;
+ GdkEventSequence *event_sequence;
+ GdkEvent *event;
+
+ event_sequence = gtk_gesture_get_last_updated_sequence (GTK_GESTURE (gesture));
+ event = (GdkEvent *) gtk_gesture_get_last_event (GTK_GESTURE (gesture), event_sequence);
+
+ self = NAUTILUS_VIEW_ICON_CONTROLLER (user_data);
+ event_button = (GdkEventButton *) event;
+
+ /* Need to update the selection so the popup has the right actions enabled */
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (self));
+ child_at_pos = GTK_WIDGET (gtk_flow_box_get_child_at_pos (GTK_FLOW_BOX (self->view_ui),
+ event_button->x, event_button->y));
+ if (child_at_pos != NULL)
+ {
+ NautilusFile *selected_file;
+ NautilusViewItemModel *item_model;
+
+ item_model = nautilus_view_icon_item_ui_get_model (NAUTILUS_VIEW_ICON_ITEM_UI (child_at_pos));
+ selected_file = nautilus_view_item_model_get_file (item_model);
+ if (g_list_find (selection, selected_file) == NULL)
+ {
+ g_list_foreach (selection, (GFunc) g_object_unref, NULL);
+ selection = g_list_append (NULL, g_object_ref (selected_file));
+ }
+ else
+ {
+ selection = g_list_prepend (selection, g_object_ref (selected_file));
+ }
+
+ nautilus_view_set_selection (NAUTILUS_VIEW (self), selection);
+
+ nautilus_files_view_pop_up_selection_context_menu (NAUTILUS_FILES_VIEW (self),
+ event);
+ }
+ else
+ {
+ nautilus_view_set_selection (NAUTILUS_VIEW (self), NULL);
+ nautilus_files_view_pop_up_background_context_menu (NAUTILUS_FILES_VIEW (self),
+ event);
+ }
+
+ g_list_foreach (selection, (GFunc) g_object_unref, NULL);
+}
+
+static int
+real_compare_files (NautilusFilesView *files_view,
+ NautilusFile *file1,
+ NautilusFile *file2)
+{
+ if (file1 < file2)
+ {
+ return -1;
+ }
+
+ if (file1 > file2)
+ {
+ return +1;
+ }
+
+ return 0;
+}
+
+static void
+real_end_loading (NautilusFilesView *files_view,
+ gboolean all_files_seen)
+{
+}
+
+static char *
+real_get_first_visible_file (NautilusFilesView *files_view)
+{
+ return NULL;
+}
+
+static void
+real_scroll_to_file (NautilusFilesView *files_view,
+ const char *uri)
+{
+}
+
+static void
+real_sort_directories_first_changed (NautilusFilesView *files_view)
+{
+ NautilusViewModelSortData sort_data;
+ NautilusViewModelSortData *current_sort_data;
+ NautilusViewIconController *self;
+
+ self = NAUTILUS_VIEW_ICON_CONTROLLER (files_view);
+ current_sort_data = nautilus_view_model_get_sort_type (self->model);
+ sort_data.sort_type = current_sort_data->sort_type;
+ sort_data.reversed = current_sort_data->reversed;
+ sort_data.directories_first = nautilus_files_view_should_sort_directories_first (NAUTILUS_FILES_VIEW (self));
+
+ nautilus_view_model_set_sort_type (self->model, &sort_data);
+}
+
+static void
+action_sort_order_changed (GSimpleAction *action,
+ GVariant *value,
+ gpointer user_data)
+{
+ const gchar *target_name;
+ const SortConstants *sort_constants;
+ NautilusViewModelSortData sort_data;
+ NautilusViewIconController *self;
+
+ /* Don't resort if the action is in the same state as before */
+ if (g_strcmp0 (g_variant_get_string (value, NULL), g_variant_get_string (g_action_get_state (G_ACTION (action)), NULL)) == 0)
+ {
+ return;
+ }
+
+ self = NAUTILUS_VIEW_ICON_CONTROLLER (user_data);
+ target_name = g_variant_get_string (value, NULL);
+ sort_constants = get_sorts_constants_from_action_target_name (target_name);
+ sort_data.sort_type = sort_constants->sort_type;
+ sort_data.reversed = sort_constants->reversed;
+ sort_data.directories_first = nautilus_files_view_should_sort_directories_first (NAUTILUS_FILES_VIEW (self));
+
+ nautilus_view_model_set_sort_type (self->model, &sort_data);
+ set_directory_sort_metadata (nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (self)),
+ sort_constants);
+
+ g_simple_action_set_state (action, value);
+}
+
+static void
+real_add_files (NautilusFilesView *files_view,
+ GList *files)
+{
+ NautilusViewIconController *self = NAUTILUS_VIEW_ICON_CONTROLLER (files_view);
+ g_autoptr (GQueue) files_queue = NULL;
+ g_autoptr (GQueue) item_models = NULL;
+
+ files_queue = convert_glist_to_queue (files);
+ item_models = convert_files_to_item_models (self, files_queue);
+ nautilus_view_model_add_items (self->model, item_models);
+}
+
+
+static guint
+real_get_view_id (NautilusFilesView *files_view)
+{
+ return NAUTILUS_VIEW_GRID_ID;
+}
+
+static GIcon *
+real_get_icon (NautilusFilesView *files_view)
+{
+ NautilusViewIconController *self = NAUTILUS_VIEW_ICON_CONTROLLER (files_view);
+
+ return self->view_icon;
+}
+
+static void
+real_select_first (NautilusFilesView *files_view)
+{
+}
+
+static void
+real_preview_selection_event (NautilusFilesView *files_view,
+ GtkDirectionType direction)
+{
+ NautilusViewIconController *self = NAUTILUS_VIEW_ICON_CONTROLLER (files_view);
+ GtkMovementStep step;
+ gint count;
+ gboolean handled;
+
+ step = (direction == GTK_DIR_UP || direction == GTK_DIR_DOWN) ?
+ GTK_MOVEMENT_DISPLAY_LINES : GTK_MOVEMENT_VISUAL_POSITIONS;
+ count = (direction == GTK_DIR_RIGHT || direction == GTK_DIR_DOWN) ?
+ 1 : -1;
+
+ g_signal_emit_by_name (self->view_ui, "move-cursor", step, count, &handled);
+}
+
+static void
+action_zoom_to_level (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusViewIconController *self = NAUTILUS_VIEW_ICON_CONTROLLER (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
+dispose (GObject *object)
+{
+ NautilusViewIconController *self;
+
+ self = NAUTILUS_VIEW_ICON_CONTROLLER (object);
+
+ g_clear_object (&self->multi_press_gesture);
+
+ G_OBJECT_CLASS (nautilus_view_icon_controller_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+ G_OBJECT_CLASS (nautilus_view_icon_controller_parent_class)->finalize (object);
+}
+
+
+const GActionEntry view_icon_actions[] =
+{
+ { "sort", NULL, "s", "'invalid'", action_sort_order_changed },
+ { "zoom-to-level", NULL, NULL, "100", action_zoom_to_level }
+};
+
+static void
+constructed (GObject *object)
+{
+ NautilusViewIconController *self = NAUTILUS_VIEW_ICON_CONTROLLER (object);
+ GtkWidget *content_widget;
+ GtkAdjustment *hadjustment;
+ GtkAdjustment *vadjustment;
+ GActionGroup *view_action_group;
+ GtkGesture *longpress_gesture;
+
+ content_widget = nautilus_files_view_get_content_widget (NAUTILUS_FILES_VIEW (self));
+ hadjustment = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (content_widget));
+ vadjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (content_widget));
+
+ self->model = nautilus_view_model_new ();
+ self->view_ui = nautilus_view_icon_ui_new (self);
+ gtk_flow_box_set_hadjustment (GTK_FLOW_BOX (self->view_ui), hadjustment);
+ gtk_flow_box_set_vadjustment (GTK_FLOW_BOX (self->view_ui), vadjustment);
+ gtk_widget_show (GTK_WIDGET (self->view_ui));
+ self->view_icon = g_themed_icon_new ("view-grid-symbolic");
+
+ /* Compensating for the lack of event boxen to allow clicks outside the flow box. */
+ self->multi_press_gesture = gtk_gesture_multi_press_new (GTK_WIDGET (content_widget));
+ gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (self->multi_press_gesture),
+ GTK_PHASE_CAPTURE);
+ gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (self->multi_press_gesture),
+ 0);
+ g_signal_connect (self->multi_press_gesture, "pressed",
+ G_CALLBACK (on_button_press_event), self);
+
+ longpress_gesture = gtk_gesture_long_press_new (GTK_WIDGET (self->view_ui));
+ gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (longpress_gesture),
+ GTK_PHASE_CAPTURE);
+ gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (longpress_gesture),
+ TRUE);
+ g_signal_connect (longpress_gesture, "pressed",
+ (GCallback) on_longpress_gesture_pressed_callback,
+ self);
+
+ gtk_container_add (GTK_CONTAINER (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);
+
+ gtk_widget_show_all (GTK_WIDGET (self));
+
+ view_action_group = nautilus_files_view_get_action_group (NAUTILUS_FILES_VIEW (self));
+ g_action_map_add_action_entries (G_ACTION_MAP (view_action_group),
+ view_icon_actions,
+ G_N_ELEMENTS (view_icon_actions),
+ 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));
+}
+
+static void
+nautilus_view_icon_controller_class_init (NautilusViewIconControllerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ NautilusFilesViewClass *files_view_class = NAUTILUS_FILES_VIEW_CLASS (klass);
+
+ object_class->dispose = dispose;
+ object_class->finalize = finalize;
+ object_class->constructed = constructed;
+
+ files_view_class->add_files = real_add_files;
+ 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->click_policy_changed = real_click_policy_changed;
+ files_view_class->clear = real_clear;
+ 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->update_actions_state = real_update_actions_state;
+ files_view_class->reveal_selection = real_reveal_selection;
+ files_view_class->select_all = real_select_all;
+ files_view_class->set_selection = real_set_selection;
+ files_view_class->compare_files = real_compare_files;
+ files_view_class->sort_directories_first_changed = real_sort_directories_first_changed;
+ files_view_class->end_file_changes = real_end_file_changes;
+ files_view_class->end_loading = real_end_loading;
+ files_view_class->get_view_id = real_get_view_id;
+ files_view_class->get_first_visible_file = real_get_first_visible_file;
+ files_view_class->scroll_to_file = real_scroll_to_file;
+ files_view_class->get_icon = real_get_icon;
+ files_view_class->select_first = real_select_first;
+ files_view_class->restore_standard_zoom_level = real_restore_standard_zoom_level;
+ files_view_class->get_zoom_level_percentage = real_get_zoom_level_percentage;
+ files_view_class->is_zoom_level_default = real_is_zoom_level_default;
+ 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_view_icon_controller_init (NautilusViewIconController *self)
+{
+}
+
+NautilusViewIconController *
+nautilus_view_icon_controller_new (NautilusWindowSlot *slot)
+{
+ return g_object_new (NAUTILUS_TYPE_VIEW_ICON_CONTROLLER,
+ "window-slot", slot,
+ NULL);
+}
+
+NautilusViewModel *
+nautilus_view_icon_controller_get_model (NautilusViewIconController *self)
+{
+ g_return_val_if_fail (NAUTILUS_IS_VIEW_ICON_CONTROLLER (self), NULL);
+
+ return self->model;
+}
diff --git a/src/nautilus-view-icon-controller.h b/src/nautilus-view-icon-controller.h
new file mode 100644
index 0000000..e521624
--- /dev/null
+++ b/src/nautilus-view-icon-controller.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include "nautilus-files-view.h"
+#include "nautilus-window-slot.h"
+#include "nautilus-view-model.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_VIEW_ICON_CONTROLLER (nautilus_view_icon_controller_get_type())
+
+G_DECLARE_FINAL_TYPE (NautilusViewIconController, nautilus_view_icon_controller, NAUTILUS, VIEW_ICON_CONTROLLER, NautilusFilesView)
+
+NautilusViewIconController *nautilus_view_icon_controller_new (NautilusWindowSlot *slot);
+
+NautilusViewModel * nautilus_view_icon_controller_get_model (NautilusViewIconController *self);
+
+G_END_DECLS \ No newline at end of file
diff --git a/src/nautilus-view-icon-item-ui.c b/src/nautilus-view-icon-item-ui.c
new file mode 100644
index 0000000..837dc54
--- /dev/null
+++ b/src/nautilus-view-icon-item-ui.c
@@ -0,0 +1,287 @@
+#include "nautilus-view-icon-item-ui.h"
+#include "nautilus-view-item-model.h"
+#include "nautilus-container-max-width.h"
+#include "nautilus-file.h"
+#include "nautilus-thumbnails.h"
+
+struct _NautilusViewIconItemUi
+{
+ GtkFlowBoxChild parent_instance;
+
+ NautilusViewItemModel *model;
+
+ NautilusContainerMaxWidth *item_container;
+ GtkWidget *icon;
+ GtkLabel *label;
+};
+
+G_DEFINE_TYPE (NautilusViewIconItemUi, nautilus_view_icon_item_ui, GTK_TYPE_FLOW_BOX_CHILD)
+
+enum
+{
+ PROP_0,
+ PROP_MODEL,
+ N_PROPS
+};
+
+static GtkWidget *
+create_icon (NautilusViewIconItemUi *self)
+{
+ NautilusFileIconFlags flags;
+ g_autoptr (GdkPixbuf) icon_pixbuf = NULL;
+ GtkImage *icon;
+ GtkBox *fixed_height_box;
+ GtkStyleContext *style_context;
+ NautilusFile *file;
+ guint icon_size;
+
+ file = nautilus_view_item_model_get_file (self->model);
+ icon_size = nautilus_view_item_model_get_icon_size (self->model);
+ flags = NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS |
+ NAUTILUS_FILE_ICON_FLAGS_FORCE_THUMBNAIL_SIZE |
+ NAUTILUS_FILE_ICON_FLAGS_USE_EMBLEMS |
+ NAUTILUS_FILE_ICON_FLAGS_USE_ONE_EMBLEM;
+
+ icon_pixbuf = nautilus_file_get_icon_pixbuf (file, icon_size,
+ TRUE, 1, flags);
+ icon = GTK_IMAGE (gtk_image_new_from_pixbuf (icon_pixbuf));
+ gtk_widget_set_hexpand (GTK_WIDGET (icon), TRUE);
+ gtk_widget_set_vexpand (GTK_WIDGET (icon), TRUE);
+ gtk_widget_set_valign (GTK_WIDGET (icon), GTK_ALIGN_CENTER);
+ gtk_widget_set_halign (GTK_WIDGET (icon), GTK_ALIGN_CENTER);
+
+ fixed_height_box = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 0));
+ gtk_widget_set_valign (GTK_WIDGET (fixed_height_box), GTK_ALIGN_CENTER);
+ gtk_widget_set_halign (GTK_WIDGET (fixed_height_box), GTK_ALIGN_CENTER);
+ gtk_widget_set_size_request (GTK_WIDGET (fixed_height_box), icon_size, icon_size);
+
+ if (nautilus_can_thumbnail (file) &&
+ nautilus_file_should_show_thumbnail (file))
+ {
+ style_context = gtk_widget_get_style_context (GTK_WIDGET (fixed_height_box));
+ gtk_style_context_add_class (style_context, "icon-background");
+ }
+
+ gtk_box_pack_start (fixed_height_box, GTK_WIDGET (icon), FALSE, FALSE, 0);
+
+ return GTK_WIDGET (fixed_height_box);
+}
+
+static void
+update_icon (NautilusViewIconItemUi *self)
+{
+ GtkBox *box;
+ guint icon_size;
+
+ icon_size = nautilus_view_item_model_get_icon_size (self->model);
+ nautilus_container_max_width_set_max_width (NAUTILUS_CONTAINER_MAX_WIDTH (self->item_container),
+ icon_size);
+ box = GTK_BOX (gtk_bin_get_child (GTK_BIN (self->item_container)));
+ if (self->icon)
+ {
+ gtk_container_remove (GTK_CONTAINER (box), GTK_WIDGET (self->icon));
+ }
+ self->icon = create_icon (self);
+ gtk_widget_show_all (GTK_WIDGET (self->icon));
+ gtk_box_pack_start (box, GTK_WIDGET (self->icon), FALSE, FALSE, 0);
+}
+
+static void
+on_view_item_file_changed (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ NautilusViewIconItemUi *self = NAUTILUS_VIEW_ICON_ITEM_UI (user_data);
+ NautilusFile *file;
+
+ file = nautilus_view_item_model_get_file (self->model);
+
+ if (self->icon)
+ {
+ update_icon (self);
+ }
+
+ if (self->label)
+ {
+ gtk_label_set_text (self->label,
+ nautilus_file_get_display_name (file));
+ }
+}
+
+static void
+on_view_item_size_changed (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ NautilusViewIconItemUi *self = NAUTILUS_VIEW_ICON_ITEM_UI (user_data);
+
+ if (self->icon)
+ {
+ update_icon (self);
+ }
+}
+
+static void
+constructed (GObject *object)
+{
+ NautilusViewIconItemUi *self = NAUTILUS_VIEW_ICON_ITEM_UI (object);
+ GtkBox *container;
+ GtkBox *item_selection_background;
+ GtkLabel *label;
+ GtkStyleContext *style_context;
+ NautilusFile *file;
+ guint icon_size;
+
+ G_OBJECT_CLASS (nautilus_view_icon_item_ui_parent_class)->constructed (object);
+
+ file = nautilus_view_item_model_get_file (self->model);
+ icon_size = nautilus_view_item_model_get_icon_size (self->model);
+ container = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 0));
+ /* This container is for having a constant selection background, instead of
+ * the dinamically sized one of the GtkFlowBox
+ */
+ item_selection_background = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 0));
+ gtk_widget_set_halign (GTK_WIDGET (item_selection_background), GTK_ALIGN_CENTER);
+ gtk_widget_set_valign (GTK_WIDGET (item_selection_background), GTK_ALIGN_START);
+ self->item_container = nautilus_container_max_width_new ();
+ self->icon = create_icon (self);
+ gtk_box_pack_start (container, GTK_WIDGET (self->icon), FALSE, FALSE, 0);
+
+ label = GTK_LABEL (gtk_label_new (nautilus_file_get_display_name (file)));
+ gtk_widget_show (GTK_WIDGET (label));
+
+#if PANGO_VERSION_CHECK (1, 44, 4)
+ {
+ PangoAttrList *attr_list = pango_attr_list_new ();
+ pango_attr_list_insert (attr_list, pango_attr_insert_hyphens_new (FALSE));
+ gtk_label_set_attributes (label, attr_list);
+ pango_attr_list_unref (attr_list);
+ }
+#endif
+
+ gtk_label_set_ellipsize (label, PANGO_ELLIPSIZE_MIDDLE);
+ gtk_label_set_line_wrap (label, TRUE);
+ gtk_label_set_line_wrap_mode (label, PANGO_WRAP_WORD_CHAR);
+ gtk_label_set_lines (label, 3);
+ gtk_label_set_justify (label, GTK_JUSTIFY_CENTER);
+ gtk_widget_set_valign (GTK_WIDGET (label), GTK_ALIGN_START);
+ gtk_box_pack_end (container, GTK_WIDGET (label), TRUE, TRUE, 0);
+
+ style_context = gtk_widget_get_style_context (GTK_WIDGET (item_selection_background));
+ gtk_style_context_add_class (style_context, "icon-item-background");
+
+ gtk_widget_set_valign (GTK_WIDGET (container), GTK_ALIGN_START);
+ gtk_widget_set_halign (GTK_WIDGET (container), GTK_ALIGN_CENTER);
+
+ gtk_container_add (GTK_CONTAINER (self->item_container),
+ GTK_WIDGET (container));
+ nautilus_container_max_width_set_max_width (NAUTILUS_CONTAINER_MAX_WIDTH (self->item_container),
+ icon_size);
+
+ gtk_container_add (GTK_CONTAINER (item_selection_background),
+ GTK_WIDGET (self->item_container));
+ gtk_container_add (GTK_CONTAINER (self),
+ GTK_WIDGET (item_selection_background));
+ gtk_widget_show_all (GTK_WIDGET (self));
+
+ g_signal_connect (self->model, "notify::icon-size",
+ (GCallback) on_view_item_size_changed, self);
+ g_signal_connect (self->model, "notify::file",
+ (GCallback) on_view_item_file_changed, self);
+}
+
+static void
+finalize (GObject *object)
+{
+ NautilusViewIconItemUi *self = (NautilusViewIconItemUi *) object;
+
+ g_signal_handlers_disconnect_by_data (self->model, self);
+ G_OBJECT_CLASS (nautilus_view_icon_item_ui_parent_class)->finalize (object);
+}
+
+static void
+get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusViewIconItemUi *self = NAUTILUS_VIEW_ICON_ITEM_UI (object);
+
+ switch (prop_id)
+ {
+ case PROP_MODEL:
+ {
+ g_value_set_object (value, self->model);
+ }
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+set_model (NautilusViewIconItemUi *self,
+ NautilusViewItemModel *model)
+{
+ self->model = g_object_ref (model);
+}
+
+static void
+set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusViewIconItemUi *self = NAUTILUS_VIEW_ICON_ITEM_UI (object);
+
+ switch (prop_id)
+ {
+ case PROP_MODEL:
+ {
+ set_model (self, g_value_get_object (value));
+ }
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+nautilus_view_icon_item_ui_class_init (NautilusViewIconItemUiClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = finalize;
+ object_class->get_property = get_property;
+ object_class->set_property = set_property;
+ object_class->constructed = constructed;
+
+ g_object_class_install_property (object_class,
+ PROP_MODEL,
+ g_param_spec_object ("model",
+ "Item model",
+ "The item model that this UI reprensents",
+ NAUTILUS_TYPE_VIEW_ITEM_MODEL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+nautilus_view_icon_item_ui_init (NautilusViewIconItemUi *self)
+{
+}
+
+NautilusViewIconItemUi *
+nautilus_view_icon_item_ui_new (NautilusViewItemModel *model)
+{
+ return g_object_new (NAUTILUS_TYPE_VIEW_ICON_ITEM_UI,
+ "model", model,
+ NULL);
+}
+
+NautilusViewItemModel *
+nautilus_view_icon_item_ui_get_model (NautilusViewIconItemUi *self)
+{
+ return self->model;
+}
diff --git a/src/nautilus-view-icon-item-ui.h b/src/nautilus-view-icon-item-ui.h
new file mode 100644
index 0000000..55b09b1
--- /dev/null
+++ b/src/nautilus-view-icon-item-ui.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include "nautilus-view-item-model.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_VIEW_ICON_ITEM_UI (nautilus_view_icon_item_ui_get_type())
+
+G_DECLARE_FINAL_TYPE (NautilusViewIconItemUi, nautilus_view_icon_item_ui, NAUTILUS, VIEW_ICON_ITEM_UI, GtkFlowBoxChild)
+
+NautilusViewIconItemUi * nautilus_view_icon_item_ui_new (NautilusViewItemModel *item_model);
+
+NautilusViewItemModel * nautilus_view_icon_item_ui_get_model (NautilusViewIconItemUi *self);
+
+G_END_DECLS
diff --git a/src/nautilus-view-icon-ui.c b/src/nautilus-view-icon-ui.c
new file mode 100644
index 0000000..04f7bd4
--- /dev/null
+++ b/src/nautilus-view-icon-ui.c
@@ -0,0 +1,261 @@
+/* nautilus-view-icon-ui.c
+ *
+ * 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 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 <glib.h>
+
+#include "nautilus-view-icon-ui.h"
+#include "nautilus-view-icon-item-ui.h"
+#include "nautilus-view-icon-controller.h"
+#include "nautilus-files-view.h"
+#include "nautilus-file.h"
+#include "nautilus-directory.h"
+#include "nautilus-global-preferences.h"
+
+struct _NautilusViewIconUi
+{
+ GtkFlowBox parent_instance;
+
+ NautilusViewIconController *controller;
+};
+
+G_DEFINE_TYPE (NautilusViewIconUi, nautilus_view_icon_ui, GTK_TYPE_FLOW_BOX)
+
+enum
+{
+ PROP_0,
+ PROP_CONTROLLER,
+ N_PROPS
+};
+
+static void
+set_controller (NautilusViewIconUi *self,
+ NautilusViewIconController *controller)
+{
+ self->controller = controller;
+
+ g_object_notify (G_OBJECT (self), "controller");
+}
+
+static void
+get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusViewIconUi *self = NAUTILUS_VIEW_ICON_UI (object);
+
+ switch (prop_id)
+ {
+ case PROP_CONTROLLER:
+ {
+ g_value_set_object (value, self->controller);
+ }
+ 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)
+{
+ NautilusViewIconUi *self = NAUTILUS_VIEW_ICON_UI (object);
+
+ switch (prop_id)
+ {
+ case PROP_CONTROLLER:
+ {
+ set_controller (self, g_value_get_object (value));
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ }
+}
+
+void
+nautilus_view_icon_ui_set_selection (NautilusViewIconUi *self,
+ GQueue *selection)
+{
+ NautilusViewItemModel *item_model;
+ NautilusViewModel *model;
+ GListStore *gmodel;
+ gint i = 0;
+
+ model = nautilus_view_icon_controller_get_model (self->controller);
+ gmodel = nautilus_view_model_get_g_model (model);
+ while ((item_model = NAUTILUS_VIEW_ITEM_MODEL (g_list_model_get_item (G_LIST_MODEL (gmodel), i))))
+ {
+ GtkWidget *item_ui;
+
+ item_ui = nautilus_view_item_model_get_item_ui (item_model);
+ if (g_queue_find (selection, item_model) != NULL)
+ {
+ gtk_flow_box_select_child (GTK_FLOW_BOX (self),
+ GTK_FLOW_BOX_CHILD (item_ui));
+ }
+ else
+ {
+ gtk_flow_box_unselect_child (GTK_FLOW_BOX (self),
+ GTK_FLOW_BOX_CHILD (item_ui));
+ }
+
+ i++;
+ }
+}
+
+
+static GtkWidget *
+create_widget_func (gpointer item,
+ gpointer user_data)
+{
+ NautilusViewItemModel *item_model = NAUTILUS_VIEW_ITEM_MODEL (item);
+ NautilusViewIconItemUi *child;
+
+ child = nautilus_view_icon_item_ui_new (item_model);
+ nautilus_view_item_model_set_item_ui (item_model, GTK_WIDGET (child));
+ gtk_widget_show (GTK_WIDGET (child));
+
+ return GTK_WIDGET (child);
+}
+
+static void
+on_child_activated (GtkFlowBox *flow_box,
+ GtkFlowBoxChild *child,
+ gpointer user_data)
+{
+ NautilusViewIconUi *self = NAUTILUS_VIEW_ICON_UI (user_data);
+ NautilusViewItemModel *item_model;
+ NautilusFile *file;
+ g_autoptr (GList) list = NULL;
+ GdkEvent *event;
+ guint keyval;
+ gboolean is_preview = FALSE;
+
+ item_model = nautilus_view_icon_item_ui_get_model (NAUTILUS_VIEW_ICON_ITEM_UI (child));
+ file = nautilus_view_item_model_get_file (item_model);
+ list = g_list_append (list, file);
+
+ event = gtk_get_current_event ();
+ if (event && gdk_event_get_keyval (event, &keyval))
+ {
+ if (keyval == GDK_KEY_space)
+ {
+ is_preview = TRUE;
+ }
+ }
+
+ if (is_preview)
+ {
+ nautilus_files_view_preview_files (NAUTILUS_FILES_VIEW (self->controller), list, NULL);
+ }
+ else
+ {
+ nautilus_files_view_activate_files (NAUTILUS_FILES_VIEW (self->controller), list, 0, TRUE);
+ }
+
+ g_clear_pointer (&event, gdk_event_free);
+}
+
+static void
+on_ui_selected_children_changed (GtkFlowBox *box,
+ gpointer user_data)
+{
+ NautilusViewIconUi *self;
+
+ self = NAUTILUS_VIEW_ICON_UI (user_data);
+ nautilus_files_view_notify_selection_changed (NAUTILUS_FILES_VIEW (self->controller));
+}
+
+static void
+finalize (GObject *object)
+{
+ G_OBJECT_CLASS (nautilus_view_icon_ui_parent_class)->finalize (object);
+}
+
+static void
+constructed (GObject *object)
+{
+ NautilusViewIconUi *self = NAUTILUS_VIEW_ICON_UI (object);
+ NautilusViewModel *model;
+ GListStore *gmodel;
+
+ G_OBJECT_CLASS (nautilus_view_icon_ui_parent_class)->constructed (object);
+
+ gtk_flow_box_set_activate_on_single_click (GTK_FLOW_BOX (self), FALSE);
+ gtk_flow_box_set_max_children_per_line (GTK_FLOW_BOX (self), 20);
+ gtk_flow_box_set_selection_mode (GTK_FLOW_BOX (self), GTK_SELECTION_MULTIPLE);
+ gtk_flow_box_set_homogeneous (GTK_FLOW_BOX (self), FALSE);
+ gtk_widget_set_can_focus (GTK_WIDGET (self), TRUE);
+ gtk_widget_set_valign (GTK_WIDGET (self), GTK_ALIGN_START);
+ gtk_widget_set_margin_top (GTK_WIDGET (self), 10);
+ gtk_widget_set_margin_start (GTK_WIDGET (self), 10);
+ gtk_widget_set_margin_bottom (GTK_WIDGET (self), 10);
+ gtk_widget_set_margin_end (GTK_WIDGET (self), 10);
+
+ model = nautilus_view_icon_controller_get_model (self->controller);
+ gmodel = nautilus_view_model_get_g_model (model);
+ gtk_flow_box_bind_model (GTK_FLOW_BOX (self),
+ G_LIST_MODEL (gmodel),
+ create_widget_func, self, NULL);
+
+ g_signal_connect (self, "child-activated", (GCallback) on_child_activated, self);
+ g_signal_connect (self, "selected-children-changed", (GCallback) on_ui_selected_children_changed, self);
+}
+
+static void
+nautilus_view_icon_ui_class_init (NautilusViewIconUiClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = finalize;
+ object_class->set_property = set_property;
+ object_class->get_property = get_property;
+ object_class->constructed = constructed;
+
+ g_object_class_install_property (object_class,
+ PROP_CONTROLLER,
+ g_param_spec_object ("controller",
+ "Controller",
+ "The controller of the view",
+ NAUTILUS_TYPE_VIEW_ICON_CONTROLLER,
+ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+nautilus_view_icon_ui_init (NautilusViewIconUi *self)
+{
+}
+
+NautilusViewIconUi *
+nautilus_view_icon_ui_new (NautilusViewIconController *controller)
+{
+ return g_object_new (NAUTILUS_TYPE_VIEW_ICON_UI,
+ "controller", controller,
+ NULL);
+}
diff --git a/src/nautilus-view-icon-ui.h b/src/nautilus-view-icon-ui.h
new file mode 100644
index 0000000..4e1871d
--- /dev/null
+++ b/src/nautilus-view-icon-ui.h
@@ -0,0 +1,38 @@
+/* nautilus-view-icon-ui.h
+ *
+ * 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 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-view-icon-controller.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_VIEW_ICON_UI (nautilus_view_icon_ui_get_type())
+
+G_DECLARE_FINAL_TYPE (NautilusViewIconUi, nautilus_view_icon_ui, NAUTILUS, VIEW_ICON_UI, GtkFlowBox)
+
+NautilusViewIconUi * nautilus_view_icon_ui_new (NautilusViewIconController *controller);
+/* TODO: this should become the "nautilus_view_set_selection" once we have a proper
+ * MVC also in the nautilus-view level. */
+void nautilus_view_icon_ui_set_selection (NautilusViewIconUi *self,
+ GQueue *selection);
+
+G_END_DECLS \ No newline at end of file
diff --git a/src/nautilus-view-item-model.c b/src/nautilus-view-item-model.c
new file mode 100644
index 0000000..e50f2d8
--- /dev/null
+++ b/src/nautilus-view-item-model.c
@@ -0,0 +1,207 @@
+#include "nautilus-view-item-model.h"
+#include "nautilus-file.h"
+
+struct _NautilusViewItemModel
+{
+ GObject parent_instance;
+ guint icon_size;
+ NautilusFile *file;
+ GtkLabel *label;
+ GtkWidget *item_ui;
+};
+
+G_DEFINE_TYPE (NautilusViewItemModel, nautilus_view_item_model, G_TYPE_OBJECT)
+
+enum
+{
+ PROP_0,
+ PROP_FILE,
+ PROP_ICON_SIZE,
+ PROP_ITEM_UI,
+ N_PROPS
+};
+
+static void
+nautilus_view_item_model_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (nautilus_view_item_model_parent_class)->finalize (object);
+}
+
+static void
+nautilus_view_item_model_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusViewItemModel *self = NAUTILUS_VIEW_ITEM_MODEL (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_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_model_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusViewItemModel *self = NAUTILUS_VIEW_ITEM_MODEL (object);
+
+ switch (prop_id)
+ {
+ case PROP_FILE:
+ {
+ nautilus_view_item_model_set_file (self, g_value_get_object (value));
+ }
+ break;
+
+ case PROP_ICON_SIZE:
+ {
+ nautilus_view_item_model_set_icon_size (self, g_value_get_int (value));
+ }
+ break;
+
+ case PROP_ITEM_UI:
+ {
+ nautilus_view_item_model_set_item_ui (self, g_value_get_object (value));
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ }
+}
+
+static void
+nautilus_view_item_model_init (NautilusViewItemModel *self)
+{
+}
+
+static void
+nautilus_view_item_model_class_init (NautilusViewItemModelClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = nautilus_view_item_model_finalize;
+ object_class->get_property = nautilus_view_item_model_get_property;
+ object_class->set_property = nautilus_view_item_model_set_property;
+
+ g_object_class_install_property (object_class,
+ PROP_ICON_SIZE,
+ g_param_spec_int ("icon-size",
+ "Icon size",
+ "The size in pixels of the icon",
+ NAUTILUS_CANVAS_ICON_SIZE_SMALL,
+ NAUTILUS_CANVAS_ICON_SIZE_LARGEST,
+ NAUTILUS_CANVAS_ICON_SIZE_LARGE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+ g_object_class_install_property (object_class,
+ PROP_FILE,
+ g_param_spec_object ("file",
+ "File",
+ "The file the icon item represents",
+ NAUTILUS_TYPE_FILE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class,
+ PROP_ITEM_UI,
+ g_param_spec_object ("item-ui",
+ "Item ui",
+ "The UI that reprensents the item model",
+ GTK_TYPE_WIDGET,
+ G_PARAM_READWRITE));
+}
+
+NautilusViewItemModel *
+nautilus_view_item_model_new (NautilusFile *file,
+ guint icon_size)
+{
+ return g_object_new (NAUTILUS_TYPE_VIEW_ITEM_MODEL,
+ "file", file,
+ "icon-size", icon_size,
+ NULL);
+}
+
+guint
+nautilus_view_item_model_get_icon_size (NautilusViewItemModel *self)
+{
+ g_return_val_if_fail (NAUTILUS_IS_VIEW_ITEM_MODEL (self), -1);
+
+ return self->icon_size;
+}
+
+void
+nautilus_view_item_model_set_icon_size (NautilusViewItemModel *self,
+ guint icon_size)
+{
+ g_return_if_fail (NAUTILUS_IS_VIEW_ITEM_MODEL (self));
+
+ self->icon_size = icon_size;
+
+ g_object_notify (G_OBJECT (self), "icon-size");
+}
+
+NautilusFile *
+nautilus_view_item_model_get_file (NautilusViewItemModel *self)
+{
+ g_return_val_if_fail (NAUTILUS_IS_VIEW_ITEM_MODEL (self), NULL);
+
+ return self->file;
+}
+
+void
+nautilus_view_item_model_set_file (NautilusViewItemModel *self,
+ NautilusFile *file)
+{
+ g_return_if_fail (NAUTILUS_IS_VIEW_ITEM_MODEL (self));
+
+ g_clear_object (&self->file);
+ self->file = g_object_ref (file);
+
+ g_object_notify (G_OBJECT (self), "file");
+}
+
+GtkWidget *
+nautilus_view_item_model_get_item_ui (NautilusViewItemModel *self)
+{
+ g_return_val_if_fail (NAUTILUS_IS_VIEW_ITEM_MODEL (self), NULL);
+
+ return self->item_ui;
+}
+
+void
+nautilus_view_item_model_set_item_ui (NautilusViewItemModel *self,
+ GtkWidget *item_ui)
+{
+ g_return_if_fail (NAUTILUS_IS_VIEW_ITEM_MODEL (self));
+
+ g_clear_object (&self->item_ui);
+ self->item_ui = g_object_ref (item_ui);
+
+ g_object_notify (G_OBJECT (self), "item-ui");
+}
diff --git a/src/nautilus-view-item-model.h b/src/nautilus-view-item-model.h
new file mode 100644
index 0000000..7529901
--- /dev/null
+++ b/src/nautilus-view-item-model.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include "nautilus-file.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_VIEW_ITEM_MODEL (nautilus_view_item_model_get_type())
+
+G_DECLARE_FINAL_TYPE (NautilusViewItemModel, nautilus_view_item_model, NAUTILUS, VIEW_ITEM_MODEL, GObject)
+
+NautilusViewItemModel * nautilus_view_item_model_new (NautilusFile *file,
+ guint icon_size);
+
+void nautilus_view_item_model_set_icon_size (NautilusViewItemModel *self,
+ guint icon_size);
+
+guint nautilus_view_item_model_get_icon_size (NautilusViewItemModel *self);
+
+void nautilus_view_item_model_set_file (NautilusViewItemModel *self,
+ NautilusFile *file);
+
+NautilusFile * nautilus_view_item_model_get_file (NautilusViewItemModel *self);
+
+void nautilus_view_item_model_set_item_ui (NautilusViewItemModel *self,
+ GtkWidget *item_ui);
+
+GtkWidget * nautilus_view_item_model_get_item_ui (NautilusViewItemModel *self);
+
+G_END_DECLS \ No newline at end of file
diff --git a/src/nautilus-view-model.c b/src/nautilus-view-model.c
new file mode 100644
index 0000000..12db63a
--- /dev/null
+++ b/src/nautilus-view-model.c
@@ -0,0 +1,286 @@
+#include "nautilus-view-model.h"
+#include "nautilus-view-item-model.h"
+#include "nautilus-global-preferences.h"
+
+struct _NautilusViewModel
+{
+ GObject parent_instance;
+
+ GHashTable *map_files_to_model;
+ GListStore *internal_model;
+ NautilusViewModelSortData *sort_data;
+};
+
+G_DEFINE_TYPE (NautilusViewModel, nautilus_view_model, G_TYPE_OBJECT)
+
+enum
+{
+ PROP_0,
+ PROP_SORT_TYPE,
+ PROP_G_MODEL,
+ N_PROPS
+};
+
+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);
+ if (self->sort_data)
+ {
+ g_free (self->sort_data);
+ }
+ g_object_unref (self->internal_model);
+}
+
+static void
+get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusViewModel *self = NAUTILUS_VIEW_MODEL (object);
+
+ switch (prop_id)
+ {
+ case PROP_SORT_TYPE:
+ {
+ g_value_set_object (value, self->sort_data);
+ }
+ break;
+
+ case PROP_G_MODEL:
+ {
+ g_value_set_object (value, self->internal_model);
+ }
+ 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_SORT_TYPE:
+ {
+ nautilus_view_model_set_sort_type (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_MODEL);
+ self->map_files_to_model = g_hash_table_new (NULL, NULL);
+}
+
+static void
+nautilus_view_model_class_init (NautilusViewModelClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = finalize;
+ object_class->get_property = get_property;
+ object_class->set_property = set_property;
+ object_class->constructed = constructed;
+}
+
+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);
+ NautilusFile *file_a;
+ NautilusFile *file_b;
+
+ file_a = nautilus_view_item_model_get_file (NAUTILUS_VIEW_ITEM_MODEL ((gpointer) a));
+ file_b = nautilus_view_item_model_get_file (NAUTILUS_VIEW_ITEM_MODEL ((gpointer) b));
+
+ return nautilus_file_compare_for_sort (file_a, file_b,
+ self->sort_data->sort_type,
+ self->sort_data->directories_first,
+ self->sort_data->reversed);
+}
+
+NautilusViewModel *
+nautilus_view_model_new ()
+{
+ return g_object_new (NAUTILUS_TYPE_VIEW_MODEL, NULL);
+}
+
+void
+nautilus_view_model_set_sort_type (NautilusViewModel *self,
+ NautilusViewModelSortData *sort_data)
+{
+ if (self->sort_data)
+ {
+ g_free (self->sort_data);
+ }
+
+ self->sort_data = g_new (NautilusViewModelSortData, 1);
+ self->sort_data->sort_type = sort_data->sort_type;
+ self->sort_data->reversed = sort_data->reversed;
+ self->sort_data->directories_first = sort_data->directories_first;
+
+ g_list_store_sort (self->internal_model, compare_data_func, self);
+}
+
+NautilusViewModelSortData *
+nautilus_view_model_get_sort_type (NautilusViewModel *self)
+{
+ return self->sort_data;
+}
+
+GListStore *
+nautilus_view_model_get_g_model (NautilusViewModel *self)
+{
+ return self->internal_model;
+}
+
+GQueue *
+nautilus_view_model_get_items_from_files (NautilusViewModel *self,
+ GQueue *files)
+{
+ GList *l;
+ NautilusViewItemModel *item_model;
+ GQueue *item_models;
+
+ item_models = g_queue_new ();
+ for (l = g_queue_peek_head_link (files); l != NULL; l = l->next)
+ {
+ NautilusFile *file1;
+ gint i = 0;
+
+ file1 = NAUTILUS_FILE (l->data);
+ while ((item_model = g_list_model_get_item (G_LIST_MODEL (self->internal_model), i)))
+ {
+ NautilusFile *file2;
+ g_autofree gchar *file1_uri = NULL;
+ g_autofree gchar *file2_uri = NULL;
+
+ file2 = nautilus_view_item_model_get_file (item_model);
+ 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 (item_models, item_model);
+ break;
+ }
+
+ i++;
+ }
+ }
+
+ return item_models;
+}
+
+NautilusViewItemModel *
+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,
+ NautilusViewItemModel *item)
+{
+ NautilusViewItemModel *item_model;
+ gint i;
+
+ i = 0;
+ item_model = NULL;
+ while ((item_model = g_list_model_get_item (G_LIST_MODEL (self->internal_model), i)))
+ {
+ if (item_model == item)
+ {
+ break;
+ }
+
+ i++;
+ }
+
+ if (item_model != NULL)
+ {
+ NautilusFile *file;
+
+ file = nautilus_view_item_model_get_file (item_model);
+ 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,
+ NautilusViewItemModel *item)
+{
+ g_hash_table_insert (self->map_files_to_model,
+ nautilus_view_item_model_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;
+
+ array = g_malloc_n (g_queue_get_length (items),
+ sizeof (NautilusViewItemModel *));
+
+ 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_model_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);
+}
diff --git a/src/nautilus-view-model.h b/src/nautilus-view-model.h
new file mode 100644
index 0000000..4c16e82
--- /dev/null
+++ b/src/nautilus-view-model.h
@@ -0,0 +1,40 @@
+#pragma once
+
+#include <glib.h>
+#include "nautilus-file.h"
+#include "nautilus-view-item-model.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)
+
+typedef struct
+{
+ NautilusFileSortType sort_type;
+ gboolean reversed;
+ gboolean directories_first;
+} NautilusViewModelSortData;
+
+NautilusViewModel * nautilus_view_model_new (void);
+
+void nautilus_view_model_set_sort_type (NautilusViewModel *self,
+ NautilusViewModelSortData *sort_data);
+NautilusViewModelSortData * nautilus_view_model_get_sort_type (NautilusViewModel *self);
+GListStore * nautilus_view_model_get_g_model (NautilusViewModel *self);
+NautilusViewItemModel * 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,
+ NautilusViewItemModel *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,
+ NautilusViewItemModel *item);
+void nautilus_view_model_add_items (NautilusViewModel *self,
+ GQueue *items);
+
+G_END_DECLS
diff --git a/src/nautilus-view.c b/src/nautilus-view.c
new file mode 100644
index 0000000..3efeeca
--- /dev/null
+++ b/src/nautilus-view.c
@@ -0,0 +1,373 @@
+/* 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:
+ * @view: a #NautilusView
+ *
+ * Retrieves the #GIcon that represents @view.
+ *
+ * Returns: (transfer full): a #GIcon
+ */
+GIcon *
+nautilus_view_get_icon (guint view_id)
+{
+ if (view_id == NAUTILUS_VIEW_GRID_ID)
+ {
+ return g_themed_icon_new ("view-grid-symbolic");
+ }
+ else if (view_id == NAUTILUS_VIEW_LIST_ID)
+ {
+ return g_themed_icon_new ("view-list-symbolic");
+ }
+ else if (view_id == NAUTILUS_VIEW_OTHER_LOCATIONS_ID)
+ {
+ return g_themed_icon_new_with_default_fallbacks ("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 _("Show grid");
+ }
+ else if (view_id == NAUTILUS_VIEW_LIST_ID)
+ {
+ return _("Show list");
+ }
+ else if (view_id == NAUTILUS_VIEW_OTHER_LOCATIONS_ID)
+ {
+ return _("Show List");
+ }
+ 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..deeb4bb
--- /dev/null
+++ b/src/nautilus-view.h
@@ -0,0 +1,123 @@
+/* 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"
+
+enum
+{
+ NAUTILUS_VIEW_GRID_ID,
+ NAUTILUS_VIEW_LIST_ID,
+ NAUTILUS_VIEW_EMPTY_ID,
+ NAUTILUS_VIEW_OTHER_LOCATIONS_ID,
+ NAUTILUS_VIEW_INVALID_ID,
+};
+
+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);
+};
+
+GIcon * nautilus_view_get_icon (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..96c5d9c
--- /dev/null
+++ b/src/nautilus-window-slot-dnd.c
@@ -0,0 +1,568 @@
+/*
+ * 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-notebook.h"
+#include "nautilus-application.h"
+#include "nautilus-files-view-dnd.h"
+#include "nautilus-window-slot-dnd.h"
+
+typedef struct
+{
+ gboolean have_data;
+ gboolean have_valid_data;
+
+ gboolean drop_occurred;
+
+ unsigned int info;
+ union
+ {
+ GList *selection_list;
+ GList *uri_list;
+ GtkSelectionData *selection_data;
+ } data;
+
+ NautilusFile *target_file;
+ NautilusWindowSlot *target_slot;
+ GtkWidget *widget;
+
+ gboolean is_notebook;
+ guint switch_location_timer;
+} NautilusDragSlotProxyInfo;
+
+static void
+switch_tab (NautilusDragSlotProxyInfo *drag_info)
+{
+ GtkWidget *notebook, *slot;
+ gint idx, n_pages;
+
+ if (drag_info->target_slot == NULL)
+ {
+ return;
+ }
+
+ notebook = gtk_widget_get_ancestor (GTK_WIDGET (drag_info->target_slot), NAUTILUS_TYPE_NOTEBOOK);
+ n_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (notebook));
+
+ for (idx = 0; idx < n_pages; idx++)
+ {
+ slot = gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook), idx);
+ if (NAUTILUS_WINDOW_SLOT (slot) == drag_info->target_slot)
+ {
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), idx);
+ break;
+ }
+ }
+}
+
+static void
+switch_location (NautilusDragSlotProxyInfo *drag_info)
+{
+ GFile *location;
+ GtkWidget *window;
+
+ if (drag_info->target_file == NULL)
+ {
+ return;
+ }
+
+ window = gtk_widget_get_toplevel (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_WINDOW_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;
+
+ if (drag_info->is_notebook)
+ {
+ switch_tab (drag_info);
+ }
+ else
+ {
+ switch_location (drag_info);
+ }
+
+ return FALSE;
+}
+
+static void
+slot_proxy_check_switch_location_timer (NautilusDragSlotProxyInfo *drag_info,
+ GtkWidget *widget)
+{
+ GtkSettings *settings;
+ guint timeout;
+
+ if (drag_info->switch_location_timer)
+ {
+ return;
+ }
+
+ settings = gtk_widget_get_settings (widget);
+ g_object_get (settings, "gtk-timeout-expand", &timeout, NULL);
+
+ drag_info->switch_location_timer =
+ gdk_threads_add_timeout (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 gboolean
+slot_proxy_drag_motion (GtkWidget *widget,
+ GdkDragContext *context,
+ int x,
+ int y,
+ unsigned int time,
+ gpointer user_data)
+{
+ NautilusDragSlotProxyInfo *drag_info;
+ NautilusWindowSlot *target_slot;
+ GtkWidget *window;
+ GdkAtom target;
+ int action;
+ char *target_uri;
+ GFile *location;
+ gboolean valid_text_drag;
+ gboolean valid_xds_drag;
+
+ drag_info = user_data;
+
+ action = 0;
+ valid_text_drag = FALSE;
+ valid_xds_drag = FALSE;
+
+ if (gtk_drag_get_source_widget (context) == widget)
+ {
+ goto out;
+ }
+
+ window = gtk_widget_get_toplevel (widget);
+ g_assert (NAUTILUS_IS_WINDOW (window));
+
+ if (!drag_info->have_data)
+ {
+ target = gtk_drag_dest_find_target (widget, context, NULL);
+
+ if (target == GDK_NONE)
+ {
+ goto out;
+ }
+
+ gtk_drag_get_data (widget, context, target, time);
+ }
+
+ 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 (drag_info->have_data &&
+ drag_info->have_valid_data)
+ {
+ if (drag_info->info == NAUTILUS_ICON_DND_GNOME_ICON_LIST)
+ {
+ nautilus_drag_default_drop_action_for_icons (context, target_uri,
+ drag_info->data.selection_list,
+ 0,
+ &action);
+ }
+ else if (drag_info->info == NAUTILUS_ICON_DND_URI_LIST)
+ {
+ action = nautilus_drag_default_drop_action_for_uri_list (context, target_uri);
+ }
+ else if (drag_info->info == NAUTILUS_ICON_DND_TEXT)
+ {
+ valid_text_drag = TRUE;
+ }
+ else if (drag_info->info == NAUTILUS_ICON_DND_XDNDDIRECTSAVE ||
+ drag_info->info == NAUTILUS_ICON_DND_RAW)
+ {
+ valid_xds_drag = TRUE;
+ }
+ }
+
+ g_free (target_uri);
+
+out:
+ if (action != 0 || valid_text_drag || valid_xds_drag)
+ {
+ gtk_drag_highlight (widget);
+ slot_proxy_check_switch_location_timer (drag_info, widget);
+ }
+ else
+ {
+ gtk_drag_unhighlight (widget);
+ slot_proxy_remove_switch_location_timer (drag_info);
+ }
+
+ gdk_drag_status (context, action, time);
+
+ return TRUE;
+}
+
+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);
+
+ if (!drag_info->have_data)
+ {
+ goto out;
+ }
+
+ if (drag_info->info == NAUTILUS_ICON_DND_GNOME_ICON_LIST)
+ {
+ nautilus_drag_destroy_selection_list (drag_info->data.selection_list);
+ }
+ else if (drag_info->info == NAUTILUS_ICON_DND_URI_LIST)
+ {
+ g_list_free (drag_info->data.uri_list);
+ }
+ else if (drag_info->info == NAUTILUS_ICON_DND_TEXT ||
+ drag_info->info == NAUTILUS_ICON_DND_XDNDDIRECTSAVE ||
+ drag_info->info == NAUTILUS_ICON_DND_RAW)
+ {
+ if (drag_info->data.selection_data != NULL)
+ {
+ gtk_selection_data_free (drag_info->data.selection_data);
+ }
+ }
+
+out:
+ drag_info->have_data = FALSE;
+ drag_info->have_valid_data = FALSE;
+
+ drag_info->drop_occurred = FALSE;
+}
+
+static void
+slot_proxy_drag_leave (GtkWidget *widget,
+ GdkDragContext *context,
+ unsigned int time,
+ gpointer user_data)
+{
+ NautilusDragSlotProxyInfo *drag_info;
+
+ drag_info = user_data;
+
+ gtk_drag_unhighlight (widget);
+ drag_info_clear (drag_info);
+}
+
+static gboolean
+slot_proxy_drag_drop (GtkWidget *widget,
+ GdkDragContext *context,
+ int x,
+ int y,
+ unsigned int time,
+ gpointer user_data)
+{
+ GdkAtom target;
+ NautilusDragSlotProxyInfo *drag_info;
+
+ drag_info = user_data;
+ g_assert (!drag_info->have_data);
+
+ drag_info->drop_occurred = TRUE;
+
+ target = gtk_drag_dest_find_target (widget, context, NULL);
+ gtk_drag_get_data (widget, context, target, time);
+
+ return TRUE;
+}
+
+
+static void
+slot_proxy_handle_drop (GtkWidget *widget,
+ GdkDragContext *context,
+ unsigned int time,
+ NautilusDragSlotProxyInfo *drag_info)
+{
+ GtkWidget *window;
+ NautilusWindowSlot *target_slot;
+ NautilusFilesView *target_view;
+ char *target_uri;
+ GList *uri_list;
+ GFile *location;
+
+ if (!drag_info->have_data ||
+ !drag_info->have_valid_data)
+ {
+ gtk_drag_finish (context, FALSE, FALSE, time);
+ drag_info_clear (drag_info);
+ return;
+ }
+
+ window = gtk_widget_get_toplevel (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 (drag_info->info == NAUTILUS_ICON_DND_GNOME_ICON_LIST)
+ {
+ uri_list = nautilus_drag_uri_list_from_selection_list (drag_info->data.selection_list);
+ g_assert (uri_list != NULL);
+
+ nautilus_files_view_drop_proxy_received_uris (target_view,
+ uri_list,
+ target_uri,
+ gdk_drag_context_get_selected_action (context));
+ g_list_free_full (uri_list, g_free);
+ }
+ else if (drag_info->info == NAUTILUS_ICON_DND_URI_LIST)
+ {
+ nautilus_files_view_drop_proxy_received_uris (target_view,
+ drag_info->data.uri_list,
+ target_uri,
+ gdk_drag_context_get_selected_action (context));
+ }
+
+ gtk_drag_finish (context, TRUE, FALSE, time);
+ }
+ else
+ {
+ gtk_drag_finish (context, FALSE, FALSE, time);
+ }
+
+ g_free (target_uri);
+
+ drag_info_clear (drag_info);
+}
+
+static void
+slot_proxy_drag_data_received (GtkWidget *widget,
+ GdkDragContext *context,
+ int x,
+ int y,
+ GtkSelectionData *data,
+ unsigned int info,
+ unsigned int time,
+ gpointer user_data)
+{
+ NautilusDragSlotProxyInfo *drag_info;
+ char **uris;
+
+ drag_info = user_data;
+
+ g_assert (!drag_info->have_data);
+
+ drag_info->have_data = TRUE;
+ drag_info->info = info;
+
+ if (gtk_selection_data_get_length (data) < 0)
+ {
+ drag_info->have_valid_data = FALSE;
+ return;
+ }
+
+ if (info == NAUTILUS_ICON_DND_GNOME_ICON_LIST)
+ {
+ drag_info->data.selection_list = nautilus_drag_build_selection_list (data);
+
+ drag_info->have_valid_data = drag_info->data.selection_list != NULL;
+ }
+ else if (info == NAUTILUS_ICON_DND_URI_LIST)
+ {
+ uris = gtk_selection_data_get_uris (data);
+ drag_info->data.uri_list = nautilus_drag_uri_list_from_array ((const char **) uris);
+ g_strfreev (uris);
+
+ drag_info->have_valid_data = drag_info->data.uri_list != NULL;
+ }
+ else if (info == NAUTILUS_ICON_DND_TEXT ||
+ info == NAUTILUS_ICON_DND_XDNDDIRECTSAVE ||
+ info == NAUTILUS_ICON_DND_RAW)
+ {
+ drag_info->data.selection_data = gtk_selection_data_copy (data);
+ drag_info->have_valid_data = drag_info->data.selection_data != NULL;
+ }
+
+ if (drag_info->drop_occurred)
+ {
+ slot_proxy_handle_drop (widget, context, time, drag_info);
+ }
+}
+
+void
+nautilus_drag_slot_proxy_init (GtkWidget *widget,
+ NautilusFile *target_file,
+ NautilusWindowSlot *target_slot)
+{
+ NautilusDragSlotProxyInfo *drag_info;
+
+ const GtkTargetEntry targets[] =
+ {
+ { NAUTILUS_ICON_DND_GNOME_ICON_LIST_TYPE, 0, NAUTILUS_ICON_DND_GNOME_ICON_LIST },
+ { NAUTILUS_ICON_DND_XDNDDIRECTSAVE_TYPE, 0, NAUTILUS_ICON_DND_XDNDDIRECTSAVE }, /* XDS Protocol Type */
+ { NAUTILUS_ICON_DND_RAW_TYPE, 0, NAUTILUS_ICON_DND_RAW }
+ };
+ GtkTargetList *target_list;
+
+ 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);
+
+ drag_info->is_notebook = (g_object_get_data (G_OBJECT (widget), "nautilus-notebook-tab") != NULL);
+
+ 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;
+
+ gtk_drag_dest_set (widget, 0,
+ NULL, 0,
+ GDK_ACTION_MOVE |
+ GDK_ACTION_COPY |
+ GDK_ACTION_LINK |
+ GDK_ACTION_ASK);
+
+ target_list = gtk_target_list_new (targets, G_N_ELEMENTS (targets));
+ gtk_target_list_add_uri_targets (target_list, NAUTILUS_ICON_DND_URI_LIST);
+ gtk_target_list_add_text_targets (target_list, NAUTILUS_ICON_DND_TEXT);
+ gtk_drag_dest_set_target_list (widget, target_list);
+ gtk_target_list_unref (target_list);
+
+ g_signal_connect (widget, "drag-motion",
+ G_CALLBACK (slot_proxy_drag_motion),
+ drag_info);
+ g_signal_connect (widget, "drag-drop",
+ G_CALLBACK (slot_proxy_drag_drop),
+ drag_info);
+ g_signal_connect (widget, "drag-data-received",
+ G_CALLBACK (slot_proxy_drag_data_received),
+ drag_info);
+ g_signal_connect (widget, "drag-leave",
+ G_CALLBACK (slot_proxy_drag_leave),
+ drag_info);
+}
diff --git a/src/nautilus-window-slot-dnd.h b/src/nautilus-window-slot-dnd.h
new file mode 100644
index 0000000..2b7b05f
--- /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); \ No newline at end of file
diff --git a/src/nautilus-window-slot.c b/src/nautilus-window-slot.c
new file mode 100644
index 0000000..b5ff000
--- /dev/null
+++ b/src/nautilus-window-slot.c
@@ -0,0 +1,3769 @@
+/*
+ * 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-mime-actions.h"
+#include "nautilus-query-editor.h"
+#include "nautilus-special-location-bar.h"
+#include "nautilus-toolbar.h"
+#include "nautilus-trash-bar.h"
+#include "nautilus-trash-monitor.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-extension.h>
+#include "nautilus-ui-utilities.h"
+#include <eel/eel-vfs-extensions.h>
+
+enum
+{
+ PROP_ACTIVE = 1,
+ PROP_WINDOW,
+ PROP_ICON,
+ PROP_TOOLBAR_MENU_SECTIONS,
+ PROP_EXTENSIONS_BACKGROUND_MENU,
+ PROP_TEMPLATES_MENU,
+ PROP_LOADING,
+ PROP_SEARCHING,
+ PROP_SELECTION,
+ PROP_LOCATION,
+ PROP_TOOLTIP,
+ NUM_PROPERTIES
+};
+
+typedef struct
+{
+ 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;
+
+ /* 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;
+} NautilusWindowSlotPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (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 trash_state_changed_cb (NautilusTrashMonitor *monitor,
+ gboolean is_empty,
+ gpointer user_data);
+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)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+
+ /* We are restoring saved history to newly created slot with no history. */
+ g_warn_if_fail (priv->back_list == NULL && priv->forward_list == NULL);
+
+ priv->back_list = g_steal_pointer (&data->back_list);
+
+ priv->forward_list = g_steal_pointer (&data->forward_list);
+
+ priv->view_mode_before_search = data->view_before_search;
+
+ g_set_object (&priv->current_location_bookmark, data->current_location_bookmark);
+
+ priv->location_change_type = NAUTILUS_LOCATION_CHANGE_RELOAD;
+}
+
+NautilusNavigationState *
+nautilus_window_slot_get_navigation_state (NautilusWindowSlot *self)
+{
+ NautilusWindowSlotPrivate *priv;
+ NautilusNavigationState *data;
+ GList *back_list;
+ GList *forward_list;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+
+ if (priv->location == NULL)
+ {
+ return NULL;
+ }
+
+ back_list = g_list_copy_deep (priv->back_list,
+ (GCopyFunc) g_object_ref,
+ NULL);
+ forward_list = g_list_copy_deep (priv->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 (priv->location);
+ data->view_before_search = priv->view_mode_before_search;
+ g_set_object (&data->current_location_bookmark, priv->current_location_bookmark);
+
+ return data;
+}
+
+gboolean
+nautilus_window_slot_handles_location (NautilusWindowSlot *self,
+ GFile *location)
+{
+ return NAUTILUS_WINDOW_SLOT_CLASS (G_OBJECT_GET_CLASS (self))->handles_location (self, location);
+}
+
+static gboolean
+real_handles_location (NautilusWindowSlot *self,
+ GFile *location)
+{
+ NautilusFile *file;
+ gboolean handles_location;
+ g_autofree char *uri = NULL;
+
+ uri = g_file_get_uri (location);
+
+ file = nautilus_file_get (location);
+ handles_location = !nautilus_file_is_other_locations (file);
+ nautilus_file_unref (file);
+
+ return handles_location;
+}
+
+static NautilusView *
+nautilus_window_slot_get_view_for_location (NautilusWindowSlot *self,
+ GFile *location)
+{
+ return NAUTILUS_WINDOW_SLOT_CLASS (G_OBJECT_GET_CLASS (self))->get_view_for_location (self, location);
+}
+
+static NautilusView *
+real_get_view_for_location (NautilusWindowSlot *self,
+ GFile *location)
+{
+ NautilusWindowSlotPrivate *priv;
+ NautilusFile *file;
+ NautilusView *view;
+ guint view_id;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ file = nautilus_file_get (location);
+ view = NULL;
+ view_id = NAUTILUS_VIEW_INVALID_ID;
+
+ /* 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 (priv->view_mode_before_search == NAUTILUS_VIEW_INVALID_ID && priv->content_view)
+ {
+ priv->view_mode_before_search = nautilus_files_view_get_view_id (priv->content_view);
+ }
+ view_id = g_settings_get_enum (nautilus_preferences, NAUTILUS_PREFERENCES_SEARCH_VIEW);
+ }
+ else if (priv->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 (priv->view_mode_before_search != NAUTILUS_VIEW_INVALID_ID)
+ {
+ view_id = priv->view_mode_before_search;
+ priv->view_mode_before_search = NAUTILUS_VIEW_INVALID_ID;
+ }
+ else
+ {
+ view_id = nautilus_files_view_get_view_id (priv->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);
+ }
+
+ /* Try to reuse the current view */
+ if (nautilus_window_slot_content_view_matches (self, view_id))
+ {
+ view = priv->content_view;
+ }
+ else
+ {
+ view = NAUTILUS_VIEW (nautilus_files_view_new (view_id, self));
+ }
+
+ nautilus_file_unref (file);
+
+ return view;
+}
+
+static gboolean
+nautilus_window_slot_content_view_matches (NautilusWindowSlot *self,
+ guint id)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ if (priv->content_view == NULL)
+ {
+ return FALSE;
+ }
+
+ if (id != NAUTILUS_VIEW_INVALID_ID && NAUTILUS_IS_FILES_VIEW (priv->content_view))
+ {
+ return nautilus_files_view_get_view_id (priv->content_view) == id;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+static void
+update_search_visible (NautilusWindowSlot *self)
+{
+ NautilusWindowSlotPrivate *priv;
+ NautilusQuery *query;
+ NautilusView *view;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+
+ 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 (priv->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 (priv->pending_search_query)
+ {
+ nautilus_window_slot_search (self, g_object_ref (priv->pending_search_query));
+ }
+}
+
+static void
+nautilus_window_slot_sync_actions (NautilusWindowSlot *self)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ GAction *action;
+ GVariant *variant;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ if (!nautilus_window_slot_get_active (self))
+ {
+ return;
+ }
+
+ if (priv->content_view == NULL || priv->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 */
+ action = g_action_map_lookup_action (G_ACTION_MAP (priv->slot_action_group), "files-view-mode");
+ if (g_action_get_enabled (action))
+ {
+ variant = g_variant_new_uint32 (nautilus_files_view_get_view_id (nautilus_window_slot_get_current_view (self)));
+ g_action_change_state (action, variant);
+ }
+}
+
+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)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ if (priv->content_view != NULL)
+ {
+ if (NAUTILUS_IS_FILES_VIEW (priv->content_view))
+ {
+ nautilus_files_view_activate_selection (NAUTILUS_FILES_VIEW (priv->content_view));
+ }
+ }
+}
+
+static void
+query_editor_focus_view_callback (NautilusQueryEditor *editor,
+ NautilusWindowSlot *self)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ if (priv->content_view != NULL)
+ {
+ gtk_widget_grab_focus (GTK_WIDGET (priv->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)
+{
+ NautilusWindowSlotPrivate *priv;
+ NautilusView *view;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ view = nautilus_window_slot_get_current_view (self);
+
+ g_clear_signal_handler (&priv->qe_changed_id, priv->query_editor);
+ g_clear_signal_handler (&priv->qe_cancel_id, priv->query_editor);
+ g_clear_signal_handler (&priv->qe_activated_id, priv->query_editor);
+ g_clear_signal_handler (&priv->qe_focus_view_id, priv->query_editor);
+
+ nautilus_query_editor_set_query (priv->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 (priv->window));
+ }
+}
+
+static GFile *
+nautilus_window_slot_get_current_location (NautilusWindowSlot *self)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ if (priv->pending_location != NULL)
+ {
+ return priv->pending_location;
+ }
+
+ if (priv->location != NULL)
+ {
+ return priv->location;
+ }
+
+ return NULL;
+}
+
+static void
+show_query_editor (NautilusWindowSlot *self)
+{
+ NautilusWindowSlotPrivate *priv;
+ NautilusView *view;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ 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 (priv->query_editor, query);
+ }
+ }
+
+ gtk_widget_grab_focus (GTK_WIDGET (priv->query_editor));
+
+ if (priv->qe_changed_id == 0)
+ {
+ priv->qe_changed_id =
+ g_signal_connect (priv->query_editor, "changed",
+ G_CALLBACK (query_editor_changed_callback), self);
+ }
+ if (priv->qe_cancel_id == 0)
+ {
+ priv->qe_cancel_id =
+ g_signal_connect (priv->query_editor, "cancel",
+ G_CALLBACK (query_editor_cancel_callback), self);
+ }
+ if (priv->qe_activated_id == 0)
+ {
+ priv->qe_activated_id =
+ g_signal_connect (priv->query_editor, "activated",
+ G_CALLBACK (query_editor_activated_callback), self);
+ }
+ if (priv->qe_focus_view_id == 0)
+ {
+ priv->qe_focus_view_id =
+ g_signal_connect (priv->query_editor, "focus-view",
+ G_CALLBACK (query_editor_focus_view_callback), self);
+ }
+}
+
+static void
+nautilus_window_slot_set_search_visible (NautilusWindowSlot *self,
+ gboolean visible)
+{
+ NautilusWindowSlotPrivate *priv;
+ GAction *action;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (priv->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)
+{
+ NautilusWindowSlotPrivate *priv;
+ GAction *action;
+ GVariant *state;
+ gboolean searching;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ action = g_action_map_lookup_action (G_ACTION_MAP (priv->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)
+{
+ NautilusWindowSlotPrivate *priv;
+ NautilusView *view;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ g_clear_object (&priv->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 (priv->query_editor, query);
+ }
+ else
+ {
+ priv->pending_search_query = g_object_ref (query);
+ }
+}
+
+gboolean
+nautilus_window_slot_handle_event (NautilusWindowSlot *self,
+ GdkEvent *event)
+{
+ NautilusWindowSlotPrivate *priv;
+ gboolean retval;
+ GAction *action;
+ guint keyval;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ retval = FALSE;
+ action = g_action_map_lookup_action (G_ACTION_MAP (priv->slot_action_group),
+ "search-visible");
+
+ if (gdk_event_get_event_type (event) != GDK_KEY_PRESS)
+ {
+ return GDK_EVENT_PROPAGATE;
+ }
+
+ if (G_UNLIKELY (!gdk_event_get_keyval (event, &keyval)))
+ {
+ g_return_val_if_reached (GDK_EVENT_PROPAGATE);
+ }
+
+ if (keyval == GDK_KEY_Escape)
+ {
+ g_autoptr (GVariant) state = NULL;
+
+ state = g_action_get_state (action);
+
+ if (g_variant_get_boolean (state))
+ {
+ nautilus_window_slot_set_search_visible (self, FALSE);
+ }
+ }
+
+ /* If the action is not enabled, don't try to handle search */
+ if (g_action_get_enabled (action))
+ {
+ retval = nautilus_query_editor_handle_event (priv->query_editor, event);
+ }
+
+ if (retval)
+ {
+ nautilus_window_slot_set_search_visible (self, TRUE);
+ }
+
+ return retval;
+}
+
+static void
+remove_all_extra_location_widgets (GtkWidget *widget,
+ gpointer data)
+{
+ NautilusWindowSlotPrivate *priv;
+ NautilusWindowSlot *self = data;
+ NautilusDirectory *directory;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ directory = nautilus_directory_get (priv->location);
+ if (widget != GTK_WIDGET (priv->query_editor))
+ {
+ gtk_container_remove (GTK_CONTAINER (priv->extra_location_widgets), widget);
+ }
+
+ nautilus_directory_unref (directory);
+}
+
+static void
+nautilus_window_slot_remove_extra_location_widgets (NautilusWindowSlot *self)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ gtk_container_foreach (GTK_CONTAINER (priv->extra_location_widgets),
+ remove_all_extra_location_widgets,
+ self);
+}
+
+static void
+nautilus_window_slot_add_extra_location_widget (NautilusWindowSlot *self,
+ GtkWidget *widget)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ gtk_box_pack_start (GTK_BOX (priv->extra_location_widgets),
+ widget, FALSE, TRUE, 0);
+ gtk_widget_show (priv->extra_location_widgets);
+}
+
+static void
+nautilus_window_slot_set_searching (NautilusWindowSlot *self,
+ gboolean searching)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+
+ priv->searching = searching;
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SEARCHING]);
+}
+
+static void
+nautilus_window_slot_set_selection (NautilusWindowSlot *self,
+ GList *selection)
+{
+ NautilusWindowSlotPrivate *priv;
+ priv = nautilus_window_slot_get_instance_private (self);
+
+ priv->selection = selection;
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SELECTION]);
+}
+
+static void
+real_set_extensions_background_menu (NautilusWindowSlot *self,
+ GMenuModel *menu)
+{
+ NautilusWindowSlotPrivate *priv;
+ priv = nautilus_window_slot_get_instance_private (self);
+
+ g_set_object (&priv->extensions_background_menu, menu);
+}
+
+static void
+real_set_templates_menu (NautilusWindowSlot *self,
+ GMenuModel *menu)
+{
+ NautilusWindowSlotPrivate *priv;
+ priv = nautilus_window_slot_get_instance_private (self);
+
+ g_set_object (&priv->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)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ return priv->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)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ return priv->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);
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+
+ 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, priv->window);
+ }
+ break;
+
+ case PROP_ICON:
+ {
+ g_value_set_object (value, nautilus_window_slot_get_icon (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;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+ break;
+ }
+}
+
+gboolean
+nautilus_window_slot_get_searching (NautilusWindowSlot *self)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+
+ return priv->searching;
+}
+
+GList *
+nautilus_window_slot_get_selection (NautilusWindowSlot *self)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+
+ return priv->selection;
+}
+
+static void
+nautilus_window_slot_constructed (GObject *object)
+{
+ NautilusWindowSlotPrivate *priv;
+ NautilusWindowSlot *self = NAUTILUS_WINDOW_SLOT (object);
+ GtkWidget *extras_vbox;
+ GtkStyleContext *style_context;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ 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);
+ priv->extra_location_widgets = extras_vbox;
+ gtk_box_pack_start (GTK_BOX (self), extras_vbox, FALSE, FALSE, 0);
+ gtk_widget_show (extras_vbox);
+
+ priv->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 (priv->query_editor);
+ gtk_widget_show (GTK_WIDGET (priv->query_editor));
+
+ priv->search_info_label = GTK_LABEL (gtk_label_new (NULL));
+ priv->search_info_label_revealer = GTK_REVEALER (gtk_revealer_new ());
+
+ gtk_container_add (GTK_CONTAINER (priv->search_info_label_revealer),
+ GTK_WIDGET (priv->search_info_label));
+ gtk_container_add (GTK_CONTAINER (self),
+ GTK_WIDGET (priv->search_info_label_revealer));
+
+ gtk_widget_show (GTK_WIDGET (priv->search_info_label));
+ gtk_widget_show (GTK_WIDGET (priv->search_info_label_revealer));
+
+ style_context = gtk_widget_get_style_context (GTK_WIDGET (priv->search_info_label));
+ gtk_style_context_add_class (style_context, "search-information");
+
+ g_object_bind_property (self, "location",
+ priv->query_editor, "location",
+ G_BINDING_DEFAULT);
+
+ priv->title = g_strdup (_("Loading…"));
+}
+
+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;
+ NautilusWindowSlotPrivate *priv;
+ guint current_view_id;
+
+ self = NAUTILUS_WINDOW_SLOT (user_data);
+ priv = nautilus_window_slot_get_instance_private (self);
+ if (priv->content_view == NULL)
+ {
+ return;
+ }
+
+ current_view_id = nautilus_files_view_get_view_id (priv->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[] =
+{
+ /* 4 is NAUTILUS_VIEW_INVALID_ID */
+ { "files-view-mode", NULL, "u", "uint32 4", 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)
+{
+ NautilusWindowSlotPrivate *priv;
+ GFile *location;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+
+ if (!nautilus_window_slot_get_searching (self))
+ {
+ gtk_revealer_set_reveal_child (priv->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 (priv->search_info_label, label);
+ gtk_revealer_set_reveal_child (priv->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
+use_experimental_views_changed_callback (GSettings *settings,
+ gchar *key,
+ gpointer callback_data)
+{
+ NautilusWindowSlot *self;
+
+ self = callback_data;
+
+ if (nautilus_window_slot_content_view_matches (self, NAUTILUS_VIEW_GRID_ID))
+ {
+ /* Note that although this call does not change the view id,
+ * it changes the canvas view between new and old.
+ */
+ nautilus_window_slot_set_content_view (self, NAUTILUS_VIEW_GRID_ID);
+ }
+}
+
+static void
+nautilus_window_slot_init (NautilusWindowSlot *self)
+{
+ GApplication *app;
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ app = g_application_get_default ();
+
+ g_signal_connect (nautilus_trash_monitor_get (),
+ "trash-state-changed",
+ G_CALLBACK (trash_state_changed_cb), self);
+ g_signal_connect_object (nautilus_preferences,
+ "changed::" NAUTILUS_PREFERENCES_USE_EXPERIMENTAL_VIEWS,
+ G_CALLBACK (use_experimental_views_changed_callback), self, 0);
+
+ g_signal_connect_object (nautilus_preferences,
+ "changed::recursive-search",
+ G_CALLBACK (recursive_search_preferences_changed),
+ self, 0);
+
+ priv->slot_action_group = G_ACTION_GROUP (g_simple_action_group_new ());
+ g_action_map_add_action_entries (G_ACTION_MAP (priv->slot_action_group),
+ slot_entries,
+ G_N_ELEMENTS (slot_entries),
+ self);
+ gtk_widget_insert_action_group (GTK_WIDGET (self),
+ "slot",
+ G_ACTION_GROUP (priv->slot_action_group));
+ nautilus_application_set_accelerator (app, "slot.files-view-mode(uint32 1)", "<control>1");
+ nautilus_application_set_accelerator (app, "slot.files-view-mode(uint32 0)", "<control>2");
+ nautilus_application_set_accelerator (app, "slot.search-visible", "<control>f");
+
+ priv->view_mode_before_search = 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,
+ NautilusWindowOpenFlags flags,
+ GList *new_selection)
+{
+ NautilusWindowSlotPrivate *priv;
+ GFile *old_location;
+ g_autolist (NautilusFile) old_selection = NULL;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ old_selection = NULL;
+ old_location = nautilus_window_slot_get_location (self);
+
+ if (priv->content_view)
+ {
+ old_selection = nautilus_view_get_selection (priv->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 = !nautilus_directory_is_local (directory);
+ }
+
+ /* 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)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ /* Set current_bookmark scroll pos */
+ if (priv->current_location_bookmark != NULL &&
+ priv->content_view != NULL &&
+ NAUTILUS_IS_FILES_VIEW (priv->content_view))
+ {
+ char *current_pos;
+
+ current_pos = nautilus_files_view_get_first_visible_file (NAUTILUS_FILES_VIEW (priv->content_view));
+ nautilus_bookmark_set_scroll_pos (priv->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)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ 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);
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ /* 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 (priv->pending_location == NULL);
+
+ priv->pending_location = g_object_ref (location);
+ priv->location_change_type = type;
+ priv->location_change_distance = distance;
+ priv->tried_mount = FALSE;
+ priv->pending_selection = nautilus_file_list_copy (new_selection);
+
+ priv->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 */
+ priv->determine_view_file = nautilus_file_get (location);
+ g_assert (priv->determine_view_file != NULL);
+
+ nautilus_file_call_when_ready (priv->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)
+{
+ NautilusWindowSlotPrivate *priv;
+ GFile *old_location;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ if (priv->location &&
+ g_file_equal (location, priv->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 = priv->location;
+ priv->location = g_object_ref (location);
+
+ if (nautilus_window_slot_get_active (self))
+ {
+ nautilus_window_sync_location_widgets (priv->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)
+{
+ NautilusWindowSlotPrivate *priv;
+ GFile *new_location;
+ gboolean is_in_trash, was_in_trash;
+
+ g_assert (NAUTILUS_IS_FILE (file));
+ g_assert (NAUTILUS_IS_WINDOW_SLOT (self));
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ g_assert (file == priv->viewed_file);
+
+ if (!nautilus_file_is_not_yet_confirmed (file))
+ {
+ priv->viewed_file_seen = TRUE;
+ }
+
+ was_in_trash = priv->viewed_file_in_trash;
+
+ priv->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 (priv->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,
+ NautilusWindowOpenFlags 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)
+{
+ NautilusWindowSlotPrivate *priv;
+ NautilusFileAttributes attributes;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ if (priv->viewed_file == file)
+ {
+ return;
+ }
+
+ nautilus_file_ref (file);
+
+ if (priv->viewed_file != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (priv->viewed_file,
+ G_CALLBACK (viewed_file_changed_callback),
+ self);
+ nautilus_file_monitor_remove (priv->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 (priv->viewed_file);
+ priv->viewed_file = file;
+}
+
+typedef struct
+{
+ GCancellable *cancellable;
+ NautilusWindowSlot *slot;
+} MountNotMountedData;
+
+static void
+mount_not_mounted_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusWindowSlotPrivate *priv;
+ MountNotMountedData *data;
+ NautilusWindowSlot *self;
+ GError *error;
+ GCancellable *cancellable;
+
+ data = user_data;
+ self = data->slot;
+ priv = nautilus_window_slot_get_instance_private (self);
+ cancellable = data->cancellable;
+ g_free (data);
+
+ if (g_cancellable_is_cancelled (cancellable))
+ {
+ /* Cancelled, don't call back */
+ g_object_unref (cancellable);
+ return;
+ }
+
+ priv->mount_cancellable = NULL;
+
+ priv->determine_view_file = nautilus_file_get (priv->pending_location);
+
+ error = NULL;
+ if (!g_file_mount_enclosing_volume_finish (G_FILE (source_object), res, &error))
+ {
+ priv->mount_error = error;
+ got_file_info_for_view_selection_callback (priv->determine_view_file, self);
+ priv->mount_error = NULL;
+ g_error_free (error);
+ }
+ else
+ {
+ nautilus_file_invalidate_all_attributes (priv->determine_view_file);
+ nautilus_file_call_when_ready (priv->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;
+
+ /* 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:
+ {
+ detail_message = g_strdup (_("Unable to find the requested file. Please check the spelling and try again."));
+ }
+ 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)
+{
+ NautilusWindowSlotPrivate *priv;
+ NautilusWindow *window;
+ GMountOperation *mount_op;
+ MountNotMountedData *data;
+ GFile *location;
+ GError *error = NULL;
+ gboolean needs_mount_handling = FALSE;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ window = nautilus_window_slot_get_window (self);
+ if (priv->mount_error)
+ {
+ error = g_error_copy (priv->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 &&
+ !priv->tried_mount)
+ {
+ priv->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;
+ priv->mount_cancellable = data->cancellable;
+ g_file_mount_enclosing_volume (location, 0, mount_op, priv->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;
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ parent_file = nautilus_file_get_parent (file);
+ if ((parent_file != NULL) &&
+ nautilus_file_get_file_type (file) == G_FILE_TYPE_REGULAR)
+ {
+ g_clear_pointer (&priv->pending_selection, nautilus_file_list_free);
+ g_clear_object (&priv->pending_location);
+ g_clear_object (&priv->pending_file_to_activate);
+ g_free (priv->pending_scroll_to);
+
+ priv->pending_location = nautilus_file_get_parent_location (file);
+ if (nautilus_file_is_archive (file))
+ {
+ priv->pending_file_to_activate = nautilus_file_ref (file);
+ }
+ else
+ {
+ priv->pending_selection = g_list_prepend (NULL, nautilus_file_ref (file));
+ }
+ priv->determine_view_file = nautilus_file_ref (parent_file);
+ priv->pending_scroll_to = nautilus_file_get_uri (file);
+
+ nautilus_file_invalidate_all_attributes (priv->determine_view_file);
+ nautilus_file_call_when_ready (priv->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)
+{
+ NautilusWindowSlotPrivate *priv;
+ GError *error = NULL;
+ NautilusWindow *window;
+ NautilusWindowSlot *self;
+ NautilusFile *viewed_file;
+ NautilusView *view;
+ GFile *location;
+ NautilusApplication *app;
+
+ self = callback_data;
+ priv = nautilus_window_slot_get_instance_private (self);
+ window = nautilus_window_slot_get_window (self);
+
+ g_assert (priv->determine_view_file == file);
+ priv->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 (priv->mount_error)
+ {
+ error = g_error_copy (priv->mount_error);
+ }
+ else if (nautilus_file_get_file_info_error (file) != NULL)
+ {
+ error = g_error_copy (nautilus_file_get_file_info_error (file));
+ }
+
+ location = priv->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_widget_destroy (GTK_WIDGET (window));
+ }
+ }
+ else
+ {
+ /* Since this is a window, destroying it will also unref it. */
+ gtk_widget_destroy (GTK_WIDGET (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;
+ NautilusWindowSlotPrivate *priv;
+
+ nautilus_profile_start (NULL);
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ nautilus_window_slot_disconnect_content_view (self);
+
+ priv->new_content_view = view;
+
+ nautilus_window_slot_connect_new_content_view (self);
+
+ /* Forward search selection and state before loading the new model */
+ old_location = priv->content_view ? nautilus_view_get_location (priv->content_view) : NULL;
+
+ /* Actually load the pending location and selection: */
+ if (priv->pending_location != NULL)
+ {
+ load_new_location (self,
+ priv->pending_location,
+ priv->pending_selection,
+ priv->pending_file_to_activate,
+ FALSE,
+ TRUE);
+
+ nautilus_file_list_free (priv->pending_selection);
+ priv->pending_selection = NULL;
+ }
+ else if (old_location != NULL)
+ {
+ g_autolist (NautilusFile) selection = NULL;
+
+ selection = nautilus_view_get_selection (priv->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 (priv->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;
+ NautilusWindowSlotPrivate *priv;
+
+ g_assert (self != NULL);
+ g_assert (location != NULL);
+
+ view = NULL;
+ priv = nautilus_window_slot_get_instance_private (self);
+
+ nautilus_profile_start (NULL);
+ /* Note, these may recurse into report_load_underway */
+ if (priv->content_view != NULL && tell_current_content_view)
+ {
+ view = priv->content_view;
+ nautilus_view_set_location (priv->content_view, location);
+ }
+
+ if (priv->new_content_view != NULL && tell_new_content_view &&
+ (!tell_current_content_view ||
+ priv->new_content_view != priv->content_view))
+ {
+ view = priv->new_content_view;
+ nautilus_view_set_location (priv->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;
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ 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 (priv->pending_scroll_to);
+ priv->pending_scroll_to = NULL;
+
+ free_location_change (self);
+}
+
+static void
+free_location_change (NautilusWindowSlot *self)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ g_clear_object (&priv->pending_location);
+ g_clear_object (&priv->pending_file_to_activate);
+ nautilus_file_list_free (priv->pending_selection);
+ priv->pending_selection = NULL;
+
+ /* Don't free details->pending_scroll_to, since thats needed until
+ * the load_complete callback.
+ */
+
+ if (priv->mount_cancellable != NULL)
+ {
+ g_cancellable_cancel (priv->mount_cancellable);
+ priv->mount_cancellable = NULL;
+ }
+
+ if (priv->determine_view_file != NULL)
+ {
+ nautilus_file_cancel_call_when_ready
+ (priv->determine_view_file,
+ got_file_info_for_view_selection_callback, self);
+ priv->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;
+ NautilusWindowSlotPrivate *priv;
+
+ g_assert (self != NULL);
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ 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 (priv->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 (priv->content_view))
+ {
+ /* If there is no selection, queue a scroll to the same icon that
+ * is currently visible */
+ priv->pending_scroll_to = nautilus_files_view_get_first_visible_file (NAUTILUS_FILES_VIEW (priv->content_view));
+ }
+
+ priv->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,
+ NautilusWindowOpenFlags flags)
+{
+ GList *list;
+ GFile *location;
+ guint len;
+ NautilusBookmark *bookmark;
+ GFile *old_location;
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ list = back ? priv->back_list : priv->forward_list;
+
+ len = (guint) g_list_length (list);
+
+ /* If we can't move in the direction at all, just return. */
+ if (len == 0)
+ {
+ 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);
+
+ if (flags != 0)
+ {
+ nautilus_window_slot_open_location_full (self, location, flags, NULL);
+ }
+ else
+ {
+ char *scroll_pos;
+
+ 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);
+
+ g_free (scroll_pos);
+ }
+
+ g_object_unref (location);
+}
+
+/* reload the contents of the window */
+static void
+nautilus_window_slot_force_reload (NautilusWindowSlot *self)
+{
+ GFile *location;
+ char *current_pos;
+ NautilusWindowSlotPrivate *priv;
+ g_autolist (NautilusFile) selection = NULL;
+
+ g_assert (NAUTILUS_IS_WINDOW_SLOT (self));
+
+ priv = nautilus_window_slot_get_instance_private (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 (priv->new_content_view)
+ {
+ selection = nautilus_view_get_selection (priv->content_view);
+
+ if (NAUTILUS_IS_FILES_VIEW (priv->new_content_view))
+ {
+ current_pos = nautilus_files_view_get_first_visible_file (NAUTILUS_FILES_VIEW (priv->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)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ g_assert (NAUTILUS_IS_WINDOW_SLOT (self));
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ if (nautilus_window_slot_get_location (self) == NULL)
+ {
+ return;
+ }
+
+ if (priv->pending_location != NULL
+ || priv->content_view == NULL
+ || nautilus_view_is_loading (priv->content_view))
+ {
+ /* there is a reload in flight */
+ priv->needs_reload = TRUE;
+ return;
+ }
+
+ nautilus_window_slot_force_reload (self);
+}
+
+static void
+nautilus_window_slot_clear_forward_list (NautilusWindowSlot *self)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ g_assert (NAUTILUS_IS_WINDOW_SLOT (self));
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ g_list_free_full (priv->forward_list, g_object_unref);
+ priv->forward_list = NULL;
+}
+
+static void
+nautilus_window_slot_clear_back_list (NautilusWindowSlot *self)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ g_assert (NAUTILUS_IS_WINDOW_SLOT (self));
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ g_list_free_full (priv->back_list, g_object_unref);
+ priv->back_list = NULL;
+}
+
+static void
+nautilus_window_slot_update_bookmark (NautilusWindowSlot *self,
+ NautilusFile *file)
+{
+ gboolean recreate;
+ GFile *new_location;
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ new_location = nautilus_file_get_location (file);
+
+ if (priv->current_location_bookmark == NULL)
+ {
+ recreate = TRUE;
+ }
+ else
+ {
+ GFile *bookmark_location;
+ bookmark_location = nautilus_bookmark_get_location (priv->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 (&priv->last_location_bookmark);
+ priv->last_location_bookmark = priv->current_location_bookmark;
+
+ display_name = nautilus_file_get_display_name (file);
+ priv->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)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ check_bookmark_location_matches (priv->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;
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ list_ptr = (forward) ? (&priv->forward_list) : (&priv->back_list);
+ other_list_ptr = (forward) ? (&priv->back_list) : (&priv->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) > priv->location_change_distance);
+ check_bookmark_location_matches (g_list_nth_data (list, priv->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, priv->last_location_bookmark);
+ g_object_ref (other_list->data);
+
+ /* Move extra links from the list to the other list */
+ for (i = 0; i < priv->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;
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+
+ /* 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. */
+ priv->back_list = g_list_prepend (priv->back_list,
+ priv->last_location_bookmark);
+ g_object_ref (priv->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;
+ NautilusWindowSlotPrivate *priv;
+
+ self = data->slot;
+ priv = nautilus_window_slot_get_instance_private (self);
+
+ 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);
+ }
+
+ priv->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;
+ NautilusWindowSlotPrivate *priv;
+
+ self = NAUTILUS_WINDOW_SLOT (data->slot);
+ priv = nautilus_window_slot_get_instance_private (self);
+ 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;
+ }
+
+ priv->find_mount_cancellable = NULL;
+
+out:
+ g_object_unref (data->cancellable);
+ g_free (data);
+}
+
+static void
+trash_state_changed_cb (NautilusTrashMonitor *monitor,
+ gboolean is_empty,
+ gpointer user_data)
+{
+ GFile *location;
+ NautilusDirectory *directory;
+ NautilusView *view;
+
+ location = nautilus_window_slot_get_current_location (user_data);
+ view = nautilus_window_slot_get_current_view (user_data);
+
+ /* The signal 'trash-state-changed' could be emitted by NautilusTrashMonitor
+ * while a NautilusWindowSlot is still initializing the content view.
+ */
+ if (location == NULL || view == NULL)
+ {
+ return;
+ }
+
+ directory = nautilus_directory_get (location);
+
+ if (nautilus_directory_is_in_trash (directory))
+ {
+ if (nautilus_trash_monitor_is_empty ())
+ {
+ nautilus_window_slot_remove_extra_location_widgets (user_data);
+ }
+ else
+ {
+ nautilus_window_slot_setup_extra_location_widgets (user_data);
+ }
+ }
+}
+
+static void
+nautilus_window_slot_show_trash_bar (NautilusWindowSlot *self)
+{
+ GtkWidget *bar;
+ NautilusView *view;
+
+ view = nautilus_window_slot_get_current_view (self);
+ bar = nautilus_trash_bar_new (NAUTILUS_FILES_VIEW (view));
+ gtk_widget_show (bar);
+
+ nautilus_window_slot_add_extra_location_widget (self, bar);
+}
+
+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
+slot_add_extension_extra_widgets (NautilusWindowSlot *self)
+{
+ GList *providers, *l;
+ GtkWidget *widget;
+ char *uri;
+ NautilusWindow *window;
+
+ providers = nautilus_module_get_extensions_for_type (NAUTILUS_TYPE_LOCATION_WIDGET_PROVIDER);
+ window = nautilus_window_slot_get_window (self);
+
+ uri = nautilus_window_slot_get_location_uri (self);
+ for (l = providers; l != NULL; l = l->next)
+ {
+ NautilusLocationWidgetProvider *provider;
+
+ provider = NAUTILUS_LOCATION_WIDGET_PROVIDER (l->data);
+ widget = nautilus_location_widget_provider_get_widget (provider, uri, GTK_WIDGET (window));
+ if (widget != NULL)
+ {
+ nautilus_window_slot_add_extra_location_widget (self, widget);
+ }
+ }
+ g_free (uri);
+
+ nautilus_module_extension_list_free (providers);
+}
+
+static void
+nautilus_window_slot_update_for_new_location (NautilusWindowSlot *self)
+{
+ GFile *new_location;
+ NautilusFile *file;
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ new_location = priv->pending_location;
+ priv->pending_location = NULL;
+
+ file = nautilus_file_get (new_location);
+ nautilus_window_slot_update_bookmark (self, file);
+
+ update_history (self, priv->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);
+ priv->viewed_file_seen = !nautilus_file_is_not_yet_confirmed (file);
+ priv->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)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ if (view == priv->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 (priv->window))))
+ {
+ gtk_widget_grab_focus (GTK_WIDGET (priv->window));
+ }
+
+ gtk_widget_show (GTK_WIDGET (priv->window));
+
+ nautilus_window_slot_set_loading (self, TRUE);
+}
+
+static void
+view_ended_loading (NautilusWindowSlot *self,
+ NautilusView *view)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ if (view == priv->content_view)
+ {
+ if (NAUTILUS_IS_FILES_VIEW (view) && priv->pending_scroll_to != NULL)
+ {
+ nautilus_files_view_scroll_to_file (NAUTILUS_FILES_VIEW (priv->content_view), priv->pending_scroll_to);
+ }
+
+ end_location_change (self);
+ }
+
+ if (priv->needs_reload)
+ {
+ nautilus_window_slot_queue_reload (self);
+ priv->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 void
+nautilus_window_slot_setup_extra_location_widgets (NautilusWindowSlot *self)
+{
+ GFile *location;
+ FindMountData *data;
+ NautilusDirectory *directory;
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ location = nautilus_window_slot_get_current_location (self);
+
+ if (location == NULL)
+ {
+ return;
+ }
+
+ directory = nautilus_directory_get (location);
+
+ if (nautilus_directory_is_in_trash (directory))
+ {
+ if (!nautilus_trash_monitor_is_empty ())
+ {
+ nautilus_window_slot_show_trash_bar (self);
+ }
+ }
+ else
+ {
+ NautilusFile *file;
+ GFile *scripts_file;
+ char *scripts_path = nautilus_get_scripts_directory_path ();
+
+ 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);
+ }
+
+ g_object_unref (scripts_file);
+ nautilus_file_unref (file);
+ }
+
+ /* need the mount to determine if we should put up the x-content cluebar */
+ if (priv->find_mount_cancellable != NULL)
+ {
+ g_cancellable_cancel (priv->find_mount_cancellable);
+ priv->find_mount_cancellable = NULL;
+ }
+
+ data = g_new (FindMountData, 1);
+ data->slot = self;
+ data->cancellable = g_cancellable_new ();
+ data->mount = NULL;
+
+ priv->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);
+
+ slot_add_extension_extra_widgets (self);
+}
+
+static void
+nautilus_window_slot_connect_new_content_view (NautilusWindowSlot *self)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ if (priv->new_content_view)
+ {
+ g_signal_connect (priv->new_content_view,
+ "notify::loading",
+ G_CALLBACK (view_is_loading_changed_cb),
+ self);
+ }
+}
+
+static void
+nautilus_window_slot_disconnect_content_view (NautilusWindowSlot *self)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ if (priv->content_view)
+ {
+ /* disconnect old view */
+ g_signal_handlers_disconnect_by_func (priv->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;
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ reusing_view = priv->new_content_view &&
+ gtk_widget_get_parent (GTK_WIDGET (priv->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 (priv->new_content_view == NULL || reusing_view)
+ {
+ goto done;
+ }
+
+ if (priv->content_view != NULL)
+ {
+ g_binding_unbind (priv->searching_binding);
+ g_binding_unbind (priv->selection_binding);
+ g_binding_unbind (priv->extensions_background_menu_binding);
+ g_binding_unbind (priv->templates_menu_binding);
+ widget = GTK_WIDGET (priv->content_view);
+ gtk_widget_destroy (widget);
+ g_clear_object (&priv->content_view);
+ }
+
+ if (priv->new_content_view != NULL)
+ {
+ priv->content_view = priv->new_content_view;
+ priv->new_content_view = NULL;
+
+ widget = GTK_WIDGET (priv->content_view);
+ gtk_container_add (GTK_CONTAINER (self), widget);
+ gtk_widget_set_vexpand (widget, TRUE);
+ gtk_widget_show (widget);
+ priv->searching_binding = g_object_bind_property (priv->content_view, "searching",
+ self, "searching",
+ G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
+ priv->selection_binding = g_object_bind_property (priv->content_view, "selection",
+ self, "selection",
+ G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
+ priv->extensions_background_menu_binding = g_object_bind_property (priv->content_view, "extensions-background-menu",
+ self, "extensions-background-menu",
+ G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
+ priv->templates_menu_binding = g_object_bind_property (priv->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]);
+ 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 */
+ priv->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)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (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 (priv->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;
+ NautilusWindowSlotPrivate *priv;
+
+ self = NAUTILUS_WINDOW_SLOT (object);
+ priv = nautilus_window_slot_get_instance_private (self);
+
+ g_signal_handlers_disconnect_by_data (nautilus_trash_monitor_get (), self);
+
+ 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 (&priv->searching_binding, g_binding_unbind);
+ g_clear_pointer (&priv->selection_binding, g_binding_unbind);
+ g_clear_pointer (&priv->extensions_background_menu_binding, g_binding_unbind);
+ g_clear_pointer (&priv->templates_menu_binding, g_binding_unbind);
+
+ if (priv->content_view)
+ {
+ gtk_widget_destroy (GTK_WIDGET (priv->content_view));
+ g_clear_object (&priv->content_view);
+ }
+
+ if (priv->new_content_view)
+ {
+ gtk_widget_destroy (GTK_WIDGET (priv->new_content_view));
+ g_clear_object (&priv->new_content_view);
+ }
+
+ nautilus_window_slot_set_viewed_file (self, NULL);
+
+ g_clear_object (&priv->location);
+ g_clear_object (&priv->pending_file_to_activate);
+ g_clear_pointer (&priv->pending_selection, nautilus_file_list_free);
+
+ g_clear_object (&priv->current_location_bookmark);
+ g_clear_object (&priv->last_location_bookmark);
+ g_clear_object (&priv->slot_action_group);
+ g_clear_object (&priv->pending_search_query);
+
+ g_clear_pointer (&priv->find_mount_cancellable, g_cancellable_cancel);
+
+ if (priv->query_editor)
+ {
+ gtk_widget_destroy (GTK_WIDGET (priv->query_editor));
+ g_clear_object (&priv->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;
+ NautilusWindowSlotPrivate *priv;
+
+ self = NAUTILUS_WINDOW_SLOT (object);
+ priv = nautilus_window_slot_get_instance_private (self);
+
+ g_clear_pointer (&priv->title, g_free);
+
+ G_OBJECT_CLASS (nautilus_window_slot_parent_class)->finalize (object);
+}
+
+static void
+nautilus_window_slot_grab_focus (GtkWidget *widget)
+{
+ NautilusWindowSlot *self;
+ NautilusWindowSlotPrivate *priv;
+
+ self = NAUTILUS_WINDOW_SLOT (widget);
+ priv = nautilus_window_slot_get_instance_private (self);
+
+ GTK_WIDGET_CLASS (nautilus_window_slot_parent_class)->grab_focus (widget);
+
+ if (nautilus_window_slot_get_search_visible (self))
+ {
+ gtk_widget_grab_focus (GTK_WIDGET (priv->query_editor));
+ }
+ else if (priv->content_view)
+ {
+ gtk_widget_grab_focus (GTK_WIDGET (priv->content_view));
+ }
+ else if (priv->new_content_view)
+ {
+ gtk_widget_grab_focus (GTK_WIDGET (priv->new_content_view));
+ }
+}
+
+static void
+nautilus_window_slot_class_init (NautilusWindowSlotClass *klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ klass->get_view_for_location = real_get_view_for_location;
+ klass->handles_location = real_handles_location;
+
+ 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] =
+ g_param_spec_object ("icon",
+ "Icon that represents the slot",
+ "The icon that represents the slot",
+ G_TYPE_ICON,
+ 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);
+
+ g_object_class_install_properties (oclass, NUM_PROPERTIES, properties);
+}
+
+GFile *
+nautilus_window_slot_get_location (NautilusWindowSlot *self)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ g_return_val_if_fail (NAUTILUS_IS_WINDOW_SLOT (self), NULL);
+
+ priv = nautilus_window_slot_get_instance_private (self);
+
+ return priv->location;
+}
+
+GFile *
+nautilus_window_slot_get_pending_location (NautilusWindowSlot *self)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ g_return_val_if_fail (NAUTILUS_IS_WINDOW_SLOT (self), NULL);
+
+ priv = nautilus_window_slot_get_instance_private (self);
+
+ return priv->pending_location;
+}
+
+const gchar *
+nautilus_window_slot_get_title (NautilusWindowSlot *self)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+
+ return priv->title;
+}
+
+char *
+nautilus_window_slot_get_location_uri (NautilusWindowSlot *self)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ g_assert (NAUTILUS_IS_WINDOW_SLOT (self));
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ if (priv->location)
+ {
+ return g_file_get_uri (priv->location);
+ }
+ return NULL;
+}
+
+NautilusWindow *
+nautilus_window_slot_get_window (NautilusWindowSlot *self)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ g_assert (NAUTILUS_IS_WINDOW_SLOT (self));
+
+ priv = nautilus_window_slot_get_instance_private (self);
+
+ return priv->window;
+}
+
+void
+nautilus_window_slot_set_window (NautilusWindowSlot *self,
+ NautilusWindow *window)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ g_assert (NAUTILUS_IS_WINDOW_SLOT (self));
+ g_assert (NAUTILUS_IS_WINDOW (window));
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ if (priv->window != window)
+ {
+ priv->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;
+ char *title;
+ gboolean do_sync = FALSE;
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ title = nautilus_compute_title_for_location (priv->location);
+ window = nautilus_window_slot_get_window (self);
+
+ if (g_strcmp0 (title, priv->title) != 0)
+ {
+ do_sync = TRUE;
+
+ g_free (priv->title);
+ priv->title = title;
+ title = NULL;
+ }
+
+ if (strlen (priv->title) > 0)
+ {
+ do_sync = TRUE;
+ }
+
+ if (do_sync)
+ {
+ nautilus_window_sync_title (window, self);
+ }
+
+ if (title != NULL)
+ {
+ g_free (title);
+ }
+}
+
+gboolean
+nautilus_window_slot_get_allow_stop (NautilusWindowSlot *self)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+
+ return priv->allow_stop;
+}
+
+void
+nautilus_window_slot_set_allow_stop (NautilusWindowSlot *self,
+ gboolean allow)
+{
+ NautilusWindow *window;
+ NautilusWindowSlotPrivate *priv;
+
+ g_assert (NAUTILUS_IS_WINDOW_SLOT (self));
+
+ priv = nautilus_window_slot_get_instance_private (self);
+
+ priv->allow_stop = allow;
+
+ window = nautilus_window_slot_get_window (self);
+ nautilus_window_sync_allow_stop (window, self);
+}
+
+void
+nautilus_window_slot_stop_loading (NautilusWindowSlot *self)
+{
+ GFile *location;
+ NautilusDirectory *directory;
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ location = nautilus_window_slot_get_location (self);
+ directory = nautilus_directory_get (priv->location);
+
+ if (NAUTILUS_IS_FILES_VIEW (priv->content_view))
+ {
+ nautilus_files_view_stop_loading (NAUTILUS_FILES_VIEW (priv->content_view));
+ }
+
+ nautilus_directory_unref (directory);
+
+ if (priv->pending_location != NULL &&
+ location != NULL &&
+ priv->content_view != NULL &&
+ NAUTILUS_IS_FILES_VIEW (priv->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 (priv->content_view);
+ load_new_location (self,
+ location,
+ selection,
+ NULL,
+ TRUE,
+ FALSE);
+ }
+
+ end_location_change (self);
+
+ if (priv->new_content_view)
+ {
+ g_object_unref (priv->new_content_view);
+ priv->new_content_view = NULL;
+ }
+}
+
+NautilusView *
+nautilus_window_slot_get_current_view (NautilusWindowSlot *self)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ if (priv->content_view != NULL)
+ {
+ return priv->content_view;
+ }
+ else if (priv->new_content_view)
+ {
+ return priv->new_content_view;
+ }
+
+ return NULL;
+}
+
+NautilusBookmark *
+nautilus_window_slot_get_bookmark (NautilusWindowSlot *self)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+
+ return priv->current_location_bookmark;
+}
+
+GList *
+nautilus_window_slot_get_back_history (NautilusWindowSlot *self)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+
+ return priv->back_list;
+}
+
+GList *
+nautilus_window_slot_get_forward_history (NautilusWindowSlot *self)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+
+ return priv->forward_list;
+}
+
+NautilusWindowSlot *
+nautilus_window_slot_new (NautilusWindow *window)
+{
+ return g_object_new (NAUTILUS_TYPE_WINDOW_SLOT,
+ "window", window,
+ NULL);
+}
+
+GIcon *
+nautilus_window_slot_get_icon (NautilusWindowSlot *self)
+{
+ guint current_view_id;
+ NautilusWindowSlotPrivate *priv;
+
+ g_return_val_if_fail (NAUTILUS_IS_WINDOW_SLOT (self), NULL);
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ if (priv->content_view == NULL)
+ {
+ return NULL;
+ }
+
+ current_view_id = nautilus_view_get_view_id (NAUTILUS_VIEW (priv->content_view));
+ switch (current_view_id)
+ {
+ case NAUTILUS_VIEW_LIST_ID:
+ {
+ return nautilus_view_get_icon (NAUTILUS_VIEW_GRID_ID);
+ }
+ break;
+
+ case NAUTILUS_VIEW_GRID_ID:
+ {
+ return nautilus_view_get_icon (NAUTILUS_VIEW_LIST_ID);
+ }
+ break;
+
+ case NAUTILUS_VIEW_OTHER_LOCATIONS_ID:
+ {
+ return nautilus_view_get_icon (NAUTILUS_VIEW_OTHER_LOCATIONS_ID);
+ }
+ break;
+
+ default:
+ {
+ return NULL;
+ }
+ }
+}
+
+const gchar *
+nautilus_window_slot_get_tooltip (NautilusWindowSlot *self)
+{
+ guint current_view_id;
+ NautilusWindowSlotPrivate *priv;
+
+ g_return_val_if_fail (NAUTILUS_IS_WINDOW_SLOT (self), NULL);
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ if (priv->content_view == NULL)
+ {
+ return NULL;
+ }
+
+ current_view_id = nautilus_view_get_view_id (NAUTILUS_VIEW (priv->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)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ g_return_val_if_fail (NAUTILUS_IS_WINDOW_SLOT (self), FALSE);
+
+ priv = nautilus_window_slot_get_instance_private (self);
+
+ return priv->active;
+}
+
+void
+nautilus_window_slot_set_active (NautilusWindowSlot *self,
+ gboolean active)
+{
+ NautilusWindowSlotPrivate *priv;
+ NautilusWindow *window;
+
+ g_return_if_fail (NAUTILUS_IS_WINDOW_SLOT (self));
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ if (priv->active != active)
+ {
+ priv->active = active;
+
+ if (active)
+ {
+ int page_num;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ window = priv->window;
+ page_num = gtk_notebook_page_num (GTK_NOTEBOOK (nautilus_window_get_notebook (window)),
+ GTK_WIDGET (self));
+ g_assert (page_num >= 0);
+
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (nautilus_window_get_notebook (window)), page_num);
+
+ /* 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", priv->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)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ g_return_if_fail (NAUTILUS_IS_WINDOW_SLOT (self));
+
+ priv = nautilus_window_slot_get_instance_private (self);
+ priv->loading = loading;
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOADING]);
+}
+
+gboolean
+nautilus_window_slot_get_loading (NautilusWindowSlot *self)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ g_return_val_if_fail (NAUTILUS_IS_WINDOW_SLOT (self), FALSE);
+
+ priv = nautilus_window_slot_get_instance_private (self);
+
+ return priv->loading;
+}
+
+NautilusQueryEditor *
+nautilus_window_slot_get_query_editor (NautilusWindowSlot *self)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ g_return_val_if_fail (NAUTILUS_IS_WINDOW_SLOT (self), NULL);
+
+ priv = nautilus_window_slot_get_instance_private (self);
+
+ return priv->query_editor;
+}
+
+/*
+ * Open the specified location and set up the navigation history including the
+ * back and forward lists. This function is intended to be called when switching
+ * between NautilusWindowSlot and NautilusOtherLocationsWindowSlot. It allows
+ * the navigation history accumulated in the slot being replaced to be loaded
+ * into the replacing slot.
+ *
+ * The 'location' member variable is set to the new location before calling
+ * begin_location_change() to ensure that it matches the
+ * 'current_location_bookmark' member as expected by the location change
+ * pipeline.
+ */
+void
+nautilus_window_slot_open_location_set_navigation_state (NautilusWindowSlot *self,
+ GFile *location,
+ NautilusWindowOpenFlags flags,
+ GList *new_selection,
+ NautilusLocationChangeType change_type,
+ NautilusNavigationState *navigation_state,
+ guint distance)
+{
+ NautilusWindowSlotPrivate *priv;
+
+ priv = nautilus_window_slot_get_instance_private (self);
+
+ nautilus_window_slot_restore_navigation_state (self, navigation_state);
+
+ g_clear_object (&priv->location);
+
+ priv->location = nautilus_file_get_location (navigation_state->file);
+
+ begin_location_change (self, location, NULL, new_selection,
+ change_type, distance, NULL);
+}
diff --git a/src/nautilus-window-slot.h b/src/nautilus-window-slot.h
new file mode 100644
index 0000000..3518c94
--- /dev/null
+++ b/src/nautilus-window-slot.h
@@ -0,0 +1,150 @@
+/*
+ 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_DERIVABLE_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;
+
+struct _NautilusWindowSlotClass {
+ GtkBoxClass parent_class;
+
+ /* wrapped NautilusWindowInfo signals, for overloading */
+ void (* active) (NautilusWindowSlot *slot);
+ void (* inactive) (NautilusWindowSlot *slot);
+
+ /* Use this in case the subclassed slot has some special views differents
+ * that the ones supported here. You can return your nautilus view
+ * subclass in this function.
+ */
+ NautilusView* (* get_view_for_location) (NautilusWindowSlot *slot,
+ GFile *location);
+ /* Whether this type of slot handles the location or not. This can be used
+ * for the special slots which handle special locations like the desktop
+ * or the other locations. */
+ gboolean (* handles_location) (NautilusWindowSlot *slot,
+ GFile *location);
+};
+
+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,
+ NautilusWindowOpenFlags flags,
+ GList *new_selection);
+
+void nautilus_window_slot_open_location_set_navigation_state (NautilusWindowSlot *slot,
+ GFile *location,
+ NautilusWindowOpenFlags flags,
+ GList *new_selection,
+ NautilusLocationChangeType change_type,
+ NautilusNavigationState *navigation_state,
+ guint distance);
+
+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,
+ GdkEvent *event);
+
+void nautilus_window_slot_queue_reload (NautilusWindowSlot *slot);
+
+GIcon* nautilus_window_slot_get_icon (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);
+
+gboolean nautilus_window_slot_handles_location (NautilusWindowSlot *self,
+ GFile *location);
+
+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,
+ NautilusWindowOpenFlags flags);
+
+void free_navigation_state (gpointer data);
diff --git a/src/nautilus-window.c b/src/nautilus-window.c
new file mode 100644
index 0000000..c81d150
--- /dev/null
+++ b/src/nautilus-window.c
@@ -0,0 +1,3100 @@
+/*
+ * 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-gtk-extensions.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/gdkwayland.h>
+#endif
+
+#ifdef GDK_WINDOWING_X11
+#include <gdk/gdkx.h>
+#endif
+
+#define DEBUG_FLAG NAUTILUS_DEBUG_WINDOW
+#include "nautilus-debug.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-list-view.h"
+#include "nautilus-location-entry.h"
+#include "nautilus-metadata.h"
+#include "nautilus-mime-actions.h"
+#include "nautilus-notebook.h"
+#include "nautilus-other-locations-window-slot.h"
+#include "nautilus-pathbar.h"
+#include "nautilus-profile.h"
+#include "nautilus-properties-window.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 close_slot (NautilusWindow *window,
+ NautilusWindowSlot *slot,
+ gboolean remove_from_notebook);
+
+/* 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
+
+#define NOTIFICATION_TIMEOUT 6 /*s */
+
+struct _NautilusWindow
+{
+ GtkApplicationWindow parent_instance;
+
+ GtkWidget *notebook;
+
+ /* available slots, and active slot.
+ * Both of them may never be NULL.
+ */
+ GList *slots;
+ NautilusWindowSlot *active_slot;
+
+ GtkWidget *content_paned;
+
+ /* Side Pane */
+ int side_pane_width;
+ GtkWidget *sidebar; /* container for the GtkPlacesSidebar */
+ 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 */
+
+ /* Main view */
+ GtkWidget *main_view;
+
+ /* Notifications */
+ GtkWidget *in_app_notification_undo;
+ GtkWidget *in_app_notification_undo_label;
+ GtkWidget *in_app_notification_undo_close_button;
+ GtkWidget *in_app_notification_undo_undo_button;
+ guint in_app_notification_undo_timeout_id;
+ GtkWidget *notification_operation;
+ GtkWidget *notification_operation_label;
+ GtkWidget *notification_operation_close;
+ GtkWidget *notification_operation_open;
+ guint notification_operation_timeout_id;
+ GFile *folder_to_open;
+
+ /* 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;
+
+ GtkPadController *pad_controller;
+
+ GtkGesture *multi_press_gesture;
+ GtkGesture *notebook_multi_press_gesture;
+};
+
+enum
+{
+ SLOT_ADDED,
+ SLOT_REMOVED,
+ ACTIVE_SELECTION_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (NautilusWindow, nautilus_window, GTK_TYPE_APPLICATION_WINDOW);
+
+static const struct
+{
+ unsigned int keyval;
+ const char *action;
+} extra_window_keybindings [] =
+{
+ /* Window actions */
+ { GDK_KEY_AddFavorite, "bookmark-current-location" },
+ { GDK_KEY_Favorites, "bookmarks" },
+ { GDK_KEY_Go, "enter-location" },
+ { GDK_KEY_HomePage, "go-home" },
+ { GDK_KEY_OpenURL, "enter-location" },
+ { GDK_KEY_Refresh, "reload" },
+ { GDK_KEY_Reload, "reload" },
+ { GDK_KEY_Search, "search" },
+ { GDK_KEY_Start, "go-home" },
+ { GDK_KEY_Stop, "stop" },
+ { GDK_KEY_Back, "back" },
+ { GDK_KEY_Forward, "forward" },
+};
+
+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;
+ NautilusWindowSlot *slot;
+
+ window = NAUTILUS_WINDOW (user_data);
+ slot = nautilus_window_get_active_slot (window);
+
+ nautilus_window_slot_close (window, slot);
+}
+
+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, nautilus_event_get_window_open_flags (), NULL, NULL);
+
+ g_object_unref (home);
+}
+
+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,
+ nautilus_event_get_window_open_flags (),
+ 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, nautilus_event_get_window_open_flags ());
+}
+
+static void
+action_forward (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ nautilus_window_back_or_forward (NAUTILUS_WINDOW (user_data),
+ FALSE, 0, nautilus_event_get_window_open_flags ());
+}
+
+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_previous (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusWindow *window = user_data;
+
+ nautilus_notebook_prev_page (NAUTILUS_NOTEBOOK (window->notebook));
+}
+
+static void
+action_tab_next (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusWindow *window = user_data;
+
+ nautilus_notebook_next_page (NAUTILUS_NOTEBOOK (window->notebook));
+}
+
+static void
+action_tab_move_left (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusWindow *window = user_data;
+
+ nautilus_notebook_reorder_current_child_relative (NAUTILUS_NOTEBOOK (window->notebook), -1);
+}
+
+static void
+action_tab_move_right (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusWindow *window = user_data;
+
+ nautilus_notebook_reorder_current_child_relative (NAUTILUS_NOTEBOOK (window->notebook), 1);
+}
+
+static void
+action_go_to_tab (GSimpleAction *action,
+ GVariant *value,
+ gpointer user_data)
+{
+ NautilusWindow *window = NAUTILUS_WINDOW (user_data);
+ GtkNotebook *notebook;
+ gint16 num;
+
+ notebook = GTK_NOTEBOOK (window->notebook);
+
+ num = g_variant_get_int32 (value);
+ if (num < gtk_notebook_get_n_pages (notebook))
+ {
+ gtk_notebook_set_current_page (notebook, num);
+ }
+}
+
+static void
+action_prompt_for_location_root (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusWindow *window = user_data;
+ GFile *location;
+ GtkWidget *entry;
+
+ location = g_file_new_for_path ("/");
+ entry = nautilus_window_ensure_location_entry (window);
+ nautilus_location_entry_set_location (NAUTILUS_LOCATION_ENTRY (entry), location);
+
+ g_object_unref (location);
+}
+
+static void
+action_prompt_for_location_home (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ GtkWidget *entry;
+
+ entry = nautilus_window_ensure_location_entry (NAUTILUS_WINDOW (user_data));
+ nautilus_location_entry_set_special_text (NAUTILUS_LOCATION_ENTRY (entry),
+ "~");
+ gtk_editable_set_position (GTK_EDITABLE (entry), -1);
+}
+
+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
+on_location_changed (NautilusWindow *window)
+{
+ gtk_places_sidebar_set_location (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
+on_slot_selection_changed (NautilusWindowSlot *slot,
+ GParamSpec *pspec,
+ NautilusWindow *window)
+{
+ if (nautilus_window_get_active_slot (window) == slot)
+ {
+ g_signal_emit (window, signals[ACTIVE_SELECTION_CHANGED], 0);
+ }
+}
+
+static void
+notebook_switch_page_cb (GtkNotebook *notebook,
+ GtkWidget *page,
+ unsigned int page_num,
+ NautilusWindow *window)
+{
+ NautilusWindowSlot *slot;
+ GtkWidget *widget;
+
+ widget = gtk_notebook_get_nth_page (GTK_NOTEBOOK (window->notebook), page_num);
+ 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);
+ g_signal_connect (slot, "notify::selection",
+ G_CALLBACK (on_slot_selection_changed), window);
+}
+
+static void
+disconnect_slot (NautilusWindow *window,
+ NautilusWindowSlot *slot)
+{
+ g_signal_handlers_disconnect_by_data (slot, window);
+}
+
+static NautilusWindowSlot *
+nautilus_window_create_slot (NautilusWindow *window,
+ GFile *location)
+{
+ NautilusFile *file = NULL;
+ NautilusWindowSlot *slot;
+
+ if (location)
+ {
+ file = nautilus_file_get (location);
+ }
+ /* If not file, assume we open the home directory. We will switch eventually
+ * to a different location if not.
+ */
+ if (file && nautilus_file_is_other_locations (file))
+ {
+ slot = NAUTILUS_WINDOW_SLOT (nautilus_other_locations_window_slot_new (window));
+ }
+ else
+ {
+ slot = nautilus_window_slot_new (window);
+ }
+
+ nautilus_file_unref (file);
+
+ return slot;
+}
+
+static NautilusWindowSlot *
+nautilus_window_create_and_init_slot (NautilusWindow *window,
+ GFile *location,
+ NautilusWindowOpenFlags flags)
+{
+ NautilusWindowSlot *slot;
+
+ slot = nautilus_window_create_slot (window, location);
+ nautilus_window_initialize_slot (window, slot, flags);
+
+ return slot;
+}
+
+static NautilusWindowSlot *
+replace_active_slot (NautilusWindow *window,
+ GFile *location,
+ NautilusWindowOpenFlags flags)
+{
+ NautilusWindowSlot *new_slot;
+ NautilusWindowSlot *active_slot;
+
+ new_slot = nautilus_window_create_and_init_slot (window, location, flags);
+ active_slot = nautilus_window_get_active_slot (window);
+ if (active_slot)
+ {
+ close_slot (window, active_slot, TRUE);
+ }
+
+ return new_slot;
+}
+
+void
+nautilus_window_initialize_slot (NautilusWindow *window,
+ NautilusWindowSlot *slot,
+ NautilusWindowOpenFlags flags)
+{
+ g_assert (NAUTILUS_IS_WINDOW (window));
+ g_assert (NAUTILUS_IS_WINDOW_SLOT (slot));
+
+ connect_slot (window, slot);
+
+ g_signal_handlers_block_by_func (window->notebook,
+ G_CALLBACK (notebook_switch_page_cb),
+ window);
+ nautilus_notebook_add_tab (NAUTILUS_NOTEBOOK (window->notebook),
+ slot,
+ (flags & NAUTILUS_WINDOW_OPEN_SLOT_APPEND) != 0 ?
+ -1 :
+ gtk_notebook_get_current_page (GTK_NOTEBOOK (window->notebook)) + 1,
+ FALSE);
+ g_signal_handlers_unblock_by_func (window->notebook,
+ G_CALLBACK (notebook_switch_page_cb),
+ window);
+
+ window->slots = g_list_append (window->slots, slot);
+ g_signal_emit (window, signals[SLOT_ADDED], 0, slot);
+}
+
+void
+nautilus_window_open_location_full (NautilusWindow *window,
+ GFile *location,
+ NautilusWindowOpenFlags flags,
+ GList *selection,
+ NautilusWindowSlot *target_slot)
+{
+ NautilusWindowSlot *active_slot;
+ gboolean new_tab_at_end;
+ NautilusNavigationState *navigation_state = NULL;
+
+ /* The location owner can be one of the slots requesting to handle an
+ * unhandled location. But this slot can be destroyed when switching to
+ * a new slot. So keep the location alive.
+ */
+ g_object_ref (location);
+
+ /* Assert that we are not managing new windows */
+ g_assert (!(flags & NAUTILUS_WINDOW_OPEN_FLAG_NEW_WINDOW));
+ /* if the flags say we want a new tab, open a slot in the current window */
+ if ((flags & NAUTILUS_WINDOW_OPEN_FLAG_NEW_TAB) != 0)
+ {
+ new_tab_at_end = g_settings_get_enum (nautilus_preferences, NAUTILUS_PREFERENCES_NEW_TAB_POSITION) == NAUTILUS_NEW_TAB_POSITION_END;
+ if (new_tab_at_end)
+ {
+ flags |= NAUTILUS_WINDOW_OPEN_SLOT_APPEND;
+ }
+ }
+
+ active_slot = nautilus_window_get_active_slot (window);
+ if (!target_slot)
+ {
+ target_slot = active_slot;
+ }
+
+ if (target_slot == NULL || (flags & NAUTILUS_WINDOW_OPEN_FLAG_NEW_TAB) != 0)
+ {
+ target_slot = nautilus_window_create_and_init_slot (window, location, flags);
+ }
+ else if (!nautilus_window_slot_handles_location (target_slot, location))
+ {
+ navigation_state = nautilus_window_slot_get_navigation_state (active_slot);
+
+ target_slot = replace_active_slot (window, location, 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_WINDOW_OPEN_FLAG_DONT_MAKE_ACTIVE))
+ {
+ gtk_window_present (GTK_WINDOW (window));
+ nautilus_window_set_active_slot (window, target_slot);
+ }
+
+ if (navigation_state != NULL)
+ {
+ nautilus_window_slot_open_location_set_navigation_state (target_slot,
+ location, flags, selection,
+ NAUTILUS_LOCATION_CHANGE_STANDARD,
+ navigation_state, 0);
+
+ free_navigation_state (navigation_state);
+ }
+ else
+ {
+ nautilus_window_slot_open_location_full (target_slot, location, flags, selection);
+ }
+
+ g_object_unref (location);
+}
+
+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 void
+nautilus_window_grab_focus (GtkWidget *widget)
+{
+ NautilusWindowSlot *slot;
+
+ slot = nautilus_window_get_active_slot (NAUTILUS_WINDOW (widget));
+
+ GTK_WIDGET_CLASS (nautilus_window_parent_class)->grab_focus (widget);
+
+ if (slot)
+ {
+ gtk_widget_grab_focus (GTK_WIDGET (slot));
+ }
+}
+
+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
+close_slot (NautilusWindow *window,
+ NautilusWindowSlot *slot,
+ gboolean remove_from_notebook)
+{
+ int page_num;
+ GtkNotebook *notebook;
+
+ g_assert (NAUTILUS_IS_WINDOW_SLOT (slot));
+
+
+ DEBUG ("Closing slot %p", slot);
+
+ disconnect_slot (window, slot);
+
+ window->slots = g_list_remove (window->slots, slot);
+
+ g_signal_emit (window, signals[SLOT_REMOVED], 0, slot);
+
+ notebook = GTK_NOTEBOOK (window->notebook);
+
+ if (remove_from_notebook)
+ {
+ page_num = gtk_notebook_page_num (notebook, GTK_WIDGET (slot));
+ g_assert (page_num >= 0);
+
+ /* this will call gtk_widget_destroy on the slot */
+ gtk_notebook_remove_page (notebook, page_num);
+ }
+}
+
+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_WINDOW_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))
+ {
+ GdkDisplay *display;
+ g_autoptr (GdkCursor) cursor = NULL;
+
+ display = gtk_widget_get_display (GTK_WIDGET (window));
+ cursor = gdk_cursor_new_from_name (display, "progress");
+ gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (window)), cursor);
+ }
+ else
+ {
+ gdk_window_set_cursor (gtk_widget_get_window (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);
+ }
+
+ /* Avoid updating the notebook if we are calling on dispose or
+ * on removal of a notebook tab */
+ if (nautilus_notebook_contains_slot (NAUTILUS_NOTEBOOK (window->notebook), slot))
+ {
+ nautilus_notebook_sync_loading (NAUTILUS_NOTEBOOK (window->notebook), slot);
+ }
+ }
+}
+
+GtkWidget *
+nautilus_window_get_notebook (NautilusWindow *window)
+{
+ g_return_val_if_fail (NAUTILUS_IS_WINDOW (window), NULL);
+
+ return window->notebook;
+}
+
+static gboolean
+save_sidebar_width_cb (gpointer user_data)
+{
+ NautilusWindow *window = user_data;
+
+
+ window->sidebar_width_handler_id = 0;
+
+ DEBUG ("Saving sidebar width: %d", window->side_pane_width);
+
+ g_settings_set_int (nautilus_window_state,
+ NAUTILUS_WINDOW_STATE_SIDEBAR_WIDTH,
+ window->side_pane_width);
+
+ return FALSE;
+}
+
+/* side pane helpers */
+static void
+side_pane_size_allocate_callback (GtkWidget *widget,
+ GtkAllocation *allocation,
+ gpointer user_data)
+{
+ NautilusWindow *window = user_data;
+
+
+ if (window->sidebar_width_handler_id != 0)
+ {
+ g_source_remove (window->sidebar_width_handler_id);
+ window->sidebar_width_handler_id = 0;
+ }
+
+ if (allocation->width != window->side_pane_width &&
+ allocation->width > 1)
+ {
+ window->side_pane_width = allocation->width;
+
+ window->sidebar_width_handler_id =
+ g_idle_add (save_sidebar_width_cb, window);
+ }
+}
+
+static void
+setup_side_pane_width (NautilusWindow *window)
+{
+ g_return_if_fail (window->sidebar != NULL);
+
+ window->side_pane_width =
+ g_settings_get_int (nautilus_window_state,
+ NAUTILUS_WINDOW_STATE_SIDEBAR_WIDTH);
+
+ gtk_paned_set_position (GTK_PANED (window->content_paned),
+ window->side_pane_width);
+}
+
+/* Callback used when the places sidebar changes location; we need to change the displayed folder */
+static void
+open_location_cb (NautilusWindow *window,
+ GFile *location,
+ GtkPlacesOpenFlags open_flags)
+{
+ NautilusWindowOpenFlags flags;
+ NautilusApplication *application;
+
+ switch (open_flags)
+ {
+ case GTK_PLACES_OPEN_NEW_TAB:
+ {
+ flags = NAUTILUS_WINDOW_OPEN_FLAG_NEW_TAB |
+ NAUTILUS_WINDOW_OPEN_FLAG_DONT_MAKE_ACTIVE;
+ }
+ break;
+
+ case GTK_PLACES_OPEN_NEW_WINDOW:
+ {
+ flags = NAUTILUS_WINDOW_OPEN_FLAG_NEW_WINDOW;
+ }
+ break;
+
+ case 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);
+}
+
+static void
+places_sidebar_unmount_operation_cb (NautilusWindow *window,
+ GMountOperation *mount_operation)
+{
+ g_signal_connect (mount_operation, "show-unmount-progress",
+ G_CALLBACK (show_unmount_progress_cb), NULL);
+ g_signal_connect (mount_operation, "aborted",
+ G_CALLBACK (show_unmount_progress_aborted_cb), NULL);
+}
+
+/* Callback used when the places sidebar needs us to present an error message */
+static void
+places_sidebar_show_error_message_cb (GtkPlacesSidebar *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,
+ GtkPlacesOpenFlags 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,
+ GtkPlacesOpenFlags open_flags)
+{
+ GFile *location;
+
+ location = g_file_new_for_uri ("starred:///");
+
+ open_location_cb (window, location, open_flags);
+
+ g_object_unref (location);
+}
+
+static GList *
+build_selection_list_from_gfile_list (GList *gfile_list)
+{
+ GList *result;
+ GList *l;
+
+ result = NULL;
+ for (l = gfile_list; l; l = l->next)
+ {
+ GFile *file;
+ NautilusDragSelectionItem *item;
+
+ file = l->data;
+
+ item = nautilus_drag_selection_item_new ();
+ item->uri = g_file_get_uri (file);
+ item->file = nautilus_file_get_existing (file);
+ item->got_icon_position = FALSE;
+ result = g_list_prepend (result, item);
+ }
+
+ return g_list_reverse (result);
+}
+
+void
+nautilus_window_start_dnd (NautilusWindow *window,
+ GdkDragContext *context)
+{
+ g_return_if_fail (NAUTILUS_IS_WINDOW (window));
+
+ gtk_places_sidebar_set_drop_targets_visible (GTK_PLACES_SIDEBAR (window->places_sidebar),
+ TRUE,
+ context);
+}
+
+void
+nautilus_window_end_dnd (NautilusWindow *window,
+ GdkDragContext *context)
+{
+ g_return_if_fail (NAUTILUS_IS_WINDOW (window));
+
+ gtk_places_sidebar_set_drop_targets_visible (GTK_PLACES_SIDEBAR (window->places_sidebar),
+ FALSE,
+ context);
+}
+
+/* Callback used when the places sidebar needs to know the drag action to suggest */
+static GdkDragAction
+places_sidebar_drag_action_requested_cb (GtkPlacesSidebar *sidebar,
+ GdkDragContext *context,
+ GFile *dest_file,
+ GList *source_file_list,
+ gpointer user_data)
+{
+ GList *items;
+ char *uri;
+ int action = 0;
+ NautilusDragInfo *info;
+ guint32 source_actions;
+
+ info = nautilus_drag_get_source_data (context);
+ if (info != NULL)
+ {
+ items = info->selection_cache;
+ source_actions = info->source_actions;
+ }
+ else
+ {
+ items = build_selection_list_from_gfile_list (source_file_list);
+ source_actions = 0;
+ }
+ uri = g_file_get_uri (dest_file);
+
+ if (items == NULL)
+ {
+ goto out;
+ }
+
+ nautilus_drag_default_drop_action_for_icons (context, uri, items, source_actions, &action);
+
+out:
+ if (info == NULL)
+ {
+ nautilus_drag_destroy_selection_list (items);
+ }
+
+ g_free (uri);
+
+ return action;
+}
+
+/* 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 (GtkPlacesSidebar *sidebar,
+ GdkDragAction actions,
+ gpointer user_data)
+{
+ return nautilus_drag_drop_action_ask (GTK_WIDGET (sidebar), actions);
+}
+
+static GList *
+build_uri_list_from_gfile_list (GList *file_list)
+{
+ GList *result;
+ GList *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 (GtkPlacesSidebar *sidebar,
+ GFile *dest_file,
+ GList *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);
+}
+
+/* Callback used in the "empty trash" menu item from the places sidebar */
+static void
+action_empty_trash (GSimpleAction *action,
+ GVariant *variant,
+ gpointer user_data)
+{
+ NautilusWindow *window = NAUTILUS_WINDOW (user_data);
+
+ nautilus_file_operations_empty_trash (GTK_WIDGET (window), TRUE, NULL);
+}
+
+/* Callback used for the "properties" menu item from the places sidebar */
+static void
+action_properties (GSimpleAction *action,
+ GVariant *variant,
+ gpointer user_data)
+{
+ NautilusWindow *window = NAUTILUS_WINDOW (user_data);
+ GList *list;
+ NautilusFile *file;
+
+ file = nautilus_file_get (window->selected_file);
+
+ list = g_list_append (NULL, file);
+ nautilus_properties_window_present (list, GTK_WIDGET (window), NULL, NULL,
+ NULL);
+ nautilus_file_list_free (list);
+
+ g_clear_object (&window->selected_file);
+}
+
+static gboolean
+check_have_gnome_disks (void)
+{
+ gchar *disks_path;
+ gboolean res;
+
+ disks_path = g_find_program_in_path ("gnome-disks");
+ res = (disks_path != NULL);
+ g_free (disks_path);
+
+ return res;
+}
+
+static gboolean
+should_show_format_command (GVolume *volume)
+{
+ gchar *unix_device_id;
+ gboolean show_format;
+
+ unix_device_id = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
+ show_format = (unix_device_id != NULL) && check_have_gnome_disks ();
+ g_free (unix_device_id);
+
+ return show_format;
+}
+
+static void
+action_restore_tab (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusWindow *window = NAUTILUS_WINDOW (user_data);
+ NautilusWindowOpenFlags flags;
+ g_autoptr (GFile) location = NULL;
+ NautilusWindowSlot *slot;
+ NautilusNavigationState *data;
+
+ if (g_queue_get_length (window->tab_data_queue) == 0)
+ {
+ return;
+ }
+
+ flags = NAUTILUS_WINDOW_OPEN_FLAG_NEW_TAB | NAUTILUS_WINDOW_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, location, flags);
+
+ nautilus_window_slot_open_location_full (slot, location, flags, NULL);
+ nautilus_window_slot_restore_navigation_state (slot, data);
+
+ free_navigation_state (data);
+}
+
+static guint
+get_window_xid (NautilusWindow *window)
+{
+#ifdef GDK_WINDOWING_X11
+ if (GDK_IS_X11_DISPLAY (gtk_widget_get_display (GTK_WIDGET (window))))
+ {
+ GdkWindow *gdk_window = gtk_widget_get_window (GTK_WIDGET (window));
+ return (guint) gdk_x11_window_get_xid (gdk_window);
+ }
+#endif
+ return 0;
+}
+
+static void
+action_format (GSimpleAction *action,
+ GVariant *variant,
+ gpointer user_data)
+{
+ NautilusWindow *window = NAUTILUS_WINDOW (user_data);
+ GAppInfo *app_info;
+ gchar *cmdline, *device_identifier, *xid_string;
+
+ device_identifier = g_volume_get_identifier (window->selected_volume,
+ G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
+ xid_string = g_strdup_printf ("%x", get_window_xid (window));
+
+ cmdline = g_strconcat ("gnome-disks ",
+ "--block-device ", device_identifier, " ",
+ "--format-device ",
+ "--xid ", xid_string,
+ NULL);
+ app_info = g_app_info_create_from_commandline (cmdline, NULL, 0, NULL);
+ g_app_info_launch (app_info, NULL, NULL, NULL);
+
+ g_free (cmdline);
+ g_free (device_identifier);
+ g_free (xid_string);
+ g_clear_object (&app_info);
+ g_clear_object (&window->selected_volume);
+}
+
+static void
+add_menu_separator (GtkWidget *menu)
+{
+ GtkWidget *separator;
+
+ separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
+ gtk_container_add (GTK_CONTAINER (menu), separator);
+ gtk_widget_show (separator);
+}
+
+static void
+places_sidebar_populate_popup_cb (GtkPlacesSidebar *sidebar,
+ GtkWidget *menu,
+ GFile *selected_file,
+ GVolume *selected_volume,
+ gpointer user_data)
+{
+ NautilusWindow *window = NAUTILUS_WINDOW (user_data);
+ GFile *trash;
+ GtkWidget *menu_item;
+ GAction *action;
+
+ g_clear_object (&window->selected_file);
+ g_clear_object (&window->selected_volume);
+
+ if (selected_file)
+ {
+ trash = g_file_new_for_uri ("trash:///");
+ if (g_file_equal (trash, selected_file))
+ {
+ add_menu_separator (menu);
+
+ menu_item = gtk_model_button_new ();
+ gtk_actionable_set_action_name (GTK_ACTIONABLE (menu_item),
+ "win.empty-trash");
+ g_object_set (menu_item, "text", _("Empty _Trash"), NULL);
+ gtk_container_add (GTK_CONTAINER (menu), menu_item);
+ gtk_widget_show (menu_item);
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (window),
+ "empty-trash");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
+ !nautilus_trash_monitor_is_empty ());
+ }
+ g_object_unref (trash);
+
+ if (g_file_is_native (selected_file))
+ {
+ window->selected_file = g_object_ref (selected_file);
+ add_menu_separator (menu);
+
+ menu_item = gtk_model_button_new ();
+ gtk_actionable_set_action_name (GTK_ACTIONABLE (menu_item),
+ "win.properties");
+ g_object_set (menu_item, "text", _("_Properties"), NULL);
+ gtk_container_add (GTK_CONTAINER (menu), menu_item);
+ gtk_widget_show (menu_item);
+ }
+ }
+ if (selected_volume)
+ {
+ if (should_show_format_command (selected_volume))
+ {
+ menu_item = gtk_model_button_new ();
+ gtk_actionable_set_action_name (GTK_ACTIONABLE (menu_item),
+ "win.format");
+ g_object_set (menu_item, "text", _("_Format…"), NULL);
+ if (selected_volume != NULL && G_IS_VOLUME (selected_volume))
+ {
+ window->selected_volume = g_object_ref (selected_volume);
+ }
+ gtk_container_add (GTK_CONTAINER (menu), menu_item);
+ gtk_widget_show (menu_item);
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (window),
+ "format");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
+ selected_volume != NULL &&
+ G_IS_VOLUME (selected_volume));
+ }
+ }
+}
+
+static void
+nautilus_window_set_up_sidebar (NautilusWindow *window)
+{
+ setup_side_pane_width (window);
+ g_signal_connect (window->sidebar,
+ "size-allocate",
+ G_CALLBACK (side_pane_size_allocate_callback),
+ window);
+
+ gtk_places_sidebar_set_open_flags (GTK_PLACES_SIDEBAR (window->places_sidebar),
+ (GTK_PLACES_OPEN_NORMAL
+ | GTK_PLACES_OPEN_NEW_TAB
+ | 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);
+ g_signal_connect (window->places_sidebar, "drag-action-ask",
+ G_CALLBACK (places_sidebar_drag_action_ask_cb), window);
+ g_signal_connect (window->places_sidebar, "drag-perform-drop",
+ G_CALLBACK (places_sidebar_drag_perform_drop_cb), window);
+ g_signal_connect (window->places_sidebar, "populate-popup",
+ G_CALLBACK (places_sidebar_populate_popup_cb), window);
+ g_signal_connect (window->places_sidebar, "unmount",
+ G_CALLBACK (places_sidebar_unmount_operation_cb), window);
+}
+
+void
+nautilus_window_hide_sidebar (NautilusWindow *window)
+{
+ DEBUG ("Called hide_sidebar()");
+
+ g_return_if_fail (NAUTILUS_IS_WINDOW (window));
+
+ gtk_widget_hide (window->sidebar);
+}
+
+void
+nautilus_window_show_sidebar (NautilusWindow *window)
+{
+ DEBUG ("Called show_sidebar()");
+
+ g_return_if_fail (NAUTILUS_IS_WINDOW (window));
+
+ gtk_widget_show (window->sidebar);
+ setup_side_pane_width (window);
+}
+
+static inline NautilusWindowSlot *
+get_first_inactive_slot (NautilusWindow *window)
+{
+ GList *l;
+ NautilusWindowSlot *slot;
+
+ for (l = window->slots; l != NULL; l = l->next)
+ {
+ slot = NAUTILUS_WINDOW_SLOT (l->data);
+ if (slot != window->active_slot)
+ {
+ return slot;
+ }
+ }
+
+ return NULL;
+}
+
+void
+nautilus_window_slot_close (NautilusWindow *window,
+ NautilusWindowSlot *slot)
+{
+ NautilusWindowSlot *next_slot;
+ NautilusNavigationState *data;
+
+ DEBUG ("Requesting to remove slot %p from window %p", slot, window);
+ if (window == NULL)
+ {
+ return;
+ }
+
+ if (window->active_slot == slot)
+ {
+ next_slot = get_first_inactive_slot (window);
+ nautilus_window_set_active_slot (window, next_slot);
+ }
+
+ data = nautilus_window_slot_get_navigation_state (slot);
+ if (data != NULL)
+ {
+ g_queue_push_head (window->tab_data_queue, data);
+ }
+
+ close_slot (window, slot, TRUE);
+
+ /* 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 = nautilus_window_slot_get_location (slot);
+
+ 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;
+ 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);
+ }
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (window), "back");
+ enabled = nautilus_window_slot_get_back_history (slot) != NULL;
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), enabled);
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (window), "forward");
+ enabled = nautilus_window_slot_get_forward_history (slot) != NULL;
+ 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 void
+remove_notifications (NautilusWindow *window)
+{
+ GtkRevealerTransitionType transition_type;
+
+ /* Hide it inmediatily so we can animate the new notification. */
+ transition_type = gtk_revealer_get_transition_type (GTK_REVEALER (window->in_app_notification_undo));
+ gtk_revealer_set_transition_type (GTK_REVEALER (window->in_app_notification_undo),
+ GTK_REVEALER_TRANSITION_TYPE_NONE);
+ gtk_revealer_set_reveal_child (GTK_REVEALER (window->in_app_notification_undo),
+ FALSE);
+ gtk_revealer_set_transition_type (GTK_REVEALER (window->in_app_notification_undo),
+ transition_type);
+ if (window->in_app_notification_undo_timeout_id != 0)
+ {
+ g_source_remove (window->in_app_notification_undo_timeout_id);
+ window->in_app_notification_undo_timeout_id = 0;
+ }
+
+ transition_type = gtk_revealer_get_transition_type (GTK_REVEALER (window->notification_operation));
+ gtk_revealer_set_transition_type (GTK_REVEALER (window->notification_operation),
+ GTK_REVEALER_TRANSITION_TYPE_NONE);
+ gtk_revealer_set_reveal_child (GTK_REVEALER (window->notification_operation),
+ FALSE);
+ gtk_revealer_set_transition_type (GTK_REVEALER (window->notification_operation),
+ transition_type);
+ if (window->notification_operation_timeout_id != 0)
+ {
+ g_source_remove (window->notification_operation_timeout_id);
+ window->notification_operation_timeout_id = 0;
+ }
+}
+
+static void
+hide_in_app_notification_undo (NautilusWindow *window)
+{
+ if (window->in_app_notification_undo_timeout_id != 0)
+ {
+ g_source_remove (window->in_app_notification_undo_timeout_id);
+ window->in_app_notification_undo_timeout_id = 0;
+ }
+
+ gtk_revealer_set_reveal_child (GTK_REVEALER (window->in_app_notification_undo), FALSE);
+}
+
+static void
+on_in_app_notification_undo_undo_button_clicked (GtkWidget *notification,
+ NautilusWindow *window)
+{
+ hide_in_app_notification_undo (window);
+
+ nautilus_file_undo_manager_undo (GTK_WINDOW (window), NULL);
+}
+
+static void
+on_in_app_notification_undo_close_button_clicked (GtkWidget *notification,
+ NautilusWindow *window)
+{
+ hide_in_app_notification_undo (window);
+}
+
+static gboolean
+on_in_app_notification_undo_timeout (NautilusWindow *window)
+{
+ hide_in_app_notification_undo (window);
+
+ return G_SOURCE_REMOVE;
+}
+
+static gchar *
+in_app_notification_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” deleted"), 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 *
+in_app_notification_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;
+
+ 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_notification = 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_has_toplevel_focus (GTK_WINDOW (window)))
+ {
+ popup_notification = TRUE;
+ label = in_app_notification_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_has_toplevel_focus (GTK_WINDOW (window)) &&
+ !nautilus_file_undo_info_starred_is_starred (NAUTILUS_FILE_UNDO_INFO_STARRED (undo_info)))
+ {
+ popup_notification = TRUE;
+ label = in_app_notification_undo_unstar_get_label (undo_info);
+ }
+ }
+
+ if (popup_notification)
+ {
+ remove_notifications (window);
+ gtk_label_set_markup (GTK_LABEL (window->in_app_notification_undo_label),
+ label);
+ gtk_revealer_set_reveal_child (GTK_REVEALER (window->in_app_notification_undo),
+ TRUE);
+ window->in_app_notification_undo_timeout_id = g_timeout_add_seconds (NOTIFICATION_TIMEOUT,
+ (GSourceFunc) on_in_app_notification_undo_timeout,
+ window);
+ }
+ }
+ else
+ {
+ hide_in_app_notification_undo (window);
+ }
+}
+
+static void
+hide_notification_operation (NautilusWindow *window)
+{
+ if (window->notification_operation_timeout_id != 0)
+ {
+ g_source_remove (window->notification_operation_timeout_id);
+ window->notification_operation_timeout_id = 0;
+ }
+
+ gtk_revealer_set_reveal_child (GTK_REVEALER (window->notification_operation), FALSE);
+ g_clear_object (&window->folder_to_open);
+}
+
+static void
+on_notification_operation_open_clicked (GtkWidget *notification,
+ NautilusWindow *window)
+{
+ nautilus_window_open_location_full (window, window->folder_to_open,
+ 0, NULL, NULL);
+ hide_notification_operation (window);
+}
+
+static void
+on_notification_operation_close_clicked (GtkWidget *notification,
+ NautilusWindow *window)
+{
+ hide_notification_operation (window);
+}
+
+static gboolean
+on_notification_operation_timeout (NautilusWindow *window)
+{
+ hide_notification_operation (window);
+
+ return FALSE;
+}
+
+void
+nautilus_window_show_operation_notification (NautilusWindow *window,
+ gchar *main_label,
+ GFile *folder_to_open)
+{
+ gchar *button_label;
+ gchar *folder_name;
+ NautilusFile *folder;
+ GFile *current_location;
+
+ current_location = nautilus_window_slot_get_location (window->active_slot);
+ if (gtk_window_has_toplevel_focus (GTK_WINDOW (window)))
+ {
+ remove_notifications (window);
+ gtk_label_set_text (GTK_LABEL (window->notification_operation_label),
+ main_label);
+
+ if (g_file_equal (folder_to_open, current_location))
+ {
+ gtk_widget_hide (window->notification_operation_open);
+ }
+ else
+ {
+ gtk_widget_show (window->notification_operation_open);
+ window->folder_to_open = g_object_ref (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);
+ gtk_button_set_label (GTK_BUTTON (window->notification_operation_open),
+ button_label);
+ nautilus_file_unref (folder);
+ g_free (folder_name);
+ g_free (button_label);
+ }
+
+ gtk_revealer_set_reveal_child (GTK_REVEALER (window->notification_operation), TRUE);
+ window->notification_operation_timeout_id = g_timeout_add_seconds (NOTIFICATION_TIMEOUT,
+ (GSourceFunc) on_notification_operation_timeout,
+ window);
+ }
+}
+
+static void
+path_bar_location_changed_callback (GtkWidget *widget,
+ GFile *location,
+ NautilusWindow *window)
+{
+ nautilus_window_open_location_full (window, location, 0, NULL, NULL);
+}
+
+static void
+notebook_popup_menu_new_tab_cb (GtkMenuItem *menuitem,
+ gpointer user_data)
+{
+ NautilusWindow *window = user_data;
+
+ nautilus_window_new_tab (window);
+}
+
+static void
+notebook_popup_menu_move_left_cb (GtkMenuItem *menuitem,
+ gpointer user_data)
+{
+ NautilusWindow *window = user_data;
+
+ nautilus_notebook_reorder_current_child_relative (NAUTILUS_NOTEBOOK (window->notebook), -1);
+}
+
+static void
+notebook_popup_menu_move_right_cb (GtkMenuItem *menuitem,
+ gpointer user_data)
+{
+ NautilusWindow *window = user_data;
+
+
+ nautilus_notebook_reorder_current_child_relative (NAUTILUS_NOTEBOOK (window->notebook), 1);
+}
+
+static void
+notebook_popup_menu_close_cb (GtkMenuItem *menuitem,
+ gpointer user_data)
+{
+ NautilusWindow *window = user_data;
+ NautilusWindowSlot *slot;
+
+ slot = window->active_slot;
+ nautilus_window_slot_close (window, slot);
+}
+
+static void
+notebook_popup_menu_show (NautilusWindow *window,
+ const GdkEvent *event)
+{
+ GtkWidget *popup;
+ GtkWidget *item;
+ gboolean can_move_left, can_move_right;
+ NautilusNotebook *notebook;
+
+ notebook = NAUTILUS_NOTEBOOK (window->notebook);
+
+ can_move_left = nautilus_notebook_can_reorder_current_child_relative (notebook, -1);
+ can_move_right = nautilus_notebook_can_reorder_current_child_relative (notebook, 1);
+
+ popup = gtk_menu_new ();
+
+ item = gtk_menu_item_new_with_mnemonic (_("_New Tab"));
+ g_signal_connect (item, "activate",
+ G_CALLBACK (notebook_popup_menu_new_tab_cb),
+ window);
+ gtk_menu_shell_append (GTK_MENU_SHELL (popup),
+ item);
+
+ gtk_menu_shell_append (GTK_MENU_SHELL (popup),
+ gtk_separator_menu_item_new ());
+
+ item = gtk_menu_item_new_with_mnemonic (_("Move Tab _Left"));
+ g_signal_connect (item, "activate",
+ G_CALLBACK (notebook_popup_menu_move_left_cb),
+ window);
+ gtk_menu_shell_append (GTK_MENU_SHELL (popup),
+ item);
+ gtk_widget_set_sensitive (item, can_move_left);
+
+ item = gtk_menu_item_new_with_mnemonic (_("Move Tab _Right"));
+ g_signal_connect (item, "activate",
+ G_CALLBACK (notebook_popup_menu_move_right_cb),
+ window);
+ gtk_menu_shell_append (GTK_MENU_SHELL (popup),
+ item);
+ gtk_widget_set_sensitive (item, can_move_right);
+
+ gtk_menu_shell_append (GTK_MENU_SHELL (popup),
+ gtk_separator_menu_item_new ());
+
+ item = gtk_menu_item_new_with_mnemonic (_("_Close Tab"));
+ g_signal_connect (item, "activate",
+ G_CALLBACK (notebook_popup_menu_close_cb), window);
+ gtk_menu_shell_append (GTK_MENU_SHELL (popup),
+ item);
+
+ gtk_widget_show_all (popup);
+
+ gtk_menu_popup_at_pointer (GTK_MENU (popup), event);
+}
+
+/* emitted when the user clicks the "close" button of tabs */
+static void
+notebook_tab_close_requested (NautilusNotebook *notebook,
+ NautilusWindowSlot *slot,
+ NautilusWindow *window)
+{
+ nautilus_window_slot_close (window, slot);
+}
+
+static void
+notebook_button_press_cb (GtkGestureMultiPress *gesture,
+ gint n_press,
+ gdouble x,
+ gdouble y,
+ gpointer user_data)
+{
+ NautilusWindow *window;
+ GdkEventSequence *sequence;
+ const GdkEvent *event;
+
+ window = NAUTILUS_WINDOW (user_data);
+
+ if (nautilus_notebook_content_area_hit (NAUTILUS_NOTEBOOK (window->notebook), x, y))
+ {
+ return;
+ }
+
+ sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
+ event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
+
+ notebook_popup_menu_show (window, event);
+}
+
+static gboolean
+notebook_popup_menu_cb (GtkWidget *widget,
+ gpointer user_data)
+{
+ NautilusWindow *window = user_data;
+ notebook_popup_menu_show (window, NULL);
+ return TRUE;
+}
+
+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;
+
+
+ g_object_set (window->toolbar, "window", window, NULL);
+
+ /* connect to the pathbar signals */
+ path_bar = nautilus_toolbar_get_path_bar (NAUTILUS_TOOLBAR (window->toolbar));
+
+ g_signal_connect_object (path_bar, "path-clicked",
+ G_CALLBACK (path_bar_location_changed_callback), window, 0);
+ g_signal_connect_swapped (path_bar, "open-location",
+ G_CALLBACK (open_location_cb), 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);
+
+ gtk_window_set_titlebar (GTK_WINDOW (window), window->toolbar);
+}
+
+static void
+notebook_page_removed_cb (GtkNotebook *notebook,
+ GtkWidget *page,
+ guint page_num,
+ gpointer user_data)
+{
+ NautilusWindow *window = user_data;
+ NautilusWindowSlot *slot = NAUTILUS_WINDOW_SLOT (page), *next_slot;
+ gboolean dnd_slot;
+
+ dnd_slot = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (slot), "dnd-window-slot"));
+ if (!dnd_slot)
+ {
+ return;
+ }
+
+ if (window->active_slot == slot)
+ {
+ next_slot = get_first_inactive_slot (window);
+ nautilus_window_set_active_slot (window, next_slot);
+ }
+
+ close_slot (window, slot, FALSE);
+}
+
+static void
+notebook_page_added_cb (GtkNotebook *notebook,
+ GtkWidget *page,
+ guint page_num,
+ gpointer user_data)
+{
+ NautilusWindow *window = user_data;
+ NautilusWindowSlot *slot = NAUTILUS_WINDOW_SLOT (page);
+ NautilusWindowSlot *dummy_slot;
+ gboolean dnd_slot;
+
+ dnd_slot = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (slot), "dnd-window-slot"));
+ if (!dnd_slot)
+ {
+ return;
+ }
+
+ g_object_set_data (G_OBJECT (page), "dnd-window-slot",
+ GINT_TO_POINTER (FALSE));
+
+ nautilus_window_slot_set_window (slot, window);
+ window->slots = g_list_append (window->slots, slot);
+ g_signal_emit (window, signals[SLOT_ADDED], 0, slot);
+
+ nautilus_window_set_active_slot (window, slot);
+
+ dummy_slot = g_list_nth_data (window->slots, 0);
+ if (dummy_slot != NULL)
+ {
+ close_slot (window, dummy_slot, TRUE);
+ }
+
+ gtk_widget_show (GTK_WIDGET (window));
+}
+
+static GtkNotebook *
+notebook_create_window_cb (GtkNotebook *notebook,
+ GtkWidget *page,
+ gint x,
+ gint y,
+ gpointer user_data)
+{
+ NautilusApplication *app;
+ NautilusWindow *new_window;
+ NautilusWindowSlot *slot;
+
+ if (!NAUTILUS_IS_WINDOW_SLOT (page))
+ {
+ return NULL;
+ }
+
+ app = NAUTILUS_APPLICATION (g_application_get_default ());
+ new_window = nautilus_application_create_window
+ (app, gtk_widget_get_screen (GTK_WIDGET (notebook)));
+
+ slot = NAUTILUS_WINDOW_SLOT (page);
+ g_object_set_data (G_OBJECT (slot), "dnd-window-slot",
+ GINT_TO_POINTER (TRUE));
+
+ gtk_window_set_position (GTK_WINDOW (new_window), GTK_WIN_POS_MOUSE);
+
+ return GTK_NOTEBOOK (new_window->notebook);
+}
+
+static void
+setup_notebook (NautilusWindow *window)
+{
+ g_signal_connect (window->notebook, "tab-close-request",
+ G_CALLBACK (notebook_tab_close_requested),
+ window);
+ g_signal_connect (window->notebook, "popup-menu",
+ G_CALLBACK (notebook_popup_menu_cb),
+ window);
+ g_signal_connect (window->notebook, "switch-page",
+ G_CALLBACK (notebook_switch_page_cb),
+ window);
+ g_signal_connect (window->notebook, "create-window",
+ G_CALLBACK (notebook_create_window_cb),
+ window);
+ g_signal_connect (window->notebook, "page-added",
+ G_CALLBACK (notebook_page_added_cb),
+ window);
+ g_signal_connect (window->notebook, "page-removed",
+ G_CALLBACK (notebook_page_removed_cb),
+ window);
+
+ g_signal_connect (window->notebook_multi_press_gesture, "pressed",
+ G_CALLBACK (notebook_button_press_cb),
+ window);
+}
+
+const GActionEntry win_entries[] =
+{
+ { "back", action_back },
+ { "forward", action_forward },
+ { "up", action_up },
+ { "view-menu", action_toggle_state_view_button, NULL, "false", NULL },
+ { "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 },
+ { "tab-previous", action_tab_previous },
+ { "tab-next", action_tab_next },
+ { "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 },
+ { "empty-trash", action_empty_trash },
+ { "properties", action_properties },
+ { "format", action_format },
+ { "restore-tab", action_restore_tab },
+};
+
+static void
+nautilus_window_initialize_actions (NautilusWindow *window)
+{
+ GApplication *app;
+ GAction *action;
+ GVariant *state;
+ gchar detailed_action[80];
+ gchar accel[80];
+ gint i;
+ const gchar *reload_accels[] =
+ {
+ "F5",
+ "<ctrl>r",
+ NULL
+ };
+ const gchar *prompt_root_location_accels[] =
+ {
+ "slash",
+ "KP_Divide",
+ NULL
+ };
+ const gchar *prompt_home_location_accels[] =
+ {
+ "asciitilde",
+ "dead_tilde",
+ NULL
+ };
+
+ g_action_map_add_action_entries (G_ACTION_MAP (window),
+ win_entries, G_N_ELEMENTS (win_entries),
+ window);
+
+ app = g_application_get_default ();
+ nautilus_application_set_accelerator (app, "win.back", "<alt>Left");
+ nautilus_application_set_accelerator (app, "win.forward", "<alt>Right");
+ nautilus_application_set_accelerator (app, "win.enter-location", "<control>l");
+ 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", reload_accels);
+
+ 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_accelerator (app, "win.bookmark-current-location", "<control>d");
+ nautilus_application_set_accelerator (app, "win.up", "<alt>Up");
+ nautilus_application_set_accelerator (app, "win.go-home", "<alt>Home");
+ nautilus_application_set_accelerator (app, "win.tab-previous", "<control>Page_Up");
+ nautilus_application_set_accelerator (app, "win.tab-next", "<control>Page_Down");
+ 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", prompt_root_location_accels);
+ /* Support keyboard layouts which have a dead tilde key but not a tilde key. */
+ nautilus_application_set_accelerators (app, "win.prompt-home-location", prompt_home_location_accels);
+ nautilus_application_set_accelerator (app, "win.view-menu", "F10");
+ nautilus_application_set_accelerator (app, "win.restore-tab", "<shift><control>t");
+
+ /* 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);
+ }
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (app), "show-hide-sidebar");
+ state = g_action_get_state (action);
+ if (g_variant_get_boolean (state))
+ {
+ nautilus_window_show_sidebar (window);
+ }
+
+ g_variant_unref (state);
+}
+
+
+static void
+nautilus_window_constructed (GObject *self)
+{
+ NautilusWindow *window;
+ NautilusWindowSlot *slot;
+ 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_notebook (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);
+
+ slot = nautilus_window_create_and_init_slot (window, NULL, 0);
+ nautilus_window_set_active_slot (window, slot);
+
+ window->bookmarks_id =
+ g_signal_connect_swapped (nautilus_application_get_bookmarks (application), "changed",
+ G_CALLBACK (nautilus_window_sync_bookmarks), window);
+
+ nautilus_toolbar_on_window_constructed (NAUTILUS_TOOLBAR (window->toolbar));
+
+ nautilus_profile_end (NULL);
+}
+
+static gint
+sort_slots_active_last (NautilusWindowSlot *a,
+ NautilusWindowSlot *b,
+ NautilusWindow *window)
+{
+ if (window->active_slot == a)
+ {
+ return 1;
+ }
+ if (window->active_slot == b)
+ {
+ return -1;
+ }
+ return 0;
+}
+
+static void
+destroy_slots_foreach (gpointer data,
+ gpointer user_data)
+{
+ NautilusWindowSlot *slot = data;
+ NautilusWindow *window = user_data;
+
+ close_slot (window, slot, TRUE);
+}
+
+static void
+nautilus_window_destroy (GtkWidget *object)
+{
+ NautilusWindow *window;
+ NautilusApplication *application;
+ GList *slots_copy;
+
+ window = NAUTILUS_WINDOW (object);
+ application = NAUTILUS_APPLICATION (gtk_window_get_application (GTK_WINDOW (window)));
+
+ DEBUG ("Destroying window");
+
+ /* close all slots safely */
+ slots_copy = g_list_copy (window->slots);
+ if (window->active_slot != NULL)
+ {
+ /* Make sure active slot is last one to be closed, to avoid default activation
+ * of others slots when closing the active one, see bug #741952 */
+ slots_copy = g_list_sort_with_data (slots_copy, (GCompareDataFunc) sort_slots_active_last, window);
+ }
+ g_list_foreach (slots_copy, (GFunc) destroy_slots_foreach, window);
+ g_list_free (slots_copy);
+
+ /* the slots list should now be empty */
+ g_assert (window->slots == NULL);
+
+ window->active_slot = NULL;
+
+ g_clear_signal_handler (&window->bookmarks_id, nautilus_application_get_bookmarks (application));
+
+ g_clear_handle_id (&window->in_app_notification_undo_timeout_id, g_source_remove);
+
+ nautilus_window_unexport_handle (window);
+
+ GTK_WIDGET_CLASS (nautilus_window_parent_class)->destroy (object);
+}
+
+static void
+nautilus_window_dispose (GObject *object)
+{
+ NautilusWindow *window;
+
+ window = NAUTILUS_WINDOW (object);
+
+ g_clear_object (&window->notebook_multi_press_gesture);
+
+ 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;
+ }
+
+ if (window->in_app_notification_undo_timeout_id != 0)
+ {
+ g_source_remove (window->in_app_notification_undo_timeout_id);
+ window->in_app_notification_undo_timeout_id = 0;
+ }
+
+ if (window->notification_operation_timeout_id != 0)
+ {
+ g_source_remove (window->notification_operation_timeout_id);
+ window->notification_operation_timeout_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);
+
+ g_object_unref (window->pad_controller);
+
+ /* 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)
+{
+ gboolean is_maximized;
+
+ g_assert (NAUTILUS_IS_WINDOW (window));
+
+ if (gtk_widget_get_window (GTK_WIDGET (window)))
+ {
+ gint width;
+ gint height;
+ GVariant *initial_size;
+
+ gtk_window_get_size (GTK_WINDOW (window), &width, &height);
+
+ initial_size = g_variant_new_parsed ("(%i, %i)", width, height);
+ is_maximized = gdk_window_get_state (gtk_widget_get_window (GTK_WIDGET (window)))
+ & GDK_WINDOW_STATE_MAXIMIZED;
+
+ if (!is_maximized)
+ {
+ g_settings_set_value (nautilus_window_state,
+ NAUTILUS_WINDOW_STATE_INITIAL_SIZE,
+ initial_size);
+ }
+
+ g_settings_set_boolean
+ (nautilus_window_state, NAUTILUS_WINDOW_STATE_MAXIMIZED,
+ is_maximized);
+ }
+}
+
+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);
+
+ gtk_widget_destroy (GTK_WIDGET (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);
+ nautilus_toolbar_set_window_slot (NAUTILUS_TOOLBAR (window->toolbar), NULL);
+ }
+
+ 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);
+ nautilus_toolbar_set_window_slot (NAUTILUS_TOOLBAR (window->toolbar), new_slot);
+
+ on_location_changed (window);
+ g_signal_emit (window, signals[ACTIVE_SELECTION_CHANGED], 0);
+ }
+}
+
+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_press_event (GtkWidget *widget,
+ GdkEventKey *event)
+{
+ NautilusWindow *window;
+ guint keyval;
+ GtkWidget *focus_widget;
+
+ window = NAUTILUS_WINDOW (widget);
+
+ if (G_UNLIKELY (!gdk_event_get_keyval ((GdkEvent *) event, &keyval)))
+ {
+ g_return_val_if_reached (GDK_EVENT_PROPAGATE);
+ }
+
+ focus_widget = gtk_window_get_focus (GTK_WINDOW (window));
+ 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 accelerator bindings too.
+ */
+ if (gtk_window_propagate_key_event (GTK_WINDOW (window),
+ (GdkEventKey *) event))
+ {
+ return GDK_EVENT_STOP;
+ }
+ }
+
+ for (int i = 0; i < G_N_ELEMENTS (extra_window_keybindings); i++)
+ {
+ if (extra_window_keybindings[i].keyval == keyval)
+ {
+ GAction *action;
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (window), extra_window_keybindings[i].action);
+
+ g_assert (action != NULL);
+ if (g_action_get_enabled (action))
+ {
+ g_action_activate (action, NULL);
+ return GDK_EVENT_STOP;
+ }
+
+ break;
+ }
+ }
+
+ if (GTK_WIDGET_CLASS (nautilus_window_parent_class)->key_press_event (widget, event))
+ {
+ return GDK_EVENT_STOP;
+ }
+
+ if (nautilus_window_slot_handle_event (window->active_slot, (GdkEvent *) event))
+ {
+ 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));
+ }
+
+ nautilus_notebook_sync_tab_label (NAUTILUS_NOTEBOOK (window->notebook), slot);
+}
+
+#ifdef GDK_WINDOWING_WAYLAND
+typedef struct
+{
+ NautilusWindow *window;
+ NautilusWindowHandleExported callback;
+ gpointer user_data;
+} WaylandWindowHandleExportedData;
+
+static void
+wayland_window_handle_exported (GdkWindow *window,
+ 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))))
+ {
+ GdkWindow *gdk_window = gtk_widget_get_window (GTK_WIDGET (window));
+ WaylandWindowHandleExportedData *data;
+
+ data = g_new0 (WaylandWindowHandleExportedData, 1);
+ data->window = window;
+ data->callback = callback;
+ data->user_data = user_data;
+
+ if (!gdk_wayland_window_export_handle (gdk_window,
+ 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))))
+ {
+ GdkWindow *gdk_window = gtk_widget_get_window (GTK_WIDGET (window));
+ if (gdk_window != NULL)
+ {
+ gdk_wayland_window_unexport_handle (gdk_window);
+ }
+ }
+#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_delete_event (GtkWidget *widget,
+ GdkEventAny *event)
+{
+ nautilus_window_close (NAUTILUS_WINDOW (widget));
+ return FALSE;
+}
+
+static void
+on_multi_press_gesture_pressed (GtkGestureMultiPress *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, 0);
+ }
+ else if (mouse_extra_buttons && (button == mouse_forward_button))
+ {
+ nautilus_window_back_or_forward (window, FALSE, 0, 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;
+
+ g_type_ensure (NAUTILUS_TYPE_TOOLBAR);
+ g_type_ensure (NAUTILUS_TYPE_NOTEBOOK);
+ gtk_widget_init_template (GTK_WIDGET (window));
+
+ g_signal_connect (window, "notify::is-maximized",
+ G_CALLBACK (on_is_maximized_changed), NULL);
+
+ g_signal_connect_object (window->in_app_notification_undo_close_button, "clicked",
+ G_CALLBACK (on_in_app_notification_undo_close_button_clicked), window, 0);
+ g_signal_connect_object (window->in_app_notification_undo_undo_button, "clicked",
+ G_CALLBACK (on_in_app_notification_undo_undo_button_clicked), window, 0);
+
+ window->slots = NULL;
+ window->active_slot = NULL;
+
+ gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (window)),
+ "nautilus-window");
+
+ window->toolbar = nautilus_toolbar_new ();
+
+ 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 ();
+
+ window->pad_controller = gtk_pad_controller_new (GTK_WINDOW (window),
+ G_ACTION_GROUP (window),
+ NULL);
+ gtk_pad_controller_set_action_entries (window->pad_controller,
+ pad_actions, G_N_ELEMENTS (pad_actions));
+
+ window->multi_press_gesture = gtk_gesture_multi_press_new (GTK_WIDGET (window));
+
+ gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (window->multi_press_gesture),
+ GTK_PHASE_CAPTURE);
+ gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (window->multi_press_gesture), 0);
+
+ g_signal_connect (window->multi_press_gesture, "pressed",
+ G_CALLBACK (on_multi_press_gesture_pressed), NULL);
+
+ window->notebook_multi_press_gesture = gtk_gesture_multi_press_new (window->notebook);
+
+ gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (window->notebook_multi_press_gesture),
+ GTK_PHASE_CAPTURE);
+ gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (window->notebook_multi_press_gesture),
+ GDK_BUTTON_SECONDARY);
+}
+
+static void
+nautilus_window_class_init (NautilusWindowClass *class)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (class);
+ GtkWidgetClass *wclass = GTK_WIDGET_CLASS (class);
+
+ oclass->dispose = nautilus_window_dispose;
+ oclass->finalize = nautilus_window_finalize;
+ oclass->constructed = nautilus_window_constructed;
+
+ wclass->destroy = nautilus_window_destroy;
+ wclass->show = nautilus_window_show;
+ wclass->realize = nautilus_window_realize;
+ wclass->key_press_event = nautilus_window_key_press_event;
+ wclass->delete_event = nautilus_window_delete_event;
+ wclass->grab_focus = nautilus_window_grab_focus;
+
+ gtk_widget_class_set_template_from_resource (wclass,
+ "/org/gnome/nautilus/ui/nautilus-window.ui");
+ gtk_widget_class_bind_template_child (wclass, NautilusWindow, content_paned);
+ gtk_widget_class_bind_template_child (wclass, NautilusWindow, sidebar);
+ gtk_widget_class_bind_template_child (wclass, NautilusWindow, places_sidebar);
+ gtk_widget_class_bind_template_child (wclass, NautilusWindow, main_view);
+ gtk_widget_class_bind_template_child (wclass, NautilusWindow, notebook);
+ gtk_widget_class_bind_template_child (wclass, NautilusWindow, in_app_notification_undo);
+ gtk_widget_class_bind_template_child (wclass, NautilusWindow, in_app_notification_undo_label);
+ gtk_widget_class_bind_template_child (wclass, NautilusWindow, in_app_notification_undo_undo_button);
+ gtk_widget_class_bind_template_child (wclass, NautilusWindow, in_app_notification_undo_close_button);
+ gtk_widget_class_bind_template_child (wclass, NautilusWindow, notification_operation);
+ gtk_widget_class_bind_template_child (wclass, NautilusWindow, notification_operation_label);
+ gtk_widget_class_bind_template_child (wclass, NautilusWindow, notification_operation_open);
+ gtk_widget_class_bind_template_child (wclass, NautilusWindow, notification_operation_close);
+
+ gtk_widget_class_bind_template_callback (wclass, places_sidebar_show_other_locations_with_flags);
+ gtk_widget_class_bind_template_callback (wclass, places_sidebar_show_starred_location);
+
+ 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);
+ signals[ACTIVE_SELECTION_CHANGED] =
+ g_signal_new ("active-selection-changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ 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);
+
+ gtk_widget_class_bind_template_callback (wclass, on_notification_operation_open_clicked);
+ gtk_widget_class_bind_template_callback (wclass, on_notification_operation_close_clicked);
+}
+
+NautilusWindow *
+nautilus_window_new (GdkScreen *screen)
+{
+ return g_object_new (NAUTILUS_TYPE_WINDOW,
+ "icon-name", APPLICATION_ID,
+ "screen", screen,
+ NULL);
+}
+
+NautilusWindowOpenFlags
+nautilus_event_get_window_open_flags (void)
+{
+ NautilusWindowOpenFlags flags = 0;
+ GdkEvent *event;
+
+ event = gtk_get_current_event ();
+
+ if (event == NULL)
+ {
+ return flags;
+ }
+
+ if ((event->type == GDK_BUTTON_PRESS || event->type == GDK_BUTTON_RELEASE) &&
+ (event->button.button == GDK_BUTTON_MIDDLE))
+ {
+ flags |= NAUTILUS_WINDOW_OPEN_FLAG_NEW_TAB;
+ }
+
+ gdk_event_free (event);
+
+ return flags;
+}
+
+void
+nautilus_window_show_about_dialog (NautilusWindow *window)
+{
+ const gchar *artists[] =
+ {
+ "The GNOME Project",
+ NULL
+ };
+ const gchar *authors[] =
+ {
+ "Alexander Larsson",
+ "Ali Abdin",
+ "Anders Carlsson",
+ "Andrew Walton",
+ "Andy Hertzfeld",
+ "Arlo Rose",
+ "Christian Neumair",
+ "Cosimo Cecchi",
+ "Darin Adler",
+ "David Camp",
+ "Eli Goldberg",
+ "Elliot Lee",
+ "Eskil Heyn Olsen",
+ "Ettore Perazzoli",
+ "Gene Z. Ragan",
+ "George Lebl",
+ "Ian McKellar",
+ "J Shane Culpepper",
+ "James Willcox",
+ "Jan Arne Petersen",
+ "John Harper",
+ "John Sullivan",
+ "Josh Barrow",
+ "Maciej Stachowiak",
+ "Mark McLoughlin",
+ "Mathieu Lacage",
+ "Mike Engber",
+ "Mike Fleming",
+ "Pavel Cisler",
+ "Ramiro Estrugo",
+ "Raph Levien",
+ "Rebecca Schulman",
+ "Robey Pointer",
+ "Robin * Slomkowski",
+ "Seth Nickell",
+ "Susan Kare",
+ "Tomas Bzatek",
+ "William Jon McCann",
+ NULL
+ };
+ const gchar *documenters[] =
+ {
+ "GNOME Documentation Team",
+ "Sun Microsystems",
+ NULL
+ };
+ g_autofree gchar *program_name = NULL;
+
+ /* “Files” is the generic application name and the suffix is
+ * an arbitrary and deliberately unlocalized string only shown
+ * in development builds.
+ */
+ program_name = g_strconcat (_("Files"), NAME_SUFFIX, NULL);
+
+ gtk_show_about_dialog (window ? GTK_WINDOW (window) : NULL,
+ "program-name", program_name,
+ "version", VERSION,
+ "comments", _("Access and organize your files"),
+ "website", "https://wiki.gnome.org/action/show/Apps/Files",
+ "copyright", "© 1999–2018 The Files Authors",
+ "license-type", GTK_LICENSE_GPL_3_0,
+ "artists", artists,
+ "authors", authors,
+ "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"),
+ "logo-icon-name", APPLICATION_ID,
+ 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");
+ }
+}
+
+/* Ideally, this method should be a simple wrapper for the slot method. However,
+ * going back or forward can result in a new slot (or another subclass), so we
+ * workaround that by duplicating part of nautilus_window_slot_back_or_forward()
+ */
+void
+nautilus_window_back_or_forward (NautilusWindow *window,
+ gboolean back,
+ guint distance,
+ NautilusWindowOpenFlags flags)
+{
+ NautilusWindowSlot *slot;
+ GList *next_location_list, *back_list, *forward_list;
+ GFile *next_location;
+ guint len;
+ NautilusBookmark *next_location_bookmark;
+ gboolean active_slot_handles_location;
+
+ slot = nautilus_window_get_active_slot (window);
+ back_list = nautilus_window_slot_get_back_history (slot);
+ forward_list = nautilus_window_slot_get_forward_history (slot);
+
+ next_location_list = back ? back_list : forward_list;
+
+ len = (guint) g_list_length (next_location_list);
+
+ /* If we can't move in the direction at all, just return. */
+ if (len == 0)
+ {
+ 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;
+ }
+
+ next_location_bookmark = g_list_nth_data (next_location_list, distance);
+ next_location = nautilus_bookmark_get_location (next_location_bookmark);
+
+ active_slot_handles_location = nautilus_window_slot_handles_location (slot, next_location);
+
+ if (!active_slot_handles_location)
+ {
+ NautilusNavigationState *navigation_state;
+ NautilusLocationChangeType location_change_type;
+
+ navigation_state = nautilus_window_slot_get_navigation_state (slot);
+
+ location_change_type = back ? NAUTILUS_LOCATION_CHANGE_BACK : NAUTILUS_LOCATION_CHANGE_FORWARD;
+
+ slot = replace_active_slot (window, next_location, flags);
+
+ nautilus_window_slot_open_location_set_navigation_state (slot,
+ next_location, flags, NULL,
+ location_change_type,
+ navigation_state, distance);
+
+ free_navigation_state (navigation_state);
+ }
+ else
+ {
+ nautilus_window_slot_back_or_forward (slot, back, distance, flags);
+ }
+}
diff --git a/src/nautilus-window.h b/src/nautilus-window.h
new file mode 100644
index 0000000..ac8833a
--- /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 "nautilus-types.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_WINDOW (nautilus_window_get_type ())
+G_DECLARE_FINAL_TYPE (NautilusWindow, nautilus_window, NAUTILUS, WINDOW, GtkApplicationWindow);
+
+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
+
+NautilusWindow * nautilus_window_new (GdkScreen *screen);
+void nautilus_window_close (NautilusWindow *window);
+
+void nautilus_window_open_location_full (NautilusWindow *window,
+ GFile *location,
+ NautilusWindowOpenFlags 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_hide_sidebar (NautilusWindow *window);
+void nautilus_window_show_sidebar (NautilusWindow *window);
+void nautilus_window_back_or_forward (NautilusWindow *window,
+ gboolean back,
+ guint distance,
+ NautilusWindowOpenFlags flags);
+void nautilus_window_reset_menus (NautilusWindow *window);
+
+GtkWidget * nautilus_window_get_notebook (NautilusWindow *window);
+
+NautilusWindowOpenFlags nautilus_event_get_window_open_flags (void);
+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_start_dnd (NautilusWindow *window,
+ GdkDragContext *context);
+void nautilus_window_end_dnd (NautilusWindow *window,
+ GdkDragContext *context);
+
+void nautilus_window_search (NautilusWindow *window,
+ NautilusQuery *query);
+
+void nautilus_window_initialize_slot (NautilusWindow *window,
+ NautilusWindowSlot *slot,
+ NautilusWindowOpenFlags flags);
+
+gboolean nautilus_window_export_handle (NautilusWindow *window,
+ NautilusWindowHandleExported callback,
+ gpointer user_data);
+void nautilus_window_unexport_handle (NautilusWindow *window);
+
+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..f37c846
--- /dev/null
+++ b/src/nautilus-x-content-bar.c
@@ -0,0 +1,352 @@
+/*
+ * 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
+{
+ GtkInfoBar 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, GTK_TYPE_INFO_BAR)
+
+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_toplevel (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 *button;
+ GAppInfo *app;
+ gboolean has_app;
+ guint i;
+
+ 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, GTK_ICON_SIZE_BUTTON);
+ }
+ else
+ {
+ image = NULL;
+ }
+
+ name = g_app_info_get_name (default_app);
+ button = gtk_info_bar_add_button (GTK_INFO_BAR (bar),
+ name,
+ n);
+
+ gtk_button_set_image (GTK_BUTTON (button), image);
+ gtk_button_set_always_show_image (GTK_BUTTON (button), TRUE);
+ gtk_button_set_label (GTK_BUTTON (button), name);
+ 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 *content_area;
+ GtkWidget *action_area;
+ PangoAttrList *attrs;
+
+ content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (bar));
+ action_area = gtk_info_bar_get_action_area (GTK_INFO_BAR (bar));
+
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (action_area), GTK_ORIENTATION_HORIZONTAL);
+
+ 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_container_add (GTK_CONTAINER (content_area), bar->label);
+
+ g_signal_connect (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,
+ "message-type", GTK_MESSAGE_QUESTION,
+ "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..78872c8
--- /dev/null
+++ b/src/nautilus-x-content-bar.h
@@ -0,0 +1,37 @@
+/*
+ * 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>
+
+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, GtkInfoBar)
+
+GtkWidget *nautilus_x_content_bar_new (GMount *mount,
+ const char * const *x_content_types);
+
+G_END_DECLS \ No newline at end of file
diff --git a/src/resources/css/Adwaita.css b/src/resources/css/Adwaita.css
new file mode 100644
index 0000000..546d3e9
--- /dev/null
+++ b/src/resources/css/Adwaita.css
@@ -0,0 +1,215 @@
+.nautilus-window,
+.nautilus-window notebook,
+.nautilus-window notebook > stack {
+ background: @theme_base_color;
+}
+
+.nautilus-canvas-item {
+ border-radius: 5px;
+}
+
+.nautilus-canvas-item.dim-label,
+.nautilus-list-dim-label {
+ color: mix (@theme_fg_color, @theme_bg_color, 0.50);
+}
+
+.nautilus-canvas-item.dim-label:selected,
+.nautilus-list-dim-label:selected {
+ color: mix (@theme_selected_fg_color, @theme_selected_bg_color, 0.20);
+}
+
+/* Toolbar */
+
+/* Here we use the .button background-image colors from Adwaita, but ligthen them,
+ * since is not possible to use lighten () in common css. */
+@keyframes needs_attention_keyframes {
+ 0% {background-image: linear-gradient(to bottom, #fafafa, #ededed 40%, #e0e0e0); border-color: @borders; }
+ /* can't do animation-direction, so holding the color on two keyframes */
+ 30% {background-image: linear-gradient(to bottom, @theme_base_color, @theme_base_color, @theme_base_color); border-color: @theme_fg_color; }
+ 90% {background-image: linear-gradient(to bottom, @theme_base_color, @theme_base_color, @theme_base_color); border-color: @theme_fg_color; }
+ 100% {background-image: linear-gradient(to bottom, #fafafa, #ededed 40%, #e0e0e0); border-color: @borders; }
+}
+
+.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;
+}
+
+.disclosure-button {
+ padding-left: 4px;
+ padding-right: 4px;
+}
+
+/* Path bar */
+
+.path-bar-box {
+ border-radius: 5px;
+ border: 1px @borders solid;
+ background-color: @theme_bg_color;
+ padding-right: 6px;
+}
+
+.nautilus-path-bar button {
+ margin: 0px;
+}
+
+.nautilus-path-bar button:first-child {
+ border-width: 0px 1px 0px 0px;
+ border-radius: 3.5px 0px 0px 3.5px;
+}
+
+.nautilus-path-bar button:not(:first-child) {
+ border-width: 0px 1px 0px 1px;
+ border-radius: 0px 0px 0px 0px;
+}
+
+.nautilus-path-bar button:not(:checked) image { opacity: 0.8; } /* dim the icon when not checked */
+
+/* Make the tags fit into the box */
+entry.search > * {
+ margin: 5px;
+}
+
+/* Sidebar */
+
+.nautilus-window .sidebar-row:selected {
+ background: mix(@theme_bg_color, @theme_fg_color, 0.07);
+}
+
+.nautilus-window .sidebar-row:selected,
+.nautilus-window .sidebar-row:selected image,
+.nautilus-window .sidebar-row:selected label {
+ color: mix(@theme_fg_color, @theme_text_color, 0.5);
+}
+
+.nautilus-window .sidebar-row:selected:backdrop {
+ background: mix(@theme_unfocused_bg_color, @theme_unfocused_fg_color, 0.07);
+}
+
+.nautilus-window .sidebar-row:selected:backdrop,
+.nautilus-window .sidebar-row:selected:backdrop label {
+ color: mix(@theme_unfocused_fg_color, @theme_unfocused_text_color, 0.15);
+}
+
+/* Floating status bar */
+.floating-bar {
+ padding: 1px;
+ background-color: @theme_base_color;
+ border-width: 1px;
+ border-style: solid solid none;
+ border-color: @borders;
+ border-radius: 3px 3px 0 0;
+}
+
+.floating-bar.bottom.left { /* axes left border and border radius */
+ border-left-style: none;
+ border-top-left-radius: 0;
+}
+.floating-bar.bottom.right { /* axes right border and border radius */
+ border-right-style: none;
+ border-top-right-radius: 0;
+}
+
+.floating-bar:backdrop {
+ background-color: @theme_unfocused_base_color;
+ border-color: @unfocused_borders;
+}
+
+.floating-bar button {
+ padding: 0px;
+}
+
+@define-color disk_space_unknown #888a85;
+@define-color disk_space_used #729fcf;
+@define-color disk_space_free #eeeeec;
+
+.disk-space-display {
+ border-style: solid;
+ border-width: 2px;
+}
+
+.disk-space-display.unknown {
+ background-color: @disk_space_unknown;
+ border-color: shade(@disk_space_unknown, 0.7);
+ color: @disk_space_unknown;
+}
+.disk-space-display.unknown.border {
+ color: shade(@disk_space_unknown, 0.7);
+}
+
+.disk-space-display.used {
+ background-color: @disk_space_used;
+ border-color: shade(@disk_space_used, 0.7);
+ color: @disk_space_used;
+}
+.disk-space-display.used.border {
+ color: shade(@disk_space_used, 0.7);
+}
+
+.disk-space-display.free {
+ background-color: @disk_space_free;
+ border-color: shade(@disk_space_free, 0.7);
+ color: @disk_space_free;
+}
+.disk-space-display.free.border {
+ color: shade(@disk_space_free, 0.7);
+}
+
+/* View */
+.nautilus-list-view .view {
+ border-bottom: 1px solid @theme_bg_color;
+}
+
+.search-information {
+ background-color: @theme_selected_bg_color;
+ color:white;
+ padding:2px;
+}
+
+/* Hide superfluous treeview drop target indication */
+.nautilus-list-view .view.dnd {
+ border-style: none;
+}
+
+@define-color conflict_bg #fef6b6;
+
+.conflict-row {
+ background: @conflict_bg;
+ color: black;
+}
+
+.conflict-row:hover {
+ background-color: shade(@conflict_bg, 0.9);
+}
+
+.conflict-row:selected {
+ background: @theme_selected_bg_color;
+ color: @theme_selected_fg_color;
+}
+
+/* Icon view */
+flowboxchild:selected {
+ background-color:transparent;
+}
+
+.icon-background {
+ background-color:black;
+ border-color:#4a90d9;
+ border-style:solid;
+ border-width:0px;
+}
+
+flowboxchild > .icon-item-background {
+ padding:4px;
+}
+flowboxchild:selected > .icon-item-background {
+ padding:4px;
+ background-color:#4a90d9;
+ border-color:#4a90d9;
+ border-style:solid;
+ border-width:0px;
+ border-radius:4px 4px 4px 4px;
+}
diff --git a/src/resources/css/nautilus.css b/src/resources/css/nautilus.css
new file mode 100644
index 0000000..a85df66
--- /dev/null
+++ b/src/resources/css/nautilus.css
@@ -0,0 +1,10 @@
+.nautilus-circular-button {
+ border-radius: 20px;
+ -gtk-outline-radius: 20px;
+}
+
+.nautilus-menu-sort-heading {
+ min-height: 26px;
+ padding-left: 5px;
+ padding-right: 5px;
+}
diff --git a/src/resources/gtk/help-overlay.ui b/src/resources/gtk/help-overlay.ui
new file mode 100644
index 0000000..5f4c689
--- /dev/null
+++ b/src/resources/gtk/help-overlay.ui
@@ -0,0 +1,413 @@
+<?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 file and close window</property>
+ <property name="accelerator">&lt;shift&gt;&lt;Primary&gt;Down</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/nautilus.gresource.xml b/src/resources/nautilus.gresource.xml
new file mode 100644
index 0000000..158594e
--- /dev/null
+++ b/src/resources/nautilus.gresource.xml
@@ -0,0 +1,33 @@
+<?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-pathbar-context-menu.ui</file>
+ <file>ui/nautilus-toolbar.ui</file>
+ <file>ui/nautilus-toolbar-switcher.ui</file>
+ <file>ui/nautilus-toolbar-view-menu.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>ui/nautilus-no-search-results.ui</file>
+ <file>ui/nautilus-folder-is-empty.ui</file>
+ <file>ui/nautilus-trash-is-empty.ui</file>
+ <file>ui/nautilus-starred-is-empty.ui</file>
+ <file>gtk/help-overlay.ui</file>
+ <file>ui/nautilus-batch-rename-dialog.ui</file>
+ <file>ui/nautilus-batch-rename-dialog-menu.ui</file>
+ <file>ui/nautilus-properties-window.ui</file>
+ <file>ui/nautilus-file-properties-change-permissions.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/thumbnail_frame.png">../../icons/thumbnail_frame.png</file>
+ <file alias="icons/filmholes.png">../../icons/filmholes.png</file>
+ <file>css/Adwaita.css</file>
+ <file>css/nautilus.css</file>
+ <file>text-x-preview.png</file>
+ </gresource>
+</gresources>
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-batch-rename-dialog-menu.ui b/src/resources/ui/nautilus-batch-rename-dialog-menu.ui
new file mode 100644
index 0000000..640cfa1
--- /dev/null
+++ b/src/resources/ui/nautilus-batch-rename-dialog-menu.ui
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <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>
+</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..be7886f
--- /dev/null
+++ b/src/resources/ui/nautilus-batch-rename-dialog.ui
@@ -0,0 +1,427 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="NautilusBatchRenameDialog" parent="GtkDialog">
+ <property name="resizable">True</property>
+ <property name="modal">True</property>
+ <property name="height-request">563</property>
+ <property name="window_position">center-on-parent</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="visible">True</property>
+ <property name="can_focus">True</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="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="can_default">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="vbox">
+ <object class="GtkBox" id="vbox">
+ <property name="border-width">0</property>
+ <child>
+ <object class="GtkGrid" id="grid">
+ <property name="visible">True</property>
+ <property name="margin">0</property>
+ <property name="row-spacing">6</property>
+ <property name="column-spacing">6</property>
+ <property name="hexpand">True</property>
+ <property name="row-homogeneous">False</property>
+ <property name="column-homogeneous">False</property>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">horizontal</property>
+ <property name="spacing">15</property>
+ <property name="visible">True</property>
+ <property name="hexpand">True</property>
+ <property name="halign">center</property>
+ <property name="margin">20</property>
+ <child>
+ <object class="GtkRadioButton" id="format_mode_button">
+ <property name="label" translatable="yes">Rename _using a template</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</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="GtkRadioButton" id="replace_mode_button">
+ <property name="label" translatable="yes">Find and replace _text</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <property name="group">format_mode_button</property>
+ <signal name="toggled" handler="batch_rename_dialog_mode_changed" swapped="yes" />
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">3</property>
+ <property name="top-attach">0</property>
+ <property name="width">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkStack" id="mode_stack">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <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="GtkGrid" id="format_stack_child">
+ <property name="visible">True</property>
+ <property name="margin-start">40</property>
+ <property name="margin-end">40</property>
+ <property name="margin-top">0</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="orientation">horizontal</property>
+ <property name="spacing">0</property>
+ <property name="visible">True</property>
+ <property name="halign">center</property>
+ <child>
+ <object class="GtkEntry" id="name_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="has_focus">True</property>
+ <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" />
+ <signal name="insert-text" handler="on_insert_text" swapped="no" />
+ <signal name="delete-text" handler="on_delete_text" swapped="no" />
+ </object>
+ </child>
+ <child>
+ <object class="GtkToggleButton" id="add_button">
+ <property name="visible">True</property>
+ <signal name="toggled" handler="add_button_clicked" swapped="yes" />
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="orientation">horizontal</property>
+ <property name="spacing">0</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="icon-name">list-add-symbolic</property>
+ <property name="icon-size">1</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Add</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <style>
+ <class name="linked"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ <property name="width">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRevealer" id="numbering_revealer">
+ <property name="visible">True</property>
+ <property name="halign">center</property>
+ <child>
+ <object class="GtkBox" id="numbering_box">
+ <property name="visible">True</property>
+ <property name="orientation">horizontal</property>
+ <property name="spacing">10</property>
+ <child>
+ <object class="GtkLabel" id="numbering_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Automatic Numbering Order</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkToggleButton" id="numbering_order_button">
+ <property name="visible">True</property>
+ <signal name="toggled" handler="numbering_order_button_clicked" swapped="yes" />
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="orientation">horizontal</property>
+ <property name="spacing">15</property>
+ <child>
+ <object class="GtkLabel" id="numbering_order_label">
+ <property name="visible">True</property>
+ <property name="width-request">180</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Original Name (Ascending)</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage" id="action_icon">
+ <property name="visible">True</property>
+ <property name="icon-name">pan-down-symbolic</property>
+ <property name="icon-size">1</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ <property name="width">5</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="name">format</property>
+ <property name="title" translatable="yes" comments="Translators: This is a noun, not a verb">Format</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid" id="replace_stack_child">
+ <property name="visible">True</property>
+ <property name="margin-start">40</property>
+ <property name="margin-end">40</property>
+ <property name="margin-top">0</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="visible">True</property>
+ <property name="label" translatable="yes">Existing Text</property>
+ <property name="can_focus">False</property>
+ <property name="sensitive">False</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ <property name="width">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="find_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="width_request">375</property>
+ <property name="hexpand">False</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" />
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">0</property>
+ <property name="width">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="replace_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Replace With</property>
+ <property name="can_focus">False</property>
+ <property name="sensitive">False</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ <property name="width">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="replace_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <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" />
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">1</property>
+ <property name="width">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="name">replace</property>
+ <property name="title" translatable="yes" context="title">Replace</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ <property name="width">8</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolled_window">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">False</property>
+ <property name="vexpand">True</property>
+ <property name="shadow_type">in</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>
+ <child>
+ <object class="GtkViewport">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkBox" id="a_box">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkListBox" id="original_name_listbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="selection_mode">GTK_SELECTION_NONE</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBox" id="arrow_listbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="selection_mode">GTK_SELECTION_NONE</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBox" id="result_listbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="selection_mode">GTK_SELECTION_NONE</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">2</property>
+ <property name="width">8</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="conflict_box">
+ <property name="orientation">horizontal</property>
+ <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="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="pack-type">start</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">horizontal</property>
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkButton" id="conflict_down">
+ <property name="visible">True</property>
+ <property name="relief">none</property>
+ <signal name="clicked" handler="select_next_conflict_down" swapped="yes" />
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="icon-name">go-down-symbolic</property>
+ <property name="icon-size">1</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="conflict_up">
+ <property name="visible">True</property>
+ <property name="relief">GTK_RELIEF_NONE</property>
+ <signal name="clicked" handler="select_next_conflict_up" swapped="yes" />
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="icon-name">go-up-symbolic</property>
+ <property name="icon-size">1</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="pack-type">end</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">3</property>
+ <property name="width">8</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+ <object class="GtkPopover" id="add_popover">
+ <property name="position">bottom</property>
+ <property name="relative-to">add_button</property>
+ <signal name="closed" handler="add_popover_closed" swapped="yes" />
+ </object>
+ <object class="GtkImage" id="done_image">
+ <property name="visible">True</property>
+ <property name="icon_name">object-select-symbolic</property>
+ </object>
+ <object class="GtkPopover" id="numbering_order_popover">
+ <property name="position">bottom</property>
+ <property name="relative-to">numbering_order_button</property>
+ <signal name="closed" handler="numbering_order_popover_closed" swapped="yes" />
+ </object>
+</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..526e9ee
--- /dev/null
+++ b/src/resources/ui/nautilus-compress-dialog.ui
@@ -0,0 +1,186 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="3.14"/>
+ <object class="GtkDialog" id="compress_dialog">
+ <property name="title" translatable="yes">Create Archive</property>
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="window_position">center-on-parent</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <property name="use-header-bar">1</property>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="vbox">
+ <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="label" translatable="yes">Archive name</property>
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="name_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRevealer" id="error_revealer">
+ <child>
+ <object class="GtkLabel" id="error_label">
+ <property name="margin_top">4</property>
+ <property name="margin_bottom">4</property>
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox">
+ <property name="orientation">horizontal</property>
+ <property name="homogeneous">True</property>
+ <property name="spacing">0</property>
+ <child>
+ <object class="GtkRadioButton" id="zip_radio_button">
+ <property name="label" translatable="no">.zip</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="zip_radio_button_on_toggled"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="tar_xz_radio_button">
+ <property name="label" translatable="no">.tar.xz</property>
+ <property name="group">zip_radio_button</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="tar_xz_radio_button_on_toggled"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="seven_zip_radio_button">
+ <property name="label" translatable="no">.7z</property>
+ <property name="group">zip_radio_button</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="seven_zip_radio_button_on_toggled"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkStack" id="description_stack">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="homogeneous">True</property>
+ <child>
+ <object class="GtkLabel" id="zip_description_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Compatible with all operating systems.</property>
+ <property name="xalign">0</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="name">zip-description-label</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="tar_xz_description_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Smaller archives but Linux and Mac only.</property>
+ <property name="xalign">0</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="name">tar-xz-description-label</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="seven_zip_description_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Smaller archives but must be installed on Windows and Mac.</property>
+ <property name="xalign">0</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="name">seven-zip-description-label</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">5</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="action">
+ <object class="GtkButton" id="cancel_button">
+ <property name="label" translatable="yes">Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ </object>
+ </child>
+ <child type="action">
+ <object class="GtkButton" id="activate_button">
+ <property name="label" translatable="yes">Create</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">True</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>
+</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..818ae08
--- /dev/null
+++ b/src/resources/ui/nautilus-create-folder-dialog.ui
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="3.14"/>
+ <object class="GtkDialog" id="create_folder_dialog">
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="window_position">center-on-parent</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <property name="use-header-bar">1</property>
+ <property name="width_request">450</property>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="vbox">
+ <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="visible">True</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="name_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRevealer" id="error_revealer">
+ <child>
+ <object class="GtkLabel" id="error_label">
+ <property name="margin_top">4</property>
+ <property name="margin_bottom">4</property>
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="action">
+ <object class="GtkButton" id="cancel_button">
+ <property name="label" translatable="yes">Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ </object>
+ </child>
+ <child type="action">
+ <object class="GtkButton" id="ok_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">True</property>
+ <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-properties-change-permissions.ui b/src/resources/ui/nautilus-file-properties-change-permissions.ui
new file mode 100644
index 0000000..122fd1f
--- /dev/null
+++ b/src/resources/ui/nautilus-file-properties-change-permissions.ui
@@ -0,0 +1,185 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="3.22"/>
+ <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="type_hint">dialog</property>
+ <property name="use-header-bar">1</property>
+ <child type="action">
+ <object class="GtkButton" id="cancel">
+ <property name="visible">True</property>
+ <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="visible">True</property>
+ <property name="label" translatable="yes">C_hange</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox">
+ <property name="can-focus">False</property>
+ <property name="orientation">vertical</property>
+ <style>
+ <class name="view"/>
+ </style>
+ <child>
+ <object class="GtkGrid" id="change_permissions_grid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">center</property>
+ <property name="border_width">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="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Files</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Folders</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Owner</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="file_owner_combo">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="folder_owner_combo">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Group</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="file_group_combo">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="folder_group_combo">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="file_other_combo">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="folder_other_combo">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Others</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </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..6438314
--- /dev/null
+++ b/src/resources/ui/nautilus-files-view-context-menus.ui
@@ -0,0 +1,239 @@
+<?xml version="1.0"?>
+<interface>
+ <menu id="background-menu">
+ <item>
+ <attribute name="label" translatable="yes">New _Folder</attribute>
+ <attribute name="action">view.new-folder</attribute>
+ </item>
+ <submenu id="templates-submenu">
+ <attribute name="label" translatable="yes">New _Document</attribute>
+ <attribute name="action">view.new-document</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </submenu>
+ <item>
+ <attribute name="label" translatable="yes">Add to _Bookmarks</attribute>
+ <attribute name="action">win.bookmark-current-location</attribute>
+ </item>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">Create _Link</attribute>
+ <attribute name="action">view.create-link</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_Paste</attribute>
+ <attribute name="action">view.paste</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Select _All</attribute>
+ <attribute name="action">view.select-all</attribute>
+ </item>
+ </section>
+ <section id="background-extensions-section">
+ </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>
+ <section id="open-with-default-application-section">
+ </section>
+ <submenu id="scripts-submenu">
+ <attribute name="label" translatable="yes">_Scripts</attribute>
+ <attribute name="action">view.scripts</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ <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>
+ <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>
+ <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 Other _Application</attribute>
+ <attribute name="action">view.open-with-other-application</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ </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>
+ <section>
+ <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>
+ </section>
+ <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>
+ </section>
+ <section>
+ <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">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>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">Rena_me…</attribute>
+ <attribute name="action">view.rename</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">Set As Wallpaper</attribute>
+ <attribute name="action">view.set-as-wallpaper</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ </section>
+ <section>
+ <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>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_Extract Here</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">C_ompress…</attribute>
+ <attribute name="action">view.compress</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ </section>
+ <section id="selection-extensions-section">
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">Tags</attribute>
+ <attribute name="action">view.edit-tags</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes" context="menu item" comments="Marks a file as starred (starred)">Star</attribute>
+ <attribute name="action">view.star</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes" context="menu item" 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>
+ <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-folder-is-empty.ui b/src/resources/ui/nautilus-folder-is-empty.ui
new file mode 100644
index 0000000..22031ba
--- /dev/null
+++ b/src/resources/ui/nautilus-folder-is-empty.ui
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.0 -->
+<interface>
+ <requires lib="gtk+" version="3.20"/>
+ <object class="GtkGrid" id="folder_is_empty">
+ <property name="can_focus">False</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="row_spacing">12</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="pixel_size">80</property>
+ <property name="icon_name">folder-symbolic</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Folder is Empty</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ <attribute name="scale" value="1.4399999999999999"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+</interface>
diff --git a/src/resources/ui/nautilus-no-search-results.ui b/src/resources/ui/nautilus-no-search-results.ui
new file mode 100644
index 0000000..92683be
--- /dev/null
+++ b/src/resources/ui/nautilus-no-search-results.ui
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.0 -->
+<interface>
+ <requires lib="gtk+" version="3.20"/>
+ <object class="GtkGrid" id="no_search_results">
+ <property name="can_focus">False</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="row_spacing">12</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="pixel_size">80</property>
+ <property name="icon_name">edit-find-symbolic</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">No Results Found</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ <attribute name="scale" value="1.4399999999999999"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Try a different search</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </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..696921e
--- /dev/null
+++ b/src/resources/ui/nautilus-pathbar-context-menu.ui
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <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>
+</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..066b157
--- /dev/null
+++ b/src/resources/ui/nautilus-preferences-window.ui
@@ -0,0 +1,1365 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.0 -->
+<interface>
+ <requires lib="gtk+" version="3.22"/>
+ <object class="GtkAdjustment" id="adjustment1">
+ <property name="lower">1</property>
+ <property name="upper">4096</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkDialog" id="preferences_window">
+ <property name="can_focus">False</property>
+ <property name="title" translatable="yes">Preferences</property>
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">normal</property>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="vbox">
+ <property name="can_focus">False</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox">
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkNotebook" id="notebook1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="border_width">0</property>
+ <property name="show_border">False</property>
+ <child>
+ <object class="GtkBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">12</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">18</property>
+ <child>
+ <object class="GtkBox" id="vbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label17">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">General</property>
+ <property name="xalign">0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ <accessibility>
+ <relation type="label-for" target="vbox2"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="show_sidebar_checkbutton">
+ <property name="label" translatable="yes">_Show sidebar</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <accessibility>
+ <relation type="labelled-by" target="label17"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox12">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" context="preferences" comments="Translators: a title in the preferences dialog with an option to sort folder before files &quot;Sort folders before files&quot;.">Sort</property>
+ <property name="xalign">0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ <accessibility>
+ <relation type="label-for" target="vbox12"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="sort_folders_first_checkbutton">
+ <property name="label" translatable="yes">Sort _folders before files</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <accessibility>
+ <relation type="labelled-by" target="label6"/>
+ </accessibility>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="vbox12-atkobject">
+ <property name="AtkObject::accessible-role">panel</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">List View</property>
+ <property name="xalign">0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ <accessibility>
+ <relation type="label-for" target="box1"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="use_tree_view_checkbutton">
+ <property name="label" translatable="yes">Allow folders to be _expanded</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <accessibility>
+ <relation type="labelled-by" target="label7"/>
+ </accessibility>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="box1-atkobject">
+ <property name="AtkObject::accessible-role">panel</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox27">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label28">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Icon View Captions</property>
+ <property name="xalign">0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ <accessibility>
+ <relation type="label-for" target="vbox27"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label29">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Add information to be displayed beneath file and folder names. More information will appear when zooming closer.</property>
+ <property name="wrap">True</property>
+ <property name="max_width_chars">65</property>
+ <property name="xalign">0</property>
+ <accessibility>
+ <relation type="description-for" target="captions_0_combobox"/>
+ <relation type="description-for" target="captions_1_combobox"/>
+ <relation type="description-for" target="captions_2_combobox"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid" id="grid1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="row_spacing">10</property>
+ <property name="column_spacing">15</property>
+ <child>
+ <object class="GtkBox" id="hbox29">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="captions_label_1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="captions_0_combobox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <accessibility>
+ <relation type="labelled-by" target="label8"/>
+ <relation type="described-by" target="label29"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox28">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="captions_label_0">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="captions_2_combobox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <accessibility>
+ <relation type="labelled-by" target="label11"/>
+ <relation type="described-by" target="label29"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox30">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="captions_label_2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="captions_1_combobox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <accessibility>
+ <relation type="labelled-by" target="label9"/>
+ <relation type="described-by" target="label29"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label9">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" context="the n-th position of an icon caption" comments="Translators: This is an ordinal number">Second</property>
+ <property name="xalign">1</property>
+ <accessibility>
+ <relation type="label-for" target="captions_1_combobox"/>
+ </accessibility>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label11">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" context="the n-th position of an icon caption" comments="Translators: This is an ordinal number">Third</property>
+ <property name="xalign">1</property>
+ <accessibility>
+ <relation type="label-for" target="captions_2_combobox"/>
+ </accessibility>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label8">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" context="the n-th position of an icon caption" comments="Translators: This is an ordinal number">First</property>
+ <property name="xalign">1</property>
+ <accessibility>
+ <relation type="label-for" target="captions_0_combobox"/>
+ </accessibility>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <accessibility>
+ <relation type="labelled-by" target="label28"/>
+ </accessibility>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="vbox27-atkobject">
+ <property name="AtkObject::accessible-role">panel</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="tab_expand">True</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Views</property>
+ </object>
+ <packing>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">12</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">10</property>
+ <child>
+ <object class="GtkBox" id="vbox6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label10">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Open Action</property>
+ <property name="xalign">0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ <accessibility>
+ <relation type="label-for" target="vbox6"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="single_click_radiobutton">
+ <property name="label" translatable="yes">_Single click to open items</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="double_click_radiobutton">
+ <property name="label" translatable="yes">_Double click to open items</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">single_click_radiobutton</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <accessibility>
+ <relation type="labelled-by" target="label10"/>
+ </accessibility>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="vbox6-atkobject">
+ <property name="AtkObject::accessible-role">panel</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox14">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label13">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Link Creation</property>
+ <property name="xalign">0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ <accessibility>
+ <relation type="label-for" target="vbox14"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="show_create_link_checkbutton">
+ <property name="label" translatable="yes">Show action to create symbolic _links</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <accessibility>
+ <relation type="labelled-by" target="label13"/>
+ </accessibility>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="vbox14-atkobject">
+ <property name="AtkObject::accessible-role">panel</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label12">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Executable Text Files</property>
+ <property name="xalign">0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ <accessibility>
+ <relation type="label-for" target="vbox7"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="scripts_view_radiobutton">
+ <property name="label" translatable="yes">_Display them</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">scripts_execute_radiobutton</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="scripts_execute_radiobutton">
+ <property name="label" translatable="yes">_Run them</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">scripts_view_radiobutton</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="scripts_confirm_radiobutton">
+ <property name="label" translatable="yes">_Ask what to do</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">scripts_execute_radiobutton</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <accessibility>
+ <relation type="labelled-by" target="label12"/>
+ </accessibility>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="vbox7-atkobject">
+ <property name="AtkObject::accessible-role">panel</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox8">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label14">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Trash</property>
+ <property name="xalign">0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ <accessibility>
+ <relation type="label-for" target="vbox8"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="trash_confirm_checkbutton">
+ <property name="label" translatable="yes">Ask before _emptying the Trash</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="show_delete_permanently_checkbutton">
+ <property name="label" translatable="yes">Show action to _permanently delete files and folders</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <accessibility>
+ <relation type="labelled-by" target="label14"/>
+ </accessibility>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="vbox8-atkobject">
+ <property name="AtkObject::accessible-role">panel</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ <property name="tab_expand">True</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Behavior</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox29">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">12</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">18</property>
+ <child>
+ <object class="GtkBox" id="vbox30">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkBox" id="list_columns_vbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label33">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Choose the order of information to appear in the list view.</property>
+ <property name="wrap">True</property>
+ <property name="max_width_chars">65</property>
+ <property name="xalign">0</property>
+ <accessibility>
+ <relation type="label-for" target="list_columns_vbox"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <accessibility>
+ <relation type="labelled-by" target="label33"/>
+ </accessibility>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="list_columns_vbox-atkobject">
+ <property name="AtkObject::accessible-role">panel</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ <property name="tab_expand">True</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label30">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">List Columns</property>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox9">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">12</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">18</property>
+ <child>
+ <object class="GtkBox" id="vbox3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Search</property>
+ <property name="xalign">0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Search in subfolders:</property>
+ <property name="xalign">0</property>
+ <accessibility>
+ <relation type="label-for" target="vbox3"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="search_recursive_only_this_computer_radiobutton">
+ <property name="label" translatable="yes">_On this computer only</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="search_recursive_all_locations_radiobutton">
+ <property name="label" translatable="yes">_All locations</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">search_recursive_only_this_computer_radiobutton</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="search_recursive_never_radiobutton">
+ <property name="label" translatable="yes">_Never</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">search_recursive_only_this_computer_radiobutton</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <accessibility>
+ <relation type="labelled-by" target="label4"/>
+ </accessibility>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="vbox3-atkobject">
+ <property name="AtkObject::accessible-role">panel</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox11">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label18">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Thumbnails</property>
+ <property name="xalign">0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label15">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Show thumbnails:</property>
+ <property name="xalign">0</property>
+ <accessibility>
+ <relation type="label-for" target="vbox11"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="thumbnails_only_this_computer_radiobutton">
+ <property name="label" translatable="yes">_Files on this computer only</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="thumbnails_all_files_radiobutton">
+ <property name="label" translatable="yes">A_ll files</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">thumbnails_only_this_computer_radiobutton</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="thumbnails_never_radiobutton">
+ <property name="label" translatable="yes">N_ever</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">thumbnails_only_this_computer_radiobutton</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox21">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="preview_label_1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Onl_y for files smaller than:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">preview_image_size_spinbutton</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="preview_image_size_spinbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="width_chars">8</property>
+ <property name="input_purpose">number</property>
+ <property name="adjustment">adjustment1</property>
+ <property name="climb_rate">10</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">5</property>
+ </packing>
+ </child>
+ <accessibility>
+ <relation type="labelled-by" target="label15"/>
+ </accessibility>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="vbox11-atkobject">
+ <property name="AtkObject::accessible-role">panel</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox13">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label22">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">File count</property>
+ <property name="xalign">0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label16">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Count number of files in folders:</property>
+ <property name="xalign">0</property>
+ <accessibility>
+ <relation type="label-for" target="vbox13"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="count_only_this_computer_radiobutton">
+ <property name="label" translatable="yes">F_olders on this computer only</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="count_all_files_radiobutton">
+ <property name="label" translatable="yes">All folder_s</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">count_only_this_computer_radiobutton</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="count_never_radiobutton">
+ <property name="label" translatable="yes">Ne_ver</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">count_only_this_computer_radiobutton</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <accessibility>
+ <relation type="labelled-by" target="label16"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ <property name="tab_expand">True</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Search &amp; Preview</property>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="titlebar">
+ <placeholder/>
+ </child>
+ </object>
+ <object class="GtkListStore" id="icon_view_zoom_levels">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Always</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Local Files Only</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Never</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="list_view_zoom_levels">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Small</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Standard</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Large</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model1">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Icon View</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">List View</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model10">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Always</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Local Files Only</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Never</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model2">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">By Name</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">By Size</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">By Type</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">By Modification Date</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">By Access Date</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">By Trashed Date</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model3">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Small</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Standard</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Large</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model7">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Always</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Local Files Only</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Never</col>
+ </row>
+ </data>
+ </object>
+</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..72d08e9
--- /dev/null
+++ b/src/resources/ui/nautilus-progress-info-widget.ui
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.0 -->
+<interface>
+ <requires lib="gtk+" version="3.20"/>
+ <object class="GtkImage" id="cancel_image">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">window-close-symbolic</property>
+ </object>
+ <template class="NautilusProgressInfoWidget" parent="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">5</property>
+ <property name="margin_end">5</property>
+ <child>
+ <object class="GtkLabel" id="status">
+ <property name="width_request">300</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</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>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkProgressBar" id="progress_bar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <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>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="valign">center</property>
+ <property name="margin_start">20</property>
+ <property name="image">cancel_image</property>
+ <style>
+ <class name="nautilus-circular-button"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="height">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="details">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <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"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ </template>
+ <object class="GtkImage" id="done_image">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">object-select-symbolic</property>
+ </object>
+</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..d9cac49
--- /dev/null
+++ b/src/resources/ui/nautilus-properties-window.ui
@@ -0,0 +1,1382 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.36.0 -->
+<interface>
+ <requires lib="gtk+" version="3.20"/>
+ <template class="NautilusPropertiesWindow" parent="GtkWindow">
+ <property name="can_focus">False</property>
+ <property name="modal">True</property>
+ <property name="type_hint">dialog</property>
+ <child>
+ <object class="GtkBox" id="content_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkNotebook" id="notebook">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="show_border">False</property>
+ <child>
+ <object class="GtkBox" id="basic_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">18</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkStack" id="icon_stack">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">center</property>
+ <property name="valign">start</property>
+ <child>
+ <object class="GtkImage" id="icon_image">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="stock">gtk-missing-image</property>
+ </object>
+ <packing>
+ <property name="name">icon_image</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="icon_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <child>
+ <object class="GtkImage" id="icon_button_image">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="stock">gtk-missing-image</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">icon_button</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="grid_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkGrid" id="basic_grid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="name_title_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Name</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">name_field</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkStack" id="name_stack">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="name_value_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="selectable">True</property>
+ <property name="ellipsize">end</property>
+ <property name="max_width_chars">24</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="name">name_value_label</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="name_field">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </object>
+ <packing>
+ <property name="name">name_value_entry</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="type_title_label">
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Type</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="type_value_label">
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="selectable">True</property>
+ <property name="ellipsize">end</property>
+ <property name="max_width_chars">24</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="link_target_title_label">
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Link target</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="link_target_value_label">
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="selectable">True</property>
+ <property name="ellipsize">end</property>
+ <property name="max_width_chars">24</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="contents_title_label">
+ <property name="can_focus">False</property>
+ <property name="valign">start</property>
+ <property name="label" translatable="yes">Contents</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="contents_value_label">
+ <property name="can_focus">False</property>
+ <property name="wrap">True</property>
+ <property name="selectable">True</property>
+ <property name="max_width_chars">24</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinner" id="contents_spinner">
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="size_title_label">
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Size</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="size_value_label">
+ <property name="can_focus">False</property>
+ <property name="selectable">True</property>
+ <property name="max_width_chars">24</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="parent_folder_title_label">
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Parent folder</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">6</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="parent_folder_value_label">
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="selectable">True</property>
+ <property name="ellipsize">end</property>
+ <property name="max_width_chars">24</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">6</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="original_folder_title_label">
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Original folder</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">11</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="original_folder_value_label">
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="selectable">True</property>
+ <property name="ellipsize">end</property>
+ <property name="max_width_chars">24</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">11</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="volume_title_label">
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Volume</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">7</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="volume_value_label">
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="selectable">True</property>
+ <property name="ellipsize">end</property>
+ <property name="max_width_chars">24</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">7</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="trashed_on_title_label">
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Trashed on</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">12</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="trashed_on_value_label">
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="selectable">True</property>
+ <property name="ellipsize">end</property>
+ <property name="max_width_chars">24</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">12</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="accessed_title_label">
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Accessed</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">9</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="accessed_value_label">
+ <property name="can_focus">False</property>
+ <property name="selectable">True</property>
+ <property name="max_width_chars">24</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">9</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="modified_title_label">
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Modified</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">10</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="modified_value_label">
+ <property name="can_focus">False</property>
+ <property name="selectable">True</property>
+ <property name="max_width_chars">24</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">10</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="free_space_title_label">
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Free space</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">14</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="free_space_value_label">
+ <property name="can_focus">False</property>
+ <property name="selectable">True</property>
+ <property name="max_width_chars">24</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">14</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="volume_widget_box">
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkGrid" id="volume_grid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">False</property>
+ <property name="border_width">5</property>
+ <property name="row_spacing">10</property>
+ <property name="column_spacing">10</property>
+ <child>
+ <object class="GtkDrawingArea" id="pie_chart">
+ <property name="width_request">200</property>
+ <property name="height_request">200</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <style>
+ <class name="disk-space-display"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="height">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="spacer_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="vexpand">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkDrawingArea" id="used_color">
+ <property name="width_request">20</property>
+ <property name="height_request">20</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">end</property>
+ <property name="vexpand">False</property>
+ <style>
+ <class name="disk-space-display"/>
+ <class name="used"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkDrawingArea" id="free_color">
+ <property name="width_request">20</property>
+ <property name="height_request">20</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">end</property>
+ <property name="vexpand">False</property>
+ <style>
+ <class name="disk-space-display"/>
+ <class name="free"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">end</property>
+ <property name="vexpand">False</property>
+ <property name="label" translatable="yes">Total capacity</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">end</property>
+ <property name="vexpand">False</property>
+ <property name="label" translatable="yes">Filesystem type</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="total_capacity_value">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="vexpand">False</property>
+ <property name="label">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="file_system_value">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="vexpand">False</property>
+ <property name="label">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="used_value">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">end</property>
+ <property name="vexpand">False</property>
+ <property name="label">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="free_value">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">end</property>
+ <property name="vexpand">False</property>
+ <property name="label">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="vexpand">False</property>
+ <property name="label" translatable="yes" comments="Refers to the capacity of the filesystem">used</property>
+ </object>
+ <packing>
+ <property name="left_attach">3</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="vexpand">False</property>
+ <property name="label" translatable="yes" comments="Refers to the capacity of the filesystem">free</property>
+ </object>
+ <packing>
+ <property name="left_attach">3</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">15</property>
+ <property name="width">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="open_in_disks_button">
+ <property name="label" translatable="yes">Open in Disks</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">16</property>
+ <property name="width">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="spacer_1">
+ <property name="height_request">6</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="spacer_2">
+ <property name="height_request">6</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">8</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="spacer_3">
+ <property name="height_request">6</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">13</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="tab_expand">True</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="basic_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Basic</property>
+ </object>
+ <packing>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="permissions_box">
+ <property name="can_focus">False</property>
+ <property name="border_width">18</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkLabel" id="not_the_owner_label">
+ <property name="can_focus">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">True</property>
+ <property name="max_width_chars">40</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparator" id="bottom_prompt_seperator">
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="padding">12</property>
+ <property name="pack_type">end</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="permission_indeterminable_label">
+ <property name="can_focus">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">True</property>
+ <property name="max_width_chars">40</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid" id="permissions_grid">
+ <property name="can_focus">False</property>
+ <property name="halign">center</property>
+ <property name="orientation">vertical</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="owner_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Owner</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">owner_combo_box</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkStack" id="owner_value_stack">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkComboBox" id="owner_combo_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ </object>
+ <packing>
+ <property name="name">combo_box</property>
+ <property name="title">page0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="owner_value_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="selectable">True</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="name">label</property>
+ <property name="title">page0</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="owner_access_label">
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Access</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="owner_folder_access_label">
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Folder access</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="owner_file_access_label">
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">File access</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="owner_access_combo">
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="owner_folder_access_combo">
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="owner_file_access_combo">
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="group_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_top">12</property>
+ <property name="label" translatable="yes">_Group</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">group_combo_box</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkStack" id="group_value_stack">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkComboBoxText" id="group_combo_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="margin_top">12</property>
+ </object>
+ <packing>
+ <property name="name">combo_box</property>
+ <property name="title">page0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="group_value_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_top">12</property>
+ <property name="selectable">True</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="name">label</property>
+ <property name="title">page1</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="group_access_label">
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Access</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">6</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="group_folder_access_label">
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Folder access</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">7</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="group_file_access_label">
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">File access</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">8</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="group_access_combo">
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">6</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="group_folder_access_combo">
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">7</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="group_file_access_combo">
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">8</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="others_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_top">12</property>
+ <property name="label" translatable="yes">Others</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">10</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="others_access_label">
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Access</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">11</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="others_folder_access_label">
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Folder access</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">12</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="others_file_access_label">
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">File access</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">13</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="others_access_combo">
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">11</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="others_folder_access_combo">
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">12</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="others_file_access_combo">
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">13</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="execute_label">
+ <property name="can_focus">False</property>
+ <property name="margin_top">12</property>
+ <property name="label" translatable="yes">Execute</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">15</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="security_context_title_label">
+ <property name="can_focus">False</property>
+ <property name="margin_top">12</property>
+ <property name="label" translatable="yes">Security context</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">17</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="security_context_value_label">
+ <property name="can_focus">False</property>
+ <property name="margin_top">12</property>
+ <property name="selectable">True</property>
+ <property name="max_width_chars">24</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">17</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="change_permissions_button_box">
+ <property name="can_focus">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="can_focus">True</property>
+ <property name="receives_default">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">19</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="execute_checkbox">
+ <property name="label" translatable="yes">Allow _executing file as program</property>
+ <property name="use_underline">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="margin_top">12</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">15</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ <property name="tab_expand">True</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="permissions_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Permissions</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="open_with_box">
+ <property name="can_focus">False</property>
+ <property name="border_width">18</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="open_with_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="wrap">True</property>
+ <property name="wrap_mode">word-char</property>
+ <property name="max_width_chars">30</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="app_chooser_widget_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButtonBox" id="app_chooser_button_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">6</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="reset_button">
+ <property name="label" translatable="yes">Reset</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ <property name="secondary">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="add_button">
+ <property name="label" translatable="yes">_Add</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="set_as_default_button">
+ <property name="label" translatable="yes">Set as default</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ <property name="tab_expand">True</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Open With</property>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child type="tab">
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <style>
+ <class name="view"/>
+ </style>
+ </object>
+ </child>
+ <child type="titlebar">
+ <placeholder/>
+ </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..f11c665
--- /dev/null
+++ b/src/resources/ui/nautilus-rename-file-popover.ui
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.0 -->
+<interface>
+ <requires lib="gtk+" version="3.20"/>
+ <object class="GtkPopover" id="rename_file_popover">
+ <property name="can_focus">False</property>
+ <property name="position">bottom</property>
+ <child>
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">10</property>
+ <property name="margin_end">10</property>
+ <property name="margin_top">10</property>
+ <property name="margin_bottom">10</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="name_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Name</property>
+ <property name="mnemonic_widget">name_entry</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="name_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="rename_button">
+ <property name="label" translatable="yes">_Rename</property>
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">False</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <style>
+ <class name="suggested-action"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRevealer" id="error_revealer">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="error_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_top">4</property>
+ <property name="margin_bottom">4</property>
+ <property name="xalign">0</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </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..1d03b48
--- /dev/null
+++ b/src/resources/ui/nautilus-search-popover.ui
@@ -0,0 +1,430 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="3.16"/>
+ <template class="NautilusSearchPopover" parent="GtkPopover">
+ <property name="can_focus">False</property>
+ <property name="modal">True</property>
+ <child>
+ <object class="GtkGrid" >
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">20</property>
+ <property name="row_spacing">8</property>
+ <property name="column_spacing">18</property>
+ <child>
+ <object class="GtkLabel" id="when_dim_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">When</property>
+ <property name="xalign">0</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkStack" id="date_stack">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="transition_type">crossfade</property>
+ <property name="transition_duration">250</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkButton" id="select_date_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <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="visible">True</property>
+ <property name="can_focus">False</property>
+ <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>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="clear_date_button">
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Clear the currently selected date</property>
+ <signal name="clicked" handler="clear_date_button_clicked" object="NautilusSearchPopover" swapped="no" />
+ <child>
+ <object class="GtkImage" id="clear_date_button_image">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">edit-clear-symbolic</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <style>
+ <class name="linked"/>
+ </style>
+ </object>
+ <packing>
+ <property name="name">date-button</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="date_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <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>
+ <packing>
+ <property name="name">date-entry</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRevealer" id="around_revealer">
+ <property name="can_focus">False</property>
+ <property name="transition_type">slide-down</property>
+ <child>
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="row_spacing">8</property>
+ <property name="column_spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="around_dim_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_top">10</property>
+ <property name="label" translatable="yes">Since…</property>
+ <property name="xalign">0</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkStack" id="around_stack">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="vhomogeneous">False</property>
+ <property name="transition_type">crossfade</property>
+ <property name="transition_duration">250</property>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="height_request">200</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkViewport">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkListBox" id="dates_listbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="selection_mode">none</property>
+ <signal name="row-activated" handler="dates_listbox_row_activated" object="NautilusSearchPopover" swapped="no" />
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">date-list</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCalendar" id="calendar">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <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>
+ <packing>
+ <property name="name">date-calendar</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="last_modified_button">
+ <property name="label" translatable="yes">Last _modified</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="xalign">0</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="search_time_type_changed" object="NautilusSearchPopover" swapped="no" />
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="last_used_button">
+ <property name="label" translatable="yes">Last _used</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="xalign">0</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">last_modified_button</property>
+ <signal name="toggled" handler="search_time_type_changed" object="NautilusSearchPopover" swapped="no" />
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="what_dim_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_top">10</property>
+ <property name="label" translatable="yes">What</property>
+ <property name="xalign">0</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">3</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkStack" id="type_stack">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="vhomogeneous">False</property>
+ <property name="transition_type">crossfade</property>
+ <property name="transition_duration">250</property>
+ <child>
+ <object class="GtkButton" id="select_type_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <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">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="type_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="label" translatable="yes">Anything</property>
+ <property name="width_chars">30</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">pan-down-symbolic</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">type-button</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="height_request">250</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkViewport">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkListBox" id="type_listbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="selection_mode">single</property>
+ <signal name="row-activated" handler="types_listbox_row_activated" object="NautilusSearchPopover" swapped="no" />
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">type-list</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">4</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="search_dim_label">
+ <property name="can_focus">False</property>
+ <property name="margin_top">10</property>
+ <property name="label" translatable="yes">Search</property>
+ <property name="xalign">0</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">5</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkRadioButton" id="full_text_search_button">
+ <property name="label" translatable="yes">Full Text</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Search on the file content and name</property>
+ <property name="hexpand">True</property>
+ <property name="xalign">0</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">False</property>
+ <signal name="toggled" handler="search_fts_mode_changed" object="NautilusSearchPopover" swapped="no" />
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="filename_search_button">
+ <property name="label" translatable="yes">File Name</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Search only on the file name</property>
+ <property name="hexpand">True</property>
+ <property name="xalign">0</property>
+ <property name="draw_indicator">False</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>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <style>
+ <class name="linked"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">6</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </template>
+ <object class="GtkSizeGroup">
+ <property name="mode">vertical</property>
+ <property name="ignore_hidden">True</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">
+ <property name="mode">horizontal</property>
+ <property name="ignore_hidden">True</property>
+ <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-starred-is-empty.ui b/src/resources/ui/nautilus-starred-is-empty.ui
new file mode 100644
index 0000000..04465cd
--- /dev/null
+++ b/src/resources/ui/nautilus-starred-is-empty.ui
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.0 -->
+<interface>
+ <requires lib="gtk+" version="3.20"/>
+ <object class="GtkGrid" id="starred_is_empty">
+ <property name="can_focus">False</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="row_spacing">12</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="pixel_size">80</property>
+ <property name="icon_name">starred-symbolic</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Starred files will appear here</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ <attribute name="scale" value="1.4399999999999999"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+</interface>
diff --git a/src/resources/ui/nautilus-toolbar-switcher.ui b/src/resources/ui/nautilus-toolbar-switcher.ui
new file mode 100644
index 0000000..3b13c92
--- /dev/null
+++ b/src/resources/ui/nautilus-toolbar-switcher.ui
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.0 -->
+<interface>
+ <requires lib="gtk+" version="3.20"/>
+ <object class="GtkStack" id="toolbar_switcher">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">False</property>
+ <property name="transition_type">crossfade</property>
+ <child>
+ <object class="GtkBox" id="path_bar_container">
+ <property name="can_focus">False</property>
+ <property name="valign">center</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <style>
+ <class name="path-bar-box"/>
+ </style>
+ </object>
+ <packing>
+ <property name="name">pathbar</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="location_entry_container">
+ <property name="can_focus">False</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <style>
+ <class name="linked"/>
+ </style>
+ </object>
+ <packing>
+ <property name="name">location</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="search_container">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="name">search</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+</interface> \ No newline at end of file
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..ff9a1c5
--- /dev/null
+++ b/src/resources/ui/nautilus-toolbar-view-menu.ui
@@ -0,0 +1,296 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.0 -->
+<interface>
+ <requires lib="gtk+" version="3.16"/>
+ <object class="GtkBox" id="extended_section">
+ <property name="width_request">160</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkBox" id="sort_menu">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkSeparator">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_top">6</property>
+ <property name="margin_bottom">6</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">False</property>
+ <property 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</property>
+ <property name="xalign">0</property>
+ <style>
+ <class name="nautilus-menu-sort-heading"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkModelButton" id="sort_name">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="action_name">view.sort</property>
+ <property name="action_target">'name'</property>
+ <property name="text" translatable="yes" context="Sort Criterion" comments="This is used to sort by name in the toolbar view menu">_A-Z</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkModelButton" id="sort_name_desc">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="action_name">view.sort</property>
+ <property name="action_target">'name-desc'</property>
+ <property name="text" translatable="yes" context="Sort Criterion" comments="This is used to sort by name, in descending order in the toolbar view menu">_Z-A</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkModelButton" id="sort_modification_date_desc">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="action_name">view.sort</property>
+ <property name="action_target">'modification-date-desc'</property>
+ <property name="text" translatable="yes">Last _Modified</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkModelButton" id="sort_modification_date">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="action_name">view.sort</property>
+ <property name="action_target">'modification-date'</property>
+ <property name="text" translatable="yes">_First Modified</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkModelButton" id="sort_size">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="action_name">view.sort</property>
+ <property name="action_target">'size'</property>
+ <property name="text" translatable="yes">_Size</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">6</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkModelButton" id="sort_type">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="action_name">view.sort</property>
+ <property name="action_target">'type'</property>
+ <property name="text" translatable="yes">_Type</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">7</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkModelButton" id="sort_trash_time">
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="action_name">view.sort</property>
+ <property name="action_target">'trash-time'</property>
+ <property name="text" translatable="yes">Last _Trashed</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">8</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparator">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_top">6</property>
+ <property name="margin_bottom">6</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkModelButton" id="visible_columns">
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="action_name">view.visible-columns</property>
+ <property name="text" translatable="yes">_Visible Columns…</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkModelButton" id="reload">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="action_name">win.reload</property>
+ <property name="text" translatable="yes">R_eload</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkModelButton" id="stop">
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="action_name">win.stop</property>
+ <property name="text" translatable="yes">St_op</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">5</property>
+ </packing>
+ </child>
+ </object>
+ <object class="GtkBox" id="zoom_section">
+ <property name="width_request">160</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkBox" id="zoom_controls_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="homogeneous">True</property>
+ <child>
+ <object class="GtkButton" id="zoom-out">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Zoom out</property>
+ <property name="action_name">view.zoom-out</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">zoom-out-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="zoom-default">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Reset zoom</property>
+ <property name="action_name">view.zoom-standard</property>
+ <child>
+ <object class="GtkLabel" id="zoom_level_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="width_chars">5</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="zoom-in">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Zoom in</property>
+ <property name="action_name">view.zoom-in</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">zoom-in-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <style>
+ <class name="linked"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+</interface>
diff --git a/src/resources/ui/nautilus-toolbar.ui b/src/resources/ui/nautilus-toolbar.ui
new file mode 100644
index 0000000..51a8828
--- /dev/null
+++ b/src/resources/ui/nautilus-toolbar.ui
@@ -0,0 +1,744 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.0 -->
+<interface>
+ <requires lib="gtk+" version="3.22"/>
+ <object class="GtkPopoverMenu" id="app_menu">
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">10</property>
+ <property name="margin_end">10</property>
+ <property name="margin_top">10</property>
+ <property name="margin_bottom">10</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_bottom">6</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkButton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">New Window</property>
+ <property name="hexpand">True</property>
+ <property name="action_name">app.new-window</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">window-new-symbolic</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">New Tab</property>
+ <property name="hexpand">True</property>
+ <property name="action_name">win.new-tab</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">tab-new-symbolic</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">New Folder</property>
+ <property name="hexpand">True</property>
+ <property name="action_name">view.new-folder</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">folder-new-symbolic</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparator">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">6</property>
+ <property name="margin_top">6</property>
+ <property name="margin_bottom">6</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_end">24</property>
+ <property name="hexpand">True</property>
+ <property name="label" translatable="yes">Edit</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Cut</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="hexpand">True</property>
+ <property name="action_name">view.cut</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">edit-cut-symbolic</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes" comments="Translators: This is a verb">Copy</property>
+ <property name="halign">center</property>
+ <property name="hexpand">True</property>
+ <property name="action_name">view.copy</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">edit-copy-symbolic</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Paste</property>
+ <property name="halign">center</property>
+ <property name="hexpand">True</property>
+ <property name="action_name">view.paste</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">edit-paste-symbolic</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparator">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkModelButton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="margin_top">6</property>
+ <property name="margin_bottom">6</property>
+ <property name="action_name">view.select-all</property>
+ <property name="text" translatable="yes">Select All</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparator">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkModelButton" id="show_hidden_files">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="margin_top">6</property>
+ <property name="action_name">view.show-hidden-files</property>
+ <property name="text" translatable="yes">Show _Hidden Files</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">6</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkModelButton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="margin_bottom">6</property>
+ <property name="action_name">app.show-hide-sidebar</property>
+ <property name="text" translatable="yes">Show _Sidebar</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">7</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparator">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">8</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkModelButton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="margin_top">6</property>
+ <property name="action_name">app.preferences</property>
+ <property name="text" translatable="yes">_Preferences</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">9</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkModelButton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="action_name">app.show-help-overlay</property>
+ <property name="text" translatable="yes">_Keyboard Shortcuts</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">10</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkModelButton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="action_name">app.help</property>
+ <property name="text" translatable="yes">_Help</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">11</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkModelButton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="action_name">app.about</property>
+ <property name="text" translatable="yes">_About Files</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">12</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="submenu">main</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <object class="GtkPopoverMenu" id="menu_popover">
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkBox">
+ <property name="width_request">160</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">9</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkBox" id="view_menu_zoom_section">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="view_menu_undo_redo_section">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkSeparator">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_top">6</property>
+ <property name="margin_bottom">6</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkModelButton" id="undo_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="action_name">win.undo</property>
+ <property name="text" translatable="yes">_Undo</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkModelButton" id="redo_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="action_name">win.redo</property>
+ <property name="text" translatable="yes">_Redo</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="view_menu_extended_section">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="submenu">main</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <object class="GtkPopover" id="operations_popover">
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hscrollbar_policy">never</property>
+ <property name="max_content_height">270</property>
+ <property name="propagate_natural_height">True</property>
+ <child>
+ <object class="GtkViewport">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkBox" id="operations_container">
+ <property name="visible">True</property>
+ <property name="can_focus">False</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>
+ <property name="orientation">vertical</property>
+ <property name="spacing">10</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <template class="NautilusToolbar" parent="GtkHeaderBar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="show_close_button">True</property>
+ <child type="title">
+ <object class="GtkBox" id="header_toolbar">
+ <property name="width_request">270</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkBox" id="toolbar_switcher_container">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="valign">center</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToggleButton" id="search_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <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>
+ <child>
+ <object class="GtkImage" id="search_icon">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">edit-find-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ </child>
+ <style>
+ <class name="image-button"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="pack_type">end</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="navigation_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkButton" id="back_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Go back</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="action_name">win.back</property>
+ <child>
+ <object class="GtkImage" id="back_icon">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">go-previous-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ </child>
+ <style>
+ <class name="image-button"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="forward_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Go forward</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="action_name">win.forward</property>
+ <child>
+ <object class="GtkImage" id="forward_icon">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">go-next-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ </child>
+ <style>
+ <class name="image-button"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <style>
+ <class name="linked"/>
+ <class name="raised"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">72</property>
+ <child>
+ <object class="GtkRevealer" id="operations_revealer">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="transition_type">slide-right</property>
+ <child>
+ <object class="GtkMenuButton" id="operations_button">
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Show operations</property>
+ <property name="margin_end">6</property>
+ <property name="popover">operations_popover</property>
+ <signal name="toggled" handler="on_operations_button_toggled" object="NautilusToolbar" swapped="yes"/>
+ <child>
+ <object class="GtkDrawingArea" id="operations_icon">
+ <property name="width_request">16</property>
+ <property name="height_request">16</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <signal name="draw" handler="on_operations_icon_draw" object="NautilusToolbar" swapped="no"/>
+ </object>
+ </child>
+ <style>
+ <class name="button"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="margin_end">6</property>
+ <child>
+ <object class="GtkButton" id="view_toggle_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Toggle view</property>
+ <property name="action_name">slot.files-view-mode-toggle</property>
+ <child>
+ <object class="GtkImage" id="view_toggle_icon">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_size">1</property>
+ </object>
+ </child>
+ <style>
+ <class name="image-button"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkMenuButton" id="view_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes" comments="“View” is a noun">View options</property>
+ <property name="halign">start</property>
+ <property name="action_name">win.view-menu</property>
+ <property name="popover">menu_popover</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">pan-down-symbolic</property>
+ </object>
+ </child>
+ <style>
+ <class name="disclosure-button"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <style>
+ <class name="linked"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkMenuButton" id="app_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="popover">app_menu</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">open-menu-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ </child>
+ <style>
+ <class name="image-button"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="pack_type">end</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </template>
+</interface>
diff --git a/src/resources/ui/nautilus-trash-is-empty.ui b/src/resources/ui/nautilus-trash-is-empty.ui
new file mode 100644
index 0000000..528f21d
--- /dev/null
+++ b/src/resources/ui/nautilus-trash-is-empty.ui
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.0 -->
+<interface>
+ <requires lib="gtk+" version="3.20"/>
+ <object class="GtkGrid" id="trash_is_empty">
+ <property name="can_focus">False</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="row_spacing">12</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="pixel_size">80</property>
+ <property name="icon_name">user-trash-symbolic</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Trash is Empty</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ <attribute name="scale" value="1.4399999999999999"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+</interface>
diff --git a/src/resources/ui/nautilus-window.ui b/src/resources/ui/nautilus-window.ui
new file mode 100644
index 0000000..4eef37c
--- /dev/null
+++ b/src/resources/ui/nautilus-window.ui
@@ -0,0 +1,197 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="NautilusWindow" parent="GtkApplicationWindow">
+ <property name="show-menubar">False</property>
+ <property name="title" translatable="yes">_Files</property>
+ <child>
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkPaned" id="content_paned">
+ <property name="visible">True</property>
+ <property name="vexpand">True</property>
+ <property name="hexpand">True</property>
+ <child>
+ <object class="GtkBox" id="sidebar">
+ <property name="visible">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkPlacesSidebar" id="places_sidebar">
+ <property name="visible">True</property>
+ <property name="populate-all">True</property>
+ <property name="show-other-locations">True</property>
+ <property name="show-starred-location">True</property>
+ <signal name="show-other-locations-with-flags" handler="places_sidebar_show_other_locations_with_flags" object="NautilusWindow" swapped="yes" />
+ <signal name="show-starred-location" handler="places_sidebar_show_starred_location" object="NautilusWindow" swapped="yes" />
+ </object>
+ <packing>
+ <property name="pack_type">start</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="resize">False</property>
+ <property name="shrink">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkOverlay" id="main_view">
+ <property name="visible">True</property>
+ <child>
+ <object class="NautilusNotebook" id="notebook">
+ <property name="visible">True</property>
+ <property name="show-tabs">False</property>
+ <property name="show-border">False</property>
+ </object>
+ </child>
+ <child type="overlay">
+ <object class="GtkRevealer" id="in_app_notification_undo">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">center</property>
+ <property name="valign">start</property>
+ <property name="transition_duration">100</property>
+ <child>
+ <object class="GtkFrame">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">12</property>
+ <property name="margin_end">4</property>
+ <child>
+ <object class="GtkLabel" id="in_app_notification_undo_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="max_width_chars">50</property>
+ <property name="ellipsize">middle</property>
+ <property name="margin_end">30</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="in_app_notification_undo_undo_button">
+ <property name="label" translatable="yes">Undo</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="no_show_all">True</property>
+ <property name="margin_end">6</property>
+ <style>
+ <class name="text-button"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="in_app_notification_undo_close_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="relief">none</property>
+ <property name="focus_on_click">False</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">window-close-symbolic</property>
+ <property name="icon_size">2</property>
+ </object>
+ </child>
+ <style>
+ <class name="image-button"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ <style>
+ <class name="app-notification"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="overlay">
+ <object class="GtkRevealer" id="notification_operation">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">center</property>
+ <property name="valign">start</property>
+ <property name="transition_duration">100</property>
+ <child>
+ <object class="GtkFrame">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">12</property>
+ <property name="margin_end">4</property>
+ <child>
+ <object class="GtkLabel" id="notification_operation_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="max_width_chars">50</property>
+ <property name="ellipsize">middle</property>
+ <property name="margin_end">30</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="notification_operation_open">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="no_show_all">True</property>
+ <property name="margin_end">6</property>
+ <signal name="clicked" handler="on_notification_operation_open_clicked" object="NautilusWindow" swapped="no"/>
+ <style>
+ <class name="text-button"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="notification_operation_close">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="relief">none</property>
+ <property name="focus_on_click">False</property>
+ <signal name="clicked" handler="on_notification_operation_close_clicked" object="NautilusWindow" swapped="no"/>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">window-close-symbolic</property>
+ <property name="icon_size">2</property>
+ </object>
+ </child>
+ <style>
+ <class name="image-button"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ <style>
+ <class name="app-notification"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="shrink">False</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>