summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am130
-rw-r--r--src/nautilus-application.c20
-rw-r--r--src/nautilus-autorun-software.c4
-rw-r--r--src/nautilus-bookmark-list.c6
-rw-r--r--src/nautilus-bookmark-list.h2
-rw-r--r--src/nautilus-bookmark.c780
-rw-r--r--src/nautilus-bookmark.h87
-rw-r--r--src/nautilus-canvas-container.c7878
-rw-r--r--src/nautilus-canvas-container.h324
-rw-r--r--src/nautilus-canvas-dnd.c1820
-rw-r--r--src/nautilus-canvas-dnd.h59
-rw-r--r--src/nautilus-canvas-item.c2635
-rw-r--r--src/nautilus-canvas-item.h99
-rw-r--r--src/nautilus-canvas-private.h269
-rw-r--r--src/nautilus-canvas-view-container.c6
-rw-r--r--src/nautilus-canvas-view-container.h2
-rw-r--r--src/nautilus-canvas-view.c24
-rw-r--r--src/nautilus-canvas-view.h2
-rw-r--r--src/nautilus-clipboard-monitor.c319
-rw-r--r--src/nautilus-clipboard-monitor.h80
-rw-r--r--src/nautilus-clipboard.c133
-rw-r--r--src/nautilus-clipboard.h37
-rw-r--r--src/nautilus-column-chooser.c682
-rw-r--r--src/nautilus-column-chooser.h65
-rw-r--r--src/nautilus-column-utilities.c357
-rw-r--r--src/nautilus-column-utilities.h39
-rw-r--r--src/nautilus-dbus-manager.c4
-rw-r--r--src/nautilus-debug.c163
-rw-r--r--src/nautilus-debug.h79
-rw-r--r--src/nautilus-default-file-icon.c534
-rw-r--r--src/nautilus-default-file-icon.h29
-rw-r--r--src/nautilus-desktop-item-properties.c4
-rw-r--r--src/nautilus-directory-async.c4558
-rw-r--r--src/nautilus-directory-notify.h62
-rw-r--r--src/nautilus-directory-private.h229
-rw-r--r--src/nautilus-directory.c1902
-rw-r--r--src/nautilus-directory.h251
-rw-r--r--src/nautilus-dnd.c952
-rw-r--r--src/nautilus-dnd.h147
-rw-r--r--src/nautilus-empty-view.c2
-rw-r--r--src/nautilus-entry.c365
-rw-r--r--src/nautilus-entry.h67
-rw-r--r--src/nautilus-error-reporting.c4
-rw-r--r--src/nautilus-error-reporting.h2
-rw-r--r--src/nautilus-file-attributes.h43
-rw-r--r--src/nautilus-file-changes-queue.c393
-rw-r--r--src/nautilus-file-changes-queue.h39
-rw-r--r--src/nautilus-file-conflict-dialog.c671
-rw-r--r--src/nautilus-file-conflict-dialog.h75
-rw-r--r--src/nautilus-file-operations.c7243
-rw-r--r--src/nautilus-file-operations.h152
-rw-r--r--src/nautilus-file-private.h288
-rw-r--r--src/nautilus-file-queue.c122
-rw-r--r--src/nautilus-file-queue.h49
-rw-r--r--src/nautilus-file-undo-manager.c272
-rw-r--r--src/nautilus-file-undo-manager.h83
-rw-r--r--src/nautilus-file-undo-operations.c1677
-rw-r--r--src/nautilus-file-undo-operations.h299
-rw-r--r--src/nautilus-file-utilities.c1181
-rw-r--r--src/nautilus-file-utilities.h101
-rw-r--r--src/nautilus-file.c8382
-rw-r--r--src/nautilus-file.h626
-rw-r--r--src/nautilus-files-view-dnd.c6
-rw-r--r--src/nautilus-files-view.c42
-rw-r--r--src/nautilus-files-view.h6
-rw-r--r--src/nautilus-freedesktop-dbus.c2
-rw-r--r--src/nautilus-global-preferences.c70
-rw-r--r--src/nautilus-global-preferences.h179
-rw-r--r--src/nautilus-icon-info.c579
-rw-r--r--src/nautilus-icon-info.h94
-rw-r--r--src/nautilus-icon-names.h51
-rw-r--r--src/nautilus-image-properties-page.c2
-rw-r--r--src/nautilus-keyfile-metadata.c316
-rw-r--r--src/nautilus-keyfile-metadata.h46
-rw-r--r--src/nautilus-lib-self-check-functions.c35
-rw-r--r--src/nautilus-lib-self-check-functions.h47
-rw-r--r--src/nautilus-link.c597
-rw-r--r--src/nautilus-link.h50
-rw-r--r--src/nautilus-list-model.c2
-rw-r--r--src/nautilus-list-model.h4
-rw-r--r--src/nautilus-list-view.c24
-rw-r--r--src/nautilus-location-entry.c6
-rw-r--r--src/nautilus-location-entry.h2
-rw-r--r--src/nautilus-main.c2
-rw-r--r--src/nautilus-metadata.c73
-rw-r--r--src/nautilus-metadata.h72
-rw-r--r--src/nautilus-mime-actions.c16
-rw-r--r--src/nautilus-mime-actions.h2
-rw-r--r--src/nautilus-mime-application-chooser.c473
-rw-r--r--src/nautilus-mime-application-chooser.h51
-rw-r--r--src/nautilus-module.c294
-rw-r--r--src/nautilus-module.h41
-rw-r--r--src/nautilus-monitor.c161
-rw-r--r--src/nautilus-monitor.h36
-rw-r--r--src/nautilus-pathbar.c12
-rw-r--r--src/nautilus-preferences-window.c8
-rw-r--r--src/nautilus-previewer.c2
-rw-r--r--src/nautilus-private-enum-types.c.template40
-rw-r--r--src/nautilus-private-enum-types.h.template27
-rw-r--r--src/nautilus-profile.c63
-rw-r--r--src/nautilus-profile.h56
-rw-r--r--src/nautilus-program-choosing.c402
-rw-r--r--src/nautilus-program-choosing.h56
-rw-r--r--src/nautilus-progress-info-manager.c224
-rw-r--r--src/nautilus-progress-info-manager.h73
-rw-r--r--src/nautilus-progress-info-widget.h2
-rw-r--r--src/nautilus-progress-info.c737
-rw-r--r--src/nautilus-progress-info.h95
-rw-r--r--src/nautilus-progress-persistence-handler.c4
-rw-r--r--src/nautilus-properties-window.c18
-rw-r--r--src/nautilus-properties-window.h2
-rw-r--r--src/nautilus-query-editor.c6
-rw-r--r--src/nautilus-query-editor.h2
-rw-r--r--src/nautilus-query.c614
-rw-r--r--src/nautilus-query.h86
-rw-r--r--src/nautilus-recent.c80
-rw-r--r--src/nautilus-recent.h13
-rw-r--r--src/nautilus-search-directory-file.c295
-rw-r--r--src/nautilus-search-directory-file.h54
-rw-r--r--src/nautilus-search-directory.c989
-rw-r--r--src/nautilus-search-directory.h66
-rw-r--r--src/nautilus-search-engine-model.c340
-rw-r--r--src/nautilus-search-engine-model.h52
-rw-r--r--src/nautilus-search-engine-simple.c525
-rw-r--r--src/nautilus-search-engine-simple.h47
-rw-r--r--src/nautilus-search-engine-tracker.c495
-rw-r--r--src/nautilus-search-engine-tracker.h49
-rw-r--r--src/nautilus-search-engine.c376
-rw-r--r--src/nautilus-search-engine.h57
-rw-r--r--src/nautilus-search-hit.c327
-rw-r--r--src/nautilus-search-hit.h61
-rw-r--r--src/nautilus-search-popover.c6
-rw-r--r--src/nautilus-search-popover.h2
-rw-r--r--src/nautilus-search-provider.c142
-rw-r--r--src/nautilus-search-provider.h97
-rw-r--r--src/nautilus-selection-canvas-item.c712
-rw-r--r--src/nautilus-selection-canvas-item.h65
-rw-r--r--src/nautilus-shell-search-provider.c10
-rw-r--r--src/nautilus-signaller.c92
-rw-r--r--src/nautilus-signaller.h44
-rw-r--r--src/nautilus-thumbnails.c569
-rw-r--r--src/nautilus-thumbnails.h40
-rw-r--r--src/nautilus-toolbar.c8
-rw-r--r--src/nautilus-trash-bar.c8
-rw-r--r--src/nautilus-trash-monitor.c210
-rw-r--r--src/nautilus-trash-monitor.h63
-rw-r--r--src/nautilus-tree-view-drag-dest.c1265
-rw-r--r--src/nautilus-tree-view-drag-dest.h99
-rw-r--r--src/nautilus-ui-utilities.c435
-rw-r--r--src/nautilus-ui-utilities.h54
-rw-r--r--src/nautilus-undo-private.h33
-rw-r--r--src/nautilus-vfs-directory.c152
-rw-r--r--src/nautilus-vfs-directory.h52
-rw-r--r--src/nautilus-vfs-file.c696
-rw-r--r--src/nautilus-vfs-file.h52
-rw-r--r--src/nautilus-video-mime-types.h65
-rw-r--r--src/nautilus-view.h2
-rw-r--r--src/nautilus-window-slot-dnd.h2
-rw-r--r--src/nautilus-window-slot.c14
-rw-r--r--src/nautilus-window.c26
-rw-r--r--src/nautilus-window.h4
-rw-r--r--src/nautilus-x-content-bar.c6
162 files changed, 61495 insertions, 175 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index d58d67c0d..4dc71cade 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -11,8 +11,6 @@ noinst_LTLIBRARIES=libnautilus.la
AM_CPPFLAGS = \
-I$(top_srcdir) \
- -I$(top_srcdir)/libnautilus-private \
- -I$(top_builddir)/libnautilus-private \
-I$(top_srcdir)/libgd \
$(BASE_CFLAGS) \
$(COMMON_CFLAGS) \
@@ -20,9 +18,11 @@ AM_CPPFLAGS = \
$(WARNING_CFLAGS) \
$(EXIF_CFLAGS) \
$(EXEMPI_CFLAGS) \
+ $(TRACKER_CFLAGS) \
-DDATADIR=\""$(datadir)"\" \
-DLIBDIR=\""$(libdir)"\" \
-DNAUTILUS_DATADIR=\""$(datadir)/nautilus"\" \
+ -DNAUTILUS_EXTENSIONDIR=\""$(libdir)/nautilus/extensions-3.0"\" \
-DPREFIX=\""$(prefix)"\" \
-DVERSION="\"$(VERSION)\"" \
$(DISABLE_DEPRECATED) \
@@ -33,7 +33,6 @@ LDADD =\
$(NULL)
libnautilus_la_LIBADD =\
- $(top_builddir)/libnautilus-private/libnautilus-private.la \
$(top_builddir)/libgd/libgd.la \
$(BASE_LIBS) \
$(COMMON_LIBS) \
@@ -42,6 +41,10 @@ libnautilus_la_LIBADD =\
$(EXIF_LIBS) \
$(EXEMPI_LIBS) \
$(POPT_LIBS) \
+ $(TRACKER_LIBS) \
+ $(SELINUX_LIBS) \
+ $(top_builddir)/eel/libeel-2.la \
+ $(top_builddir)/libnautilus-extension/libnautilus-extension.la \
$(NULL)
dbus_built_sources = \
@@ -85,6 +88,8 @@ $(dbus_shell_search_provider_built_sources) : Makefile.am $(top_srcdir)/data/she
headers = \
nautilus-search-popover.h \
nautilus-special-location-bar.h \
+ nautilus-query.h \
+ nautilus-search-provider.h \
$(NULL)
resource_files = $(shell glib-compile-resources --sourcedir=$(srcdir)/resources --generate-dependencies $(srcdir)/resources/nautilus.gresource.xml)
@@ -132,6 +137,12 @@ nautilus-enum-types.c: $(headers) Makefile
&& cp xgen-gtc $(@F) \
&& rm -f xgen-gtc
+if ENABLE_TRACKER
+nautilus_tracker_engine_sources = \
+ nautilus-search-engine-tracker.c \
+ nautilus-search-engine-tracker.h
+endif
+
nautilus_built_sources = \
$(dbus_built_sources) \
$(dbus_freedesktop_built_sources) \
@@ -224,9 +235,120 @@ nautilus_no_main_sources = \
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-monitor.c \
+ nautilus-clipboard-monitor.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-default-file-icon.c \
+ nautilus-default-file-icon.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-entry.c \
+ nautilus-entry.h \
+ nautilus-file-attributes.h \
+ nautilus-file-changes-queue.c \
+ nautilus-file-changes-queue.h \
+ nautilus-file-conflict-dialog.c \
+ nautilus-file-conflict-dialog.h \
+ nautilus-file-operations.c \
+ nautilus-file-operations.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-link.c \
+ nautilus-link.h \
+ nautilus-metadata.h \
+ nautilus-metadata.c \
+ nautilus-mime-application-chooser.c \
+ nautilus-mime-application-chooser.h \
+ 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-recent.c \
+ nautilus-recent.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-model.c \
+ nautilus-search-engine-model.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-query.h \
+ 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_tracker_engine_sources) \
$(NULL)
-nodist_libnautilus_la_SOURCES = \
+libnautilus_la_SOURCES = \
$(nautilus_built_sources) \
$(nautilus_no_main_sources) \
$(NULL)
diff --git a/src/nautilus-application.c b/src/nautilus-application.c
index 1f3b6c176..de136cb61 100644
--- a/src/nautilus-application.c
+++ b/src/nautilus-application.c
@@ -39,19 +39,19 @@
#include "nautilus-window-slot.h"
#include "nautilus-preferences-window.h"
-#include <libnautilus-private/nautilus-directory-private.h>
-#include <libnautilus-private/nautilus-file-utilities.h>
-#include <libnautilus-private/nautilus-file-operations.h>
-#include <libnautilus-private/nautilus-global-preferences.h>
-#include <libnautilus-private/nautilus-lib-self-check-functions.h>
-#include <libnautilus-private/nautilus-module.h>
-#include <libnautilus-private/nautilus-profile.h>
-#include <libnautilus-private/nautilus-signaller.h>
-#include <libnautilus-private/nautilus-ui-utilities.h>
+#include "nautilus-directory-private.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-file-operations.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-lib-self-check-functions.h"
+#include "nautilus-module.h"
+#include "nautilus-profile.h"
+#include "nautilus-signaller.h"
+#include "nautilus-ui-utilities.h"
#include <libnautilus-extension/nautilus-menu-provider.h>
#define DEBUG_FLAG NAUTILUS_DEBUG_APPLICATION
-#include <libnautilus-private/nautilus-debug.h>
+#include "nautilus-debug.h"
#include <sys/types.h>
#include <sys/stat.h>
diff --git a/src/nautilus-autorun-software.c b/src/nautilus-autorun-software.c
index 0c7e8551e..8720daa6f 100644
--- a/src/nautilus-autorun-software.c
+++ b/src/nautilus-autorun-software.c
@@ -32,8 +32,8 @@
#include <glib/gi18n.h>
-#include <libnautilus-private/nautilus-module.h>
-#include <libnautilus-private/nautilus-icon-info.h>
+#include "nautilus-module.h"
+#include "nautilus-icon-info.h"
typedef struct
{
diff --git a/src/nautilus-bookmark-list.c b/src/nautilus-bookmark-list.c
index d22662056..e7939a541 100644
--- a/src/nautilus-bookmark-list.c
+++ b/src/nautilus-bookmark-list.c
@@ -26,9 +26,9 @@
#include <config.h>
#include "nautilus-bookmark-list.h"
-#include <libnautilus-private/nautilus-file-utilities.h>
-#include <libnautilus-private/nautilus-file.h>
-#include <libnautilus-private/nautilus-icon-names.h>
+#include "nautilus-file-utilities.h"
+#include "nautilus-file.h"
+#include "nautilus-icon-names.h"
#include <gio/gio.h>
#include <string.h>
diff --git a/src/nautilus-bookmark-list.h b/src/nautilus-bookmark-list.h
index e8b2d7361..699a8b7be 100644
--- a/src/nautilus-bookmark-list.h
+++ b/src/nautilus-bookmark-list.h
@@ -26,7 +26,7 @@
#ifndef NAUTILUS_BOOKMARK_LIST_H
#define NAUTILUS_BOOKMARK_LIST_H
-#include <libnautilus-private/nautilus-bookmark.h>
+#include "nautilus-bookmark.h"
#include <gio/gio.h>
typedef struct NautilusBookmarkList NautilusBookmarkList;
diff --git a/src/nautilus-bookmark.c b/src/nautilus-bookmark.c
new file mode 100644
index 000000000..29a36f9d1
--- /dev/null
+++ b/src/nautilus-bookmark.c
@@ -0,0 +1,780 @@
+
+/* 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-global-preferences.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 NautilusBookmarkDetails
+{
+ 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->details->name, new_name) != 0) {
+ g_free (bookmark->details->name);
+ bookmark->details->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)
+{
+ gchar *display_name;
+
+ if (self->details->has_custom_name) {
+ return;
+ }
+
+ display_name = nautilus_file_get_display_name (self->details->file);
+
+ if (nautilus_file_is_other_locations (self->details->file)) {
+ nautilus_bookmark_set_name_internal (self, _("Other Locations"));
+ } else if (nautilus_file_is_home (self->details->file)) {
+ nautilus_bookmark_set_name_internal (self, _("Home"));
+ } else if (g_strcmp0 (self->details->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);
+ }
+
+ g_free (display_name);
+}
+
+static void
+bookmark_file_changed_callback (NautilusFile *file,
+ NautilusBookmark *bookmark)
+{
+ GFile *location;
+
+ g_assert (file == bookmark->details->file);
+
+ DEBUG ("%s: file changed", nautilus_bookmark_get_name (bookmark));
+
+ location = nautilus_file_get_location (file);
+
+ if (!g_file_equal (bookmark->details->location, location) &&
+ !nautilus_file_is_in_trash (file)) {
+ DEBUG ("%s: file got moved", nautilus_bookmark_get_name (bookmark));
+
+ g_object_unref (bookmark->details->location);
+ bookmark->details->location = g_object_ref (location);
+
+ g_object_notify_by_pspec (G_OBJECT (bookmark), properties[PROP_LOCATION]);
+ g_signal_emit (bookmark, signals[CONTENTS_CHANGED], 0);
+ }
+
+ g_object_unref (location);
+
+ 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 *warning, *emblemed_icon;
+ GEmblem *emblem;
+
+ 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 (emblem);
+ g_object_unref (warning);
+ 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 */
+ if (xdg_type == G_USER_DIRECTORY_DESKTOP &&
+ !g_settings_get_boolean (gnome_background_preferences, NAUTILUS_PREFERENCES_SHOW_DESKTOP)) {
+ return FALSE;
+ }
+
+ return (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->details->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->details->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)
+{
+ GIcon *icon, *symbolic_icon;
+ char *uri;
+
+ if (g_file_is_native (bookmark->details->location)) {
+ symbolic_icon = get_native_icon (bookmark, TRUE);
+ icon = get_native_icon (bookmark, FALSE);
+ } else {
+ uri = nautilus_bookmark_get_uri (bookmark);
+ if (g_str_has_prefix (uri, EEL_SEARCH_URI)) {
+ symbolic_icon = g_themed_icon_new (NAUTILUS_ICON_FOLDER_SAVED_SEARCH);
+ icon = g_themed_icon_new (NAUTILUS_ICON_FULLCOLOR_FOLDER_SAVED_SEARCH);
+ } else {
+ symbolic_icon = g_themed_icon_new (NAUTILUS_ICON_FOLDER_REMOTE);
+ icon = g_themed_icon_new (NAUTILUS_ICON_FULLCOLOR_FOLDER_REMOTE);
+ }
+ g_free (uri);
+ }
+
+ if (!bookmark->details->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);
+
+ g_object_unref (icon);
+ g_object_unref (symbolic_icon);
+}
+
+static void
+nautilus_bookmark_disconnect_file (NautilusBookmark *bookmark)
+{
+ if (bookmark->details->file != NULL) {
+ DEBUG ("%s: disconnecting file",
+ nautilus_bookmark_get_name (bookmark));
+
+ g_signal_handlers_disconnect_by_func (bookmark->details->file,
+ G_CALLBACK (bookmark_file_changed_callback),
+ bookmark);
+ g_clear_object (&bookmark->details->file);
+ }
+
+ if (bookmark->details->cancellable != NULL) {
+ g_cancellable_cancel (bookmark->details->cancellable);
+ g_clear_object (&bookmark->details->cancellable);
+ }
+
+ if (bookmark->details->exists_id != 0) {
+ g_source_remove (bookmark->details->exists_id);
+ bookmark->details->exists_id = 0;
+ }
+}
+
+static void
+nautilus_bookmark_connect_file (NautilusBookmark *bookmark)
+{
+ if (bookmark->details->file != NULL) {
+ DEBUG ("%s: file already connected, returning",
+ nautilus_bookmark_get_name (bookmark));
+ return;
+ }
+
+ if (bookmark->details->exists) {
+ DEBUG ("%s: creating file", nautilus_bookmark_get_name (bookmark));
+
+ bookmark->details->file = nautilus_file_get (bookmark->details->location);
+ g_assert (!nautilus_file_is_gone (bookmark->details->file));
+
+ g_signal_connect_object (bookmark->details->file, "changed",
+ G_CALLBACK (bookmark_file_changed_callback), bookmark, 0);
+ }
+
+ if (bookmark->details->icon == NULL ||
+ bookmark->details->symbolic_icon == NULL) {
+ nautilus_bookmark_set_icon_to_default (bookmark);
+ }
+
+ if (bookmark->details->file != NULL &&
+ nautilus_file_check_if_ready (bookmark->details->file, NAUTILUS_FILE_ATTRIBUTE_INFO)) {
+ bookmark_set_name_from_ready_file (bookmark, bookmark->details->file);
+ }
+
+ if (bookmark->details->name == NULL) {
+ bookmark->details->name = nautilus_compute_title_for_location (bookmark->details->location);
+ }
+}
+
+static void
+nautilus_bookmark_set_exists (NautilusBookmark *bookmark,
+ gboolean exists)
+{
+ if (bookmark->details->exists == exists) {
+ return;
+ }
+
+ bookmark->details->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->details->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)
+{
+ GFileInfo *info;
+ NautilusBookmark *bookmark;
+ 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)) {
+ g_clear_error (&error);
+ return;
+ }
+
+ g_clear_error (&error);
+ bookmark = user_data;
+
+ if (info) {
+ exists = TRUE;
+
+ g_object_unref (info);
+ g_clear_object (&bookmark->details->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->details->location) &&
+ bookmark->details->exists_id == 0) {
+ bookmark->details->exists_id =
+ g_idle_add (exists_non_native_idle_cb, bookmark);
+ return;
+ }
+
+ if (bookmark->details->cancellable != NULL) {
+ return;
+ }
+
+ bookmark->details->cancellable = g_cancellable_new ();
+ g_file_query_info_async (bookmark->details->location,
+ G_FILE_ATTRIBUTE_STANDARD_TYPE,
+ 0, G_PRIORITY_DEFAULT,
+ bookmark->details->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->details->icon, new_icon)) {
+ g_clear_object (&self->details->icon);
+ self->details->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->details->symbolic_icon, new_icon)) {
+ g_clear_object (&self->details->symbolic_icon);
+ self->details->symbolic_icon = g_object_ref (new_icon);
+ }
+
+ break;
+ case PROP_LOCATION:
+ self->details->location = g_value_dup_object (value);
+ break;
+ case PROP_CUSTOM_NAME:
+ self->details->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->details->name);
+ break;
+ case PROP_ICON:
+ g_value_set_object (value, self->details->icon);
+ break;
+ case PROP_SYMBOLIC_ICON:
+ g_value_set_object (value, self->details->symbolic_icon);
+ break;
+ case PROP_LOCATION:
+ g_value_set_object (value, self->details->location);
+ break;
+ case PROP_CUSTOM_NAME:
+ g_value_set_boolean (value, self->details->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->details->location);
+ g_clear_object (&bookmark->details->icon);
+ g_clear_object (&bookmark->details->symbolic_icon);
+
+ g_free (bookmark->details->name);
+ g_free (bookmark->details->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,
+ G_STRUCT_OFFSET (NautilusBookmarkClass, contents_changed),
+ 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);
+
+ g_type_class_add_private (class, sizeof (NautilusBookmarkDetails));
+}
+
+static void
+nautilus_bookmark_init (NautilusBookmark *bookmark)
+{
+ bookmark->details = G_TYPE_INSTANCE_GET_PRIVATE (bookmark, NAUTILUS_TYPE_BOOKMARK,
+ NautilusBookmarkDetails);
+
+ bookmark->details->exists = TRUE;
+}
+
+const gchar *
+nautilus_bookmark_get_name (NautilusBookmark *bookmark)
+{
+ g_return_val_if_fail (NAUTILUS_IS_BOOKMARK (bookmark), NULL);
+
+ return bookmark->details->name;
+}
+
+gboolean
+nautilus_bookmark_get_has_custom_name (NautilusBookmark *bookmark)
+{
+ g_return_val_if_fail(NAUTILUS_IS_BOOKMARK (bookmark), FALSE);
+
+ return (bookmark->details->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 (a), 1);
+ g_return_val_if_fail (NAUTILUS_IS_BOOKMARK (b), 1);
+
+ bookmark_a = NAUTILUS_BOOKMARK (a);
+ bookmark_b = NAUTILUS_BOOKMARK (b);
+
+ if (!g_file_equal (bookmark_a->details->location,
+ bookmark_b->details->location)) {
+ return 1;
+ }
+
+ if (g_strcmp0 (bookmark_a->details->name,
+ bookmark_b->details->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->details->symbolic_icon) {
+ return g_object_ref (bookmark->details->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->details->icon) {
+ return g_object_ref (bookmark->details->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->details->location);
+}
+
+char *
+nautilus_bookmark_get_uri (NautilusBookmark *bookmark)
+{
+ GFile *file;
+ char *uri;
+
+ file = nautilus_bookmark_get_location (bookmark);
+ uri = g_file_get_uri (file);
+ g_object_unref (file);
+ return uri;
+}
+
+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->details->scroll_file);
+ bookmark->details->scroll_file = g_strdup (uri);
+}
+
+char *
+nautilus_bookmark_get_scroll_pos (NautilusBookmark *bookmark)
+{
+ return g_strdup (bookmark->details->scroll_file);
+}
diff --git a/src/nautilus-bookmark.h b/src/nautilus-bookmark.h
new file mode 100644
index 000000000..b716bebf2
--- /dev/null
+++ b/src/nautilus-bookmark.h
@@ -0,0 +1,87 @@
+
+/* 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>
+ */
+
+#ifndef NAUTILUS_BOOKMARK_H
+#define NAUTILUS_BOOKMARK_H
+
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+typedef struct NautilusBookmark NautilusBookmark;
+
+#define NAUTILUS_TYPE_BOOKMARK nautilus_bookmark_get_type()
+#define NAUTILUS_BOOKMARK(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_BOOKMARK, NautilusBookmark))
+#define NAUTILUS_BOOKMARK_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_BOOKMARK, NautilusBookmarkClass))
+#define NAUTILUS_IS_BOOKMARK(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_BOOKMARK))
+#define NAUTILUS_IS_BOOKMARK_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_BOOKMARK))
+#define NAUTILUS_BOOKMARK_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_BOOKMARK, NautilusBookmarkClass))
+
+typedef struct NautilusBookmarkDetails NautilusBookmarkDetails;
+
+struct NautilusBookmark {
+ GObject object;
+ NautilusBookmarkDetails *details;
+};
+
+struct NautilusBookmarkClass {
+ GObjectClass parent_class;
+
+ /* Signals that clients can connect to. */
+
+ /* The contents-changed signal is emitted when the bookmark's contents
+ * (custom name or URI) changed.
+ */
+ void (* contents_changed) (NautilusBookmark *bookmark);
+};
+
+typedef struct NautilusBookmarkClass NautilusBookmarkClass;
+
+GType nautilus_bookmark_get_type (void);
+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);
+
+#endif /* NAUTILUS_BOOKMARK_H */
diff --git a/src/nautilus-canvas-container.c b/src/nautilus-canvas-container.c
new file mode 100644
index 000000000..e7c1eea84
--- /dev/null
+++ b/src/nautilus-canvas-container.c
@@ -0,0 +1,7878 @@
+
+/* 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 <config.h>
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <math.h>
+#include "nautilus-canvas-container.h"
+
+#include "nautilus-global-preferences.h"
+#include "nautilus-canvas-private.h"
+#include "nautilus-lib-self-check-functions.h"
+#include "nautilus-selection-canvas-item.h"
+#include <atk/atkaction.h>
+#include <eel/eel-accessibility.h>
+#include <eel/eel-vfs-extensions.h>
+#include <eel/eel-gtk-extensions.h>
+#include <eel/eel-art-extensions.h>
+
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkx.h>
+#include <glib/gi18n.h>
+#include <stdio.h>
+#include <string.h>
+
+#define DEBUG_FLAG NAUTILUS_DEBUG_CANVAS_CONTAINER
+#include "nautilus-debug.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
+
+/* Desktop layout mode defines */
+#define DESKTOP_PAD_HORIZONTAL 10
+#define DESKTOP_PAD_VERTICAL 10
+#define SNAP_SIZE_X 78
+#define SNAP_SIZE_Y 20
+
+#define SNAP_HORIZONTAL(func,x) ((func ((double)((x) - DESKTOP_PAD_HORIZONTAL) / SNAP_SIZE_X) * SNAP_SIZE_X) + DESKTOP_PAD_HORIZONTAL)
+#define SNAP_VERTICAL(func, y) ((func ((double)((y) - DESKTOP_PAD_VERTICAL) / SNAP_SIZE_Y) * SNAP_SIZE_Y) + DESKTOP_PAD_VERTICAL)
+
+#define SNAP_NEAREST_HORIZONTAL(x) SNAP_HORIZONTAL (floor, x + SNAP_SIZE_X / 2)
+#define SNAP_NEAREST_VERTICAL(y) SNAP_VERTICAL (floor, y + SNAP_SIZE_Y / 2)
+
+#define SNAP_CEIL_HORIZONTAL(x) SNAP_HORIZONTAL (ceil, x)
+#define SNAP_CEIL_VERTICAL(y) SNAP_VERTICAL (ceil, y)
+
+/* 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 void compute_stretch (StretchState *start,
+ StretchState *current);
+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 store_layout_timestamps_now (NautilusCanvasContainer *container);
+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,
+ CAN_ACCEPT_ITEM,
+ CONTEXT_CLICK_BACKGROUND,
+ CONTEXT_CLICK_SELECTION,
+ MIDDLE_CLICK,
+ GET_CONTAINER_URI,
+ GET_ICON_URI,
+ GET_ICON_ACTIVATION_URI,
+ GET_ICON_DROP_TARGET_URI,
+ GET_STORED_ICON_POSITION,
+ ICON_POSITION_CHANGED,
+ GET_STORED_LAYOUT_TIMESTAMP,
+ STORE_LAYOUT_TIMESTAMP,
+ ICON_RENAME_STARTED,
+ ICON_RENAME_ENDED,
+ ICON_STRETCH_STARTED,
+ ICON_STRETCH_ENDED,
+ LAYOUT_CHANGED,
+ 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)
+{
+ NautilusCanvasContainer *container;
+ double pixels_per_unit;
+ int container_left, container_top, container_right, container_bottom;
+ int x1, x2, y1, y2;
+ int container_x, container_y, container_width, container_height;
+ EelDRect icon_bounds;
+ int item_width, item_height;
+ int height_above, width_left;
+ int min_x, max_x, min_y, max_y;
+
+ if (icon->x == x && icon->y == y) {
+ return;
+ }
+
+ container = NAUTILUS_CANVAS_CONTAINER (EEL_CANVAS_ITEM (icon->item)->canvas);
+
+ if (nautilus_canvas_container_get_is_fixed_size (container)) {
+ /* FIXME: This should be:
+
+ container_x = GTK_WIDGET (container)->allocation.x;
+ container_y = GTK_WIDGET (container)->allocation.y;
+ container_width = GTK_WIDGET (container)->allocation.width;
+ container_height = GTK_WIDGET (container)->allocation.height;
+
+ But for some reason the widget allocation is sometimes not done
+ at startup, and the allocation is then only 45x60. which is
+ really bad.
+
+ For now, we have a cheesy workaround:
+ */
+ container_x = 0;
+ container_y = 0;
+ container_width = gdk_screen_width () - container_x
+ - container->details->left_margin
+ - container->details->right_margin;
+ container_height = gdk_screen_height () - container_y
+ - container->details->top_margin
+ - container->details->bottom_margin;
+ pixels_per_unit = EEL_CANVAS (container)->pixels_per_unit;
+ /* Clip the position of the icon within our desktop bounds */
+ container_left = container_x / pixels_per_unit;
+ container_top = container_y / pixels_per_unit;
+ container_right = container_left + container_width / pixels_per_unit;
+ container_bottom = container_top + container_height / pixels_per_unit;
+
+ icon_get_bounding_box (icon, &x1, &y1, &x2, &y2,
+ BOUNDS_USAGE_FOR_ENTIRE_ITEM);
+ item_width = x2 - x1;
+ item_height = y2 - y1;
+
+ icon_bounds = nautilus_canvas_item_get_icon_rectangle (icon->item);
+
+ /* determine icon rectangle relative to item rectangle */
+ height_above = icon_bounds.y0 - y1;
+ width_left = icon_bounds.x0 - x1;
+
+ min_x = container_left + DESKTOP_PAD_HORIZONTAL + width_left;
+ max_x = container_right - DESKTOP_PAD_HORIZONTAL - item_width + width_left;
+ x = CLAMP (x, min_x, max_x);
+
+ min_y = container_top + height_above + DESKTOP_PAD_VERTICAL;
+ max_y = container_bottom - DESKTOP_PAD_VERTICAL - item_height + height_above;
+ y = CLAMP (y, min_y, max_y);
+ }
+
+ 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;
+ case NAUTILUS_CANVAS_ZOOM_LEVEL_STANDARD:
+ return STANDARD_ICON_GRID_WIDTH;
+ case NAUTILUS_CANVAS_ZOOM_LEVEL_LARGE:
+ return LARGE_ICON_GRID_WIDTH;
+ case NAUTILUS_CANVAS_ZOOM_LEVEL_LARGER:
+ return LARGER_ICON_GRID_WIDTH;
+ }
+ g_return_val_if_reached (STANDARD_ICON_GRID_WIDTH);
+}
+
+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;
+ case NAUTILUS_CANVAS_ZOOM_LEVEL_STANDARD:
+ return NAUTILUS_CANVAS_ICON_SIZE_STANDARD;
+ case NAUTILUS_CANVAS_ZOOM_LEVEL_LARGE:
+ return NAUTILUS_CANVAS_ICON_SIZE_LARGE;
+ case NAUTILUS_CANVAS_ZOOM_LEVEL_LARGER:
+ return NAUTILUS_CANVAS_ICON_SIZE_LARGER;
+ }
+ g_return_val_if_reached (NAUTILUS_CANVAS_ICON_SIZE_STANDARD);
+}
+
+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)
+ * icon->scale, NAUTILUS_CANVAS_ICON_SIZE_SMALL);
+ }
+}
+
+/* The icon_set_size function is used by the stretching user
+ * interface, which currently stretches in a way that keeps the aspect
+ * ratio. Later we might have a stretching interface that stretches Y
+ * separate from X and we will change this around.
+ */
+static void
+icon_set_size (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon,
+ guint icon_size,
+ gboolean snap,
+ gboolean update_position)
+{
+ guint old_size;
+ double scale;
+
+ icon_get_size (container, icon, &old_size);
+ if (icon_size == old_size) {
+ return;
+ }
+
+ scale = (double) icon_size /
+ nautilus_canvas_container_get_icon_size_for_zoom_level
+ (container->details->zoom_level);
+ nautilus_canvas_container_move_icon (container, icon,
+ icon->x, icon->y,
+ scale, FALSE,
+ snap, update_position);
+}
+
+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
+emit_stretch_started (NautilusCanvasContainer *container, NautilusCanvasIcon *icon)
+{
+ g_signal_emit (container,
+ signals[ICON_STRETCH_STARTED], 0,
+ icon->data);
+}
+
+static void
+emit_stretch_ended (NautilusCanvasContainer *container, NautilusCanvasIcon *icon)
+{
+ g_signal_emit (container,
+ signals[ICON_STRETCH_ENDED], 0,
+ icon->data);
+}
+
+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);
+
+ /* If the icon is deselected, then get rid of the stretch handles.
+ * No harm in doing the same if the item is newly selected.
+ */
+ if (icon == container->details->stretch_icon) {
+ container->details->stretch_icon = NULL;
+ nautilus_canvas_item_set_show_stretch_handles (icon->item, FALSE);
+ /* snap the icon if necessary */
+ if (container->details->keep_aligned) {
+ nautilus_canvas_container_move_icon (container,
+ icon,
+ icon->x, icon->y,
+ icon->scale,
+ FALSE, TRUE, TRUE);
+ }
+
+ emit_stretch_ended (container, icon);
+ }
+
+ /* 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)
+{
+ g_assert (select == FALSE || select == TRUE);
+ g_assert (icon->is_selected == FALSE || icon->is_selected == TRUE);
+
+ 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));
+
+ if (nautilus_canvas_container_is_auto_layout (container)) {
+ /* ensure that we reveal the entire row/column */
+ icon_get_row_and_column_bounds (container, icon, &bounds);
+ } else {
+ item_get_canvas_bounds (EEL_CANVAS_ITEM (icon->item), &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->keyboard_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
+clear_keyboard_focus (NautilusCanvasContainer *container)
+{
+ if (container->details->keyboard_focus != NULL) {
+ eel_canvas_item_set (EEL_CANVAS_ITEM (container->details->keyboard_focus->item),
+ "highlighted_as_keyboard_focus", 0,
+ NULL);
+ }
+
+ container->details->keyboard_focus = NULL;
+}
+
+static void inline
+emit_atk_focus_tracker_notify (NautilusCanvasIcon *icon)
+{
+ AtkObject *atk_object = atk_gobject_accessible_for_object (G_OBJECT (icon->item));
+ atk_focus_tracker_notify (atk_object);
+}
+
+/* Set @icon as the icon currently selected for keyboard operations. */
+static void
+set_keyboard_focus (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon)
+{
+ g_assert (icon != NULL);
+
+ if (icon == container->details->keyboard_focus) {
+ return;
+ }
+
+ clear_keyboard_focus (container);
+
+ container->details->keyboard_focus = icon;
+
+ eel_canvas_item_set (EEL_CANVAS_ITEM (container->details->keyboard_focus->item),
+ "highlighted_as_keyboard_focus", 1,
+ NULL);
+
+ emit_atk_focus_tracker_notify (icon);
+}
+
+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);
+}
+
+/* Don't preserve visible white space the next time the scroll region
+ * is recomputed when the container is not empty. */
+void
+nautilus_canvas_container_reset_scroll_region (NautilusCanvasContainer *container)
+{
+ container->details->reset_scroll_region_trigger = TRUE;
+}
+
+/* Set a new scroll region without eliminating any of the currently-visible area. */
+static void
+canvas_set_scroll_region_include_visible_area (EelCanvas *canvas,
+ double x1, double y1,
+ double x2, double y2)
+{
+ double old_x1, old_y1, old_x2, old_y2;
+ double old_scroll_x, old_scroll_y;
+ double height, width;
+ GtkAllocation allocation;
+
+ eel_canvas_get_scroll_region (canvas, &old_x1, &old_y1, &old_x2, &old_y2);
+ gtk_widget_get_allocation (GTK_WIDGET (canvas), &allocation);
+
+ width = (allocation.width) / canvas->pixels_per_unit;
+ height = (allocation.height) / canvas->pixels_per_unit;
+
+ old_scroll_x = gtk_adjustment_get_value (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (canvas)));
+ old_scroll_y = gtk_adjustment_get_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (canvas)));
+
+ x1 = MIN (x1, old_x1 + old_scroll_x);
+ y1 = MIN (y1, old_y1 + old_scroll_y);
+ x2 = MAX (x2, old_x1 + old_scroll_x + width);
+ y2 = MAX (y2, old_y1 + old_scroll_y + height);
+
+ eel_canvas_set_scroll_region
+ (canvas, x1, y1, x2, y2);
+}
+
+void
+nautilus_canvas_container_update_scroll_region (NautilusCanvasContainer *container)
+{
+ double x1, y1, x2, y2;
+ double pixels_per_unit;
+ GtkAdjustment *hadj, *vadj;
+ float step_increment;
+ gboolean reset_scroll_region;
+ GtkAllocation allocation;
+
+ pixels_per_unit = EEL_CANVAS (container)->pixels_per_unit;
+
+ if (nautilus_canvas_container_get_is_fixed_size (container)) {
+ /* Set the scroll region to the size of the container allocation */
+ gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
+ eel_canvas_set_scroll_region
+ (EEL_CANVAS (container),
+ (double) - container->details->left_margin / pixels_per_unit,
+ (double) - container->details->top_margin / pixels_per_unit,
+ ((double) (allocation.width - 1)
+ - container->details->left_margin
+ - container->details->right_margin)
+ / pixels_per_unit,
+ ((double) (allocation.height - 1)
+ - container->details->top_margin
+ - container->details->bottom_margin)
+ / pixels_per_unit);
+ return;
+ }
+
+ reset_scroll_region = container->details->reset_scroll_region_trigger
+ || nautilus_canvas_container_is_empty (container)
+ || nautilus_canvas_container_is_auto_layout (container);
+
+ /* The trigger is only cleared when container is non-empty, so
+ * callers can reliably reset the scroll region when an item
+ * is added even if extraneous relayouts are called when the
+ * window is still empty.
+ */
+ if (!nautilus_canvas_container_is_empty (container)) {
+ container->details->reset_scroll_region_trigger = FALSE;
+ }
+
+ 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.
+ * For horizontal layouts, we add a bottom border.
+ * Vertical layout is used by the compact view so the end
+ * depends on the RTL setting.
+ */
+ if (nautilus_canvas_container_is_layout_vertical (container)) {
+ if (nautilus_canvas_container_is_layout_rtl (container)) {
+ x1 -= ICON_PAD_LEFT + CONTAINER_PAD_LEFT;
+ } else {
+ x2 += ICON_PAD_RIGHT + CONTAINER_PAD_RIGHT;
+ }
+ } else {
+ 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 */
+ if (nautilus_canvas_container_is_auto_layout (container)) {
+ gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
+ x1 = MIN (x1, 0);
+ x2 = MAX (x2, allocation.width / pixels_per_unit);
+ y1 = 0;
+ } else {
+ /* Otherwise we add the padding that is at the start of the
+ layout */
+ if (nautilus_canvas_container_is_layout_rtl (container)) {
+ x2 += ICON_PAD_RIGHT + CONTAINER_PAD_RIGHT;
+ } else {
+ x1 -= ICON_PAD_LEFT + CONTAINER_PAD_LEFT;
+ }
+ y1 -= ICON_PAD_TOP + CONTAINER_PAD_TOP;
+ }
+
+ x2 -= 1;
+ x2 = MAX(x1, x2);
+
+ y2 -= 1;
+ y2 = MAX(y1, y2);
+
+ if (reset_scroll_region) {
+ eel_canvas_set_scroll_region
+ (EEL_CANVAS (container),
+ x1, y1, x2, y2);
+ } else {
+ canvas_set_scroll_region_include_visible_area
+ (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, 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++);
+ y_offset = position->y_offset;
+
+ icon_set_position
+ (icon,
+ is_rtl ? get_mirror_x_position (container, icon, x + position->x_offset) : x + position->x_offset,
+ y + y_offset);
+ nautilus_canvas_item_set_entire_text (icon->item, whole_text);
+
+ icon->saved_ltr_x = is_rtl ? get_mirror_x_position (container, icon, 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;
+ 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 grid_width;
+ int icon_width, icon_size;
+ int i;
+ GtkAllocation allocation;
+
+ g_assert (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ 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);
+
+ 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);
+
+ 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, TRUE);
+ }
+
+ g_array_free (positions, TRUE);
+}
+
+static void
+snap_position (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon,
+ int *x, int *y)
+{
+ int center_x;
+ int baseline_y;
+ int icon_width;
+ int icon_height;
+ int total_width;
+ int total_height;
+ EelDRect canvas_position;
+ GtkAllocation allocation;
+
+ canvas_position = nautilus_canvas_item_get_icon_rectangle (icon->item);
+ icon_width = canvas_position.x1 - canvas_position.x0;
+ icon_height = canvas_position.y1 - canvas_position.y0;
+
+ gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
+ total_width = CANVAS_WIDTH (container, allocation);
+ total_height = CANVAS_HEIGHT (container, allocation);
+
+ if (nautilus_canvas_container_is_layout_rtl (container))
+ *x = get_mirror_x_position (container, icon, *x);
+
+ if (*x + icon_width / 2 < DESKTOP_PAD_HORIZONTAL + SNAP_SIZE_X) {
+ *x = DESKTOP_PAD_HORIZONTAL + SNAP_SIZE_X - icon_width / 2;
+ }
+
+ if (*x + icon_width / 2 > total_width - (DESKTOP_PAD_HORIZONTAL + SNAP_SIZE_X)) {
+ *x = total_width - (DESKTOP_PAD_HORIZONTAL + SNAP_SIZE_X + (icon_width / 2));
+ }
+
+ if (*y + icon_height < DESKTOP_PAD_VERTICAL + SNAP_SIZE_Y) {
+ *y = DESKTOP_PAD_VERTICAL + SNAP_SIZE_Y - icon_height;
+ }
+
+ if (*y + icon_height > total_height - (DESKTOP_PAD_VERTICAL + SNAP_SIZE_Y)) {
+ *y = total_height - (DESKTOP_PAD_VERTICAL + SNAP_SIZE_Y + (icon_height / 2));
+ }
+
+ center_x = *x + icon_width / 2;
+ *x = SNAP_NEAREST_HORIZONTAL (center_x) - (icon_width / 2);
+ if (nautilus_canvas_container_is_layout_rtl (container)) {
+ *x = get_mirror_x_position (container, icon, *x);
+ }
+
+
+ /* Find the grid position vertically and place on the proper baseline */
+ baseline_y = *y + icon_height;
+ baseline_y = SNAP_NEAREST_VERTICAL (baseline_y);
+ *y = baseline_y - icon_height;
+}
+
+static int
+compare_icons_by_position (gconstpointer a, gconstpointer b)
+{
+ NautilusCanvasIcon *icon_a, *icon_b;
+ int x1, y1, x2, y2;
+ int center_a;
+ int center_b;
+
+ icon_a = (NautilusCanvasIcon *)a;
+ icon_b = (NautilusCanvasIcon *)b;
+
+ icon_get_bounding_box (icon_a, &x1, &y1, &x2, &y2,
+ BOUNDS_USAGE_FOR_DISPLAY);
+ center_a = x1 + (x2 - x1) / 2;
+ icon_get_bounding_box (icon_b, &x1, &y1, &x2, &y2,
+ BOUNDS_USAGE_FOR_DISPLAY);
+ center_b = x1 + (x2 - x1) / 2;
+
+ return center_a == center_b ?
+ icon_a->y - icon_b->y :
+ center_a - center_b;
+}
+
+static PlacementGrid *
+placement_grid_new (NautilusCanvasContainer *container, gboolean tight)
+{
+ PlacementGrid *grid;
+ int width, height;
+ int num_columns;
+ int num_rows;
+ int i;
+ GtkAllocation allocation;
+
+ /* Get container dimensions */
+ gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
+ width = CANVAS_WIDTH(container, allocation);
+ height = CANVAS_HEIGHT(container, allocation);
+
+ num_columns = width / SNAP_SIZE_X;
+ num_rows = height / SNAP_SIZE_Y;
+
+ if (num_columns == 0 || num_rows == 0) {
+ return NULL;
+ }
+
+ grid = g_new0 (PlacementGrid, 1);
+ grid->tight = tight;
+ grid->num_columns = num_columns;
+ grid->num_rows = num_rows;
+
+ grid->grid_memory = g_new0 (int, (num_rows * num_columns));
+ grid->icon_grid = g_new0 (int *, num_columns);
+
+ for (i = 0; i < num_columns; i++) {
+ grid->icon_grid[i] = grid->grid_memory + (i * num_rows);
+ }
+
+ return grid;
+}
+
+static void
+placement_grid_free (PlacementGrid *grid)
+{
+ g_free (grid->icon_grid);
+ g_free (grid->grid_memory);
+ g_free (grid);
+}
+
+static gboolean
+placement_grid_position_is_free (PlacementGrid *grid, EelIRect pos)
+{
+ int x, y;
+
+ g_assert (pos.x0 >= 0 && pos.x0 < grid->num_columns);
+ g_assert (pos.y0 >= 0 && pos.y0 < grid->num_rows);
+ g_assert (pos.x1 >= 0 && pos.x1 < grid->num_columns);
+ g_assert (pos.y1 >= 0 && pos.y1 < grid->num_rows);
+
+ for (x = pos.x0; x <= pos.x1; x++) {
+ for (y = pos.y0; y <= pos.y1; y++) {
+ if (grid->icon_grid[x][y] != 0) {
+ return FALSE;
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+placement_grid_mark (PlacementGrid *grid, EelIRect pos)
+{
+ int x, y;
+
+ g_assert (pos.x0 >= 0 && pos.x0 < grid->num_columns);
+ g_assert (pos.y0 >= 0 && pos.y0 < grid->num_rows);
+ g_assert (pos.x1 >= 0 && pos.x1 < grid->num_columns);
+ g_assert (pos.y1 >= 0 && pos.y1 < grid->num_rows);
+
+ for (x = pos.x0; x <= pos.x1; x++) {
+ for (y = pos.y0; y <= pos.y1; y++) {
+ grid->icon_grid[x][y] = 1;
+ }
+ }
+}
+
+static void
+canvas_position_to_grid_position (PlacementGrid *grid,
+ EelIRect canvas_position,
+ EelIRect *grid_position)
+{
+ /* The first causes minimal moving around during a snap, but
+ * can end up with partially overlapping icons. The second one won't
+ * allow any overlapping, but can cause more movement to happen
+ * during a snap. */
+ if (grid->tight) {
+ grid_position->x0 = ceil ((double)(canvas_position.x0 - DESKTOP_PAD_HORIZONTAL) / SNAP_SIZE_X);
+ grid_position->y0 = ceil ((double)(canvas_position.y0 - DESKTOP_PAD_VERTICAL) / SNAP_SIZE_Y);
+ grid_position->x1 = floor ((double)(canvas_position.x1 - DESKTOP_PAD_HORIZONTAL) / SNAP_SIZE_X);
+ grid_position->y1 = floor ((double)(canvas_position.y1 - DESKTOP_PAD_VERTICAL) / SNAP_SIZE_Y);
+ } else {
+ grid_position->x0 = floor ((double)(canvas_position.x0 - DESKTOP_PAD_HORIZONTAL) / SNAP_SIZE_X);
+ grid_position->y0 = floor ((double)(canvas_position.y0 - DESKTOP_PAD_VERTICAL) / SNAP_SIZE_Y);
+ grid_position->x1 = floor ((double)(canvas_position.x1 - DESKTOP_PAD_HORIZONTAL) / SNAP_SIZE_X);
+ grid_position->y1 = floor ((double)(canvas_position.y1 - DESKTOP_PAD_VERTICAL) / SNAP_SIZE_Y);
+ }
+
+ grid_position->x0 = CLAMP (grid_position->x0, 0, grid->num_columns - 1);
+ grid_position->y0 = CLAMP (grid_position->y0, 0, grid->num_rows - 1);
+ grid_position->x1 = CLAMP (grid_position->x1, grid_position->x0, grid->num_columns - 1);
+ grid_position->y1 = CLAMP (grid_position->y1, grid_position->y0, grid->num_rows - 1);
+}
+
+static void
+placement_grid_mark_icon (PlacementGrid *grid, NautilusCanvasIcon *icon)
+{
+ EelIRect canvas_pos;
+ EelIRect grid_pos;
+
+ icon_get_bounding_box (icon,
+ &canvas_pos.x0, &canvas_pos.y0,
+ &canvas_pos.x1, &canvas_pos.y1,
+ BOUNDS_USAGE_FOR_LAYOUT);
+ canvas_position_to_grid_position (grid,
+ canvas_pos,
+ &grid_pos);
+ placement_grid_mark (grid, grid_pos);
+}
+
+static void
+find_empty_location (NautilusCanvasContainer *container,
+ PlacementGrid *grid,
+ NautilusCanvasIcon *icon,
+ int start_x,
+ int start_y,
+ int *x,
+ int *y)
+{
+ double icon_width, icon_height;
+ int canvas_width;
+ int canvas_height;
+ int height_for_bound_check;
+ EelIRect icon_position;
+ EelDRect pixbuf_rect;
+ gboolean collision;
+ GtkAllocation allocation;
+
+ /* Get container dimensions */
+ gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
+ canvas_width = CANVAS_WIDTH(container, allocation);
+ canvas_height = CANVAS_HEIGHT(container, allocation);
+
+ icon_get_bounding_box (icon,
+ &icon_position.x0, &icon_position.y0,
+ &icon_position.x1, &icon_position.y1,
+ BOUNDS_USAGE_FOR_LAYOUT);
+ icon_width = icon_position.x1 - icon_position.x0;
+ icon_height = icon_position.y1 - icon_position.y0;
+
+ icon_get_bounding_box (icon,
+ NULL, &icon_position.y0,
+ NULL, &icon_position.y1,
+ BOUNDS_USAGE_FOR_ENTIRE_ITEM);
+ height_for_bound_check = icon_position.y1 - icon_position.y0;
+
+ pixbuf_rect = nautilus_canvas_item_get_icon_rectangle (icon->item);
+
+ /* Start the icon on a grid location */
+ snap_position (container, icon, &start_x, &start_y);
+
+ icon_position.x0 = start_x;
+ icon_position.y0 = start_y;
+ icon_position.x1 = icon_position.x0 + icon_width;
+ icon_position.y1 = icon_position.y0 + icon_height;
+
+ do {
+ EelIRect grid_position;
+ gboolean need_new_column;
+
+ collision = FALSE;
+
+ canvas_position_to_grid_position (grid,
+ icon_position,
+ &grid_position);
+
+ need_new_column = icon_position.y0 + height_for_bound_check + DESKTOP_PAD_VERTICAL > canvas_height;
+
+ if (need_new_column ||
+ !placement_grid_position_is_free (grid, grid_position)) {
+ icon_position.y0 += SNAP_SIZE_Y;
+ icon_position.y1 = icon_position.y0 + icon_height;
+
+ if (need_new_column) {
+ /* Move to the next column */
+ icon_position.y0 = DESKTOP_PAD_VERTICAL + SNAP_SIZE_Y - (pixbuf_rect.y1 - pixbuf_rect.y0);
+ while (icon_position.y0 < DESKTOP_PAD_VERTICAL) {
+ icon_position.y0 += SNAP_SIZE_Y;
+ }
+ icon_position.y1 = icon_position.y0 + icon_height;
+
+ icon_position.x0 += SNAP_SIZE_X;
+ icon_position.x1 = icon_position.x0 + icon_width;
+ }
+
+ collision = TRUE;
+ }
+ } while (collision && (icon_position.x1 < canvas_width));
+
+ *x = icon_position.x0;
+ *y = icon_position.y0;
+}
+
+static void
+align_icons (NautilusCanvasContainer *container)
+{
+ GList *unplaced_icons;
+ GList *l;
+ PlacementGrid *grid;
+
+ unplaced_icons = g_list_copy (container->details->icons);
+
+ unplaced_icons = g_list_sort (unplaced_icons,
+ compare_icons_by_position);
+
+ if (nautilus_canvas_container_is_layout_rtl (container)) {
+ unplaced_icons = g_list_reverse (unplaced_icons);
+ }
+
+ grid = placement_grid_new (container, TRUE);
+
+ if (!grid) {
+ return;
+ }
+
+ for (l = unplaced_icons; l != NULL; l = l->next) {
+ NautilusCanvasIcon *icon;
+ int x, y;
+
+ icon = l->data;
+ x = icon->saved_ltr_x;
+ y = icon->y;
+ find_empty_location (container, grid,
+ icon, x, y, &x, &y);
+
+ icon_set_position (icon, x, y);
+ icon->saved_ltr_x = icon->x;
+ placement_grid_mark_icon (grid, icon);
+ }
+
+ g_list_free (unplaced_icons);
+
+ placement_grid_free (grid);
+
+ if (nautilus_canvas_container_is_layout_rtl (container)) {
+ nautilus_canvas_container_set_rtl_positions (container);
+ }
+}
+
+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_vertical_desktop (NautilusCanvasContainer *container, GList *icons)
+{
+ GList *p, *placed_icons, *unplaced_icons;
+ int total, new_length, placed;
+ NautilusCanvasIcon *icon;
+ int height, max_width, column_width, icon_width, icon_height;
+ int x, y, x1, x2, y1, y2;
+ EelDRect icon_rect;
+ GtkAllocation allocation;
+
+ /* Get container dimensions */
+ gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
+ height = CANVAS_HEIGHT(container, allocation);
+
+ /* Determine which icons have and have not been placed */
+ placed_icons = NULL;
+ unplaced_icons = NULL;
+
+ total = g_list_length (container->details->icons);
+ new_length = g_list_length (icons);
+ placed = total - new_length;
+ if (placed > 0) {
+ PlacementGrid *grid;
+ /* Add only placed icons in list */
+ for (p = container->details->icons; p != NULL; p = p->next) {
+ icon = p->data;
+ if (icon_is_positioned (icon)) {
+ icon_set_position(icon, icon->saved_ltr_x, icon->y);
+ placed_icons = g_list_prepend (placed_icons, icon);
+ } else {
+ icon->x = 0;
+ icon->y = 0;
+ unplaced_icons = g_list_prepend (unplaced_icons, icon);
+ }
+ }
+ placed_icons = g_list_reverse (placed_icons);
+ unplaced_icons = g_list_reverse (unplaced_icons);
+
+ grid = placement_grid_new (container, FALSE);
+
+ if (grid) {
+ for (p = placed_icons; p != NULL; p = p->next) {
+ placement_grid_mark_icon
+ (grid, (NautilusCanvasIcon *)p->data);
+ }
+
+ /* Place unplaced icons in the best locations */
+ for (p = unplaced_icons; p != NULL; p = p->next) {
+ icon = p->data;
+
+ icon_rect = nautilus_canvas_item_get_icon_rectangle (icon->item);
+
+ /* Start the icon in the first column */
+ x = DESKTOP_PAD_HORIZONTAL + (SNAP_SIZE_X / 2) - ((icon_rect.x1 - icon_rect.x0) / 2);
+ y = DESKTOP_PAD_VERTICAL + SNAP_SIZE_Y - (icon_rect.y1 - icon_rect.y0);
+
+ find_empty_location (container,
+ grid,
+ icon,
+ x, y,
+ &x, &y);
+
+ icon_set_position (icon, x, y);
+ icon->saved_ltr_x = x;
+ placement_grid_mark_icon (grid, icon);
+ }
+
+ placement_grid_free (grid);
+ }
+
+ g_list_free (placed_icons);
+ g_list_free (unplaced_icons);
+ } else {
+ /* There are no placed icons. Just lay them down using our rules */
+ x = DESKTOP_PAD_HORIZONTAL;
+
+ while (icons != NULL) {
+ int center_x;
+ int baseline;
+ int icon_height_for_bound_check;
+ gboolean should_snap;
+
+ should_snap = container->details->keep_aligned;
+
+ y = DESKTOP_PAD_VERTICAL;
+
+ max_width = 0;
+
+ /* Calculate max width for column */
+ for (p = icons; p != NULL; p = p->next) {
+ icon = p->data;
+
+ icon_get_bounding_box (icon, &x1, &y1, &x2, &y2,
+ BOUNDS_USAGE_FOR_LAYOUT);
+ icon_width = x2 - x1;
+ icon_height = y2 - y1;
+
+ icon_get_bounding_box (icon, NULL, &y1, NULL, &y2,
+ BOUNDS_USAGE_FOR_ENTIRE_ITEM);
+ icon_height_for_bound_check = y2 - y1;
+
+ if (should_snap) {
+ /* Snap the baseline to a grid position */
+ icon_rect = nautilus_canvas_item_get_icon_rectangle (icon->item);
+ baseline = y + (icon_rect.y1 - icon_rect.y0);
+ baseline = SNAP_CEIL_VERTICAL (baseline);
+ y = baseline - (icon_rect.y1 - icon_rect.y0);
+ }
+
+ /* Check and see if we need to move to a new column */
+ if (y != DESKTOP_PAD_VERTICAL && y + icon_height_for_bound_check > height) {
+ break;
+ }
+
+ if (max_width < icon_width) {
+ max_width = icon_width;
+ }
+
+ y += icon_height + DESKTOP_PAD_VERTICAL;
+ }
+
+ y = DESKTOP_PAD_VERTICAL;
+
+ center_x = x + max_width / 2;
+ column_width = max_width;
+ if (should_snap) {
+ /* Find the grid column to center on */
+ center_x = SNAP_CEIL_HORIZONTAL (center_x);
+ column_width = (center_x - x) + (max_width / 2);
+ }
+
+ /* Lay out column */
+ for (p = icons; p != NULL; p = p->next) {
+ icon = p->data;
+ icon_get_bounding_box (icon, &x1, &y1, &x2, &y2,
+ BOUNDS_USAGE_FOR_LAYOUT);
+ icon_height = y2 - y1;
+
+ icon_get_bounding_box (icon, NULL, &y1, NULL, &y2,
+ BOUNDS_USAGE_FOR_ENTIRE_ITEM);
+ icon_height_for_bound_check = y2 - y1;
+
+ icon_rect = nautilus_canvas_item_get_icon_rectangle (icon->item);
+
+ if (should_snap) {
+ baseline = y + (icon_rect.y1 - icon_rect.y0);
+ baseline = SNAP_CEIL_VERTICAL (baseline);
+ y = baseline - (icon_rect.y1 - icon_rect.y0);
+ }
+
+ /* Check and see if we need to move to a new column */
+ if (y != DESKTOP_PAD_VERTICAL && y > height - icon_height_for_bound_check &&
+ /* Make sure we lay out at least one icon per column, to make progress */
+ p != icons) {
+ x += column_width + DESKTOP_PAD_HORIZONTAL;
+ break;
+ }
+
+ icon_set_position (icon,
+ center_x - (icon_rect.x1 - icon_rect.x0) / 2,
+ y);
+
+ icon->saved_ltr_x = icon->x;
+ y += icon_height + DESKTOP_PAD_VERTICAL;
+ }
+ icons = p;
+ }
+ }
+
+ /* These modes are special. We freeze all of our positions
+ * after we do the layout.
+ */
+ /* FIXME bugzilla.gnome.org 42478:
+ * This should not be tied to the direction of layout.
+ * It should be a separate switch.
+ */
+ nautilus_canvas_container_freeze_icon_positions (container);
+}
+
+
+static void
+lay_down_icons (NautilusCanvasContainer *container, GList *icons, double start_y)
+{
+ if (container->details->is_desktop) {
+ lay_down_icons_vertical_desktop (container, icons);
+ } else {
+ 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;
+ }
+
+ /* Don't do any re-laying-out during stretching. Later we
+ * might add smart logic that does this and leaves room for
+ * the stretched icon, but if we do it we want it to be fast
+ * and only re-lay-out when it's really needed.
+ */
+ if (container->details->auto_layout
+ && container->details->drag_state != DRAG_STATE_STRETCH) {
+ 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);
+ redo_layout_internal (container);
+}
+
+static void
+reload_icon_positions (NautilusCanvasContainer *container)
+{
+ GList *p, *no_position_icons;
+ NautilusCanvasIcon *icon;
+ gboolean have_stored_position;
+ NautilusCanvasPosition position;
+ EelDRect bounds;
+ double bottom;
+ EelCanvasItem *item;
+
+ g_assert (!container->details->auto_layout);
+
+ resort (container);
+
+ no_position_icons = NULL;
+
+ /* Place all the icons with positions. */
+ bottom = 0;
+ for (p = container->details->icons; p != NULL; p = p->next) {
+ icon = p->data;
+
+ have_stored_position = FALSE;
+ g_signal_emit (container,
+ signals[GET_STORED_ICON_POSITION], 0,
+ icon->data,
+ &position,
+ &have_stored_position);
+ if (have_stored_position) {
+ icon_set_position (icon, position.x, position.y);
+ item = EEL_CANVAS_ITEM (icon->item);
+ nautilus_canvas_item_get_bounds_for_layout (icon->item,
+ &bounds.x0,
+ &bounds.y0,
+ &bounds.x1,
+ &bounds.y1);
+ eel_canvas_item_i2w (item->parent,
+ &bounds.x0,
+ &bounds.y0);
+ eel_canvas_item_i2w (item->parent,
+ &bounds.x1,
+ &bounds.y1);
+ if (bounds.y1 > bottom) {
+ bottom = bounds.y1;
+ }
+ } else {
+ no_position_icons = g_list_prepend (no_position_icons, icon);
+ }
+ }
+ no_position_icons = g_list_reverse (no_position_icons);
+
+ /* Place all the other icons. */
+ lay_down_icons (container, no_position_icons, bottom + ICON_PAD_BOTTOM);
+ g_list_free (no_position_icons);
+}
+
+/* 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;
+ }
+
+ }
+
+ if (selection_changed && icon2 != NULL) {
+ emit_atk_focus_tracker_notify (icon2);
+ }
+ 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) {
+ emit_atk_focus_tracker_notify (icon_to_select);
+ reveal_icon (container, icon_to_select);
+ }
+ return selection_changed;
+}
+
+static gboolean
+unselect_all (NautilusCanvasContainer *container)
+{
+ return select_one_unselect_others (container, NULL);
+}
+
+void
+nautilus_canvas_container_move_icon (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon,
+ int x, int y,
+ double scale,
+ gboolean raise,
+ gboolean snap,
+ gboolean update_position)
+{
+ NautilusCanvasContainerDetails *details;
+ gboolean emit_signal;
+ NautilusCanvasPosition position;
+
+ details = container->details;
+
+ emit_signal = FALSE;
+
+ if (scale != icon->scale) {
+ icon->scale = scale;
+ nautilus_canvas_container_update_icon (container, icon);
+ if (update_position) {
+ redo_layout (container);
+ emit_signal = TRUE;
+ }
+ }
+
+ if (!details->auto_layout) {
+ if (details->keep_aligned && snap) {
+ snap_position (container, icon, &x, &y);
+ }
+
+ if (x != icon->x || y != icon->y) {
+ icon_set_position (icon, x, y);
+ emit_signal = update_position;
+ }
+
+ icon->saved_ltr_x = nautilus_canvas_container_is_layout_rtl (container) ? get_mirror_x_position (container, icon, icon->x) : icon->x;
+ }
+
+ if (emit_signal) {
+ position.x = icon->saved_ltr_x;
+ position.y = icon->y;
+ position.scale = scale;
+ g_signal_emit (container,
+ signals[ICON_POSITION_CHANGED], 0,
+ icon->data, &position);
+ }
+
+ if (raise) {
+ icon_raise (icon);
+ }
+
+ /* FIXME bugzilla.gnome.org 42474:
+ * Handling of the scroll region is inconsistent here. In
+ * the scale-changing case, redo_layout is called, which updates the
+ * scroll region appropriately. In other cases, it's up to the
+ * caller to make sure the scroll region is updated. This could
+ * lead to hard-to-track-down bugs.
+ */
+}
+
+/* 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;
+ GdkDisplay *display;
+ GdkSeat *seat;
+ 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;
+ }
+
+ display = gtk_widget_get_display (widget);
+ seat = gdk_display_get_default_seat (display);
+
+ gdk_window_get_device_position (gtk_widget_get_window (widget),
+ gdk_seat_get_pointer (seat),
+ &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
+get_rubber_color (NautilusCanvasContainer *container,
+ GdkRGBA *bgcolor,
+ GdkRGBA *bordercolor)
+{
+ Atom real_type;
+ gint result = -1;
+ gint real_format;
+ gulong items_read = 0;
+ gulong items_left = 0;
+ gchar *colors;
+ Atom representative_colors_atom;
+ Display *display;
+
+ if (nautilus_canvas_container_get_is_desktop (container)) {
+ representative_colors_atom = gdk_x11_get_xatom_by_name ("_GNOME_BACKGROUND_REPRESENTATIVE_COLORS");
+ display = gdk_x11_display_get_xdisplay (gdk_display_get_default ());
+
+ gdk_error_trap_push ();
+ result = XGetWindowProperty (display,
+ GDK_ROOT_WINDOW (),
+ representative_colors_atom,
+ 0L,
+ G_MAXLONG,
+ False,
+ XA_STRING,
+ &real_type,
+ &real_format,
+ &items_read,
+ &items_left,
+ (guchar **) &colors);
+ gdk_error_trap_pop_ignored ();
+ }
+
+ if (result == Success && items_read) {
+ /* by treating the result as a nul-terminated string, we
+ * select the first colour in the list.
+ */
+ GdkRGBA read;
+ gdouble shade;
+
+ gdk_rgba_parse (&read, colors);
+ XFree (colors);
+
+ /* Border
+ *
+ * We shade darker colours to be slightly lighter and
+ * lighter ones to be slightly darker.
+ */
+ shade = read.green < 0.5 ? 1.1 : 0.9;
+ bordercolor->red = read.red * shade;
+ bordercolor->green = read.green * shade;
+ bordercolor->blue = read.blue * shade;
+ bordercolor->alpha = 1.0;
+
+ /* Background */
+ *bgcolor = read;
+ bgcolor->alpha = 0.6;
+ } else {
+ /* Fallback to the style context if we can't get the Atom */
+ GtkStyleContext *context;
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (container));
+ gtk_style_context_save (context);
+ gtk_style_context_add_class (context, GTK_STYLE_CLASS_RUBBERBAND);
+
+ gtk_style_context_get_background_color (context, GTK_STATE_FLAG_NORMAL, bgcolor);
+ gtk_style_context_get_border_color (context, GTK_STATE_FLAG_NORMAL, bordercolor);
+
+ gtk_style_context_restore (context);
+ }
+}
+
+static void
+start_rubberbanding (NautilusCanvasContainer *container,
+ GdkEventButton *event)
+{
+ AtkObject *accessible;
+ NautilusCanvasContainerDetails *details;
+ NautilusCanvasRubberbandInfo *band_info;
+ GdkRGBA bg_color, border_color;
+ GList *p;
+ NautilusCanvasIcon *icon;
+
+ details = container->details;
+ band_info = &details->rubberband_info;
+
+ g_signal_emit (container,
+ signals[BAND_SELECT_STARTED], 0);
+
+ 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);
+
+ get_rubber_color (container, &bg_color, &border_color);
+
+ 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,
+ "fill_color_rgba", &bg_color,
+ "outline_color_rgba", &border_color,
+ "width_pixels", 1,
+ 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, event->time);
+}
+
+static void
+stop_rubberbanding (NautilusCanvasContainer *container,
+ guint32 time)
+{
+ NautilusCanvasRubberbandInfo *band_info;
+ GList *icons;
+ gboolean enable_animation;
+
+ band_info = &container->details->rubberband_info;
+
+ g_assert (band_info->timer_id != 0);
+ g_source_remove (band_info->timer_id);
+ band_info->timer_id = 0;
+
+ band_info->active = FALSE;
+
+ 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, time);
+ eel_canvas_item_lower_to_bottom (band_info->selection_rectangle);
+ if (enable_animation) {
+ nautilus_selection_canvas_item_fade_out (NAUTILUS_SELECTION_CANVAS_ITEM (band_info->selection_rectangle), 150);
+ } else {
+ 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
+next_column_bottommost (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *start_icon,
+ NautilusCanvasIcon *best_so_far,
+ NautilusCanvasIcon *candidate,
+ void *data)
+{
+ /* sort out icons that are not on the right of the current column */
+ if (compare_with_start_column (container, candidate) >= 0) {
+ return FALSE;
+ }
+
+ if (best_so_far != NULL) {
+ if (compare_icons_horizontal_first (container,
+ best_so_far,
+ candidate) > 0) {
+ /* candidate is above best choice, but below the current row */
+ return TRUE;
+ }
+
+ if (compare_icons_vertical_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
+previous_column_highest (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *start_icon,
+ NautilusCanvasIcon *best_so_far,
+ NautilusCanvasIcon *candidate,
+ void *data)
+{
+ /* sort out icons that are not before the current column */
+ if (compare_with_start_column (container, candidate) <= 0) {
+ return FALSE;
+ }
+
+ if (best_so_far != NULL) {
+ if (compare_icons_horizontal (container,
+ best_so_far,
+ candidate) < 0) {
+ /* candidate is right of the best choice, but left of the current column */
+ return TRUE;
+ }
+
+ if (compare_icons_vertical (container,
+ best_so_far,
+ candidate) > 0) {
+ return TRUE;
+ }
+ }
+
+ return best_so_far == NULL;
+}
+
+
+static gboolean
+next_column_highest (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *start_icon,
+ NautilusCanvasIcon *best_so_far,
+ NautilusCanvasIcon *candidate,
+ void *data)
+{
+ /* sort out icons that are not after the current column */
+ if (compare_with_start_column (container, candidate) >= 0) {
+ return FALSE;
+ }
+
+ if (best_so_far != NULL) {
+ if (compare_icons_horizontal_first (container,
+ best_so_far,
+ candidate) > 0) {
+ /* candidate is left of the best choice, but right of the current column */
+ return TRUE;
+ }
+
+ if (compare_icons_vertical_first (container,
+ best_so_far,
+ candidate) > 0) {
+ return TRUE;
+ }
+ }
+
+ return best_so_far == NULL;
+}
+
+static gboolean
+previous_column_lowest (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *start_icon,
+ NautilusCanvasIcon *best_so_far,
+ NautilusCanvasIcon *candidate,
+ void *data)
+{
+ /* sort out icons that are not before the current column */
+ if (compare_with_start_column (container, candidate) <= 0) {
+ return FALSE;
+ }
+
+ if (best_so_far != NULL) {
+ if (compare_icons_horizontal_first (container,
+ best_so_far,
+ candidate) < 0) {
+ /* candidate is right of the best choice, but left of the current column */
+ return TRUE;
+ }
+
+ if (compare_icons_vertical_first (container,
+ best_so_far,
+ candidate) < 0) {
+ return TRUE;
+ }
+ }
+
+ return best_so_far == NULL;
+}
+
+static gboolean
+last_column_lowest (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *start_icon,
+ NautilusCanvasIcon *best_so_far,
+ NautilusCanvasIcon *candidate,
+ void *data)
+{
+ if (best_so_far == NULL) {
+ return TRUE;
+ }
+ return compare_icons_horizontal_first (container, best_so_far, candidate) < 0;
+}
+
+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();
+ }
+
+ 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;
+ }
+
+ if (event != NULL &&
+ (event->state & GDK_CONTROL_MASK) != 0 &&
+ (event->state & GDK_SHIFT_MASK) == 0) {
+ /* Move the keyboard focus. Use Control modifier
+ * rather than Alt to avoid Sawfish conflict.
+ */
+ set_keyboard_focus (container, icon);
+ container->details->keyboard_rubberband_start = NULL;
+ } else if (event != NULL &&
+ ((event->state & GDK_CONTROL_MASK) != 0 ||
+ !container->details->auto_layout) &&
+ (event->state & GDK_SHIFT_MASK) != 0) {
+ /* Do rubberband selection */
+ EelDRect rect;
+
+ if (from && !container->details->keyboard_rubberband_start) {
+ set_keyboard_rubberband_start (container, from);
+ }
+
+ set_keyboard_focus (container, icon);
+
+ 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;
+ }
+
+ set_keyboard_focus (container, icon);
+
+ if (select_range (container, start_icon, icon, TRUE)) {
+ g_signal_emit (container,
+ signals[SELECTION_CHANGED], 0);
+ }
+ } else {
+ /* Select icons and get rid of the special keyboard focus. */
+ clear_keyboard_focus (container);
+ 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,
+ nautilus_canvas_container_is_layout_vertical (container) ?
+ last_column_lowest :
+ 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->keyboard_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,
+ container->details->auto_layout ? better_destination : better_destination_manual,
+ &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 &&
+ container->details->auto_layout &&
+ 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->state & GDK_CONTROL_MASK) != 0 &&
+ (event->state & GDK_SHIFT_MASK) != 0;
+}
+
+static void
+keyboard_right (NautilusCanvasContainer *container,
+ GdkEventKey *event)
+{
+ IsBetterCanvasFunction fallback;
+ IsBetterCanvasFunction next_column_fallback;
+
+ fallback = NULL;
+ if (container->details->auto_layout &&
+ !nautilus_canvas_container_is_layout_vertical (container) &&
+ !is_rectangle_selection_event (event)) {
+ fallback = next_row_leftmost;
+ }
+
+ next_column_fallback = NULL;
+ if (nautilus_canvas_container_is_layout_vertical (container) &&
+ gtk_widget_get_direction (GTK_WIDGET (container)) != GTK_TEXT_DIR_RTL) {
+ next_column_fallback = next_column_bottommost;
+ }
+
+ /* 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,
+ next_column_fallback,
+ closest_in_90_degrees);
+}
+
+static void
+keyboard_left (NautilusCanvasContainer *container,
+ GdkEventKey *event)
+{
+ IsBetterCanvasFunction fallback;
+ IsBetterCanvasFunction previous_column_fallback;
+
+ fallback = NULL;
+ if (container->details->auto_layout &&
+ !nautilus_canvas_container_is_layout_vertical (container) &&
+ !is_rectangle_selection_event (event)) {
+ fallback = previous_row_rightmost;
+ }
+
+ previous_column_fallback = NULL;
+ if (nautilus_canvas_container_is_layout_vertical (container) &&
+ gtk_widget_get_direction (GTK_WIDGET (container)) == GTK_TEXT_DIR_RTL) {
+ previous_column_fallback = previous_column_lowest;
+ }
+
+ /* 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,
+ previous_column_fallback,
+ closest_in_90_degrees);
+}
+
+static void
+keyboard_down (NautilusCanvasContainer *container,
+ GdkEventKey *event)
+{
+ IsBetterCanvasFunction fallback;
+ IsBetterCanvasFunction next_row_fallback;
+
+ fallback = NULL;
+ if (container->details->auto_layout &&
+ nautilus_canvas_container_is_layout_vertical (container) &&
+ !is_rectangle_selection_event (event)) {
+ if (gtk_widget_get_direction (GTK_WIDGET (container)) == GTK_TEXT_DIR_RTL) {
+ fallback = previous_column_highest;
+ } else {
+ fallback = next_column_highest;
+ }
+ }
+
+ next_row_fallback = NULL;
+ if (!nautilus_canvas_container_is_layout_vertical (container)) {
+ 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,
+ fallback,
+ next_row_fallback,
+ closest_in_90_degrees);
+}
+
+static void
+keyboard_up (NautilusCanvasContainer *container,
+ GdkEventKey *event)
+{
+ IsBetterCanvasFunction fallback;
+
+ fallback = NULL;
+ if (container->details->auto_layout &&
+ nautilus_canvas_container_is_layout_vertical (container) &&
+ !is_rectangle_selection_event (event)) {
+ if (gtk_widget_get_direction (GTK_WIDGET (container)) == GTK_TEXT_DIR_RTL) {
+ fallback = next_column_bottommost;
+ } else {
+ fallback = previous_column_lowest;
+ }
+ }
+
+ /* 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,
+ fallback,
+ NULL,
+ closest_in_90_degrees);
+}
+
+static void
+keyboard_space (NautilusCanvasContainer *container,
+ GdkEventKey *event)
+{
+ NautilusCanvasIcon *icon;
+
+ if (!has_selection (container) &&
+ container->details->keyboard_focus != NULL) {
+ keyboard_move_to (container,
+ container->details->keyboard_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->keyboard_focus != NULL) {
+ icon_toggle_selected (container, container->details->keyboard_focus);
+ g_signal_emit (container, signals[SELECTION_CHANGED], 0);
+ if (container->details->keyboard_focus->is_selected) {
+ container->details->range_selection_base_icon = container->details->keyboard_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_keyboard_focus (container, icon);
+ }
+ }
+ } 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->stretch_idle_id != 0) {
+ g_source_remove (container->details->stretch_idle_id);
+ container->details->stretch_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_signal_handlers_disconnect_by_func (nautilus_desktop_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);
+
+ /* Don't chain up to parent, if this is a desktop container,
+ * because that resets the background of the window.
+ */
+ if (!nautilus_canvas_container_get_is_desktop (container)) {
+ 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_focus (container);
+ 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;
+ }
+
+ 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
+start_stretching (NautilusCanvasContainer *container)
+{
+ NautilusCanvasContainerDetails *details;
+ NautilusCanvasIcon *icon;
+ GtkWidget *toplevel;
+ GdkDisplay *display;
+ GtkCornerType corner;
+ GdkCursor *cursor;
+
+ details = container->details;
+ icon = details->stretch_icon;
+ display = gtk_widget_get_display (GTK_WIDGET (container));
+
+ /* Check if we hit the stretch handles. */
+ if (!nautilus_canvas_item_hit_test_stretch_handles (icon->item,
+ details->drag_x, details->drag_y,
+ &corner)) {
+ return FALSE;
+ }
+
+ switch (corner) {
+ case GTK_CORNER_TOP_LEFT:
+ cursor = gdk_cursor_new_for_display (display, GDK_TOP_LEFT_CORNER);
+ break;
+ case GTK_CORNER_BOTTOM_LEFT:
+ cursor = gdk_cursor_new_for_display (display,GDK_BOTTOM_LEFT_CORNER);
+ break;
+ case GTK_CORNER_TOP_RIGHT:
+ cursor = gdk_cursor_new_for_display (display,GDK_TOP_RIGHT_CORNER);
+ break;
+ case GTK_CORNER_BOTTOM_RIGHT:
+ cursor = gdk_cursor_new_for_display (display,GDK_BOTTOM_RIGHT_CORNER);
+ break;
+ default:
+ cursor = NULL;
+ break;
+ }
+ /* Set up the dragging. */
+ details->drag_state = DRAG_STATE_STRETCH;
+ eel_canvas_w2c (EEL_CANVAS (container),
+ details->drag_x,
+ details->drag_y,
+ &details->stretch_start.pointer_x,
+ &details->stretch_start.pointer_y);
+ eel_canvas_w2c (EEL_CANVAS (container),
+ icon->x, icon->y,
+ &details->stretch_start.icon_x,
+ &details->stretch_start.icon_y);
+ icon_get_size (container, icon,
+ &details->stretch_start.icon_size);
+
+ eel_canvas_item_grab (EEL_CANVAS_ITEM (icon->item),
+ (GDK_POINTER_MOTION_MASK
+ | GDK_BUTTON_RELEASE_MASK),
+ cursor,
+ GDK_CURRENT_TIME);
+ if (cursor)
+ g_object_unref (cursor);
+
+ /* Ensure the window itself is focused.. */
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (container));
+ if (toplevel != NULL && gtk_widget_get_realized (toplevel)) {
+ gdk_window_focus (gtk_widget_get_window (toplevel), GDK_CURRENT_TIME);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+update_stretch_at_idle (NautilusCanvasContainer *container)
+{
+ NautilusCanvasContainerDetails *details;
+ NautilusCanvasIcon *icon;
+ double world_x, world_y;
+ StretchState stretch_state;
+
+ details = container->details;
+ icon = details->stretch_icon;
+
+ if (icon == NULL) {
+ container->details->stretch_idle_id = 0;
+ return FALSE;
+ }
+
+ eel_canvas_w2c (EEL_CANVAS (container),
+ details->world_x, details->world_y,
+ &stretch_state.pointer_x, &stretch_state.pointer_y);
+
+ compute_stretch (&details->stretch_start,
+ &stretch_state);
+
+ eel_canvas_c2w (EEL_CANVAS (container),
+ stretch_state.icon_x, stretch_state.icon_y,
+ &world_x, &world_y);
+
+ icon_set_position (icon, world_x, world_y);
+ icon_set_size (container, icon, stretch_state.icon_size, FALSE, FALSE);
+
+ container->details->stretch_idle_id = 0;
+
+ return FALSE;
+}
+
+static void
+continue_stretching (NautilusCanvasContainer *container,
+ double world_x, double world_y)
+{
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ container->details->world_x = world_x;
+ container->details->world_y = world_y;
+
+ if (container->details->stretch_idle_id == 0) {
+ container->details->stretch_idle_id = g_idle_add ((GSourceFunc) update_stretch_at_idle, container);
+ }
+}
+
+static gboolean
+keyboard_stretching (NautilusCanvasContainer *container,
+ GdkEventKey *event)
+{
+ NautilusCanvasIcon *icon;
+ guint size;
+
+ icon = container->details->stretch_icon;
+
+ if (icon == NULL || !icon->is_selected) {
+ return FALSE;
+ }
+
+ icon_get_size (container, icon, &size);
+
+ switch (event->keyval) {
+ case GDK_KEY_equal:
+ case GDK_KEY_plus:
+ case GDK_KEY_KP_Add:
+ icon_set_size (container, icon, size + 5, FALSE, FALSE);
+ break;
+ case GDK_KEY_minus:
+ case GDK_KEY_KP_Subtract:
+ icon_set_size (container, icon, size - 5, FALSE, FALSE);
+ break;
+ case GDK_KEY_0:
+ case GDK_KEY_KP_0:
+ nautilus_canvas_container_move_icon (container, icon,
+ icon->x, icon->y,
+ 1.0,
+ FALSE, TRUE, TRUE);
+ break;
+ }
+
+ return TRUE;
+}
+
+static void
+ungrab_stretch_icon (NautilusCanvasContainer *container)
+{
+ eel_canvas_item_ungrab (EEL_CANVAS_ITEM (container->details->stretch_icon->item),
+ GDK_CURRENT_TIME);
+}
+
+static void
+end_stretching (NautilusCanvasContainer *container,
+ double world_x, double world_y)
+{
+ NautilusCanvasPosition position;
+ NautilusCanvasIcon *icon;
+
+ continue_stretching (container, world_x, world_y);
+ ungrab_stretch_icon (container);
+
+ /* now that we're done stretching, update the icon's position */
+
+ icon = container->details->drag_icon;
+ if (nautilus_canvas_container_is_layout_rtl (container)) {
+ position.x = icon->saved_ltr_x = get_mirror_x_position (container, icon, icon->x);
+ } else {
+ position.x = icon->x;
+ }
+ position.y = icon->y;
+ position.scale = icon->scale;
+ g_signal_emit (container,
+ signals[ICON_POSITION_CHANGED], 0,
+ icon->data, &position);
+
+ clear_drag_state (container);
+ redo_layout (container);
+}
+
+static gboolean
+undo_stretching (NautilusCanvasContainer *container)
+{
+ NautilusCanvasIcon *stretched_icon;
+
+ stretched_icon = container->details->stretch_icon;
+
+ if (stretched_icon == NULL) {
+ return FALSE;
+ }
+
+ if (container->details->drag_state == DRAG_STATE_STRETCH) {
+ ungrab_stretch_icon (container);
+ clear_drag_state (container);
+ }
+ nautilus_canvas_item_set_show_stretch_handles
+ (stretched_icon->item, FALSE);
+
+ icon_set_position (stretched_icon,
+ container->details->stretch_initial_x,
+ container->details->stretch_initial_y);
+ icon_set_size (container,
+ stretched_icon,
+ container->details->stretch_initial_size,
+ TRUE,
+ TRUE);
+
+ container->details->stretch_icon = NULL;
+ emit_stretch_ended (container, stretched_icon);
+ redo_layout (container);
+
+ return TRUE;
+}
+
+static gboolean
+button_release_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ NautilusCanvasContainer *container;
+ NautilusCanvasContainerDetails *details;
+ double world_x, world_y;
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+ details = container->details;
+
+ if (event->button == RUBBERBAND_BUTTON && details->rubberband_info.active) {
+ stop_rubberbanding (container, event->time);
+ 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;
+ case DRAG_STATE_STRETCH:
+ eel_canvas_window_to_world
+ (EEL_CANVAS (container), event->x, event->y, &world_x, &world_y);
+ end_stretching (container, world_x, world_y);
+ 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;
+ case DRAG_STATE_STRETCH:
+ eel_canvas_window_to_world
+ (EEL_CANVAS (container), event->x, event->y, &world_x, &world_y);
+ continue_stretching (container, world_x, world_y);
+ 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,
+ GdkEventKey *event,
+ const char *signal)
+{
+ GdkEventButton button_event = { 0 };
+
+ /* ensure we clear the drag state before showing the menu */
+ clear_drag_state (container);
+
+ g_signal_emit_by_name (container, signal, &button_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_Return:
+ case GDK_KEY_KP_Enter:
+ if ((event->state & GDK_SHIFT_MASK) != 0) {
+ activate_selected_items_alternate (container, NULL);
+ } else {
+ activate_selected_items (container);
+ }
+
+ handled = TRUE;
+ break;
+ case GDK_KEY_Escape:
+ handled = undo_stretching (container);
+ break;
+ case GDK_KEY_plus:
+ case GDK_KEY_minus:
+ case GDK_KEY_equal:
+ case GDK_KEY_KP_Add:
+ case GDK_KEY_KP_Subtract:
+ case GDK_KEY_0:
+ case GDK_KEY_KP_0:
+ if (event->state & GDK_CONTROL_MASK) {
+ handled = keyboard_stretching (container, event);
+ }
+ 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 popup_menu().
+ */
+ if (event->state & GDK_CONTROL_MASK) {
+ handled = handle_popups (container, 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 gboolean
+popup_menu (GtkWidget *widget)
+{
+ NautilusCanvasContainer *container;
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+
+ if (has_selection (container)) {
+ handle_popups (container, NULL,
+ "context_click_selection");
+ } else {
+ handle_popups (container, NULL,
+ "context_click_background");
+ }
+
+ return TRUE;
+}
+
+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,
+ GDK_CURRENT_TIME);
+ }
+}
+
+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);
+ if (nautilus_canvas_container_get_is_desktop (container)) {
+ g_signal_connect_swapped (nautilus_desktop_preferences,
+ "changed::" NAUTILUS_PREFERENCES_DESKTOP_TEXT_ELLIPSIS_LIMIT,
+ G_CALLBACK (text_ellipsis_limit_changed_container_callback),
+ container);
+ } else {
+ 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[ICON_POSITION_CHANGED]
+ = g_signal_new ("icon-position-changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ icon_position_changed),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 2,
+ G_TYPE_POINTER,
+ G_TYPE_POINTER);
+ signals[ICON_STRETCH_STARTED]
+ = g_signal_new ("icon-stretch-started",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ icon_stretch_started),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
+ signals[ICON_STRETCH_ENDED]
+ = g_signal_new ("icon-stretch-ended",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ icon_stretch_ended),
+ 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, 6,
+ G_TYPE_POINTER,
+ G_TYPE_POINTER,
+ G_TYPE_POINTER,
+ GDK_TYPE_DRAG_ACTION,
+ G_TYPE_INT,
+ G_TYPE_INT);
+ 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, 5,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ GDK_TYPE_DRAG_ACTION,
+ G_TYPE_INT,
+ G_TYPE_INT);
+ 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, 5,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ GDK_TYPE_DRAG_ACTION,
+ G_TYPE_INT,
+ G_TYPE_INT);
+ 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, 5,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ GDK_TYPE_DRAG_ACTION,
+ G_TYPE_INT,
+ G_TYPE_INT);
+ 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, 7,
+ G_TYPE_POINTER,
+ G_TYPE_INT,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ GDK_TYPE_DRAG_ACTION,
+ G_TYPE_INT,
+ G_TYPE_INT);
+ 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[CAN_ACCEPT_ITEM]
+ = g_signal_new ("can-accept-item",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ can_accept_item),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_INT, 2,
+ G_TYPE_POINTER,
+ G_TYPE_STRING);
+ signals[GET_STORED_ICON_POSITION]
+ = g_signal_new ("get-stored-icon-position",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ get_stored_icon_position),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_BOOLEAN, 2,
+ G_TYPE_POINTER,
+ G_TYPE_POINTER);
+ signals[GET_STORED_LAYOUT_TIMESTAMP]
+ = g_signal_new ("get-stored-layout-timestamp",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ get_stored_layout_timestamp),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_BOOLEAN, 2,
+ G_TYPE_POINTER,
+ G_TYPE_POINTER);
+ signals[STORE_LAYOUT_TIMESTAMP]
+ = g_signal_new ("store-layout-timestamp",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ store_layout_timestamp),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_BOOLEAN, 2,
+ G_TYPE_POINTER,
+ G_TYPE_POINTER);
+ signals[LAYOUT_CHANGED]
+ = g_signal_new ("layout-changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ layout_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 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->popup_menu = popup_menu;
+ 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 ());
+
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_boolean ("activate_prelight_icon_label",
+ "Activate Prelight Icon Label",
+ "Whether icon labels should make use of its prelight color in prelight state",
+ FALSE,
+ G_PARAM_READABLE));
+}
+
+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 gboolean
+handle_focus_in_event (GtkWidget *widget, GdkEventFocus *event, gpointer user_data)
+{
+ update_selected (NAUTILUS_CANVAS_CONTAINER (widget));
+
+ return FALSE;
+}
+
+static gboolean
+handle_focus_out_event (GtkWidget *widget, GdkEventFocus *event, gpointer user_data)
+{
+ update_selected (NAUTILUS_CANVAS_CONTAINER (widget));
+
+ return FALSE;
+}
+
+
+static int text_ellipsis_limits[NAUTILUS_CANVAS_ZOOM_LEVEL_N_ENTRIES];
+static int desktop_text_ellipsis_limit;
+
+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) {
+ for (p = strs; *p != NULL; p++) {
+ if (sscanf (*p, str, limit)) {
+ success = TRUE;
+ }
+ }
+ }
+
+ 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
+desktop_text_ellipsis_limit_changed_callback (gpointer callback_data)
+{
+ int pref;
+
+ pref = g_settings_get_int (nautilus_desktop_preferences, NAUTILUS_PREFERENCES_DESKTOP_TEXT_ELLIPSIS_LIMIT);
+ desktop_text_ellipsis_limit = 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->layout_timestamp = UNDEFINED_TIME;
+ details->zoom_level = NAUTILUS_CANVAS_ZOOM_LEVEL_STANDARD;
+
+ container->details = details;
+
+ g_signal_connect (container, "focus-in-event",
+ G_CALLBACK (handle_focus_in_event), NULL);
+ g_signal_connect (container, "focus-out-event",
+ G_CALLBACK (handle_focus_out_event), 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);
+
+ g_signal_connect_swapped (nautilus_icon_view_preferences,
+ "changed::" NAUTILUS_PREFERENCES_DESKTOP_TEXT_ELLIPSIS_LIMIT,
+ G_CALLBACK (desktop_text_ellipsis_limit_changed_callback),
+ NULL);
+ desktop_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;
+
+ /* Check to see if this is a click on the stretch handles.
+ * If so, it won't modify the selection.
+ */
+ if (icon == container->details->stretch_icon) {
+ if (start_stretching (container)) {
+ return TRUE;
+ }
+ }
+ }
+
+ /* 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;
+
+ 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) {
+ 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_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;
+ }
+}
+
+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;
+ details->layout_timestamp = UNDEFINED_TIME;
+ details->store_layout_timestamps_when_finishing_new_icons = FALSE;
+
+ if (details->icons == NULL) {
+ return;
+ }
+
+ clear_keyboard_focus (container);
+ clear_keyboard_rubberband_start (container);
+ unschedule_keyboard_icon_reveal (container);
+ set_pending_icon_to_reveal (container, NULL);
+ details->stretch_icon = 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;
+ if (nautilus_canvas_container_is_layout_vertical (container)) {
+ pos = &x1;
+ if (nautilus_canvas_container_is_layout_rtl (container)) {
+ compare_lt = TRUE;
+ better_icon = x1 < x + ICON_PAD_LEFT;
+ } else {
+ better_icon = x2 > x + ICON_PAD_LEFT;
+ }
+ } else {
+ 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;
+}
+
+/* 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 *hadj, *vadj;
+ EelIRect bounds;
+ 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);
+
+ /* 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)) {
+
+ if (nautilus_canvas_container_is_auto_layout (container)) {
+ /* ensure that we reveal the entire row/column */
+ icon_get_row_and_column_bounds (container, icon, &bounds);
+ } else {
+ item_get_canvas_bounds (EEL_CANVAS_ITEM (icon->item), &bounds);
+ }
+
+ if (nautilus_canvas_container_is_layout_vertical (container)) {
+ if (nautilus_canvas_container_is_layout_rtl (container)) {
+ gtk_adjustment_set_value (hadj, bounds.x1 - allocation.width);
+ } else {
+ gtk_adjustment_set_value (hadj, bounds.x0);
+ }
+ } else {
+ 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->keyboard_focus == icon ||
+ details->keyboard_focus == NULL) {
+ if (icon_to_focus != NULL) {
+ set_keyboard_focus (container, icon_to_focus);
+ } else {
+ clear_keyboard_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);
+ }
+ if (details->stretch_icon == icon) {
+ details->stretch_icon = 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);
+
+ if (nautilus_canvas_container_is_layout_vertical (container)) {
+ visible = x1 >= min_x && x0 <= max_x;
+ } else {
+ 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)
+{
+ if (!nautilus_canvas_container_is_layout_vertical (container)) {
+ nautilus_canvas_container_update_visible_icons (container);
+ }
+}
+
+static void
+handle_hadjustment_changed (GtkAdjustment *adjustment,
+ NautilusCanvasContainer *container)
+{
+ if (nautilus_canvas_container_is_layout_vertical (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 gboolean
+assign_icon_position (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon)
+{
+ gboolean have_stored_position;
+ NautilusCanvasPosition position;
+
+ /* Get the stored position. */
+ have_stored_position = FALSE;
+ position.scale = 1.0;
+ g_signal_emit (container,
+ signals[GET_STORED_ICON_POSITION], 0,
+ icon->data,
+ &position,
+ &have_stored_position);
+ icon->scale = position.scale;
+ if (!container->details->auto_layout) {
+ if (have_stored_position) {
+ icon_set_position (icon, position.x, position.y);
+ icon->saved_ltr_x = icon->x;
+ } else {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+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, *no_position_icons, *semi_position_icons;
+ NautilusCanvasIcon *icon;
+ double bottom;
+
+ new_icons = container->details->new_icons;
+ container->details->new_icons = NULL;
+
+ /* Position most icons (not unpositioned manual-layout icons). */
+ new_icons = g_list_reverse (new_icons);
+ no_position_icons = semi_position_icons = NULL;
+ for (p = new_icons; p != NULL; p = p->next) {
+ icon = p->data;
+ if (icon->has_lazy_position) {
+ assign_icon_position (container, icon);
+ semi_position_icons = g_list_prepend (semi_position_icons, icon);
+ } else if (!assign_icon_position (container, icon)) {
+ no_position_icons = g_list_prepend (no_position_icons, icon);
+ }
+
+ finish_adding_icon (container, icon);
+ }
+ g_list_free (new_icons);
+
+ if (semi_position_icons != NULL) {
+ PlacementGrid *grid;
+ time_t now;
+ gboolean dummy;
+
+ g_assert (!container->details->auto_layout);
+
+ semi_position_icons = g_list_reverse (semi_position_icons);
+
+ /* This is currently only used on the desktop.
+ * Thus, we pass FALSE for tight, like lay_down_icons_tblr */
+ grid = placement_grid_new (container, FALSE);
+
+ /* we can do nothing, just return */
+ if (grid == NULL)
+ return FALSE;
+
+ for (p = container->details->icons; p != NULL; p = p->next) {
+ icon = p->data;
+
+ if (icon_is_positioned (icon) && !icon->has_lazy_position) {
+ placement_grid_mark_icon (grid, icon);
+ }
+ }
+
+ now = time (NULL);
+
+ for (p = semi_position_icons; p != NULL; p = p->next) {
+ NautilusCanvasIcon *icon;
+ NautilusCanvasPosition position;
+ int x, y;
+
+ icon = p->data;
+ x = icon->x;
+ y = icon->y;
+
+ find_empty_location (container, grid,
+ icon, x, y, &x, &y);
+
+ icon_set_position (icon, x, y);
+
+ position.x = icon->x;
+ position.y = icon->y;
+ position.scale = icon->scale;
+ placement_grid_mark_icon (grid, icon);
+ g_signal_emit (container, signals[ICON_POSITION_CHANGED], 0,
+ icon->data, &position);
+ g_signal_emit (container, signals[STORE_LAYOUT_TIMESTAMP], 0,
+ icon->data, &now, &dummy);
+
+ /* ensure that next time we run this code, the formerly semi-positioned
+ * icons are treated as being positioned. */
+ icon->has_lazy_position = FALSE;
+ }
+
+ placement_grid_free (grid);
+
+ g_list_free (semi_position_icons);
+ }
+
+ /* Position the unpositioned manual layout icons. */
+ if (no_position_icons != NULL) {
+ g_assert (!container->details->auto_layout);
+
+ sort_icons (container, &no_position_icons);
+ if (nautilus_canvas_container_get_is_desktop (container)) {
+ lay_down_icons (container, no_position_icons, CONTAINER_PAD_TOP);
+ } else {
+ get_all_icon_bounds (container, NULL, NULL, NULL, &bottom, BOUNDS_USAGE_FOR_LAYOUT);
+ lay_down_icons (container, no_position_icons, bottom + ICON_PAD_BOTTOM);
+ }
+ g_list_free (no_position_icons);
+ }
+
+ if (container->details->store_layout_timestamps_when_finishing_new_icons) {
+ store_layout_timestamps_now (container);
+ container->details->store_layout_timestamps_when_finishing_new_icons = FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+is_old_or_unknown_icon_data (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data)
+{
+ time_t timestamp;
+ gboolean success;
+
+ if (container->details->layout_timestamp == UNDEFINED_TIME) {
+ /* don't know */
+ return FALSE;
+ }
+
+ g_signal_emit (container,
+ signals[GET_STORED_LAYOUT_TIMESTAMP], 0,
+ data, &timestamp, &success);
+ return (!success || timestamp < container->details->layout_timestamp);
+}
+
+/**
+ * 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->has_lazy_position = is_old_or_unknown_icon_data (container, data);
+ icon->scale = 1.0;
+ 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)
+{
+ 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))));
+}
+
+/**
+ * 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 an array of GdkRectangles of the icons. The bounding box is adjusted
+ * with the pixels_per_unit already, so they are the final positions on the canvas */
+static GArray *
+nautilus_canvas_container_get_icons_bounding_box (NautilusCanvasContainer *container,
+ GList *icons)
+{
+ GArray *result;
+ GList *node;
+ int index;
+ int x1, x2, y1, y2;
+
+ result = g_array_new (FALSE, TRUE, sizeof (GdkRectangle));
+ result = g_array_set_size (result, g_list_length (icons));
+
+ for (index = 0, node = icons; node != NULL; index++, node = node->next) {
+ icon_get_bounding_box ((NautilusCanvasIcon *)node->data,
+ &x1, &y1, &x2, &y2,
+ BOUNDS_USAGE_FOR_DISPLAY);
+ g_array_index (result, GdkRectangle, index).x = x1 * EEL_CANVAS (container)->pixels_per_unit +
+ container->details->left_margin;
+ g_array_index (result, GdkRectangle, index).width = (x2 - x1) * EEL_CANVAS (container)->pixels_per_unit;
+ g_array_index (result, GdkRectangle, index).y = y1 * EEL_CANVAS (container)->pixels_per_unit +
+ container->details->top_margin;
+ g_array_index (result, GdkRectangle, index).height = (y2 - y1) * EEL_CANVAS (container)->pixels_per_unit;
+ }
+
+ return result;
+}
+
+GArray *
+nautilus_canvas_container_get_selected_icons_bounding_box (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_icons_bounding_box (container, icons);
+ g_list_free (icons);
+
+ return result;
+}
+
+/**
+ * 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;
+}
+
+/**
+ * nautilus_canvas_container_show_stretch_handles:
+ * @container: An canvas container widget.
+ *
+ * Makes stretch handles visible on the first selected icon.
+ **/
+void
+nautilus_canvas_container_show_stretch_handles (NautilusCanvasContainer *container)
+{
+ NautilusCanvasContainerDetails *details;
+ NautilusCanvasIcon *icon;
+ guint initial_size;
+
+ icon = get_first_selected_icon (container);
+ if (icon == NULL) {
+ return;
+ }
+
+ /* Check if it already has stretch handles. */
+ details = container->details;
+ if (details->stretch_icon == icon) {
+ return;
+ }
+
+ /* Get rid of the existing stretch handles and put them on the new canvas. */
+ if (details->stretch_icon != NULL) {
+ nautilus_canvas_item_set_show_stretch_handles
+ (details->stretch_icon->item, FALSE);
+ ungrab_stretch_icon (container);
+ emit_stretch_ended (container, details->stretch_icon);
+ }
+ nautilus_canvas_item_set_show_stretch_handles (icon->item, TRUE);
+ details->stretch_icon = icon;
+
+ icon_get_size (container, icon, &initial_size);
+
+ /* only need to keep size in one dimension, since they are constrained to be the same */
+ container->details->stretch_initial_x = icon->x;
+ container->details->stretch_initial_y = icon->y;
+ container->details->stretch_initial_size = initial_size;
+
+ emit_stretch_started (container, icon);
+}
+
+/**
+ * nautilus_canvas_container_has_stretch_handles
+ * @container: An canvas container widget.
+ *
+ * Returns true if the first selected item has stretch handles.
+ **/
+gboolean
+nautilus_canvas_container_has_stretch_handles (NautilusCanvasContainer *container)
+{
+ NautilusCanvasIcon *icon;
+
+ icon = get_first_selected_icon (container);
+ if (icon == NULL) {
+ return FALSE;
+ }
+
+ return icon == container->details->stretch_icon;
+}
+
+/**
+ * nautilus_canvas_container_is_stretched
+ * @container: An canvas container widget.
+ *
+ * Returns true if the any selected item is stretched to a size other than 1.0.
+ **/
+gboolean
+nautilus_canvas_container_is_stretched (NautilusCanvasContainer *container)
+{
+ GList *p;
+ NautilusCanvasIcon *icon;
+
+ for (p = container->details->icons; p != NULL; p = p->next) {
+ icon = p->data;
+ if (icon->is_selected && icon->scale != 1.0) {
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+/**
+ * nautilus_canvas_container_unstretch
+ * @container: An canvas container widget.
+ *
+ * Gets rid of any canvas stretching.
+ **/
+void
+nautilus_canvas_container_unstretch (NautilusCanvasContainer *container)
+{
+ GList *p;
+ NautilusCanvasIcon *icon;
+
+ for (p = container->details->icons; p != NULL; p = p->next) {
+ icon = p->data;
+ if (icon->is_selected) {
+ nautilus_canvas_container_move_icon (container, icon,
+ icon->x, icon->y,
+ 1.0,
+ FALSE, TRUE, TRUE);
+ }
+ }
+}
+
+static void
+compute_stretch (StretchState *start,
+ StretchState *current)
+{
+ gboolean right, bottom;
+ int x_stretch, y_stretch;
+
+ /* FIXME bugzilla.gnome.org 45390: This doesn't correspond to
+ * the way the handles are drawn.
+ */
+ /* Figure out which handle we are dragging. */
+ right = start->pointer_x > start->icon_x + (int) start->icon_size / 2;
+ bottom = start->pointer_y > start->icon_y + (int) start->icon_size / 2;
+
+ /* Figure out how big we should stretch. */
+ x_stretch = start->pointer_x - current->pointer_x;
+ y_stretch = start->pointer_y - current->pointer_y;
+ if (right) {
+ x_stretch = - x_stretch;
+ }
+ if (bottom) {
+ y_stretch = - y_stretch;
+ }
+ current->icon_size = MAX ((int) start->icon_size + MIN (x_stretch, y_stretch),
+ (int) NAUTILUS_CANVAS_ICON_SIZE_SMALL);
+
+ /* Figure out where the corner of the icon should be. */
+ current->icon_x = start->icon_x;
+ if (!right) {
+ current->icon_x += start->icon_size - current->icon_size;
+ }
+ current->icon_y = start->icon_y;
+ if (!bottom) {
+ current->icon_y += start->icon_size - current->icon_size;
+ }
+}
+
+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;
+}
+
+/* Call to reset the scroll region only if the container is not empty,
+ * to avoid having the flag linger until the next file is added.
+ */
+static void
+reset_scroll_region_if_not_empty (NautilusCanvasContainer *container)
+{
+ if (!nautilus_canvas_container_is_empty (container)) {
+ nautilus_canvas_container_reset_scroll_region (container);
+ }
+}
+
+/* Switch from automatic layout to manual or vice versa.
+ * If we switch to manual layout, we restore the icon positions from the
+ * last manual layout.
+ */
+void
+nautilus_canvas_container_set_auto_layout (NautilusCanvasContainer *container,
+ gboolean auto_layout)
+{
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+ g_return_if_fail (auto_layout == FALSE || auto_layout == TRUE);
+
+ if (container->details->auto_layout == auto_layout) {
+ return;
+ }
+
+ reset_scroll_region_if_not_empty (container);
+ container->details->auto_layout = auto_layout;
+
+ if (!auto_layout) {
+ reload_icon_positions (container);
+ nautilus_canvas_container_freeze_icon_positions (container);
+ }
+
+ container->details->needs_resort = TRUE;
+ redo_layout (container);
+
+ g_signal_emit (container, signals[LAYOUT_CHANGED], 0);
+}
+
+gboolean
+nautilus_canvas_container_is_keep_aligned (NautilusCanvasContainer *container)
+{
+ return container->details->keep_aligned;
+}
+
+static gboolean
+align_icons_callback (gpointer callback_data)
+{
+ NautilusCanvasContainer *container;
+
+ container = NAUTILUS_CANVAS_CONTAINER (callback_data);
+ align_icons (container);
+ container->details->align_idle_id = 0;
+
+ return FALSE;
+}
+
+static void
+unschedule_align_icons (NautilusCanvasContainer *container)
+{
+ if (container->details->align_idle_id != 0) {
+ g_source_remove (container->details->align_idle_id);
+ container->details->align_idle_id = 0;
+ }
+}
+
+static void
+schedule_align_icons (NautilusCanvasContainer *container)
+{
+ if (container->details->align_idle_id == 0
+ && container->details->has_been_allocated) {
+ container->details->align_idle_id = g_idle_add
+ (align_icons_callback, container);
+ }
+}
+
+void
+nautilus_canvas_container_set_keep_aligned (NautilusCanvasContainer *container,
+ gboolean keep_aligned)
+{
+ if (container->details->keep_aligned != keep_aligned) {
+ container->details->keep_aligned = keep_aligned;
+
+ if (keep_aligned && !container->details->auto_layout) {
+ schedule_align_icons (container);
+ } else {
+ unschedule_align_icons (container);
+ }
+ }
+}
+
+/* Switch from automatic to manual layout, freezing all the icons in their
+ * current positions instead of restoring canvas positions from the last manual
+ * layout as set_auto_layout does.
+ */
+void
+nautilus_canvas_container_freeze_icon_positions (NautilusCanvasContainer *container)
+{
+ gboolean changed;
+ GList *p;
+ NautilusCanvasIcon *icon;
+ NautilusCanvasPosition position;
+
+ changed = container->details->auto_layout;
+ container->details->auto_layout = FALSE;
+
+ for (p = container->details->icons; p != NULL; p = p->next) {
+ icon = p->data;
+
+ position.x = icon->saved_ltr_x;
+ position.y = icon->y;
+ position.scale = icon->scale;
+ g_signal_emit (container, signals[ICON_POSITION_CHANGED], 0,
+ icon->data, &position);
+ }
+
+ if (changed) {
+ g_signal_emit (container, signals[LAYOUT_CHANGED], 0);
+ }
+}
+
+/* Re-sort, switching to automatic layout if it was in manual layout. */
+void
+nautilus_canvas_container_sort (NautilusCanvasContainer *container)
+{
+ gboolean changed;
+
+ changed = !container->details->auto_layout;
+ container->details->auto_layout = TRUE;
+
+ reset_scroll_region_if_not_empty (container);
+ container->details->needs_resort = TRUE;
+ redo_layout (container);
+
+ if (changed) {
+ g_signal_emit (container, signals[LAYOUT_CHANGED], 0);
+ }
+}
+
+gboolean
+nautilus_canvas_container_is_auto_layout (NautilusCanvasContainer *container)
+{
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container), FALSE);
+
+ return container->details->auto_layout;
+}
+
+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;
+}
+
+/* Return if the canvas container is a fixed size */
+gboolean
+nautilus_canvas_container_get_is_fixed_size (NautilusCanvasContainer *container)
+{
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container), FALSE);
+
+ return container->details->is_fixed_size;
+}
+
+/* Set the canvas container to be a fixed size */
+void
+nautilus_canvas_container_set_is_fixed_size (NautilusCanvasContainer *container,
+ gboolean is_fixed_size)
+{
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ container->details->is_fixed_size = is_fixed_size;
+}
+
+gboolean
+nautilus_canvas_container_get_is_desktop (NautilusCanvasContainer *container)
+{
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container), FALSE);
+
+ return container->details->is_desktop;
+}
+
+void
+nautilus_canvas_container_set_is_desktop (NautilusCanvasContainer *container,
+ gboolean is_desktop)
+{
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ container->details->is_desktop = is_desktop;
+
+ if (is_desktop) {
+ GtkStyleContext *context;
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (container));
+ gtk_style_context_add_class (context, "nautilus-desktop");
+ }
+}
+
+void
+nautilus_canvas_container_set_margins (NautilusCanvasContainer *container,
+ int left_margin,
+ int right_margin,
+ int top_margin,
+ int bottom_margin)
+{
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ container->details->left_margin = left_margin;
+ container->details->right_margin = right_margin;
+ container->details->top_margin = top_margin;
+ container->details->bottom_margin = bottom_margin;
+
+ /* redo layout of icons as the margins have changed */
+ schedule_redo_layout (container);
+}
+
+/* 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;
+ }
+ 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;
+
+ 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);
+}
+
+gboolean
+nautilus_canvas_container_is_layout_vertical (NautilusCanvasContainer *container)
+{
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container), FALSE);
+
+ /* we only do vertical layout in the desktop nowadays */
+ return container->details->is_desktop;
+}
+
+int
+nautilus_canvas_container_get_max_layout_lines_for_pango (NautilusCanvasContainer *container)
+{
+ int limit;
+
+ if (nautilus_canvas_container_get_is_desktop (container)) {
+ limit = desktop_text_ellipsis_limit;
+ } else {
+ 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;
+
+ if (nautilus_canvas_container_get_is_desktop (container)) {
+ limit = desktop_text_ellipsis_limit;
+ } else {
+ limit = text_ellipsis_limits[container->details->zoom_level];
+ }
+
+ if (limit <= 0) {
+ return G_MAXINT;
+ }
+
+ return limit;
+}
+
+void
+nautilus_canvas_container_begin_loading (NautilusCanvasContainer *container)
+{
+ gboolean dummy;
+
+ if (nautilus_canvas_container_get_store_layout_timestamps (container)) {
+ container->details->layout_timestamp = UNDEFINED_TIME;
+ g_signal_emit (container,
+ signals[GET_STORED_LAYOUT_TIMESTAMP], 0,
+ NULL, &container->details->layout_timestamp, &dummy);
+ }
+}
+
+static void
+store_layout_timestamps_now (NautilusCanvasContainer *container)
+{
+ NautilusCanvasIcon *icon;
+ GList *p;
+ gboolean dummy;
+
+ container->details->layout_timestamp = time (NULL);
+ g_signal_emit (container,
+ signals[STORE_LAYOUT_TIMESTAMP], 0,
+ NULL, &container->details->layout_timestamp, &dummy);
+
+ for (p = container->details->icons; p != NULL; p = p->next) {
+ icon = p->data;
+
+ g_signal_emit (container,
+ signals[STORE_LAYOUT_TIMESTAMP], 0,
+ icon->data, &container->details->layout_timestamp, &dummy);
+ }
+}
+
+
+void
+nautilus_canvas_container_end_loading (NautilusCanvasContainer *container,
+ gboolean all_icons_added)
+{
+ if (all_icons_added &&
+ nautilus_canvas_container_get_store_layout_timestamps (container)) {
+ if (container->details->new_icons == NULL) {
+ store_layout_timestamps_now (container);
+ } else {
+ container->details->store_layout_timestamps_when_finishing_new_icons = TRUE;
+ }
+ }
+}
+
+gboolean
+nautilus_canvas_container_get_store_layout_timestamps (NautilusCanvasContainer *container)
+{
+ return container->details->store_layout_timestamps;
+}
+
+
+void
+nautilus_canvas_container_set_store_layout_timestamps (NautilusCanvasContainer *container,
+ gboolean store_layout_timestamps)
+{
+ container->details->store_layout_timestamps = store_layout_timestamps;
+}
+
+
+#if ! defined (NAUTILUS_OMIT_SELF_CHECK)
+
+static char *
+check_compute_stretch (int icon_x, int icon_y, int icon_size,
+ int start_pointer_x, int start_pointer_y,
+ int end_pointer_x, int end_pointer_y)
+{
+ StretchState start, current;
+
+ start.icon_x = icon_x;
+ start.icon_y = icon_y;
+ start.icon_size = icon_size;
+ start.pointer_x = start_pointer_x;
+ start.pointer_y = start_pointer_y;
+ current.pointer_x = end_pointer_x;
+ current.pointer_y = end_pointer_y;
+
+ compute_stretch (&start, &current);
+
+ return g_strdup_printf ("%d,%d:%d",
+ current.icon_x,
+ current.icon_y,
+ current.icon_size);
+}
+
+void
+nautilus_self_check_canvas_container (void)
+{
+ EEL_CHECK_STRING_RESULT (check_compute_stretch (0, 0, 64, 0, 0, 0, 0), "0,0:64");
+ EEL_CHECK_STRING_RESULT (check_compute_stretch (0, 0, 64, 64, 64, 65, 65), "0,0:65");
+ EEL_CHECK_STRING_RESULT (check_compute_stretch (0, 0, 64, 64, 64, 65, 64), "0,0:64");
+ EEL_CHECK_STRING_RESULT (check_compute_stretch (100, 100, 64, 105, 105, 40, 40), "35,35:129");
+}
+
+#endif /* ! NAUTILUS_OMIT_SELF_CHECK */
diff --git a/src/nautilus-canvas-container.h b/src/nautilus-canvas-container.h
new file mode 100644
index 000000000..635349d7e
--- /dev/null
+++ b/src/nautilus-canvas-container.h
@@ -0,0 +1,324 @@
+
+/* 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>
+*/
+
+#ifndef NAUTILUS_CANVAS_CONTAINER_H
+#define NAUTILUS_CANVAS_CONTAINER_H
+
+#include <eel/eel-canvas.h>
+#include "nautilus-icon-info.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 NautilusCanvasContainerDetails NautilusCanvasContainerDetails;
+
+typedef struct {
+ EelCanvas canvas;
+ NautilusCanvasContainerDetails *details;
+} NautilusCanvasContainer;
+
+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,
+ GdkPoint *relative_item_points,
+ 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 (* can_accept_item) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *target,
+ const char *item_uri);
+ 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);
+ void (* icon_stretch_started) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data);
+ void (* icon_stretch_ended) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data);
+ 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);
+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);
+
+/* control the layout */
+gboolean nautilus_canvas_container_is_auto_layout (NautilusCanvasContainer *container);
+void nautilus_canvas_container_set_auto_layout (NautilusCanvasContainer *container,
+ gboolean auto_layout);
+
+gboolean nautilus_canvas_container_is_keep_aligned (NautilusCanvasContainer *container);
+void nautilus_canvas_container_set_keep_aligned (NautilusCanvasContainer *container,
+ gboolean keep_aligned);
+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);
+
+
+/* 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);
+GArray * nautilus_canvas_container_get_selected_icons_bounding_box (NautilusCanvasContainer *container);
+gboolean nautilus_canvas_container_has_stretch_handles (NautilusCanvasContainer *container);
+gboolean nautilus_canvas_container_is_stretched (NautilusCanvasContainer *container);
+void nautilus_canvas_container_show_stretch_handles (NautilusCanvasContainer *container);
+void nautilus_canvas_container_unstretch (NautilusCanvasContainer *container);
+
+/* 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);
+gboolean nautilus_canvas_container_get_is_fixed_size (NautilusCanvasContainer *container);
+void nautilus_canvas_container_set_is_fixed_size (NautilusCanvasContainer *container,
+ gboolean is_fixed_size);
+gboolean nautilus_canvas_container_get_is_desktop (NautilusCanvasContainer *container);
+void nautilus_canvas_container_set_is_desktop (NautilusCanvasContainer *container,
+ gboolean is_desktop);
+void nautilus_canvas_container_reset_scroll_region (NautilusCanvasContainer *container);
+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_is_layout_vertical (NautilusCanvasContainer *container);
+
+gboolean nautilus_canvas_container_get_store_layout_timestamps (NautilusCanvasContainer *container);
+void nautilus_canvas_container_set_store_layout_timestamps (NautilusCanvasContainer *container,
+ gboolean store_layout);
+
+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 \
+ - container->details->left_margin \
+ - container->details->right_margin) \
+ / EEL_CANVAS (container)->pixels_per_unit)
+
+#define CANVAS_HEIGHT(container,allocation) ((allocation.height \
+ - container->details->top_margin \
+ - container->details->bottom_margin) \
+ / EEL_CANVAS (container)->pixels_per_unit)
+
+#endif /* NAUTILUS_CANVAS_CONTAINER_H */
diff --git a/src/nautilus-canvas-dnd.c b/src/nautilus-canvas-dnd.c
new file mode 100644
index 000000000..39b68af93
--- /dev/null
+++ b/src/nautilus-canvas-dnd.c
@@ -0,0 +1,1820 @@
+
+/* 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-link.h"
+#include "nautilus-metadata.h"
+#include "nautilus-selection-canvas-item.h"
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-gnome-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/gdkx.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,
+ gboolean rewrite_desktop);
+
+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;
+ GdkRGBA black = { 0, 0, 0, 1 };
+
+ 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,
+ "outline-color-rgba", &black,
+ "outline-stippling", TRUE,
+ "width_pixels", 1,
+ 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);
+ if (!eel_uri_is_desktop (uri) && !nautilus_file_is_nautilus_link (file)) {
+ 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, TRUE);
+
+ 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);
+ }
+
+ if (nautilus_canvas_container_get_is_desktop (NAUTILUS_CANVAS_CONTAINER (widget))) {
+ list = drop_types_list_root;
+ } else {
+ 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_occured) {
+ /* 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 (container->details->dnd_source_info->selection_cache);
+ dnd_info->drag_info.selection_list = 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);
+
+ if (eel_uri_is_desktop (container_uri_string)) {
+ result = nautilus_drag_items_on_desktop (items);
+ } else {
+ 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, TRUE);
+
+ g_signal_emit_by_name (container, "handle-netscape-url",
+ encoded_url,
+ drop_target,
+ gdk_drag_context_get_selected_action (context),
+ x, y);
+
+ 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, TRUE);
+
+ g_signal_emit_by_name (container, "handle-uri-list",
+ uri_list,
+ drop_target,
+ gdk_drag_context_get_selected_action (context),
+ x, y);
+
+ 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, TRUE);
+
+ g_signal_emit_by_name (container, "handle-text",
+ text,
+ drop_target,
+ gdk_drag_context_get_selected_action (context),
+ x, y);
+
+ 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, TRUE);
+
+ g_signal_emit_by_name (container, "handle-raw",
+ raw_data,
+ length,
+ drop_target,
+ direct_save_uri,
+ gdk_drag_context_get_selected_action (context),
+ x, y);
+
+ 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_local_move (NautilusCanvasContainer *container,
+ double world_x, double world_y)
+{
+ GList *moved_icons, *p;
+ NautilusDragSelectionItem *item;
+ NautilusCanvasIcon *icon;
+ NautilusFile *file;
+ char screen_string[32];
+ GdkScreen *screen;
+ time_t now;
+
+ if (container->details->auto_layout) {
+ return;
+ }
+
+ time (&now);
+
+ /* Move and select the icons. */
+ moved_icons = NULL;
+ for (p = container->details->dnd_info->drag_info.selection_list; p != NULL; p = p->next) {
+ item = p->data;
+
+ icon = nautilus_canvas_container_get_icon_by_uri
+ (container, item->uri);
+
+ if (icon == NULL) {
+ /* probably dragged from another screen. Add it to
+ * this screen
+ */
+
+ file = nautilus_file_get_by_uri (item->uri);
+
+ screen = gtk_widget_get_screen (GTK_WIDGET (container));
+ g_snprintf (screen_string, sizeof (screen_string), "%d",
+ gdk_screen_get_number (screen));
+ nautilus_file_set_metadata (file,
+ NAUTILUS_METADATA_KEY_SCREEN,
+ NULL, screen_string);
+ nautilus_file_set_time_metadata (file,
+ NAUTILUS_METADATA_KEY_ICON_POSITION_TIMESTAMP, now);
+
+ nautilus_canvas_container_add (container, NAUTILUS_CANVAS_ICON_DATA (file));
+
+ icon = nautilus_canvas_container_get_icon_by_uri
+ (container, item->uri);
+ }
+
+ if (item->got_icon_position) {
+ nautilus_canvas_container_move_icon
+ (container, icon,
+ world_x + item->icon_x, world_y + item->icon_y,
+ icon->scale,
+ TRUE, TRUE, TRUE);
+ }
+ moved_icons = g_list_prepend (moved_icons, icon);
+ }
+ nautilus_canvas_container_select_list_unselect_others
+ (container, moved_icons);
+ /* Might have been moved in a way that requires adjusting scroll region. */
+ nautilus_canvas_container_update_scroll_region (container);
+ g_list_free (moved_icons);
+}
+
+static void
+handle_nonlocal_move (NautilusCanvasContainer *container,
+ GdkDragAction action,
+ int x, int y,
+ const char *target_uri,
+ gboolean icon_hit)
+{
+ GList *source_uris, *p;
+ GArray *source_item_locations;
+ gboolean free_target_uri, is_rtl;
+ int index, item_x;
+ GtkAllocation allocation;
+
+ 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);
+
+ is_rtl = nautilus_canvas_container_is_layout_rtl (container);
+
+ source_item_locations = g_array_new (FALSE, TRUE, sizeof (GdkPoint));
+ if (!icon_hit) {
+ /* Drop onto a container. Pass along the item points to allow placing
+ * the items in their same relative positions in the new container.
+ */
+ source_item_locations = g_array_set_size (source_item_locations,
+ g_list_length (container->details->dnd_info->drag_info.selection_list));
+
+ for (index = 0, p = container->details->dnd_info->drag_info.selection_list;
+ p != NULL; index++, p = p->next) {
+ item_x = ((NautilusDragSelectionItem *)p->data)->icon_x;
+ if (is_rtl)
+ item_x = -item_x - ((NautilusDragSelectionItem *)p->data)->icon_width;
+ g_array_index (source_item_locations, GdkPoint, index).x = item_x;
+ g_array_index (source_item_locations, GdkPoint, index).y =
+ ((NautilusDragSelectionItem *)p->data)->icon_y;
+ }
+ }
+
+ free_target_uri = FALSE;
+ /* Rewrite internal desktop URIs to the normal target uri */
+ if (eel_uri_is_desktop (target_uri)) {
+ target_uri = nautilus_get_desktop_directory_uri ();
+ free_target_uri = TRUE;
+ }
+
+ if (is_rtl) {
+ gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
+ x = CANVAS_WIDTH (container, allocation) - x;
+ }
+
+ /* start the copy */
+ g_signal_emit_by_name (container, "move-copy-items",
+ source_uris,
+ source_item_locations,
+ target_uri,
+ action,
+ x, y);
+
+ if (free_target_uri) {
+ g_free ((char *)target_uri);
+ }
+
+ g_list_free (source_uris);
+ g_array_free (source_item_locations, TRUE);
+}
+
+static char *
+nautilus_canvas_container_find_drop_target (NautilusCanvasContainer *container,
+ GdkDragContext *context,
+ int x, int y,
+ gboolean *icon_hit,
+ gboolean rewrite_desktop)
+{
+ 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) {
+ if (rewrite_desktop && eel_uri_is_desktop (container_uri)) {
+ g_free (container_uri);
+ container_uri = nautilus_get_desktop_directory_uri ();
+ } else {
+ 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) {
+ /* FIXME bugzilla.gnome.org 42485: This belongs in FMDirectoryView, not here. */
+ /* Check for special case items in selection list */
+ if (nautilus_drag_selection_includes_special_link (container->details->dnd_info->drag_info.selection_list)) {
+ /* We only want to move the trash */
+ action = GDK_ACTION_MOVE;
+ } else {
+ 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, FALSE);
+
+ local_move_only = FALSE;
+ if (!icon_hit && real_action == GDK_ACTION_MOVE) {
+ /* we can just move the canvas positions if the move ended up in
+ * the item's parent container
+ */
+ local_move_only = nautilus_canvas_container_selection_items_local
+ (container, container->details->dnd_info->drag_info.selection_list);
+ }
+
+ if (local_move_only) {
+ handle_local_move (container, world_x, world_y);
+ } else {
+ handle_nonlocal_move (container, real_action, world_x, world_y, 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, FALSE);
+ 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;
+
+ 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_set_device_offset (surface, -x_offset, -y_offset);
+ 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);
+
+ nautilus_window_start_dnd (window, context);
+}
+
+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;
+ GtkWidget *toplevel;
+
+ dnd_info = NAUTILUS_CANVAS_CONTAINER (widget)->details->dnd_info;
+
+ toplevel = gtk_widget_get_toplevel (widget);
+ if (toplevel != NULL &&
+ g_object_get_data (G_OBJECT (toplevel), "is_desktop_window")) {
+ return;
+ }
+
+ 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;
+ }
+
+ if (nautilus_canvas_container_get_is_desktop (container)) {
+ 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, TRUE);
+ 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 occured and that it can actually
+ process the actions.
+ make sure it is going to be called at least once.
+ */
+ dnd_info->drag_info.drop_occured = 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_occured) {
+
+ 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) {
+ GdkPoint p;
+ GFile *location;
+
+ location = g_file_new_for_uri (drag_info->direct_save_uri);
+
+ nautilus_file_changes_queue_file_added (location);
+ p.x = x; p.y = y;
+ nautilus_file_changes_queue_schedule_position_set (
+ location,
+ p,
+ gdk_screen_get_number (
+ gtk_widget_get_screen (widget)));
+ g_object_unref (location);
+ nautilus_file_changes_consume_changes (TRUE);
+ success = TRUE;
+ }
+ break;
+ } /* NAUTILUS_ICON_DND_XDNDDIRECTSAVE */
+ }
+ 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_occured = 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);
+ if (!nautilus_canvas_container_get_is_desktop (container)) {
+ /* Don't set up rootwindow drop */
+ n_elements -= 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 000000000..7b659e38c
--- /dev/null
+++ b/src/nautilus-canvas-dnd.h
@@ -0,0 +1,59 @@
+
+/* 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>
+*/
+
+#ifndef NAUTILUS_CANVAS_DND_H
+#define NAUTILUS_CANVAS_DND_H
+
+#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);
+
+#endif /* NAUTILUS_CANVAS_DND_H */
diff --git a/src/nautilus-canvas-item.c b/src/nautilus-canvas-item.c
new file mode 100644
index 000000000..547b9fe10
--- /dev/null
+++ b/src/nautilus-canvas-item.c
@@ -0,0 +1,2635 @@
+
+/* 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-gdk-extensions.h>
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-graphic-effects.h>
+#include <eel/eel-string.h>
+#include <eel/eel-accessibility.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 show_stretch_handles : 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;
+
+ /* Accessibility bits */
+ GailTextUtil *text_util;
+};
+
+/* 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 void nautilus_canvas_item_text_interface_init (EelAccessibleTextIface *iface);
+static GType nautilus_canvas_item_accessible_factory_get_type (void);
+
+G_DEFINE_TYPE_WITH_CODE (NautilusCanvasItem, nautilus_canvas_item, EEL_TYPE_CANVAS_ITEM,
+ G_IMPLEMENT_INTERFACE (EEL_TYPE_ACCESSIBLE_TEXT,
+ nautilus_canvas_item_text_interface_init));
+
+/* private */
+static void get_icon_rectangle (NautilusCanvasItem *item,
+ EelIRect *rect);
+static void draw_pixbuf (GdkPixbuf *pixbuf,
+ cairo_t *cr,
+ int x,
+ int y);
+static PangoLayout *get_label_layout (PangoLayout **layout,
+ NautilusCanvasItem *item,
+ const char *text);
+static gboolean hit_test_stretch_handle (NautilusCanvasItem *item,
+ EelIRect icon_rect,
+ GtkCornerType *corner);
+;
+
+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_util != NULL) {
+ g_object_unref (details->text_util);
+ }
+
+ 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;
+
+ 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;
+ }
+
+ g_free (details->editable_text);
+ details->editable_text = g_strdup (g_value_get_string (value));
+ if (details->text_util) {
+ gail_text_util_text_setup (details->text_util,
+ details->editable_text);
+ 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);
+
+ if (details->is_highlighted_as_keyboard_focus) {
+ atk_focus_tracker_notify (accessible);
+ }
+ 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, prelight_label;
+ 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;
+
+ gtk_widget_style_get (GTK_WIDGET (container),
+ "activate_prelight_icon_label", &prelight_label,
+ NULL);
+
+ /* 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 if (!needs_highlight && have_editable &&
+ details->text_width > 0 && details->text_height > 0 &&
+ prelight_label && item->details->is_prelit) {
+ state |= GTK_STATE_FLAG_PRELIGHT;
+
+ 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 (prelight_label && item->details->is_prelit) {
+ state |= GTK_STATE_FLAG_PRELIGHT;
+ }
+
+ 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;
+ }
+}
+
+static GdkPixbuf *
+get_knob_pixbuf (void)
+{
+ GdkPixbuf *knob_pixbuf = NULL;
+ GInputStream *stream = g_resources_open_stream ("/org/gnome/nautilus/icons/knob.png", 0, NULL);
+
+ if (stream != NULL) {
+ knob_pixbuf = gdk_pixbuf_new_from_stream (stream, NULL, NULL);
+ g_object_unref (stream);
+ }
+
+ return knob_pixbuf;
+}
+
+static void
+draw_stretch_handles (NautilusCanvasItem *item,
+ cairo_t *cr,
+ const EelIRect *rect)
+{
+ GtkWidget *widget;
+ GdkPixbuf *knob_pixbuf;
+ int knob_width, knob_height;
+ double dash = { 2.0 };
+ GtkStyleContext *style;
+ GdkRGBA color;
+
+ if (!item->details->show_stretch_handles) {
+ return;
+ }
+
+ widget = GTK_WIDGET (EEL_CANVAS_ITEM (item)->canvas);
+ style = gtk_widget_get_style_context (widget);
+
+ cairo_save (cr);
+ knob_pixbuf = get_knob_pixbuf ();
+ knob_width = gdk_pixbuf_get_width (knob_pixbuf);
+ knob_height = gdk_pixbuf_get_height (knob_pixbuf);
+
+ /* first draw the box */
+ gtk_style_context_get_color (style, GTK_STATE_FLAG_SELECTED, &color);
+ gdk_cairo_set_source_rgba (cr, &color);
+ cairo_set_dash (cr, &dash, 1, 0);
+ cairo_set_line_width (cr, 1.0);
+ cairo_rectangle (cr,
+ rect->x0 + 0.5,
+ rect->y0 + 0.5,
+ rect->x1 - rect->x0 - 1,
+ rect->y1 - rect->y0 - 1);
+ cairo_stroke (cr);
+
+ cairo_restore (cr);
+
+ /* draw the stretch handles themselves */
+ draw_pixbuf (knob_pixbuf, cr, rect->x0, rect->y0);
+ draw_pixbuf (knob_pixbuf, cr, rect->x0, rect->y1 - knob_height);
+ draw_pixbuf (knob_pixbuf, cr, rect->x1 - knob_width, rect->y0);
+ draw_pixbuf (knob_pixbuf, cr, rect->x1 - knob_width, rect->y1 - knob_height);
+
+ g_object_unref (knob_pixbuf);
+}
+
+static void
+draw_pixbuf (GdkPixbuf *pixbuf,
+ cairo_t *cr,
+ int x, int y)
+{
+ cairo_save (cr);
+ gdk_cairo_set_source_pixbuf (cr, pixbuf, x, y);
+ cairo_paint (cr);
+ cairo_restore (cr);
+}
+
+/* shared code to highlight or dim the passed-in pixbuf */
+static cairo_surface_t *
+real_map_surface (NautilusCanvasItem *canvas_item)
+{
+ EelCanvas *canvas;
+ GdkPixbuf *temp_pixbuf, *old_pixbuf;
+ GtkStyleContext *style;
+ GdkRGBA color;
+ cairo_surface_t *surface;
+
+ temp_pixbuf = canvas_item->details->pixbuf;
+ canvas = EEL_CANVAS_ITEM(canvas_item)->canvas;
+
+ g_object_ref (temp_pixbuf);
+
+ if (canvas_item->details->is_prelit ||
+ canvas_item->details->is_highlighted_for_clipboard) {
+ old_pixbuf = temp_pixbuf;
+
+ temp_pixbuf = eel_create_spotlight_pixbuf (temp_pixbuf);
+ g_object_unref (old_pixbuf);
+ }
+
+ if (canvas_item->details->is_highlighted_for_selection
+ || canvas_item->details->is_highlighted_for_drop) {
+ style = gtk_widget_get_style_context (GTK_WIDGET (canvas));
+
+ if (gtk_widget_has_focus (GTK_WIDGET (canvas))) {
+ gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED, &color);
+ } else {
+ gtk_style_context_get_background_color (style, GTK_STATE_FLAG_ACTIVE, &color);
+ }
+
+ old_pixbuf = temp_pixbuf;
+ temp_pixbuf = eel_create_colorized_pixbuf (temp_pixbuf, &color);
+
+ g_object_unref (old_pixbuf);
+ }
+
+ surface = gdk_cairo_surface_create_from_pixbuf (temp_pixbuf,
+ gtk_widget_get_scale_factor (GTK_WIDGET (canvas)),
+ gtk_widget_get_window (GTK_WIDGET (canvas)));
+ g_object_unref (temp_pixbuf);
+
+ return surface;
+}
+
+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 stretching handles (if necessary). */
+ draw_stretch_handles (canvas_item, cr, &icon_rect);
+
+ /* Draw the label text. */
+ draw_label_text (canvas_item, cr, icon_rect);
+
+ gtk_style_context_restore (context);
+}
+
+#define ZERO_WIDTH_SPACE "\xE2\x80\x8B"
+
+#define ZERO_OR_THREE_DIGITS(p) \
+ (!g_ascii_isdigit (*(p)) || \
+ (g_ascii_isdigit (*(p+1)) && \
+ g_ascii_isdigit (*(p+2))))
+
+
+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 == '.' && ZERO_OR_THREE_DIGITS (p+1))) {
+ /* Ensure that we allow to break after '_' or '.' characters,
+ * if they are not likely to be part of a version information, to
+ * not break wrapping of foobar-0.0.1.
+ * Wrap before IPs and long numbers, though. */
+ 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_auto_dir (layout, FALSE);
+ 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);
+
+ /* 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 hits in the stretch handles. */
+ if (hit_test_stretch_handle (canvas_item, icon_rect, NULL)) {
+ return TRUE;
+ }
+
+ /* 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;
+}
+
+void
+nautilus_canvas_item_set_show_stretch_handles (NautilusCanvasItem *item,
+ gboolean show_stretch_handles)
+{
+ g_return_if_fail (NAUTILUS_IS_CANVAS_ITEM (item));
+ g_return_if_fail (show_stretch_handles == FALSE || show_stretch_handles == TRUE);
+
+ if (!item->details->show_stretch_handles == !show_stretch_handles) {
+ return;
+ }
+
+ item->details->show_stretch_handles = show_stretch_handles;
+ eel_canvas_item_request_update (EEL_CANVAS_ITEM (item));
+}
+
+/* Check if one of the stretch handles was hit. */
+static gboolean
+hit_test_stretch_handle (NautilusCanvasItem *item,
+ EelIRect probe_icon_rect,
+ GtkCornerType *corner)
+{
+ EelIRect icon_rect;
+ GdkPixbuf *knob_pixbuf;
+ int knob_width, knob_height;
+ int hit_corner;
+
+ g_assert (NAUTILUS_IS_CANVAS_ITEM (item));
+
+ /* Make sure there are handles to hit. */
+ if (!item->details->show_stretch_handles) {
+ return FALSE;
+ }
+
+ /* Quick check to see if the rect hits the canvas at all. */
+ icon_rect = item->details->icon_rect;
+ if (!eel_irect_hits_irect (probe_icon_rect, icon_rect)) {
+ return FALSE;
+ }
+
+ knob_pixbuf = get_knob_pixbuf ();
+ knob_width = gdk_pixbuf_get_width (knob_pixbuf);
+ knob_height = gdk_pixbuf_get_height (knob_pixbuf);
+ g_object_unref (knob_pixbuf);
+
+ /* Check for hits in the stretch handles. */
+ hit_corner = -1;
+ if (probe_icon_rect.x0 < icon_rect.x0 + knob_width) {
+ if (probe_icon_rect.y0 < icon_rect.y0 + knob_height)
+ hit_corner = GTK_CORNER_TOP_LEFT;
+ else if (probe_icon_rect.y1 >= icon_rect.y1 - knob_height)
+ hit_corner = GTK_CORNER_BOTTOM_LEFT;
+ }
+ else if (probe_icon_rect.x1 >= icon_rect.x1 - knob_width) {
+ if (probe_icon_rect.y0 < icon_rect.y0 + knob_height)
+ hit_corner = GTK_CORNER_TOP_RIGHT;
+ else if (probe_icon_rect.y1 >= icon_rect.y1 - knob_height)
+ hit_corner = GTK_CORNER_BOTTOM_RIGHT;
+ }
+ if (corner)
+ *corner = hit_corner;
+
+ return hit_corner != -1;
+}
+
+gboolean
+nautilus_canvas_item_hit_test_stretch_handles (NautilusCanvasItem *item,
+ gdouble world_x,
+ gdouble world_y,
+ GtkCornerType *corner)
+{
+ EelIRect icon_rect;
+
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_ITEM (item), FALSE);
+
+ eel_canvas_w2c (EEL_CANVAS_ITEM (item)->canvas,
+ world_x,
+ world_y,
+ &icon_rect.x0,
+ &icon_rect.y0);
+ icon_rect.x1 = icon_rect.x0 + 1;
+ icon_rect.y1 = icon_rect.y0 + 1;
+ return hit_test_stretch_handle (item, icon_rect, corner);
+}
+
+/* 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));
+}
+
+static GailTextUtil *
+nautilus_canvas_item_get_text (GObject *text)
+{
+ return NAUTILUS_CANVAS_ITEM (text)->details->text_util;
+}
+
+static void
+nautilus_canvas_item_text_interface_init (EelAccessibleTextIface *iface)
+{
+ iface->get_text = nautilus_canvas_item_get_text;
+}
+
+/* ============================= 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_position (ATK_COMPONENT (image), x, y, 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_position (ATK_COMPONENT (text), &pos_x, &pos_y, 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 void
+nautilus_canvas_item_accessible_text_interface_init (AtkTextIface *iface)
+{
+ iface->get_text = eel_accessibility_text_get_text;
+ iface->get_character_at_offset = eel_accessibility_text_get_character_at_offset;
+ iface->get_text_before_offset = eel_accessibility_text_get_text_before_offset;
+ iface->get_text_at_offset = eel_accessibility_text_get_text_at_offset;
+ iface->get_text_after_offset = eel_accessibility_text_get_text_after_offset;
+ iface->get_character_count = eel_accessibility_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;
+ GString *item_text;
+
+ item = NAUTILUS_CANVAS_ITEM (for_object);
+ g_assert (item != NULL);
+
+ item_text = g_string_new (NULL);
+ if (item->details->editable_text) {
+ g_string_append (item_text, item->details->editable_text);
+ }
+ if (item->details->additional_text) {
+ g_string_append (item_text, item->details->additional_text);
+ }
+
+ item->details->text_util = gail_text_util_new ();
+ gail_text_util_text_setup (item->details->text_util,
+ item_text->str);
+ g_string_free (item_text, TRUE);
+
+ 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 000000000..89dfe8709
--- /dev/null
+++ b/src/nautilus-canvas-item.h
@@ -0,0 +1,99 @@
+
+/* 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/>.
+ */
+
+#ifndef NAUTILUS_CANVAS_ITEM_H
+#define NAUTILUS_CANVAS_ITEM_H
+
+#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);
+void nautilus_canvas_item_set_show_stretch_handles (NautilusCanvasItem *item,
+ gboolean show_stretch_handles);
+
+/* geometry and hit testing */
+gboolean nautilus_canvas_item_hit_test_rectangle (NautilusCanvasItem *item,
+ EelIRect canvas_rect);
+gboolean nautilus_canvas_item_hit_test_stretch_handles (NautilusCanvasItem *item,
+ gdouble world_x,
+ gdouble world_y,
+ GtkCornerType *corner);
+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
+
+#endif /* NAUTILUS_CANVAS_ITEM_H */
diff --git a/src/nautilus-canvas-private.h b/src/nautilus-canvas-private.h
new file mode 100644
index 000000000..8e9f7c587
--- /dev/null
+++ b/src/nautilus-canvas-private.h
@@ -0,0 +1,269 @@
+/* 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>
+*/
+
+#ifndef NAUTILUS_CANVAS_CONTAINER_PRIVATE_H
+#define NAUTILUS_CANVAS_CONTAINER_PRIVATE_H
+
+#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;
+
+ /* Scale factor (stretches icon). */
+ double scale;
+
+ /* 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;
+
+ eel_boolean_bit has_lazy_position : 1;
+} NautilusCanvasIcon;
+
+
+/* Private NautilusCanvasContainer members. */
+
+typedef struct {
+ gboolean active;
+
+ double start_x, start_y;
+
+ EelCanvasItem *selection_rectangle;
+
+ 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;
+
+ /* Current icon for keyboard navigation. */
+ NautilusCanvasIcon *keyboard_focus;
+ NautilusCanvasIcon *keyboard_rubberband_start;
+
+ /* Current icon with stretch handles, so we have only one. */
+ NautilusCanvasIcon *stretch_icon;
+ double stretch_initial_x, stretch_initial_y;
+ guint stretch_initial_size;
+
+ /* 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;
+ StretchState stretch_start;
+
+ 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;
+
+ /* Idle handler for stretch code */
+ guint stretch_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;
+ gboolean auto_layout;
+
+ /* Should the container keep icons aligned to a grid */
+ gboolean keep_aligned;
+
+ /* Set to TRUE after first allocation has been done */
+ gboolean has_been_allocated;
+
+ int size_allocation_count;
+ guint size_allocation_count_id;
+
+ /* Is the container fixed or resizable */
+ gboolean is_fixed_size;
+
+ /* Is the container for a desktop window */
+ gboolean is_desktop;
+
+ /* Ignore the visible area the next time the scroll region is recomputed */
+ gboolean reset_scroll_region_trigger;
+
+ /* The position we are scaling to on stretch */
+ double world_x;
+ double world_y;
+
+ /* margins to follow, used for the desktop panel avoidance */
+ int left_margin;
+ int right_margin;
+ int top_margin;
+ int bottom_margin;
+
+ /* a11y items used by canvas items */
+ guint a11y_item_action_idle_handler;
+ GQueue* a11y_item_action_queue;
+
+ eel_boolean_bit is_loading : 1;
+ eel_boolean_bit needs_resort : 1;
+ eel_boolean_bit selection_needs_resort : 1;
+
+ eel_boolean_bit store_layout_timestamps : 1;
+ eel_boolean_bit store_layout_timestamps_when_finishing_new_icons : 1;
+ time_t layout_timestamp;
+};
+
+/* Private functions shared by mutiple files. */
+NautilusCanvasIcon *nautilus_canvas_container_get_icon_by_uri (NautilusCanvasContainer *container,
+ const char *uri);
+void nautilus_canvas_container_move_icon (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon,
+ int x,
+ int y,
+ double scale,
+ gboolean raise,
+ gboolean snap,
+ gboolean update_position);
+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);
+
+#endif /* NAUTILUS_CANVAS_CONTAINER_PRIVATE_H */
diff --git a/src/nautilus-canvas-view-container.c b/src/nautilus-canvas-view-container.c
index b4e0df67e..53f29d8ea 100644
--- a/src/nautilus-canvas-view-container.c
+++ b/src/nautilus-canvas-view-container.c
@@ -27,9 +27,9 @@
#include <glib/gi18n.h>
#include <gio/gio.h>
#include <eel/eel-glib-extensions.h>
-#include <libnautilus-private/nautilus-global-preferences.h>
-#include <libnautilus-private/nautilus-file-attributes.h>
-#include <libnautilus-private/nautilus-thumbnails.h>
+#include "nautilus-global-preferences.h"
+#include "nautilus-file-attributes.h"
+#include "nautilus-thumbnails.h"
G_DEFINE_TYPE (NautilusCanvasViewContainer, nautilus_canvas_view_container, NAUTILUS_TYPE_CANVAS_CONTAINER);
diff --git a/src/nautilus-canvas-view-container.h b/src/nautilus-canvas-view-container.h
index 219751922..26bcefe05 100644
--- a/src/nautilus-canvas-view-container.h
+++ b/src/nautilus-canvas-view-container.h
@@ -25,7 +25,7 @@
#include "nautilus-canvas-view.h"
-#include <libnautilus-private/nautilus-canvas-container.h>
+#include "nautilus-canvas-container.h"
typedef struct NautilusCanvasViewContainer NautilusCanvasViewContainer;
typedef struct NautilusCanvasViewContainerClass NautilusCanvasViewContainerClass;
diff --git a/src/nautilus-canvas-view.c b/src/nautilus-canvas-view.c
index a72b4b543..dcd491414 100644
--- a/src/nautilus-canvas-view.c
+++ b/src/nautilus-canvas-view.c
@@ -35,20 +35,20 @@
#include <gtk/gtk.h>
#include <glib/gi18n.h>
#include <gio/gio.h>
-#include <libnautilus-private/nautilus-clipboard-monitor.h>
-#include <libnautilus-private/nautilus-directory.h>
-#include <libnautilus-private/nautilus-dnd.h>
-#include <libnautilus-private/nautilus-file-utilities.h>
-#include <libnautilus-private/nautilus-ui-utilities.h>
-#include <libnautilus-private/nautilus-global-preferences.h>
-#include <libnautilus-private/nautilus-canvas-container.h>
-#include <libnautilus-private/nautilus-canvas-dnd.h>
-#include <libnautilus-private/nautilus-link.h>
-#include <libnautilus-private/nautilus-metadata.h>
-#include <libnautilus-private/nautilus-clipboard.h>
+#include "nautilus-clipboard-monitor.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-link.h"
+#include "nautilus-metadata.h"
+#include "nautilus-clipboard.h"
#define DEBUG_FLAG NAUTILUS_DEBUG_CANVAS_VIEW
-#include <libnautilus-private/nautilus-debug.h>
+#include "nautilus-debug.h"
#include <locale.h>
#include <signal.h>
diff --git a/src/nautilus-canvas-view.h b/src/nautilus-canvas-view.h
index 3e515b4b9..cc5cb1dfb 100644
--- a/src/nautilus-canvas-view.h
+++ b/src/nautilus-canvas-view.h
@@ -26,7 +26,7 @@
#define NAUTILUS_CANVAS_VIEW_H
#include "nautilus-files-view.h"
-#include "libnautilus-private/nautilus-canvas-container.h"
+#include "nautilus-canvas-container.h"
typedef struct NautilusCanvasView NautilusCanvasView;
typedef struct NautilusCanvasViewClass NautilusCanvasViewClass;
diff --git a/src/nautilus-clipboard-monitor.c b/src/nautilus-clipboard-monitor.c
new file mode 100644
index 000000000..4d164723c
--- /dev/null
+++ b/src/nautilus-clipboard-monitor.c
@@ -0,0 +1,319 @@
+/*
+ nautilus-clipboard-monitor.c: catch clipboard changes.
+
+ Copyright (C) 2004 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 "nautilus-clipboard-monitor.h"
+#include "nautilus-file.h"
+
+#include <eel/eel-debug.h>
+#include <gtk/gtk.h>
+
+/* X11 has a weakness when it comes to clipboard handling,
+ * there is no way to get told when the owner of the clipboard
+ * changes. This is often needed, for instance to set the
+ * sensitivity of the paste menu item. We work around this
+ * internally in an app by telling the clipboard monitor when
+ * we changed the clipboard. Unfortunately this doesn't give
+ * us perfect results, we still don't catch changes made by
+ * other clients
+ *
+ * This is fixed with the XFIXES extensions, which recent versions
+ * of Gtk+ supports as the owner_change signal on GtkClipboard. We
+ * use this now, but keep the old code since not all X servers support
+ * XFIXES.
+ */
+
+enum {
+ CLIPBOARD_CHANGED,
+ CLIPBOARD_INFO,
+ LAST_SIGNAL
+};
+
+struct NautilusClipboardMonitorDetails {
+ NautilusClipboardInfo *info;
+};
+
+static guint signals[LAST_SIGNAL];
+static GdkAtom copied_files_atom;
+
+G_DEFINE_TYPE (NautilusClipboardMonitor, nautilus_clipboard_monitor, G_TYPE_OBJECT);
+
+static NautilusClipboardMonitor *clipboard_monitor = NULL;
+
+static void
+destroy_clipboard_monitor (void)
+{
+ if (clipboard_monitor != NULL) {
+ g_object_unref (clipboard_monitor);
+ }
+}
+
+NautilusClipboardMonitor *
+nautilus_clipboard_monitor_get (void)
+{
+ GtkClipboard *clipboard;
+
+ if (clipboard_monitor == NULL) {
+ clipboard_monitor = NAUTILUS_CLIPBOARD_MONITOR (g_object_new (NAUTILUS_TYPE_CLIPBOARD_MONITOR, NULL));
+ eel_debug_call_at_shutdown (destroy_clipboard_monitor);
+
+ clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+ g_signal_connect (clipboard, "owner-change",
+ G_CALLBACK (nautilus_clipboard_monitor_emit_changed), NULL);
+ }
+ return clipboard_monitor;
+}
+
+void
+nautilus_clipboard_monitor_emit_changed (void)
+{
+ NautilusClipboardMonitor *monitor;
+
+ monitor = nautilus_clipboard_monitor_get ();
+
+ g_signal_emit (monitor, signals[CLIPBOARD_CHANGED], 0);
+}
+
+static NautilusClipboardInfo *
+nautilus_clipboard_info_new (GList *files,
+ gboolean cut)
+{
+ NautilusClipboardInfo *info;
+
+ info = g_slice_new0 (NautilusClipboardInfo);
+ info->files = nautilus_file_list_copy (files);
+ info->cut = cut;
+
+ return info;
+}
+
+static NautilusClipboardInfo *
+nautilus_clipboard_info_copy (NautilusClipboardInfo *info)
+{
+ NautilusClipboardInfo *new_info;
+
+ new_info = NULL;
+
+ if (info != NULL) {
+ new_info = nautilus_clipboard_info_new (info->files,
+ info->cut);
+ }
+
+ return new_info;
+}
+
+static void
+nautilus_clipboard_info_free (NautilusClipboardInfo *info)
+{
+ nautilus_file_list_free (info->files);
+
+ g_slice_free (NautilusClipboardInfo, info);
+}
+
+static void
+nautilus_clipboard_monitor_init (NautilusClipboardMonitor *monitor)
+{
+ monitor->details =
+ G_TYPE_INSTANCE_GET_PRIVATE (monitor, NAUTILUS_TYPE_CLIPBOARD_MONITOR,
+ NautilusClipboardMonitorDetails);
+}
+
+static void
+clipboard_monitor_finalize (GObject *object)
+{
+ NautilusClipboardMonitor *monitor;
+
+ monitor = NAUTILUS_CLIPBOARD_MONITOR (object);
+
+ if (monitor->details->info != NULL) {
+ nautilus_clipboard_info_free (monitor->details->info);
+ monitor->details->info = NULL;
+ }
+
+ G_OBJECT_CLASS (nautilus_clipboard_monitor_parent_class)->finalize (object);
+}
+
+static void
+nautilus_clipboard_monitor_class_init (NautilusClipboardMonitorClass *klass)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->finalize = clipboard_monitor_finalize;
+
+ copied_files_atom = gdk_atom_intern ("x-special/gnome-copied-files", FALSE);
+
+ signals[CLIPBOARD_CHANGED] =
+ g_signal_new ("clipboard-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusClipboardMonitorClass, clipboard_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ signals[CLIPBOARD_INFO] =
+ g_signal_new ("clipboard-info",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusClipboardMonitorClass, clipboard_info),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE,
+ 1, G_TYPE_POINTER);
+
+ g_type_class_add_private (klass, sizeof (NautilusClipboardMonitorDetails));
+}
+
+void
+nautilus_clipboard_monitor_set_clipboard_info (NautilusClipboardMonitor *monitor,
+ NautilusClipboardInfo *info)
+{
+ if (monitor->details->info != NULL) {
+ nautilus_clipboard_info_free (monitor->details->info);
+ monitor->details->info = NULL;
+ }
+
+ monitor->details->info = nautilus_clipboard_info_copy (info);
+
+ g_signal_emit (monitor, signals[CLIPBOARD_INFO], 0, monitor->details->info);
+
+ nautilus_clipboard_monitor_emit_changed ();
+}
+
+NautilusClipboardInfo *
+nautilus_clipboard_monitor_get_clipboard_info (NautilusClipboardMonitor *monitor)
+{
+ return monitor->details->info;
+}
+
+gboolean
+nautilus_clipboard_monitor_is_cut (NautilusClipboardMonitor *monitor)
+{
+ NautilusClipboardInfo *info;
+
+ info = nautilus_clipboard_monitor_get_clipboard_info (monitor);
+
+ return info != NULL ? info->cut : FALSE;
+}
+
+void
+nautilus_clear_clipboard_callback (GtkClipboard *clipboard,
+ gpointer user_data)
+{
+ nautilus_clipboard_monitor_set_clipboard_info
+ (nautilus_clipboard_monitor_get (), NULL);
+}
+
+static char *
+convert_file_list_to_string (NautilusClipboardInfo *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 (info->cut ? "cut" : "copy");
+ }
+
+ 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);
+ }
+
+ /* skip newline for last element */
+ if (i + 1 < g_list_length (info->files)) {
+ g_string_append_c (uris, '\n');
+ }
+ } else {
+ g_string_append_c (uris, '\n');
+ g_string_append (uris, uri);
+ }
+
+ g_free (uri);
+ }
+
+ *len = uris->len;
+ return g_string_free (uris, FALSE);
+}
+
+void
+nautilus_get_clipboard_callback (GtkClipboard *clipboard,
+ GtkSelectionData *selection_data,
+ guint info,
+ gpointer user_data)
+{
+ char **uris;
+ GList *l;
+ int i;
+ NautilusClipboardInfo *clipboard_info;
+ GdkAtom target;
+
+ clipboard_info =
+ nautilus_clipboard_monitor_get_clipboard_info (nautilus_clipboard_monitor_get ());
+
+ 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, TRUE, &len);
+ gtk_selection_data_set_text (selection_data, str, len);
+ g_free (str);
+ } else if (target == copied_files_atom) {
+ char *str;
+ gsize len;
+
+ str = convert_file_list_to_string (clipboard_info, FALSE, &len);
+ gtk_selection_data_set (selection_data, copied_files_atom, 8, (guchar *) str, len);
+ g_free (str);
+ }
+}
diff --git a/src/nautilus-clipboard-monitor.h b/src/nautilus-clipboard-monitor.h
new file mode 100644
index 000000000..cb7a948b4
--- /dev/null
+++ b/src/nautilus-clipboard-monitor.h
@@ -0,0 +1,80 @@
+/*
+ nautilus-clipboard-monitor.h: lets you notice clipboard changes.
+
+ Copyright (C) 2004 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>
+*/
+
+#ifndef NAUTILUS_CLIPBOARD_MONITOR_H
+#define NAUTILUS_CLIPBOARD_MONITOR_H
+
+#include <gtk/gtk.h>
+
+#define NAUTILUS_TYPE_CLIPBOARD_MONITOR nautilus_clipboard_monitor_get_type()
+#define NAUTILUS_CLIPBOARD_MONITOR(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_CLIPBOARD_MONITOR, NautilusClipboardMonitor))
+#define NAUTILUS_CLIPBOARD_MONITOR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_CLIPBOARD_MONITOR, NautilusClipboardMonitorClass))
+#define NAUTILUS_IS_CLIPBOARD_MONITOR(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_CLIPBOARD_MONITOR))
+#define NAUTILUS_IS_CLIPBOARD_MONITOR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_CLIPBOARD_MONITOR))
+#define NAUTILUS_CLIPBOARD_MONITOR_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_CLIPBOARD_MONITOR, NautilusClipboardMonitorClass))
+
+typedef struct NautilusClipboardMonitorDetails NautilusClipboardMonitorDetails;
+typedef struct NautilusClipboardInfo NautilusClipboardInfo;
+
+typedef struct {
+ GObject parent_slot;
+
+ NautilusClipboardMonitorDetails *details;
+} NautilusClipboardMonitor;
+
+typedef struct {
+ GObjectClass parent_slot;
+
+ void (* clipboard_changed) (NautilusClipboardMonitor *monitor);
+ void (* clipboard_info) (NautilusClipboardMonitor *monitor,
+ NautilusClipboardInfo *info);
+} NautilusClipboardMonitorClass;
+
+struct NautilusClipboardInfo {
+ GList *files;
+ gboolean cut;
+};
+
+GType nautilus_clipboard_monitor_get_type (void);
+
+NautilusClipboardMonitor * nautilus_clipboard_monitor_get (void);
+void nautilus_clipboard_monitor_set_clipboard_info (NautilusClipboardMonitor *monitor,
+ NautilusClipboardInfo *info);
+NautilusClipboardInfo * nautilus_clipboard_monitor_get_clipboard_info (NautilusClipboardMonitor *monitor);
+gboolean nautilus_clipboard_monitor_is_cut (NautilusClipboardMonitor *monitor);
+void nautilus_clipboard_monitor_emit_changed (void);
+
+void nautilus_clear_clipboard_callback (GtkClipboard *clipboard,
+ gpointer user_data);
+void nautilus_get_clipboard_callback (GtkClipboard *clipboard,
+ GtkSelectionData *selection_data,
+ guint info,
+ gpointer user_data);
+
+
+
+#endif /* NAUTILUS_CLIPBOARD_MONITOR_H */
+
diff --git a/src/nautilus-clipboard.c b/src/nautilus-clipboard.c
new file mode 100644
index 000000000..36e68002d
--- /dev/null
+++ b/src/nautilus-clipboard.c
@@ -0,0 +1,133 @@
+
+/* 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.
+ *
+ * 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 <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <string.h>
+
+static GList *
+convert_lines_to_str_list (char **lines, gboolean *cut)
+{
+ int i;
+ GList *result;
+
+ if (cut) {
+ *cut = FALSE;
+ }
+
+ if (lines[0] == NULL) {
+ return NULL;
+ }
+
+ if (strcmp (lines[0], "cut") == 0) {
+ if (cut) {
+ *cut = TRUE;
+ }
+ } else if (strcmp (lines[0], "copy") != 0) {
+ return NULL;
+ }
+
+ result = NULL;
+ for (i = 1; lines[i] != NULL; i++) {
+ result = g_list_prepend (result, g_strdup (lines[i]));
+ }
+ return g_list_reverse (result);
+}
+
+GList*
+nautilus_clipboard_get_uri_list_from_selection_data (GtkSelectionData *selection_data,
+ gboolean *cut,
+ GdkAtom copied_files_atom)
+{
+ GList *items;
+ char **lines;
+
+ if (gtk_selection_data_get_data_type (selection_data) != copied_files_atom
+ || gtk_selection_data_get_length (selection_data) <= 0) {
+ items = NULL;
+ } else {
+ gchar *data;
+ /* 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.
+ */
+ data = (gchar *) gtk_selection_data_get_data (selection_data);
+ data[gtk_selection_data_get_length (selection_data)] = '\0';
+ lines = g_strsplit (data, "\n", 0);
+ items = convert_lines_to_str_list (lines, cut);
+ g_strfreev (lines);
+ }
+
+ 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,
+ GdkAtom copied_files_atom)
+{
+ GtkSelectionData *data;
+ GList *clipboard_item_uris, *l;
+ gboolean collision;
+
+ collision = FALSE;
+ data = gtk_clipboard_wait_for_contents (nautilus_clipboard_get (widget),
+ copied_files_atom);
+ if (data == NULL) {
+ return;
+ }
+
+ clipboard_item_uris = nautilus_clipboard_get_uri_list_from_selection_data (data, NULL,
+ copied_files_atom);
+
+ for (l = (GList *) item_uris; l; l = l->next) {
+ if (g_list_find_custom ((GList *) 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);
+ }
+}
diff --git a/src/nautilus-clipboard.h b/src/nautilus-clipboard.h
new file mode 100644
index 000000000..1452b0f0d
--- /dev/null
+++ b/src/nautilus-clipboard.h
@@ -0,0 +1,37 @@
+
+/* 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>
+ */
+
+#ifndef NAUTILUS_CLIPBOARD_H
+#define NAUTILUS_CLIPBOARD_H
+
+#include <gtk/gtk.h>
+
+void nautilus_clipboard_clear_if_colliding_uris (GtkWidget *widget,
+ const GList *item_uris,
+ GdkAtom copied_files_atom);
+GtkClipboard* nautilus_clipboard_get (GtkWidget *widget);
+GList* nautilus_clipboard_get_uri_list_from_selection_data
+ (GtkSelectionData *selection_data,
+ gboolean *cut,
+ GdkAtom copied_files_atom);
+
+#endif /* NAUTILUS_CLIPBOARD_H */
diff --git a/src/nautilus-column-chooser.c b/src/nautilus-column-chooser.c
new file mode 100644
index 000000000..f6e3d65d6
--- /dev/null
+++ b/src/nautilus-column-chooser.c
@@ -0,0 +1,682 @@
+
+/* 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-column-utilities.h"
+
+struct _NautilusColumnChooserDetails
+{
+ 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->details->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,
+ G_STRUCT_OFFSET (NautilusColumnChooserClass,
+ changed),
+ 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,
+ G_STRUCT_OFFSET (NautilusColumnChooserClass,
+ use_default),
+ 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));
+
+ g_type_class_add_private (chooser_class, sizeof (NautilusColumnChooserDetails));
+}
+
+static void
+update_buttons (NautilusColumnChooser *chooser)
+{
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+
+ selection = gtk_tree_view_get_selection (chooser->details->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->details->store),
+ &iter,
+ COLUMN_VISIBLE, &visible,
+ -1);
+
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (chooser->details->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->details->store),
+ &iter);
+
+ gtk_widget_set_sensitive (chooser->details->move_up_button,
+ !top);
+ gtk_widget_set_sensitive (chooser->details->move_down_button,
+ !bottom);
+ } else {
+ gtk_widget_set_sensitive (chooser->details->move_up_button,
+ FALSE);
+ gtk_widget_set_sensitive (chooser->details->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->details->store),
+ &iter, path);
+ gtk_tree_model_get (GTK_TREE_MODEL (chooser->details->store),
+ &iter, COLUMN_VISIBLE, &visible, -1);
+ gtk_list_store_set (chooser->details->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->details->view = GTK_TREE_VIEW (view);
+ chooser->details->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->details->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->details->view);
+
+ if (gtk_tree_selection_get_selected (selection, NULL, &iter)) {
+ GtkTreePath *path;
+ GtkTreeIter prev;
+
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (chooser->details->store), &iter);
+ gtk_tree_path_prev (path);
+ if (gtk_tree_model_get_iter (GTK_TREE_MODEL (chooser->details->store), &prev, path)) {
+ gtk_list_store_move_before (chooser->details->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->details->view);
+
+ if (gtk_tree_selection_get_selected (selection, NULL, &iter)) {
+ GtkTreeIter next;
+
+ next = iter;
+
+ if (gtk_tree_model_iter_next (GTK_TREE_MODEL (chooser->details->store), &next)) {
+ gtk_list_store_move_after (chooser->details->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->details->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->details->move_up_button = gtk_button_new_from_icon_name ("go-up-symbolic",
+ GTK_ICON_SIZE_SMALL_TOOLBAR);
+ g_signal_connect (chooser->details->move_up_button,
+ "clicked", G_CALLBACK (move_up_clicked_callback),
+ chooser);
+ gtk_widget_set_sensitive (chooser->details->move_up_button, FALSE);
+ gtk_container_add (GTK_CONTAINER (box), chooser->details->move_up_button);
+
+ chooser->details->move_down_button = gtk_button_new_from_icon_name ("go-down-symbolic",
+ GTK_ICON_SIZE_SMALL_TOOLBAR);
+ g_signal_connect (chooser->details->move_down_button,
+ "clicked", G_CALLBACK (move_down_clicked_callback),
+ chooser);
+ gtk_widget_set_sensitive (chooser->details->move_down_button, FALSE);
+ gtk_container_add (GTK_CONTAINER (box), chooser->details->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->details->use_default_button = gtk_button_new_with_mnemonic (_("Reset to De_fault"));
+ gtk_widget_set_tooltip_text (chooser->details->use_default_button,
+ _("Replace the current List Columns settings with the default settings"));
+ g_signal_connect (chooser->details->use_default_button,
+ "clicked", G_CALLBACK (use_default_clicked_callback),
+ chooser);
+ gtk_container_add (GTK_CONTAINER (box), chooser->details->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->details->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->details->store, &iter);
+ gtk_list_store_set (chooser->details->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->details->store, "row-deleted",
+ G_CALLBACK (row_deleted_callback), chooser);
+}
+
+static void
+nautilus_column_chooser_init (NautilusColumnChooser *chooser)
+{
+ chooser->details = G_TYPE_INSTANCE_GET_PRIVATE ((chooser), NAUTILUS_TYPE_COLUMN_CHOOSER, NautilusColumnChooserDetails);
+
+ g_object_set (G_OBJECT (chooser),
+ "homogeneous", FALSE,
+ "spacing", 8,
+ "orientation", GTK_ORIENTATION_HORIZONTAL,
+ NULL);
+
+ chooser->details->main_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ gtk_widget_set_hexpand (chooser->details->main_box, TRUE);
+ gtk_widget_show (chooser->details->main_box);
+ gtk_container_add (GTK_CONTAINER (chooser), chooser->details->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->details->store),
+ &iter)) {
+ do {
+ char *name;
+ gboolean visible;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (chooser->details->store),
+ &iter,
+ COLUMN_NAME, &name,
+ -1);
+
+ visible = (g_hash_table_lookup (visible_columns_hash, name) != NULL);
+
+ gtk_list_store_set (chooser->details->store,
+ &iter,
+ COLUMN_VISIBLE, visible,
+ -1);
+ g_free (name);
+
+ } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (chooser->details->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->details->store),
+ &iter)) {
+ do {
+ char *name;
+ gboolean visible;
+ gtk_tree_model_get (GTK_TREE_MODEL (chooser->details->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->details->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->details->store),
+ iter)) {
+ do {
+ char *name;
+
+
+ gtk_tree_model_get (GTK_TREE_MODEL (chooser->details->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->details->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->details->file);
+ columns = nautilus_sort_columns (columns, column_order);
+
+ g_signal_handlers_block_by_func (chooser->details->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->details->store),
+ &before, path);
+ gtk_list_store_move_after (chooser->details->store,
+ &iter, &before);
+ gtk_tree_path_next (path);
+
+ } else {
+ gtk_list_store_move_after (chooser->details->store,
+ &iter, NULL);
+ }
+ }
+ }
+ gtk_tree_path_free (path);
+ g_signal_handlers_unblock_by_func (chooser->details->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 000000000..371379e7c
--- /dev/null
+++ b/src/nautilus-column-chooser.h
@@ -0,0 +1,65 @@
+
+/* 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>
+*/
+
+#ifndef NAUTILUS_COLUMN_CHOOSER_H
+#define NAUTILUS_COLUMN_CHOOSER_H
+
+#include <gtk/gtk.h>
+#include "nautilus-file.h"
+
+#define NAUTILUS_TYPE_COLUMN_CHOOSER nautilus_column_chooser_get_type()
+#define NAUTILUS_COLUMN_CHOOSER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_COLUMN_CHOOSER, NautilusColumnChooser))
+#define NAUTILUS_COLUMN_CHOOSER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_COLUMN_CHOOSER, NautilusColumnChooserClass))
+#define NAUTILUS_IS_COLUMN_CHOOSER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_COLUMN_CHOOSER))
+#define NAUTILUS_IS_COLUMN_CHOOSER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_COLUMN_CHOOSER))
+#define NAUTILUS_COLUMN_CHOOSER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_COLUMN_CHOOSER, NautilusColumnChooserClass))
+
+typedef struct _NautilusColumnChooserDetails NautilusColumnChooserDetails;
+
+typedef struct {
+ GtkBox parent;
+
+ NautilusColumnChooserDetails *details;
+} NautilusColumnChooser;
+
+typedef struct {
+ GtkBoxClass parent_slot;
+
+ void (*changed) (NautilusColumnChooser *chooser);
+ void (*use_default) (NautilusColumnChooser *chooser);
+} NautilusColumnChooserClass;
+
+GType nautilus_column_chooser_get_type (void);
+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);
+
+#endif /* NAUTILUS_COLUMN_CHOOSER_H */
diff --git a/src/nautilus-column-utilities.c b/src/nautilus-column-utilities.c
new file mode 100644
index 000000000..0a10229ca
--- /dev/null
+++ b/src/nautilus-column-utilities.c
@@ -0,0 +1,357 @@
+
+/* 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 <libnautilus-extension/nautilus-column-provider.h>
+#include "nautilus-module.h"
+
+static const char *default_column_order[] = {
+ "name",
+ "size",
+ "type",
+ "owner",
+ "group",
+ "permissions",
+ "mime_type",
+ "where",
+ "date_modified_with_time",
+ "date_modified",
+ "date_accessed",
+ 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", "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", "mime_type",
+ "attribute", "mime_type",
+ "label", _("MIME Type"),
+ "description", _("The MIME type 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));
+
+ 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 000000000..83e392812
--- /dev/null
+++ b/src/nautilus-column-utilities.h
@@ -0,0 +1,39 @@
+
+/* 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>
+*/
+
+#ifndef NAUTILUS_COLUMN_UTILITIES_H
+#define NAUTILUS_COLUMN_UTILITIES_H
+
+#include <libnautilus-extension/nautilus-column.h>
+#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);
+
+
+#endif /* NAUTILUS_COLUMN_UTILITIES_H */
diff --git a/src/nautilus-dbus-manager.c b/src/nautilus-dbus-manager.c
index fa379a982..847afb577 100644
--- a/src/nautilus-dbus-manager.c
+++ b/src/nautilus-dbus-manager.c
@@ -25,10 +25,10 @@
#include "nautilus-dbus-manager.h"
#include "nautilus-generated.h"
-#include <libnautilus-private/nautilus-file-operations.h>
+#include "nautilus-file-operations.h"
#define DEBUG_FLAG NAUTILUS_DEBUG_DBUS
-#include <libnautilus-private/nautilus-debug.h>
+#include "nautilus-debug.h"
#include <gio/gio.h>
diff --git a/src/nautilus-debug.c b/src/nautilus-debug.c
new file mode 100644
index 000000000..55f59913b
--- /dev/null
+++ b/src/nautilus-debug.c
@@ -0,0 +1,163 @@
+/*
+ * 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 },
+ { "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 },
+ { 0, }
+};
+
+static void
+nautilus_debug_set_flags_from_env ()
+{
+ 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);
+}
+
+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);
+}
+
+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 000000000..8c9eb8859
--- /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.
+ */
+
+#ifndef __NAUTILUS_DEBUG_H__
+#define __NAUTILUS_DEBUG_H__
+
+#include <config.h>
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+ NAUTILUS_DEBUG_APPLICATION = 1 << 1,
+ NAUTILUS_DEBUG_BOOKMARKS = 1 << 2,
+ NAUTILUS_DEBUG_DBUS = 1 << 3,
+ NAUTILUS_DEBUG_DIRECTORY_VIEW = 1 << 4,
+ NAUTILUS_DEBUG_FILE = 1 << 5,
+ NAUTILUS_DEBUG_CANVAS_CONTAINER = 1 << 6,
+ NAUTILUS_DEBUG_CANVAS_VIEW = 1 << 7,
+ NAUTILUS_DEBUG_LIST_VIEW = 1 << 8,
+ NAUTILUS_DEBUG_MIME = 1 << 9,
+ NAUTILUS_DEBUG_PLACES = 1 << 10,
+ NAUTILUS_DEBUG_PREVIEWER = 1 << 11,
+ NAUTILUS_DEBUG_SMCLIENT = 1 << 12,
+ NAUTILUS_DEBUG_WINDOW = 1 << 13,
+ NAUTILUS_DEBUG_UNDO = 1 << 14,
+ NAUTILUS_DEBUG_SEARCH = 1 << 15,
+ NAUTILUS_DEBUG_SEARCH_HIT = 1 << 16,
+} 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
+
+#endif /* __NAUTILUS_DEBUG_H__ */
diff --git a/src/nautilus-default-file-icon.c b/src/nautilus-default-file-icon.c
new file mode 100644
index 000000000..cceaf0deb
--- /dev/null
+++ b/src/nautilus-default-file-icon.c
@@ -0,0 +1,534 @@
+/*
+ Default file icon used by the 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: Darin Adler <darin@bentspoon.com>
+*/
+
+#include <config.h>
+#include "nautilus-default-file-icon.h"
+
+const int nautilus_default_file_icon_width = 48;
+const int nautilus_default_file_icon_height = 48;
+const unsigned char nautilus_default_file_icon[] =
+ /* This is from text-x-preview.svg in the gnome icon theme */
+ "\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\243\255\243\31\252\254\245\307\246\251\243\374\246\251\243\377\246"
+ "\251\243\377\246\251\242\377\246\250\242\377\246\250\242\377\246\250"
+ "\242\377\245\250\242\377\245\250\242\377\245\250\242\377\245\250\241"
+ "\377\245\247\241\377\245\247\241\377\245\247\241\377\244\247\241\377"
+ "\244\247\241\377\244\247\241\377\244\247\240\377\244\246\240\377\244"
+ "\246\240\377\244\246\240\377\243\246\240\377\243\246\240\377\243\246"
+ "\237\377\244\247\240\371\245\251\242\333\245\247\242\216\246\246\233"
+ "\27\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\252\254\246\277\347\351"
+ "\346\376\377\377\376\377\377\377\377\377\377\377\377\377\377\377\377"
+ "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
+ "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
+ "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
+ "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
+ "\377\377\377\377\377\374\374\374\377\362\363\361\377\353\355\352\377"
+ "\344\345\342\377\276\300\272\371\244\245\240\366\244\247\237h\377\377"
+ "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377"
+ "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377"
+ "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377"
+ "\377\0\377\377\377\0\246\251\244\373\376\376\376\377\354\355\352\377"
+ "\352\354\350\377\351\353\347\377\351\353\347\377\350\352\346\377\350"
+ "\351\345\377\347\351\344\377\346\350\344\377\345\350\343\377\344\347"
+ "\342\377\344\346\341\377\343\346\340\377\342\345\340\377\341\344\337"
+ "\377\341\343\336\377\340\342\335\377\337\342\334\377\337\341\333\377"
+ "\336\340\332\377\335\337\332\377\334\337\331\377\333\336\330\377\332"
+ "\335\327\377\331\334\326\377\331\334\325\377\333\334\327\377\362\363"
+ "\361\377\333\335\331\377\250\253\245\364\245\246\240\236\200\200\200"
+ "\2\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\245\247\241\377\377\377\377\377\352\354\350\377\351\353\347\377\351"
+ "\353\347\377\350\352\346\377\350\352\346\377\347\351\345\377\347\350"
+ "\344\377\346\351\344\377\345\350\343\377\344\347\342\377\344\346\342"
+ "\377\343\346\341\377\342\345\340\377\341\344\337\377\341\344\336\377"
+ "\340\343\335\377\340\342\335\377\337\341\334\377\336\341\333\377\335"
+ "\340\332\377\334\337\331\377\334\336\330\377\333\336\327\377\332\335"
+ "\326\377\331\334\325\377\326\331\322\377\326\330\324\377\372\372\371"
+ "\377\333\336\331\377\253\257\251\364\241\246\240\251\377\377\377\1\377"
+ "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377"
+ "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377"
+ "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\244\247\240\377"
+ "\377\377\377\377\351\353\347\377\351\353\347\377\350\353\347\377\350"
+ "\352\346\377\347\352\345\377\347\351\345\377\347\350\344\377\346\350"
+ "\343\377\345\347\343\377\344\347\343\377\344\346\342\377\343\346\341"
+ "\377\342\345\340\377\342\344\337\377\341\344\336\377\341\343\336\377"
+ "\340\342\335\377\337\342\334\377\337\341\333\377\336\340\332\377\335"
+ "\340\332\377\334\337\331\377\333\336\330\377\333\335\327\377\332\335"
+ "\326\377\330\333\324\377\312\314\307\377\374\375\374\377\363\364\362"
+ "\377\325\330\322\377\253\255\250\365\242\244\235\222\377\377\377\0\377"
+ "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377"
+ "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377"
+ "\377\377\0\377\377\377\0\377\377\377\0\243\246\240\377\377\377\377\377"
+ "\351\352\346\377\350\352\346\377\350\352\346\377\347\352\346\377\347"
+ "\351\345\377\347\351\344\377\346\350\344\377\346\350\343\377\345\347"
+ "\342\377\344\346\342\377\344\346\342\377\343\346\341\377\342\345\340"
+ "\377\342\345\337\377\342\344\337\377\341\343\336\377\340\343\335\377"
+ "\340\342\334\377\337\341\333\377\336\341\333\377\335\340\332\377\335"
+ "\337\331\377\334\337\330\377\333\336\327\377\332\335\326\377\332\335"
+ "\326\377\314\317\310\377\372\372\372\377\370\370\370\377\346\351\345"
+ "\377\324\330\321\377\245\247\241\364\241\244\235Q\377\377\377\0\377\377"
+ "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377"
+ "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377"
+ "\377\0\377\377\377\0\243\245\237\377\377\377\377\377\350\352\346\377"
+ "\350\351\345\377\347\351\345\377\347\351\345\377\347\351\344\377\346"
+ "\350\344\377\346\350\343\377\345\347\343\377\345\347\342\377\344\346"
+ "\342\377\344\346\341\377\343\345\341\377\342\345\340\377\342\345\337"
+ "\377\341\344\337\377\340\344\336\377\341\343\335\377\340\342\334\377"
+ "\337\342\334\377\336\341\333\377\336\340\332\377\335\340\331\377\334"
+ "\337\331\377\334\336\330\377\333\336\327\377\332\335\326\377\320\323"
+ "\314\377\305\307\301\377\271\274\266\377\262\265\257\377\300\303\276"
+ "\377\314\317\311\377\240\244\234\355\222\222\222\7\377\377\377\0\377"
+ "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377"
+ "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377"
+ "\377\377\0\242\245\237\377\377\377\377\377\347\351\345\377\347\351\344"
+ "\377\347\350\345\377\346\350\344\377\346\350\344\377\346\350\343\377"
+ "\346\350\343\377\345\347\342\377\345\347\342\377\344\346\342\377\344"
+ "\346\341\377\343\345\341\377\342\345\340\377\342\345\337\377\341\344"
+ "\337\377\341\344\336\377\341\343\335\377\340\343\334\377\337\342\334"
+ "\377\337\341\333\377\336\341\332\377\335\340\332\377\335\340\331\377"
+ "\334\337\330\377\333\336\327\377\333\336\327\377\322\325\317\377\307"
+ "\312\304\377\274\300\271\377\261\264\257\377\246\250\244\377\302\304"
+ "\300\377\260\263\255\363\237\242\235h\377\377\377\0\377\377\377\0\377"
+ "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377"
+ "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\241"
+ "\244\236\377\377\377\377\377\347\351\344\377\346\350\344\377\346\350"
+ "\344\377\346\350\344\377\345\350\343\377\346\350\343\377\345\347\342"
+ "\377\345\347\343\377\344\347\342\377\344\346\342\377\343\346\341\377"
+ "\343\345\341\377\342\345\340\377\342\344\337\377\342\344\337\377\341"
+ "\343\336\377\341\343\335\377\340\343\335\377\340\342\334\377\337\342"
+ "\333\377\336\341\333\377\336\341\332\377\335\340\331\377\335\340\331"
+ "\377\334\337\330\377\334\337\330\377\334\337\330\377\333\335\327\377"
+ "\331\333\325\377\322\324\317\377\306\310\303\377\273\276\271\377\322"
+ "\325\320\377\242\244\236\312\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\241\243\235"
+ "\377\377\377\377\377\346\350\343\377\346\350\344\377\346\350\343\377"
+ "\345\347\343\377\345\347\343\377\345\347\342\377\345\347\342\377\345"
+ "\347\342\377\344\347\342\377\344\346\341\377\343\346\341\377\343\345"
+ "\340\377\342\345\340\377\342\344\337\377\341\344\337\377\341\343\336"
+ "\377\341\343\335\377\340\342\335\377\340\342\334\377\337\341\334\377"
+ "\337\341\334\377\336\341\333\377\336\340\333\377\335\340\332\377\335"
+ "\337\331\377\335\337\331\377\335\337\331\377\334\337\331\377\334\337"
+ "\331\377\334\337\331\377\334\337\331\377\336\341\333\377\350\352\347"
+ "\377\236\240\232\370\377\377\377\0\377\377\377\0\377\377\377\0\377\377"
+ "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377"
+ "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\240\242\234\377\377"
+ "\377\377\377\346\347\343\377\345\350\343\377\345\347\343\377\345\347"
+ "\343\377\345\347\342\377\344\347\342\377\345\346\342\377\344\347\342"
+ "\377\344\346\342\377\344\346\341\377\343\346\341\377\343\345\340\377"
+ "\342\345\340\377\342\344\337\377\341\344\337\377\342\343\337\377\341"
+ "\343\336\377\341\342\336\377\340\342\335\377\340\341\335\377\337\341"
+ "\334\377\336\340\333\377\336\340\333\377\336\340\333\377\336\340\333"
+ "\377\336\340\333\377\336\340\333\377\336\340\333\377\335\340\332\377"
+ "\335\340\332\377\335\340\332\377\337\342\334\377\363\364\362\377\233"
+ "\235\230\377\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0"
+ "\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\237\242\234\377\377\377"
+ "\377\377\345\347\342\377\345\347\342\377\345\347\343\377\345\347\342"
+ "\377\344\347\342\377\344\346\342\377\345\346\342\377\344\346\342\377"
+ "\344\346\341\377\344\346\341\377\343\346\341\377\343\345\340\377\342"
+ "\345\340\377\342\344\337\377\341\344\337\377\342\344\337\377\341\343"
+ "\336\377\341\343\336\377\340\342\335\377\340\342\335\377\337\341\334"
+ "\377\337\341\334\377\337\341\334\377\337\341\334\377\337\341\334\377"
+ "\337\341\334\377\337\341\334\377\337\341\334\377\337\341\334\377\336"
+ "\341\333\377\336\341\333\377\340\343\335\377\373\373\372\377\232\235"
+ "\227\377\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377"
+ "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377"
+ "\377\377\0\377\377\377\0\377\377\377\0\237\241\233\377\377\377\377\377"
+ "\344\347\342\377\345\346\342\377\345\347\342\377\344\347\342\377\344"
+ "\347\342\377\344\346\341\377\344\346\342\377\344\346\342\377\344\345"
+ "\341\377\343\346\341\377\343\346\341\377\343\345\340\377\342\345\340"
+ "\377\342\345\337\377\341\344\340\377\341\344\337\377\341\343\337\377"
+ "\341\343\336\377\340\342\336\377\340\342\335\377\340\342\335\377\340"
+ "\342\335\377\340\342\335\377\340\342\335\377\340\342\335\377\340\342"
+ "\335\377\340\342\335\377\337\342\335\377\337\342\335\377\337\342\334"
+ "\377\337\342\334\377\341\344\336\377\375\375\375\377\232\234\226\377"
+ "\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\236\240\233\377\377\377\377\377\344\346"
+ "\341\377\345\346\342\377\344\346\342\377\344\347\342\377\344\346\341"
+ "\377\344\346\341\377\343\346\342\377\344\346\341\377\344\346\341\377"
+ "\343\345\341\377\343\345\340\377\343\345\340\377\342\345\340\377\342"
+ "\345\337\377\341\344\340\377\341\344\337\377\342\344\337\377\341\343"
+ "\336\377\341\343\336\377\341\343\336\377\341\343\336\377\341\343\336"
+ "\377\341\343\336\377\341\343\336\377\341\343\336\377\341\343\336\377"
+ "\341\343\336\377\341\343\336\377\340\343\336\377\340\343\336\377\340"
+ "\343\335\377\342\345\337\377\375\375\375\377\231\233\226\377\377\377"
+ "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377"
+ "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377"
+ "\377\0\377\377\377\0\235\240\232\377\377\377\377\377\344\346\341\377"
+ "\343\346\341\377\344\346\342\377\344\346\341\377\344\346\341\377\344"
+ "\346\341\377\343\346\341\377\344\346\341\377\344\345\341\377\343\345"
+ "\341\377\343\345\340\377\343\345\340\377\342\345\341\377\342\345\340"
+ "\377\342\345\340\377\342\344\337\377\342\344\337\377\342\344\337\377"
+ "\342\344\337\377\342\344\337\377\342\344\337\377\342\344\337\377\342"
+ "\344\337\377\342\344\337\377\342\344\337\377\342\344\337\377\342\344"
+ "\337\377\342\344\337\377\341\344\337\377\341\344\337\377\341\344\337"
+ "\377\341\343\336\377\375\375\375\377\230\233\225\377\377\377\377\0\377"
+ "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377"
+ "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377"
+ "\377\377\0\235\237\231\377\377\377\377\377\343\345\341\377\343\346\341"
+ "\377\344\346\341\377\344\346\341\377\344\346\341\377\343\345\341\377"
+ "\343\346\341\377\344\346\341\377\344\346\341\377\343\345\341\377\343"
+ "\345\340\377\343\345\340\377\343\345\341\377\342\345\340\377\342\345"
+ "\340\377\342\345\340\377\342\345\340\377\343\345\340\377\343\345\340"
+ "\377\343\345\340\377\343\345\340\377\343\345\340\377\343\345\340\377"
+ "\343\345\340\377\343\345\340\377\343\345\340\377\343\345\340\377\342"
+ "\345\340\377\342\345\340\377\342\345\340\377\342\344\340\377\342\344"
+ "\337\377\377\377\377\377\230\232\224\377\377\377\377\0\377\377\377\0"
+ "\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\234\236\231\377\377\377\377\377\343\345\341\377\343\345\340\377\343"
+ "\346\341\377\344\346\341\377\344\345\341\377\343\345\341\377\343\345"
+ "\341\377\343\346\341\377\344\346\341\377\344\346\341\377\343\345\340"
+ "\377\343\345\340\377\343\345\341\377\343\345\341\377\343\346\341\377"
+ "\343\346\341\377\344\346\341\377\344\346\341\377\344\346\341\377\344"
+ "\346\341\377\344\346\341\377\344\346\341\377\344\346\341\377\344\346"
+ "\341\377\344\346\341\377\344\346\341\377\344\346\341\377\343\346\341"
+ "\377\343\346\341\377\343\345\341\377\343\345\341\377\343\345\341\377"
+ "\377\377\377\377\227\231\224\377\377\377\377\0\377\377\377\0\377\377"
+ "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377"
+ "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\233\236"
+ "\230\377\377\377\377\377\343\345\340\377\343\345\340\377\343\346\341"
+ "\377\343\346\341\377\344\346\341\377\344\346\341\377\343\345\341\377"
+ "\343\345\341\377\344\346\341\377\344\346\341\377\344\346\341\377\344"
+ "\346\342\377\344\346\342\377\344\346\342\377\344\346\342\377\344\346"
+ "\342\377\345\346\342\377\345\346\342\377\345\347\342\377\345\347\342"
+ "\377\345\347\342\377\345\347\342\377\345\347\342\377\345\347\342\377"
+ "\345\347\342\377\345\347\342\377\345\346\342\377\345\346\342\377\344"
+ "\346\342\377\344\346\342\377\344\346\342\377\344\346\342\377\377\377"
+ "\377\377\226\231\223\377\377\377\377\0\377\377\377\0\377\377\377\0\377"
+ "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377"
+ "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\233\235\227\377"
+ "\377\377\377\377\343\345\340\377\343\345\340\377\343\345\341\377\343"
+ "\346\341\377\344\346\341\377\344\346\341\377\344\346\342\377\344\346"
+ "\342\377\345\346\342\377\345\347\342\377\345\347\342\377\345\347\343"
+ "\377\345\347\343\377\345\347\343\377\345\347\343\377\346\347\343\377"
+ "\346\347\343\377\346\347\343\377\346\347\343\377\346\347\343\377\346"
+ "\350\343\377\346\350\343\377\346\350\343\377\346\350\343\377\346\347"
+ "\343\377\346\347\343\377\346\347\343\377\346\347\343\377\345\347\343"
+ "\377\345\347\343\377\345\347\343\377\345\347\343\377\377\377\377\377"
+ "\226\230\222\377\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\232\234\227\377\377\377"
+ "\377\377\343\346\341\377\344\346\341\377\344\346\342\377\344\346\342"
+ "\377\345\346\342\377\345\347\342\377\345\347\343\377\345\347\343\377"
+ "\345\347\343\377\346\347\343\377\346\350\343\377\346\350\344\377\346"
+ "\350\344\377\346\350\344\377\346\350\344\377\346\350\344\377\347\350"
+ "\344\377\347\350\344\377\347\350\344\377\347\350\344\377\347\350\344"
+ "\377\347\350\344\377\347\350\344\377\347\350\344\377\347\350\344\377"
+ "\347\350\344\377\347\350\344\377\346\350\344\377\346\350\344\377\346"
+ "\350\344\377\346\350\344\377\346\350\344\377\377\377\377\377\225\227"
+ "\222\377\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377"
+ "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377"
+ "\377\377\0\377\377\377\0\377\377\377\0\231\234\226\377\377\377\377\377"
+ "\344\346\342\377\345\346\342\377\345\347\342\377\345\347\343\377\345"
+ "\347\343\377\346\347\343\377\346\350\344\377\346\350\344\377\346\350"
+ "\344\377\346\350\344\377\347\350\344\377\347\351\345\377\347\351\345"
+ "\377\347\351\345\377\347\351\345\377\347\351\345\377\350\351\345\377"
+ "\350\351\345\377\350\351\346\377\350\351\346\377\350\351\346\377\350"
+ "\351\346\377\350\351\346\377\350\351\346\377\350\351\346\377\350\351"
+ "\346\377\350\351\345\377\347\351\345\377\347\351\345\377\347\351\345"
+ "\377\347\351\345\377\347\351\345\377\377\377\377\377\224\227\221\377"
+ "\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\231\233\225\377\377\377\377\377\345\347"
+ "\343\377\345\347\343\377\346\347\343\377\346\350\344\377\346\350\344"
+ "\377\346\350\344\377\347\350\344\377\347\351\345\377\347\351\345\377"
+ "\347\351\345\377\350\351\346\377\350\351\346\377\350\352\346\377\350"
+ "\352\346\377\350\352\346\377\350\352\346\377\351\352\346\377\351\352"
+ "\347\377\351\352\347\377\351\352\347\377\351\352\347\377\351\352\347"
+ "\377\351\352\347\377\351\352\347\377\351\352\347\377\351\352\347\377"
+ "\351\352\346\377\351\352\346\377\350\352\346\377\350\352\346\377\350"
+ "\352\346\377\350\352\346\377\377\377\377\377\224\226\220\377\377\377"
+ "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377"
+ "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377"
+ "\377\0\377\377\377\0\230\232\225\377\377\377\377\377\346\350\344\377"
+ "\346\350\344\377\346\350\344\377\347\350\344\377\347\351\345\377\347"
+ "\351\345\377\350\351\346\377\350\351\346\377\350\352\346\377\350\352"
+ "\346\377\351\352\346\377\351\352\347\377\351\352\347\377\351\352\347"
+ "\377\351\353\347\377\351\353\347\377\351\353\347\377\352\353\350\377"
+ "\352\353\350\377\352\353\350\377\352\353\350\377\352\353\350\377\352"
+ "\353\350\377\352\353\350\377\352\353\350\377\352\353\350\377\352\353"
+ "\350\377\351\353\347\377\351\353\347\377\351\353\347\377\351\352\347"
+ "\377\351\352\347\377\377\377\377\377\223\225\220\377\377\377\377\0\377"
+ "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377"
+ "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377"
+ "\377\377\0\227\232\224\377\377\377\377\377\347\350\344\377\347\351\345"
+ "\377\347\351\345\377\350\351\345\377\350\351\346\377\350\352\346\377"
+ "\351\352\346\377\351\352\347\377\351\352\347\377\351\353\347\377\351"
+ "\353\347\377\352\353\350\377\352\353\350\377\352\353\350\377\352\353"
+ "\350\377\352\354\350\377\352\354\350\377\353\354\351\377\353\354\351"
+ "\377\353\354\351\377\353\354\351\377\353\354\351\377\353\354\351\377"
+ "\353\354\351\377\353\354\351\377\353\354\351\377\353\354\351\377\352"
+ "\354\350\377\352\354\350\377\352\353\350\377\352\353\350\377\352\353"
+ "\350\377\377\377\377\377\222\224\217\377\377\377\377\0\377\377\377\0"
+ "\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\227\231\223\377\377\377\377\377\347\351\345\377\350\351\346\377\350"
+ "\352\346\377\350\352\346\377\351\352\347\377\351\352\347\377\351\353"
+ "\347\377\352\353\350\377\352\353\350\377\352\353\350\377\352\354\350"
+ "\377\353\354\351\377\353\354\351\377\353\354\351\377\353\354\351\377"
+ "\353\354\351\377\353\355\352\377\354\355\352\377\354\355\352\377\354"
+ "\355\352\377\354\355\352\377\354\355\352\377\354\355\352\377\354\355"
+ "\352\377\354\355\352\377\354\355\352\377\354\355\352\377\353\355\352"
+ "\377\353\354\351\377\353\354\351\377\353\354\351\377\353\354\351\377"
+ "\377\377\377\377\222\224\216\377\377\377\377\0\377\377\377\0\377\377"
+ "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377"
+ "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\226\230"
+ "\223\377\377\377\377\377\350\352\346\377\350\352\346\377\351\352\347"
+ "\377\351\352\347\377\351\353\347\377\352\353\350\377\352\353\350\377"
+ "\352\354\350\377\353\354\351\377\353\354\351\377\353\354\351\377\354"
+ "\355\352\377\354\355\352\377\354\355\352\377\354\355\352\377\354\355"
+ "\352\377\354\356\353\377\354\356\353\377\355\356\353\377\355\356\353"
+ "\377\355\356\353\377\355\356\353\377\355\356\353\377\355\356\353\377"
+ "\355\356\353\377\355\356\353\377\354\356\353\377\354\355\353\377\354"
+ "\355\352\377\354\355\352\377\354\355\352\377\354\355\352\377\377\377"
+ "\377\377\221\223\216\377\377\377\377\0\377\377\377\0\377\377\377\0\377"
+ "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377"
+ "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\225\230\222\377"
+ "\377\377\377\377\351\352\347\377\351\352\347\377\351\353\347\377\352"
+ "\353\350\377\352\353\350\377\352\354\350\377\353\354\351\377\353\354"
+ "\351\377\354\355\352\377\354\355\352\377\354\355\352\377\354\355\353"
+ "\377\355\356\353\377\355\356\353\377\355\356\353\377\355\356\353\377"
+ "\355\356\354\377\356\356\354\377\356\356\354\377\356\357\354\377\356"
+ "\357\354\377\356\357\354\377\356\357\354\377\356\357\354\377\356\357"
+ "\354\377\356\356\354\377\355\356\354\377\355\356\354\377\355\356\353"
+ "\377\355\356\353\377\355\356\353\377\354\356\353\377\377\377\377\377"
+ "\220\222\215\377\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\225\227\221\377\377\377"
+ "\377\377\351\353\347\377\352\353\350\377\352\353\350\377\352\354\350"
+ "\377\353\354\351\377\353\354\351\377\354\355\352\377\354\355\352\377"
+ "\354\355\352\377\354\356\353\377\355\356\353\377\355\356\353\377\355"
+ "\356\354\377\356\357\354\377\356\357\354\377\356\357\354\377\356\357"
+ "\355\377\357\357\355\377\357\357\355\377\357\357\355\377\357\360\355"
+ "\377\357\360\355\377\357\360\355\377\357\360\355\377\357\357\355\377"
+ "\357\357\355\377\356\357\355\377\356\357\355\377\356\357\354\377\356"
+ "\357\354\377\356\356\354\377\355\356\354\377\377\377\377\377\220\222"
+ "\215\377\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377"
+ "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377"
+ "\377\377\0\377\377\377\0\377\377\377\0\224\226\221\377\377\377\377\377"
+ "\352\353\350\377\352\353\350\377\353\354\351\377\353\354\351\377\353"
+ "\355\352\377\354\355\352\377\354\355\352\377\355\356\353\377\355\356"
+ "\353\377\355\356\354\377\356\356\354\377\356\357\354\377\356\357\355"
+ "\377\357\357\355\377\357\360\355\377\357\360\356\377\357\360\356\377"
+ "\357\360\356\377\360\360\356\377\360\360\356\377\360\360\356\377\360"
+ "\360\356\377\360\360\356\377\360\360\356\377\360\360\356\377\357\360"
+ "\356\377\357\360\356\377\357\360\356\377\357\360\355\377\357\357\355"
+ "\377\356\357\355\377\356\357\354\377\377\377\377\377\217\221\214\377"
+ "\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\223\225\220\377\377\377\377\377\352\353"
+ "\350\377\353\354\351\377\353\354\351\377\354\355\352\377\354\355\352"
+ "\377\354\355\353\377\355\356\353\377\355\356\353\377\356\356\354\377"
+ "\356\357\354\377\356\357\355\377\357\357\355\377\357\360\356\377\357"
+ "\360\356\377\360\360\356\377\360\361\356\377\360\361\357\377\360\361"
+ "\357\377\361\361\357\377\361\361\357\377\361\361\357\377\361\361\357"
+ "\377\361\361\357\377\361\361\357\377\361\361\357\377\360\361\357\377"
+ "\360\361\357\377\360\361\357\377\360\360\356\377\357\360\356\377\357"
+ "\360\356\377\357\360\355\377\377\377\377\377\216\220\213\377\377\377"
+ "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377"
+ "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377"
+ "\377\0\377\377\377\0\223\225\217\377\377\377\377\377\353\354\351\377"
+ "\353\354\351\377\354\355\352\377\354\355\352\377\354\355\353\377\355"
+ "\356\353\377\355\356\354\377\356\357\354\377\356\357\354\377\357\357"
+ "\355\377\357\360\355\377\357\360\356\377\360\360\356\377\360\361\357"
+ "\377\360\361\357\377\361\361\357\377\361\362\360\377\361\362\360\377"
+ "\362\362\360\377\362\362\360\377\362\362\360\377\362\362\360\377\362"
+ "\362\360\377\362\362\360\377\362\362\360\377\361\362\360\377\361\362"
+ "\360\377\361\361\357\377\361\361\357\377\360\361\357\377\360\361\356"
+ "\377\357\360\356\377\377\377\377\377\216\220\213\377\377\377\377\0\377"
+ "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377"
+ "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377"
+ "\377\377\0\222\224\217\377\377\377\377\377\353\354\351\377\353\355\352"
+ "\377\354\355\352\377\354\355\352\377\355\356\353\377\355\356\353\377"
+ "\356\357\354\377\356\357\354\377\357\357\355\377\357\360\355\377\357"
+ "\360\356\377\360\361\356\377\360\361\357\377\361\361\357\377\361\362"
+ "\360\377\361\362\360\377\362\362\360\377\362\362\361\377\362\363\361"
+ "\377\363\363\361\377\363\363\362\377\363\363\362\377\363\363\362\377"
+ "\363\363\361\377\362\363\361\377\362\363\361\377\362\362\361\377\362"
+ "\362\360\377\361\362\360\377\361\361\357\377\360\361\357\377\360\361"
+ "\357\377\377\377\377\377\215\217\212\377\377\377\377\0\377\377\377\0"
+ "\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\221\223\216\377\377\377\377\377\353\354\351\377\354\355\352\377\354"
+ "\355\352\377\354\356\353\377\355\356\353\377\355\356\354\377\356\357"
+ "\354\377\356\357\355\377\357\360\355\377\357\360\356\377\360\360\356"
+ "\377\360\361\357\377\361\361\357\377\361\362\360\377\362\362\360\377"
+ "\362\362\361\377\362\363\361\377\363\363\362\377\363\363\362\377\363"
+ "\364\362\377\364\364\363\377\364\364\363\377\364\364\363\377\364\364"
+ "\362\377\363\364\362\377\363\363\362\377\363\363\361\377\362\363\361"
+ "\377\362\362\360\377\361\362\360\377\361\362\360\377\360\361\357\377"
+ "\377\377\377\377\214\216\211\377\377\377\377\0\377\377\377\0\377\377"
+ "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377"
+ "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\221\223"
+ "\215\377\377\377\377\377\353\354\351\377\354\355\352\377\354\355\352"
+ "\377\355\356\353\377\355\356\353\377\356\356\354\377\356\357\354\377"
+ "\357\357\355\377\357\360\356\377\360\360\356\377\360\361\357\377\361"
+ "\361\357\377\361\362\360\377\362\362\360\377\362\362\361\377\362\363"
+ "\361\377\363\363\362\377\363\364\362\377\364\364\363\377\364\364\363"
+ "\377\365\365\363\377\365\365\364\377\365\365\364\377\364\365\363\377"
+ "\364\364\363\377\364\364\362\377\363\363\362\377\363\363\361\377\362"
+ "\363\361\377\362\362\360\377\361\362\360\377\361\361\357\377\377\377"
+ "\377\377\214\216\211\377\377\377\377\0\377\377\377\0\377\377\377\0\377"
+ "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377"
+ "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\220\222\215\377"
+ "\377\377\377\377\353\354\351\377\354\355\352\377\354\355\352\377\355"
+ "\356\353\377\355\356\353\377\356\357\354\377\356\357\354\377\357\357"
+ "\355\377\357\360\356\377\360\360\356\377\360\361\357\377\361\361\357"
+ "\377\361\362\360\377\362\362\360\377\362\362\361\377\362\363\361\377"
+ "\363\363\362\377\363\364\362\377\364\364\363\377\364\365\363\377\365"
+ "\365\364\377\365\365\364\377\365\365\364\377\365\365\364\377\364\364"
+ "\363\377\364\364\363\377\363\363\362\377\363\363\362\377\362\363\361"
+ "\377\362\362\360\377\361\362\360\377\361\361\357\377\377\377\377\377"
+ "\213\215\210\377\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\217\221\214\377\377\377"
+ "\377\377\353\354\351\377\354\355\352\377\354\355\352\377\355\356\353"
+ "\377\355\356\353\377\356\356\354\377\356\357\354\377\357\357\355\377"
+ "\357\360\355\377\357\360\356\377\360\361\356\377\360\361\357\377\361"
+ "\361\357\377\361\362\360\377\362\362\360\377\362\363\361\377\363\363"
+ "\362\377\363\363\362\377\364\364\362\377\364\364\363\377\364\364\363"
+ "\377\364\364\363\377\364\364\363\377\364\364\363\377\364\364\363\377"
+ "\363\364\362\377\363\363\362\377\362\363\361\377\362\362\361\377\362"
+ "\362\360\377\361\362\360\377\361\361\357\377\377\377\377\377\212\214"
+ "\207\377\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377"
+ "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377"
+ "\377\377\0\377\377\377\0\377\377\377\0\217\221\213\377\377\377\377\377"
+ "\353\354\351\377\354\355\352\377\354\355\352\377\354\355\353\377\355"
+ "\356\353\377\355\356\354\377\356\357\354\377\356\357\355\377\357\360"
+ "\355\377\357\360\356\377\360\360\356\377\360\361\357\377\361\361\357"
+ "\377\361\362\360\377\361\362\360\377\362\362\360\377\362\363\361\377"
+ "\362\363\361\377\363\363\362\377\363\363\362\377\363\363\362\377\363"
+ "\364\362\377\363\364\362\377\363\363\362\377\363\363\362\377\363\363"
+ "\361\377\362\363\361\377\362\362\361\377\362\362\360\377\361\362\360"
+ "\377\361\361\357\377\360\361\357\377\377\377\377\377\212\214\207\377"
+ "\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\0\0\0\5\216\220\213\377\377\377\377\377\353\354\351"
+ "\377\353\354\351\377\354\355\352\377\354\355\352\377\355\356\353\377"
+ "\355\356\353\377\355\356\354\377\356\357\354\377\356\357\355\377\357"
+ "\360\355\377\357\360\356\377\360\360\356\377\360\361\357\377\360\361"
+ "\357\377\361\361\357\377\361\362\360\377\361\362\360\377\362\362\360"
+ "\377\362\362\361\377\362\362\361\377\362\363\361\377\362\363\361\377"
+ "\362\363\361\377\362\363\361\377\362\362\361\377\362\362\360\377\362"
+ "\362\360\377\361\362\360\377\361\362\360\377\361\361\357\377\360\361"
+ "\357\377\360\360\356\377\377\377\377\377\211\213\206\377\0\0\0\4\377"
+ "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377"
+ "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0\10\0\0\0\21"
+ "\215\217\212\377\377\377\377\377\352\354\350\377\353\354\351\377\353"
+ "\354\351\377\354\355\352\377\354\355\352\377\355\356\353\377\355\356"
+ "\353\377\355\356\354\377\356\357\354\377\356\357\355\377\357\357\355"
+ "\377\357\360\355\377\357\360\356\377\360\360\356\377\360\361\357\377"
+ "\360\361\357\377\361\361\357\377\361\361\357\377\361\362\360\377\361"
+ "\362\360\377\361\362\360\377\361\362\360\377\361\362\360\377\361\362"
+ "\360\377\361\362\360\377\361\362\360\377\361\361\357\377\360\361\357"
+ "\377\360\361\357\377\360\361\356\377\357\360\356\377\357\360\356\377"
+ "\377\377\377\377\210\212\205\377\0\0\0\20\0\0\0\7\377\377\377\0\377\377"
+ "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377"
+ "\377\0\0\0\0\4\0\0\0\17\0\0\0\37\216\220\212\374\376\376\376\377\353"
+ "\354\351\377\352\354\350\377\353\354\351\377\353\354\351\377\354\355"
+ "\352\377\354\355\352\377\354\356\353\377\355\356\353\377\355\356\354"
+ "\377\356\356\354\377\356\357\354\377\356\357\355\377\357\357\355\377"
+ "\357\360\355\377\357\360\356\377\357\360\356\377\360\360\356\377\360"
+ "\361\356\377\360\361\357\377\360\361\357\377\360\361\357\377\360\361"
+ "\357\377\360\361\357\377\360\361\357\377\360\361\357\377\360\361\357"
+ "\377\360\360\356\377\360\360\356\377\357\360\356\377\357\360\356\377"
+ "\357\360\355\377\360\360\356\377\376\376\376\377\212\214\207\374\0\0"
+ "\0\33\0\0\0\16\0\0\0\2\377\377\377\0\377\377\377\0\377\377\377\0\377"
+ "\377\377\0\377\377\377\0\377\377\377\0\0\0\0\6\0\0\0\22\0\0\0*\206\207"
+ "\203\320\335\335\334\376\376\376\376\377\377\377\377\377\377\377\377"
+ "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
+ "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
+ "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
+ "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
+ "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
+ "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
+ "\377\377\377\377\377\377\377\377\377\377\377\376\376\376\377\334\335"
+ "\332\376\203\205\200\315\0\0\0&\0\0\0\20\0\0\0\4\377\377\377\0\377\377"
+ "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\0\0\0"
+ "\4\0\0\0\17\0\0\0\37//,L\205\206\202\327\213\215\210\375\213\215\210"
+ "\377\213\215\210\377\213\215\210\377\212\214\207\377\212\214\207\377"
+ "\212\214\207\377\212\214\207\377\212\214\207\377\212\214\207\377\212"
+ "\214\207\377\211\213\206\377\211\213\206\377\211\213\206\377\211\213"
+ "\206\377\211\213\206\377\211\213\206\377\211\213\206\377\210\212\205"
+ "\377\210\212\205\377\210\212\205\377\210\212\205\377\210\212\205\377"
+ "\210\212\205\377\210\212\205\377\210\212\205\377\210\212\205\377\210"
+ "\212\205\377\210\212\205\377\210\212\205\377\210\212\205\377\210\212"
+ "\205\375\204\205\200\325-1-I\0\0\0\33\0\0\0\16\0\0\0\2\377\377\377\0"
+ "\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
+ "\0\377\377\377\0\0\0\0\10\0\0\0\21\0\0\0\32\0\0\0%\0\0\0+\0\0\0""2\0"
+ "\0\0""2\0\0\0""2\0\0\0""2\0\0\0""2\0\0\0""2\0\0\0""2\0\0\0""2\0\0\0""2"
+ "\0\0\0""2\0\0\0""2\0\0\0""2\0\0\0""2\0\0\0""2\0\0\0""2\0\0\0""2\0\0\0"
+ "2\0\0\0""2\0\0\0""2\0\0\0""2\0\0\0""2\0\0\0""2\0\0\0""2\0\0\0""2\0\0"
+ "\0""2\0\0\0""2\0\0\0""2\0\0\0""2\0\0\0""2\0\0\0""2\0\0\0*\0\0\0$\0\0"
+ "\0\30\0\0\0\20\0\0\0\7\377\377\377\0\377\377\377\0\377\377\377\0\377"
+ "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377"
+ "\377\377\0\0\0\0\5\0\0\0\13\0\0\0\17\0\0\0\20\0\0\0\21\0\0\0\21\0\0\0"
+ "\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0"
+ "\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21"
+ "\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0"
+ "\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\20\0\0\0\16\0\0\0\12\0"
+ "\0\0\4\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377"
+ "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377"
+ "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377"
+ "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377"
+ "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377"
+ "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377"
+ "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377"
+ "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377"
+ "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377"
+ "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377"
+ "\377\0\377\377\377\0\377\377\377\0\377\377\377\0";
diff --git a/src/nautilus-default-file-icon.h b/src/nautilus-default-file-icon.h
new file mode 100644
index 000000000..6ac08257f
--- /dev/null
+++ b/src/nautilus-default-file-icon.h
@@ -0,0 +1,29 @@
+/*
+ Default file icon used by the 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: Darin Adler <darin@bentspoon.com>
+*/
+
+#ifndef NAUTILUS_DEFAULT_FILE_ICON_H
+#define NAUTILUS_DEFAULT_FILE_ICON_H
+
+extern const int nautilus_default_file_icon_width;
+extern const int nautilus_default_file_icon_height;
+extern const unsigned char nautilus_default_file_icon[];
+
+#endif /* NAUTILUS_DEFAULT_FILE_ICON_H */
diff --git a/src/nautilus-desktop-item-properties.c b/src/nautilus-desktop-item-properties.c
index e98e8e593..1094d7337 100644
--- a/src/nautilus-desktop-item-properties.c
+++ b/src/nautilus-desktop-item-properties.c
@@ -31,8 +31,8 @@
#include <glib/gi18n.h>
#include <libnautilus-extension/nautilus-extension-types.h>
#include <libnautilus-extension/nautilus-file-info.h>
-#include <libnautilus-private/nautilus-file.h>
-#include <libnautilus-private/nautilus-file-attributes.h>
+#include "nautilus-file.h"
+#include "nautilus-file-attributes.h"
#define MAIN_GROUP "Desktop Entry"
diff --git a/src/nautilus-directory-async.c b/src/nautilus-directory-async.c
new file mode 100644
index 000000000..ea97da61f
--- /dev/null
+++ b/src/nautilus-directory-async.c
@@ -0,0 +1,4558 @@
+/*
+ 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 <config.h>
+
+#include "nautilus-directory-notify.h"
+#include "nautilus-directory-private.h"
+#include "nautilus-file-attributes.h"
+#include "nautilus-file-private.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-signaller.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-link.h"
+#include "nautilus-profile.h"
+#include <eel/eel-glib-extensions.h>
+#include <gtk/gtk.h>
+#include <libxml/parser.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+/* turn this on to see messages about each load_directory call: */
+#if 0
+#define DEBUG_LOAD_DIRECTORY
+#endif
+
+/* turn this on to check if async. job calls are balanced */
+#if 0
+#define DEBUG_ASYNC_JOBS
+#endif
+
+/* turn this on to log things starting and stopping */
+#if 0
+#define DEBUG_START_STOP
+#endif
+
+#define DIRECTORY_LOAD_ITEMS_PER_CALLBACK 100
+
+/* Keep async. jobs down to this number for all directories. */
+#define MAX_ASYNC_JOBS 10
+
+struct TopLeftTextReadState {
+ NautilusDirectory *directory;
+ NautilusFile *file;
+ gboolean large;
+ GCancellable *cancellable;
+};
+
+struct LinkInfoReadState {
+ NautilusDirectory *directory;
+ GCancellable *cancellable;
+ NautilusFile *file;
+};
+
+struct ThumbnailState {
+ NautilusDirectory *directory;
+ GCancellable *cancellable;
+ NautilusFile *file;
+ gboolean trying_original;
+ gboolean tried_original;
+};
+
+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 link_info_done (NautilusDirectory *directory,
+ NautilusFile *file,
+ const char *uri,
+ const char *name,
+ GIcon *icon,
+ gboolean is_launcher,
+ gboolean is_foreign);
+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;
+
+ fail = FALSE;
+ for (i = 0; i < REQUEST_TYPE_LAST; i ++) {
+ counters[i] = 0;
+ }
+ for (l = directory->details->monitor_list; l != NULL; 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
+
+#ifdef DEBUG_START_STOP
+ g_message ("starting %s in %p", job, directory->details->location);
+#endif
+
+ 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
+
+#ifdef DEBUG_START_STOP
+ g_message ("stopping %s in %p", job, directory->details->location);
+#endif
+
+ 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
+link_info_cancel (NautilusDirectory *directory)
+{
+ if (directory->details->link_info_read_state != NULL) {
+ g_cancellable_cancel (directory->details->link_info_read_state->cancellable);
+ directory->details->link_info_read_state->directory = NULL;
+ directory->details->link_info_read_state = NULL;
+ async_job_end (directory, "link info");
+ }
+}
+
+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 GList *
+find_monitor (NautilusDirectory *directory,
+ NautilusFile *file,
+ gconstpointer client)
+{
+ Monitor monitor;
+
+ monitor.client = client;
+ monitor.file = file;
+
+ return g_list_find_custom (directory->details->monitor_list,
+ &monitor,
+ monitor_key_compare);
+}
+
+static void
+remove_monitor_link (NautilusDirectory *directory,
+ GList *link)
+{
+ Monitor *monitor;
+
+ if (link != NULL) {
+ monitor = link->data;
+ request_counter_remove_request (directory->details->monitor_counters,
+ monitor->request);
+ directory->details->monitor_list =
+ g_list_remove_link (directory->details->monitor_list, link);
+ g_free (monitor);
+ g_list_free_1 (link);
+ }
+}
+
+static void
+remove_monitor (NautilusDirectory *directory,
+ NautilusFile *file,
+ gconstpointer client)
+{
+ remove_monitor_link (directory, find_monitor (directory, file, client));
+}
+
+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_LINK_INFO) {
+ REQUEST_SET_TYPE (request, REQUEST_FILE_INFO);
+ REQUEST_SET_TYPE (request, REQUEST_LINK_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_LINK_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);
+ }
+ directory->details->monitor_list =
+ g_list_prepend (directory->details->monitor_list, monitor);
+ request_counter_add_request (directory->details->monitor_counters,
+ monitor->request);
+
+ 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
+ && directory->details->monitor_list == NULL) {
+ 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, **list, *node, *next;
+ Monitor *monitor;
+
+ g_assert (NAUTILUS_IS_DIRECTORY (directory));
+ g_assert (NAUTILUS_IS_FILE (file));
+ g_assert (file->details->directory == directory);
+
+ result = NULL;
+
+ list = &directory->details->monitor_list;
+ for (node = directory->details->monitor_list; node != NULL; node = next) {
+ next = node->next;
+ monitor = node->data;
+
+ if (monitor->file == file) {
+ *list = g_list_remove_link (*list, node);
+ result = g_list_concat (node, result);
+ request_counter_remove_request (directory->details->monitor_counters,
+ monitor->request);
+ }
+ }
+
+ /* 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 **list;
+ 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;
+ request_counter_add_request (directory->details->monitor_counters,
+ monitor->request);
+ }
+
+ list = &directory->details->monitor_list;
+ *list = g_list_concat (*list, (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. */
+ for (node = directory->details->monitor_list; node != NULL; node = next) {
+ next = node->next;
+ monitor = node->data;
+
+ if (monitor->file == file) {
+ /* Client should have removed monitor earlier. */
+ g_warning ("destroyed file still being monitored");
+ remove_monitor_link (directory, node);
+ 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->link_info_read_state != NULL &&
+ directory->details->link_info_read_state->file == file) {
+ directory->details->link_info_read_state->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_link_info (NautilusFile *file)
+{
+ if (file->details->file_info_is_up_to_date &&
+ !file->details->link_info_is_up_to_date) {
+ if (nautilus_file_is_nautilus_link (file)) {
+ return TRUE;
+ } else {
+ link_info_done (file->details->directory, file, NULL, NULL, NULL, FALSE, FALSE);
+ return FALSE;
+ }
+ } else {
+ return FALSE;
+ }
+}
+
+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;
+ }
+ }
+
+ if (REQUEST_WANTS_TYPE (request, REQUEST_LINK_INFO)) {
+ if (has_problem (directory, file, lacks_link_info)) {
+ 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;
+}
+
+gboolean
+nautilus_directory_has_active_request_for_file (NautilusDirectory *directory,
+ NautilusFile *file)
+{
+ GList *node;
+ ReadyCallback *callback;
+ Monitor *monitor;
+
+ 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;
+ }
+ }
+
+ for (node = directory->details->monitor_list;
+ node != NULL; node = node->next) {
+ monitor = node->data;
+ if (monitor->file == file ||
+ monitor->file == 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;
+
+
+#ifdef DEBUG_LOAD_DIRECTORY
+ g_message ("load_directory called to monitor file list of %p", directory->details->location);
+#endif
+
+ 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;
+ }
+ 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,
+ TRUE);
+}
+
+static gboolean
+is_needy (NautilusFile *file,
+ FileCheck check_missing,
+ RequestType request_type_wanted)
+{
+ NautilusDirectory *directory;
+ GList *node;
+ ReadyCallback *callback;
+ Monitor *monitor;
+
+ 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) {
+ for (node = directory->details->monitor_list;
+ node != NULL; node = node->next) {
+ monitor = node->data;
+ if (REQUEST_WANTS_TYPE (monitor->request, request_type_wanted)) {
+ if (monitor_includes_file (monitor, file)) {
+ 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);
+#ifdef DEBUG_LOAD_DIRECTORY
+ {
+ char *uri;
+ uri = g_file_get_uri (location);
+ g_message ("load_directory called to get shallow file count for %s", uri);
+ g_free (uri);
+ }
+#endif
+
+ 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);
+
+#ifdef DEBUG_LOAD_DIRECTORY
+ g_message ("load_directory called to get deep file count for %p", location);
+#endif
+ 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);
+#ifdef DEBUG_LOAD_DIRECTORY
+ {
+ char *uri;
+ uri = g_file_get_uri (location);
+ g_message ("load_directory called to get MIME list of %s", uri);
+ g_free (uri);
+ }
+#endif
+
+ 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 gboolean
+is_link_trusted (NautilusFile *file,
+ gboolean is_launcher)
+{
+ GFile *location;
+ gboolean res;
+
+ if (!is_launcher) {
+ return TRUE;
+ }
+
+ if (nautilus_file_can_execute (file)) {
+ return TRUE;
+ }
+
+ res = FALSE;
+
+ if (nautilus_file_is_local (file)) {
+ location = nautilus_file_get_location (file);
+ res = nautilus_is_in_system_dir (location);
+ g_object_unref (location);
+ }
+
+ return res;
+}
+
+static void
+link_info_done (NautilusDirectory *directory,
+ NautilusFile *file,
+ const char *uri,
+ const char *name,
+ GIcon *icon,
+ gboolean is_launcher,
+ gboolean is_foreign)
+{
+ gboolean is_trusted;
+
+ file->details->link_info_is_up_to_date = TRUE;
+
+ is_trusted = is_link_trusted (file, is_launcher);
+
+ if (is_trusted) {
+ nautilus_file_set_display_name (file, name, name, TRUE);
+ } else {
+ nautilus_file_set_display_name (file, NULL, NULL, TRUE);
+ }
+
+ file->details->got_link_info = TRUE;
+ g_clear_object (&file->details->custom_icon);
+
+ if (uri) {
+ g_free (file->details->activation_uri);
+ file->details->activation_uri = NULL;
+ file->details->got_custom_activation_uri = TRUE;
+ file->details->activation_uri = g_strdup (uri);
+ }
+ if (is_trusted && (icon != NULL)) {
+ file->details->custom_icon = g_object_ref (icon);
+ }
+ file->details->is_launcher = is_launcher;
+ file->details->is_foreign_link = is_foreign;
+ file->details->is_trusted_link = is_trusted;
+
+ nautilus_directory_async_state_changed (directory);
+}
+
+static void
+link_info_stop (NautilusDirectory *directory)
+{
+ NautilusFile *file;
+
+ if (directory->details->link_info_read_state != NULL) {
+ file = directory->details->link_info_read_state->file;
+
+ if (file != NULL) {
+ g_assert (NAUTILUS_IS_FILE (file));
+ g_assert (file->details->directory == directory);
+ if (is_needy (file,
+ lacks_link_info,
+ REQUEST_LINK_INFO)) {
+ return;
+ }
+ }
+
+ /* The link info is not wanted, so stop it. */
+ link_info_cancel (directory);
+ }
+}
+
+static void
+link_info_got_data (NautilusDirectory *directory,
+ NautilusFile *file,
+ gboolean result,
+ goffset bytes_read,
+ char *file_contents)
+{
+ char *link_uri, *uri, *name;
+ GIcon *icon;
+ gboolean is_launcher;
+ gboolean is_foreign;
+
+ nautilus_directory_ref (directory);
+
+ uri = NULL;
+ name = NULL;
+ icon = NULL;
+ is_launcher = FALSE;
+ is_foreign = FALSE;
+
+ /* Handle the case where we read the Nautilus link. */
+ if (result) {
+ link_uri = nautilus_file_get_uri (file);
+ nautilus_link_get_link_info_given_file_contents (file_contents, bytes_read, link_uri,
+ &uri, &name, &icon, &is_launcher, &is_foreign);
+ g_free (link_uri);
+ } else {
+ /* FIXME bugzilla.gnome.org 42433: We should report this error to the user. */
+ }
+
+ nautilus_file_ref (file);
+ link_info_done (directory, file, uri, name, icon, is_launcher, is_foreign);
+ nautilus_file_changed (file);
+ nautilus_file_unref (file);
+
+ g_free (uri);
+ g_free (name);
+
+ if (icon != NULL) {
+ g_object_unref (icon);
+ }
+
+ nautilus_directory_unref (directory);
+}
+
+static void
+link_info_read_state_free (LinkInfoReadState *state)
+{
+ g_object_unref (state->cancellable);
+ g_free (state);
+}
+
+static void
+link_info_nautilus_link_read_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ LinkInfoReadState *state;
+ gsize file_size;
+ char *file_contents;
+ gboolean result;
+ NautilusDirectory *directory;
+
+ state = user_data;
+
+ if (state->directory == NULL) {
+ /* Operation was cancelled. Bail out */
+ link_info_read_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);
+
+ state->directory->details->link_info_read_state = NULL;
+ async_job_end (state->directory, "link info");
+
+ link_info_got_data (state->directory, state->file, result, file_size, file_contents);
+
+ if (result) {
+ g_free (file_contents);
+ }
+
+ link_info_read_state_free (state);
+
+ nautilus_directory_unref (directory);
+}
+
+static void
+link_info_start (NautilusDirectory *directory,
+ NautilusFile *file,
+ gboolean *doing_io)
+{
+ GFile *location;
+ gboolean nautilus_style_link;
+ LinkInfoReadState *state;
+
+ if (directory->details->link_info_read_state != NULL) {
+ *doing_io = TRUE;
+ return;
+ }
+
+ if (!is_needy (file,
+ lacks_link_info,
+ REQUEST_LINK_INFO)) {
+ return;
+ }
+ *doing_io = TRUE;
+
+ /* Figure out if it is a link. */
+ nautilus_style_link = nautilus_file_is_nautilus_link (file);
+ location = nautilus_file_get_location (file);
+
+ /* If it's not a link we are done. If it is, we need to read it. */
+ if (!nautilus_style_link) {
+ link_info_done (directory, file, NULL, NULL, NULL, FALSE, FALSE);
+ } else {
+ if (!async_job_start (directory, "link info")) {
+ g_object_unref (location);
+ return;
+ }
+
+ state = g_new0 (LinkInfoReadState, 1);
+ state->directory = directory;
+ state->file = file;
+ state->cancellable = g_cancellable_new ();
+
+ directory->details->link_info_read_state = state;
+
+ g_file_load_contents_async (location,
+ state->cancellable,
+ link_info_nautilus_link_read_callback,
+ state);
+ }
+ g_object_unref (location);
+}
+
+static void
+thumbnail_done (NautilusDirectory *directory,
+ NautilusFile *file,
+ GdkPixbuf *pixbuf,
+ gboolean tried_original)
+{
+ const char *thumb_mtime_str;
+ time_t thumb_mtime = 0;
+
+ file->details->thumbnail_is_up_to_date = TRUE;
+ file->details->thumbnail_tried_original = tried_original;
+ 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) {
+ if (tried_original) {
+ thumb_mtime = file->details->mtime;
+ } else {
+ 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,
+ gboolean tried_original)
+{
+ nautilus_directory_ref (directory);
+
+ nautilus_file_ref (file);
+ thumbnail_done (directory, file, pixbuf, tried_original);
+ 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);
+}
+
+extern int cached_thumbnail_size;
+
+/* 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_LARGER * cached_thumbnail_size / 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;
+ GFile *location;
+
+ 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);
+ }
+
+ if (pixbuf == NULL && state->trying_original) {
+ state->trying_original = FALSE;
+
+ location = g_file_new_for_path (state->file->details->thumbnail_path);
+ g_file_load_contents_async (location,
+ state->cancellable,
+ thumbnail_read_callback,
+ state);
+ g_object_unref (location);
+ } else {
+ state->directory->details->thumbnail_state = NULL;
+ async_job_end (state->directory, "thumbnail");
+
+ thumbnail_got_pixbuf (state->directory, state->file, pixbuf, state->tried_original);
+
+ 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 ();
+
+ if (file->details->thumbnail_wants_original) {
+ state->tried_original = TRUE;
+ state->trying_original = TRUE;
+ location = nautilus_file_get_location (file);
+ } else {
+ 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);
+ if (g_strcmp0 (eel_ref_str_peek (file->details->filesystem_type), filesystem_type) != 0) {
+ eel_ref_str_unref (file->details->filesystem_type);
+ file->details->filesystem_type = eel_ref_str_get_unique (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_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);
+ link_info_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);
+ link_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);
+ link_info_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_link_info_for_file (NautilusDirectory *directory,
+ NautilusFile *file)
+{
+ if (directory->details->link_info_read_state != NULL &&
+ directory->details->link_info_read_state->file == file) {
+ link_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_LINK_INFO)) {
+ link_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_LINK_INFO)) {
+ cancel_link_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 000000000..a55a8363f
--- /dev/null
+++ b/src/nautilus-directory-notify.h
@@ -0,0 +1,62 @@
+/*
+ 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>
+*/
+
+#include <gdk/gdk.h>
+#include "nautilus-file.h"
+
+typedef struct {
+ char *from_uri;
+ char *to_uri;
+} URIPair;
+
+typedef struct {
+ GFile *from;
+ GFile *to;
+} GFilePair;
+
+typedef struct {
+ GFile *location;
+ gboolean set;
+ GdkPoint point;
+ int screen;
+} NautilusFileChangesQueuePosition;
+
+/* 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);
+void nautilus_directory_schedule_position_set (GList *position_setting_list);
+
+/* 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 000000000..dae1b96ce
--- /dev/null
+++ b/src/nautilus-directory-private.h
@@ -0,0 +1,229 @@
+/*
+ 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>
+*/
+
+#include <gio/gio.h>
+#include <eel/eel-vfs-extensions.h>
+#include "nautilus-directory.h"
+#include "nautilus-file-queue.h"
+#include "nautilus-file.h"
+#include "nautilus-monitor.h"
+#include <libnautilus-extension/nautilus-info-provider.h>
+#include <libxml/tree.h>
+
+typedef struct LinkInfoReadState LinkInfoReadState;
+typedef struct TopLeftTextReadState TopLeftTextReadState;
+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_LINK_INFO,
+ 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;
+ GList *monitor_list;
+ 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;
+
+ LinkInfoReadState *link_info_read_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 000000000..8cee2251b
--- /dev/null
+++ b/src/nautilus-directory.c
@@ -0,0 +1,1902 @@
+/*
+ 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 <config.h>
+#include "nautilus-directory-private.h"
+
+#include "nautilus-directory-notify.h"
+#include "nautilus-file-attributes.h"
+#include "nautilus-file-private.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-search-directory.h"
+#include "nautilus-search-directory-file.h"
+#include "nautilus-vfs-file.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-lib-self-check-functions.h"
+#include "nautilus-metadata.h"
+#include "nautilus-profile.h"
+#include "nautilus-vfs-directory.h"
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-string.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.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 void nautilus_directory_finalize (GObject *object);
+static NautilusDirectory *nautilus_directory_new (GFile *location);
+static GList * real_get_file_list (NautilusDirectory *directory);
+static gboolean real_is_editable (NautilusDirectory *directory);
+static void set_directory_location (NautilusDirectory *directory,
+ GFile *location);
+static NautilusFile * real_new_file_from_filename (NautilusDirectory *directory,
+ const char *filename,
+ gboolean self_owned);
+static gboolean real_handles_location (GFile *location);
+
+G_DEFINE_TYPE (NautilusDirectory, nautilus_directory, G_TYPE_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->new_file_from_filename = real_new_file_from_filename;
+
+ 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);
+
+ klass->get_file_list = real_get_file_list;
+ klass->is_editable = real_is_editable;
+ klass->handles_location = real_handles_location;
+
+ 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 (g_str_hash, g_str_equal);
+ 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 ();
+}
+
+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
+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 (directory->details->monitor_list != NULL) {
+ g_warning ("destroying a NautilusDirectory while it's being monitored");
+ g_list_free_full (directory->details->monitor_list, g_free);
+ }
+
+ 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
+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)
+{
+ GList *dirs, *l;
+ NautilusDirectory *directory;
+
+ 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 (l = dirs; l != NULL; l = l->next) {
+ directory = NAUTILUS_DIRECTORY (l->data);
+ nautilus_directory_invalidate_count_and_mime_list (directory);
+ }
+
+ nautilus_directory_list_unref (dirs);
+}
+
+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);
+}
+
+static gboolean
+real_handles_location (GFile *location)
+{
+ /* This class is the fallback on handling any location */
+ return TRUE;
+}
+
+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 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 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);
+ 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_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_remote (NautilusDirectory *directory)
+{
+ NautilusFile *file;
+ gboolean is_remote;
+
+ g_assert (NAUTILUS_IS_DIRECTORY (directory));
+
+ file = nautilus_directory_get_corresponding_file (directory);
+ is_remote = nautilus_file_is_remote (file);
+ nautilus_file_unref (file);
+
+ return is_remote;
+}
+
+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)
+{
+ const char *name;
+
+ name = eel_ref_str_peek (file->details->name);
+
+ g_assert (node != NULL);
+ g_assert (g_hash_table_lookup (directory->details->file_hash,
+ name) == NULL);
+ g_hash_table_insert (directory->details->file_hash, (char *) name, node);
+}
+
+static GList *
+extract_from_hash_table (NautilusDirectory *directory, NautilusFile *file)
+{
+ const char *name;
+ GList *node;
+
+ name = eel_ref_str_peek (file->details->name);
+ 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));
+ g_assert (file->details->name != NULL);
+
+ /* 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));
+ g_assert (file->details->name != NULL);
+
+ /* 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 *directory, GList *file_list)
+{
+ GList *node;
+ NautilusFile *file;
+
+ for (node = file_list; node != NULL; node = node->next) {
+ file = node->data;
+ if (file->details->directory == directory) {
+ nautilus_directory_add_file_to_work_queue (directory,
+ file);
+ }
+ }
+ nautilus_directory_async_state_changed (directory);
+ nautilus_directory_emit_change_signals (directory, 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) {
+ /* Tell it to re-get info now, and later emit
+ * a changed signal.
+ */
+ file->details->file_info_is_up_to_date = FALSE;
+ file->details->link_info_is_up_to_date = FALSE;
+ nautilus_file_invalidate_extension_info_internal (file);
+
+ hash_table_list_prepend (changed_lists,
+ file->details->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;
+ NautilusDirectory *directory;
+ 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) {
+ 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)) {
+ /* Mark it gone and prepare to send the changed signal. */
+ nautilus_file_mark_gone (file);
+ hash_table_list_prepend (changed_lists,
+ file->details->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) {
+ file = NAUTILUS_FILE (node->data);
+ hash_table_list_prepend (hash,
+ file->details->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) {
+ /* Mark it gone and prepare to send the changed signal. */
+ nautilus_file_mark_gone (file);
+ new_directory = file->details->directory;
+ hash_table_list_prepend (changed_lists,
+ new_directory,
+ file);
+ collect_parent_directories (parent_directories,
+ new_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) {
+ file = NAUTILUS_FILE (node->data);
+ hash_table_list_prepend (changed_lists,
+ file->details->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 {
+ /* Handle notification in the old directory. */
+ old_directory = file->details->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);
+}
+
+void
+nautilus_directory_schedule_position_set (GList *position_setting_list)
+{
+ GList *p;
+ const NautilusFileChangesQueuePosition *item;
+ NautilusFile *file;
+ char str[64];
+ time_t now;
+
+ time (&now);
+
+ for (p = position_setting_list; p != NULL; p = p->next) {
+ item = (NautilusFileChangesQueuePosition *) p->data;
+
+ file = nautilus_file_get (item->location);
+
+ if (item->set) {
+ g_snprintf (str, sizeof (str), "%d,%d", item->point.x, item->point.y);
+ } else {
+ str[0] = 0;
+ }
+ nautilus_file_set_metadata
+ (file,
+ NAUTILUS_METADATA_KEY_ICON_POSITION,
+ NULL,
+ str);
+
+ if (item->set) {
+ nautilus_file_set_time_metadata
+ (file,
+ NAUTILUS_METADATA_KEY_ICON_POSITION_TIMESTAMP,
+ now);
+ } else {
+ nautilus_file_set_time_metadata
+ (file,
+ NAUTILUS_METADATA_KEY_ICON_POSITION_TIMESTAMP,
+ UNDEFINED_TIME);
+ }
+
+ if (item->set) {
+ g_snprintf (str, sizeof (str), "%d", item->screen);
+ } else {
+ str[0] = 0;
+ }
+ nautilus_file_set_metadata
+ (file,
+ NAUTILUS_METADATA_KEY_SCREEN,
+ NULL,
+ str);
+
+ nautilus_file_unref (file);
+ }
+}
+
+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);
+}
+
+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;
+}
+
+GList *
+nautilus_directory_get_file_list (NautilusDirectory *directory)
+{
+ return NAUTILUS_DIRECTORY_CLASS (G_OBJECT_GET_CLASS (directory))->get_file_list (directory);
+}
+
+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;
+}
+
+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>
+#include "nautilus-file-attributes.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;
+
+ 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 000000000..25e3aa211
--- /dev/null
+++ b/src/nautilus-directory.h
@@ -0,0 +1,251 @@
+/*
+ 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>
+*/
+
+#ifndef NAUTILUS_DIRECTORY_H
+#define NAUTILUS_DIRECTORY_H
+
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+#include "nautilus-file-attributes.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 NautilusDirectoryDetails NautilusDirectoryDetails;
+
+typedef struct
+{
+ GObject object;
+ NautilusDirectoryDetails *details;
+} NautilusDirectory;
+
+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);
+
+/* 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_in_trash (NautilusDirectory *directory);
+gboolean nautilus_directory_is_in_recent (NautilusDirectory *directory);
+gboolean nautilus_directory_is_remote (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);
+
+#endif /* NAUTILUS_DIRECTORY_H */
diff --git a/src/nautilus-dnd.c b/src/nautilus-dnd.c
new file mode 100644
index 000000000..1731b473b
--- /dev/null
+++ b/src/nautilus-dnd.c
@@ -0,0 +1,952 @@
+
+/* 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 "nautilus-link.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_occured = 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);
+}
+
+gboolean
+nautilus_drag_items_on_desktop (const GList *selection_list)
+{
+ char *uri;
+ GFile *desktop, *item, *parent;
+ gboolean result;
+
+ /* check if the first item on the list is in trash.
+ * 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
+ */
+ uri = ((NautilusDragSelectionItem *)selection_list->data)->uri;
+ if (eel_uri_is_desktop (uri)) {
+ return TRUE;
+ }
+
+ desktop = nautilus_get_desktop_location ();
+
+ item = g_file_new_for_uri (uri);
+ parent = g_file_get_parent (item);
+ g_object_unref (item);
+
+ result = FALSE;
+
+ if (parent) {
+ result = g_file_equal (desktop, parent);
+ g_object_unref (parent);
+ }
+ g_object_unref (desktop);
+
+ return result;
+
+}
+
+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);
+
+ if (eel_uri_is_desktop (dropped_uri) &&
+ !eel_uri_is_desktop (target_uri_string)) {
+ /* Desktop items only move on the desktop */
+ *action = 0;
+ return;
+ }
+
+ /*
+ * 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 (dropped_file != NULL && nautilus_file_is_launcher (dropped_file)) {
+ if (actions & GDK_ACTION_MOVE) {
+ *action = GDK_ACTION_MOVE;
+ }
+ nautilus_file_unref (target_file);
+ return;
+ } else if (eel_uri_is_desktop (target_uri_string)) {
+ target = nautilus_get_desktop_location ();
+
+ nautilus_file_unref (target_file);
+ target_file = nautilus_file_get (target);
+
+ if (eel_uri_is_desktop (dropped_uri)) {
+ /* Only move to Desktop icons */
+ if (actions & GDK_ACTION_MOVE) {
+ *action = GDK_ACTION_MOVE;
+ }
+
+ g_object_unref (target);
+ 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 = 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 (GTK_MENU (menu), NULL, NULL,
+ NULL, NULL, 0, GDK_CURRENT_TIME);
+
+ 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;
+ }
+}
+
+gboolean
+nautilus_drag_selection_includes_special_link (GList *selection_list)
+{
+ GList *node;
+ char *uri;
+
+ for (node = selection_list; node != NULL; node = node->next) {
+ uri = ((NautilusDragSelectionItem *) node->data)->uri;
+
+ if (eel_uri_is_desktop (uri)) {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
diff --git a/src/nautilus-dnd.h b/src/nautilus-dnd.h
new file mode 100644
index 000000000..35872ae9b
--- /dev/null
+++ b/src/nautilus-dnd.h
@@ -0,0 +1,147 @@
+
+/* 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>
+*/
+
+#ifndef NAUTILUS_DND_H
+#define NAUTILUS_DND_H
+
+#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_BGIMAGE_TYPE "property/bgimage"
+#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 occured ? */
+ gboolean drop_occured;
+
+ /* 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);
+gboolean nautilus_drag_items_on_desktop (const GList *selection_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);
+
+gboolean nautilus_drag_selection_includes_special_link (GList *selection_list);
+
+NautilusDragInfo * nautilus_drag_get_source_data (GdkDragContext *context);
+
+GList * nautilus_drag_file_list_from_selection_list (const GList *selection_list);
+
+#endif
diff --git a/src/nautilus-empty-view.c b/src/nautilus-empty-view.c
index 2302c7c9c..b8518ab10 100644
--- a/src/nautilus-empty-view.c
+++ b/src/nautilus-empty-view.c
@@ -27,7 +27,7 @@
#include "nautilus-files-view.h"
#include <string.h>
-#include <libnautilus-private/nautilus-file-utilities.h>
+#include "nautilus-file-utilities.h"
#include <eel/eel-vfs-extensions.h>
struct NautilusEmptyViewDetails {
diff --git a/src/nautilus-entry.c b/src/nautilus-entry.c
new file mode 100644
index 000000000..1b790a2ab
--- /dev/null
+++ b/src/nautilus-entry.c
@@ -0,0 +1,365 @@
+
+/* NautilusEntry: one-line text editing widget. This consists of bug fixes
+ * and other improvements to GtkEntry, and all the changes could be rolled
+ * into GtkEntry some day.
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ *
+ * Author: John Sullivan <sullivan@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 "nautilus-entry.h"
+
+#include <string.h>
+#include "nautilus-global-preferences.h"
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+
+struct NautilusEntryDetails {
+ gboolean special_tab_handling;
+
+ guint select_idle_id;
+};
+
+enum {
+ SELECTION_CHANGED,
+ LAST_SIGNAL
+};
+static guint signals[LAST_SIGNAL];
+
+static void nautilus_entry_editable_init (GtkEditableInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (NautilusEntry, nautilus_entry, GTK_TYPE_ENTRY,
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE,
+ nautilus_entry_editable_init));
+
+static GtkEditableInterface *parent_editable_interface = NULL;
+
+static void
+nautilus_entry_init (NautilusEntry *entry)
+{
+ entry->details = g_new0 (NautilusEntryDetails, 1);
+}
+
+GtkWidget *
+nautilus_entry_new (void)
+{
+ return gtk_widget_new (NAUTILUS_TYPE_ENTRY, NULL);
+}
+
+static void
+nautilus_entry_finalize (GObject *object)
+{
+ NautilusEntry *entry;
+
+ entry = NAUTILUS_ENTRY (object);
+
+ if (entry->details->select_idle_id != 0) {
+ g_source_remove (entry->details->select_idle_id);
+ }
+
+ g_free (entry->details);
+
+ G_OBJECT_CLASS (nautilus_entry_parent_class)->finalize (object);
+}
+
+static gboolean
+nautilus_entry_key_press (GtkWidget *widget, GdkEventKey *event)
+{
+ NautilusEntry *entry;
+ GtkEditable *editable;
+ int position;
+ gboolean old_has, new_has;
+ gboolean result;
+
+ entry = NAUTILUS_ENTRY (widget);
+ editable = GTK_EDITABLE (widget);
+
+ if (!gtk_editable_get_editable (editable)) {
+ return FALSE;
+ }
+
+ switch (event->keyval) {
+ case GDK_KEY_Tab:
+ /* 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 (entry->details->special_tab_handling && gtk_editable_get_selection_bounds (editable, NULL, NULL)) {
+ position = strlen (gtk_entry_get_text (GTK_ENTRY (editable)));
+ gtk_editable_select_region (editable, position, position);
+ return TRUE;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ old_has = gtk_editable_get_selection_bounds (editable, NULL, NULL);
+
+ result = GTK_WIDGET_CLASS (nautilus_entry_parent_class)->key_press_event (widget, event);
+
+ /* Pressing a key usually changes the selection if there is a selection.
+ * If there is not selection, we can save work by not emitting a signal.
+ */
+ if (result) {
+ new_has = gtk_editable_get_selection_bounds (editable, NULL, NULL);
+ if (old_has || new_has) {
+ g_signal_emit (widget, signals[SELECTION_CHANGED], 0);
+ }
+ }
+
+ return result;
+
+}
+
+static gboolean
+nautilus_entry_motion_notify (GtkWidget *widget, GdkEventMotion *event)
+{
+ int result;
+ gboolean old_had, new_had;
+ int old_start, old_end, new_start, new_end;
+ GtkEditable *editable;
+
+ editable = GTK_EDITABLE (widget);
+
+ old_had = gtk_editable_get_selection_bounds (editable, &old_start, &old_end);
+
+ result = GTK_WIDGET_CLASS (nautilus_entry_parent_class)->motion_notify_event (widget, event);
+
+ /* Send a signal if dragging the mouse caused the selection to change. */
+ if (result) {
+ new_had = gtk_editable_get_selection_bounds (editable, &new_start, &new_end);
+ if (old_had != new_had || (old_had && (old_start != new_start || old_end != new_end))) {
+ g_signal_emit (widget, signals[SELECTION_CHANGED], 0);
+ }
+ }
+
+ return result;
+}
+
+/**
+ * nautilus_entry_select_all
+ *
+ * Select all text, leaving the text cursor position at the end.
+ *
+ * @entry: A NautilusEntry
+ **/
+void
+nautilus_entry_select_all (NautilusEntry *entry)
+{
+ g_return_if_fail (NAUTILUS_IS_ENTRY (entry));
+
+ gtk_editable_set_position (GTK_EDITABLE (entry), -1);
+ gtk_editable_select_region (GTK_EDITABLE (entry), 0, -1);
+}
+
+static gboolean
+select_all_at_idle (gpointer callback_data)
+{
+ NautilusEntry *entry;
+
+ entry = NAUTILUS_ENTRY (callback_data);
+
+ nautilus_entry_select_all (entry);
+
+ entry->details->select_idle_id = 0;
+
+ return FALSE;
+}
+
+/**
+ * nautilus_entry_select_all_at_idle
+ *
+ * Select all text at the next idle, not immediately.
+ * This is useful when reacting to a key press, because
+ * changing the selection and the text cursor position doesn't
+ * work in a key_press signal handler.
+ *
+ * @entry: A NautilusEntry
+ **/
+void
+nautilus_entry_select_all_at_idle (NautilusEntry *entry)
+{
+ g_return_if_fail (NAUTILUS_IS_ENTRY (entry));
+
+ /* If the text cursor position changes in this routine
+ * then gtk_entry_key_press will unselect (and we want
+ * to move the text cursor position to the end).
+ */
+
+ if (entry->details->select_idle_id == 0) {
+ entry->details->select_idle_id = g_idle_add (select_all_at_idle, entry);
+ }
+}
+
+/**
+ * nautilus_entry_set_text
+ *
+ * This function wraps gtk_entry_set_text. It sets undo_registered
+ * to TRUE and preserves the old value for a later restore. This is
+ * done so the programmatic changes to the entry do not register
+ * with the undo manager.
+ *
+ * @entry: A NautilusEntry
+ * @test: The text to set
+ **/
+
+void
+nautilus_entry_set_text (NautilusEntry *entry, const gchar *text)
+{
+ g_return_if_fail (NAUTILUS_IS_ENTRY (entry));
+
+ gtk_entry_set_text (GTK_ENTRY (entry), text);
+ g_signal_emit (entry, signals[SELECTION_CHANGED], 0);
+}
+
+static void
+nautilus_entry_set_selection_bounds (GtkEditable *editable,
+ int start_pos,
+ int end_pos)
+{
+ parent_editable_interface->set_selection_bounds (editable, start_pos, end_pos);
+
+ g_signal_emit (editable, signals[SELECTION_CHANGED], 0);
+}
+
+static gboolean
+nautilus_entry_button_press (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ gboolean result;
+
+ result = GTK_WIDGET_CLASS (nautilus_entry_parent_class)->button_press_event (widget, event);
+
+ if (result) {
+ g_signal_emit (widget, signals[SELECTION_CHANGED], 0);
+ }
+
+ return result;
+}
+
+static gboolean
+nautilus_entry_button_release (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ gboolean result;
+
+ result = GTK_WIDGET_CLASS (nautilus_entry_parent_class)->button_release_event (widget, event);
+
+ if (result) {
+ g_signal_emit (widget, signals[SELECTION_CHANGED], 0);
+ }
+
+ return result;
+}
+
+static void
+nautilus_entry_insert_text (GtkEditable *editable, const gchar *text,
+ int length, int *position)
+{
+ parent_editable_interface->insert_text (editable, text, length, position);
+
+ g_signal_emit (editable, signals[SELECTION_CHANGED], 0);
+}
+
+static void
+nautilus_entry_delete_text (GtkEditable *editable, int start_pos, int end_pos)
+{
+ parent_editable_interface->delete_text (editable, start_pos, end_pos);
+
+ g_signal_emit (editable, signals[SELECTION_CHANGED], 0);
+}
+
+/* Overridden to work around GTK bug. The selection_clear_event is queued
+ * when the selection changes. Changing the selection to NULL and then
+ * back to the original selection owner still sends the event, so the
+ * selection owner then gets the selection ripped away from it. We ran into
+ * this with type-completion behavior in NautilusLocationBar (see bug 5313).
+ * There's a FIXME comment that seems to be about this same issue in
+ * gtk+/gtkselection.c, gtk_selection_clear.
+ */
+static gboolean
+nautilus_entry_selection_clear (GtkWidget *widget,
+ GdkEventSelection *event)
+{
+ g_assert (NAUTILUS_IS_ENTRY (widget));
+
+ if (gdk_selection_owner_get (event->selection) == gtk_widget_get_window (widget)) {
+ return FALSE;
+ }
+
+ return GTK_WIDGET_CLASS (nautilus_entry_parent_class)->selection_clear_event (widget, event);
+}
+
+static void
+nautilus_entry_editable_init (GtkEditableInterface *iface)
+{
+ parent_editable_interface = g_type_interface_peek_parent (iface);
+
+ iface->insert_text = nautilus_entry_insert_text;
+ iface->delete_text = nautilus_entry_delete_text;
+ iface->set_selection_bounds = nautilus_entry_set_selection_bounds;
+
+ /* Otherwise we might need some memcpy loving */
+ g_assert (iface->do_insert_text != NULL);
+ g_assert (iface->get_position != NULL);
+ g_assert (iface->get_chars != NULL);
+}
+
+static void
+nautilus_entry_class_init (NautilusEntryClass *class)
+{
+ GtkWidgetClass *widget_class;
+ GObjectClass *gobject_class;
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ gobject_class = G_OBJECT_CLASS (class);
+
+ widget_class->button_press_event = nautilus_entry_button_press;
+ widget_class->button_release_event = nautilus_entry_button_release;
+ widget_class->key_press_event = nautilus_entry_key_press;
+ widget_class->motion_notify_event = nautilus_entry_motion_notify;
+ widget_class->selection_clear_event = nautilus_entry_selection_clear;
+
+ gobject_class->finalize = nautilus_entry_finalize;
+
+ /* Set up signals */
+ signals[SELECTION_CHANGED] = g_signal_new
+ ("selection-changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusEntryClass, selection_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+void
+nautilus_entry_set_special_tab_handling (NautilusEntry *entry,
+ gboolean special_tab_handling)
+{
+ g_return_if_fail (NAUTILUS_IS_ENTRY (entry));
+
+ entry->details->special_tab_handling = special_tab_handling;
+}
+
+
diff --git a/src/nautilus-entry.h b/src/nautilus-entry.h
new file mode 100644
index 000000000..eb6d9779d
--- /dev/null
+++ b/src/nautilus-entry.h
@@ -0,0 +1,67 @@
+
+/* NautilusEntry: one-line text editing widget. This consists of bug fixes
+ * and other improvements to GtkEntry, and all the changes could be rolled
+ * into GtkEntry some day.
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ *
+ * Author: John Sullivan <sullivan@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/>.
+ */
+
+#ifndef NAUTILUS_ENTRY_H
+#define NAUTILUS_ENTRY_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_ENTRY nautilus_entry_get_type()
+#define NAUTILUS_ENTRY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_ENTRY, NautilusEntry))
+#define NAUTILUS_ENTRY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_ENTRY, NautilusEntryClass))
+#define NAUTILUS_IS_ENTRY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_ENTRY))
+#define NAUTILUS_IS_ENTRY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_ENTRY))
+#define NAUTILUS_ENTRY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_ENTRY, NautilusEntryClass))
+
+typedef struct NautilusEntryDetails NautilusEntryDetails;
+
+typedef struct {
+ GtkEntry parent;
+ NautilusEntryDetails *details;
+} NautilusEntry;
+
+typedef struct {
+ GtkEntryClass parent_class;
+
+ void (*selection_changed) (NautilusEntry *entry);
+} NautilusEntryClass;
+
+GType nautilus_entry_get_type (void);
+GtkWidget *nautilus_entry_new (void);
+void nautilus_entry_set_text (NautilusEntry *entry,
+ const char *text);
+void nautilus_entry_select_all (NautilusEntry *entry);
+void nautilus_entry_select_all_at_idle (NautilusEntry *entry);
+void nautilus_entry_set_special_tab_handling (NautilusEntry *entry,
+ gboolean special_tab_handling);
+
+G_END_DECLS
+
+#endif /* NAUTILUS_ENTRY_H */
diff --git a/src/nautilus-error-reporting.c b/src/nautilus-error-reporting.c
index d64499f83..be08b3ee2 100644
--- a/src/nautilus-error-reporting.c
+++ b/src/nautilus-error-reporting.c
@@ -27,12 +27,12 @@
#include <string.h>
#include <glib/gi18n.h>
-#include <libnautilus-private/nautilus-file.h>
+#include "nautilus-file.h"
#include <eel/eel-string.h>
#include <eel/eel-stock-dialogs.h>
#define DEBUG_FLAG NAUTILUS_DEBUG_DIRECTORY_VIEW
-#include <libnautilus-private/nautilus-debug.h>
+#include "nautilus-debug.h"
#define NEW_NAME_TAG "Nautilus: new name"
#define MAXIMUM_DISPLAYED_FILE_NAME_LENGTH 50
diff --git a/src/nautilus-error-reporting.h b/src/nautilus-error-reporting.h
index e588b9c45..955b937ac 100644
--- a/src/nautilus-error-reporting.h
+++ b/src/nautilus-error-reporting.h
@@ -25,7 +25,7 @@
#define NAUTILUS_ERROR_REPORTING_H
#include <gtk/gtk.h>
-#include <libnautilus-private/nautilus-file.h>
+#include "nautilus-file.h"
void nautilus_report_error_loading_directory (NautilusFile *file,
GError *error,
diff --git a/src/nautilus-file-attributes.h b/src/nautilus-file-attributes.h
new file mode 100644
index 000000000..281de9384
--- /dev/null
+++ b/src/nautilus-file-attributes.h
@@ -0,0 +1,43 @@
+/*
+ nautilus-file-attributes.h: #defines and other file-attribute-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: Darin Adler <darin@bentspoon.com>
+*/
+
+#ifndef NAUTILUS_FILE_ATTRIBUTES_H
+#define NAUTILUS_FILE_ATTRIBUTES_H
+
+/* Names for NautilusFile attributes. These are used when registering
+ * interest in changes to the attributes or when waiting for them.
+ */
+
+typedef enum {
+ NAUTILUS_FILE_ATTRIBUTE_INFO = 1 << 0, /* All standard info */
+ NAUTILUS_FILE_ATTRIBUTE_LINK_INFO = 1 << 1, /* info from desktop links */
+ NAUTILUS_FILE_ATTRIBUTE_DEEP_COUNTS = 1 << 2,
+ NAUTILUS_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT = 1 << 3,
+ NAUTILUS_FILE_ATTRIBUTE_DIRECTORY_ITEM_MIME_TYPES = 1 << 4,
+ NAUTILUS_FILE_ATTRIBUTE_TOP_LEFT_TEXT = 1 << 5,
+ NAUTILUS_FILE_ATTRIBUTE_LARGE_TOP_LEFT_TEXT = 1 << 6,
+ NAUTILUS_FILE_ATTRIBUTE_EXTENSION_INFO = 1 << 7,
+ NAUTILUS_FILE_ATTRIBUTE_THUMBNAIL = 1 << 8,
+ NAUTILUS_FILE_ATTRIBUTE_MOUNT = 1 << 9,
+ NAUTILUS_FILE_ATTRIBUTE_FILESYSTEM_INFO = 1 << 10,
+} NautilusFileAttributes;
+
+#endif /* NAUTILUS_FILE_ATTRIBUTES_H */
diff --git a/src/nautilus-file-changes-queue.c b/src/nautilus-file-changes-queue.c
new file mode 100644
index 000000000..b637c61e2
--- /dev/null
+++ b/src/nautilus-file-changes-queue.c
@@ -0,0 +1,393 @@
+/*
+ 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"
+
+typedef enum {
+ CHANGE_FILE_INITIAL,
+ CHANGE_FILE_ADDED,
+ CHANGE_FILE_CHANGED,
+ CHANGE_FILE_REMOVED,
+ CHANGE_FILE_MOVED,
+ CHANGE_POSITION_SET,
+ CHANGE_POSITION_REMOVE
+} 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);
+}
+
+void
+nautilus_file_changes_queue_schedule_position_set (GFile *location,
+ GdkPoint point,
+ int screen)
+{
+ NautilusFileChange *new_item;
+ NautilusFileChangesQueue *queue;
+
+ queue = nautilus_file_changes_queue_get ();
+
+ new_item = g_new (NautilusFileChange, 1);
+ new_item->kind = CHANGE_POSITION_SET;
+ new_item->from = g_object_ref (location);
+ new_item->point = point;
+ new_item->screen = screen;
+ nautilus_file_changes_queue_add_common (queue, new_item);
+}
+
+void
+nautilus_file_changes_queue_schedule_position_remove (GFile *location)
+{
+ NautilusFileChange *new_item;
+ NautilusFileChangesQueue *queue;
+
+ queue = nautilus_file_changes_queue_get ();
+
+ new_item = g_new (NautilusFileChange, 1);
+ new_item->kind = CHANGE_POSITION_REMOVE;
+ new_item->from = g_object_ref (location);
+ 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);
+}
+
+static void
+position_set_list_free (GList *list)
+{
+ GList *p;
+ NautilusFileChangesQueuePosition *item;
+
+ for (p = list; p != NULL; p = p->next) {
+ item = p->data;
+ g_object_unref (item->location);
+ }
+ /* delete the list and the now empty structs */
+ g_list_free_full (list, g_free);
+}
+
+/* go through changes in the change queue, send ones with the same kind
+ * in a list to the different nautilus_directory_notify calls
+ */
+void
+nautilus_file_changes_consume_changes (gboolean consume_all)
+{
+ NautilusFileChange *change;
+ GList *additions, *changes, *deletions, *moves;
+ GList *position_set_requests;
+ GFilePair *pair;
+ NautilusFileChangesQueuePosition *position_set;
+ guint chunk_count;
+ NautilusFileChangesQueue *queue;
+ gboolean flush_needed;
+
+
+ additions = NULL;
+ changes = NULL;
+ deletions = NULL;
+ moves = NULL;
+ position_set_requests = 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
+ && change->kind != CHANGE_POSITION_SET
+ && change->kind != CHANGE_POSITION_REMOVE;
+
+ flush_needed |= changes != NULL
+ && change->kind != CHANGE_FILE_CHANGED;
+
+ flush_needed |= moves != NULL
+ && change->kind != CHANGE_FILE_MOVED
+ && change->kind != CHANGE_POSITION_SET
+ && change->kind != CHANGE_POSITION_REMOVE;
+
+ flush_needed |= deletions != NULL
+ && change->kind != CHANGE_FILE_REMOVED;
+
+ flush_needed |= position_set_requests != NULL
+ && change->kind != CHANGE_POSITION_SET
+ && change->kind != CHANGE_POSITION_REMOVE
+ && change->kind != CHANGE_FILE_ADDED
+ && change->kind != CHANGE_FILE_MOVED;
+
+ 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 (position_set_requests != NULL) {
+ position_set_requests = g_list_reverse (position_set_requests);
+ nautilus_directory_schedule_position_set (position_set_requests);
+ position_set_list_free (position_set_requests);
+ position_set_requests = 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:
+ pair = g_new (GFilePair, 1);
+ pair->from = change->from;
+ pair->to = change->to;
+ moves = g_list_prepend (moves, pair);
+ break;
+
+ case CHANGE_POSITION_SET:
+ position_set = g_new (NautilusFileChangesQueuePosition, 1);
+ position_set->location = change->from;
+ position_set->set = TRUE;
+ position_set->point = change->point;
+ position_set->screen = change->screen;
+ position_set_requests = g_list_prepend (position_set_requests,
+ position_set);
+ break;
+
+ case CHANGE_POSITION_REMOVE:
+ position_set = g_new (NautilusFileChangesQueuePosition, 1);
+ position_set->location = change->from;
+ position_set->set = FALSE;
+ position_set_requests = g_list_prepend (position_set_requests,
+ position_set);
+ 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 000000000..aa75238c6
--- /dev/null
+++ b/src/nautilus-file-changes-queue.h
@@ -0,0 +1,39 @@
+/*
+ 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>
+*/
+
+#ifndef NAUTILUS_FILE_CHANGES_QUEUE_H
+#define NAUTILUS_FILE_CHANGES_QUEUE_H
+
+#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_queue_schedule_position_set (GFile *location,
+ GdkPoint point,
+ int screen);
+void nautilus_file_changes_queue_schedule_position_remove (GFile *location);
+
+void nautilus_file_changes_consume_changes (gboolean consume_all);
+
+
+#endif /* NAUTILUS_FILE_CHANGES_QUEUE_H */
diff --git a/src/nautilus-file-conflict-dialog.c b/src/nautilus-file-conflict-dialog.c
new file mode 100644
index 000000000..91b7fb286
--- /dev/null
+++ b/src/nautilus-file-conflict-dialog.c
@@ -0,0 +1,671 @@
+
+/* 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"
+
+struct _NautilusFileConflictDialogDetails
+{
+ /* conflicting objects */
+ NautilusFile *source;
+ NautilusFile *destination;
+ NautilusFile *dest_dir;
+
+ gchar *conflict_name;
+ NautilusFileListHandle *handle;
+ gulong src_handler_id;
+ gulong dest_handler_id;
+
+ /* UI objects */
+ GtkWidget *titles_vbox;
+ GtkWidget *first_hbox;
+ GtkWidget *second_hbox;
+ GtkWidget *expander;
+ GtkWidget *entry;
+ GtkWidget *checkbox;
+ GtkWidget *rename_button;
+ GtkWidget *replace_button;
+ GtkWidget *dest_image;
+ GtkWidget *src_image;
+};
+
+G_DEFINE_TYPE (NautilusFileConflictDialog,
+ nautilus_file_conflict_dialog,
+ GTK_TYPE_DIALOG);
+
+#define NAUTILUS_FILE_CONFLICT_DIALOG_GET_PRIVATE(object) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((object), NAUTILUS_TYPE_FILE_CONFLICT_DIALOG, \
+ NautilusFileConflictDialogDetails))
+
+static void
+file_icons_changed (NautilusFile *file,
+ NautilusFileConflictDialog *fcd)
+{
+ GdkPixbuf *pixbuf;
+
+ pixbuf = nautilus_file_get_icon_pixbuf (fcd->details->destination,
+ NAUTILUS_CANVAS_ICON_SIZE_SMALL,
+ TRUE,
+ gtk_widget_get_scale_factor (fcd->details->dest_image),
+ NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS);
+
+ gtk_image_set_from_pixbuf (GTK_IMAGE (fcd->details->dest_image), pixbuf);
+ g_object_unref (pixbuf);
+
+ pixbuf = nautilus_file_get_icon_pixbuf (fcd->details->source,
+ NAUTILUS_CANVAS_ICON_SIZE_SMALL,
+ TRUE,
+ gtk_widget_get_scale_factor (fcd->details->src_image),
+ NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS);
+
+ gtk_image_set_from_pixbuf (GTK_IMAGE (fcd->details->src_image), pixbuf);
+ g_object_unref (pixbuf);
+}
+
+static void
+file_list_ready_cb (GList *files,
+ gpointer user_data)
+{
+ NautilusFileConflictDialog *fcd = user_data;
+ NautilusFile *src, *dest, *dest_dir;
+ time_t src_mtime, dest_mtime;
+ gboolean source_is_dir, dest_is_dir, should_show_type;
+ NautilusFileConflictDialogDetails *details;
+ char *primary_text, *message, *secondary_text;
+ const gchar *message_extra;
+ char *dest_name, *dest_dir_name, *edit_name;
+ char *label_text;
+ char *size, *date, *type = NULL;
+ GdkPixbuf *pixbuf;
+ GtkWidget *label;
+ GString *str;
+ PangoAttrList *attr_list;
+
+ details = fcd->details;
+
+ details->handle = NULL;
+
+ dest_dir = g_list_nth_data (files, 0);
+ dest = g_list_nth_data (files, 1);
+ src = g_list_nth_data (files, 2);
+
+ src_mtime = nautilus_file_get_mtime (src);
+ dest_mtime = nautilus_file_get_mtime (dest);
+
+ dest_name = nautilus_file_get_display_name (dest);
+ dest_dir_name = nautilus_file_get_display_name (dest_dir);
+
+ source_is_dir = nautilus_file_is_directory (src);
+ dest_is_dir = nautilus_file_is_directory (dest);
+
+ type = nautilus_file_get_mime_type (dest);
+ should_show_type = !nautilus_file_is_mime_type (src, type);
+
+ g_free (type);
+ type = NULL;
+
+ /* Set up the right labels */
+ if (dest_is_dir) {
+ if (source_is_dir) {
+ primary_text = g_strdup_printf
+ (_("Merge folder “%s”?"),
+ dest_name);
+
+ message_extra =
+ _("Merging will ask for confirmation before replacing any files in "
+ "the folder that conflict with the files being copied.");
+
+ if (src_mtime > dest_mtime) {
+ message = g_strdup_printf (
+ _("An older folder with the same name already exists in “%s”."),
+ dest_dir_name);
+ } else if (src_mtime < dest_mtime) {
+ message = g_strdup_printf (
+ _("A newer folder with the same name already exists in “%s”."),
+ dest_dir_name);
+ } else {
+ message = g_strdup_printf (
+ _("Another folder with the same name already exists in “%s”."),
+ dest_dir_name);
+ }
+ } else {
+ message_extra =
+ _("Replacing it will remove all files in the folder.");
+ primary_text = g_strdup_printf
+ (_("Replace folder “%s”?"), dest_name);
+ message = g_strdup_printf
+ (_("A folder with the same name already exists in “%s”."),
+ dest_dir_name);
+ }
+ } else {
+ primary_text = g_strdup_printf
+ (_("Replace file “%s”?"), dest_name);
+
+ message_extra = _("Replacing it will overwrite its content.");
+
+ if (src_mtime > dest_mtime) {
+ message = g_strdup_printf (
+ _("An older file with the same name already exists in “%s”."),
+ dest_dir_name);
+ } else if (src_mtime < dest_mtime) {
+ message = g_strdup_printf (
+ _("A newer file with the same name already exists in “%s”."),
+ dest_dir_name);
+ } else {
+ message = g_strdup_printf (
+ _("Another file with the same name already exists in “%s”."),
+ dest_dir_name);
+ }
+ }
+
+ secondary_text = g_strdup_printf ("%s\n%s", message, message_extra);
+ g_free (message);
+
+ 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 (details->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 (details->titles_vbox),
+ label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+ g_free (primary_text);
+ g_free (secondary_text);
+
+ /* Set up file icons */
+ pixbuf = nautilus_file_get_icon_pixbuf (dest,
+ NAUTILUS_CANVAS_ICON_SIZE_SMALL,
+ TRUE,
+ gtk_widget_get_scale_factor (fcd->details->titles_vbox),
+ NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS);
+ details->dest_image = gtk_image_new_from_pixbuf (pixbuf);
+ gtk_box_pack_start (GTK_BOX (details->first_hbox),
+ details->dest_image, FALSE, FALSE, 0);
+ gtk_widget_show (details->dest_image);
+ g_object_unref (pixbuf);
+
+ pixbuf = nautilus_file_get_icon_pixbuf (src,
+ NAUTILUS_CANVAS_ICON_SIZE_SMALL,
+ TRUE,
+ gtk_widget_get_scale_factor (fcd->details->titles_vbox),
+ NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS);
+ details->src_image = gtk_image_new_from_pixbuf (pixbuf);
+ gtk_box_pack_start (GTK_BOX (details->second_hbox),
+ details->src_image, FALSE, FALSE, 0);
+ gtk_widget_show (details->src_image);
+ g_object_unref (pixbuf);
+
+ /* Set up labels */
+ label = gtk_label_new (NULL);
+ date = nautilus_file_get_string_attribute (dest,
+ "date_modified");
+ size = nautilus_file_get_string_attribute (dest, "size");
+
+ if (should_show_type) {
+ type = nautilus_file_get_string_attribute (dest, "type");
+ }
+
+ str = g_string_new (NULL);
+ if (dest_is_dir) {
+ g_string_append_printf (str, "<b>%s</b>\n", _("Original folder"));
+ g_string_append_printf (str, "%s %s\n", _("Items:"), size);
+ }
+ else {
+ g_string_append_printf (str, "<b>%s</b>\n", _("Original file"));
+ g_string_append_printf (str, "%s %s\n", _("Size:"), size);
+ }
+
+ if (should_show_type) {
+ g_string_append_printf (str, "%s %s\n", _("Type:"), type);
+ }
+
+ g_string_append_printf (str, "%s %s", _("Last modified:"), date);
+
+ label_text = str->str;
+ gtk_label_set_markup (GTK_LABEL (label),
+ label_text);
+ gtk_box_pack_start (GTK_BOX (details->first_hbox),
+ label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+
+ g_free (size);
+ g_free (type);
+ g_free (date);
+ g_string_erase (str, 0, -1);
+
+ /* Second label */
+ label = gtk_label_new (NULL);
+ date = nautilus_file_get_string_attribute (src,
+ "date_modified");
+ size = nautilus_file_get_string_attribute (src, "size");
+
+ if (should_show_type) {
+ type = nautilus_file_get_string_attribute (src, "type");
+ }
+
+ if (source_is_dir) {
+ g_string_append_printf (str, "<b>%s</b>\n", dest_is_dir ? _("Merge with") : _("Replace with"));
+ g_string_append_printf (str, "%s %s\n", _("Items:"), size);
+ }
+ else {
+ g_string_append_printf (str, "<b>%s</b>\n", _("Replace with"));
+ g_string_append_printf (str, "%s %s\n", _("Size:"), size);
+ }
+
+ if (should_show_type) {
+ g_string_append_printf (str, "%s %s\n", _("Type:"), type);
+ }
+
+ g_string_append_printf (str, "%s %s", _("Last modified:"), date);
+ label_text = g_string_free (str, FALSE);
+
+ gtk_label_set_markup (GTK_LABEL (label),
+ label_text);
+ gtk_box_pack_start (GTK_BOX (details->second_hbox),
+ label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+
+ g_free (size);
+ g_free (date);
+ g_free (type);
+ g_free (label_text);
+
+ /* Populate the entry */
+ edit_name = nautilus_file_get_edit_name (dest);
+ details->conflict_name = edit_name;
+
+ gtk_entry_set_text (GTK_ENTRY (details->entry), edit_name);
+
+ if (source_is_dir && dest_is_dir) {
+ gtk_button_set_label (GTK_BUTTON (details->replace_button),
+ _("Merge"));
+ }
+
+ nautilus_file_monitor_add (src, fcd, NAUTILUS_FILE_ATTRIBUTES_FOR_ICON);
+ nautilus_file_monitor_add (dest, fcd, NAUTILUS_FILE_ATTRIBUTES_FOR_ICON);
+
+ details->src_handler_id = g_signal_connect (src, "changed",
+ G_CALLBACK (file_icons_changed), fcd);
+ details->dest_handler_id = g_signal_connect (dest, "changed",
+ G_CALLBACK (file_icons_changed), fcd);
+}
+
+static void
+build_dialog_appearance (NautilusFileConflictDialog *fcd)
+{
+ GList *files = NULL;
+ NautilusFileConflictDialogDetails *details = fcd->details;
+
+ files = g_list_prepend (files, details->source);
+ files = g_list_prepend (files, details->destination);
+ files = g_list_prepend (files, details->dest_dir);
+
+ nautilus_file_list_call_when_ready (files,
+ NAUTILUS_FILE_ATTRIBUTES_FOR_ICON,
+ &details->handle, file_list_ready_cb, fcd);
+ g_list_free (files);
+}
+
+static void
+set_source_and_destination (GtkWidget *w,
+ GFile *source,
+ GFile *destination,
+ GFile *dest_dir)
+{
+ NautilusFileConflictDialog *dialog;
+ NautilusFileConflictDialogDetails *details;
+
+ dialog = NAUTILUS_FILE_CONFLICT_DIALOG (w);
+ details = dialog->details;
+
+ details->source = nautilus_file_get (source);
+ details->destination = nautilus_file_get (destination);
+ details->dest_dir = nautilus_file_get (dest_dir);
+
+ build_dialog_appearance (dialog);
+}
+
+static void
+entry_text_changed_cb (GtkEditable *entry,
+ NautilusFileConflictDialog *dialog)
+{
+ NautilusFileConflictDialogDetails *details;
+
+ details = dialog->details;
+
+ /* 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)), details->conflict_name) != 0) {
+ gtk_widget_hide (details->replace_button);
+ gtk_widget_show (details->rename_button);
+
+ gtk_widget_set_sensitive (details->checkbox, FALSE);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog),
+ CONFLICT_RESPONSE_RENAME);
+ } else {
+ gtk_widget_hide (details->rename_button);
+ gtk_widget_show (details->replace_button);
+
+ gtk_widget_set_sensitive (details->checkbox, TRUE);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog),
+ CONFLICT_RESPONSE_REPLACE);
+ }
+}
+
+static void
+expander_activated_cb (GtkExpander *w,
+ NautilusFileConflictDialog *dialog)
+{
+ NautilusFileConflictDialogDetails *details;
+ int start_pos, end_pos;
+
+ details = dialog->details;
+
+ if (!gtk_expander_get_expanded (w)) {
+ if (g_strcmp0 (gtk_entry_get_text (GTK_ENTRY (details->entry)),
+ details->conflict_name) == 0) {
+ gtk_widget_grab_focus (details->entry);
+
+ eel_filename_get_rename_region (details->conflict_name,
+ &start_pos, &end_pos);
+ gtk_editable_select_region (GTK_EDITABLE (details->entry),
+ start_pos, end_pos);
+ }
+ }
+}
+
+static void
+checkbox_toggled_cb (GtkToggleButton *t,
+ NautilusFileConflictDialog *dialog)
+{
+ NautilusFileConflictDialogDetails *details;
+
+ details = dialog->details;
+
+ gtk_widget_set_sensitive (details->expander,
+ !gtk_toggle_button_get_active (t));
+ gtk_widget_set_sensitive (details->rename_button,
+ !gtk_toggle_button_get_active (t));
+
+ if (!gtk_toggle_button_get_active (t) &&
+ g_strcmp0 (gtk_entry_get_text (GTK_ENTRY (details->entry)),
+ "") != 0 &&
+ g_strcmp0 (gtk_entry_get_text (GTK_ENTRY (details->entry)),
+ details->conflict_name) != 0) {
+ gtk_widget_hide (details->replace_button);
+ gtk_widget_show (details->rename_button);
+ } else {
+ gtk_widget_hide (details->rename_button);
+ gtk_widget_show (details->replace_button);
+ }
+}
+
+static void
+reset_button_clicked_cb (GtkButton *w,
+ NautilusFileConflictDialog *dialog)
+{
+ NautilusFileConflictDialogDetails *details;
+ int start_pos, end_pos;
+
+ details = dialog->details;
+
+ gtk_entry_set_text (GTK_ENTRY (details->entry),
+ details->conflict_name);
+ gtk_widget_grab_focus (details->entry);
+ eel_filename_get_rename_region (details->conflict_name,
+ &start_pos, &end_pos);
+ gtk_editable_select_region (GTK_EDITABLE (details->entry),
+ start_pos, end_pos);
+
+}
+
+static void
+nautilus_file_conflict_dialog_init (NautilusFileConflictDialog *fcd)
+{
+ GtkWidget *hbox, *vbox, *vbox2, *alignment;
+ GtkWidget *widget, *dialog_area;
+ NautilusFileConflictDialogDetails *details;
+ GtkDialog *dialog;
+
+ details = fcd->details = NAUTILUS_FILE_CONFLICT_DIALOG_GET_PRIVATE (fcd);
+ 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);
+ details->titles_vbox = widget;
+
+ /* Setup the hboxes to pack file infos into */
+ alignment = gtk_alignment_new (0.0, 0.0, 0.0, 0.0);
+ g_object_set (alignment, "left-padding", 12, NULL);
+ vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
+ gtk_container_add (GTK_CONTAINER (alignment), vbox2);
+ gtk_box_pack_start (GTK_BOX (vbox), alignment, FALSE, FALSE, 0);
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
+ gtk_box_pack_start (GTK_BOX (vbox2), hbox, FALSE, FALSE, 0);
+ details->first_hbox = hbox;
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
+ gtk_box_pack_start (GTK_BOX (vbox2), hbox, FALSE, FALSE, 0);
+ details->second_hbox = hbox;
+
+ /* Setup the expander for the rename action */
+ details->expander = gtk_expander_new_with_mnemonic (_("_Select a new name for the destination"));
+ gtk_box_pack_start (GTK_BOX (vbox2), details->expander, FALSE, FALSE, 0);
+ g_signal_connect (details->expander, "activate",
+ G_CALLBACK (expander_activated_cb), dialog);
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_container_add (GTK_CONTAINER (details->expander), hbox);
+
+ widget = gtk_entry_new ();
+ gtk_box_pack_start (GTK_BOX (hbox), widget, TRUE, TRUE, 6);
+ details->entry = widget;
+ g_signal_connect (widget, "changed",
+ G_CALLBACK (entry_text_changed_cb), dialog);
+
+ 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 (alignment);
+
+
+ /* 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);
+ details->checkbox = widget;
+ g_signal_connect (widget, "toggled",
+ G_CALLBACK (checkbox_toggled_cb), dialog);
+
+ /* Add buttons */
+ gtk_dialog_add_buttons (dialog,
+ _("_Cancel"),
+ GTK_RESPONSE_CANCEL,
+ _("_Skip"),
+ CONFLICT_RESPONSE_SKIP,
+ NULL);
+ details->rename_button =
+ gtk_dialog_add_button (dialog,
+ _("Re_name"),
+ CONFLICT_RESPONSE_RENAME);
+ gtk_widget_hide (details->rename_button);
+
+ details->replace_button =
+ gtk_dialog_add_button (dialog,
+ _("Replace"),
+ CONFLICT_RESPONSE_REPLACE);
+ gtk_widget_grab_focus (details->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)
+{
+ NautilusFileConflictDialogDetails *details =
+ NAUTILUS_FILE_CONFLICT_DIALOG (self)->details;
+
+ g_free (details->conflict_name);
+
+ if (details->handle != NULL) {
+ nautilus_file_list_cancel_call_when_ready (details->handle);
+ }
+
+ if (details->src_handler_id) {
+ g_signal_handler_disconnect (details->source, details->src_handler_id);
+ nautilus_file_monitor_remove (details->source, self);
+ }
+
+ if (details->dest_handler_id) {
+ g_signal_handler_disconnect (details->destination, details->dest_handler_id);
+ nautilus_file_monitor_remove (details->destination, self);
+ }
+
+ nautilus_file_unref (details->source);
+ nautilus_file_unref (details->destination);
+ nautilus_file_unref (details->dest_dir);
+
+ 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;
+
+ g_type_class_add_private (klass, sizeof (NautilusFileConflictDialogDetails));
+}
+
+char *
+nautilus_file_conflict_dialog_get_new_name (NautilusFileConflictDialog *dialog)
+{
+ return g_strdup (gtk_entry_get_text
+ (GTK_ENTRY (dialog->details->entry)));
+}
+
+gboolean
+nautilus_file_conflict_dialog_get_apply_to_all (NautilusFileConflictDialog *dialog)
+{
+ return gtk_toggle_button_get_active
+ (GTK_TOGGLE_BUTTON (dialog->details->checkbox));
+}
+
+GtkWidget *
+nautilus_file_conflict_dialog_new (GtkWindow *parent,
+ GFile *source,
+ GFile *destination,
+ GFile *dest_dir)
+{
+ GtkWidget *dialog;
+ NautilusFile *src, *dest;
+ gboolean source_is_dir, dest_is_dir;
+
+ src = nautilus_file_get (source);
+ dest = nautilus_file_get (destination);
+
+ source_is_dir = nautilus_file_is_directory (src);
+ dest_is_dir = nautilus_file_is_directory (dest);
+
+ if (source_is_dir) {
+ dialog = GTK_WIDGET (g_object_new (NAUTILUS_TYPE_FILE_CONFLICT_DIALOG,
+ "use-header-bar", TRUE,
+ "modal", TRUE,
+ "title", dest_is_dir ? _("Merge Folder") : _("File and Folder conflict"),
+ NULL));
+ } else {
+ dialog = GTK_WIDGET (g_object_new (NAUTILUS_TYPE_FILE_CONFLICT_DIALOG,
+ "use-header-bar", TRUE,
+ "modal", TRUE,
+ "title", dest_is_dir ? _("File and Folder conflict") : _("File conflict"),
+ NULL));
+ }
+
+ set_source_and_destination (dialog,
+ source,
+ destination,
+ dest_dir);
+ gtk_window_set_transient_for (GTK_WINDOW (dialog),
+ parent);
+
+ g_object_unref (src);
+ g_object_unref (dest);
+
+ return dialog;
+}
diff --git a/src/nautilus-file-conflict-dialog.h b/src/nautilus-file-conflict-dialog.h
new file mode 100644
index 000000000..b7767ce6d
--- /dev/null
+++ b/src/nautilus-file-conflict-dialog.h
@@ -0,0 +1,75 @@
+
+/* 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>
+*/
+
+#ifndef NAUTILUS_FILE_CONFLICT_DIALOG_H
+#define NAUTILUS_FILE_CONFLICT_DIALOG_H
+
+#include <glib-object.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+#define NAUTILUS_TYPE_FILE_CONFLICT_DIALOG \
+ (nautilus_file_conflict_dialog_get_type ())
+#define NAUTILUS_FILE_CONFLICT_DIALOG(o) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((o), NAUTILUS_TYPE_FILE_CONFLICT_DIALOG,\
+ NautilusFileConflictDialog))
+#define NAUTILUS_FILE_CONFLICT_DIALOG_CLASS(k) \
+ (G_TYPE_CHECK_CLASS_CAST((k), NAUTILUS_TYPE_FILE_CONFLICT_DIALOG,\
+ NautilusFileConflictDialogClass))
+#define NAUTILUS_IS_FILE_CONFLICT_DIALOG(o) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((o), NAUTILUS_TYPE_FILE_CONFLICT_DIALOG))
+#define NAUTILUS_IS_FILE_CONFLICT_DIALOG_CLASS(k) \
+ (G_TYPE_CHECK_CLASS_TYPE ((k), NAUTILUS_TYPE_FILE_CONFLICT_DIALOG))
+#define NAUTILUS_FILE_CONFLICT_DIALOG_GET_CLASS(o) \
+ (G_TYPE_INSTANCE_GET_CLASS ((o), NAUTILUS_TYPE_FILE_CONFLICT_DIALOG,\
+ NautilusFileConflictDialogClass))
+
+typedef struct _NautilusFileConflictDialog NautilusFileConflictDialog;
+typedef struct _NautilusFileConflictDialogClass NautilusFileConflictDialogClass;
+typedef struct _NautilusFileConflictDialogDetails NautilusFileConflictDialogDetails;
+
+struct _NautilusFileConflictDialog {
+ GtkDialog parent;
+ NautilusFileConflictDialogDetails *details;
+};
+
+struct _NautilusFileConflictDialogClass {
+ GtkDialogClass parent_class;
+};
+
+enum
+{
+ CONFLICT_RESPONSE_SKIP = 1,
+ CONFLICT_RESPONSE_REPLACE = 2,
+ CONFLICT_RESPONSE_RENAME = 3,
+};
+
+GType nautilus_file_conflict_dialog_get_type (void) G_GNUC_CONST;
+
+GtkWidget* nautilus_file_conflict_dialog_new (GtkWindow *parent,
+ GFile *source,
+ GFile *destination,
+ GFile *dest_dir);
+char* nautilus_file_conflict_dialog_get_new_name (NautilusFileConflictDialog *dialog);
+gboolean nautilus_file_conflict_dialog_get_apply_to_all (NautilusFileConflictDialog *dialog);
+
+#endif /* NAUTILUS_FILE_CONFLICT_DIALOG_H */
diff --git a/src/nautilus-file-operations.c b/src/nautilus-file-operations.c
new file mode 100644
index 000000000..e25c260fd
--- /dev/null
+++ b/src/nautilus-file-operations.c
@@ -0,0 +1,7243 @@
+
+/* 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-gtk-extensions.h>
+#include <eel/eel-stock-dialogs.h>
+#include <eel/eel-vfs-extensions.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-file-changes-queue.h"
+#include "nautilus-file-private.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-link.h"
+#include "nautilus-trash-monitor.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-file-conflict-dialog.h"
+#include "nautilus-file-undo-operations.h"
+#include "nautilus-file-undo-manager.h"
+
+/* TODO: TESTING!!! */
+
+typedef struct {
+ GTimer *time;
+ GtkWindow *parent_window;
+ int screen_num;
+ 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 *desktop_location;
+ GFile *fake_display_source;
+ GdkPoint *icon_positions;
+ int n_icon_positions;
+ 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;
+ GdkPoint position;
+ gboolean has_position;
+ 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
+} 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;
+
+#define SECONDS_NEEDED_FOR_RELIABLE_TRANSFER_RATE 8
+#define NSEC_PER_MICROSEC 1000
+
+#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")
+
+static void
+mark_desktop_file_trusted (CommonJob *common,
+ GCancellable *cancellable,
+ GFile *file,
+ gboolean interactive);
+
+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);
+
+/* keep in time with format_time()
+ *
+ * This counts and outputs the number of “time units”
+ * formatted and displayed by format_time().
+ * For instance, if format_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 char *
+format_time (int seconds)
+{
+ int minutes;
+ int hours;
+ char *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) {
+ char *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 ("approximately %'d hour",
+ "approximately %'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;
+ }
+
+ 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);
+ }
+ }
+
+ 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)
+{
+ 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;
+ if (**suffix != '\0') {
+ *name_base = extract_string_until (name, *suffix);
+ } else {
+ *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;
+
+ }
+
+ 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);
+ }
+ }
+
+ return result;
+}
+
+static char *
+get_duplicate_name (const char *name, int count_increment, int max_length)
+{
+ char *result;
+ char *name_base;
+ const char *suffix;
+ int count;
+
+ parse_previous_duplicate_name (name, &name_base, &suffix, &count);
+ 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 char *
+custom_full_name_to_string (char *format, va_list va)
+{
+ GFile *file;
+
+ file = va_arg (va, GFile *);
+
+ return g_file_get_parse_name (file);
+}
+
+static void
+custom_full_name_skip (va_list *va)
+{
+ (void) va_arg (*va, GFile *);
+}
+
+static char *
+custom_basename_to_string (char *format, va_list va)
+{
+ GFile *file;
+ GFileInfo *info;
+ char *name, *basename, *tmp;
+ GMount *mount;
+
+ file = va_arg (va, GFile *);
+
+ 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 void
+custom_basename_skip (va_list *va)
+{
+ (void) va_arg (*va, GFile *);
+}
+
+
+static char *
+custom_size_to_string (char *format, va_list va)
+{
+ goffset size;
+
+ size = va_arg (va, goffset);
+ return g_format_size (size);
+}
+
+static void
+custom_size_skip (va_list *va)
+{
+ (void) va_arg (*va, goffset);
+}
+
+static char *
+custom_time_to_string (char *format, va_list va)
+{
+ int secs;
+
+ secs = va_arg (va, int);
+ return format_time (secs);
+}
+
+static void
+custom_time_skip (va_list *va)
+{
+ (void) va_arg (*va, int);
+}
+
+static char *
+custom_mount_to_string (char *format, va_list va)
+{
+ GMount *mount;
+
+ mount = va_arg (va, GMount *);
+ return g_mount_get_name (mount);
+}
+
+static void
+custom_mount_skip (va_list *va)
+{
+ (void) va_arg (*va, GMount *);
+}
+
+
+static EelPrintfHandler handlers[] = {
+ { 'F', custom_full_name_to_string, custom_full_name_skip },
+ { 'B', custom_basename_to_string, custom_basename_skip },
+ { 'S', custom_size_to_string, custom_size_skip },
+ { 'T', custom_time_to_string, custom_time_skip },
+ { 'V', custom_mount_to_string, custom_mount_skip },
+ { 0 }
+};
+
+
+static char *
+f (const char *format, ...) {
+ va_list va;
+ char *res;
+
+ va_start (va, format);
+ res = eel_strdup_vprintf_with_custom (handlers, format, va);
+ va_end (va);
+
+ return res;
+}
+
+#define op_job_new(__type, parent_window) ((__type *)(init_common (sizeof(__type), parent_window)))
+
+static gpointer
+init_common (gsize job_size,
+ GtkWindow *parent_window)
+{
+ CommonJob *common;
+ GdkScreen *screen;
+
+ 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);
+
+ }
+ common->progress = nautilus_progress_info_new ();
+ common->cancellable = nautilus_progress_info_get_cancellable (common->progress);
+ common->time = g_timer_new ();
+ common->inhibit_cookie = 0;
+ common->screen_num = 0;
+ if (parent_window) {
+ screen = gtk_widget_get_screen (GTK_WIDGET (parent_window));
+ common->screen_num = gdk_screen_get_number (screen);
+ }
+
+ 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->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)
+{
+ if (g_file_has_uri_scheme (file, "burn") ||
+ g_file_has_uri_scheme (file, "recent") ||
+ g_file_has_uri_scheme (file, "x-nautilus-desktop")) {
+ 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;
+ 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 gboolean
+do_run_simple_dialog (gpointer _data)
+{
+ RunSimpleDialogData *data = _data;
+ const char *button_title;
+ GtkWidget *dialog;
+ 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;
+ }
+
+ gtk_dialog_add_button (GTK_DIALOG (dialog), button_title, response_id);
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), response_id);
+ }
+
+ if (data->details_text) {
+ eel_gtk_message_dialog_set_details_label (GTK_MESSAGE_DIALOG (dialog),
+ data->details_text);
+ }
+
+ /* 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->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) {
+ prompt = f (_("Are you sure you want to permanently delete “%B” "
+ "from the trash?"), files->data);
+ } else {
+ prompt = f (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,
+ f (_("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 = f (_("Empty all items from Trash?"));
+
+ response = run_warning (job,
+ prompt,
+ f(_("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) {
+ prompt = f (_("Are you sure you want to permanently delete “%B”?"),
+ files->data);
+ } else {
+ prompt = f (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,
+ f (_("If you delete an item, it will be permanently lost.")),
+ NULL,
+ FALSE,
+ CANCEL, DELETE,
+ NULL);
+
+ return response == 1;
+}
+
+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) {
+ if (files_left == 0) {
+ status = _("Deleted “%B”");
+ } else {
+ status = _("Deleting “%B”");
+ }
+ nautilus_progress_info_take_status (job->progress,
+ f (status,
+ (GFile*) delete_job->files->data));
+
+ } 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,
+ f (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 = f (_("%'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 = f (_("%'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;
+
+ /* To translators: %T 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 %T argument).
+ */
+ time_left_message = ngettext ("%'d / %'d \xE2\x80\x94 %T left",
+ "%'d / %'d \xE2\x80\x94 %T 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);
+
+ details = f (concat_detail,
+ transfer_info->num_files + 1, source_info->num_files,
+ remaining_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 = f (_("%'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);
+ }
+}
+
+static void delete_file (CommonJob *job, GFile *file,
+ gboolean *skipped_file,
+ SourceInfo *source_info,
+ TransferInfo *transfer_info,
+ gboolean toplevel);
+
+static void
+delete_dir (CommonJob *job, GFile *dir,
+ gboolean *skipped_file,
+ SourceInfo *source_info,
+ TransferInfo *transfer_info,
+ gboolean toplevel)
+{
+ GFileInfo *info;
+ GError *error;
+ GFile *file;
+ GFileEnumerator *enumerator;
+ char *primary, *secondary, *details;
+ int response;
+ gboolean skip_error;
+ gboolean local_skipped_file;
+
+ local_skipped_file = FALSE;
+
+ skip_error = should_skip_readdir_error (job, dir);
+ retry:
+ error = NULL;
+ enumerator = g_file_enumerate_children (dir,
+ 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) {
+ file = g_file_get_child (dir,
+ g_file_info_get_name (info));
+ delete_file (job, file, &local_skipped_file, source_info, transfer_info, FALSE);
+ g_object_unref (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) {
+ primary = f (_("Error while deleting."));
+ details = NULL;
+
+ if (IS_IO_ERROR (error, PERMISSION_DENIED)) {
+ secondary = f (_("Files in the folder “%B” cannot be deleted because you do "
+ "not have permissions to see them."), dir);
+ } else {
+ secondary = f (_("There was an error getting information about the files in the folder “%B”."), dir);
+ 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 ();
+ }
+ }
+
+ } else if (IS_IO_ERROR (error, CANCELLED)) {
+ g_error_free (error);
+ } else {
+ primary = f (_("Error while deleting."));
+ details = NULL;
+ if (IS_IO_ERROR (error, PERMISSION_DENIED)) {
+ secondary = f (_("The folder “%B” cannot be deleted because you do not have "
+ "permissions to read it."), dir);
+ } else {
+ secondary = f (_("There was an error reading the folder “%B”."), dir);
+ 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 (!job_aborted (job) &&
+ /* Don't delete dir if there was a skipped file */
+ !local_skipped_file) {
+ if (!g_file_delete (dir, job->cancellable, &error)) {
+ if (job->skip_all_error) {
+ goto skip;
+ }
+ primary = f (_("Error while deleting."));
+ secondary = f (_("Could not remove the folder %B."), dir);
+ 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);
+ } else {
+ nautilus_file_changes_queue_file_removed (dir);
+ transfer_info->num_files ++;
+ report_delete_progress (job, source_info, transfer_info);
+ return;
+ }
+ }
+
+ if (local_skipped_file) {
+ *skipped_file = TRUE;
+ }
+}
+
+static void
+delete_file (CommonJob *job, GFile *file,
+ gboolean *skipped_file,
+ SourceInfo *source_info,
+ TransferInfo *transfer_info,
+ gboolean toplevel)
+{
+ GError *error;
+ char *primary, *secondary, *details;
+ int response;
+
+ if (should_skip_file (job, file)) {
+ *skipped_file = TRUE;
+ return;
+ }
+
+ error = NULL;
+ if (g_file_delete (file, job->cancellable, &error)) {
+ nautilus_file_changes_queue_file_removed (file);
+ transfer_info->num_files ++;
+ report_delete_progress (job, source_info, transfer_info);
+ return;
+ }
+
+ if (IS_IO_ERROR (error, NOT_EMPTY)) {
+ g_error_free (error);
+ delete_dir (job, file,
+ skipped_file,
+ source_info, transfer_info,
+ toplevel);
+ return;
+
+ } else if (IS_IO_ERROR (error, CANCELLED)) {
+ g_error_free (error);
+
+ } else {
+ if (job->skip_all_error) {
+ goto skip;
+ }
+ primary = f (_("Error while deleting."));
+ secondary = f (_("There was an error deleting %B."), file);
+ 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;
+ } else if (response == 2) { /* skip */
+ /* do nothing */
+ } else {
+ g_assert_not_reached ();
+ }
+ skip:
+ g_error_free (error);
+ }
+
+ *skipped_file = TRUE;
+}
+
+static void
+delete_files (CommonJob *job, GList *files, int *files_skipped)
+{
+ GList *l;
+ GFile *file;
+ SourceInfo source_info;
+ TransferInfo transfer_info;
+ gboolean skipped_file;
+
+ 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);
+
+ for (l = files;
+ l != NULL && !job_aborted (job);
+ l = l->next) {
+ file = l->data;
+
+ skipped_file = FALSE;
+ delete_file (job, file,
+ &skipped_file,
+ &source_info, &transfer_info,
+ TRUE);
+ if (skipped_file) {
+ (*files_skipped)++;
+ }
+ }
+}
+
+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) {
+ if (files_left > 0) {
+ status = _("Trashing “%B”");
+ } else {
+ status = _("Trashed “%B”");
+ }
+ nautilus_progress_info_take_status (job->progress,
+ f (status,
+ (GFile*) delete_job->files->data));
+
+ } 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,
+ f (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 = f (_("%'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 = f (_("%'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;
+
+ /* To translators: %T 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 %T argument).
+ */
+ time_left_message = ngettext ("%'d / %'d \xE2\x80\x94 %T left",
+ "%'d / %'d \xE2\x80\x94 %T 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);
+
+ details = f (concat_detail,
+ transfer_info->num_files + 1, source_info->num_files,
+ remaining_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 = f (_("%'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);
+ }
+}
+
+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;
+
+ 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;
+ }
+
+ /* Translators: %B is a file name */
+ primary = f (_("“%B” can't be put in the trash. Do you want to delete it immediately?"), file);
+ details = NULL;
+ secondary = NULL;
+ if (!IS_IO_ERROR (error, NOT_SUPPORTED)) {
+ details = error->message;
+ } else if (!g_file_is_native (file)) {
+ secondary = f (_("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
+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)++;
+ }
+ }
+
+ 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
+delete_task_thread_func (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ DeleteJob *job = task_data;
+ GList *to_trash_files;
+ GList *to_delete_files;
+ 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);
+
+ to_trash_files = NULL;
+ to_delete_files = NULL;
+
+ 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);
+ }
+
+ g_list_free (to_trash_files);
+ g_list_free (to_delete_files);
+
+ if (files_skipped == g_list_length (job->files)) {
+ /* User has skipped all files, report user cancel */
+ job->user_cancel = TRUE;
+ }
+}
+
+static void
+trash_or_delete_internal (GList *files,
+ GtkWindow *parent_window,
+ gboolean try_trash,
+ NautilusDeleteCallback done_callback,
+ gpointer done_callback_data)
+{
+ GTask *task;
+ DeleteJob *job;
+
+ /* TODO: special case desktop icon link files ... */
+
+ job = op_job_new (DeleteJob, parent_window);
+ 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 (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));
+ }
+
+ task = g_task_new (NULL, NULL, delete_task_done, job);
+ g_task_set_task_data (task, job, NULL);
+ g_task_run_in_thread (task, delete_task_thread_func);
+ g_object_unref (task);
+}
+
+void
+nautilus_file_operations_trash_or_delete (GList *files,
+ GtkWindow *parent_window,
+ NautilusDeleteCallback done_callback,
+ gpointer done_callback_data)
+{
+ trash_or_delete_internal (files, parent_window,
+ TRUE,
+ done_callback, done_callback_data);
+}
+
+void
+nautilus_file_operations_delete (GList *files,
+ GtkWindow *parent_window,
+ NautilusDeleteCallback done_callback,
+ gpointer done_callback_data)
+{
+ trash_or_delete_internal (files, parent_window,
+ 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) {
+ if (data->eject) {
+ primary = f (_("Unable to eject %V"), source_object);
+ } else {
+ primary = f (_("Unable to unmount %V"), source_object);
+ }
+ eel_show_error_dialog (primary,
+ error->message,
+ data->parent_window);
+ 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);
+ }
+ 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);
+ gtk_window_set_wmclass (GTK_WINDOW (dialog), "empty_trash",
+ "Nautilus");
+
+ /* 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);
+ 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;
+ eel_show_error_dialog (primary,
+ error->message,
+ parent);
+ 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:
+ s = f (ngettext("Preparing to copy %'d file (%S)",
+ "Preparing to copy %'d files (%S)",
+ source_info->num_files),
+ source_info->num_files, source_info->num_bytes);
+ break;
+ case OP_KIND_MOVE:
+ s = f (ngettext("Preparing to move %'d file (%S)",
+ "Preparing to move %'d files (%S)",
+ source_info->num_files),
+ source_info->num_files, source_info->num_bytes);
+ break;
+ case OP_KIND_DELETE:
+ s = f (ngettext("Preparing to delete %'d file (%S)",
+ "Preparing to delete %'d files (%S)",
+ source_info->num_files),
+ source_info->num_files, source_info->num_bytes);
+ break;
+ case OP_KIND_TRASH:
+ s = f (ngettext("Preparing to trash %'d file",
+ "Preparing to trash %'d files",
+ source_info->num_files),
+ source_info->num_files);
+ break;
+ }
+
+ 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 f (_("Error while copying."));
+ case OP_KIND_MOVE:
+ return f (_("Error while moving."));
+ case OP_KIND_DELETE:
+ return f (_("Error while deleting."));
+ case OP_KIND_TRASH:
+ return f (_("Error while moving files to trash."));
+ }
+}
+
+static void
+scan_dir (GFile *dir,
+ SourceInfo *source_info,
+ CommonJob *job,
+ GQueue *dirs)
+{
+ GFileInfo *info;
+ GError *error;
+ GFile *subdir;
+ GFileEnumerator *enumerator;
+ char *primary, *secondary, *details;
+ int response;
+ SourceInfo saved_info;
+
+ 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) {
+ 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) {
+ primary = get_scan_primary (source_info->op);
+ details = NULL;
+
+ if (IS_IO_ERROR (error, PERMISSION_DENIED)) {
+ secondary = f (_("Files in the folder “%B” cannot be handled because you do "
+ "not have permissions to see them."), dir);
+ } else {
+ secondary = f (_("There was an error getting information about the files in the folder “%B”."), dir);
+ 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 {
+ primary = get_scan_primary (source_info->op);
+ details = NULL;
+
+ if (IS_IO_ERROR (error, PERMISSION_DENIED)) {
+ secondary = f (_("The folder “%B” cannot be handled because you do not have "
+ "permissions to read it."), dir);
+ } else {
+ secondary = f (_("There was an error reading the folder “%B”."), dir);
+ 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)
+{
+ GFileInfo *info;
+ GError *error;
+ GQueue *dirs;
+ GFile *dir;
+ char *primary;
+ char *secondary;
+ char *details;
+ int response;
+
+ dirs = g_queue_new ();
+
+ retry:
+ error = NULL;
+ info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_STANDARD_TYPE","
+ G_FILE_ATTRIBUTE_STANDARD_SIZE,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ job->cancellable,
+ &error);
+
+ if (info) {
+ count_file (info, job, source_info);
+
+ if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) {
+ 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 {
+ primary = get_scan_primary (source_info->op);
+ details = NULL;
+
+ if (IS_IO_ERROR (error, PERMISSION_DENIED)) {
+ secondary = f (_("The file “%B” cannot be handled because you do not have "
+ "permissions to read it."), file);
+ } else {
+ secondary = f (_("There was an error getting information about “%B”."), file);
+ details = error->message;
+ }
+ /* set show_all to TRUE here, as we don't know how many
+ * files we'll end up processing yet.
+ */
+ response = run_warning (job,
+ primary,
+ secondary,
+ details,
+ TRUE,
+ CANCEL, SKIP_ALL, SKIP, RETRY,
+ NULL);
+
+ g_error_free (error);
+
+ if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
+ abort_job (job);
+ } else if (response == 1 || response == 2) {
+ if (response == 1) {
+ job->skip_all_error = TRUE;
+ }
+ skip_file (job, file);
+ } else if (response == 3) {
+ goto retry;
+ } else {
+ g_assert_not_reached ();
+ }
+ }
+
+ while (!job_aborted (job) &&
+ (dir = g_queue_pop_head (dirs)) != NULL) {
+ scan_dir (dir, source_info, job, dirs);
+ g_object_unref (dir);
+ }
+
+ /* Free all from queue if we exited early */
+ g_queue_foreach (dirs, (GFunc)g_object_unref, NULL);
+ g_queue_free (dirs);
+}
+
+static void
+scan_sources (GList *files,
+ SourceInfo *source_info,
+ CommonJob *job,
+ OpKind kind)
+{
+ GList *l;
+ GFile *file;
+
+ memset (source_info, 0, sizeof (SourceInfo));
+ source_info->op = kind;
+
+ report_preparing_count_progress (job, source_info);
+
+ for (l = files; l != NULL && !job_aborted (job); l = l->next) {
+ file = l->data;
+
+ scan_file (file,
+ source_info,
+ job);
+ }
+
+ /* Make sure we report the final count */
+ report_preparing_count_progress (job, source_info);
+}
+
+static void
+verify_destination (CommonJob *job,
+ GFile *dest,
+ char **dest_fs_id,
+ goffset required_size)
+{
+ GFileInfo *info, *fsinfo;
+ GError *error;
+ 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) {
+ if (IS_IO_ERROR (error, CANCELLED)) {
+ g_error_free (error);
+ return;
+ }
+
+ primary = f (_("Error while copying to “%B”."), dest);
+ details = NULL;
+
+ if (IS_IO_ERROR (error, PERMISSION_DENIED)) {
+ secondary = f (_("You do not have permissions to access the destination folder."));
+ } else {
+ secondary = f (_("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) {
+ primary = f (_("Error while copying to “%B”."), dest);
+ secondary = f (_("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) {
+ size_difference = required_size - free_size;
+ primary = f (_("Error while copying to “%B”."), dest);
+ secondary = f (_("There is not enough space on the destination. Try to remove files to make space."));
+
+ details = f (_("%S more space is required to copy to the destination."), size_difference);
+
+ 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)) {
+ primary = f (_("Error while copying to “%B”."), dest);
+ secondary = f (_("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);
+}
+
+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;
+
+ 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) {
+ if (copy_job->destination != NULL) {
+ if (is_move) {
+ if (files_left > 0) {
+ status = _("Moving “%B” to “%B”");
+ } else {
+ status = _("Moved “%B” to “%B”");
+ }
+ } else {
+ if (files_left > 0) {
+ status = _("Copying “%B” to “%B”");
+ } else {
+ status = _("Copied “%B” to “%B”");
+ }
+ }
+ nautilus_progress_info_take_status (job->progress,
+ f (status,
+ copy_job->fake_display_source != NULL ?
+ copy_job->fake_display_source :
+ (GFile *)copy_job->files->data,
+ copy_job->destination));
+ } else {
+ if (files_left > 0) {
+ status = _("Duplicating “%B”");
+ } else {
+ status = _("Duplicated “%B”");
+ }
+ nautilus_progress_info_take_status (job->progress,
+ f (status,
+ (GFile *)copy_job->files->data));
+ }
+ } else if (copy_job->files != NULL) {
+ if (copy_job->destination != NULL) {
+ if (files_left > 0) {
+ if (is_move) {
+ status = ngettext ("Moving %'d file to “%B”",
+ "Moving %'d files to “%B”",
+ source_info->num_files);
+ } else {
+ status = ngettext ("Copying %'d file to “%B”",
+ "Copying %'d files to “%B”",
+ source_info->num_files);
+ }
+ nautilus_progress_info_take_status (job->progress,
+ f (status,
+ source_info->num_files,
+ (GFile *)copy_job->destination));
+ } else {
+ if (is_move) {
+ status = ngettext ("Moved %'d file to “%B”",
+ "Moved %'d files to “%B”",
+ source_info->num_files);
+ } else {
+ status = ngettext ("Copied %'d file to “%B”",
+ "Copied %'d files to “%B”",
+ source_info->num_files);
+ }
+ nautilus_progress_info_take_status (job->progress,
+ f (status,
+ source_info->num_files,
+ (GFile *)copy_job->destination));
+ }
+ } else {
+ GFile *parent;
+
+ parent = g_file_get_parent (copy_job->files->data);
+ if (files_left > 0) {
+ status = ngettext ("Duplicating %'d file in “%B”",
+ "Duplicating %'d files in “%B”",
+ transfer_info->num_files + 1);
+ nautilus_progress_info_take_status (job->progress,
+ f (status,
+ source_info->num_files + 1,
+ parent));
+ } else {
+ status = ngettext ("Duplicated %'d file in “%B”",
+ "Duplicated %'d files in “%B”",
+ source_info->num_files);
+ nautilus_progress_info_take_status (job->progress,
+ f (status,
+ source_info->num_files,
+ parent));
+ }
+ 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) {
+ /* To translators: %S will expand to a size like "2 bytes" or "3 MB", so something like "4 kb / 4 MB" */
+ details = f (_("%S / %S"), transfer_info->num_bytes, 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 = f (_("%'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 = f (_("%'d / %'d"),
+ transfer_info->num_files,
+ source_info->num_files);
+ }
+ }
+ } else {
+ if (source_info->num_files == 1) {
+ if (files_left > 0) {
+ /* To translators: %S will expand to a size like "2 bytes" or "3 MB", %T 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 %T argument).
+ */
+ details = f (ngettext ("%S / %S \xE2\x80\x94 %T left (%S/sec)",
+ "%S / %S \xE2\x80\x94 %T left (%S/sec)",
+ seconds_count_format_time_units (remaining_time)),
+ transfer_info->num_bytes, total_size,
+ remaining_time,
+ (goffset)transfer_rate);
+ } else {
+ /* To translators: %S will expand to a size like "2 bytes" or "3 MB". */
+ details = f (_("%S / %S"),
+ transfer_info->num_bytes,
+ total_size);
+ }
+ } else {
+ if (files_left > 0) {
+ /* To translators: %T 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 %T argument).
+ */
+ details = f (ngettext ("%'d / %'d \xE2\x80\x94 %T left (%S/sec)",
+ "%'d / %'d \xE2\x80\x94 %T left (%S/sec)",
+ seconds_count_format_time_units (remaining_time)),
+ transfer_info->num_files + 1, source_info->num_files,
+ remaining_time,
+ (goffset)transfer_rate);
+ } else {
+ /* To translators: %'d is the number of files completed for the operation,
+ * so it will be something like 2/14. */
+ details = f (_("%'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);
+}
+
+static int
+get_max_name_length (GFile *file_dir)
+{
+ int max_length;
+ char *dir;
+ long max_path;
+ long max_name;
+
+ max_length = -1;
+
+ if (!g_file_has_uri_scheme (file_dir, "file"))
+ return max_length;
+
+ dir = g_file_get_path (file_dir);
+ if (!dir)
+ return max_length;
+
+ max_path = pathconf (dir, _PC_PATH_MAX);
+ max_name = pathconf (dir, _PC_NAME_MAX);
+
+ if (max_name == -1 && max_path == -1) {
+ max_length = -1;
+ } else if (max_name == -1 && max_path != -1) {
+ max_length = max_path - (strlen (dir) + 1);
+ } else if (max_name != -1 && max_path == -1) {
+ max_length = max_name;
+ } else {
+ int leftover;
+
+ leftover = max_path - (strlen (dir) + 1);
+
+ max_length = MIN (leftover, max_name);
+ }
+
+ g_free (dir);
+
+ return max_length;
+}
+
+#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") ||
+ !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;
+
+ max_length = get_max_name_length (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_duplicate_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);
+
+ if (g_utf8_validate (basename, -1, NULL)) {
+ new_name = get_duplicate_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) {
+ 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 = get_max_name_length (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)
+{
+ GFileInfo *info;
+ gboolean res;
+
+ res = FALSE;
+ info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_STANDARD_TYPE,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ NULL, NULL);
+ if (info) {
+ res = g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY;
+ g_object_unref (info);
+ }
+
+ return res;
+}
+
+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,
+ GdkPoint *point,
+ 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) {
+ 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 = f (_("Error while copying."));
+ details = NULL;
+
+ if (IS_IO_ERROR (error, PERMISSION_DENIED)) {
+ secondary = f (_("The folder “%B” cannot be copied because you do not have "
+ "permissions to create it in the destination."), src);
+ } else {
+ secondary = f (_("There was an error creating the folder “%B”."), src);
+ 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, NULL, FALSE, &local_skipped_file,
+ readonly_source_fs);
+ 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) {
+ if (copy_job->is_move) {
+ primary = f (_("Error while moving."));
+ } else {
+ primary = f (_("Error while copying."));
+ }
+ details = NULL;
+
+ if (IS_IO_ERROR (error, PERMISSION_DENIED)) {
+ secondary = f (_("Files in the folder “%B” cannot be copied because you do "
+ "not have permissions to see them."), src);
+ } else {
+ secondary = f (_("There was an error getting information about the files in the folder “%B”."), src);
+ 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 {
+ if (copy_job->is_move) {
+ primary = f (_("Error while moving."));
+ } else {
+ primary = f (_("Error while copying."));
+ }
+ details = NULL;
+
+ if (IS_IO_ERROR (error, PERMISSION_DENIED)) {
+ secondary = f (_("The folder “%B” cannot be copied because you do not have "
+ "permissions to read it."), src);
+ } else {
+ secondary = f (_("There was an error reading the folder “%B”."), src);
+ 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)) {
+ if (job->skip_all_error) {
+ goto skip;
+ }
+ primary = f (_("Error while moving “%B”."), src);
+ secondary = f (_("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;
+}
+
+static gboolean
+remove_target_recursively (CommonJob *job,
+ GFile *src,
+ GFile *toplevel_dest,
+ GFile *file)
+{
+ GFileEnumerator *enumerator;
+ GError *error;
+ GFile *child;
+ gboolean stop;
+ char *primary, *secondary, *details;
+ int response;
+ GFileInfo *info;
+
+ stop = FALSE;
+
+ error = NULL;
+ enumerator = g_file_enumerate_children (file,
+ 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, &error)) != NULL) {
+ child = g_file_get_child (file,
+ g_file_info_get_name (info));
+ if (!remove_target_recursively (job, src, toplevel_dest, child)) {
+ stop = TRUE;
+ break;
+ }
+ g_object_unref (child);
+ g_object_unref (info);
+ }
+ g_file_enumerator_close (enumerator, job->cancellable, NULL);
+ g_object_unref (enumerator);
+
+ } else if (IS_IO_ERROR (error, NOT_DIRECTORY)) {
+ /* Not a dir, continue */
+ g_error_free (error);
+
+ } else if (IS_IO_ERROR (error, CANCELLED)) {
+ g_error_free (error);
+ } else {
+ if (job->skip_all_error) {
+ goto skip1;
+ }
+
+ primary = f (_("Error while copying “%B”."), src);
+ secondary = f (_("Could not remove files from the already existing folder %F."), file);
+ 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,
+ 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 ();
+ }
+ skip1:
+ g_error_free (error);
+
+ stop = TRUE;
+ }
+
+ if (stop) {
+ return FALSE;
+ }
+
+ error = NULL;
+
+ if (!g_file_delete (file, job->cancellable, &error)) {
+ if (job->skip_all_error ||
+ IS_IO_ERROR (error, CANCELLED)) {
+ goto skip2;
+ }
+ primary = f (_("Error while copying “%B”."), src);
+ secondary = f (_("Could not remove the already existing file %F."), file);
+ 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,
+ 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 ();
+ }
+
+ skip2:
+ g_error_free (error);
+
+ return FALSE;
+ }
+ nautilus_file_changes_queue_file_removed (file);
+
+ return TRUE;
+
+}
+
+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 gboolean
+is_trusted_desktop_file (GFile *file,
+ GCancellable *cancellable)
+{
+ char *basename;
+ gboolean res;
+ GFileInfo *info;
+
+ /* Don't trust non-local files */
+ if (!g_file_is_native (file)) {
+ return FALSE;
+ }
+
+ basename = g_file_get_basename (file);
+ if (!g_str_has_suffix (basename, ".desktop")) {
+ g_free (basename);
+ return FALSE;
+ }
+ g_free (basename);
+
+ info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_STANDARD_TYPE ","
+ G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ cancellable,
+ NULL);
+
+ if (info == NULL) {
+ return FALSE;
+ }
+
+ res = FALSE;
+
+ /* Weird file => not trusted,
+ Already executable => no need to mark trusted */
+ if (g_file_info_get_file_type (info) == G_FILE_TYPE_REGULAR &&
+ !g_file_info_get_attribute_boolean (info,
+ G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE) &&
+ nautilus_is_in_system_dir (file)) {
+ res = TRUE;
+ }
+ g_object_unref (info);
+
+ return res;
+}
+
+typedef struct {
+ int id;
+ char *new_name;
+ gboolean apply_to_all;
+} ConflictResponseData;
+
+typedef struct {
+ GFile *src;
+ GFile *dest;
+ GFile *dest_dir;
+ GtkWindow *parent;
+ ConflictResponseData *resp_data;
+ /* Dialogs are ran from operation threads, which need to be blocked until
+ * the user gives a valid response
+ */
+ gboolean completed;
+ GMutex mutex;
+ GCond cond;
+} ConflictDialogData;
+
+static gboolean
+do_run_conflict_dialog (gpointer _data)
+{
+ ConflictDialogData *data = _data;
+ GtkWidget *dialog;
+ int response;
+
+ g_mutex_lock (&data->mutex);
+
+ dialog = nautilus_file_conflict_dialog_new (data->parent,
+ data->src,
+ data->dest,
+ data->dest_dir);
+ response = gtk_dialog_run (GTK_DIALOG (dialog));
+
+ if (response == CONFLICT_RESPONSE_RENAME) {
+ data->resp_data->new_name =
+ nautilus_file_conflict_dialog_get_new_name (NAUTILUS_FILE_CONFLICT_DIALOG (dialog));
+ } else if (response != GTK_RESPONSE_CANCEL ||
+ response != GTK_RESPONSE_NONE) {
+ data->resp_data->apply_to_all =
+ nautilus_file_conflict_dialog_get_apply_to_all
+ (NAUTILUS_FILE_CONFLICT_DIALOG (dialog));
+ }
+
+ data->resp_data->id = response;
+ data->completed = TRUE;
+
+ gtk_widget_destroy (dialog);
+
+ g_cond_signal (&data->cond);
+ g_mutex_unlock (&data->mutex);
+
+ return FALSE;
+}
+
+static ConflictResponseData *
+run_conflict_dialog (CommonJob *job,
+ GFile *src,
+ GFile *dest,
+ GFile *dest_dir)
+{
+ ConflictDialogData *data;
+ ConflictResponseData *resp_data;
+
+ g_timer_stop (job->time);
+
+ data = g_slice_new0 (ConflictDialogData);
+ data->parent = job->parent_window;
+ data->src = src;
+ data->dest = dest;
+ data->dest_dir = dest_dir;
+
+ resp_data = g_slice_new0 (ConflictResponseData);
+ resp_data->new_name = NULL;
+ data->resp_data = resp_data;
+
+ data->completed = FALSE;
+ g_mutex_init (&data->mutex);
+ g_cond_init (&data->cond);
+
+ nautilus_progress_info_pause (job->progress);
+
+ g_mutex_lock (&data->mutex);
+
+ g_main_context_invoke (NULL,
+ do_run_conflict_dialog,
+ data);
+
+ while (!data->completed) {
+ g_cond_wait (&data->cond, &data->mutex);
+ }
+
+ nautilus_progress_info_resume (job->progress);
+
+ g_mutex_unlock (&data->mutex);
+ g_mutex_clear (&data->mutex);
+ g_cond_clear (&data->cond);
+
+ g_slice_free (ConflictDialogData, data);
+
+ g_timer_continue (job->time);
+
+ return resp_data;
+}
+
+static void
+conflict_response_data_free (ConflictResponseData *data)
+{
+ g_free (data->new_name);
+ g_slice_free (ConflictResponseData, data);
+}
+
+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;
+}
+
+/* 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,
+ GdkPoint *position,
+ gboolean overwrite,
+ gboolean *skipped_file,
+ gboolean readonly_source_fs)
+{
+ GFile *dest, *new_dest;
+ GError *error;
+ GFileCopyFlags flags;
+ char *primary, *secondary, *details;
+ int response;
+ ProgressData pdata;
+ gboolean would_recurse, is_merge;
+ CommonJob *job;
+ gboolean res;
+ int unique_name_nr;
+ gboolean handled_invalid_filename;
+
+ job = (CommonJob *)copy_job;
+
+ 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 {
+ 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)) {
+ 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)) {
+ 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) {
+ if (position) {
+ nautilus_file_changes_queue_schedule_position_set (dest, *position, job->screen_num);
+ } else {
+ nautilus_file_changes_queue_schedule_position_remove (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 copying a trusted desktop file to the desktop,
+ mark it as trusted. */
+ if (copy_job->desktop_location != NULL &&
+ g_file_equal (copy_job->desktop_location, dest_dir) &&
+ is_trusted_desktop_file (src, job->cancellable)) {
+ mark_desktop_file_trusted (job,
+ job->cancellable,
+ dest,
+ FALSE);
+ }
+
+ 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 is_merge;
+ ConflictResponseData *response;
+
+ 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 (is_dir (dest) && is_dir (src)) {
+ is_merge = TRUE;
+ }
+
+ if ((is_merge && job->merge_all) ||
+ (!is_merge && job->replace_all)) {
+ overwrite = TRUE;
+ goto retry;
+ }
+
+ if (job->skip_all_conflict) {
+ goto out;
+ }
+
+ response = run_conflict_dialog (job, src, dest, dest_dir);
+
+ if (response->id == GTK_RESPONSE_CANCEL ||
+ response->id == GTK_RESPONSE_DELETE_EVENT) {
+ conflict_response_data_free (response);
+ abort_job (job);
+ } else if (response->id == CONFLICT_RESPONSE_SKIP) {
+ if (response->apply_to_all) {
+ job->skip_all_conflict = TRUE;
+ }
+ conflict_response_data_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;
+ conflict_response_data_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);
+ conflict_response_data_free (response);
+ goto retry;
+ } else {
+ g_assert_not_reached ();
+ }
+ }
+
+ else if (overwrite &&
+ IS_IO_ERROR (error, IS_DIRECTORY)) {
+
+ g_error_free (error);
+
+ if (remove_target_recursively (job, src, dest, dest)) {
+ goto retry;
+ }
+ }
+
+ /* Needs to recurse */
+ else if (IS_IO_ERROR (error, WOULD_RECURSE) ||
+ IS_IO_ERROR (error, WOULD_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)) {
+ if (job->skip_all_error) {
+ g_error_free (error);
+ goto out;
+ }
+ if (copy_job->is_move) {
+ primary = f (_("Error while moving “%B”."), src);
+ } else {
+ primary = f (_("Error while copying “%B”."), src);
+ }
+ secondary = f (_("Could not remove the already existing file with the same name in %F."), dest_dir);
+ 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 {
+ if (job->skip_all_error) {
+ g_error_free (error);
+ goto out;
+ }
+ primary = f (_("Error while copying “%B”."), src);
+ secondary = f (_("There was an error copying the file into %F."), dest_dir);
+ 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;
+ GdkPoint *point;
+ 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;
+
+ if (i < job->n_icon_positions) {
+ point = &job->icon_positions[i];
+ } else {
+ point = NULL;
+ }
+
+
+ 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,
+ point, FALSE, &skipped_file,
+ readonly_source_fs);
+ g_object_unref (dest);
+ }
+ 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);
+ }
+ if (job->desktop_location) {
+ g_object_unref (job->desktop_location);
+ }
+ g_hash_table_unref (job->debuting_files);
+ g_free (job->icon_positions);
+ g_free (job->target_name);
+
+ g_clear_object (&job->fake_display_source);
+
+ finalize_common ((CommonJob *)job);
+
+ nautilus_file_changes_consume_changes (TRUE);
+}
+
+static void
+copy_task_thread_func (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ CopyMoveJob *job;
+ CommonJob *common;
+ SourceInfo source_info;
+ TransferInfo transfer_info;
+ char *dest_fs_id;
+ GFile *dest;
+
+ job = task_data;
+ common = &job->common;
+
+ dest_fs_id = NULL;
+
+ nautilus_progress_info_start (job->common.progress);
+
+ scan_sources (job->files,
+ &source_info,
+ common,
+ OP_KIND_COPY);
+ if (job_aborted (common)) {
+ goto aborted;
+ }
+
+ 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)) {
+ goto aborted;
+ }
+
+ g_timer_start (job->common.time);
+
+ memset (&transfer_info, 0, sizeof (transfer_info));
+ copy_files (job,
+ dest_fs_id,
+ &source_info, &transfer_info);
+
+ aborted:
+
+ g_free (dest_fs_id);
+}
+
+void
+nautilus_file_operations_copy_file (GFile *source_file,
+ GFile *target_dir,
+ const gchar *source_display_name,
+ const gchar *new_name,
+ GtkWindow *parent_window,
+ NautilusCopyCallback done_callback,
+ gpointer done_callback_data)
+{
+ GTask *task;
+ CopyMoveJob *job;
+
+ job = op_job_new (CopyMoveJob, parent_window);
+ job->done_callback = done_callback;
+ job->done_callback_data = done_callback_data;
+ job->files = g_list_append (NULL, g_object_ref (source_file));
+ 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->target_name = g_strdup (new_name);
+ job->debuting_files = g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, g_object_unref, NULL);
+
+ if (source_display_name != NULL) {
+ gchar *path;
+
+ path = g_build_filename ("/", source_display_name, NULL);
+ job->fake_display_source = g_file_new_for_path (path);
+
+ g_free (path);
+ }
+
+ inhibit_power_manager ((CommonJob *)job, _("Copying Files"));
+
+ 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, copy_task_thread_func);
+ g_object_unref (task);
+}
+
+void
+nautilus_file_operations_copy (GList *files,
+ GArray *relative_item_points,
+ GFile *target_dir,
+ GtkWindow *parent_window,
+ NautilusCopyCallback done_callback,
+ gpointer done_callback_data)
+{
+ GTask *task;
+ CopyMoveJob *job;
+
+ job = op_job_new (CopyMoveJob, parent_window);
+ job->desktop_location = nautilus_get_desktop_location ();
+ 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);
+ if (relative_item_points != NULL &&
+ relative_item_points->len > 0) {
+ job->icon_positions =
+ g_memdup (relative_item_points->data,
+ sizeof (GdkPoint) * relative_item_points->len);
+ job->n_icon_positions = relative_item_points->len;
+ }
+ job->debuting_files = g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, g_object_unref, NULL);
+
+ inhibit_power_manager ((CommonJob *)job, _("Copying Files"));
+
+ if (!nautilus_file_undo_manager_is_operating ()) {
+ GFile* src_dir;
+
+ src_dir = g_file_get_parent (files->data);
+ job->common.undo_info = nautilus_file_undo_info_ext_new (NAUTILUS_FILE_UNDO_OP_COPY,
+ g_list_length (files),
+ src_dir, target_dir);
+
+ g_object_unref (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, copy_task_thread_func);
+ g_object_unref (task);
+}
+
+static void
+report_preparing_move_progress (CopyMoveJob *move_job, int total, int left)
+{
+ CommonJob *job;
+
+ job = (CommonJob *)move_job;
+
+ nautilus_progress_info_take_status (job->progress,
+ f (_("Preparing to move to “%B”"),
+ move_job->destination));
+
+ nautilus_progress_info_take_details (job->progress,
+ f (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;
+ gboolean has_position;
+ GdkPoint position;
+} MoveFileCopyFallback;
+
+static MoveFileCopyFallback *
+move_copy_file_callback_new (GFile *file,
+ gboolean overwrite,
+ GdkPoint *position)
+{
+ MoveFileCopyFallback *fallback;
+
+ fallback = g_new (MoveFileCopyFallback, 1);
+ fallback->file = file;
+ fallback->overwrite = overwrite;
+ if (position) {
+ fallback->has_position = TRUE;
+ fallback->position = *position;
+ } else {
+ fallback->has_position = FALSE;
+ }
+
+ 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,
+ GdkPoint *position,
+ GList **fallback_files,
+ int files_left)
+{
+ GFile *dest, *new_dest;
+ GError *error;
+ CommonJob *job;
+ gboolean overwrite;
+ char *primary, *secondary, *details;
+ int response;
+ GFileCopyFlags flags;
+ MoveFileCopyFallback *fallback;
+ gboolean handled_invalid_filename;
+
+ overwrite = FALSE;
+ handled_invalid_filename = *dest_fs_type != NULL;
+
+ job = (CommonJob *)move_job;
+
+ 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)) {
+ 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);
+
+ if (position) {
+ nautilus_file_changes_queue_schedule_position_set (dest, *position, job->screen_num);
+ } else {
+ nautilus_file_changes_queue_schedule_position_remove (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 is_merge;
+ ConflictResponseData *response;
+
+ g_error_free (error);
+
+ is_merge = FALSE;
+ if (is_dir (dest) && is_dir (src)) {
+ is_merge = TRUE;
+ }
+
+ if ((is_merge && job->merge_all) ||
+ (!is_merge && job->replace_all)) {
+ overwrite = TRUE;
+ goto retry;
+ }
+
+ if (job->skip_all_conflict) {
+ goto out;
+ }
+
+ response = run_conflict_dialog (job, src, dest, dest_dir);
+
+ if (response->id == GTK_RESPONSE_CANCEL ||
+ response->id == GTK_RESPONSE_DELETE_EVENT) {
+ conflict_response_data_free (response);
+ abort_job (job);
+ } else if (response->id == CONFLICT_RESPONSE_SKIP) {
+ if (response->apply_to_all) {
+ job->skip_all_conflict = TRUE;
+ }
+ conflict_response_data_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;
+ conflict_response_data_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);
+ conflict_response_data_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) ||
+ (overwrite && IS_IO_ERROR (error, IS_DIRECTORY))) {
+ g_error_free (error);
+
+ fallback = move_copy_file_callback_new (src,
+ overwrite,
+ position);
+ *fallback_files = g_list_prepend (*fallback_files, fallback);
+ }
+
+ else if (IS_IO_ERROR (error, CANCELLED)) {
+ g_error_free (error);
+ }
+
+ /* Other error */
+ else {
+ if (job->skip_all_error) {
+ g_error_free (error);
+ goto out;
+ }
+ primary = f (_("Error while moving “%B”."), src);
+ secondary = f (_("There was an error moving the file into %F."), dest_dir);
+ 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;
+ GdkPoint *point;
+ 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;
+
+ if (i < job->n_icon_positions) {
+ point = &job->icon_positions[i];
+ } else {
+ point = NULL;
+ }
+
+
+ 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,
+ point,
+ 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;
+ GdkPoint *point;
+ 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;
+
+ if (fallback->has_position) {
+ point = &fallback->position;
+ } else {
+ point = NULL;
+ }
+
+ 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,
+ point, fallback->overwrite, &skipped_file, FALSE);
+ i++;
+ }
+}
+
+
+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);
+ g_free (job->icon_positions);
+
+ finalize_common ((CommonJob *)job);
+
+ nautilus_file_changes_consume_changes (TRUE);
+}
+
+static void
+move_task_thread_func (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ CopyMoveJob *job;
+ CommonJob *common;
+ GList *fallbacks;
+ SourceInfo source_info;
+ TransferInfo transfer_info;
+ char *dest_fs_id;
+ char *dest_fs_type;
+ GList *fallback_files;
+
+ job = task_data;
+ common = &job->common;
+
+ dest_fs_id = NULL;
+ dest_fs_type = NULL;
+
+ fallbacks = NULL;
+
+ nautilus_progress_info_start (job->common.progress);
+
+ 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);
+
+ g_free (dest_fs_id);
+ g_free (dest_fs_type);
+}
+
+void
+nautilus_file_operations_move (GList *files,
+ GArray *relative_item_points,
+ GFile *target_dir,
+ GtkWindow *parent_window,
+ NautilusCopyCallback done_callback,
+ gpointer done_callback_data)
+{
+ GTask *task;
+ CopyMoveJob *job;
+
+ job = op_job_new (CopyMoveJob, parent_window);
+ 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, target_dir);
+ if (relative_item_points != NULL &&
+ relative_item_points->len > 0) {
+ job->icon_positions =
+ g_memdup (relative_item_points->data,
+ sizeof (GdkPoint) * relative_item_points->len);
+ job->n_icon_positions = relative_item_points->len;
+ }
+ job->debuting_files = g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, g_object_unref, NULL);
+
+ inhibit_power_manager ((CommonJob *)job, _("Moving Files"));
+
+ if (!nautilus_file_undo_manager_is_operating ()) {
+ GFile* src_dir;
+
+ src_dir = g_file_get_parent (files->data);
+
+ if (g_file_has_uri_scheme (g_list_first (files)->data, "trash")) {
+ job->common.undo_info = nautilus_file_undo_info_ext_new (NAUTILUS_FILE_UNDO_OP_RESTORE_FROM_TRASH,
+ g_list_length (files),
+ src_dir, target_dir);
+ } else {
+ job->common.undo_info = nautilus_file_undo_info_ext_new (NAUTILUS_FILE_UNDO_OP_MOVE,
+ g_list_length (files),
+ src_dir, target_dir);
+ }
+
+ g_object_unref (src_dir);
+ }
+
+ 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, move_task_thread_func);
+ g_object_unref (task);
+}
+
+static void
+report_preparing_link_progress (CopyMoveJob *link_job, int total, int left)
+{
+ CommonJob *job;
+
+ job = (CommonJob *)link_job;
+
+ nautilus_progress_info_take_status (job->progress,
+ f (_("Creating links in “%B”"),
+ link_job->destination));
+
+ nautilus_progress_info_take_details (job->progress,
+ f (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,
+ GdkPoint *position,
+ int files_left)
+{
+ GFile *src_dir, *dest, *new_dest;
+ 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);
+ if (position) {
+ nautilus_file_changes_queue_schedule_position_set (dest, *position, common->screen_num);
+ } else {
+ nautilus_file_changes_queue_schedule_position_remove (dest);
+ }
+
+ g_object_unref (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) {
+ if (common->skip_all_error) {
+ goto out;
+ }
+ primary = f (_("Error while creating link to %B."), src);
+ if (not_local) {
+ secondary = f (_("Symbolic links only supported for local files"));
+ details = NULL;
+ } else if (IS_IO_ERROR (error, NOT_SUPPORTED)) {
+ secondary = f (_("The target doesn't support symbolic links."));
+ details = NULL;
+ } else {
+ secondary = f (_("There was an error creating the symlink in %F."), dest_dir);
+ 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 ();
+ }
+ }
+
+ out:
+ g_object_unref (dest);
+}
+
+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);
+ g_free (job->icon_positions);
+
+ 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;
+ GdkPoint *point;
+ char *dest_fs_type;
+ int total, left;
+ int i;
+ GList *l;
+
+ job = task_data;
+ common = &job->common;
+
+ dest_fs_type = NULL;
+
+ nautilus_progress_info_start (job->common.progress);
+
+ verify_destination (&job->common,
+ job->destination,
+ NULL,
+ -1);
+ if (job_aborted (common)) {
+ goto aborted;
+ }
+
+ 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;
+
+ if (i < job->n_icon_positions) {
+ point = &job->icon_positions[i];
+ } else {
+ point = NULL;
+ }
+
+
+ link_file (job, src, job->destination,
+ &dest_fs_type, job->debuting_files,
+ point, left);
+ report_preparing_link_progress (job, total, --left);
+ i++;
+
+ }
+
+ aborted:
+ g_free (dest_fs_type);
+}
+
+void
+nautilus_file_operations_link (GList *files,
+ GArray *relative_item_points,
+ GFile *target_dir,
+ GtkWindow *parent_window,
+ NautilusCopyCallback done_callback,
+ gpointer done_callback_data)
+{
+ GTask *task;
+ CopyMoveJob *job;
+
+ job = op_job_new (CopyMoveJob, parent_window);
+ 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);
+ if (relative_item_points != NULL &&
+ relative_item_points->len > 0) {
+ job->icon_positions =
+ g_memdup (relative_item_points->data,
+ sizeof (GdkPoint) * relative_item_points->len);
+ job->n_icon_positions = relative_item_points->len;
+ }
+ 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 ()) {
+ GFile* src_dir;
+
+ 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);
+ g_object_unref (src_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);
+ g_object_unref (task);
+}
+
+
+void
+nautilus_file_operations_duplicate (GList *files,
+ GArray *relative_item_points,
+ GtkWindow *parent_window,
+ NautilusCopyCallback done_callback,
+ gpointer done_callback_data)
+{
+ GTask *task;
+ CopyMoveJob *job;
+ GFile *parent;
+
+ job = op_job_new (CopyMoveJob, parent_window);
+ 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);
+ if (relative_item_points != NULL &&
+ relative_item_points->len > 0) {
+ job->icon_positions =
+ g_memdup (relative_item_points->data,
+ sizeof (GdkPoint) * relative_item_points->len);
+ job->n_icon_positions = relative_item_points->len;
+ }
+ 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 ()) {
+ GFile* src_dir;
+
+ 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);
+ g_object_unref (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, copy_task_thread_func);
+ g_object_unref (task);
+
+ g_object_unref (parent);
+}
+
+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)
+{
+ CommonJob *common;
+ GFileInfo *child_info;
+ gboolean free_info;
+ guint32 current;
+ guint32 value;
+ guint32 mask;
+ GFileEnumerator *enumerator;
+ GFile *child;
+
+ 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) {
+ 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) {
+ while (!job_aborted (common) &&
+ (child_info = g_file_enumerator_next_file (enumerator, common->cancellable, NULL)) != NULL) {
+ 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);
+ }
+ }
+ 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_file (job, job->file, NULL);
+}
+
+
+
+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)
+{
+ GTask *task;
+ SetPermissionsJob *job;
+
+ job = op_job_new (SetPermissionsJob, 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);
+ g_object_unref (task);
+}
+
+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,
+ GArray *relative_item_points,
+ const char *target_dir,
+ GdkDragAction copy_action,
+ GtkWidget *parent_view,
+ 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,
+ relative_item_points,
+ parent_window,
+ done_callback, done_callback_data);
+ } else {
+ nautilus_file_operations_copy (locations,
+ relative_item_points,
+ dest,
+ parent_window,
+ 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 (locations,
+ parent_window,
+ (NautilusDeleteCallback) callback_for_move_to_trash,
+ cb_data);
+ } else {
+
+ nautilus_file_operations_move (locations,
+ relative_item_points,
+ dest,
+ parent_window,
+ done_callback, done_callback_data);
+ }
+ } else {
+
+ nautilus_file_operations_link (locations,
+ relative_item_points,
+ dest,
+ parent_window,
+ 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;
+ GFile *dest;
+ char *basename;
+ char *filename, *filename2, *new_filename;
+ char *filename_base, *suffix;
+ char *dest_fs_type;
+ 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;
+
+ dest_fs_type = NULL;
+ filename = NULL;
+ dest = NULL;
+
+ max_length = get_max_name_length (job->dest_dir);
+
+ verify_destination (common,
+ job->dest_dir,
+ NULL, -1);
+ if (job_aborted (common)) {
+ goto aborted;
+ }
+
+ 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) {
+ basename = g_file_get_basename (job->src);
+ /* localizers: the initial name of a new template document */
+ filename = g_strdup_printf ("%s", basename);
+
+ g_free (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_NONE,
+ 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) {
+ gchar *uri;
+
+ 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);
+
+ g_free (uri);
+ }
+
+ } 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);
+ if (job->has_position) {
+ nautilus_file_changes_queue_schedule_position_set (dest, job->position, common->screen_num);
+ } else {
+ nautilus_file_changes_queue_schedule_position_remove (dest);
+ }
+ } else {
+ g_assert (error != NULL);
+
+ if (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 (job->dest_dir, common->cancellable);
+
+ if (count == 1) {
+ new_filename = g_strdup (filename);
+ } else {
+ 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);
+ }
+
+ g_free (filename2);
+ g_free (suffix);
+ }
+
+ 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_free (new_filename);
+ g_error_free (error);
+ goto retry;
+ }
+ g_free (new_filename);
+ }
+
+ if (IS_IO_ERROR (error, EXISTS)) {
+ g_object_unref (dest);
+ dest = NULL;
+ 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) {
+ 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_free (filename2);
+ g_free (suffix);
+ g_error_free (error);
+ goto retry;
+ }
+
+ else if (IS_IO_ERROR (error, CANCELLED)) {
+ g_error_free (error);
+ }
+
+ /* Other error */
+ else {
+ if (job->make_dir) {
+ primary = f (_("Error while creating directory %B."), dest);
+ } else {
+ primary = f (_("Error while creating file %B."), dest);
+ }
+ secondary = f (_("There was an error creating the directory in %F."), job->dest_dir);
+ 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 ();
+ }
+ }
+ }
+
+ aborted:
+ if (dest) {
+ g_object_unref (dest);
+ }
+ g_free (filename);
+ g_free (dest_fs_type);
+}
+
+void
+nautilus_file_operations_new_folder (GtkWidget *parent_view,
+ GdkPoint *target_point,
+ const char *parent_dir,
+ const char *folder_name,
+ NautilusCreateCallback done_callback,
+ gpointer done_callback_data)
+{
+ GTask *task;
+ 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);
+ 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 (target_point != NULL) {
+ job->position = *target_point;
+ job->has_position = 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);
+ g_object_unref (task);
+}
+
+void
+nautilus_file_operations_new_file_from_template (GtkWidget *parent_view,
+ GdkPoint *target_point,
+ const char *parent_dir,
+ const char *target_filename,
+ const char *template_uri,
+ NautilusCreateCallback done_callback,
+ gpointer done_callback_data)
+{
+ GTask *task;
+ 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);
+ job->done_callback = done_callback;
+ job->done_callback_data = done_callback_data;
+ job->dest_dir = g_file_new_for_uri (parent_dir);
+ if (target_point != NULL) {
+ job->position = *target_point;
+ job->has_position = TRUE;
+ }
+ 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);
+ g_object_unref (task);
+}
+
+void
+nautilus_file_operations_new_file (GtkWidget *parent_view,
+ GdkPoint *target_point,
+ const char *parent_dir,
+ const char *target_filename,
+ const char *initial_contents,
+ int length,
+ NautilusCreateCallback done_callback,
+ gpointer done_callback_data)
+{
+ GTask *task;
+ 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);
+ job->done_callback = done_callback;
+ job->done_callback_data = done_callback_data;
+ job->dest_dir = g_file_new_for_uri (parent_dir);
+ if (target_point != NULL) {
+ job->position = *target_point;
+ job->has_position = TRUE;
+ }
+ 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);
+ g_object_unref (task);
+}
+
+
+
+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) {
+ 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) {
+ child = g_file_get_child (file,
+ g_file_info_get_name (info));
+ delete_trash_file (job, child, TRUE,
+ g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY);
+ 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)
+{
+ GTask *task;
+ 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);
+ job->trash_dirs = g_list_prepend (job->trash_dirs,
+ g_file_new_for_uri ("trash:"));
+ job->should_confirm = TRUE;
+
+ 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);
+ g_object_unref (task);
+}
+
+static void
+mark_trusted_task_done (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ MarkTrustedJob *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);
+}
+
+#define TRUSTED_SHEBANG "#!/usr/bin/env xdg-open\n"
+
+static void
+mark_desktop_file_trusted (CommonJob *common,
+ GCancellable *cancellable,
+ GFile *file,
+ gboolean interactive)
+{
+ char *contents, *new_contents;
+ gsize length, new_length;
+ GError *error;
+ guint32 current_perms, new_perms;
+ int response;
+ GFileInfo *info;
+
+ retry:
+ error = NULL;
+ if (!g_file_load_contents (file,
+ cancellable,
+ &contents, &length,
+ NULL, &error)) {
+ if (interactive) {
+ response = run_error (common,
+ g_strdup (_("Unable to mark launcher trusted (executable)")),
+ error->message,
+ NULL,
+ FALSE,
+ CANCEL, RETRY,
+ NULL);
+ } else {
+ response = 0;
+ }
+
+
+ if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
+ abort_job (common);
+ } else if (response == 1) {
+ goto retry;
+ } else {
+ g_assert_not_reached ();
+ }
+
+ goto out;
+ }
+
+ if (!g_str_has_prefix (contents, "#!")) {
+ new_length = length + strlen (TRUSTED_SHEBANG);
+ new_contents = g_malloc (new_length);
+
+ strcpy (new_contents, TRUSTED_SHEBANG);
+ memcpy (new_contents + strlen (TRUSTED_SHEBANG),
+ contents, length);
+
+ if (!g_file_replace_contents (file,
+ new_contents,
+ new_length,
+ NULL,
+ FALSE, 0,
+ NULL, cancellable, &error)) {
+ g_free (contents);
+ g_free (new_contents);
+
+ if (interactive) {
+ response = run_error (common,
+ g_strdup (_("Unable to mark launcher trusted (executable)")),
+ error->message,
+ NULL,
+ FALSE,
+ CANCEL, RETRY,
+ NULL);
+ } else {
+ response = 0;
+ }
+
+ if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
+ abort_job (common);
+ } else if (response == 1) {
+ goto retry;
+ } else {
+ g_assert_not_reached ();
+ }
+
+ goto out;
+ }
+ g_free (new_contents);
+
+ }
+ g_free (contents);
+
+ info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_STANDARD_TYPE","
+ G_FILE_ATTRIBUTE_UNIX_MODE,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ common->cancellable,
+ &error);
+
+ if (info == NULL) {
+ if (interactive) {
+ response = run_error (common,
+ g_strdup (_("Unable to mark launcher trusted (executable)")),
+ error->message,
+ NULL,
+ FALSE,
+ CANCEL, RETRY,
+ NULL);
+ } else {
+ response = 0;
+ }
+
+ if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
+ abort_job (common);
+ } else if (response == 1) {
+ goto retry;
+ } else {
+ g_assert_not_reached ();
+ }
+
+ goto out;
+ }
+
+
+ if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_MODE)) {
+ current_perms = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE);
+ new_perms = current_perms | S_IXGRP | S_IXUSR | S_IXOTH;
+
+ if ((current_perms != new_perms) &&
+ !g_file_set_attribute_uint32 (file, G_FILE_ATTRIBUTE_UNIX_MODE,
+ new_perms, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ common->cancellable, &error))
+ {
+ g_object_unref (info);
+
+ if (interactive) {
+ response = run_error (common,
+ g_strdup (_("Unable to mark launcher trusted (executable)")),
+ error->message,
+ NULL,
+ FALSE,
+ CANCEL, RETRY,
+ NULL);
+ } else {
+ response = 0;
+ }
+
+ if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
+ abort_job (common);
+ } else if (response == 1) {
+ goto retry;
+ } else {
+ g_assert_not_reached ();
+ }
+
+ goto out;
+ }
+ }
+ g_object_unref (info);
+ out:
+ ;
+}
+
+static void
+mark_trusted_task_thread_func (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ MarkTrustedJob *job = task_data;
+ CommonJob *common;
+
+ common = (CommonJob *)job;
+
+ nautilus_progress_info_start (job->common.progress);
+
+ mark_desktop_file_trusted (common,
+ cancellable,
+ job->file,
+ job->interactive);
+}
+
+void
+nautilus_file_mark_desktop_file_trusted (GFile *file,
+ GtkWindow *parent_window,
+ gboolean interactive,
+ NautilusOpCallback done_callback,
+ gpointer done_callback_data)
+{
+ GTask *task;
+ MarkTrustedJob *job;
+
+ job = op_job_new (MarkTrustedJob, parent_window);
+ job->file = g_object_ref (file);
+ job->interactive = interactive;
+ job->done_callback = done_callback;
+ job->done_callback_data = done_callback_data;
+
+ task = g_task_new (NULL, NULL, mark_trusted_task_done, job);
+ g_task_set_task_data (task, job, NULL);
+ g_task_run_in_thread (task, mark_trusted_task_thread_func);
+ g_object_unref (task);
+}
+
+#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), " (another copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo", 1, -1), "foo (copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name (".bashrc", 1, -1), ".bashrc (copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name (".foo.txt", 1, -1), ".foo (copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo foo", 1, -1), "foo foo (copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo.txt", 1, -1), "foo (copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo foo.txt", 1, -1), "foo foo (copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo foo.txt txt", 1, -1), "foo foo (copy).txt txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo...txt", 1, -1), "foo.. (copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo...", 1, -1), "foo... (copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo. (copy)", 1, -1), "foo. (another copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (copy)", 1, -1), "foo (another copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (copy).txt", 1, -1), "foo (another copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (another copy)", 1, -1), "foo (3rd copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (another copy).txt", 1, -1), "foo (3rd copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo foo (another copy).txt", 1, -1), "foo foo (3rd copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (13th copy)", 1, -1), "foo (14th copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (13th copy).txt", 1, -1), "foo (14th copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (21st copy)", 1, -1), "foo (22nd copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (21st copy).txt", 1, -1), "foo (22nd copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (22nd copy)", 1, -1), "foo (23rd copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (22nd copy).txt", 1, -1), "foo (23rd copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (23rd copy)", 1, -1), "foo (24th copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (23rd copy).txt", 1, -1), "foo (24th copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (24th copy)", 1, -1), "foo (25th copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (24th copy).txt", 1, -1), "foo (25th copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo foo (24th copy)", 1, -1), "foo foo (25th copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo foo (24th copy).txt", 1, -1), "foo foo (25th copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo foo (100000000000000th copy).txt", 1, -1), "foo foo (copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (10th copy)", 1, -1), "foo (11th copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (10th copy).txt", 1, -1), "foo (11th copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (11th copy)", 1, -1), "foo (12th copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (11th copy).txt", 1, -1), "foo (12th copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (12th copy)", 1, -1), "foo (13th copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (12th copy).txt", 1, -1), "foo (13th copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (110th copy)", 1, -1), "foo (111th copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (110th copy).txt", 1, -1), "foo (111th copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (122nd copy)", 1, -1), "foo (123rd copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (122nd copy).txt", 1, -1), "foo (123rd copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (123rd copy)", 1, -1), "foo (124th copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (123rd copy).txt", 1, -1), "foo (124th copy).txt");
+
+ setlocale (LC_MESSAGES, "");
+}
+
+#endif
diff --git a/src/nautilus-file-operations.h b/src/nautilus-file-operations.h
new file mode 100644
index 000000000..08b11ca95
--- /dev/null
+++ b/src/nautilus-file-operations.h
@@ -0,0 +1,152 @@
+
+/* 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>
+*/
+
+#ifndef NAUTILUS_FILE_OPERATIONS_H
+#define NAUTILUS_FILE_OPERATIONS_H
+
+#include <gtk/gtk.h>
+#include <gio/gio.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);
+
+/* FIXME: int copy_action should be an enum */
+
+void nautilus_file_operations_copy_move (const GList *item_uris,
+ GArray *relative_item_points,
+ const char *target_dir_uri,
+ GdkDragAction copy_action,
+ GtkWidget *parent_view,
+ NautilusCopyCallback done_callback,
+ gpointer done_callback_data);
+void nautilus_file_operations_copy_file (GFile *source_file,
+ GFile *target_dir,
+ const gchar *source_display_name,
+ const gchar *new_name,
+ GtkWindow *parent_window,
+ NautilusCopyCallback done_callback,
+ gpointer done_callback_data);
+void nautilus_file_operations_empty_trash (GtkWidget *parent_view);
+void nautilus_file_operations_new_folder (GtkWidget *parent_view,
+ GdkPoint *target_point,
+ 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,
+ GdkPoint *target_point,
+ 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,
+ GdkPoint *target_point,
+ const char *parent_dir,
+ const char *target_filename,
+ const char *template_uri,
+ NautilusCreateCallback done_callback,
+ gpointer data);
+
+void nautilus_file_operations_delete (GList *files,
+ GtkWindow *parent_window,
+ NautilusDeleteCallback done_callback,
+ gpointer done_callback_data);
+void nautilus_file_operations_trash_or_delete (GList *files,
+ GtkWindow *parent_window,
+ 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 (GList *files,
+ GArray *relative_item_points,
+ GFile *target_dir,
+ GtkWindow *parent_window,
+ NautilusCopyCallback done_callback,
+ gpointer done_callback_data);
+void nautilus_file_operations_move (GList *files,
+ GArray *relative_item_points,
+ GFile *target_dir,
+ GtkWindow *parent_window,
+ NautilusCopyCallback done_callback,
+ gpointer done_callback_data);
+void nautilus_file_operations_duplicate (GList *files,
+ GArray *relative_item_points,
+ GtkWindow *parent_window,
+ NautilusCopyCallback done_callback,
+ gpointer done_callback_data);
+void nautilus_file_operations_link (GList *files,
+ GArray *relative_item_points,
+ GFile *target_dir,
+ GtkWindow *parent_window,
+ NautilusCopyCallback done_callback,
+ gpointer done_callback_data);
+void nautilus_file_mark_desktop_file_trusted (GFile *file,
+ GtkWindow *parent_window,
+ gboolean interactive,
+ NautilusOpCallback done_callback,
+ gpointer done_callback_data);
+
+
+#endif /* NAUTILUS_FILE_OPERATIONS_H */
diff --git a/src/nautilus-file-private.h b/src/nautilus-file-private.h
new file mode 100644
index 000000000..c4986511b
--- /dev/null
+++ b/src/nautilus-file-private.h
@@ -0,0 +1,288 @@
+/*
+ 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>
+*/
+
+#ifndef NAUTILUS_FILE_PRIVATE_H
+#define NAUTILUS_FILE_PRIVATE_H
+
+#include "nautilus-directory.h"
+#include "nautilus-file.h"
+#include "nautilus-monitor.h"
+#include "nautilus-file-undo-operations.h"
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-string.h>
+
+#define NAUTILUS_FILE_DEFAULT_ATTRIBUTES \
+ "standard::*,access::*,mountable::*,time::*,unix::*,owner::*,selinux::*,thumbnail::*,id::filesystem,trash::orig-path,trash::deletion-date,metadata::*"
+
+/* 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;
+
+ eel_ref_str name;
+
+ /* File info: */
+ GFileType type;
+
+ eel_ref_str display_name;
+ char *display_name_collation_key;
+ char *directory_name_collation_key;
+ eel_ref_str edit_name;
+
+ goffset size; /* -1 is unknown */
+
+ int sort_order;
+
+ guint32 permissions;
+ int uid; /* -1 is none */
+ int gid; /* -1 is none */
+
+ eel_ref_str owner;
+ eel_ref_str owner_real;
+ eel_ref_str group;
+
+ time_t atime; /* 0 is unknown */
+ time_t mtime; /* 0 is unknown */
+
+ char *symlink_name;
+
+ eel_ref_str 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.
+ */
+ eel_ref_str 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_link_info : 1;
+ eel_boolean_bit link_info_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 thumbnail_wants_original : 1;
+ eel_boolean_bit thumbnail_tried_original : 1;
+ eel_boolean_bit thumbnailing_failed : 1;
+
+ eel_boolean_bit is_thumbnailing : 1;
+
+ eel_boolean_bit is_launcher : 1;
+ eel_boolean_bit is_trusted_link : 1;
+ eel_boolean_bit is_foreign_link : 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_ref_str filesystem_type;
+
+ time_t trash_time; /* 0 is unknown */
+
+ gdouble search_relevance;
+
+ 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;
+ 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);
+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);
+
+#endif
diff --git a/src/nautilus-file-queue.c b/src/nautilus-file-queue.c
new file mode 100644
index 000000000..846b1c4df
--- /dev/null
+++ b/src/nautilus-file-queue.c
@@ -0,0 +1,122 @@
+/*
+ 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 000000000..b7aae0b38
--- /dev/null
+++ b/src/nautilus-file-queue.h
@@ -0,0 +1,49 @@
+/*
+ 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>
+*/
+
+#ifndef NAUTILUS_FILE_QUEUE_H
+#define NAUTILUS_FILE_QUEUE_H
+
+#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);
+
+#endif /* NAUTILUS_FILE_CHANGES_QUEUE_H */
diff --git a/src/nautilus-file-undo-manager.c b/src/nautilus-file-undo-manager.c
new file mode 100644
index 000000000..e3e915fbd
--- /dev/null
+++ b/src/nautilus-file-undo-manager.c
@@ -0,0 +1,272 @@
+
+/* 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, };
+
+G_DEFINE_TYPE (NautilusFileUndoManager, nautilus_file_undo_manager, G_TYPE_OBJECT)
+
+struct _NautilusFileUndoManagerPrivate
+{
+ NautilusFileUndoInfo *info;
+ NautilusFileUndoManagerState state;
+ NautilusFileUndoManagerState last_state;
+
+ guint is_operating : 1;
+
+ gulong trash_signal_id;
+};
+
+static NautilusFileUndoManager *undo_singleton = NULL;
+
+static NautilusFileUndoManager *
+get_singleton (void)
+{
+ if (undo_singleton == NULL) {
+ 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->priv->info);
+ self->priv->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->priv->state == NAUTILUS_FILE_UNDO_MANAGER_STATE_UNDO &&
+ NAUTILUS_IS_FILE_UNDO_INFO_TRASH (self->priv->info)) {
+ file_undo_manager_clear (self);
+ g_signal_emit (self, signals[SIGNAL_UNDO_CHANGED], 0);
+ }
+}
+
+static void
+nautilus_file_undo_manager_init (NautilusFileUndoManager * self)
+{
+ NautilusFileUndoManagerPrivate *priv = self->priv =
+ G_TYPE_INSTANCE_GET_PRIVATE (self,
+ NAUTILUS_TYPE_FILE_UNDO_MANAGER,
+ NautilusFileUndoManagerPrivate);
+
+ priv->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);
+ NautilusFileUndoManagerPrivate *priv = self->priv;
+
+ if (priv->trash_signal_id != 0) {
+ g_signal_handler_disconnect (nautilus_trash_monitor_get (),
+ priv->trash_signal_id);
+ priv->trash_signal_id = 0;
+ }
+
+ 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);
+
+ g_type_class_add_private (klass, sizeof (NautilusFileUndoManagerPrivate));
+}
+
+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->priv->is_operating = FALSE;
+
+ /* just return in case we got another another operation set */
+ if ((self->priv->info != NULL) &&
+ (self->priv->info != info)) {
+ return;
+ }
+
+ if (success) {
+ if (self->priv->last_state == NAUTILUS_FILE_UNDO_MANAGER_STATE_UNDO) {
+ self->priv->state = NAUTILUS_FILE_UNDO_MANAGER_STATE_REDO;
+ } else if (self->priv->last_state == NAUTILUS_FILE_UNDO_MANAGER_STATE_REDO) {
+ self->priv->state = NAUTILUS_FILE_UNDO_MANAGER_STATE_UNDO;
+ }
+
+ self->priv->info = g_object_ref (info);
+ } else if (user_cancel) {
+ self->priv->state = self->priv->last_state;
+ self->priv->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)
+{
+ gboolean undo = self->priv->state == NAUTILUS_FILE_UNDO_MANAGER_STATE_UNDO;
+
+ self->priv->last_state = self->priv->state;
+
+ self->priv->is_operating = TRUE;
+ nautilus_file_undo_info_apply_async (self->priv->info, undo, parent_window,
+ 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)
+{
+ NautilusFileUndoManager *self = get_singleton ();
+
+ if (self->priv->state != NAUTILUS_FILE_UNDO_MANAGER_STATE_REDO) {
+ g_warning ("Called redo, but state is %s!", self->priv->state == 0 ?
+ "none" : "undo");
+ return;
+ }
+
+ do_undo_redo (self, parent_window);
+}
+
+void
+nautilus_file_undo_manager_undo (GtkWindow *parent_window)
+{
+ NautilusFileUndoManager *self = get_singleton ();
+
+ if (self->priv->state != NAUTILUS_FILE_UNDO_MANAGER_STATE_UNDO) {
+ g_warning ("Called undo, but state is %s!", self->priv->state == 0 ?
+ "none" : "redo");
+ return;
+ }
+
+ do_undo_redo (self, parent_window);
+}
+
+void
+nautilus_file_undo_manager_set_action (NautilusFileUndoInfo *info)
+{
+ NautilusFileUndoManager *self = get_singleton ();
+
+ DEBUG ("Setting undo information %p", info);
+
+ file_undo_manager_clear (self);
+
+ if (info != NULL) {
+ self->priv->info = g_object_ref (info);
+ self->priv->state = NAUTILUS_FILE_UNDO_MANAGER_STATE_UNDO;
+ self->priv->last_state = NAUTILUS_FILE_UNDO_MANAGER_STATE_NONE;
+ }
+
+ g_signal_emit (self, signals[SIGNAL_UNDO_CHANGED], 0);
+}
+
+NautilusFileUndoInfo *
+nautilus_file_undo_manager_get_action (void)
+{
+ NautilusFileUndoManager *self = get_singleton ();
+
+ return self->priv->info;
+}
+
+NautilusFileUndoManagerState
+nautilus_file_undo_manager_get_state (void)
+{
+ NautilusFileUndoManager *self = get_singleton ();
+
+ return self->priv->state;
+}
+
+
+gboolean
+nautilus_file_undo_manager_is_operating ()
+{
+ NautilusFileUndoManager *self = get_singleton ();
+ return self->priv->is_operating;
+}
+
+NautilusFileUndoManager *
+nautilus_file_undo_manager_get ()
+{
+ return get_singleton ();
+}
diff --git a/src/nautilus-file-undo-manager.h b/src/nautilus-file-undo-manager.h
new file mode 100644
index 000000000..a5e21d796
--- /dev/null
+++ b/src/nautilus-file-undo-manager.h
@@ -0,0 +1,83 @@
+
+/* 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>
+ */
+
+#ifndef __NAUTILUS_FILE_UNDO_MANAGER_H__
+#define __NAUTILUS_FILE_UNDO_MANAGER_H__
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+
+#include "nautilus-file-undo-operations.h"
+
+typedef struct _NautilusFileUndoManager NautilusFileUndoManager;
+typedef struct _NautilusFileUndoManagerClass NautilusFileUndoManagerClass;
+typedef struct _NautilusFileUndoManagerPrivate NautilusFileUndoManagerPrivate;
+
+#define NAUTILUS_TYPE_FILE_UNDO_MANAGER\
+ (nautilus_file_undo_manager_get_type())
+#define NAUTILUS_FILE_UNDO_MANAGER(object)\
+ (G_TYPE_CHECK_INSTANCE_CAST((object), NAUTILUS_TYPE_FILE_UNDO_MANAGER,\
+ NautilusFileUndoManager))
+#define NAUTILUS_FILE_UNDO_MANAGER_CLASS(klass)\
+ (G_TYPE_CHECK_CLASS_CAST((klass), NAUTILUS_TYPE_FILE_UNDO_MANAGER,\
+ NautilusFileUndoManagerClass))
+#define NAUTILUS_IS_FILE_UNDO_MANAGER(object)\
+ (G_TYPE_CHECK_INSTANCE_TYPE((object), NAUTILUS_TYPE_FILE_UNDO_MANAGER))
+#define NAUTILUS_IS_FILE_UNDO_MANAGER_CLASS(klass)\
+ (G_TYPE_CHECK_CLASS_TYPE((klass), NAUTILUS_TYPE_FILE_UNDO_MANAGER))
+#define NAUTILUS_FILE_UNDO_MANAGER_GET_CLASS(object)\
+ (G_TYPE_INSTANCE_GET_CLASS((object), NAUTILUS_TYPE_FILE_UNDO_MANAGER,\
+ NautilusFileUndoManagerClass))
+
+typedef enum {
+ NAUTILUS_FILE_UNDO_MANAGER_STATE_NONE,
+ NAUTILUS_FILE_UNDO_MANAGER_STATE_UNDO,
+ NAUTILUS_FILE_UNDO_MANAGER_STATE_REDO
+} NautilusFileUndoManagerState;
+
+struct _NautilusFileUndoManager {
+ GObject parent_instance;
+
+ /* < private > */
+ NautilusFileUndoManagerPrivate* priv;
+};
+
+struct _NautilusFileUndoManagerClass {
+ GObjectClass parent_class;
+};
+
+GType nautilus_file_undo_manager_get_type (void) G_GNUC_CONST;
+
+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);
+void nautilus_file_undo_manager_redo (GtkWindow *parent_window);
+
+gboolean nautilus_file_undo_manager_is_operating (void);
+
+#endif /* __NAUTILUS_FILE_UNDO_MANAGER_H__ */
diff --git a/src/nautilus-file-undo-operations.c b/src/nautilus-file-undo-operations.c
new file mode 100644
index 000000000..25d90bb18
--- /dev/null
+++ b/src/nautilus-file-undo-operations.c
@@ -0,0 +1,1677 @@
+
+/* 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 <config.h>
+#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"
+
+/* 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
+
+G_DEFINE_TYPE (NautilusFileUndoInfo, nautilus_file_undo_info, G_TYPE_OBJECT)
+
+enum {
+ PROP_OP_TYPE = 1,
+ PROP_ITEM_COUNT,
+ N_PROPERTIES
+};
+
+static GParamSpec *properties[N_PROPERTIES] = { NULL, };
+
+struct _NautilusFileUndoInfoDetails {
+ NautilusFileUndoOp op_type;
+ guint count; /* Number of items */
+
+ GTask *apply_async_task;
+
+ gchar *undo_label;
+ gchar *redo_label;
+ gchar *undo_description;
+ gchar *redo_description;
+};
+
+/* description helpers */
+static void
+nautilus_file_undo_info_init (NautilusFileUndoInfo *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NAUTILUS_TYPE_FILE_UNDO_INFO,
+ NautilusFileUndoInfoDetails);
+ 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 = NAUTILUS_FILE_UNDO_INFO (object);
+
+ switch (property_id) {
+ case PROP_OP_TYPE:
+ g_value_set_int (value, self->priv->op_type);
+ break;
+ case PROP_ITEM_COUNT:
+ g_value_set_int (value, self->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 = NAUTILUS_FILE_UNDO_INFO (object);
+
+ switch (property_id) {
+ case PROP_OP_TYPE:
+ self->priv->op_type = g_value_get_int (value);
+ break;
+ case PROP_ITEM_COUNT:
+ self->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)
+{
+ 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)
+{
+ 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 *obj)
+{
+ NautilusFileUndoInfo *self = NAUTILUS_FILE_UNDO_INFO (obj);
+
+ g_clear_object (&self->priv->apply_async_task);
+
+ G_OBJECT_CLASS (nautilus_file_undo_info_parent_class)->finalize (obj);
+}
+
+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_type_class_add_private (klass, sizeof (NautilusFileUndoInfoDetails));
+ g_object_class_install_properties (oclass, N_PROPERTIES, properties);
+}
+
+NautilusFileUndoOp
+nautilus_file_undo_info_get_op_type (NautilusFileUndoInfo *self)
+{
+ return self->priv->op_type;
+}
+
+static gint
+nautilus_file_undo_info_get_item_count (NautilusFileUndoInfo *self)
+{
+ return self->priv->count;
+}
+
+void
+nautilus_file_undo_info_apply_async (NautilusFileUndoInfo *self,
+ gboolean undo,
+ GtkWindow *parent_window,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_assert (self->priv->apply_async_task == NULL);
+
+ self->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);
+ } else {
+ NAUTILUS_FILE_UNDO_INFO_CLASS (G_OBJECT_GET_CLASS (self))->redo_func (self, parent_window);
+ }
+}
+
+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)
+{
+ FileUndoInfoOpRes *op_res = g_slice_new0 (FileUndoInfoOpRes);
+
+ op_res->user_cancel = user_cancel;
+ op_res->success = success;
+
+ g_task_return_pointer (self->priv->apply_async_task, op_res,
+ file_undo_info_op_res_free);
+
+ g_clear_object (&self->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 */
+G_DEFINE_TYPE (NautilusFileUndoInfoExt, nautilus_file_undo_info_ext, NAUTILUS_TYPE_FILE_UNDO_INFO)
+
+struct _NautilusFileUndoInfoExtDetails {
+ GFile *src_dir;
+ GFile *dest_dir;
+ GList *sources; /* Relative to src_dir */
+ GList *destinations; /* Relative to dest_dir */
+};
+
+static char *
+ext_get_first_target_short_name (NautilusFileUndoInfoExt *self)
+{
+ GList *targets_first;
+ char *file_name = NULL;
+
+ targets_first = g_list_first (self->priv->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->priv->src_dir);
+ destination = g_file_get_path (self->priv->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)
+{
+ nautilus_file_operations_link (self->priv->sources, NULL,
+ self->priv->dest_dir, parent_window,
+ file_undo_info_transfer_callback, self);
+}
+
+static void
+ext_duplicate_redo_func (NautilusFileUndoInfoExt *self,
+ GtkWindow *parent_window)
+{
+ nautilus_file_operations_duplicate (self->priv->sources, NULL, parent_window,
+ file_undo_info_transfer_callback, self);
+}
+
+static void
+ext_copy_redo_func (NautilusFileUndoInfoExt *self,
+ GtkWindow *parent_window)
+{
+ nautilus_file_operations_copy (self->priv->sources, NULL,
+ self->priv->dest_dir, parent_window,
+ file_undo_info_transfer_callback, self);
+}
+
+static void
+ext_move_restore_redo_func (NautilusFileUndoInfoExt *self,
+ GtkWindow *parent_window)
+{
+ nautilus_file_operations_move (self->priv->sources, NULL,
+ self->priv->dest_dir, parent_window,
+ file_undo_info_transfer_callback, self);
+}
+
+static void
+ext_redo_func (NautilusFileUndoInfo *info,
+ GtkWindow *parent_window)
+{
+ 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);
+ } else if (op_type == NAUTILUS_FILE_UNDO_OP_COPY) {
+ ext_copy_redo_func (self, parent_window);
+ } else if (op_type == NAUTILUS_FILE_UNDO_OP_DUPLICATE) {
+ ext_duplicate_redo_func (self, parent_window);
+ } else if (op_type == NAUTILUS_FILE_UNDO_OP_CREATE_LINK) {
+ ext_create_link_redo_func (self, parent_window);
+ } else {
+ g_assert_not_reached ();
+ }
+}
+
+static void
+ext_restore_undo_func (NautilusFileUndoInfoExt *self,
+ GtkWindow *parent_window)
+{
+ nautilus_file_operations_trash_or_delete (self->priv->destinations, parent_window,
+ file_undo_info_delete_callback, self);
+}
+
+
+static void
+ext_move_undo_func (NautilusFileUndoInfoExt *self,
+ GtkWindow *parent_window)
+{
+ nautilus_file_operations_move (self->priv->destinations, NULL,
+ self->priv->src_dir, parent_window,
+ file_undo_info_transfer_callback, self);
+}
+
+static void
+ext_copy_duplicate_undo_func (NautilusFileUndoInfoExt *self,
+ GtkWindow *parent_window)
+{
+ GList *files;
+
+ files = g_list_copy (self->priv->destinations);
+ files = g_list_reverse (files); /* Deleting must be done in reverse */
+
+ nautilus_file_operations_delete (files, parent_window,
+ file_undo_info_delete_callback, self);
+
+ g_list_free (files);
+}
+
+static void
+ext_undo_func (NautilusFileUndoInfo *info,
+ GtkWindow *parent_window)
+{
+ 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);
+ } else if (op_type == NAUTILUS_FILE_UNDO_OP_MOVE) {
+ ext_move_undo_func (self, parent_window);
+ } else if (op_type == NAUTILUS_FILE_UNDO_OP_RESTORE_FROM_TRASH) {
+ ext_restore_undo_func (self, parent_window);
+ } else {
+ g_assert_not_reached ();
+ }
+}
+
+static void
+nautilus_file_undo_info_ext_init (NautilusFileUndoInfoExt *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, nautilus_file_undo_info_ext_get_type (),
+ NautilusFileUndoInfoExtDetails);
+}
+
+static void
+nautilus_file_undo_info_ext_finalize (GObject *obj)
+{
+ NautilusFileUndoInfoExt *self = NAUTILUS_FILE_UNDO_INFO_EXT (obj);
+
+ if (self->priv->sources) {
+ g_list_free_full (self->priv->sources, g_object_unref);
+ }
+
+ if (self->priv->destinations) {
+ g_list_free_full (self->priv->destinations, g_object_unref);
+ }
+
+ g_clear_object (&self->priv->src_dir);
+ g_clear_object (&self->priv->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;
+
+ g_type_class_add_private (klass, sizeof (NautilusFileUndoInfoExtDetails));
+}
+
+NautilusFileUndoInfo *
+nautilus_file_undo_info_ext_new (NautilusFileUndoOp op_type,
+ gint item_count,
+ GFile *src_dir,
+ GFile *target_dir)
+{
+ NautilusFileUndoInfoExt *retval;
+
+ retval = g_object_new (NAUTILUS_TYPE_FILE_UNDO_INFO_EXT,
+ "op-type", op_type,
+ "item-count", item_count,
+ NULL);
+
+ retval->priv->src_dir = g_object_ref (src_dir);
+ retval->priv->dest_dir = g_object_ref (target_dir);
+
+ return NAUTILUS_FILE_UNDO_INFO (retval);
+}
+
+void
+nautilus_file_undo_info_ext_add_origin_target_pair (NautilusFileUndoInfoExt *self,
+ GFile *origin,
+ GFile *target)
+{
+ self->priv->sources =
+ g_list_append (self->priv->sources, g_object_ref (origin));
+ self->priv->destinations =
+ g_list_append (self->priv->destinations, g_object_ref (target));
+}
+
+/* create new file/folder */
+G_DEFINE_TYPE (NautilusFileUndoInfoCreate, nautilus_file_undo_info_create, NAUTILUS_TYPE_FILE_UNDO_INFO)
+
+struct _NautilusFileUndoInfoCreateDetails {
+ char *template;
+ GFile *target_file;
+ gint length;
+};
+
+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->priv->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)
+{
+ GFile *parent;
+ gchar *parent_uri, *new_name;
+
+ parent = g_file_get_parent (self->priv->target_file);
+ parent_uri = g_file_get_uri (parent);
+ new_name = g_file_get_parse_name (self->priv->target_file);
+ nautilus_file_operations_new_file_from_template (NULL, NULL,
+ parent_uri, new_name,
+ self->priv->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)
+{
+ GFile *parent;
+ gchar *parent_uri;
+ gchar *name;
+
+ name = g_file_get_basename (self->priv->target_file);
+ parent = g_file_get_parent (self->priv->target_file);
+ parent_uri = g_file_get_uri (parent);
+ nautilus_file_operations_new_folder (NULL, NULL, 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)
+
+{
+ GFile *parent;
+ gchar *parent_uri;
+ gchar *new_name;
+
+ parent = g_file_get_parent (self->priv->target_file);
+ parent_uri = g_file_get_uri (parent);
+ new_name = g_file_get_parse_name (self->priv->target_file);
+ nautilus_file_operations_new_file (NULL, NULL, parent_uri,
+ new_name,
+ self->priv->template,
+ self->priv->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)
+{
+ 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);
+ } else if (op_type == NAUTILUS_FILE_UNDO_OP_CREATE_FOLDER) {
+ create_folder_redo_func (self, parent_window);
+ } else if (op_type == NAUTILUS_FILE_UNDO_OP_CREATE_FILE_FROM_TEMPLATE) {
+ create_from_template_redo_func (self, parent_window);
+ } else {
+ g_assert_not_reached ();
+ }
+}
+
+static void
+create_undo_func (NautilusFileUndoInfo *info,
+ GtkWindow *parent_window)
+{
+ NautilusFileUndoInfoCreate *self = NAUTILUS_FILE_UNDO_INFO_CREATE (info);
+ GList *files = NULL;
+
+ files = g_list_append (files, g_object_ref (self->priv->target_file));
+ nautilus_file_operations_delete (files, parent_window,
+ file_undo_info_delete_callback, self);
+
+ g_list_free_full (files, g_object_unref);
+}
+
+static void
+nautilus_file_undo_info_create_init (NautilusFileUndoInfoCreate *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, nautilus_file_undo_info_create_get_type (),
+ NautilusFileUndoInfoCreateDetails);
+}
+
+static void
+nautilus_file_undo_info_create_finalize (GObject *obj)
+{
+ NautilusFileUndoInfoCreate *self = NAUTILUS_FILE_UNDO_INFO_CREATE (obj);
+ g_clear_object (&self->priv->target_file);
+ g_free (self->priv->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;
+
+ g_type_class_add_private (klass, sizeof (NautilusFileUndoInfoCreateDetails));
+}
+
+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->priv->target_file = g_object_ref (file);
+ self->priv->template = g_strdup (template);
+ self->priv->length = length;
+}
+
+/* rename */
+G_DEFINE_TYPE (NautilusFileUndoInfoRename, nautilus_file_undo_info_rename, NAUTILUS_TYPE_FILE_UNDO_INFO)
+
+struct _NautilusFileUndoInfoRenameDetails {
+ GFile *old_file;
+ GFile *new_file;
+ gchar *old_display_name;
+ gchar *new_display_name;
+};
+
+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->priv->new_file);
+ old_name = g_file_get_parse_name (self->priv->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)
+{
+ NautilusFileUndoInfoRename *self = NAUTILUS_FILE_UNDO_INFO_RENAME (info);
+ NautilusFile *file;
+
+ file = nautilus_file_get (self->priv->old_file);
+ nautilus_file_rename (file, self->priv->new_display_name,
+ file_undo_info_operation_callback, self);
+
+ nautilus_file_unref (file);
+}
+
+static void
+rename_undo_func (NautilusFileUndoInfo *info,
+ GtkWindow *parent_window)
+{
+ NautilusFileUndoInfoRename *self = NAUTILUS_FILE_UNDO_INFO_RENAME (info);
+ NautilusFile *file;
+
+ file = nautilus_file_get (self->priv->new_file);
+ nautilus_file_rename (file, self->priv->old_display_name,
+ file_undo_info_operation_callback, self);
+
+ nautilus_file_unref (file);
+}
+
+static void
+nautilus_file_undo_info_rename_init (NautilusFileUndoInfoRename *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, nautilus_file_undo_info_rename_get_type (),
+ NautilusFileUndoInfoRenameDetails);
+}
+
+static void
+nautilus_file_undo_info_rename_finalize (GObject *obj)
+{
+ NautilusFileUndoInfoRename *self = NAUTILUS_FILE_UNDO_INFO_RENAME (obj);
+ g_clear_object (&self->priv->old_file);
+ g_clear_object (&self->priv->new_file);
+ g_free (self->priv->old_display_name);
+ g_free (self->priv->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;
+
+ g_type_class_add_private (klass, sizeof (NautilusFileUndoInfoRenameDetails));
+}
+
+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->priv->old_file = g_object_ref (old_file);
+ self->priv->old_display_name = g_strdup (old_display_name);
+ self->priv->new_display_name = g_strdup (new_display_name);
+}
+
+void
+nautilus_file_undo_info_rename_set_data_post (NautilusFileUndoInfoRename *self,
+ GFile *new_file)
+{
+ self->priv->new_file = g_object_ref (new_file);
+}
+
+/* trash */
+G_DEFINE_TYPE (NautilusFileUndoInfoTrash, nautilus_file_undo_info_trash, NAUTILUS_TYPE_FILE_UNDO_INFO)
+
+struct _NautilusFileUndoInfoTrashDetails {
+ GHashTable *trashed;
+};
+
+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->priv->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->priv->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->priv->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->priv->trashed);
+
+ self->priv->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)
+{
+ NautilusFileUndoInfoTrash *self = NAUTILUS_FILE_UNDO_INFO_TRASH (info);
+
+ if (g_hash_table_size (self->priv->trashed) > 0) {
+ GList *locations;
+
+ locations = g_hash_table_get_keys (self->priv->trashed);
+ nautilus_file_operations_trash_or_delete (locations, parent_window,
+ 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->priv->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)
+{
+ 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->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, nautilus_file_undo_info_trash_get_type (),
+ NautilusFileUndoInfoTrashDetails);
+ self->priv->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->priv->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;
+
+ g_type_class_add_private (klass, sizeof (NautilusFileUndoInfoTrashDetails));
+}
+
+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->priv->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->priv->trashed);
+}
+
+/* recursive permissions */
+G_DEFINE_TYPE (NautilusFileUndoInfoRecPermissions, nautilus_file_undo_info_rec_permissions, NAUTILUS_TYPE_FILE_UNDO_INFO)
+
+struct _NautilusFileUndoInfoRecPermissionsDetails {
+ GFile *dest_dir;
+ GHashTable *original_permissions;
+ guint32 dir_mask;
+ guint32 dir_permissions;
+ guint32 file_mask;
+ guint32 file_permissions;
+};
+
+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->priv->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)
+{
+ NautilusFileUndoInfoRecPermissions *self = NAUTILUS_FILE_UNDO_INFO_REC_PERMISSIONS (info);
+ gchar *parent_uri;
+
+ parent_uri = g_file_get_uri (self->priv->dest_dir);
+ nautilus_file_set_permissions_recursive (parent_uri,
+ self->priv->file_permissions,
+ self->priv->file_mask,
+ self->priv->dir_permissions,
+ self->priv->dir_mask,
+ rec_permissions_callback, self);
+ g_free (parent_uri);
+}
+
+static void
+rec_permissions_undo_func (NautilusFileUndoInfo *info,
+ GtkWindow *parent_window)
+{
+ NautilusFileUndoInfoRecPermissions *self = NAUTILUS_FILE_UNDO_INFO_REC_PERMISSIONS (info);
+
+ if (g_hash_table_size (self->priv->original_permissions) > 0) {
+ GList *gfiles_list;
+ guint32 perm;
+ GList *l;
+ GFile *dest;
+ char *item;
+
+ gfiles_list = g_hash_table_get_keys (self->priv->original_permissions);
+ for (l = gfiles_list; l != NULL; l = l->next) {
+ item = l->data;
+ perm = GPOINTER_TO_UINT (g_hash_table_lookup (self->priv->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->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, nautilus_file_undo_info_rec_permissions_get_type (),
+ NautilusFileUndoInfoRecPermissionsDetails);
+
+ self->priv->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->priv->original_permissions);
+ g_clear_object (&self->priv->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;
+
+ g_type_class_add_private (klass, sizeof (NautilusFileUndoInfoRecPermissionsDetails));
+}
+
+NautilusFileUndoInfo *
+nautilus_file_undo_info_rec_permissions_new (GFile *dest,
+ guint32 file_permissions,
+ guint32 file_mask,
+ guint32 dir_permissions,
+ guint32 dir_mask)
+{
+ NautilusFileUndoInfoRecPermissions *retval;
+
+ retval = g_object_new (NAUTILUS_TYPE_FILE_UNDO_INFO_REC_PERMISSIONS,
+ "op-type", NAUTILUS_FILE_UNDO_OP_RECURSIVE_SET_PERMISSIONS,
+ "item-count", 1,
+ NULL);
+
+ retval->priv->dest_dir = g_object_ref (dest);
+ retval->priv->file_permissions = file_permissions;
+ retval->priv->file_mask = file_mask;
+ retval->priv->dir_permissions = dir_permissions;
+ retval->priv->dir_mask = dir_mask;
+
+ return NAUTILUS_FILE_UNDO_INFO (retval);
+}
+
+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->priv->original_permissions, original_uri, GUINT_TO_POINTER (permission));
+}
+
+/* single file change permissions */
+G_DEFINE_TYPE (NautilusFileUndoInfoPermissions, nautilus_file_undo_info_permissions, NAUTILUS_TYPE_FILE_UNDO_INFO)
+
+struct _NautilusFileUndoInfoPermissionsDetails {
+ GFile *target_file;
+ guint32 current_permissions;
+ guint32 new_permissions;
+};
+
+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->priv->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->priv->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)
+{
+ NautilusFileUndoInfoPermissions *self = NAUTILUS_FILE_UNDO_INFO_PERMISSIONS (info);
+ permissions_real_func (self, self->priv->new_permissions);
+}
+
+static void
+permissions_undo_func (NautilusFileUndoInfo *info,
+ GtkWindow *parent_window)
+{
+ NautilusFileUndoInfoPermissions *self = NAUTILUS_FILE_UNDO_INFO_PERMISSIONS (info);
+ permissions_real_func (self, self->priv->current_permissions);
+}
+
+static void
+nautilus_file_undo_info_permissions_init (NautilusFileUndoInfoPermissions *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, nautilus_file_undo_info_permissions_get_type (),
+ NautilusFileUndoInfoPermissionsDetails);
+}
+
+static void
+nautilus_file_undo_info_permissions_finalize (GObject *obj)
+{
+ NautilusFileUndoInfoPermissions *self = NAUTILUS_FILE_UNDO_INFO_PERMISSIONS (obj);
+ g_clear_object (&self->priv->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;
+
+ g_type_class_add_private (klass, sizeof (NautilusFileUndoInfoPermissionsDetails));
+}
+
+NautilusFileUndoInfo *
+nautilus_file_undo_info_permissions_new (GFile *file,
+ guint32 current_permissions,
+ guint32 new_permissions)
+{
+ NautilusFileUndoInfoPermissions *retval;
+
+ retval = g_object_new (NAUTILUS_TYPE_FILE_UNDO_INFO_PERMISSIONS,
+ "op-type", NAUTILUS_FILE_UNDO_OP_SET_PERMISSIONS,
+ "item-count", 1,
+ NULL);
+
+ retval->priv->target_file = g_object_ref (file);
+ retval->priv->current_permissions = current_permissions;
+ retval->priv->new_permissions = new_permissions;
+
+ return NAUTILUS_FILE_UNDO_INFO (retval);
+}
+
+/* group and owner change */
+G_DEFINE_TYPE (NautilusFileUndoInfoOwnership, nautilus_file_undo_info_ownership, NAUTILUS_TYPE_FILE_UNDO_INFO)
+
+struct _NautilusFileUndoInfoOwnershipDetails {
+ GFile *target_file;
+ char *original_ownership;
+ char *new_ownership;
+};
+
+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->priv->target_file);
+
+ if (op_type == NAUTILUS_FILE_UNDO_OP_CHANGE_OWNER) {
+ *undo_description = g_strdup_printf (_("Restore group of '%s' to '%s'"),
+ name, self->priv->original_ownership);
+ *redo_description = g_strdup_printf (_("Set group of '%s' to '%s'"),
+ name, self->priv->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->priv->original_ownership);
+ *redo_description = g_strdup_printf (_("Set owner of '%s' to '%s'"),
+ name, self->priv->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->priv->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)
+{
+ NautilusFileUndoInfoOwnership *self = NAUTILUS_FILE_UNDO_INFO_OWNERSHIP (info);
+ ownership_real_func (self, self->priv->new_ownership);
+}
+
+static void
+ownership_undo_func (NautilusFileUndoInfo *info,
+ GtkWindow *parent_window)
+{
+ NautilusFileUndoInfoOwnership *self = NAUTILUS_FILE_UNDO_INFO_OWNERSHIP (info);
+ ownership_real_func (self, self->priv->original_ownership);
+}
+
+static void
+nautilus_file_undo_info_ownership_init (NautilusFileUndoInfoOwnership *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, nautilus_file_undo_info_ownership_get_type (),
+ NautilusFileUndoInfoOwnershipDetails);
+}
+
+static void
+nautilus_file_undo_info_ownership_finalize (GObject *obj)
+{
+ NautilusFileUndoInfoOwnership *self = NAUTILUS_FILE_UNDO_INFO_OWNERSHIP (obj);
+
+ g_clear_object (&self->priv->target_file);
+ g_free (self->priv->original_ownership);
+ g_free (self->priv->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;
+
+ g_type_class_add_private (klass, sizeof (NautilusFileUndoInfoOwnershipDetails));
+}
+
+NautilusFileUndoInfo *
+nautilus_file_undo_info_ownership_new (NautilusFileUndoOp op_type,
+ GFile *file,
+ const char *current_data,
+ const char *new_data)
+{
+ NautilusFileUndoInfoOwnership *retval;
+
+ retval = g_object_new (NAUTILUS_TYPE_FILE_UNDO_INFO_OWNERSHIP,
+ "item-count", 1,
+ "op-type", op_type,
+ NULL);
+
+ retval->priv->target_file = g_object_ref (file);
+ retval->priv->original_ownership = g_strdup (current_data);
+ retval->priv->new_ownership = g_strdup (new_data);
+
+ return NAUTILUS_FILE_UNDO_INFO (retval);
+}
diff --git a/src/nautilus-file-undo-operations.h b/src/nautilus-file-undo-operations.h
new file mode 100644
index 000000000..cec1c7c4e
--- /dev/null
+++ b/src/nautilus-file-undo-operations.h
@@ -0,0 +1,299 @@
+
+/* 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>
+ *
+ */
+
+#ifndef __NAUTILUS_FILE_UNDO_OPERATIONS_H__
+#define __NAUTILUS_FILE_UNDO_OPERATIONS_H__
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+typedef enum {
+ NAUTILUS_FILE_UNDO_OP_COPY,
+ NAUTILUS_FILE_UNDO_OP_DUPLICATE,
+ NAUTILUS_FILE_UNDO_OP_MOVE,
+ NAUTILUS_FILE_UNDO_OP_RENAME,
+ 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_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 ())
+#define NAUTILUS_FILE_UNDO_INFO(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), NAUTILUS_TYPE_FILE_UNDO_INFO, NautilusFileUndoInfo))
+#define NAUTILUS_FILE_UNDO_INFO_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), NAUTILUS_TYPE_FILE_UNDO_INFO, NautilusFileUndoInfoClass))
+#define NAUTILUS_IS_FILE_UNDO_INFO(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), NAUTILUS_TYPE_FILE_UNDO_INFO))
+#define NAUTILUS_IS_FILE_UNDO_INFO_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), NAUTILUS_TYPE_FILE_UNDO_INFO))
+#define NAUTILUS_FILE_UNDO_INFO_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), NAUTILUS_TYPE_FILE_UNDO_INFO, NautilusFileUndoInfoClass))
+
+typedef struct _NautilusFileUndoInfo NautilusFileUndoInfo;
+typedef struct _NautilusFileUndoInfoClass NautilusFileUndoInfoClass;
+typedef struct _NautilusFileUndoInfoDetails NautilusFileUndoInfoDetails;
+
+struct _NautilusFileUndoInfo {
+ GObject parent;
+ NautilusFileUndoInfoDetails *priv;
+};
+
+struct _NautilusFileUndoInfoClass {
+ GObjectClass parent_class;
+
+ void (* undo_func) (NautilusFileUndoInfo *self,
+ GtkWindow *parent_window);
+ void (* redo_func) (NautilusFileUndoInfo *self,
+ GtkWindow *parent_window);
+
+ void (* strings_func) (NautilusFileUndoInfo *self,
+ gchar **undo_label,
+ gchar **undo_description,
+ gchar **redo_label,
+ gchar **redo_description);
+};
+
+GType nautilus_file_undo_info_get_type (void) G_GNUC_CONST;
+
+void nautilus_file_undo_info_apply_async (NautilusFileUndoInfo *self,
+ gboolean undo,
+ GtkWindow *parent_window,
+ 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 ())
+#define NAUTILUS_FILE_UNDO_INFO_EXT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), NAUTILUS_TYPE_FILE_UNDO_INFO_EXT, NautilusFileUndoInfoExt))
+#define NAUTILUS_FILE_UNDO_INFO_EXT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), NAUTILUS_TYPE_FILE_UNDO_INFO_EXT, NautilusFileUndoInfoExtClass))
+#define NAUTILUS_IS_FILE_UNDO_INFO_EXT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), NAUTILUS_TYPE_FILE_UNDO_INFO_EXT))
+#define NAUTILUS_IS_FILE_UNDO_INFO_EXT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), NAUTILUS_TYPE_FILE_UNDO_INFO_EXT))
+#define NAUTILUS_FILE_UNDO_INFO_EXT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), NAUTILUS_TYPE_FILE_UNDO_INFO_EXT, NautilusFileUndoInfoExtClass))
+
+typedef struct _NautilusFileUndoInfoExt NautilusFileUndoInfoExt;
+typedef struct _NautilusFileUndoInfoExtClass NautilusFileUndoInfoExtClass;
+typedef struct _NautilusFileUndoInfoExtDetails NautilusFileUndoInfoExtDetails;
+
+struct _NautilusFileUndoInfoExt {
+ NautilusFileUndoInfo parent;
+ NautilusFileUndoInfoExtDetails *priv;
+};
+
+struct _NautilusFileUndoInfoExtClass {
+ NautilusFileUndoInfoClass parent_class;
+};
+
+GType nautilus_file_undo_info_ext_get_type (void) G_GNUC_CONST;
+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 ())
+#define NAUTILUS_FILE_UNDO_INFO_CREATE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), NAUTILUS_TYPE_FILE_UNDO_INFO_CREATE, NautilusFileUndoInfoCreate))
+#define NAUTILUS_FILE_UNDO_INFO_CREATE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), NAUTILUS_TYPE_FILE_UNDO_INFO_CREATE, NautilusFileUndoInfoCreateClass))
+#define NAUTILUS_IS_FILE_UNDO_INFO_CREATE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), NAUTILUS_TYPE_FILE_UNDO_INFO_CREATE))
+#define NAUTILUS_IS_FILE_UNDO_INFO_CREATE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), NAUTILUS_TYPE_FILE_UNDO_INFO_CREATE))
+#define NAUTILUS_FILE_UNDO_INFO_CREATE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), NAUTILUS_TYPE_FILE_UNDO_INFO_CREATE, NautilusFileUndoInfoCreateClass))
+
+typedef struct _NautilusFileUndoInfoCreate NautilusFileUndoInfoCreate;
+typedef struct _NautilusFileUndoInfoCreateClass NautilusFileUndoInfoCreateClass;
+typedef struct _NautilusFileUndoInfoCreateDetails NautilusFileUndoInfoCreateDetails;
+
+struct _NautilusFileUndoInfoCreate {
+ NautilusFileUndoInfo parent;
+ NautilusFileUndoInfoCreateDetails *priv;
+};
+
+struct _NautilusFileUndoInfoCreateClass {
+ NautilusFileUndoInfoClass parent_class;
+};
+
+GType nautilus_file_undo_info_create_get_type (void) G_GNUC_CONST;
+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 ())
+#define NAUTILUS_FILE_UNDO_INFO_RENAME(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), NAUTILUS_TYPE_FILE_UNDO_INFO_RENAME, NautilusFileUndoInfoRename))
+#define NAUTILUS_FILE_UNDO_INFO_RENAME_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), NAUTILUS_TYPE_FILE_UNDO_INFO_RENAME, NautilusFileUndoInfoRenameClass))
+#define NAUTILUS_IS_FILE_UNDO_INFO_RENAME(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), NAUTILUS_TYPE_FILE_UNDO_INFO_RENAME))
+#define NAUTILUS_IS_FILE_UNDO_INFO_RENAME_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), NAUTILUS_TYPE_FILE_UNDO_INFO_RENAME))
+#define NAUTILUS_FILE_UNDO_INFO_RENAME_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), NAUTILUS_TYPE_FILE_UNDO_INFO_RENAME, NautilusFileUndoInfoRenameClass))
+
+typedef struct _NautilusFileUndoInfoRename NautilusFileUndoInfoRename;
+typedef struct _NautilusFileUndoInfoRenameClass NautilusFileUndoInfoRenameClass;
+typedef struct _NautilusFileUndoInfoRenameDetails NautilusFileUndoInfoRenameDetails;
+
+struct _NautilusFileUndoInfoRename {
+ NautilusFileUndoInfo parent;
+ NautilusFileUndoInfoRenameDetails *priv;
+};
+
+struct _NautilusFileUndoInfoRenameClass {
+ NautilusFileUndoInfoClass parent_class;
+};
+
+GType nautilus_file_undo_info_rename_get_type (void) G_GNUC_CONST;
+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);
+
+/* trash */
+#define NAUTILUS_TYPE_FILE_UNDO_INFO_TRASH (nautilus_file_undo_info_trash_get_type ())
+#define NAUTILUS_FILE_UNDO_INFO_TRASH(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), NAUTILUS_TYPE_FILE_UNDO_INFO_TRASH, NautilusFileUndoInfoTrash))
+#define NAUTILUS_FILE_UNDO_INFO_TRASH_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), NAUTILUS_TYPE_FILE_UNDO_INFO_TRASH, NautilusFileUndoInfoTrashClass))
+#define NAUTILUS_IS_FILE_UNDO_INFO_TRASH(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), NAUTILUS_TYPE_FILE_UNDO_INFO_TRASH))
+#define NAUTILUS_IS_FILE_UNDO_INFO_TRASH_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), NAUTILUS_TYPE_FILE_UNDO_INFO_TRASH))
+#define NAUTILUS_FILE_UNDO_INFO_TRASH_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), NAUTILUS_TYPE_FILE_UNDO_INFO_TRASH, NautilusFileUndoInfoTrashClass))
+
+typedef struct _NautilusFileUndoInfoTrash NautilusFileUndoInfoTrash;
+typedef struct _NautilusFileUndoInfoTrashClass NautilusFileUndoInfoTrashClass;
+typedef struct _NautilusFileUndoInfoTrashDetails NautilusFileUndoInfoTrashDetails;
+
+struct _NautilusFileUndoInfoTrash {
+ NautilusFileUndoInfo parent;
+ NautilusFileUndoInfoTrashDetails *priv;
+};
+
+struct _NautilusFileUndoInfoTrashClass {
+ NautilusFileUndoInfoClass parent_class;
+};
+
+GType nautilus_file_undo_info_trash_get_type (void) G_GNUC_CONST;
+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 ())
+#define NAUTILUS_FILE_UNDO_INFO_REC_PERMISSIONS(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), NAUTILUS_TYPE_FILE_UNDO_INFO_REC_PERMISSIONS, NautilusFileUndoInfoRecPermissions))
+#define NAUTILUS_FILE_UNDO_INFO_REC_PERMISSIONS_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), NAUTILUS_TYPE_FILE_UNDO_INFO_REC_PERMISSIONS, NautilusFileUndoInfoRecPermissionsClass))
+#define NAUTILUS_IS_FILE_UNDO_INFO_REC_PERMISSIONS(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), NAUTILUS_TYPE_FILE_UNDO_INFO_REC_PERMISSIONS))
+#define NAUTILUS_IS_FILE_UNDO_INFO_REC_PERMISSIONS_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), NAUTILUS_TYPE_FILE_UNDO_INFO_REC_PERMISSIONS))
+#define NAUTILUS_FILE_UNDO_INFO_REC_PERMISSIONS_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), NAUTILUS_TYPE_FILE_UNDO_INFO_REC_PERMISSIONS, NautilusFileUndoInfoRecPermissionsClass))
+
+typedef struct _NautilusFileUndoInfoRecPermissions NautilusFileUndoInfoRecPermissions;
+typedef struct _NautilusFileUndoInfoRecPermissionsClass NautilusFileUndoInfoRecPermissionsClass;
+typedef struct _NautilusFileUndoInfoRecPermissionsDetails NautilusFileUndoInfoRecPermissionsDetails;
+
+struct _NautilusFileUndoInfoRecPermissions {
+ NautilusFileUndoInfo parent;
+ NautilusFileUndoInfoRecPermissionsDetails *priv;
+};
+
+struct _NautilusFileUndoInfoRecPermissionsClass {
+ NautilusFileUndoInfoClass parent_class;
+};
+
+GType nautilus_file_undo_info_rec_permissions_get_type (void) G_GNUC_CONST;
+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 ())
+#define NAUTILUS_FILE_UNDO_INFO_PERMISSIONS(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), NAUTILUS_TYPE_FILE_UNDO_INFO_PERMISSIONS, NautilusFileUndoInfoPermissions))
+#define NAUTILUS_FILE_UNDO_INFO_PERMISSIONS_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), NAUTILUS_TYPE_FILE_UNDO_INFO_PERMISSIONS, NautilusFileUndoInfoPermissionsClass))
+#define NAUTILUS_IS_FILE_UNDO_INFO_PERMISSIONS(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), NAUTILUS_TYPE_FILE_UNDO_INFO_PERMISSIONS))
+#define NAUTILUS_IS_FILE_UNDO_INFO_PERMISSIONS_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), NAUTILUS_TYPE_FILE_UNDO_INFO_PERMISSIONS))
+#define NAUTILUS_FILE_UNDO_INFO_PERMISSIONS_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), NAUTILUS_TYPE_FILE_UNDO_INFO_PERMISSIONS, NautilusFileUndoInfoPermissionsClass))
+
+typedef struct _NautilusFileUndoInfoPermissions NautilusFileUndoInfoPermissions;
+typedef struct _NautilusFileUndoInfoPermissionsClass NautilusFileUndoInfoPermissionsClass;
+typedef struct _NautilusFileUndoInfoPermissionsDetails NautilusFileUndoInfoPermissionsDetails;
+
+struct _NautilusFileUndoInfoPermissions {
+ NautilusFileUndoInfo parent;
+ NautilusFileUndoInfoPermissionsDetails *priv;
+};
+
+struct _NautilusFileUndoInfoPermissionsClass {
+ NautilusFileUndoInfoClass parent_class;
+};
+
+GType nautilus_file_undo_info_permissions_get_type (void) G_GNUC_CONST;
+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 ())
+#define NAUTILUS_FILE_UNDO_INFO_OWNERSHIP(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), NAUTILUS_TYPE_FILE_UNDO_INFO_OWNERSHIP, NautilusFileUndoInfoOwnership))
+#define NAUTILUS_FILE_UNDO_INFO_OWNERSHIP_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), NAUTILUS_TYPE_FILE_UNDO_INFO_OWNERSHIP, NautilusFileUndoInfoOwnershipClass))
+#define NAUTILUS_IS_FILE_UNDO_INFO_OWNERSHIP(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), NAUTILUS_TYPE_FILE_UNDO_INFO_OWNERSHIP))
+#define NAUTILUS_IS_FILE_UNDO_INFO_OWNERSHIP_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), NAUTILUS_TYPE_FILE_UNDO_INFO_OWNERSHIP))
+#define NAUTILUS_FILE_UNDO_INFO_OWNERSHIP_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), NAUTILUS_TYPE_FILE_UNDO_INFO_OWNERSHIP, NautilusFileUndoInfoOwnershipClass))
+
+typedef struct _NautilusFileUndoInfoOwnership NautilusFileUndoInfoOwnership;
+typedef struct _NautilusFileUndoInfoOwnershipClass NautilusFileUndoInfoOwnershipClass;
+typedef struct _NautilusFileUndoInfoOwnershipDetails NautilusFileUndoInfoOwnershipDetails;
+
+struct _NautilusFileUndoInfoOwnership {
+ NautilusFileUndoInfo parent;
+ NautilusFileUndoInfoOwnershipDetails *priv;
+};
+
+struct _NautilusFileUndoInfoOwnershipClass {
+ NautilusFileUndoInfoClass parent_class;
+};
+
+GType nautilus_file_undo_info_ownership_get_type (void) G_GNUC_CONST;
+NautilusFileUndoInfo *nautilus_file_undo_info_ownership_new (NautilusFileUndoOp op_type,
+ GFile *file,
+ const char *current_data,
+ const char *new_data);
+
+#endif /* __NAUTILUS_FILE_UNDO_OPERATIONS_H__ */
diff --git a/src/nautilus-file-utilities.c b/src/nautilus-file-utilities.c
new file mode 100644
index 000000000..40b37df51
--- /dev/null
+++ b/src/nautilus-file-utilities.c
@@ -0,0 +1,1181 @@
+
+/* 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 <eel/eel-glib-extensions.h>
+#include <eel/eel-stock-dialogs.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)
+#define DEFAULT_DESKTOP_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 {
+ 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 (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);
+}
+
+static const char *
+get_desktop_path (void)
+{
+ const char *desktop_path;
+
+ desktop_path = g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP);
+ if (desktop_path == NULL) {
+ desktop_path = g_get_home_dir ();
+ }
+
+ return desktop_path;
+}
+
+/**
+ * nautilus_get_desktop_directory:
+ *
+ * Get the path for the directory containing files on the desktop.
+ *
+ * Return value: the directory path.
+ **/
+char *
+nautilus_get_desktop_directory (void)
+{
+ const char *desktop_directory;
+
+ desktop_directory = get_desktop_path ();
+
+ /* Don't try to create a home directory */
+ if (!g_file_test (desktop_directory, G_FILE_TEST_EXISTS)) {
+ g_mkdir (desktop_directory, DEFAULT_DESKTOP_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 g_strdup (desktop_directory);
+}
+
+GFile *
+nautilus_get_desktop_location (void)
+{
+ return g_file_new_for_path (get_desktop_path ());
+}
+
+/**
+ * nautilus_get_desktop_directory_uri:
+ *
+ * Get the uri for the directory containing files on the desktop.
+ *
+ * Return value: the directory path.
+ **/
+char *
+nautilus_get_desktop_directory_uri (void)
+{
+ char *desktop_path;
+ char *desktop_uri;
+
+ desktop_path = nautilus_get_desktop_directory ();
+ desktop_uri = g_filename_to_uri (desktop_path, NULL, NULL);
+ g_free (desktop_path);
+
+ return desktop_uri;
+}
+
+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;
+}
+
+/* These need to be reset to NULL when desktop_is_home_dir changes */
+static GFile *desktop_dir = NULL;
+static GFile *desktop_dir_dir = NULL;
+static char *desktop_dir_filename = NULL;
+
+static void
+update_desktop_dir (void)
+{
+ const char *path;
+ char *dirname;
+
+ path = get_desktop_path ();
+ desktop_dir = g_file_new_for_path (path);
+
+ dirname = g_path_get_dirname (path);
+ desktop_dir_dir = g_file_new_for_path (dirname);
+ g_free (dirname);
+ desktop_dir_filename = g_path_get_basename (path);
+}
+
+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_desktop_directory_file (GFile *dir,
+ const char *file)
+{
+
+ if (desktop_dir == NULL) {
+ update_desktop_dir ();
+ }
+
+ return (g_file_equal (dir, desktop_dir_dir) &&
+ strcmp (file, desktop_dir_filename) == 0);
+}
+
+gboolean
+nautilus_is_desktop_directory (GFile *dir)
+{
+
+ if (desktop_dir == NULL) {
+ update_desktop_dir ();
+ }
+
+ return g_file_equal (dir, desktop_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);
+}
+
+GMount *
+nautilus_get_mounted_mount_for_root (GFile *location)
+{
+ GVolumeMonitor *volume_monitor;
+ 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;
+}
+
+char *
+nautilus_ensure_unique_file_name (const char *directory_uri,
+ const char *base_name,
+ const char *extension)
+{
+ GFileInfo *info;
+ char *filename;
+ GFile *dir, *child;
+ int copy;
+ char *res;
+
+ dir = g_file_new_for_uri (directory_uri);
+
+ info = g_file_query_info (dir, G_FILE_ATTRIBUTE_STANDARD_TYPE, 0, NULL, NULL);
+ if (info == NULL) {
+ g_object_unref (dir);
+ return NULL;
+ }
+ g_object_unref (info);
+
+ filename = g_strdup_printf ("%s%s",
+ base_name,
+ extension);
+ child = g_file_get_child (dir, filename);
+ g_free (filename);
+
+ copy = 1;
+ while ((info = g_file_query_info (child, G_FILE_ATTRIBUTE_STANDARD_TYPE, 0, NULL, NULL)) != NULL) {
+ g_object_unref (info);
+ g_object_unref (child);
+
+ filename = g_strdup_printf ("%s-%d%s",
+ base_name,
+ copy,
+ extension);
+ child = g_file_get_child (dir, filename);
+ g_free (filename);
+
+ copy++;
+ }
+
+ res = g_file_get_uri (child);
+ g_object_unref (child);
+ g_object_unref (dir);
+
+ return res;
+}
+
+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;
+}
+
+/* Returns TRUE if the file is in XDG_DATA_DIRS. This is used for
+ deciding if a desktop file is "trusted" based on the path */
+gboolean
+nautilus_is_in_system_dir (GFile *file)
+{
+ const char * const * data_dirs;
+ char *path;
+ int i;
+ gboolean res;
+
+ if (!g_file_is_native (file)) {
+ return FALSE;
+ }
+
+ path = g_file_get_path (file);
+
+ res = FALSE;
+
+ data_dirs = g_get_system_data_dirs ();
+ for (i = 0; path != NULL && data_dirs[i] != NULL; i++) {
+ if (g_str_has_prefix (path, data_dirs[i])) {
+ res = TRUE;
+ break;
+ }
+
+ }
+
+ g_free (path);
+
+ return res;
+}
+
+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;
+}
+
+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
+ (locations, NULL,
+ original_dir_location,
+ data->parent_window,
+ 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);
+
+ eel_show_warning_dialog (message,
+ _("The item cannot be restored from trash"),
+ parent_window);
+ 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 */
+ /* translators: these describe the contents of removable media */
+ if (strcmp (content_type, "x-content/audio-cdda") == 0) {
+ 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"));
+ } 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) {
+
+ /* translators: these describe the contents of removable media */
+ if (strcmp (content_types[0], "x-content/audio-player") == 0) {
+ 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;
+}
+
+#if !defined (NAUTILUS_OMIT_SELF_CHECK)
+
+void
+nautilus_self_check_file_utilities (void)
+{
+}
+
+void
+nautilus_ensure_extension_builtins (void)
+{
+ g_type_ensure (NAUTILUS_TYPE_SEARCH_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 */
diff --git a/src/nautilus-file-utilities.h b/src/nautilus-file-utilities.h
new file mode 100644
index 000000000..ee5f62498
--- /dev/null
+++ b/src/nautilus-file-utilities.h
@@ -0,0 +1,101 @@
+
+/* 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>
+*/
+
+#ifndef NAUTILUS_FILE_UTILITIES_H
+#define NAUTILUS_FILE_UTILITIES_H
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+#define NAUTILUS_SAVED_SEARCH_EXTENSION ".savedSearch"
+#define NAUTILUS_SAVED_SEARCH_MIMETYPE "application/x-gnome-saved-search"
+
+/* 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_desktop_directory (void);
+GFile * nautilus_get_desktop_location (void);
+char * nautilus_get_desktop_directory_uri (void);
+char * nautilus_get_home_directory_uri (void);
+gboolean nautilus_is_desktop_directory_file (GFile *dir,
+ const char *filename);
+gboolean nautilus_is_root_directory (GFile *dir);
+gboolean nautilus_is_desktop_directory (GFile *dir);
+gboolean nautilus_is_home_directory (GFile *dir);
+gboolean nautilus_is_home_directory_file (GFile *dir,
+ const char *filename);
+gboolean nautilus_is_in_system_dir (GFile *location);
+gboolean nautilus_is_search_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 name that is guranteed to be unique, but
+ * tries to make the name readable to users.
+ * This isn't race-free, so don't use for security-related things
+ */
+char * nautilus_ensure_unique_file_name (const char *directory_uri,
+ const char *base_name,
+ const char *extension);
+
+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);
+
+void nautilus_ensure_extension_points (void);
+void nautilus_ensure_extension_builtins (void);
+
+#endif /* NAUTILUS_FILE_UTILITIES_H */
diff --git a/src/nautilus-file.c b/src/nautilus-file.c
new file mode 100644
index 000000000..49f23c95e
--- /dev/null
+++ b/src/nautilus-file.c
@@ -0,0 +1,8382 @@
+/*
+ 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 <config.h>
+#include "nautilus-file.h"
+
+#include "nautilus-directory-notify.h"
+#include "nautilus-directory-private.h"
+#include "nautilus-signaller.h"
+#include "nautilus-file-attributes.h"
+#include "nautilus-file-private.h"
+#include "nautilus-file-operations.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-lib-self-check-functions.h"
+#include "nautilus-link.h"
+#include "nautilus-metadata.h"
+#include "nautilus-module.h"
+#include "nautilus-thumbnails.h"
+#include "nautilus-ui-utilities.h"
+#include "nautilus-video-mime-types.h"
+#include "nautilus-vfs-file.h"
+#include "nautilus-file-undo-operations.h"
+#include "nautilus-file-undo-manager.h"
+#include <eel/eel-debug.h>
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-gtk-extensions.h>
+#include <eel/eel-vfs-extensions.h>
+#include <eel/eel-string.h>
+#include <grp.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gio/gio.h>
+#include <glib.h>
+#include <gdesktop-enums.h>
+#include <libnautilus-extension/nautilus-file-info.h>
+#include <libnautilus-extension/nautilus-extension-private.h>
+#include <libxml/parser.h>
+#include <pwd.h>
+#include <stdlib.h>
+#include <sys/time.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#ifdef HAVE_SELINUX
+#include <selinux/selinux.h>
+#endif
+
+#define DEBUG_FLAG NAUTILUS_DEBUG_FILE
+#include "nautilus-debug.h"
+
+/* 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
+
+/* 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 (1<<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;
+int cached_thumbnail_size;
+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_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;
+
+static void nautilus_file_info_iface_init (NautilusFileInfoIface *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_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);
+static gboolean real_drag_can_accept_files (NautilusFile *drop_target_item);
+
+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 (eel_ref_str_peek (file->details->display_name), display_name) != 0) {
+ changed = TRUE;
+
+ eel_ref_str_unref (file->details->display_name);
+
+ if (g_strcmp0 (eel_ref_str_peek (file->details->name), display_name) == 0) {
+ file->details->display_name = eel_ref_str_ref (file->details->name);
+ } else {
+ file->details->display_name = eel_ref_str_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 (eel_ref_str_peek (file->details->edit_name), edit_name) != 0) {
+ changed = TRUE;
+
+ eel_ref_str_unref (file->details->edit_name);
+ if (g_strcmp0 (eel_ref_str_peek (file->details->display_name), edit_name) == 0) {
+ file->details->edit_name = eel_ref_str_ref (file->details->display_name);
+ } else {
+ file->details->edit_name = eel_ref_str_new (edit_name);
+ }
+ }
+
+ file->details->got_custom_display_name = custom;
+ return changed;
+}
+
+static void
+nautilus_file_clear_display_name (NautilusFile *file)
+{
+ eel_ref_str_unref (file->details->display_name);
+ file->details->display_name = NULL;
+ g_free (file->details->display_name_collation_key);
+ file->details->display_name_collation_key = NULL;
+ eel_ref_str_unref (file->details->edit_name);
+ file->details->edit_name = NULL;
+}
+
+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
+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 (!eel_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_launcher = FALSE;
+ file->details->is_foreign_link = FALSE;
+ file->details->is_trusted_link = 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;
+ g_free (file->details->symlink_name);
+ file->details->symlink_name = NULL;
+ eel_ref_str_unref (file->details->mime_type);
+ file->details->mime_type = NULL;
+ g_free (file->details->selinux_context);
+ file->details->selinux_context = NULL;
+ g_free (file->details->description);
+ file->details->description = NULL;
+ eel_ref_str_unref (file->details->owner);
+ file->details->owner = NULL;
+ eel_ref_str_unref (file->details->owner_real);
+ file->details->owner_real = NULL;
+ eel_ref_str_unref (file->details->group);
+ file->details->group = NULL;
+
+ eel_ref_str_unref (file->details->filesystem_id);
+ file->details->filesystem_id = NULL;
+
+ clear_metadata (file);
+}
+
+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 = eel_ref_str_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 NautilusFile *
+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 file;
+}
+
+NautilusFile *
+nautilus_file_get (GFile *location)
+{
+ return nautilus_file_get_internal (location, TRUE);
+}
+
+NautilusFile *
+nautilus_file_get_existing (GFile *location)
+{
+ return nautilus_file_get_internal (location, FALSE);
+}
+
+NautilusFile *
+nautilus_file_get_existing_by_uri (const char *uri)
+{
+ GFile *location;
+ NautilusFile *file;
+
+ location = g_file_new_for_uri (uri);
+ file = nautilus_file_get_internal (location, FALSE);
+ g_object_unref (location);
+
+ return file;
+}
+
+NautilusFile *
+nautilus_file_get_by_uri (const char *uri)
+{
+ GFile *location;
+ NautilusFile *file;
+
+ location = g_file_new_for_uri (uri);
+ file = nautilus_file_get_internal (location, TRUE);
+ g_object_unref (location);
+
+ return file;
+}
+
+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);
+ eel_ref_str_unref (file->details->name);
+ eel_ref_str_unref (file->details->display_name);
+ g_free (file->details->display_name_collation_key);
+ g_free (file->details->directory_name_collation_key);
+ eel_ref_str_unref (file->details->edit_name);
+ if (file->details->icon) {
+ g_object_unref (file->details->icon);
+ }
+ g_free (file->details->thumbnail_path);
+ g_free (file->details->symlink_name);
+ eel_ref_str_unref (file->details->mime_type);
+ eel_ref_str_unref (file->details->owner);
+ eel_ref_str_unref (file->details->owner_real);
+ eel_ref_str_unref (file->details->group);
+ 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);
+ }
+
+ eel_ref_str_unref (file->details->filesystem_id);
+ eel_ref_str_unref (file->details->filesystem_type);
+ file->details->filesystem_type = NULL;
+ 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_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_assert (NAUTILUS_IS_FILE (file));
+
+ 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);
+}
+
+GFile *
+nautilus_file_get_parent_location (NautilusFile *file)
+{
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ if (nautilus_file_is_self_owned (file)) {
+ return NULL;
+ }
+
+ return nautilus_directory_get_location (file->details->directory);
+}
+
+NautilusFile *
+nautilus_file_get_parent (NautilusFile *file)
+{
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ if (nautilus_file_is_self_owned (file)) {
+ return NULL;
+ }
+
+ return nautilus_directory_get_corresponding_file (file->details->directory);
+}
+
+/**
+ * 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 file->details->can_write;
+}
+
+/**
+ * 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_is_desktop_directory:
+ *
+ * Check whether this file is the desktop directory.
+ *
+ * @file: The file to check.
+ *
+ * Return value: TRUE if this is the physical desktop directory.
+ */
+gboolean
+nautilus_file_is_desktop_directory (NautilusFile *file)
+{
+ GFile *dir;
+
+ dir = file->details->directory->details->location;
+
+ if (dir == NULL) {
+ return FALSE;
+ }
+
+ return nautilus_is_desktop_directory_file (dir, eel_ref_str_peek (file->details->name));
+}
+
+static gboolean
+is_desktop_file (NautilusFile *file)
+{
+ return nautilus_file_is_mime_type (file, "application/x-desktop");
+}
+
+static gboolean
+can_rename_desktop_file (NautilusFile *file)
+{
+ GFile *location;
+ gboolean res;
+
+ location = nautilus_file_get_location (file);
+ res = g_file_is_native (location);
+ g_object_unref (location);
+ return res;
+}
+
+/**
+ * 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)
+{
+ return NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->can_rename (file);
+}
+
+static gboolean
+real_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 ((is_desktop_file (file) && !can_rename_desktop_file (file)) ||
+ 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)
+{
+ GFile *dir;
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL);
+
+ dir = file->details->directory->details->location;
+
+ if (nautilus_file_is_self_owned (file)) {
+ return g_object_ref (dir);
+ }
+
+ return g_file_get_child (dir, eel_ref_str_peek (file->details->name));
+}
+
+/* Return the actual uri associated with the passed-in file. */
+char *
+nautilus_file_get_uri (NautilusFile *file)
+{
+ char *uri;
+ GFile *loc;
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL);
+
+ loc = nautilus_file_get_location (file);
+ uri = g_file_get_uri (loc);
+ g_object_unref (loc);
+
+ return uri;
+}
+
+char *
+nautilus_file_get_uri_scheme (NautilusFile *file)
+{
+ GFile *loc;
+ char *scheme;
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL);
+
+ if (file->details->directory == NULL ||
+ file->details->directory->details->location == NULL) {
+ return NULL;
+ }
+
+ loc = nautilus_directory_get_location (file->details->directory);
+ scheme = g_file_get_uri_scheme (loc);
+ g_object_unref (loc);
+
+ return scheme;
+}
+
+
+gboolean
+nautilus_file_opens_in_view (NautilusFile *file)
+{
+ return NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->opens_in_view (file);
+}
+
+static gboolean
+real_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)
+{
+ op->file->details->operations_in_progress = g_list_remove
+ (op->file->details->operations_in_progress, op);
+}
+
+void
+nautilus_file_operation_free (NautilusFileOperation *op)
+{
+ nautilus_file_operation_remove (op);
+ nautilus_file_unref (op->file);
+ 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);
+ 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) {
+ 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_uri = nautilus_file_get_uri (op->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);
+
+ /* the rename could have affected the display name if e.g.
+ * we're in a vfolder where the name comes from a desktop file
+ * and a rename affects the contents of the desktop file.
+ */
+ if (op->file->details->got_custom_display_name) {
+ nautilus_file_invalidate_attributes (op->file,
+ NAUTILUS_FILE_ATTRIBUTE_INFO |
+ NAUTILUS_FILE_ATTRIBUTE_LINK_INFO);
+ }
+
+ 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 = eel_ref_str_peek (file->details->name);
+ return strcmp (new_name, old_name) == 0;
+}
+
+void
+nautilus_file_rename (NautilusFile *file,
+ const char *new_name,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data)
+{
+ NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->rename (file,
+ new_name,
+ callback,
+ callback_data);
+}
+
+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"));
+ (* callback) (file, NULL, error, callback_data);
+ g_error_free (error);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+real_rename (NautilusFile *file,
+ const char *new_name,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data)
+{
+ NautilusFileOperation *op;
+ char *uri;
+ char *old_name;
+ char *new_file_name;
+ gboolean success, name_changed;
+ gboolean is_renameable_desktop_file;
+ GFile *location;
+ GError *error;
+
+ g_return_if_fail (NAUTILUS_IS_FILE (file));
+ g_return_if_fail (new_name != NULL);
+ g_return_if_fail (callback != NULL);
+
+ is_renameable_desktop_file =
+ is_desktop_file (file) && can_rename_desktop_file (file);
+
+ /* Return an error for incoming names containing path separators.
+ * But not for .desktop files as '/' are allowed for them */
+ if (strstr (new_name, "/") != NULL && !is_renameable_desktop_file) {
+ error = g_error_new (G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
+ _("Slashes are not allowed in filenames"));
+ (* callback) (file, NULL, error, callback_data);
+ g_error_free (error);
+ return;
+ }
+
+ /* 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;
+ }
+ /* 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 (!is_renameable_desktop_file &&
+ name_is (file, new_name)) {
+ (* callback) (file, NULL, NULL, callback_data);
+ return;
+ }
+
+ /* 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"));
+
+ (* callback) (file, NULL, error, callback_data);
+ g_error_free (error);
+ return;
+ }
+
+ if (is_renameable_desktop_file) {
+ /* Don't actually change the name if the new name is the same.
+ * This helps for the vfolder method where this can happen and
+ * we want to minimize actual changes
+ */
+ uri = nautilus_file_get_uri (file);
+ old_name = nautilus_link_local_get_text (uri);
+ if (old_name != NULL && strcmp (new_name, old_name) == 0) {
+ success = TRUE;
+ name_changed = FALSE;
+ } else {
+ success = nautilus_link_local_set_text (uri, new_name);
+ name_changed = TRUE;
+ }
+ g_free (old_name);
+ g_free (uri);
+
+ if (!success) {
+ error = g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Probably the content of the file is an invalid desktop file format"));
+ (* callback) (file, NULL, error, callback_data);
+ g_error_free (error);
+ return;
+ }
+ new_file_name = g_strdup_printf ("%s.desktop", new_name);
+ new_file_name = g_strdelimit (new_file_name, "/", '-');
+
+ if (name_is (file, new_file_name)) {
+ if (name_changed) {
+ nautilus_file_invalidate_attributes (file,
+ NAUTILUS_FILE_ATTRIBUTE_INFO |
+ NAUTILUS_FILE_ATTRIBUTE_LINK_INFO);
+ }
+
+ (* callback) (file, NULL, NULL, callback_data);
+ g_free (new_file_name);
+ return;
+ }
+ } else {
+ new_file_name = g_strdup (new_name);
+ }
+
+ /* 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_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);
+}
+
+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;
+ 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);
+
+ mime_type = g_file_info_get_content_type (info);
+ 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 &&
+ !nautilus_file_is_in_trash (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 (eel_ref_str_peek (file->details->owner), owner) != 0) {
+ changed = TRUE;
+ eel_ref_str_unref (file->details->owner);
+ file->details->owner = eel_ref_str_get_unique (owner);
+ }
+
+ if (g_strcmp0 (eel_ref_str_peek (file->details->owner_real), owner_real) != 0) {
+ changed = TRUE;
+ eel_ref_str_unref (file->details->owner_real);
+ file->details->owner_real = eel_ref_str_get_unique (owner_real);
+ }
+
+ if (g_strcmp0 (eel_ref_str_peek (file->details->group), group) != 0) {
+ changed = TRUE;
+ eel_ref_str_unref (file->details->group);
+ file->details->group = eel_ref_str_get_unique (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 (g_strcmp0 (eel_ref_str_peek (file->details->mime_type), mime_type) != 0) {
+ changed = TRUE;
+ eel_ref_str_unref (file->details->mime_type);
+ file->details->mime_type = eel_ref_str_get_unique (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 (eel_ref_str_peek (file->details->filesystem_id), filesystem_id) != 0) {
+ changed = TRUE;
+ eel_ref_str_unref (file->details->filesystem_id);
+ file->details->filesystem_id = eel_ref_str_get_unique (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;
+ }
+
+ 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 (eel_ref_str_peek (file->details->name), name) != 0) {
+ changed = TRUE;
+
+ node = nautilus_directory_begin_file_name_change
+ (file->details->directory, file);
+
+ eel_ref_str_unref (file->details->name);
+ if (g_strcmp0 (eel_ref_str_peek (file->details->display_name),
+ name) == 0) {
+ file->details->name = eel_ref_str_ref (file->details->display_name);
+ } else {
+ file->details->name = eel_ref_str_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);
+ }
+
+ eel_ref_str_unref (file->details->name);
+ file->details->name = eel_ref_str_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;
+ 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 (eel_ref_str_peek (file_1->details->mime_type),
+ eel_ref_str_peek (file_2->details->mime_type)) == 0) {
+ return 0;
+ }
+
+ type_string_1 = nautilus_file_get_type_as_string (file_1);
+ type_string_2 = nautilus_file_get_type_as_string (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 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_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;
+ 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_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);
+ }
+
+ /* 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
+ * @pattern: A string we are comparing it with
+ *
+ * Return value: result of a comparison of the file name and the given pattern,
+ * using the same sorting order as sort by name.
+ **/
+int
+nautilus_file_compare_display_name (NautilusFile *file,
+ const char *pattern)
+{
+ const char *name;
+ int result;
+
+ g_return_val_if_fail (pattern != NULL, -1);
+
+ name = nautilus_file_peek_display_name (file);
+ result = g_utf8_collate (name, pattern);
+ 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
+ * @show_foreign: whether we want to show foreign 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,
+ gboolean show_foreign)
+{
+ /* 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;
+ }
+
+ if (!show_foreign && nautilus_file_is_foreign_link (file)) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+nautilus_file_is_home (NautilusFile *file)
+{
+ GFile *dir;
+
+ dir = file->details->directory->details->location;
+ if (dir == NULL) {
+ return FALSE;
+ }
+
+ return nautilus_is_home_directory_file (dir,
+ eel_ref_str_peek (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,
+ TRUE);
+}
+
+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 *last;
+ GList *reversed;
+
+ *failed = NULL;
+ /* Avoid using g_list_append since it's O(n) */
+ reversed = g_list_copy (files);
+ reversed = g_list_reverse (reversed);
+ last = g_list_last (reversed);
+ for (l = last; l != NULL; l = l->prev) {
+ 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;
+}
+
+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 = eel_ref_str_peek (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 ?
+ eel_ref_str_peek (file->details->display_name) : "";
+}
+
+char *
+nautilus_file_get_display_name (NautilusFile *file)
+{
+ return g_strdup (nautilus_file_peek_display_name (file));
+}
+
+char *
+nautilus_file_get_edit_name (NautilusFile *file)
+{
+ const char *res;
+
+ res = eel_ref_str_peek (file->details->edit_name);
+ if (res == NULL)
+ res = "";
+
+ return g_strdup (res);
+}
+
+char *
+nautilus_file_get_name (NautilusFile *file)
+{
+ return g_strdup (eel_ref_str_peek (file->details->name));
+}
+
+/**
+ * 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_is_launcher (NautilusFile *file)
+{
+ return file->details->is_launcher;
+}
+
+gboolean
+nautilus_file_is_foreign_link (NautilusFile *file)
+{
+ return file->details->is_foreign_link;
+}
+
+gboolean
+nautilus_file_is_trusted_link (NautilusFile *file)
+{
+ return file->details->is_trusted_link;
+}
+
+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);
+
+ if (file->details->activation_uri != NULL) {
+ return g_strdup (file->details->activation_uri);
+ }
+
+ return nautilus_file_get_uri (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);
+}
+
+
+char *
+nautilus_file_get_target_uri (NautilusFile *file)
+{
+ return NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->get_target_uri (file);
+}
+
+static char *
+real_get_target_uri (NautilusFile *file)
+{
+ char *uri, *target_uri;
+ GFile *location;
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL);
+
+ uri = nautilus_file_get_uri (file);
+
+ /* Check for Nautilus link */
+ if (nautilus_file_is_nautilus_link (file)) {
+ location = nautilus_file_get_location (file);
+ /* FIXME bugzilla.gnome.org 43020: This does sync. I/O and works only locally. */
+ if (g_file_is_native (location)) {
+ target_uri = nautilus_link_local_get_link_uri (uri);
+ if (target_uri != NULL) {
+ g_free (uri);
+ uri = target_uri;
+ }
+ }
+ g_object_unref (location);
+ }
+
+ return uri;
+}
+
+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_link_icon (NautilusFile *file)
+{
+ GIcon *icon = NULL;
+
+ if (file->details->got_link_info && file->details->custom_icon != NULL) {
+ icon = g_object_ref (file->details->custom_icon);
+ }
+
+ return 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_custom_or_link_icon (NautilusFile *file)
+{
+ GIcon *icon;
+
+ icon = get_custom_icon (file);
+ if (icon != NULL) {
+ return icon;
+ }
+
+ icon = get_link_icon (file);
+ if (icon != NULL) {
+ return icon;
+ }
+
+ return NULL;
+}
+
+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)
+{
+ NautilusFile *parent;
+ char *filesystem_type = NULL;
+
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ if (nautilus_file_is_directory (file)) {
+ filesystem_type = g_strdup (eel_ref_str_peek (file->details->filesystem_type));
+ } else {
+ parent = nautilus_file_get_parent (file);
+ if (parent != NULL) {
+ filesystem_type = g_strdup (eel_ref_str_peek (parent->details->filesystem_type));
+ nautilus_file_unref (parent);
+ }
+ }
+
+ return filesystem_type;
+}
+
+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 = eel_ref_str_peek (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 = eel_ref_str_peek (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 GIcon *
+apply_emblems_to_icon (NautilusFile *file,
+ GIcon *icon,
+ NautilusFileIconFlags flags)
+{
+ GIcon *emblemed_icon;
+ GEmblem *emblem;
+ GList *emblems, *l;
+
+ emblemed_icon = NULL;
+ emblems = nautilus_file_get_emblem_icons (file);
+
+ for (l = emblems; l != NULL; l = l->next) {
+ 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 (emblems != NULL) {
+ g_list_free_full (emblems, g_object_unref);
+ }
+
+ if (emblemed_icon != NULL) {
+ return emblemed_icon;
+ } else {
+ return g_object_ref (icon);
+ }
+}
+
+GIcon *
+nautilus_file_get_gicon (NautilusFile *file,
+ NautilusFileIconFlags flags)
+{
+ const char * const * names;
+ const char *name;
+ GPtrArray *prepend_array;
+ GIcon *icon, *emblemed_icon;
+ int i;
+ gboolean is_folder = FALSE, is_inode_directory = FALSE;
+
+ if (file == NULL) {
+ return NULL;
+ }
+
+ icon = get_custom_or_link_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) {
+ emblemed_icon = apply_emblems_to_icon (file, icon, flags);
+ g_object_unref (icon);
+ icon = emblemed_icon;
+ }
+
+ return icon;
+}
+
+char *
+nautilus_file_get_thumbnail_path (NautilusFile *file)
+{
+ return g_strdup (file->details->thumbnail_path);
+}
+
+static NautilusIconInfo *
+nautilus_file_get_thumbnail_icon (NautilusFile *file,
+ int size,
+ int scale,
+ NautilusFileIconFlags flags)
+{
+ int modified_size;
+ GdkPixbuf *pixbuf;
+ int w, h, s;
+ double thumb_scale;
+ GIcon *gicon, *emblemed_icon;
+ 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 * cached_thumbnail_size / NAUTILUS_CANVAS_ICON_SIZE_SMALL;
+ DEBUG ("Modifying icon size to %d, as our cached thumbnail size is %d",
+ modified_size, cached_thumbnail_size);
+ }
+
+ 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 <= cached_thumbnail_size) {
+ 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 {
+ 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) {
+ if (nautilus_is_video_file (file)) {
+ nautilus_ui_frame_video (&pixbuf);
+ } else {
+ nautilus_ui_frame_image (&pixbuf);
+ }
+ }
+
+ g_clear_object (&file->details->scaled_thumbnail);
+ file->details->scaled_thumbnail = pixbuf;
+ file->details->thumbnail_scale = thumb_scale;
+ }
+
+ /* Don't scale up if more than 25%, then read the original
+ image instead. We don't want to compare to exactly 100%,
+ since the zoom level 150% gives thumbnails at 144, which is
+ ok to scale up from 128. */
+ if (modified_size > 128 * 1.25 * scale &&
+ !file->details->thumbnail_wants_original &&
+ nautilus_can_thumbnail_internally (file)) {
+ /* Invalidate if we resize upward */
+ file->details->thumbnail_wants_original = TRUE;
+ nautilus_file_invalidate_attributes (file, NAUTILUS_FILE_ATTRIBUTE_THUMBNAIL);
+ }
+
+ DEBUG ("Returning thumbnailed image, at size %d %d",
+ (int) (w * thumb_scale), (int) (h * thumb_scale));
+ } 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_object_ref (pixbuf);
+ } else if (file->details->is_thumbnailing) {
+ gicon = g_themed_icon_new (ICON_NAME_THUMBNAIL_LOADING);
+ }
+
+ if (gicon != NULL) {
+ emblemed_icon = apply_emblems_to_icon (file, gicon, flags);
+ g_object_unref (gicon);
+
+ if (g_icon_equal (emblemed_icon, G_ICON (pixbuf))) {
+ icon = nautilus_icon_info_new_for_pixbuf (pixbuf, scale);
+ } else {
+ icon = nautilus_icon_info_lookup (emblemed_icon, size, scale);
+ }
+
+ g_object_unref (emblemed_icon);
+ }
+
+ 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_SMALL)
+ 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_or_link_icon (file);
+ if (gicon != NULL) {
+ icon = nautilus_icon_info_lookup (gicon, size, scale);
+ g_object_unref (gicon);
+
+ goto out;
+ }
+
+ DEBUG ("Called file_get_icon(), at size %d, 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,
+ 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, *parent;
+ GFile *location;
+ char *filename;
+
+ if (file->details->trash_orig_path != NULL) {
+ orig_file = nautilus_file_get_trash_original_file (file);
+ parent = nautilus_file_get_parent (orig_file);
+ location = nautilus_file_get_location (parent);
+
+ filename = g_file_get_basename (location);
+
+ g_object_unref (location);
+ nautilus_file_unref (parent);
+ nautilus_file_unref (orig_file);
+
+ return filename;
+ }
+
+ return NULL;
+}
+
+/**
+ * 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, *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 = g_date_time_new_from_unix_local (file_time_raw);
+ if (date_format != NAUTILUS_DATE_FORMAT_FULL) {
+ 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, 1, 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 < 1) {
+ 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 < 2) {
+ 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 < 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 (now);
+ g_date_time_unref (today_midnight);
+ } else {
+ // xgettext:no-c-format
+ format = _("%c");
+ }
+
+ result = g_date_time_format (file_date, format);
+ g_date_time_unref (file_date);
+
+ /* 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 (eel_ref_str_peek (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;
+}
+
+
+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;
+}
+
+/**
+ * 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;
+ }
+
+ /* Only root is also allowed to set the owner. */
+ return 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 (eel_ref_str_peek (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 (eel_ref_str_peek (file->details->owner));
+ } else if (file->details->owner == NULL) {
+ user_name = g_strdup (eel_ref_str_peek (file->details->owner_real));
+ } else if (include_real_name &&
+ strcmp (eel_ref_str_peek (file->details->owner), eel_ref_str_peek (file->details->owner_real)) != 0) {
+ user_name = g_strdup (eel_ref_str_peek (file->details->owner_real));
+ } else {
+ user_name = g_strdup (eel_ref_str_peek (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"
+ *
+ * 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_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)
+{
+ return nautilus_file_get_string_attribute_q (file, g_quark_from_string (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 ? _("? items") : "...");
+ }
+ 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 ("");
+ }
+
+ /* 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) {
+ 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) {
+ basic_type = g_strdup (_("Unknown"));
+ }
+
+ 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 = eel_ref_str_peek (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_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)
+{
+ if (file == NULL) {
+ return G_FILE_TYPE_UNKNOWN;
+ }
+
+ return file->details->type;
+}
+
+/**
+ * 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)
+{
+ if (file != NULL) {
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL);
+ if (file->details->mime_type != NULL) {
+ return g_strdup (eel_ref_str_peek (file->details->mime_type));
+ }
+ }
+ return g_strdup ("application/octet-stream");
+}
+
+/**
+ * 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);
+
+ if (file->details->mime_type == NULL) {
+ return FALSE;
+ }
+ return g_content_type_is_a (eel_ref_str_peek (file->details->mime_type),
+ 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 (eel_ref_str_peek (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_directory (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)
+{
+ if (file->details->mount) {
+ return g_object_ref (file->details->mount);
+ }
+ return NULL;
+}
+
+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_nautilus_link
+ *
+ * Check if this file is a "nautilus link", meaning a historical
+ * nautilus xml link file or a desktop file.
+ * @file: NautilusFile representing the file in question.
+ *
+ * Returns: True if the file is a nautilus link.
+ *
+ **/
+gboolean
+nautilus_file_is_nautilus_link (NautilusFile *file)
+{
+ if (file->details->mime_type == NULL) {
+ return FALSE;
+ }
+ return g_content_type_equals (eel_ref_str_peek (file->details->mime_type),
+ "application/x-desktop");
+}
+
+/**
+ * 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)
+{
+ return nautilus_file_get_file_type (file) == G_FILE_TYPE_DIRECTORY;
+}
+
+/**
+ * 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_special_link (NautilusFile *file)
+{
+ return NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->is_special_link (file);
+}
+
+static gboolean
+real_is_special_link (NautilusFile *file)
+{
+ return FALSE;
+}
+
+gboolean
+nautilus_file_is_archive (NautilusFile *file)
+{
+ char *mime_type;
+ int i;
+ static const char * archive_mime_types[] = { "application/x-gtar",
+ "application/x-zip",
+ "application/x-zip-compressed",
+ "application/zip",
+ "application/x-zip",
+ "application/x-tar",
+ "application/x-7z-compressed",
+ "application/x-rar",
+ "application/x-rar-compressed",
+ "application/x-jar",
+ "application/x-java-archive",
+ "application/x-war",
+ "application/x-ear",
+ "application/x-arj",
+ "application/x-gzip",
+ "application/x-bzip-compressed-tar",
+ "application/x-compressed-tar" };
+
+ g_return_val_if_fail (file != NULL, FALSE);
+
+ mime_type = nautilus_file_get_mime_type (file);
+ for (i = 0; i < G_N_ELEMENTS (archive_mime_types); i++) {
+ if (!strcmp (mime_type, archive_mime_types[i])) {
+ g_free (mime_type);
+ return TRUE;
+ }
+ }
+ g_free (mime_type);
+
+ return FALSE;
+}
+
+
+/**
+ * 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);
+}
+
+static const gchar * const remote_types[] = {
+ "afp",
+ "google-drive",
+ "sftp",
+ "webdav",
+ "ftp",
+ "nfs",
+ "cifs",
+ NULL
+};
+
+/**
+ * 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)
+{
+ char *filesystem_type;
+
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ filesystem_type = nautilus_file_get_filesystem_type (file);
+
+ return filesystem_type != NULL && g_strv_contains (remote_types, 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_assert (NAUTILUS_IS_FILE (file));
+
+ uri = nautilus_file_get_uri (file);
+ is_other_locations = g_strcmp0 (uri, "other-locations:///") == 0;
+
+ g_free (uri);
+
+ return is_other_locations;
+}
+
+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 (eel_ref_str_peek (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);
+
+ /* Let the directory know it's gone. */
+ directory = file->details->directory;
+ if (!nautilus_file_is_self_owned (file)) {
+ nautilus_directory_remove_file (directory, file);
+ }
+
+ nautilus_file_clear_info (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) {
+ if (p->data != file) {
+ 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 file->details->is_gone;
+}
+
+/**
+ * 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_link_info (NautilusFile *file)
+{
+ file->details->link_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)
+{
+ NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->invalidate_attributes_internal (file, file_attributes);
+}
+
+static void
+real_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_LINK_INFO)) {
+ invalidate_link_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_LINK_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_desktop_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_desktop_dir =
+ nautilus_file_is_user_special_directory (file, G_USER_DIRECTORY_DESKTOP);
+ is_trash_dir =
+ nautilus_file_is_in_trash (file);
+ is_search_dir =
+ nautilus_file_is_in_search (file);
+
+ if (is_download_dir && !is_desktop_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;
+}
+
+NautilusFileSortType
+nautilus_file_get_default_sort_type (NautilusFile *file,
+ gboolean *reversed)
+{
+ NautilusFileSortType retval;
+ gboolean is_recent, is_download, is_trash, is_search, res;
+
+ retval = NAUTILUS_FILE_SORT_NONE;
+ is_recent = is_download = is_trash = 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_ATIME;
+ } 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;
+ }
+
+ if (reversed != NULL) {
+ *reversed = res;
+ }
+ }
+
+ return retval;
+}
+
+const gchar *
+nautilus_file_get_default_sort_attribute (NautilusFile *file,
+ gboolean *reversed)
+{
+ const gchar *retval;
+ gboolean is_recent, is_download, is_trash, is_search, res;
+
+ retval = NULL;
+ is_download = is_trash = is_search = FALSE;
+ res = get_attributes_for_default_sort_type (file, &is_recent, &is_download, &is_trash, &is_search);
+
+ if (res) {
+ if (is_recent || is_download) {
+ retval = g_quark_to_string (attribute_date_modified_q);
+ } else if (is_trash) {
+ retval = g_quark_to_string (attribute_trashed_on_q);
+ } else if (is_search) {
+ retval = g_quark_to_string (attribute_search_relevance_q);
+ }
+
+ if (reversed != NULL) {
+ *reversed = res;
+ }
+ }
+
+ 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)
+{
+ g_settings_get (nautilus_preferences,
+ NAUTILUS_PREFERENCES_FILE_THUMBNAIL_LIMIT,
+ "t", &cached_thumbnail_limit);
+
+ /* 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
+thumbnail_size_changed_callback (gpointer user_data)
+{
+ cached_thumbnail_size = g_settings_get_int (nautilus_icon_view_preferences,
+ NAUTILUS_PREFERENCES_ICON_VIEW_THUMBNAIL_SIZE);
+
+ /* 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 void
+icon_theme_changed_callback (GtkIconTheme *icon_theme,
+ gpointer user_data)
+{
+ /* Clear all pixmap caches as the icon => pixmap lookup changed */
+ nautilus_icon_info_clear_caches ();
+
+ /* Tell the world that icons might have changed. We could invent a narrower-scope
+ * signal to mean only "thumbnails might have changed" if this ends up being slow
+ * for some reason.
+ */
+ emit_change_signals_for_all_files_in_all_directories ();
+}
+
+static void
+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)
+{
+ GtkIconTheme *icon_theme;
+
+ 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_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");
+
+ G_OBJECT_CLASS (class)->finalize = finalize;
+ G_OBJECT_CLASS (class)->constructor = nautilus_file_constructor;
+
+ class->set_metadata = real_set_metadata;
+ class->set_metadata_as_list = real_set_metadata_as_list;
+ class->can_rename = real_can_rename;
+ class->rename = real_rename;
+ class->get_target_uri = real_get_target_uri;
+ class->drag_can_accept_files = real_drag_can_accept_files;
+ class->invalidate_attributes_internal = real_invalidate_attributes_internal;
+ class->opens_in_view = real_opens_in_view;
+ class->is_special_link = real_is_special_link;
+
+ 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);
+ thumbnail_size_changed_callback (NULL);
+ g_signal_connect_swapped (nautilus_preferences,
+ "changed::" NAUTILUS_PREFERENCES_ICON_VIEW_THUMBNAIL_SIZE,
+ G_CALLBACK (thumbnail_size_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);
+
+ icon_theme = gtk_icon_theme_get_default ();
+ g_signal_connect_object (icon_theme,
+ "changed",
+ G_CALLBACK (icon_theme_changed_callback),
+ NULL, 0);
+
+ g_signal_connect (nautilus_signaller_get_current (),
+ "mime-data-changed",
+ G_CALLBACK (mime_type_data_changed_callback),
+ NULL);
+}
+
+static void
+nautilus_file_add_emblem (NautilusFile *file,
+ const char *emblem_name)
+{
+ 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 void
+nautilus_file_add_string_attribute (NautilusFile *file,
+ const char *attribute_name,
+ const char *value)
+{
+ if (file->details->pending_info_providers) {
+ /* Lazily create hashtable */
+ if (!file->details->pending_extension_attributes) {
+ 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) {
+ 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
+nautilus_file_invalidate_extension_info (NautilusFile *file)
+{
+ nautilus_file_invalidate_attributes (file, NAUTILUS_FILE_ATTRIBUTE_EXTENSION_INFO);
+}
+
+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)
+{
+ return NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (drop_target_item))->drag_can_accept_files (drop_target_item);
+}
+
+static gboolean
+real_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;
+ }
+
+ /* Launchers are an acceptable drop target */
+ if (nautilus_file_is_launcher (drop_target_item)) {
+ return TRUE;
+ }
+
+ 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 void
+nautilus_file_info_iface_init (NautilusFileInfoIface *iface)
+{
+ iface->is_gone = nautilus_file_is_gone;
+ iface->get_name = nautilus_file_get_name;
+ iface->get_file_type = nautilus_file_get_file_type;
+ iface->get_location = nautilus_file_get_location;
+ iface->get_uri = nautilus_file_get_uri;
+ iface->get_parent_location = nautilus_file_get_parent_location;
+ iface->get_parent_uri = nautilus_file_get_parent_uri;
+ iface->get_parent_info = nautilus_file_get_parent;
+ iface->get_mount = nautilus_file_get_mount;
+ iface->get_uri_scheme = nautilus_file_get_uri_scheme;
+ iface->get_activation_uri = nautilus_file_get_activation_uri;
+ iface->get_mime_type = nautilus_file_get_mime_type;
+ iface->is_mime_type = nautilus_file_is_mime_type;
+ iface->is_directory = nautilus_file_is_directory;
+ iface->can_write = nautilus_file_can_write;
+ iface->add_emblem = nautilus_file_add_emblem;
+ iface->get_string_attribute = nautilus_file_get_string_attribute;
+ iface->add_string_attribute = nautilus_file_add_string_attribute;
+ iface->invalidate_extension_info = nautilus_file_invalidate_extension_info;
+}
+
+#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);
+
+ /* ALEX: I removed this, because it was breaking distchecks.
+ * It used to work, but when canonical uris changed from
+ * foo: to foo:/// it broke. I don't expect it to matter
+ * in real life */
+ file_1 = nautilus_file_get_by_uri (":");
+ EEL_CHECK_STRING_RESULT (nautilus_file_get_name (file_1), ":");
+ nautilus_file_unref (file_1);
+
+ file_1 = nautilus_file_get_by_uri ("eazel:");
+ EEL_CHECK_STRING_RESULT (nautilus_file_get_name (file_1), "eazel:///");
+ 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 000000000..46085b26b
--- /dev/null
+++ b/src/nautilus-file.h
@@ -0,0 +1,626 @@
+/*
+ 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>
+*/
+
+#ifndef NAUTILUS_FILE_H
+#define NAUTILUS_FILE_H
+
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+#include "nautilus-file-attributes.h"
+#include "nautilus-icon-info.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 {
+ NAUTILUS_FILE_SORT_NONE,
+ NAUTILUS_FILE_SORT_BY_DISPLAY_NAME,
+ NAUTILUS_FILE_SORT_BY_SIZE,
+ NAUTILUS_FILE_SORT_BY_TYPE,
+ NAUTILUS_FILE_SORT_BY_MTIME,
+ NAUTILUS_FILE_SORT_BY_ATIME,
+ NAUTILUS_FILE_SORT_BY_TRASHED_TIME,
+ NAUTILUS_FILE_SORT_BY_SEARCH_RELEVANCE
+} 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);
+typedef int (*NautilusWidthMeasureCallback) (const char *string,
+ void *context);
+typedef char * (*NautilusTruncateCallback) (const char *string,
+ int width,
+ void *context);
+
+
+#define NAUTILUS_FILE_ATTRIBUTES_FOR_ICON (NAUTILUS_FILE_ATTRIBUTE_INFO | NAUTILUS_FILE_ATTRIBUTE_LINK_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);
+
+/* 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);
+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_nautilus_link (NautilusFile *file);
+gboolean nautilus_file_is_executable (NautilusFile *file);
+gboolean nautilus_file_is_directory (NautilusFile *file);
+gboolean nautilus_file_is_user_special_directory (NautilusFile *file,
+ GUserDirectory special_directory);
+gboolean nautilus_file_is_special_link (NautilusFile *file);
+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_remote (NautilusFile *file);
+gboolean nautilus_file_is_other_locations (NautilusFile *file);
+gboolean nautilus_file_is_home (NautilusFile *file);
+gboolean nautilus_file_is_desktop_directory (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_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);
+
+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_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);
+
+/* Comparing two file objects for sorting */
+NautilusFileSortType nautilus_file_get_default_sort_type (NautilusFile *file,
+ gboolean *reversed);
+const gchar * nautilus_file_get_default_sort_attribute (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_display_name (NautilusFile *file_1,
+ const char *pattern);
+int nautilus_file_compare_location (NautilusFile *file_1,
+ NautilusFile *file_2);
+
+/* 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,
+ gboolean show_foreign);
+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_is_launcher (NautilusFile *file);
+gboolean nautilus_file_is_foreign_link (NautilusFile *file);
+gboolean nautilus_file_is_trusted_link (NautilusFile *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);
+
+char * nautilus_file_get_target_uri (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);
+/* 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
+} 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);
+
+ gboolean (* can_rename) (NautilusFile *file);
+
+ void (* rename) (NautilusFile *file,
+ const char *new_name,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data);
+
+ char* (* get_target_uri) (NautilusFile *file);
+
+ gboolean (* drag_can_accept_files) (NautilusFile *drop_target_item);
+
+ void (* invalidate_attributes_internal) (NautilusFile *file,
+ NautilusFileAttributes file_attributes);
+
+ gboolean (* opens_in_view) (NautilusFile *file);
+
+ /* Use this if the custom file class doesn't support usual operations like
+ * copy, delete or move.
+ */
+ gboolean (* is_special_link) (NautilusFile *file);
+} NautilusFileClass;
+
+#endif /* NAUTILUS_FILE_H */
diff --git a/src/nautilus-files-view-dnd.c b/src/nautilus-files-view-dnd.c
index addd87243..eeba6631c 100644
--- a/src/nautilus-files-view-dnd.c
+++ b/src/nautilus-files-view-dnd.c
@@ -36,9 +36,9 @@
#include <glib/gi18n.h>
-#include <libnautilus-private/nautilus-clipboard.h>
-#include <libnautilus-private/nautilus-dnd.h>
-#include <libnautilus-private/nautilus-global-preferences.h>
+#include "nautilus-clipboard.h"
+#include "nautilus-dnd.h"
+#include "nautilus-global-preferences.h"
#define GET_ANCESTOR(obj) \
GTK_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (obj), GTK_TYPE_WINDOW))
diff --git a/src/nautilus-files-view.c b/src/nautilus-files-view.c
index f1e4bac3c..0605ac7c0 100644
--- a/src/nautilus-files-view.c
+++ b/src/nautilus-files-view.c
@@ -67,31 +67,31 @@
#include <eel/eel-vfs-extensions.h>
#include <libnautilus-extension/nautilus-menu-provider.h>
-#include <libnautilus-private/nautilus-clipboard.h>
-#include <libnautilus-private/nautilus-clipboard-monitor.h>
-#include <libnautilus-private/nautilus-search-directory.h>
-#include <libnautilus-private/nautilus-directory.h>
-#include <libnautilus-private/nautilus-dnd.h>
-#include <libnautilus-private/nautilus-file-attributes.h>
-#include <libnautilus-private/nautilus-file-changes-queue.h>
-#include <libnautilus-private/nautilus-file-operations.h>
-#include <libnautilus-private/nautilus-file-utilities.h>
-#include <libnautilus-private/nautilus-file-private.h>
-#include <libnautilus-private/nautilus-global-preferences.h>
-#include <libnautilus-private/nautilus-link.h>
-#include <libnautilus-private/nautilus-metadata.h>
-#include <libnautilus-private/nautilus-module.h>
-#include <libnautilus-private/nautilus-profile.h>
-#include <libnautilus-private/nautilus-program-choosing.h>
-#include <libnautilus-private/nautilus-trash-monitor.h>
-#include <libnautilus-private/nautilus-ui-utilities.h>
-#include <libnautilus-private/nautilus-signaller.h>
-#include <libnautilus-private/nautilus-icon-names.h>
+#include "nautilus-clipboard.h"
+#include "nautilus-clipboard-monitor.h"
+#include "nautilus-search-directory.h"
+#include "nautilus-directory.h"
+#include "nautilus-dnd.h"
+#include "nautilus-file-attributes.h"
+#include "nautilus-file-changes-queue.h"
+#include "nautilus-file-operations.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-file-private.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-link.h"
+#include "nautilus-metadata.h"
+#include "nautilus-module.h"
+#include "nautilus-profile.h"
+#include "nautilus-program-choosing.h"
+#include "nautilus-trash-monitor.h"
+#include "nautilus-ui-utilities.h"
+#include "nautilus-signaller.h"
+#include "nautilus-icon-names.h"
#include <gdesktop-enums.h>
#define DEBUG_FLAG NAUTILUS_DEBUG_DIRECTORY_VIEW
-#include <libnautilus-private/nautilus-debug.h>
+#include "nautilus-debug.h"
/* Minimum starting update inverval */
#define UPDATE_INTERVAL_MIN 100
diff --git a/src/nautilus-files-view.h b/src/nautilus-files-view.h
index efb4dea7f..9b5a28b11 100644
--- a/src/nautilus-files-view.h
+++ b/src/nautilus-files-view.h
@@ -28,9 +28,9 @@
#include <gtk/gtk.h>
#include <gio/gio.h>
-#include <libnautilus-private/nautilus-directory.h>
-#include <libnautilus-private/nautilus-file.h>
-#include <libnautilus-private/nautilus-link.h>
+#include "nautilus-directory.h"
+#include "nautilus-file.h"
+#include "nautilus-link.h"
typedef struct NautilusFilesView NautilusFilesView;
typedef struct NautilusFilesViewClass NautilusFilesViewClass;
diff --git a/src/nautilus-freedesktop-dbus.c b/src/nautilus-freedesktop-dbus.c
index 5b72fcb9b..7d03bc086 100644
--- a/src/nautilus-freedesktop-dbus.c
+++ b/src/nautilus-freedesktop-dbus.c
@@ -26,7 +26,7 @@
/* We share the same debug domain as nautilus-dbus-manager */
#define DEBUG_FLAG NAUTILUS_DEBUG_DBUS
-#include <libnautilus-private/nautilus-debug.h>
+#include "nautilus-debug.h"
#include "nautilus-properties-window.h"
diff --git a/src/nautilus-global-preferences.c b/src/nautilus-global-preferences.c
new file mode 100644
index 000000000..19b475f3b
--- /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_icon_view_preferences;
+GSettings *nautilus_list_view_preferences;
+GSettings *nautilus_desktop_preferences;
+GSettings *nautilus_window_state;
+GSettings *gtk_filechooser_preferences;
+GSettings *gnome_lockdown_preferences;
+GSettings *gnome_background_preferences;
+GSettings *gnome_interface_preferences;
+GSettings *gnome_privacy_preferences;
+
+void
+nautilus_global_preferences_init (void)
+{
+ static gboolean initialized = FALSE;
+
+ if (initialized) {
+ return;
+ }
+
+ initialized = TRUE;
+
+ nautilus_preferences = g_settings_new("org.gnome.nautilus.preferences");
+ nautilus_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");
+ nautilus_desktop_preferences = g_settings_new("org.gnome.nautilus.desktop");
+ /* 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");
+ gnome_privacy_preferences = g_settings_new ("org.gnome.desktop.privacy");
+}
diff --git a/src/nautilus-global-preferences.h b/src/nautilus-global-preferences.h
new file mode 100644
index 000000000..ef1f8aabe
--- /dev/null
+++ b/src/nautilus-global-preferences.h
@@ -0,0 +1,179 @@
+
+/* 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>
+*/
+
+#ifndef NAUTILUS_GLOBAL_PREFERENCES_H
+#define NAUTILUS_GLOBAL_PREFERENCES_H
+
+#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_GEOMETRY "geometry"
+#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"
+
+/* Icon View */
+#define NAUTILUS_PREFERENCES_ICON_VIEW_DEFAULT_ZOOM_LEVEL "default-zoom-level"
+
+/* Which text attributes appear beneath icon names */
+#define NAUTILUS_PREFERENCES_ICON_VIEW_CAPTIONS "captions"
+
+/* The default size for thumbnail icons */
+#define NAUTILUS_PREFERENCES_ICON_VIEW_THUMBNAIL_SIZE "thumbnail-size"
+
+/* ellipsization preferences */
+#define NAUTILUS_PREFERENCES_ICON_VIEW_TEXT_ELLIPSIS_LIMIT "text-ellipsis-limit"
+#define NAUTILUS_PREFERENCES_DESKTOP_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;
+
+#define NAUTILUS_PREFERENCES_DESKTOP_FONT "font"
+#define NAUTILUS_PREFERENCES_DESKTOP_HOME_VISIBLE "home-icon-visible"
+#define NAUTILUS_PREFERENCES_DESKTOP_HOME_NAME "home-icon-name"
+#define NAUTILUS_PREFERENCES_DESKTOP_TRASH_VISIBLE "trash-icon-visible"
+#define NAUTILUS_PREFERENCES_DESKTOP_TRASH_NAME "trash-icon-name"
+#define NAUTILUS_PREFERENCES_DESKTOP_VOLUMES_VISIBLE "volumes-visible"
+#define NAUTILUS_PREFERENCES_DESKTOP_NETWORK_VISIBLE "network-icon-visible"
+#define NAUTILUS_PREFERENCES_DESKTOP_NETWORK_NAME "network-icon-name"
+#define NAUTILUS_PREFERENCES_DESKTOP_BACKGROUND_FADE "background-fade"
+
+/* bulk rename utility */
+#define NAUTILUS_PREFERENCES_BULK_RENAME_TOOL "bulk-rename-tool"
+
+/* Lockdown */
+#define NAUTILUS_PREFERENCES_LOCKDOWN_COMMAND_LINE "disable-command-line"
+
+/* Desktop background */
+#define NAUTILUS_PREFERENCES_SHOW_DESKTOP "show-desktop-icons"
+
+/* Recent files */
+#define NAUTILUS_PREFERENCES_RECENT_FILES_ENABLED "remember-recent-files"
+
+/* Move to trash shorcut changed dialog */
+#define NAUTILUS_PREFERENCES_SHOW_MOVE_TO_TRASH_SHORTCUT_CHANGED_DIALOG "show-move-to-trash-shortcut-changed-dialog"
+
+/* 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"
+
+void nautilus_global_preferences_init (void);
+
+extern GSettings *nautilus_preferences;
+extern GSettings *nautilus_icon_view_preferences;
+extern GSettings *nautilus_list_view_preferences;
+extern GSettings *nautilus_desktop_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 *gnome_privacy_preferences;
+
+G_END_DECLS
+
+#endif /* NAUTILUS_GLOBAL_PREFERENCES_H */
diff --git a/src/nautilus-icon-info.c b/src/nautilus-icon-info.c
new file mode 100644
index 000000000..9c4bc2ad2
--- /dev/null
+++ b/src/nautilus-icon-info.c
@@ -0,0 +1,579 @@
+/* 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 <config.h>
+#include <string.h>
+#include "nautilus-icon-info.h"
+#include "nautilus-icon-names.h"
+#include "nautilus-default-file-icon.h"
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+
+struct _NautilusIconInfo
+{
+ GObject parent;
+
+ gboolean sole_owner;
+ gint64 last_use_time;
+ GdkPixbuf *pixbuf;
+
+ char *icon_name;
+
+ gint orig_scale;
+};
+
+struct _NautilusIconInfoClass
+{
+ GObjectClass parent_class;
+};
+
+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 size;
+} LoadableIconKey;
+
+typedef struct {
+ char *filename;
+ 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->size;
+}
+
+static gboolean
+loadable_icon_key_equal (const LoadableIconKey *a,
+ const LoadableIconKey *b)
+{
+ return a->size == b->size &&
+ g_icon_equal (a->icon, b->icon);
+}
+
+static LoadableIconKey *
+loadable_icon_key_new (GIcon *icon, int size)
+{
+ LoadableIconKey *key;
+
+ key = g_slice_new (LoadableIconKey);
+ key->icon = g_object_ref (icon);
+ 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 &&
+ g_str_equal (a->filename, b->filename);
+}
+
+static ThemedIconKey *
+themed_icon_key_new (const char *filename, int size)
+{
+ ThemedIconKey *key;
+
+ key = g_slice_new (ThemedIconKey);
+ key->filename = g_strdup (filename);
+ 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;
+ GdkPixbuf *pixbuf;
+
+ if (G_IS_LOADABLE_ICON (icon)) {
+ 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.size = size;
+
+ 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, 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.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, 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_data (nautilus_default_file_icon,
+ GDK_COLORSPACE_RGB,
+ TRUE,
+ 8,
+ nautilus_default_file_icon_width,
+ nautilus_default_file_icon_height,
+ nautilus_default_file_icon_width * 4, /* stride */
+ NULL, /* don't destroy info */
+ 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;
+ scaled_pixbuf = gdk_pixbuf_scale_simple (pixbuf,
+ w * scale, h * scale,
+ 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 000000000..03998d4b9
--- /dev/null
+++ b/src/nautilus-icon-info.h
@@ -0,0 +1,94 @@
+#ifndef NAUTILUS_ICON_INFO_H
+#define NAUTILUS_ICON_INFO_H
+
+#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
+
+/* Names for Nautilus's different zoom levels, from tiniest items to largest items */
+typedef enum {
+ NAUTILUS_CANVAS_ZOOM_LEVEL_SMALL,
+ NAUTILUS_CANVAS_ZOOM_LEVEL_STANDARD,
+ NAUTILUS_CANVAS_ZOOM_LEVEL_LARGE,
+ NAUTILUS_CANVAS_ZOOM_LEVEL_LARGER,
+} NautilusCanvasZoomLevel;
+
+typedef enum {
+ NAUTILUS_LIST_ZOOM_LEVEL_SMALL,
+ NAUTILUS_LIST_ZOOM_LEVEL_STANDARD,
+ NAUTILUS_LIST_ZOOM_LEVEL_LARGE,
+ NAUTILUS_LIST_ZOOM_LEVEL_LARGER,
+} NautilusListZoomLevel;
+
+#define NAUTILUS_LIST_ZOOM_LEVEL_N_ENTRIES (NAUTILUS_LIST_ZOOM_LEVEL_LARGER + 1)
+#define NAUTILUS_CANVAS_ZOOM_LEVEL_N_ENTRIES (NAUTILUS_CANVAS_ZOOM_LEVEL_LARGER + 1)
+
+/* Nominal icon sizes for each Nautilus zoom level.
+ * This scheme assumes that icons are designed to
+ * fit in a square space, though each image needn't
+ * be square. Since individual icons can be stretched,
+ * each icon is not constrained to this nominal size.
+ */
+#define NAUTILUS_LIST_ICON_SIZE_SMALL 16
+#define NAUTILUS_LIST_ICON_SIZE_STANDARD 32
+#define NAUTILUS_LIST_ICON_SIZE_LARGE 48
+#define NAUTILUS_LIST_ICON_SIZE_LARGER 64
+
+#define NAUTILUS_CANVAS_ICON_SIZE_SMALL 48
+#define NAUTILUS_CANVAS_ICON_SIZE_STANDARD 64
+#define NAUTILUS_CANVAS_ICON_SIZE_LARGE 96
+#define NAUTILUS_CANVAS_ICON_SIZE_LARGER 128
+
+/* Maximum size of an icon that the icon factory will ever produce */
+#define NAUTILUS_ICON_MAXIMUM_SIZE 320
+
+typedef struct _NautilusIconInfo NautilusIconInfo;
+typedef struct _NautilusIconInfoClass NautilusIconInfoClass;
+
+
+#define NAUTILUS_TYPE_ICON_INFO (nautilus_icon_info_get_type ())
+#define NAUTILUS_ICON_INFO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_ICON_INFO, NautilusIconInfo))
+#define NAUTILUS_ICON_INFO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_ICON_INFO, NautilusIconInfoClass))
+#define NAUTILUS_IS_ICON_INFO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_ICON_INFO))
+#define NAUTILUS_IS_ICON_INFO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_ICON_INFO))
+#define NAUTILUS_ICON_INFO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_ICON_INFO, NautilusIconInfoClass))
+
+
+GType nautilus_icon_info_get_type (void) G_GNUC_CONST;
+
+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);
+
+/* Relationship between zoom levels and icons sizes. */
+guint nautilus_get_list_icon_size_for_zoom_level (NautilusListZoomLevel zoom_level);
+guint nautilus_get_canvas_icon_size_for_zoom_level (NautilusCanvasZoomLevel zoom_level);
+
+gint nautilus_get_icon_size_for_stock_size (GtkIconSize size);
+
+G_END_DECLS
+
+#endif /* NAUTILUS_ICON_INFO_H */
+
diff --git a/src/nautilus-icon-names.h b/src/nautilus-icon-names.h
new file mode 100644
index 000000000..ddc4543d5
--- /dev/null
+++ b/src/nautilus-icon-names.h
@@ -0,0 +1,51 @@
+#ifndef NAUTILUS_ICON_NAMES_H
+#define NAUTILUS_ICON_NAMES_H
+
+/* 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_NETWORK "network-workgroup-symbolic"
+#define NAUTILUS_ICON_NETWORK_SERVER "network-server-symbolic"
+#define NAUTILUS_ICON_SEARCH "system-search"
+#define NAUTILUS_ICON_TRASH "user-trash-symbolic"
+#define NAUTILUS_ICON_TRASH_FULL "user-trash-full-symbolic"
+#define NAUTILUS_ICON_DELETE "edit-delete-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"
+#define NAUTILUS_ICON_FOLDER_SAVED_SEARCH "folder-saved-search-symbolic"
+
+/* Icons for desktop */
+#define NAUTILUS_DESKTOP_ICON_DESKTOP "user-desktop"
+#define NAUTILUS_DESKTOP_ICON_TRASH "user-trash"
+#define NAUTILUS_DESKTOP_ICON_TRASH_FULL "user-trash-full"
+#define NAUTILUS_DESKTOP_ICON_HOME "user-home"
+#define NAUTILUS_DESKTOP_ICON_NETWORK "network-workgroup"
+
+/* 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"
+#define NAUTILUS_ICON_FULLCOLOR_FOLDER_SAVED_SEARCH "folder-saved-search"
+
+/* Other icons */
+#define NAUTILUS_ICON_TEMPLATE "text-x-generic-template"
+
+/* Icons not provided by fd.o naming spec or nautilus itself */
+#define NAUTILUS_ICON_BURN "nautilus-cd-burner"
+
+#endif /* NAUTILUS_ICON_NAMES_H */
+
diff --git a/src/nautilus-image-properties-page.c b/src/nautilus-image-properties-page.c
index bac8b7cc9..3cf3253c5 100644
--- a/src/nautilus-image-properties-page.c
+++ b/src/nautilus-image-properties-page.c
@@ -28,7 +28,7 @@
#include <gio/gio.h>
#include <eel/eel-vfs-extensions.h>
#include <libnautilus-extension/nautilus-property-page-provider.h>
-#include <libnautilus-private/nautilus-module.h>
+#include "nautilus-module.h"
#include <string.h>
#ifdef HAVE_EXIF
diff --git a/src/nautilus-keyfile-metadata.c b/src/nautilus-keyfile-metadata.c
new file mode 100644
index 000000000..e74711a40
--- /dev/null
+++ b/src/nautilus-keyfile-metadata.c
@@ -0,0 +1,316 @@
+/*
+ * 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 000000000..f9dede523
--- /dev/null
+++ b/src/nautilus-keyfile-metadata.h
@@ -0,0 +1,46 @@
+/*
+ * 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>
+ */
+
+#ifndef __NAUTILUS_KEYFILE_METADATA_H__
+#define __NAUTILUS_KEYFILE_METADATA_H__
+
+#include <glib.h>
+
+#include "nautilus-file.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);
+
+#endif /* __NAUTILUS_KEYFILE_METADATA_H__ */
diff --git a/src/nautilus-lib-self-check-functions.c b/src/nautilus-lib-self-check-functions.c
new file mode 100644
index 000000000..5793fca0a
--- /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 000000000..c326c60fd
--- /dev/null
+++ b/src/nautilus-lib-self-check-functions.h
@@ -0,0 +1,47 @@
+/*
+ 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>
+*/
+
+#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) \
+ macro (nautilus_self_check_canvas_container) \
+/* 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-link.c b/src/nautilus-link.c
new file mode 100644
index 000000000..b02aa9237
--- /dev/null
+++ b/src/nautilus-link.c
@@ -0,0 +1,597 @@
+/*
+ nautilus-link.c: .desktop link files.
+
+ Copyright (C) 2001 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 historicalied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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: Jonathan Blandford <jrb@redhat.com>
+ Alexander Larsson <alexl@redhat.com>
+*/
+
+#include <config.h>
+#include "nautilus-link.h"
+
+#include "nautilus-directory-notify.h"
+#include "nautilus-directory.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-file.h"
+#include "nautilus-program-choosing.h"
+#include "nautilus-icon-names.h"
+#include <eel/eel-vfs-extensions.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define MAIN_GROUP "Desktop Entry"
+
+#define NAUTILUS_LINK_GENERIC_TAG "Link"
+#define NAUTILUS_LINK_TRASH_TAG "X-nautilus-trash"
+#define NAUTILUS_LINK_MOUNT_TAG "FSDevice"
+#define NAUTILUS_LINK_HOME_TAG "X-nautilus-home"
+
+static gboolean
+is_link_mime_type (const char *mime_type)
+{
+ if (mime_type != NULL &&
+ g_ascii_strcasecmp (mime_type, "application/x-desktop") == 0) {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+is_local_file_a_link (const char *uri)
+{
+ gboolean link;
+ GFile *file;
+ GFileInfo *info;
+ GError *error;
+
+ error = NULL;
+ link = FALSE;
+
+ file = g_file_new_for_uri (uri);
+
+ info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
+ 0, NULL, &error);
+ if (info) {
+ link = is_link_mime_type (g_file_info_get_content_type (info));
+ g_object_unref (info);
+ }
+ else {
+ g_warning ("Error getting info: %s\n", error->message);
+ g_error_free (error);
+ }
+
+ g_object_unref (file);
+
+ return link;
+}
+
+static gboolean
+_g_key_file_load_from_gfile (GKeyFile *key_file,
+ GFile *file,
+ GKeyFileFlags flags,
+ GError **error)
+{
+ char *data;
+ gsize len;
+ gboolean res;
+
+ if (!g_file_load_contents (file, NULL, &data, &len, NULL, error)) {
+ return FALSE;
+ }
+
+ res = g_key_file_load_from_data (key_file, data, len, flags, error);
+
+ g_free (data);
+
+ return res;
+}
+
+static gboolean
+_g_key_file_save_to_gfile (GKeyFile *key_file,
+ GFile *file,
+ GError **error)
+{
+ char *data;
+ gsize len;
+
+ data = g_key_file_to_data (key_file, &len, error);
+ if (data == NULL) {
+ return FALSE;
+ }
+
+ if (!g_file_replace_contents (file,
+ data, len,
+ NULL, FALSE,
+ G_FILE_CREATE_NONE,
+ NULL, NULL, error)) {
+ g_free (data);
+ return FALSE;
+ }
+ g_free (data);
+ return TRUE;
+}
+
+
+
+static GKeyFile *
+_g_key_file_new_from_uri (const char *uri,
+ GKeyFileFlags flags,
+ GError **error)
+{
+ GKeyFile *key_file;
+ GFile *file;
+
+ file = g_file_new_for_uri (uri);
+ key_file = g_key_file_new ();
+ if (!_g_key_file_load_from_gfile (key_file, file, flags, error)) {
+ g_key_file_free (key_file);
+ key_file = NULL;
+ }
+ g_object_unref (file);
+ return key_file;
+}
+
+static char *
+slurp_key_string (const char *uri,
+ const char *keyname,
+ gboolean localize)
+{
+ GKeyFile *key_file;
+ char *result;
+
+ key_file = _g_key_file_new_from_uri (uri, G_KEY_FILE_NONE, NULL);
+ if (key_file == NULL) {
+ return NULL;
+ }
+
+ if (localize) {
+ result = g_key_file_get_locale_string (key_file, MAIN_GROUP, keyname, NULL, NULL);
+ } else {
+ result = g_key_file_get_string (key_file, MAIN_GROUP, keyname, NULL);
+ }
+ g_key_file_free (key_file);
+
+ return result;
+}
+
+gboolean
+nautilus_link_local_create (const char *directory_uri,
+ const char *base_name,
+ const char *display_name,
+ const char *image,
+ const char *target_uri,
+ const GdkPoint *point,
+ int screen,
+ gboolean unique_filename)
+{
+ char *real_directory_uri;
+ char *uri, *contents;
+ GFile *file;
+ GList dummy_list;
+ NautilusFileChangesQueuePosition item;
+
+ g_return_val_if_fail (directory_uri != NULL, FALSE);
+ g_return_val_if_fail (base_name != NULL, FALSE);
+ g_return_val_if_fail (display_name != NULL, FALSE);
+ g_return_val_if_fail (target_uri != NULL, FALSE);
+
+ if (eel_uri_is_trash (directory_uri) ||
+ eel_uri_is_search (directory_uri)) {
+ return FALSE;
+ }
+
+ if (eel_uri_is_desktop (directory_uri)) {
+ real_directory_uri = nautilus_get_desktop_directory_uri ();
+ } else {
+ real_directory_uri = g_strdup (directory_uri);
+ }
+
+ if (unique_filename) {
+ uri = nautilus_ensure_unique_file_name (real_directory_uri,
+ base_name, ".desktop");
+ if (uri == NULL) {
+ g_free (real_directory_uri);
+ return FALSE;
+ }
+ file = g_file_new_for_uri (uri);
+ g_free (uri);
+ } else {
+ char *link_name;
+ GFile *dir;
+
+ link_name = g_strdup_printf ("%s.desktop", base_name);
+
+ /* replace '/' with '-', just in case */
+ g_strdelimit (link_name, "/", '-');
+
+ dir = g_file_new_for_uri (directory_uri);
+ file = g_file_get_child (dir, link_name);
+
+ g_free (link_name);
+ g_object_unref (dir);
+ }
+
+ g_free (real_directory_uri);
+
+ contents = g_strdup_printf ("[Desktop Entry]\n"
+ "Encoding=UTF-8\n"
+ "Name=%s\n"
+ "Type=Link\n"
+ "URL=%s\n"
+ "%s%s\n",
+ display_name,
+ target_uri,
+ image != NULL ? "Icon=" : "",
+ image != NULL ? image : "");
+
+
+ if (!g_file_replace_contents (file,
+ contents, strlen (contents),
+ NULL, FALSE,
+ G_FILE_CREATE_NONE,
+ NULL, NULL, NULL)) {
+ g_free (contents);
+ g_object_unref (file);
+ return FALSE;
+ }
+ g_free (contents);
+
+ dummy_list.data = file;
+ dummy_list.next = NULL;
+ dummy_list.prev = NULL;
+ nautilus_directory_notify_files_added (&dummy_list);
+
+ if (point != NULL) {
+ item.location = file;
+ item.set = TRUE;
+ item.point.x = point->x;
+ item.point.y = point->y;
+ item.screen = screen;
+ dummy_list.data = &item;
+ dummy_list.next = NULL;
+ dummy_list.prev = NULL;
+
+ nautilus_directory_schedule_position_set (&dummy_list);
+ }
+
+ g_object_unref (file);
+ return TRUE;
+}
+
+static const char *
+get_language (void)
+{
+ const char * const *langs_pointer;
+ int i;
+
+ langs_pointer = g_get_language_names ();
+ for (i = 0; langs_pointer[i] != NULL; i++) {
+ /* find first without encoding */
+ if (strchr (langs_pointer[i], '.') == NULL) {
+ return langs_pointer[i];
+ }
+ }
+ return NULL;
+}
+
+static gboolean
+nautilus_link_local_set_key (const char *uri,
+ const char *key,
+ const char *value,
+ gboolean localize)
+{
+ gboolean success;
+ GKeyFile *key_file;
+ GFile *file;
+
+ file = g_file_new_for_uri (uri);
+ key_file = g_key_file_new ();
+ if (!_g_key_file_load_from_gfile (key_file, file, G_KEY_FILE_KEEP_COMMENTS, NULL)) {
+ g_key_file_free (key_file);
+ g_object_unref (file);
+ return FALSE;
+ }
+ if (localize) {
+ g_key_file_set_locale_string (key_file,
+ MAIN_GROUP,
+ key,
+ get_language (),
+ value);
+ } else {
+ g_key_file_set_string (key_file, MAIN_GROUP, key, value);
+ }
+
+
+ success = _g_key_file_save_to_gfile (key_file, file, NULL);
+ g_key_file_free (key_file);
+ g_object_unref (file);
+ return success;
+}
+
+gboolean
+nautilus_link_local_set_text (const char *uri,
+ const char *text)
+{
+ return nautilus_link_local_set_key (uri, "Name", text, TRUE);
+}
+
+
+gboolean
+nautilus_link_local_set_icon (const char *uri,
+ const char *icon)
+{
+ return nautilus_link_local_set_key (uri, "Icon", icon, FALSE);
+}
+
+char *
+nautilus_link_local_get_text (const char *path)
+{
+ return slurp_key_string (path, "Name", TRUE);
+}
+
+static char *
+nautilus_link_get_link_uri_from_desktop (GKeyFile *key_file, const char *desktop_file_uri)
+{
+ GFile *file, *parent;
+ char *type;
+ char *retval;
+ char *scheme;
+
+ retval = NULL;
+
+ type = g_key_file_get_string (key_file, MAIN_GROUP, "Type", NULL);
+ if (type == NULL) {
+ return NULL;
+ }
+
+ if (strcmp (type, "URL") == 0) {
+ /* Some old broken desktop files use this nonstandard feature, we need handle it though */
+ retval = g_key_file_get_string (key_file, MAIN_GROUP, "Exec", NULL);
+ } else if ((strcmp (type, NAUTILUS_LINK_GENERIC_TAG) == 0) ||
+ (strcmp (type, NAUTILUS_LINK_MOUNT_TAG) == 0) ||
+ (strcmp (type, NAUTILUS_LINK_TRASH_TAG) == 0) ||
+ (strcmp (type, NAUTILUS_LINK_HOME_TAG) == 0)) {
+ retval = g_key_file_get_string (key_file, MAIN_GROUP, "URL", NULL);
+ }
+ g_free (type);
+
+ if (retval != NULL && desktop_file_uri != NULL) {
+ /* Handle local file names.
+ * Ideally, we'd be able to use
+ * g_file_parse_name(), but it does not know how to resolve
+ * relative file names, since the base directory is unknown.
+ */
+ scheme = g_uri_parse_scheme (retval);
+ if (scheme == NULL) {
+ file = g_file_new_for_uri (desktop_file_uri);
+ parent = g_file_get_parent (file);
+ g_object_unref (file);
+
+ if (parent != NULL) {
+ file = g_file_resolve_relative_path (parent, retval);
+ g_free (retval);
+ retval = g_file_get_uri (file);
+ g_object_unref (file);
+ g_object_unref (parent);
+ }
+ }
+ g_free (scheme);
+ }
+
+ return retval;
+}
+
+static char *
+nautilus_link_get_link_name_from_desktop (GKeyFile *key_file)
+{
+ return g_key_file_get_locale_string (key_file, MAIN_GROUP, "Name", NULL, NULL);
+}
+
+static GIcon *
+nautilus_link_get_link_icon_from_desktop (GKeyFile *key_file)
+{
+ char *icon_str, *p, *type = NULL;
+ GFile *file;
+ GIcon *icon;
+
+ /* Look at the Icon: key */
+ icon_str = g_key_file_get_string (key_file, MAIN_GROUP, "Icon", NULL);
+
+ /* if it's an absolute path, return a GFileIcon for that path */
+ if (icon_str != NULL && g_path_is_absolute (icon_str)) {
+ file = g_file_new_for_path (icon_str);
+ icon = g_file_icon_new (file);
+
+ g_object_unref (file);
+
+ goto out;
+ }
+
+ type = g_key_file_get_string (key_file, MAIN_GROUP, "Type", NULL);
+
+ if (icon_str == NULL) {
+ if (g_strcmp0 (type, "Application") == 0) {
+ icon_str = g_strdup ("application-x-executable");
+ } else if (g_strcmp0 (type, "FSDevice") == 0) {
+ icon_str = g_strdup ("drive-harddisk");
+ } else if (g_strcmp0 (type, "Directory") == 0) {
+ icon_str = g_strdup (NAUTILUS_ICON_FOLDER);
+ } else if (g_strcmp0 (type, "Service") == 0 ||
+ g_strcmp0 (type, "ServiceType") == 0) {
+ icon_str = g_strdup ("folder-remote");
+ } else {
+ icon_str = g_strdup ("text-x-preview");
+ }
+ } else {
+ /* Strip out any extension on non-filename icons. Old desktop files may have this */
+ p = strchr (icon_str, '.');
+ /* Only strip known icon extensions */
+ if ((p != NULL) &&
+ ((g_ascii_strcasecmp (p, ".png") == 0)
+ || (g_ascii_strcasecmp (p, ".svn") == 0)
+ || (g_ascii_strcasecmp (p, ".jpg") == 0)
+ || (g_ascii_strcasecmp (p, ".xpm") == 0)
+ || (g_ascii_strcasecmp (p, ".bmp") == 0)
+ || (g_ascii_strcasecmp (p, ".jpeg") == 0))) {
+ *p = 0;
+ }
+ }
+
+ icon = g_themed_icon_new_with_default_fallbacks (icon_str);
+
+ /* apply a link emblem if it's a link */
+ if (g_strcmp0 (type, "Link") == 0) {
+ GIcon *emblemed, *emblem_icon;
+ GEmblem *emblem;
+
+ emblem_icon = g_themed_icon_new ("emblem-symbolic-link");
+ emblem = g_emblem_new (emblem_icon);
+
+ emblemed = g_emblemed_icon_new (icon, emblem);
+
+ g_object_unref (icon);
+ g_object_unref (emblem_icon);
+ g_object_unref (emblem);
+
+ icon = emblemed;
+ }
+
+ out:
+ g_free (icon_str);
+ g_free (type);
+
+ return icon;
+}
+
+char *
+nautilus_link_local_get_link_uri (const char *uri)
+{
+ GKeyFile *key_file;
+ char *retval;
+
+ if (!is_local_file_a_link (uri)) {
+ return NULL;
+ }
+
+ key_file = _g_key_file_new_from_uri (uri, G_KEY_FILE_NONE, NULL);
+ if (key_file == NULL) {
+ return NULL;
+ }
+
+ retval = nautilus_link_get_link_uri_from_desktop (key_file, uri);
+ g_key_file_free (key_file);
+
+ return retval;
+}
+
+static gboolean
+string_array_contains (gchar **array,
+ gchar **desktop_names)
+{
+ gchar **p;
+ gchar **desktop;
+
+ if (!array)
+ return FALSE;
+
+ for (p = array; *p; p++) {
+ for (desktop = desktop_names; *desktop; desktop++) {
+ if (g_ascii_strcasecmp (*p, *desktop) == 0)
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static gchar **
+get_desktop_names (void)
+{
+ const gchar *current_desktop;
+
+ current_desktop = g_getenv ("XDG_CURRENT_DESKTOP");
+
+ if (current_desktop == NULL || current_desktop[0] == 0) {
+ /* historic behavior */
+ current_desktop = "GNOME";
+ }
+
+ return g_strsplit (current_desktop, ":", -1);
+}
+
+void
+nautilus_link_get_link_info_given_file_contents (const char *file_contents,
+ int link_file_size,
+ const char *file_uri,
+ char **uri,
+ char **name,
+ GIcon **icon,
+ gboolean *is_launcher,
+ gboolean *is_foreign)
+{
+ GKeyFile *key_file;
+ gchar **desktop_names;
+ char *type;
+ char **only_show_in;
+ char **not_show_in;
+
+ key_file = g_key_file_new ();
+ if (!g_key_file_load_from_data (key_file,
+ file_contents,
+ link_file_size,
+ G_KEY_FILE_NONE,
+ NULL)) {
+ g_key_file_free (key_file);
+ return;
+ }
+
+ desktop_names = get_desktop_names ();
+
+ *uri = nautilus_link_get_link_uri_from_desktop (key_file, file_uri);
+ *name = nautilus_link_get_link_name_from_desktop (key_file);
+ *icon = nautilus_link_get_link_icon_from_desktop (key_file);
+
+ *is_launcher = FALSE;
+ type = g_key_file_get_string (key_file, MAIN_GROUP, "Type", NULL);
+ if (g_strcmp0 (type, "Application") == 0 &&
+ g_key_file_has_key (key_file, MAIN_GROUP, "Exec", NULL)) {
+ *is_launcher = TRUE;
+ }
+ g_free (type);
+
+ *is_foreign = FALSE;
+ only_show_in = g_key_file_get_string_list (key_file, MAIN_GROUP,
+ "OnlyShowIn", NULL, NULL);
+ if (only_show_in && !string_array_contains (only_show_in, desktop_names)) {
+ *is_foreign = TRUE;
+ }
+ g_strfreev (only_show_in);
+
+ not_show_in = g_key_file_get_string_list (key_file, MAIN_GROUP,
+ "NotShowIn", NULL, NULL);
+ if (not_show_in && string_array_contains (not_show_in, desktop_names)) {
+ *is_foreign = TRUE;
+ }
+ g_strfreev (not_show_in);
+
+ g_strfreev (desktop_names);
+ g_key_file_free (key_file);
+}
diff --git a/src/nautilus-link.h b/src/nautilus-link.h
new file mode 100644
index 000000000..72d9c1743
--- /dev/null
+++ b/src/nautilus-link.h
@@ -0,0 +1,50 @@
+/*
+ nautilus-link.h: .
+
+ Copyright (C) 2001 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: Jonathan Blandford <jrb@redhat.com>
+*/
+
+#ifndef NAUTILUS_LINK_H
+#define NAUTILUS_LINK_H
+
+#include <gdk/gdk.h>
+
+gboolean nautilus_link_local_create (const char *directory_uri,
+ const char *base_name,
+ const char *display_name,
+ const char *image,
+ const char *target_uri,
+ const GdkPoint *point,
+ int screen,
+ gboolean unique_filename);
+gboolean nautilus_link_local_set_text (const char *uri,
+ const char *text);
+gboolean nautilus_link_local_set_icon (const char *uri,
+ const char *icon);
+char * nautilus_link_local_get_text (const char *uri);
+char * nautilus_link_local_get_link_uri (const char *uri);
+void nautilus_link_get_link_info_given_file_contents (const char *file_contents,
+ int link_file_size,
+ const char *file_uri,
+ char **uri,
+ char **name,
+ GIcon **icon,
+ gboolean *is_launcher,
+ gboolean *is_foreign);
+
+#endif /* NAUTILUS_LINK_H */
diff --git a/src/nautilus-list-model.c b/src/nautilus-list-model.c
index 6fc715bec..fe7fea2cf 100644
--- a/src/nautilus-list-model.c
+++ b/src/nautilus-list-model.c
@@ -33,7 +33,7 @@
#include <cairo-gobject.h>
#include <eel/eel-graphic-effects.h>
-#include <libnautilus-private/nautilus-dnd.h>
+#include "nautilus-dnd.h"
enum {
SUBDIRECTORY_UNLOADED,
diff --git a/src/nautilus-list-model.h b/src/nautilus-list-model.h
index 2f3fe19ea..88f486d62 100644
--- a/src/nautilus-list-model.h
+++ b/src/nautilus-list-model.h
@@ -22,8 +22,8 @@
#include <gtk/gtk.h>
#include <gdk/gdk.h>
-#include <libnautilus-private/nautilus-file.h>
-#include <libnautilus-private/nautilus-directory.h>
+#include "nautilus-file.h"
+#include "nautilus-directory.h"
#include <libnautilus-extension/nautilus-column.h>
#ifndef NAUTILUS_LIST_MODEL_H
diff --git a/src/nautilus-list-view.c b/src/nautilus-list-view.c
index 208ae8133..f23f4ce52 100644
--- a/src/nautilus-list-view.c
+++ b/src/nautilus-list-view.c
@@ -44,20 +44,20 @@
#include <glib-object.h>
#include <libgd/gd.h>
#include <libnautilus-extension/nautilus-column-provider.h>
-#include <libnautilus-private/nautilus-clipboard-monitor.h>
-#include <libnautilus-private/nautilus-column-chooser.h>
-#include <libnautilus-private/nautilus-column-utilities.h>
-#include <libnautilus-private/nautilus-dnd.h>
-#include <libnautilus-private/nautilus-file-utilities.h>
-#include <libnautilus-private/nautilus-ui-utilities.h>
-#include <libnautilus-private/nautilus-global-preferences.h>
-#include <libnautilus-private/nautilus-metadata.h>
-#include <libnautilus-private/nautilus-module.h>
-#include <libnautilus-private/nautilus-tree-view-drag-dest.h>
-#include <libnautilus-private/nautilus-clipboard.h>
+#include "nautilus-clipboard-monitor.h"
+#include "nautilus-column-chooser.h"
+#include "nautilus-column-utilities.h"
+#include "nautilus-dnd.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-ui-utilities.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-metadata.h"
+#include "nautilus-module.h"
+#include "nautilus-tree-view-drag-dest.h"
+#include "nautilus-clipboard.h"
#define DEBUG_FLAG NAUTILUS_DEBUG_LIST_VIEW
-#include <libnautilus-private/nautilus-debug.h>
+#include "nautilus-debug.h"
/* We use a rectangle to make the popover point to the right column. In an
* ideal world with GtkListBox we would just point to the GtkListBoxRow. In our case, we
diff --git a/src/nautilus-location-entry.c b/src/nautilus-location-entry.c
index 53d6ef277..306b6e4b7 100644
--- a/src/nautilus-location-entry.c
+++ b/src/nautilus-location-entry.c
@@ -37,9 +37,9 @@
#include <gdk/gdkkeysyms.h>
#include <glib/gi18n.h>
#include <gio/gio.h>
-#include <libnautilus-private/nautilus-file-utilities.h>
-#include <libnautilus-private/nautilus-entry.h>
-#include <libnautilus-private/nautilus-clipboard.h>
+#include "nautilus-file-utilities.h"
+#include "nautilus-entry.h"
+#include "nautilus-clipboard.h"
#include <eel/eel-stock-dialogs.h>
#include <eel/eel-string.h>
#include <eel/eel-vfs-extensions.h>
diff --git a/src/nautilus-location-entry.h b/src/nautilus-location-entry.h
index eeaafe6cd..980a56103 100644
--- a/src/nautilus-location-entry.h
+++ b/src/nautilus-location-entry.h
@@ -25,7 +25,7 @@
#ifndef NAUTILUS_LOCATION_ENTRY_H
#define NAUTILUS_LOCATION_ENTRY_H
-#include <libnautilus-private/nautilus-entry.h>
+#include "nautilus-entry.h"
#define NAUTILUS_TYPE_LOCATION_ENTRY nautilus_location_entry_get_type()
#define NAUTILUS_LOCATION_ENTRY(obj) \
diff --git a/src/nautilus-main.c b/src/nautilus-main.c
index 01c65508a..511cfdad0 100644
--- a/src/nautilus-main.c
+++ b/src/nautilus-main.c
@@ -31,7 +31,7 @@
#include "nautilus-application.h"
#include "nautilus-resources.h"
-#include <libnautilus-private/nautilus-debug.h>
+#include "nautilus-debug.h"
#include <eel/eel-debug.h>
#include <glib/gi18n.h>
diff --git a/src/nautilus-metadata.c b/src/nautilus-metadata.c
new file mode 100644
index 000000000..3147f0c8b
--- /dev/null
+++ b/src/nautilus-metadata.c
@@ -0,0 +1,73 @@
+
+/* 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_LOCATION_BACKGROUND_COLOR,
+ NAUTILUS_METADATA_KEY_LOCATION_BACKGROUND_IMAGE,
+ NAUTILUS_METADATA_KEY_ICON_VIEW_AUTO_LAYOUT,
+ NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_BY,
+ NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_REVERSED,
+ NAUTILUS_METADATA_KEY_ICON_VIEW_KEEP_ALIGNED,
+ NAUTILUS_METADATA_KEY_ICON_VIEW_LAYOUT_TIMESTAMP,
+ NAUTILUS_METADATA_KEY_DESKTOP_ICON_SIZE,
+ 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_WINDOW_GEOMETRY,
+ NAUTILUS_METADATA_KEY_WINDOW_SCROLL_POSITION,
+ NAUTILUS_METADATA_KEY_WINDOW_SHOW_HIDDEN_FILES,
+ NAUTILUS_METADATA_KEY_WINDOW_MAXIMIZED,
+ NAUTILUS_METADATA_KEY_WINDOW_STICKY,
+ NAUTILUS_METADATA_KEY_WINDOW_KEEP_ABOVE,
+ NAUTILUS_METADATA_KEY_SIDEBAR_BACKGROUND_COLOR,
+ NAUTILUS_METADATA_KEY_SIDEBAR_BACKGROUND_IMAGE,
+ NAUTILUS_METADATA_KEY_SIDEBAR_BUTTONS,
+ NAUTILUS_METADATA_KEY_ANNOTATION,
+ NAUTILUS_METADATA_KEY_ICON_POSITION,
+ NAUTILUS_METADATA_KEY_ICON_POSITION_TIMESTAMP,
+ NAUTILUS_METADATA_KEY_ICON_SCALE,
+ NAUTILUS_METADATA_KEY_CUSTOM_ICON,
+ NAUTILUS_METADATA_KEY_CUSTOM_ICON_NAME,
+ NAUTILUS_METADATA_KEY_SCREEN,
+ 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 000000000..7a734af31
--- /dev/null
+++ b/src/nautilus-metadata.h
@@ -0,0 +1,72 @@
+/*
+ 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>
+*/
+
+#ifndef NAUTILUS_METADATA_H
+#define NAUTILUS_METADATA_H
+
+/* 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_LOCATION_BACKGROUND_COLOR "folder-background-color"
+#define NAUTILUS_METADATA_KEY_LOCATION_BACKGROUND_IMAGE "folder-background-image"
+
+#define NAUTILUS_METADATA_KEY_ICON_VIEW_AUTO_LAYOUT "nautilus-icon-view-auto-layout"
+#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_ICON_VIEW_KEEP_ALIGNED "nautilus-icon-view-keep-aligned"
+#define NAUTILUS_METADATA_KEY_ICON_VIEW_LAYOUT_TIMESTAMP "nautilus-icon-view-layout-timestamp"
+
+#define NAUTILUS_METADATA_KEY_DESKTOP_ICON_SIZE "nautilus-desktop-icon-size"
+
+#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_WINDOW_GEOMETRY "nautilus-window-geometry"
+#define NAUTILUS_METADATA_KEY_WINDOW_SCROLL_POSITION "nautilus-window-scroll-position"
+#define NAUTILUS_METADATA_KEY_WINDOW_SHOW_HIDDEN_FILES "nautilus-window-show-hidden-files"
+#define NAUTILUS_METADATA_KEY_WINDOW_MAXIMIZED "nautilus-window-maximized"
+#define NAUTILUS_METADATA_KEY_WINDOW_STICKY "nautilus-window-sticky"
+#define NAUTILUS_METADATA_KEY_WINDOW_KEEP_ABOVE "nautilus-window-keep-above"
+
+#define NAUTILUS_METADATA_KEY_SIDEBAR_BACKGROUND_COLOR "nautilus-sidebar-background-color"
+#define NAUTILUS_METADATA_KEY_SIDEBAR_BACKGROUND_IMAGE "nautilus-sidebar-background-image"
+#define NAUTILUS_METADATA_KEY_SIDEBAR_BUTTONS "nautilus-sidebar-buttons"
+
+#define NAUTILUS_METADATA_KEY_ICON_POSITION "nautilus-icon-position"
+#define NAUTILUS_METADATA_KEY_ICON_POSITION_TIMESTAMP "nautilus-icon-position-timestamp"
+#define NAUTILUS_METADATA_KEY_ANNOTATION "annotation"
+#define NAUTILUS_METADATA_KEY_ICON_SCALE "icon-scale"
+#define NAUTILUS_METADATA_KEY_CUSTOM_ICON "custom-icon"
+#define NAUTILUS_METADATA_KEY_CUSTOM_ICON_NAME "custom-icon-name"
+#define NAUTILUS_METADATA_KEY_SCREEN "screen"
+#define NAUTILUS_METADATA_KEY_EMBLEMS "emblems"
+
+guint nautilus_metadata_get_id (const char *metadata);
+
+#endif /* NAUTILUS_METADATA_H */
diff --git a/src/nautilus-mime-actions.c b/src/nautilus-mime-actions.c
index a7fd4c9c7..c09ca43e6 100644
--- a/src/nautilus-mime-actions.c
+++ b/src/nautilus-mime-actions.c
@@ -36,16 +36,16 @@
#include <string.h>
#include <gdk/gdkx.h>
-#include <libnautilus-private/nautilus-file-attributes.h>
-#include <libnautilus-private/nautilus-file.h>
-#include <libnautilus-private/nautilus-file-operations.h>
-#include <libnautilus-private/nautilus-metadata.h>
-#include <libnautilus-private/nautilus-program-choosing.h>
-#include <libnautilus-private/nautilus-global-preferences.h>
-#include <libnautilus-private/nautilus-signaller.h>
+#include "nautilus-file-attributes.h"
+#include "nautilus-file.h"
+#include "nautilus-file-operations.h"
+#include "nautilus-metadata.h"
+#include "nautilus-program-choosing.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-signaller.h"
#define DEBUG_FLAG NAUTILUS_DEBUG_MIME
-#include <libnautilus-private/nautilus-debug.h>
+#include "nautilus-debug.h"
typedef enum {
ACTIVATION_ACTION_LAUNCH_DESKTOP_FILE,
diff --git a/src/nautilus-mime-actions.h b/src/nautilus-mime-actions.h
index 0349c21c0..f96ca66a7 100644
--- a/src/nautilus-mime-actions.h
+++ b/src/nautilus-mime-actions.h
@@ -26,7 +26,7 @@
#include <gio/gio.h>
#include <glib/gi18n.h>
-#include <libnautilus-private/nautilus-file.h>
+#include "nautilus-file.h"
#include "nautilus-window.h"
diff --git a/src/nautilus-mime-application-chooser.c b/src/nautilus-mime-application-chooser.c
new file mode 100644
index 000000000..ebe069fa3
--- /dev/null
+++ b/src/nautilus-mime-application-chooser.c
@@ -0,0 +1,473 @@
+
+/*
+ * nautilus-mime-application-chooser.c: an mime-application chooser
+ *
+ * Copyright (C) 2004 Novell, Inc.
+ * Copyright (C) 2007, 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 APPLICATIONOUT ANY WARRANTY; applicationout 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 application the Gnome Library; see the file COPYING.LIB. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Dave Camp <dave@novell.com>
+ * Alexander Larsson <alexl@redhat.com>
+ * Cosimo Cecchi <ccecchi@redhat.com>
+ */
+
+#include <config.h>
+#include "nautilus-mime-application-chooser.h"
+
+#include "nautilus-file.h"
+#include "nautilus-signaller.h"
+#include <eel/eel-stock-dialogs.h>
+
+#include <string.h>
+#include <glib/gi18n-lib.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+
+struct _NautilusMimeApplicationChooserDetails {
+ GList *files;
+
+ char *content_type;
+
+ GtkWidget *label;
+ GtkWidget *entry;
+ GtkWidget *set_as_default_button;
+ GtkWidget *open_with_widget;
+ GtkWidget *add_button;
+};
+
+enum {
+ PROP_CONTENT_TYPE = 1,
+ PROP_FILES,
+ NUM_PROPERTIES
+};
+
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
+
+G_DEFINE_TYPE (NautilusMimeApplicationChooser, nautilus_mime_application_chooser, GTK_TYPE_BOX);
+
+static void
+add_clicked_cb (GtkButton *button,
+ gpointer user_data)
+{
+ NautilusMimeApplicationChooser *chooser = user_data;
+ GAppInfo *info;
+ gchar *message;
+ GError *error = NULL;
+
+ info = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (chooser->details->open_with_widget));
+
+ if (info == NULL)
+ return;
+
+ g_app_info_set_as_last_used_for_type (info, chooser->details->content_type, &error);
+
+ if (error != NULL) {
+ message = g_strdup_printf (_("Error while adding “%s”: %s"),
+ g_app_info_get_display_name (info), error->message);
+ eel_show_error_dialog (_("Could not add application"),
+ message,
+ GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (chooser))));
+ g_error_free (error);
+ g_free (message);
+ } else {
+ gtk_app_chooser_refresh (GTK_APP_CHOOSER (chooser->details->open_with_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)
+{
+ NautilusMimeApplicationChooser *chooser = user_data;
+ GError *error;
+ GAppInfo *info;
+
+ info = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (chooser->details->open_with_widget));
+
+ if (info) {
+ error = NULL;
+ if (!g_app_info_remove_supports_type (info,
+ chooser->details->content_type,
+ &error)) {
+ eel_show_error_dialog (_("Could not forget association"),
+ error->message,
+ GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (chooser))));
+ g_error_free (error);
+
+ }
+
+ gtk_app_chooser_refresh (GTK_APP_CHOOSER (chooser->details->open_with_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;
+ NautilusMimeApplicationChooser *chooser = 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), chooser);
+ }
+}
+
+static void
+reset_clicked_cb (GtkButton *button,
+ gpointer user_data)
+{
+ NautilusMimeApplicationChooser *chooser;
+
+ chooser = NAUTILUS_MIME_APPLICATION_CHOOSER (user_data);
+
+ g_app_info_reset_type_associations (chooser->details->content_type);
+ gtk_app_chooser_refresh (GTK_APP_CHOOSER (chooser->details->open_with_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)
+{
+ NautilusMimeApplicationChooser *chooser = user_data;
+ GAppInfo *info;
+ GError *error = NULL;
+ gchar *message = NULL;
+
+ info = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (chooser->details->open_with_widget));
+
+ g_app_info_set_as_default_for_type (info, chooser->details->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);
+ eel_show_error_dialog (_("Could not set as default"),
+ message,
+ GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (chooser))));
+ }
+
+ g_object_unref (info);
+
+ gtk_app_chooser_refresh (GTK_APP_CHOOSER (chooser->details->open_with_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)
+{
+ NautilusMimeApplicationChooser *chooser = user_data;
+ GAppInfo *default_app;
+
+ default_app = g_app_info_get_default_for_type (chooser->details->content_type, FALSE);
+ if (default_app != NULL) {
+ gtk_widget_set_sensitive (chooser->details->set_as_default_button,
+ !g_app_info_equal (info, default_app));
+ g_object_unref (default_app);
+ }
+ gtk_widget_set_sensitive (chooser->details->add_button,
+ app_info_can_add (info, chooser->details->content_type));
+}
+
+static void
+nautilus_mime_application_chooser_apply_labels (NautilusMimeApplicationChooser *chooser)
+{
+ gchar *label, *extension = NULL, *description = NULL;
+ gint num_files;
+ NautilusFile *file;
+
+ num_files = g_list_length (chooser->details->files);
+ file = chooser->details->files->data;
+
+ /* here we assume all files are of the same content type */
+ if (g_content_type_is_unknown (chooser->details->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 (chooser->details->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 (chooser->details->label), label);
+
+ g_free (label);
+ g_free (extension);
+ g_free (description);
+}
+
+static void
+nautilus_mime_application_chooser_build_ui (NautilusMimeApplicationChooser *chooser)
+{
+ GtkWidget *box, *button;
+ GAppInfo *info;
+
+ gtk_container_set_border_width (GTK_CONTAINER (chooser), 8);
+ gtk_box_set_spacing (GTK_BOX (chooser), 0);
+ gtk_box_set_homogeneous (GTK_BOX (chooser), FALSE);
+
+ chooser->details->label = gtk_label_new ("");
+ gtk_label_set_xalign (GTK_LABEL (chooser->details->label), 0);
+ gtk_label_set_line_wrap (GTK_LABEL (chooser->details->label), TRUE);
+ gtk_label_set_line_wrap_mode (GTK_LABEL (chooser->details->label),
+ PANGO_WRAP_WORD_CHAR);
+ gtk_label_set_max_width_chars (GTK_LABEL (chooser->details->label), 60);
+ gtk_box_pack_start (GTK_BOX (chooser), chooser->details->label,
+ FALSE, FALSE, 0);
+
+ gtk_widget_show (chooser->details->label);
+
+ chooser->details->open_with_widget = gtk_app_chooser_widget_new (chooser->details->content_type);
+ gtk_app_chooser_widget_set_show_default (GTK_APP_CHOOSER_WIDGET (chooser->details->open_with_widget),
+ TRUE);
+ gtk_app_chooser_widget_set_show_fallback (GTK_APP_CHOOSER_WIDGET (chooser->details->open_with_widget),
+ TRUE);
+ gtk_app_chooser_widget_set_show_other (GTK_APP_CHOOSER_WIDGET (chooser->details->open_with_widget),
+ TRUE);
+ gtk_box_pack_start (GTK_BOX (chooser), chooser->details->open_with_widget,
+ TRUE, TRUE, 6);
+ gtk_widget_show (chooser->details->open_with_widget);
+
+ box = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL);
+ gtk_box_set_spacing (GTK_BOX (box), 6);
+ gtk_button_box_set_layout (GTK_BUTTON_BOX (box), GTK_BUTTONBOX_END);
+ gtk_box_pack_start (GTK_BOX (chooser), box, FALSE, FALSE, 6);
+ gtk_widget_show (box);
+
+ button = gtk_button_new_with_label (_("Reset"));
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (reset_clicked_cb),
+ chooser);
+ gtk_widget_show (button);
+ gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0);
+ gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (box), button, TRUE);
+
+ button = gtk_button_new_with_mnemonic (_("_Add"));
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (add_clicked_cb),
+ chooser);
+ gtk_widget_show (button);
+ gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0);
+ chooser->details->add_button = button;
+
+ button = gtk_button_new_with_label (_("Set as default"));
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (set_as_default_clicked_cb),
+ chooser);
+ gtk_widget_show (button);
+ gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0);
+
+ chooser->details->set_as_default_button = button;
+
+ /* initialize sensitivity */
+ info = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (chooser->details->open_with_widget));
+ if (info != NULL) {
+ application_selected_cb (GTK_APP_CHOOSER_WIDGET (chooser->details->open_with_widget),
+ info, chooser);
+ g_object_unref (info);
+ }
+
+ g_signal_connect (chooser->details->open_with_widget,
+ "application-selected",
+ G_CALLBACK (application_selected_cb),
+ chooser);
+ g_signal_connect (chooser->details->open_with_widget,
+ "populate-popup",
+ G_CALLBACK (populate_popup_cb),
+ chooser);
+}
+
+static void
+nautilus_mime_application_chooser_init (NautilusMimeApplicationChooser *chooser)
+{
+ chooser->details = G_TYPE_INSTANCE_GET_PRIVATE (chooser, NAUTILUS_TYPE_MIME_APPLICATION_CHOOSER,
+ NautilusMimeApplicationChooserDetails);
+
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (chooser),
+ GTK_ORIENTATION_VERTICAL);
+}
+
+static void
+nautilus_mime_application_chooser_constructed (GObject *object)
+{
+ NautilusMimeApplicationChooser *chooser = NAUTILUS_MIME_APPLICATION_CHOOSER (object);
+
+ if (G_OBJECT_CLASS (nautilus_mime_application_chooser_parent_class)->constructed != NULL)
+ G_OBJECT_CLASS (nautilus_mime_application_chooser_parent_class)->constructed (object);
+
+ nautilus_mime_application_chooser_build_ui (chooser);
+ nautilus_mime_application_chooser_apply_labels (chooser);
+}
+
+static void
+nautilus_mime_application_chooser_finalize (GObject *object)
+{
+ NautilusMimeApplicationChooser *chooser;
+
+ chooser = NAUTILUS_MIME_APPLICATION_CHOOSER (object);
+
+ g_free (chooser->details->content_type);
+ nautilus_file_list_free (chooser->details->files);
+
+ G_OBJECT_CLASS (nautilus_mime_application_chooser_parent_class)->finalize (object);
+}
+
+static void
+nautilus_mime_application_chooser_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusMimeApplicationChooser *chooser = NAUTILUS_MIME_APPLICATION_CHOOSER (object);
+
+ switch (property_id) {
+ case PROP_CONTENT_TYPE:
+ g_value_set_string (value, chooser->details->content_type);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+nautilus_mime_application_chooser_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusMimeApplicationChooser *chooser = NAUTILUS_MIME_APPLICATION_CHOOSER (object);
+
+ switch (property_id) {
+ case PROP_CONTENT_TYPE:
+ chooser->details->content_type = g_value_dup_string (value);
+ break;
+ case PROP_FILES:
+ chooser->details->files = nautilus_file_list_copy (g_value_get_pointer (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+nautilus_mime_application_chooser_class_init (NautilusMimeApplicationChooserClass *class)
+{
+ GObjectClass *gobject_class;
+
+ gobject_class = G_OBJECT_CLASS (class);
+ gobject_class->set_property = nautilus_mime_application_chooser_set_property;
+ gobject_class->get_property = nautilus_mime_application_chooser_get_property;
+ gobject_class->finalize = nautilus_mime_application_chooser_finalize;
+ gobject_class->constructed = nautilus_mime_application_chooser_constructed;
+
+ properties[PROP_CONTENT_TYPE] = g_param_spec_string ("content-type",
+ "Content type",
+ "Content type for this widget",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS);
+ properties[PROP_FILES] = g_param_spec_pointer ("files",
+ "Files",
+ "Files for this widget",
+ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties);
+
+ g_type_class_add_private (class, sizeof (NautilusMimeApplicationChooserDetails));
+}
+
+GtkWidget *
+nautilus_mime_application_chooser_new (GList *files,
+ const char *mime_type)
+{
+ GtkWidget *chooser;
+
+ chooser = g_object_new (NAUTILUS_TYPE_MIME_APPLICATION_CHOOSER,
+ "files", files,
+ "content-type", mime_type,
+ NULL);
+
+ return chooser;
+}
diff --git a/src/nautilus-mime-application-chooser.h b/src/nautilus-mime-application-chooser.h
new file mode 100644
index 000000000..fd7ae1e0a
--- /dev/null
+++ b/src/nautilus-mime-application-chooser.h
@@ -0,0 +1,51 @@
+
+/*
+ nautilus-mime-application-chooser.c: Manages applications for mime types
+
+ 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 APPLICATIONOUT ANY WARRANTY; applicationout 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 application the Gnome Library; see the file COPYING.LIB. If not,
+ see <http://www.gnu.org/licenses/>.
+
+ Authors: Dave Camp <dave@novell.com>
+*/
+
+#ifndef NAUTILUS_MIME_APPLICATION_CHOOSER_H
+#define NAUTILUS_MIME_APPLICATION_CHOOSER_H
+
+#include <gtk/gtk.h>
+
+#define NAUTILUS_TYPE_MIME_APPLICATION_CHOOSER (nautilus_mime_application_chooser_get_type ())
+#define NAUTILUS_MIME_APPLICATION_CHOOSER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_MIME_APPLICATION_CHOOSER, NautilusMimeApplicationChooser))
+#define NAUTILUS_MIME_APPLICATION_CHOOSER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_MIME_APPLICATION_CHOOSER, NautilusMimeApplicationChooserClass))
+#define NAUTILUS_IS_MIME_APPLICATION_CHOOSER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_MIME_APPLICATION_CHOOSER)
+
+typedef struct _NautilusMimeApplicationChooser NautilusMimeApplicationChooser;
+typedef struct _NautilusMimeApplicationChooserClass NautilusMimeApplicationChooserClass;
+typedef struct _NautilusMimeApplicationChooserDetails NautilusMimeApplicationChooserDetails;
+
+struct _NautilusMimeApplicationChooser {
+ GtkBox parent;
+ NautilusMimeApplicationChooserDetails *details;
+};
+
+struct _NautilusMimeApplicationChooserClass {
+ GtkBoxClass parent_class;
+};
+
+GType nautilus_mime_application_chooser_get_type (void);
+GtkWidget * nautilus_mime_application_chooser_new (GList *files,
+ const char *mime_type);
+
+#endif /* NAUTILUS_MIME_APPLICATION_CHOOSER_H */
diff --git a/src/nautilus-module.c b/src/nautilus-module.c
new file mode 100644
index 000000000..7e52243fd
--- /dev/null
+++ b/src/nautilus-module.c
@@ -0,0 +1,294 @@
+/*
+ * 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 000000000..b4854a8b0
--- /dev/null
+++ b/src/nautilus-module.h
@@ -0,0 +1,41 @@
+/*
+ * 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>
+ *
+ */
+
+#ifndef NAUTILUS_MODULE_H
+#define NAUTILUS_MODULE_H
+
+#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
+
+#endif
diff --git a/src/nautilus-monitor.c b/src/nautilus-monitor.c
new file mode 100644
index 000000000..38e63d6d1
--- /dev/null
+++ b/src/nautilus-monitor.c
@@ -0,0 +1,161 @@
+/*
+ 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 *uri, *to_uri;
+
+ uri = g_file_get_uri (child);
+ 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 (uri);
+ 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 000000000..29faa2318
--- /dev/null
+++ b/src/nautilus-monitor.h
@@ -0,0 +1,36 @@
+/*
+ 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>
+*/
+
+#ifndef NAUTILUS_MONITOR_H
+#define NAUTILUS_MONITOR_H
+
+#include <glib.h>
+#include <gio/gio.h>
+
+typedef struct NautilusMonitor NautilusMonitor;
+
+NautilusMonitor *nautilus_monitor_directory (GFile *location);
+void nautilus_monitor_cancel (NautilusMonitor *monitor);
+
+#endif /* NAUTILUS_MONITOR_H */
diff --git a/src/nautilus-pathbar.c b/src/nautilus-pathbar.c
index 693fcb513..d7c3d95c5 100644
--- a/src/nautilus-pathbar.c
+++ b/src/nautilus-pathbar.c
@@ -25,12 +25,12 @@
#include "nautilus-pathbar.h"
#include "nautilus-properties-window.h"
-#include <libnautilus-private/nautilus-file.h>
-#include <libnautilus-private/nautilus-file-utilities.h>
-#include <libnautilus-private/nautilus-global-preferences.h>
-#include <libnautilus-private/nautilus-icon-names.h>
-#include <libnautilus-private/nautilus-trash-monitor.h>
-#include <libnautilus-private/nautilus-ui-utilities.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"
diff --git a/src/nautilus-preferences-window.c b/src/nautilus-preferences-window.c
index 22ffc4465..5fe783bd3 100644
--- a/src/nautilus-preferences-window.c
+++ b/src/nautilus-preferences-window.c
@@ -35,10 +35,10 @@
#include <eel/eel-glib-extensions.h>
-#include <libnautilus-private/nautilus-column-chooser.h>
-#include <libnautilus-private/nautilus-column-utilities.h>
-#include <libnautilus-private/nautilus-global-preferences.h>
-#include <libnautilus-private/nautilus-module.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"
diff --git a/src/nautilus-previewer.c b/src/nautilus-previewer.c
index 559440159..272abed70 100644
--- a/src/nautilus-previewer.c
+++ b/src/nautilus-previewer.c
@@ -25,7 +25,7 @@
#include "nautilus-previewer.h"
#define DEBUG_FLAG NAUTILUS_DEBUG_PREVIEWER
-#include <libnautilus-private/nautilus-debug.h>
+#include "nautilus-debug.h"
#include <gio/gio.h>
diff --git a/src/nautilus-private-enum-types.c.template b/src/nautilus-private-enum-types.c.template
new file mode 100644
index 000000000..4acb2d8a8
--- /dev/null
+++ b/src/nautilus-private-enum-types.c.template
@@ -0,0 +1,40 @@
+/*** BEGIN file-header ***/
+#include "nautilus-private-enum-types.h"
+
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+/* enumerations from "@filename@" */
+#include "@filename@"
+
+/*** END file-production ***/
+
+/*** BEGIN value-header ***/
+GType
+@enum_name@_get_type (void)
+{
+ static GType the_type = 0;
+
+ if (the_type == 0)
+ {
+ static const G@Type@Value values[] = {
+/*** END value-header ***/
+
+/*** BEGIN value-production ***/
+ { @VALUENAME@,
+ "@VALUENAME@",
+ "@valuenick@" },
+/*** END value-production ***/
+
+/*** BEGIN value-tail ***/
+ { 0, NULL, NULL }
+ };
+ the_type = g_@type@_register_static (
+ g_intern_static_string ("@EnumName@"),
+ values);
+ }
+ return the_type;
+}
+
+/*** END value-tail ***/
+
diff --git a/src/nautilus-private-enum-types.h.template b/src/nautilus-private-enum-types.h.template
new file mode 100644
index 000000000..c4dbbe7b6
--- /dev/null
+++ b/src/nautilus-private-enum-types.h.template
@@ -0,0 +1,27 @@
+/*** BEGIN file-header ***/
+#ifndef __NAUTILUS_PRIVATE_ENUM_TYPES_H__
+#define __NAUTILUS_PRIVATE_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) G_GNUC_CONST;
+
+/*** END enumeration-production ***/
+
+/*** BEGIN file-tail ***/
+G_END_DECLS
+
+#endif /* __NAUTILUS_PRIVATE_ENUM_TYPES_H__ */
+/*** END file-tail ***/
+
diff --git a/src/nautilus-profile.c b/src/nautilus-profile.c
new file mode 100644
index 000000000..444fdc061
--- /dev/null
+++ b/src/nautilus-profile.c
@@ -0,0 +1,63 @@
+/*
+ * 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 000000000..583d2963e
--- /dev/null
+++ b/src/nautilus-profile.h
@@ -0,0 +1,56 @@
+/*
+ * 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
+ */
+
+#ifndef __NAUTILUS_PROFILE_H
+#define __NAUTILUS_PROFILE_H
+
+#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
+
+#endif /* __NAUTILUS_PROFILE_H */
diff --git a/src/nautilus-program-choosing.c b/src/nautilus-program-choosing.c
new file mode 100644
index 000000000..871ca7053
--- /dev/null
+++ b/src/nautilus-program-choosing.c
@@ -0,0 +1,402 @@
+
+/* 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-recent.h"
+#include <eel/eel-gnome-extensions.h>
+#include <eel/eel-stock-dialogs.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <gio/gdesktopappinfo.h>
+#include <stdlib.h>
+
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+
+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);
+}
+
+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;
+ GdkDisplay *display;
+ GdkAppLaunchContext *launch_context;
+ 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);
+
+ 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));
+ }
+
+ 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);
+ }
+
+ g_object_unref (launch_context);
+
+ if (result) {
+ for (l = uris; l != NULL; l = l->next) {
+ file = nautilus_file_get_by_uri (l->data);
+ nautilus_recent_add_file (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)
+{
+ GAppInfo *app;
+ GdkAppLaunchContext *ctx;
+ GdkDisplay *display;
+
+ if (use_terminal) {
+ eel_gnome_open_terminal_on_screen (full_command, screen);
+ } else {
+ app = g_app_info_create_from_commandline (full_command, NULL, 0, NULL);
+
+ if (app != NULL) {
+ display = gdk_screen_get_display (screen);
+ ctx = gdk_display_get_app_launch_context (display);
+ gdk_app_launch_context_set_screen (ctx, screen);
+
+ g_app_info_launch (app, NULL, G_APP_LAUNCH_CONTEXT (ctx), NULL);
+
+ g_object_unref (app);
+ g_object_unref (ctx);
+ }
+ }
+}
+
+/**
+ * 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);
+ eel_show_error_dialog
+ (_("Sorry, but you cannot execute commands from "
+ "a remote site."),
+ _("This is disabled due to security considerations."),
+ parent_window);
+
+ 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) {
+ eel_show_error_dialog
+ (_("There was an error launching the application."),
+ NULL,
+ parent_window);
+ 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 */
+ eel_show_error_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);
+
+ g_list_free_full (files, g_object_unref);
+ g_object_unref (app_info);
+ return;
+ } else if (count != total) {
+ /* some files are non-local */
+ eel_show_warning_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);
+ }
+ }
+
+ 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);
+ eel_show_error_dialog
+ (_("There was an error launching the application."),
+ message,
+ parent_window);
+
+ g_error_free (error);
+ g_free (message);
+ }
+
+ g_list_free_full (files, g_object_unref);
+ g_object_unref (context);
+ g_object_unref (app_info);
+}
diff --git a/src/nautilus-program-choosing.h b/src/nautilus-program-choosing.h
new file mode 100644
index 000000000..04dbf4cfe
--- /dev/null
+++ b/src/nautilus-program-choosing.h
@@ -0,0 +1,56 @@
+
+/* 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>
+*/
+
+#ifndef NAUTILUS_PROGRAM_CHOOSING_H
+#define NAUTILUS_PROGRAM_CHOOSING_H
+
+#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);
+
+#endif /* NAUTILUS_PROGRAM_CHOOSING_H */
diff --git a/src/nautilus-progress-info-manager.c b/src/nautilus-progress-info-manager.c
new file mode 100644
index 000000000..29046c933
--- /dev/null
+++ b/src/nautilus-progress-info-manager.c
@@ -0,0 +1,224 @@
+/*
+ * 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 _NautilusProgressInfoManagerPriv {
+ 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->priv->progress_infos != NULL) {
+ g_list_free_full (self->priv->progress_infos, g_object_unref);
+ }
+
+ for (l = self->priv->current_viewers; l != NULL; l = l->next) {
+ g_object_weak_unref (l->data, (GWeakNotify) remove_viewer, self);
+ }
+ g_list_free (self->priv->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_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)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NAUTILUS_TYPE_PROGRESS_INFO_MANAGER,
+ NautilusProgressInfoManagerPriv);
+}
+
+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);
+
+ g_type_class_add_private (klass, sizeof (NautilusProgressInfoManagerPriv));
+}
+
+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->priv->progress_infos, info) != NULL) {
+ g_warning ("Adding two times the same progress info object to the manager");
+ return;
+ }
+
+ self->priv->progress_infos =
+ g_list_prepend (self->priv->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->priv->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->priv->progress_infos = g_list_remove (self->priv->progress_infos,
+ l->data);
+ }
+ l = next;
+ }
+}
+
+GList *
+nautilus_progress_info_manager_get_all_infos (NautilusProgressInfoManager *self)
+{
+ return self->priv->progress_infos;
+}
+
+gboolean
+nautilus_progress_manager_are_all_infos_finished_or_cancelled (NautilusProgressInfoManager *self)
+{
+ GList *l;
+
+ for (l = self->priv->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->priv->current_viewers = g_list_remove (self->priv->current_viewers, viewer);
+
+ if (self->priv->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->priv->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->priv->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->priv->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->priv->current_viewers != NULL;
+}
diff --git a/src/nautilus-progress-info-manager.h b/src/nautilus-progress-info-manager.h
new file mode 100644
index 000000000..571990251
--- /dev/null
+++ b/src/nautilus-progress-info-manager.h
@@ -0,0 +1,73 @@
+/*
+ * 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>
+ */
+
+#ifndef __NAUTILUS_PROGRESS_INFO_MANAGER_H__
+#define __NAUTILUS_PROGRESS_INFO_MANAGER_H__
+
+#include <glib-object.h>
+
+#include "nautilus-progress-info.h"
+
+#define NAUTILUS_TYPE_PROGRESS_INFO_MANAGER nautilus_progress_info_manager_get_type()
+#define NAUTILUS_PROGRESS_INFO_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_PROGRESS_INFO_MANAGER, NautilusProgressInfoManager))
+#define NAUTILUS_PROGRESS_INFO_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_PROGRESS_INFO_MANAGER, NautilusProgressInfoManagerClass))
+#define NAUTILUS_IS_PROGRESS_INFO_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_PROGRESS_INFO_MANAGER))
+#define NAUTILUS_IS_PROGRESS_INFO_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_PROGRESS_INFO_MANAGER))
+#define NAUTILUS_PROGRESS_INFO_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_PROGRESS_INFO_MANAGER, NautilusProgressInfoManagerClass))
+
+typedef struct _NautilusProgressInfoManager NautilusProgressInfoManager;
+typedef struct _NautilusProgressInfoManagerClass NautilusProgressInfoManagerClass;
+typedef struct _NautilusProgressInfoManagerPriv NautilusProgressInfoManagerPriv;
+
+struct _NautilusProgressInfoManager {
+ GObject parent;
+
+ /* private */
+ NautilusProgressInfoManagerPriv *priv;
+};
+
+struct _NautilusProgressInfoManagerClass {
+ GObjectClass parent_class;
+};
+
+GType nautilus_progress_info_manager_get_type (void);
+
+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
+
+#endif /* __NAUTILUS_PROGRESS_INFO_MANAGER_H__ */
diff --git a/src/nautilus-progress-info-widget.h b/src/nautilus-progress-info-widget.h
index d57e38d7e..74c5a4d9c 100644
--- a/src/nautilus-progress-info-widget.h
+++ b/src/nautilus-progress-info-widget.h
@@ -26,7 +26,7 @@
#include <gtk/gtk.h>
-#include <libnautilus-private/nautilus-progress-info.h>
+#include "nautilus-progress-info.h"
#define NAUTILUS_TYPE_PROGRESS_INFO_WIDGET nautilus_progress_info_widget_get_type()
#define NAUTILUS_PROGRESS_INFO_WIDGET(obj) \
diff --git a/src/nautilus-progress-info.c b/src/nautilus-progress-info.c
new file mode 100644
index 000000000..4ae3b5d6b
--- /dev/null
+++ b/src/nautilus-progress-info.c
@@ -0,0 +1,737 @@
+/*
+ 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;
+ GCancellable *details_in_thread_cancellable;
+
+ 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;
+};
+
+struct _NautilusProgressInfoClass
+{
+ GObjectClass parent_class;
+};
+
+G_LOCK_DEFINE_STATIC(progress_info);
+
+G_DEFINE_TYPE (NautilusProgressInfo, nautilus_progress_info, G_TYPE_OBJECT)
+
+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, (GDestroyNotify) g_timer_destroy);
+ g_cancellable_disconnect (info->cancellable, info->cancellable_id);
+ g_object_unref (info->cancellable);
+ g_cancellable_cancel (info->details_in_thread_cancellable);
+ g_clear_object (&info->details_in_thread_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;
+
+ source = g_main_current_source ();
+
+ G_LOCK (progress_info);
+
+ /* 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
+set_details_in_thread (GTask *task,
+ NautilusProgressInfo *info,
+ gpointer user_data,
+ GCancellable *cancellable)
+{
+ if (!g_cancellable_is_cancelled (cancellable)) {
+ nautilus_progress_info_set_details (info, _("Cancelled"));
+ G_LOCK (progress_info);
+ info->cancel_at_idle = TRUE;
+ g_timer_stop (info->progress_timer);
+ queue_idle (info, TRUE);
+ G_UNLOCK (progress_info);
+ }
+}
+
+static void
+on_canceled (GCancellable *cancellable,
+ NautilusProgressInfo *info)
+{
+ GTask *task;
+
+ /* We can't do any lock operaton here, since this is probably the main
+ * thread, so modify the details in another thread. Also it can happens
+ * that we were finalizing the object, so create a new cancellable here
+ * so it can be cancelled in finalize */
+ info->details_in_thread_cancellable = g_cancellable_new ();
+ task = g_task_new (info, info->details_in_thread_cancellable, NULL, NULL);
+ g_task_run_in_thread (task, (GTaskThreadFunc) set_details_in_thread);
+
+ g_object_unref (task);
+}
+
+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)
+{
+ G_LOCK (progress_info);
+
+ g_cancellable_cancel (info->cancellable);
+ g_timer_stop (info->progress_timer);
+
+ G_UNLOCK (progress_info);
+}
+
+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);
+}
+
+void
+nautilus_progress_info_take_status (NautilusProgressInfo *info,
+ char *status)
+{
+ G_LOCK (progress_info);
+
+ if (g_strcmp0 (info->status, status) != 0) {
+ g_free (info->status);
+ info->status = status;
+
+ info->changed_at_idle = TRUE;
+ queue_idle (info, FALSE);
+ } else {
+ g_free (status);
+ }
+
+ G_UNLOCK (progress_info);
+}
+
+void
+nautilus_progress_info_set_status (NautilusProgressInfo *info,
+ const char *status)
+{
+ G_LOCK (progress_info);
+
+ if (g_strcmp0 (info->status, status) != 0) {
+ g_free (info->status);
+ info->status = g_strdup (status);
+
+ info->changed_at_idle = TRUE;
+ queue_idle (info, FALSE);
+ }
+
+ G_UNLOCK (progress_info);
+}
+
+
+void
+nautilus_progress_info_take_details (NautilusProgressInfo *info,
+ char *details)
+{
+ G_LOCK (progress_info);
+
+ if (g_strcmp0 (info->details, details) != 0) {
+ g_free (info->details);
+ info->details = details;
+
+ info->changed_at_idle = TRUE;
+ queue_idle (info, FALSE);
+ } else {
+ g_free (details);
+ }
+
+ G_UNLOCK (progress_info);
+}
+
+void
+nautilus_progress_info_set_details (NautilusProgressInfo *info,
+ const char *details)
+{
+ G_LOCK (progress_info);
+
+ if (g_strcmp0 (info->details, details) != 0) {
+ g_free (info->details);
+ info->details = g_strdup (details);
+
+ info->changed_at_idle = TRUE;
+ queue_idle (info, FALSE);
+ }
+
+ 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 */
+ ) {
+ 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 000000000..522dc18ba
--- /dev/null
+++ b/src/nautilus-progress-info.h
@@ -0,0 +1,95 @@
+/*
+ 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>
+*/
+
+#ifndef NAUTILUS_PROGRESS_INFO_H
+#define NAUTILUS_PROGRESS_INFO_H
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#define NAUTILUS_TYPE_PROGRESS_INFO (nautilus_progress_info_get_type ())
+#define NAUTILUS_PROGRESS_INFO(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), NAUTILUS_TYPE_PROGRESS_INFO, NautilusProgressInfo))
+#define NAUTILUS_PROGRESS_INFO_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), NAUTILUS_TYPE_PROGRESS_INFO, NautilusProgressInfoClass))
+#define NAUTILUS_IS_PROGRESS_INFO(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), NAUTILUS_TYPE_PROGRESS_INFO))
+#define NAUTILUS_IS_PROGRESS_INFO_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), NAUTILUS_TYPE_PROGRESS_INFO))
+#define NAUTILUS_PROGRESS_INFO_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), NAUTILUS_TYPE_PROGRESS_INFO, NautilusProgressInfoClass))
+
+typedef struct _NautilusProgressInfo NautilusProgressInfo;
+typedef struct _NautilusProgressInfoClass NautilusProgressInfoClass;
+
+GType nautilus_progress_info_get_type (void) G_GNUC_CONST;
+
+/* 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);
+
+
+
+#endif /* NAUTILUS_PROGRESS_INFO_H */
diff --git a/src/nautilus-progress-persistence-handler.c b/src/nautilus-progress-persistence-handler.c
index 774d392e0..3c704cdd2 100644
--- a/src/nautilus-progress-persistence-handler.c
+++ b/src/nautilus-progress-persistence-handler.c
@@ -31,8 +31,8 @@
#include <glib/gi18n.h>
-#include <libnautilus-private/nautilus-progress-info.h>
-#include <libnautilus-private/nautilus-progress-info-manager.h>
+#include "nautilus-progress-info.h"
+#include "nautilus-progress-info-manager.h"
struct _NautilusProgressPersistenceHandlerPriv {
NautilusProgressInfoManager *manager;
diff --git a/src/nautilus-properties-window.c b/src/nautilus-properties-window.c
index 986b107f6..94544c32b 100644
--- a/src/nautilus-properties-window.c
+++ b/src/nautilus-properties-window.c
@@ -47,15 +47,15 @@
#include <eel/eel-vfs-extensions.h>
#include <libnautilus-extension/nautilus-property-page-provider.h>
-#include <libnautilus-private/nautilus-entry.h>
-#include <libnautilus-private/nautilus-file-attributes.h>
-#include <libnautilus-private/nautilus-file-operations.h>
-#include <libnautilus-private/nautilus-file-utilities.h>
-#include <libnautilus-private/nautilus-global-preferences.h>
-#include <libnautilus-private/nautilus-link.h>
-#include <libnautilus-private/nautilus-metadata.h>
-#include <libnautilus-private/nautilus-mime-application-chooser.h>
-#include <libnautilus-private/nautilus-module.h>
+#include "nautilus-entry.h"
+#include "nautilus-file-attributes.h"
+#include "nautilus-file-operations.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-link.h"
+#include "nautilus-metadata.h"
+#include "nautilus-mime-application-chooser.h"
+#include "nautilus-module.h"
#if HAVE_SYS_VFS_H
#include <sys/vfs.h>
diff --git a/src/nautilus-properties-window.h b/src/nautilus-properties-window.h
index 34ac4dae8..9eff54c4e 100644
--- a/src/nautilus-properties-window.h
+++ b/src/nautilus-properties-window.h
@@ -25,7 +25,7 @@
#define NAUTILUS_PROPERTIES_WINDOW_H
#include <gtk/gtk.h>
-#include <libnautilus-private/nautilus-file.h>
+#include "nautilus-file.h"
typedef struct NautilusPropertiesWindow NautilusPropertiesWindow;
diff --git a/src/nautilus-query-editor.c b/src/nautilus-query-editor.c
index 26f601dd5..4e69fe975 100644
--- a/src/nautilus-query-editor.c
+++ b/src/nautilus-query-editor.c
@@ -33,9 +33,9 @@
#include <libgd/gd.h>
#include <eel/eel-glib-extensions.h>
-#include <libnautilus-private/nautilus-file-utilities.h>
-#include <libnautilus-private/nautilus-ui-utilities.h>
-#include <libnautilus-private/nautilus-global-preferences.h>
+#include "nautilus-file-utilities.h"
+#include "nautilus-ui-utilities.h"
+#include "nautilus-global-preferences.h"
typedef struct {
GtkWidget *entry;
diff --git a/src/nautilus-query-editor.h b/src/nautilus-query-editor.h
index f3bab150c..b940996dd 100644
--- a/src/nautilus-query-editor.h
+++ b/src/nautilus-query-editor.h
@@ -24,7 +24,7 @@
#include <gtk/gtk.h>
-#include <libnautilus-private/nautilus-query.h>
+#include "nautilus-query.h"
#define NAUTILUS_TYPE_QUERY_EDITOR nautilus_query_editor_get_type()
diff --git a/src/nautilus-query.c b/src/nautilus-query.c
new file mode 100644
index 000000000..96cc3a4bb
--- /dev/null
+++ b/src/nautilus-query.c
@@ -0,0 +1,614 @@
+/*
+ * 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 <string.h>
+
+#include <eel/eel-glib-extensions.h>
+#include <glib/gi18n.h>
+
+#include "nautilus-global-preferences.h"
+
+#include "nautilus-file-utilities.h"
+#include "nautilus-query.h"
+#include "nautilus-enum-types.h"
+
+struct _NautilusQuery {
+ GObject parent;
+
+ char *text;
+ GFile *location;
+ GList *mime_types;
+ gboolean show_hidden;
+ GPtrArray *date_range;
+ NautilusQuerySearchType search_type;
+ NautilusQuerySearchContent search_content;
+
+ gboolean searching;
+ gboolean recursive;
+ 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_boolean (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_boolean (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:
+ *
+ * MIME types the query holds.
+ *
+ */
+ 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_boolean ("recursive",
+ "Whether the query is being performed on subdirectories",
+ "Whether the query is being performed on subdirectories or not",
+ FALSE,
+ 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->show_hidden = TRUE;
+ query->location = g_file_new_for_path (g_get_home_dir ());
+ 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;
+ }
+
+ retval = MAX (10.0, 50.0 - (gdouble) (ptr - prepared_string) - nonexact_malus);
+ 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");
+ }
+
+}
+
+GList *
+nautilus_query_get_mime_types (NautilusQuery *query)
+{
+ g_return_val_if_fail (NAUTILUS_IS_QUERY (query), NULL);
+
+ return g_list_copy_deep (query->mime_types, (GCopyFunc) g_strdup, NULL);
+}
+
+void
+nautilus_query_set_mime_types (NautilusQuery *query, GList *mime_types)
+{
+ g_return_if_fail (NAUTILUS_IS_QUERY (query));
+
+ g_list_free_full (query->mime_types, g_free);
+ query->mime_types = g_list_copy_deep (mime_types, (GCopyFunc) g_strdup, NULL);
+
+ g_object_notify (G_OBJECT (query), "mimetypes");
+}
+
+void
+nautilus_query_add_mime_type (NautilusQuery *query, const char *mime_type)
+{
+ g_return_if_fail (NAUTILUS_IS_QUERY (query));
+
+ query->mime_types = g_list_append (query->mime_types, g_strdup (mime_type));
+
+ 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");
+ }
+}
+
+gboolean
+nautilus_query_get_recursive (NautilusQuery *query)
+{
+ g_return_val_if_fail (NAUTILUS_IS_QUERY (query), FALSE);
+
+ return query->recursive;
+}
+
+void
+nautilus_query_set_recursive (NautilusQuery *query,
+ gboolean recursive)
+{
+ g_return_if_fail (NAUTILUS_IS_QUERY (query));
+
+ recursive = !!recursive;
+
+ 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) {
+ return TRUE;
+ }
+
+ return FALSE;
+}
diff --git a/src/nautilus-query.h b/src/nautilus-query.h
new file mode 100644
index 000000000..2264f505f
--- /dev/null
+++ b/src/nautilus-query.h
@@ -0,0 +1,86 @@
+/*
+ * 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>
+ *
+ */
+
+#ifndef NAUTILUS_QUERY_H
+#define NAUTILUS_QUERY_H
+
+#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;
+
+#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);
+
+GList * nautilus_query_get_mime_types (NautilusQuery *query);
+void nautilus_query_set_mime_types (NautilusQuery *query, GList *mime_types);
+void nautilus_query_add_mime_type (NautilusQuery *query, const char *mime_type);
+
+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);
+
+gboolean nautilus_query_get_recursive (NautilusQuery *query);
+
+void nautilus_query_set_recursive (NautilusQuery *query,
+ gboolean 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);
+
+#endif /* NAUTILUS_QUERY_H */
diff --git a/src/nautilus-recent.c b/src/nautilus-recent.c
new file mode 100644
index 000000000..1e23a58dc
--- /dev/null
+++ b/src/nautilus-recent.c
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2002 James Willcox
+ *
+ * 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-recent.h"
+
+#include <eel/eel-vfs-extensions.h>
+
+#define DEFAULT_APP_EXEC "gnome-open %u"
+
+static GtkRecentManager *
+nautilus_recent_get_manager (void)
+{
+ static GtkRecentManager *manager = NULL;
+
+ if (manager == NULL) {
+ manager = gtk_recent_manager_get_default ();
+ }
+
+ return manager;
+}
+
+void
+nautilus_recent_add_file (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) ||
+ eel_uri_is_desktop (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 ());
+
+ if (application != NULL)
+ recent_data.app_exec = g_strdup (g_app_info_get_commandline (application));
+ else
+ recent_data.app_exec = g_strdup (DEFAULT_APP_EXEC);
+
+ recent_data.groups = NULL;
+ recent_data.is_private = FALSE;
+
+ gtk_recent_manager_add_full (nautilus_recent_get_manager (),
+ uri, &recent_data);
+
+ g_free (recent_data.mime_type);
+ g_free (recent_data.app_name);
+ g_free (recent_data.app_exec);
+
+ g_free (uri);
+}
diff --git a/src/nautilus-recent.h b/src/nautilus-recent.h
new file mode 100644
index 000000000..5c64f6c09
--- /dev/null
+++ b/src/nautilus-recent.h
@@ -0,0 +1,13 @@
+
+
+#ifndef __NAUTILUS_RECENT_H__
+#define __NAUTILUS_RECENT_H__
+
+#include <gtk/gtk.h>
+#include "nautilus-file.h"
+#include <gio/gio.h>
+
+void nautilus_recent_add_file (NautilusFile *file,
+ GAppInfo *application);
+
+#endif
diff --git a/src/nautilus-search-directory-file.c b/src/nautilus-search-directory-file.c
new file mode 100644
index 000000000..177494509
--- /dev/null
+++ b/src/nautilus-search-directory-file.c
@@ -0,0 +1,295 @@
+/*
+ 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 <config.h>
+#include "nautilus-search-directory-file.h"
+
+#include "nautilus-directory-notify.h"
+#include "nautilus-directory-private.h"
+#include "nautilus-file-attributes.h"
+#include "nautilus-file-private.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-keyfile-metadata.h"
+#include <eel/eel-glib-extensions.h>
+#include "nautilus-search-directory.h"
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <string.h>
+
+struct NautilusSearchDirectoryFileDetails {
+ 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) {
+ file_list = nautilus_directory_get_file_list (file->details->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)
+{
+ NautilusFile *dir_file;
+ GList *file_list, *l;
+ guint dirs, files;
+ GFileType type;
+
+ file_list = nautilus_directory_get_file_list (file->details->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->details->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->details->metadata_filename,
+ "directory", key, (const gchar **) value);
+}
+
+void
+nautilus_search_directory_file_update_display_name (NautilusSearchDirectoryFile *search_file)
+{
+ NautilusFile *file;
+ NautilusSearchDirectory *search_dir;
+ NautilusQuery *query;
+ char *display_name;
+ gboolean changed;
+
+
+ display_name = NULL;
+ file = NAUTILUS_FILE (search_file);
+ if (file->details->directory) {
+ search_dir = NAUTILUS_SEARCH_DIRECTORY (file->details->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);
+
+ search_file->details = G_TYPE_INSTANCE_GET_PRIVATE (search_file,
+ NAUTILUS_TYPE_SEARCH_DIRECTORY_FILE,
+ NautilusSearchDirectoryFileDetails);
+
+ xdg_dir = nautilus_get_user_directory ();
+ search_file->details->metadata_filename = g_build_filename (xdg_dir,
+ "search-metadata",
+ NULL);
+ g_free (xdg_dir);
+
+ file->details->got_file_info = TRUE;
+ file->details->mime_type = eel_ref_str_get_unique ("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->got_link_info = TRUE;
+ file->details->link_info_is_up_to_date = TRUE;
+
+ 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->details->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;
+
+ g_type_class_add_private (object_class, sizeof(NautilusSearchDirectoryFileDetails));
+}
diff --git a/src/nautilus-search-directory-file.h b/src/nautilus-search-directory-file.h
new file mode 100644
index 000000000..5f47c3baf
--- /dev/null
+++ b/src/nautilus-search-directory-file.h
@@ -0,0 +1,54 @@
+/*
+ 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>
+*/
+
+#ifndef NAUTILUS_SEARCH_DIRECTORY_FILE_H
+#define NAUTILUS_SEARCH_DIRECTORY_FILE_H
+
+#include "nautilus-file.h"
+
+#define NAUTILUS_TYPE_SEARCH_DIRECTORY_FILE nautilus_search_directory_file_get_type()
+#define NAUTILUS_SEARCH_DIRECTORY_FILE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_SEARCH_DIRECTORY_FILE, NautilusSearchDirectoryFile))
+#define NAUTILUS_SEARCH_DIRECTORY_FILE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_SEARCH_DIRECTORY_FILE, NautilusSearchDirectoryFileClass))
+#define NAUTILUS_IS_SEARCH_DIRECTORY_FILE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_SEARCH_DIRECTORY_FILE))
+#define NAUTILUS_IS_SEARCH_DIRECTORY_FILE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_SEARCH_DIRECTORY_FILE))
+#define NAUTILUS_SEARCH_DIRECTORY_FILE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_SEARCH_DIRECTORY_FILE, NautilusSearchDirectoryFileClass))
+
+typedef struct NautilusSearchDirectoryFileDetails NautilusSearchDirectoryFileDetails;
+
+typedef struct {
+ NautilusFile parent_slot;
+ NautilusSearchDirectoryFileDetails *details;
+} NautilusSearchDirectoryFile;
+
+typedef struct {
+ NautilusFileClass parent_slot;
+} NautilusSearchDirectoryFileClass;
+
+GType nautilus_search_directory_file_get_type (void);
+void nautilus_search_directory_file_update_display_name (NautilusSearchDirectoryFile *search_file);
+
+#endif /* NAUTILUS_SEARCH_DIRECTORY_FILE_H */
diff --git a/src/nautilus-search-directory.c b/src/nautilus-search-directory.c
new file mode 100644
index 000000000..559c531d7
--- /dev/null
+++ b/src/nautilus-search-directory.c
@@ -0,0 +1,989 @@
+/*
+ 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 <config.h>
+#include "nautilus-search-directory.h"
+#include "nautilus-search-directory-file.h"
+
+#include "nautilus-directory-private.h"
+#include "nautilus-file.h"
+#include "nautilus-file-private.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-search-provider.h"
+#include "nautilus-search-engine.h"
+#include "nautilus-search-engine-model.h"
+
+#include <eel/eel-glib-extensions.h>
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+#include <string.h>
+#include <sys/time.h>
+
+struct NautilusSearchDirectoryDetails {
+ 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 ();
+ 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 *search);
+static void search_engine_error (NautilusSearchEngine *engine, const char *error, NautilusSearchDirectory *search);
+static void search_callback_file_ready_callback (NautilusFile *file, gpointer data);
+static void file_changed (NautilusFile *file, NautilusSearchDirectory *search);
+
+static void
+reset_file_list (NautilusSearchDirectory *search)
+{
+ GList *list, *monitor_list;
+ NautilusFile *file;
+ SearchMonitor *monitor;
+
+ /* Remove file connections */
+ for (list = search->details->files; list != NULL; list = list->next) {
+ file = list->data;
+
+ /* Disconnect change handler */
+ g_signal_handlers_disconnect_by_func (file, file_changed, search);
+
+ /* Remove monitors */
+ for (monitor_list = search->details->monitor_list; monitor_list;
+ monitor_list = monitor_list->next) {
+ monitor = monitor_list->data;
+ nautilus_file_monitor_remove (file, monitor);
+ }
+ }
+
+ nautilus_file_list_free (search->details->files);
+ search->details->files = NULL;
+
+ g_hash_table_remove_all (search->details->files_hash);
+}
+
+static void
+set_hidden_files (NautilusSearchDirectory *search)
+{
+ GList *l;
+ SearchMonitor *monitor;
+ gboolean monitor_hidden = FALSE;
+
+ for (l = search->details->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 (search->details->query, monitor_hidden);
+}
+
+static void
+start_search (NautilusSearchDirectory *search)
+{
+ NautilusSearchEngineModel *model_provider;
+ NautilusSearchEngineSimple *simple_provider;
+ gboolean recursive;
+
+ if (!search->details->query) {
+ return;
+ }
+
+ if (search->details->search_running) {
+ return;
+ }
+
+ if (!search->details->monitor_list && !search->details->pending_callback_list) {
+ return;
+ }
+
+ /* We need to start the search engine */
+ search->details->search_running = TRUE;
+ search->details->search_ready_and_valid = FALSE;
+
+ set_hidden_files (search);
+ nautilus_search_provider_set_query (NAUTILUS_SEARCH_PROVIDER (search->details->engine),
+ search->details->query);
+
+ model_provider = nautilus_search_engine_get_model_provider (search->details->engine);
+ nautilus_search_engine_model_set_model (model_provider, search->details->base_model);
+
+ simple_provider = nautilus_search_engine_get_simple_provider (search->details->engine);
+ recursive = nautilus_query_get_recursive (search->details->query);
+ g_object_set (simple_provider, "recursive", recursive, NULL);
+
+ reset_file_list (search);
+
+ nautilus_search_provider_start (NAUTILUS_SEARCH_PROVIDER (search->details->engine));
+}
+
+static void
+stop_search (NautilusSearchDirectory *search)
+{
+ if (!search->details->search_running) {
+ return;
+ }
+
+ search->details->search_running = FALSE;
+ nautilus_search_provider_stop (NAUTILUS_SEARCH_PROVIDER (search->details->engine));
+
+ reset_file_list (search);
+}
+
+static void
+file_changed (NautilusFile *file, NautilusSearchDirectory *search)
+{
+ GList list;
+
+ list.data = file;
+ list.next = NULL;
+
+ nautilus_directory_emit_files_changed (NAUTILUS_DIRECTORY (search), &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 *search;
+ NautilusFile *file;
+
+ search = NAUTILUS_SEARCH_DIRECTORY (directory);
+
+ monitor = g_new0 (SearchMonitor, 1);
+ monitor->monitor_hidden_files = monitor_hidden_files;
+ monitor->monitor_attributes = file_attributes;
+ monitor->client = client;
+
+ search->details->monitor_list = g_list_prepend (search->details->monitor_list, monitor);
+
+ if (callback != NULL) {
+ (* callback) (directory, search->details->files, callback_data);
+ }
+
+ for (list = search->details->files; list != NULL; list = list->next) {
+ file = list->data;
+
+ /* Add monitors */
+ nautilus_file_monitor_add (file, monitor, file_attributes);
+ }
+
+ start_search (search);
+}
+
+static void
+search_monitor_remove_file_monitors (SearchMonitor *monitor, NautilusSearchDirectory *search)
+{
+ GList *list;
+ NautilusFile *file;
+
+ for (list = search->details->files; list != NULL; list = list->next) {
+ file = list->data;
+
+ nautilus_file_monitor_remove (file, monitor);
+ }
+}
+
+static void
+search_monitor_destroy (SearchMonitor *monitor, NautilusSearchDirectory *search)
+{
+ search_monitor_remove_file_monitors (monitor, search);
+
+ g_free (monitor);
+}
+
+static void
+search_monitor_remove (NautilusDirectory *directory,
+ gconstpointer client)
+{
+ NautilusSearchDirectory *search;
+ SearchMonitor *monitor;
+ GList *list;
+
+ search = NAUTILUS_SEARCH_DIRECTORY (directory);
+
+ for (list = search->details->monitor_list; list != NULL; list = list->next) {
+ monitor = list->data;
+
+ if (monitor->client == client) {
+ search->details->monitor_list = g_list_delete_link (search->details->monitor_list, list);
+
+ search_monitor_destroy (monitor, search);
+
+ break;
+ }
+ }
+
+ if (!search->details->monitor_list) {
+ stop_search (search);
+ }
+}
+
+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->details->callback_list =
+ g_list_remove (search_callback->search_directory->details->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 *search, NautilusDirectoryCallback callback, gpointer callback_data)
+{
+ SearchCallback *search_callback;
+ GList *list;
+
+ for (list = search->details->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 *search, NautilusDirectoryCallback callback, gpointer callback_data)
+{
+ SearchCallback *search_callback;
+ GList *list;
+
+ for (list = search->details->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 *search;
+ SearchCallback *search_callback;
+
+ search = NAUTILUS_SEARCH_DIRECTORY (directory);
+
+ search_callback = search_callback_find (search, callback, callback_data);
+ if (search_callback == NULL) {
+ search_callback = search_callback_find_pending (search, 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 = search;
+ 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 && !search->details->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. */
+ search->details->pending_callback_list =
+ g_list_prepend (search->details->pending_callback_list, search_callback);
+
+ /* We might need to start the search engine */
+ start_search (search);
+ } else {
+ search_callback->file_list = nautilus_file_list_copy (search->details->files);
+ search_callback->non_ready_hash = file_list_to_hash_table (search->details->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 {
+ search->details->callback_list = g_list_prepend (search->details->callback_list, search_callback);
+ search_callback_add_file_callbacks (search_callback);
+ }
+ }
+}
+
+static void
+search_cancel_callback (NautilusDirectory *directory,
+ NautilusDirectoryCallback callback,
+ gpointer callback_data)
+{
+ NautilusSearchDirectory *search;
+ SearchCallback *search_callback;
+
+ search = NAUTILUS_SEARCH_DIRECTORY (directory);
+ search_callback = search_callback_find (search, callback, callback_data);
+
+ if (search_callback) {
+ search->details->callback_list = g_list_remove (search->details->callback_list, search_callback);
+
+ search_callback_destroy (search_callback);
+
+ goto done;
+ }
+
+ /* Check for a pending callback */
+ search_callback = search_callback_find_pending (search, callback, callback_data);
+
+ if (search_callback) {
+ search->details->pending_callback_list = g_list_remove (search->details->pending_callback_list, search_callback);
+
+ search_callback_destroy (search_callback);
+ }
+
+done:
+ if (!search->details->callback_list && !search->details->pending_callback_list) {
+ stop_search (search);
+ }
+}
+
+static void
+search_callback_add_pending_file_callbacks (SearchCallback *callback)
+{
+ callback->file_list = nautilus_file_list_copy (callback->search_directory->details->files);
+ callback->non_ready_hash = file_list_to_hash_table (callback->search_directory->details->files);
+
+ search_callback_add_file_callbacks (callback);
+}
+
+static void
+search_directory_add_pending_files_callbacks (NautilusSearchDirectory *search)
+{
+ /* Add all file callbacks */
+ g_list_foreach (search->details->pending_callback_list,
+ (GFunc)search_callback_add_pending_file_callbacks, NULL);
+ search->details->callback_list = g_list_concat (search->details->callback_list,
+ search->details->pending_callback_list);
+
+ g_list_free (search->details->pending_callback_list);
+ search->details->pending_callback_list = NULL;
+}
+
+static void
+on_search_directory_search_ready_and_valid (NautilusSearchDirectory *search)
+{
+ search_directory_add_pending_files_callbacks (search);
+ search->details->search_ready_and_valid = TRUE;
+}
+
+static void
+search_engine_hits_added (NautilusSearchEngine *engine, GList *hits,
+ NautilusSearchDirectory *search)
+{
+ 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);
+ if (g_str_has_suffix (uri, NAUTILUS_SAVED_SEARCH_EXTENSION)) {
+ /* Never return saved searches themselves as hits */
+ continue;
+ }
+
+ nautilus_search_hit_compute_scores (hit, search->details->query);
+
+ file = nautilus_file_get_by_uri (uri);
+ nautilus_file_set_search_relevance (file, nautilus_search_hit_get_relevance (hit));
+
+ for (monitor_list = search->details->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), search),
+
+ file_list = g_list_prepend (file_list, file);
+ g_hash_table_add (search->details->files_hash, file);
+ }
+
+ search->details->files = g_list_concat (search->details->files, file_list);
+
+ nautilus_directory_emit_files_added (NAUTILUS_DIRECTORY (search), file_list);
+
+ file = nautilus_directory_get_corresponding_file (NAUTILUS_DIRECTORY (search));
+ nautilus_file_emit_changed (file);
+ nautilus_file_unref (file);
+
+ search_directory_add_pending_files_callbacks (search);
+}
+
+static void
+search_engine_error (NautilusSearchEngine *engine, const char *error_message, NautilusSearchDirectory *search)
+{
+ GError *error;
+
+ error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED,
+ error_message);
+ nautilus_directory_emit_load_error (NAUTILUS_DIRECTORY (search),
+ error);
+ g_error_free (error);
+}
+
+static void
+search_engine_finished (NautilusSearchEngine *engine,
+ NautilusSearchProviderStatus status,
+ NautilusSearchDirectory *search)
+{
+ /* 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 (search);
+ nautilus_directory_emit_done_loading (NAUTILUS_DIRECTORY (search));
+ } 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 (search);
+ }
+}
+
+static void
+search_force_reload (NautilusDirectory *directory)
+{
+ NautilusSearchDirectory *search;
+ NautilusFile *file;
+
+ search = NAUTILUS_SEARCH_DIRECTORY (directory);
+
+ if (!search->details->query) {
+ return;
+ }
+
+ search->details->search_ready_and_valid = FALSE;
+
+ /* Remove file monitors */
+ reset_file_list (search);
+ stop_search (search);
+
+ 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 *search;
+
+ search = NAUTILUS_SEARCH_DIRECTORY (directory);
+
+ return (!search->details->query ||
+ search->details->search_ready_and_valid);
+}
+
+static gboolean
+search_contains_file (NautilusDirectory *directory,
+ NautilusFile *file)
+{
+ NautilusSearchDirectory *search;
+
+ search = NAUTILUS_SEARCH_DIRECTORY (directory);
+ return (g_hash_table_lookup (search->details->files_hash, file) != NULL);
+}
+
+static GList *
+search_get_file_list (NautilusDirectory *directory)
+{
+ NautilusSearchDirectory *search;
+
+ search = NAUTILUS_SEARCH_DIRECTORY (directory);
+
+ return nautilus_file_list_copy (search->details->files);
+}
+
+
+static gboolean
+search_is_editable (NautilusDirectory *directory)
+{
+ return FALSE;
+}
+
+static gboolean
+real_handles_location (GFile *location)
+{
+ g_autofree gchar *uri;
+
+ 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 *search = NAUTILUS_SEARCH_DIRECTORY (object);
+
+ switch (property_id) {
+ case PROP_BASE_MODEL:
+ nautilus_search_directory_set_base_model (search, g_value_get_object (value));
+ break;
+ case PROP_QUERY:
+ nautilus_search_directory_set_query (search, 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 *search = NAUTILUS_SEARCH_DIRECTORY (object);
+
+ switch (property_id) {
+ case PROP_BASE_MODEL:
+ g_value_set_object (value, nautilus_search_directory_get_base_model (search));
+ break;
+ case PROP_QUERY:
+ g_value_take_object (value, nautilus_search_directory_get_query (search));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+clear_base_model (NautilusSearchDirectory *search)
+{
+ if (search->details->base_model != NULL) {
+ nautilus_directory_file_monitor_remove (search->details->base_model,
+ &search->details->base_model);
+ g_clear_object (&search->details->base_model);
+ }
+}
+
+static void
+search_connect_engine (NautilusSearchDirectory *search)
+{
+ g_signal_connect (search->details->engine, "hits-added",
+ G_CALLBACK (search_engine_hits_added),
+ search);
+ g_signal_connect (search->details->engine, "error",
+ G_CALLBACK (search_engine_error),
+ search);
+ g_signal_connect (search->details->engine, "finished",
+ G_CALLBACK (search_engine_finished),
+ search);
+}
+
+static void
+search_disconnect_engine (NautilusSearchDirectory *search)
+{
+ g_signal_handlers_disconnect_by_func (search->details->engine,
+ search_engine_hits_added,
+ search);
+ g_signal_handlers_disconnect_by_func (search->details->engine,
+ search_engine_error,
+ search);
+ g_signal_handlers_disconnect_by_func (search->details->engine,
+ search_engine_finished,
+ search);
+}
+
+static void
+search_dispose (GObject *object)
+{
+ NautilusSearchDirectory *search;
+ GList *list;
+
+ search = NAUTILUS_SEARCH_DIRECTORY (object);
+
+ clear_base_model (search);
+
+ /* Remove search monitors */
+ if (search->details->monitor_list) {
+ for (list = search->details->monitor_list; list != NULL; list = list->next) {
+ search_monitor_destroy ((SearchMonitor *)list->data, search);
+ }
+
+ g_list_free (search->details->monitor_list);
+ search->details->monitor_list = NULL;
+ }
+
+ reset_file_list (search);
+
+ if (search->details->callback_list) {
+ /* Remove callbacks */
+ g_list_foreach (search->details->callback_list,
+ (GFunc)search_callback_destroy, NULL);
+ g_list_free (search->details->callback_list);
+ search->details->callback_list = NULL;
+ }
+
+ if (search->details->pending_callback_list) {
+ g_list_foreach (search->details->pending_callback_list,
+ (GFunc)search_callback_destroy, NULL);
+ g_list_free (search->details->pending_callback_list);
+ search->details->pending_callback_list = NULL;
+ }
+
+ g_clear_object (&search->details->query);
+ stop_search (search);
+ search_disconnect_engine(search);
+
+ g_clear_object (&search->details->engine);
+
+ G_OBJECT_CLASS (nautilus_search_directory_parent_class)->dispose (object);
+}
+
+static void
+search_finalize (GObject *object)
+{
+ NautilusSearchDirectory *search;
+
+ search = NAUTILUS_SEARCH_DIRECTORY (object);
+
+ g_hash_table_destroy (search->details->files_hash);
+
+ G_OBJECT_CLASS (nautilus_search_directory_parent_class)->finalize (object);
+}
+
+static void
+nautilus_search_directory_init (NautilusSearchDirectory *search)
+{
+ search->details = G_TYPE_INSTANCE_GET_PRIVATE (search, NAUTILUS_TYPE_SEARCH_DIRECTORY,
+ NautilusSearchDirectoryDetails);
+
+ search->details->files_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ search->details->engine = nautilus_search_engine_new ();
+ search_connect_engine (search);
+}
+
+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);
+ g_type_class_add_private (class, sizeof (NautilusSearchDirectoryDetails));
+}
+
+void
+nautilus_search_directory_set_base_model (NautilusSearchDirectory *search,
+ NautilusDirectory *base_model)
+{
+ if (search->details->base_model == base_model) {
+ return;
+ }
+
+ if (search->details->query != NULL) {
+ GFile *query_location, *model_location;
+ gboolean is_equal;
+
+ query_location = nautilus_query_get_location (search->details->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 (search);
+ search->details->base_model = nautilus_directory_ref (base_model);
+
+ if (search->details->base_model != NULL) {
+ nautilus_directory_file_monitor_add (base_model, &search->details->base_model,
+ TRUE, NAUTILUS_FILE_ATTRIBUTE_INFO,
+ NULL, NULL);
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (search), properties[PROP_BASE_MODEL]);
+}
+
+NautilusDirectory *
+nautilus_search_directory_get_base_model (NautilusSearchDirectory *search)
+{
+ return search->details->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 *search,
+ NautilusQuery *query)
+{
+ NautilusFile *file;
+ NautilusQuery *old_query;
+
+ old_query = search->details->query;
+
+ if (search->details->query != query) {
+ search->details->query = g_object_ref (query);
+
+ g_clear_pointer (&search->details->binding, g_binding_unbind);
+
+ if (query) {
+ search->details->binding = g_object_bind_property (search->details->engine, "running",
+ query, "searching",
+ G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (search), properties[PROP_QUERY]);
+
+ g_clear_object (&old_query);
+ }
+
+ file = nautilus_directory_get_existing_corresponding_file (NAUTILUS_DIRECTORY (search));
+ 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 *search)
+{
+ if (search->details->query != NULL) {
+ return g_object_ref (search->details->query);
+ }
+
+ return NULL;
+}
diff --git a/src/nautilus-search-directory.h b/src/nautilus-search-directory.h
new file mode 100644
index 000000000..a6b702bf8
--- /dev/null
+++ b/src/nautilus-search-directory.h
@@ -0,0 +1,66 @@
+/*
+ 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/>.
+*/
+
+#ifndef NAUTILUS_SEARCH_DIRECTORY_H
+#define NAUTILUS_SEARCH_DIRECTORY_H
+
+#include "nautilus-directory.h"
+#include "nautilus-query.h"
+
+#define NAUTILUS_SEARCH_DIRECTORY_PROVIDER_NAME "search-directory-provider"
+
+#define NAUTILUS_TYPE_SEARCH_DIRECTORY nautilus_search_directory_get_type()
+#define NAUTILUS_SEARCH_DIRECTORY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_SEARCH_DIRECTORY, NautilusSearchDirectory))
+#define NAUTILUS_SEARCH_DIRECTORY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_SEARCH_DIRECTORY, NautilusSearchDirectoryClass))
+#define NAUTILUS_IS_SEARCH_DIRECTORY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_SEARCH_DIRECTORY))
+#define NAUTILUS_IS_SEARCH_DIRECTORY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_SEARCH_DIRECTORY))
+#define NAUTILUS_SEARCH_DIRECTORY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_SEARCH_DIRECTORY, NautilusSearchDirectoryClass))
+
+typedef struct NautilusSearchDirectoryDetails NautilusSearchDirectoryDetails;
+
+typedef struct {
+ NautilusDirectory parent_slot;
+ NautilusSearchDirectoryDetails *details;
+} NautilusSearchDirectory;
+
+typedef struct {
+ NautilusDirectoryClass parent_slot;
+} NautilusSearchDirectoryClass;
+
+GType nautilus_search_directory_get_type (void);
+
+char *nautilus_search_directory_generate_new_uri (void);
+
+NautilusQuery *nautilus_search_directory_get_query (NautilusSearchDirectory *search);
+void nautilus_search_directory_set_query (NautilusSearchDirectory *search,
+ NautilusQuery *query);
+
+NautilusDirectory *
+ nautilus_search_directory_get_base_model (NautilusSearchDirectory *search);
+void nautilus_search_directory_set_base_model (NautilusSearchDirectory *search,
+ NautilusDirectory *base_model);
+
+#endif /* NAUTILUS_SEARCH_DIRECTORY_H */
diff --git a/src/nautilus-search-engine-model.c b/src/nautilus-search-engine-model.c
new file mode 100644
index 000000000..3294bd077
--- /dev/null
+++ b/src/nautilus-search-engine-model.c
@@ -0,0 +1,340 @@
+/*
+ * 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 NautilusSearchEngineModelDetails {
+ 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->details->hits != NULL) {
+ g_list_free_full (model->details->hits, g_object_unref);
+ model->details->hits = NULL;
+ }
+
+ if (model->details->finished_id != 0) {
+ g_source_remove (model->details->finished_id);
+ model->details->finished_id = 0;
+ }
+
+ g_clear_object (&model->details->directory);
+ g_clear_object (&model->details->query);
+
+ G_OBJECT_CLASS (nautilus_search_engine_model_parent_class)->finalize (object);
+}
+
+static gboolean
+search_finished (NautilusSearchEngineModel *model)
+{
+ model->details->finished_id = 0;
+
+ if (model->details->hits != NULL) {
+ DEBUG ("Model engine hits added");
+ nautilus_search_provider_hits_added (NAUTILUS_SEARCH_PROVIDER (model),
+ model->details->hits);
+ g_list_free_full (model->details->hits, g_object_unref);
+ model->details->hits = NULL;
+ }
+
+ model->details->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->details->finished_id != 0) {
+ return;
+ }
+
+ model->details->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;
+ gchar *uri, *display_name;
+ GList *files, *hits, *mime_types, *l, *m;
+ 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->details->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->details->query, display_name);
+ found = (match > -1);
+
+ if (found && mime_types) {
+ found = FALSE;
+
+ for (m = mime_types; m != NULL; m = m->next) {
+ if (nautilus_file_is_mime_type (file, m->data)) {
+ found = TRUE;
+ break;
+ }
+ }
+ }
+
+ date_range = nautilus_query_get_date_range (model->details->query);
+ if (found && date_range != NULL) {
+ NautilusQuerySearchType type;
+ guint64 current_file_unix_time;
+
+ type = nautilus_query_get_search_type (model->details->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);
+ }
+
+ g_list_free_full (mime_types, g_free);
+ nautilus_file_list_free (files);
+ model->details->hits = hits;
+
+ search_finished (model);
+}
+
+static void
+nautilus_search_engine_model_start (NautilusSearchProvider *provider)
+{
+ NautilusSearchEngineModel *model;
+
+ model = NAUTILUS_SEARCH_ENGINE_MODEL (provider);
+
+ if (model->details->query_pending) {
+ return;
+ }
+
+ DEBUG ("Model engine start");
+
+ g_object_ref (model);
+ model->details->query_pending = TRUE;
+
+ g_object_notify (G_OBJECT (provider), "running");
+
+ if (model->details->directory == NULL) {
+ search_finished_idle (model);
+ return;
+ }
+
+ nautilus_directory_call_when_ready (model->details->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->details->query_pending) {
+ DEBUG ("Model engine stop");
+
+ nautilus_directory_cancel_callback (model->details->directory,
+ model_directory_ready_cb, model);
+ search_finished_idle (model);
+ }
+
+ g_clear_object (&model->details->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->details->query);
+ model->details->query = query;
+}
+
+static gboolean
+nautilus_search_engine_model_is_running (NautilusSearchProvider *provider)
+{
+ NautilusSearchEngineModel *model;
+
+ model = NAUTILUS_SEARCH_ENGINE_MODEL (provider);
+
+ return model->details->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");
+
+ g_type_class_add_private (class, sizeof (NautilusSearchEngineModelDetails));
+}
+
+static void
+nautilus_search_engine_model_init (NautilusSearchEngineModel *engine)
+{
+ engine->details = G_TYPE_INSTANCE_GET_PRIVATE (engine, NAUTILUS_TYPE_SEARCH_ENGINE_MODEL,
+ NautilusSearchEngineModelDetails);
+}
+
+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->details->directory);
+ model->details->directory = nautilus_directory_ref (directory);
+}
+
+NautilusDirectory *
+nautilus_search_engine_model_get_model (NautilusSearchEngineModel *model)
+{
+ return model->details->directory;
+}
diff --git a/src/nautilus-search-engine-model.h b/src/nautilus-search-engine-model.h
new file mode 100644
index 000000000..16f3388fc
--- /dev/null
+++ b/src/nautilus-search-engine-model.h
@@ -0,0 +1,52 @@
+/*
+ * 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>
+ *
+ */
+
+#ifndef NAUTILUS_SEARCH_ENGINE_MODEL_H
+#define NAUTILUS_SEARCH_ENGINE_MODEL_H
+
+#include "nautilus-directory.h"
+
+#define NAUTILUS_TYPE_SEARCH_ENGINE_MODEL (nautilus_search_engine_model_get_type ())
+#define NAUTILUS_SEARCH_ENGINE_MODEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_SEARCH_ENGINE_MODEL, NautilusSearchEngineModel))
+#define NAUTILUS_SEARCH_ENGINE_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_SEARCH_ENGINE_MODEL, NautilusSearchEngineModelClass))
+#define NAUTILUS_IS_SEARCH_ENGINE_MODEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_SEARCH_ENGINE_MODEL))
+#define NAUTILUS_IS_SEARCH_ENGINE_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_SEARCH_ENGINE_MODEL))
+#define NAUTILUS_SEARCH_ENGINE_MODEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_SEARCH_ENGINE_MODEL, NautilusSearchEngineModelClass))
+
+typedef struct NautilusSearchEngineModelDetails NautilusSearchEngineModelDetails;
+
+typedef struct NautilusSearchEngineModel {
+ GObject parent;
+ NautilusSearchEngineModelDetails *details;
+} NautilusSearchEngineModel;
+
+typedef struct {
+ GObjectClass parent_class;
+} NautilusSearchEngineModelClass;
+
+GType nautilus_search_engine_model_get_type (void);
+
+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);
+
+#endif /* NAUTILUS_SEARCH_ENGINE_MODEL_H */
diff --git a/src/nautilus-search-engine-simple.c b/src/nautilus-search-engine-simple.c
new file mode 100644
index 000000000..253185aac
--- /dev/null
+++ b/src/nautilus-search-engine-simple.c
@@ -0,0 +1,525 @@
+/*
+ * 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-simple.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_RECURSIVE = 1,
+ PROP_RUNNING,
+ NUM_PROPERTIES
+};
+
+typedef struct {
+ NautilusSearchEngineSimple *engine;
+ GCancellable *cancellable;
+
+ GList *mime_types;
+ GList *found_list;
+
+ GQueue *directories; /* GFiles */
+
+ GHashTable *visited;
+
+ gboolean recursive;
+ gint n_processed_files;
+ GList *hits;
+
+ NautilusQuery *query;
+} SearchThreadData;
+
+
+struct NautilusSearchEngineSimpleDetails {
+ NautilusQuery *query;
+
+ SearchThreadData *active_search;
+
+ gboolean recursive;
+ gboolean query_finished;
+};
+
+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;
+
+ simple = NAUTILUS_SEARCH_ENGINE_SIMPLE (object);
+ g_clear_object (&simple->details->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 ();
+
+ return data;
+}
+
+static void
+search_thread_data_free (SearchThreadData *data)
+{
+ 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_list_free_full (data->mime_types, g_free);
+ g_list_free_full (data->hits, g_object_unref);
+ g_object_unref (data->engine);
+
+ g_free (data);
+}
+
+static gboolean
+search_thread_done_idle (gpointer user_data)
+{
+ SearchThreadData *data = user_data;
+ NautilusSearchEngineSimple *engine = data->engine;
+
+
+ if (g_cancellable_is_cancelled (data->cancellable)) {
+ DEBUG ("Simple engine finished and cancelled");
+ } else {
+ DEBUG ("Simple engine finished");
+ }
+ engine->details->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 FALSE;
+}
+
+typedef struct {
+ GList *hits;
+ SearchThreadData *thread_data;
+} SearchHitsData;
+
+
+static gboolean
+search_thread_add_hits_idle (gpointer user_data)
+{
+ SearchHitsData *data = user_data;
+
+ if (!g_cancellable_is_cancelled (data->thread_data->cancellable)) {
+ DEBUG ("Simple engine add hits");
+ nautilus_search_provider_hits_added (NAUTILUS_SEARCH_PROVIDER (data->thread_data->engine),
+ data->hits);
+ }
+
+ g_list_free_full (data->hits, g_object_unref);
+ g_free (data);
+
+ return FALSE;
+}
+
+static void
+send_batch (SearchThreadData *thread_data)
+{
+ SearchHitsData *data;
+
+ thread_data->n_processed_files = 0;
+
+ if (thread_data->hits) {
+ data = g_new (SearchHitsData, 1);
+ data->hits = thread_data->hits;
+ data->thread_data = thread_data;
+ g_idle_add (search_thread_add_hits_idle, data);
+ }
+ 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)
+{
+ GFileEnumerator *enumerator;
+ GFileInfo *info;
+ GFile *child;
+ const char *mime_type, *display_name;
+ gdouble match;
+ gboolean is_hidden, found;
+ GList *l;
+ const char *id;
+ gboolean visited;
+ guint64 atime;
+ guint64 mtime;
+ GPtrArray *date_range;
+ GDateTime *initial_date;
+ GDateTime *end_date;
+
+
+ enumerator = g_file_enumerate_children (dir,
+ data->mime_types != NULL ?
+ STD_ATTRIBUTES ","
+ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE
+ :
+ STD_ATTRIBUTES
+ ,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ data->cancellable, NULL);
+
+ if (enumerator == NULL) {
+ return;
+ }
+
+ 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) {
+ mime_type = g_file_info_get_content_type (info);
+ found = FALSE;
+
+ for (l = data->mime_types; mime_type != NULL && l != NULL; l = l->next) {
+ if (g_content_type_is_a (mime_type, l->data)) {
+ found = TRUE;
+ break;
+ }
+ }
+ }
+
+ mtime = g_file_info_get_attribute_uint64 (info, "time::modified");
+ atime = g_file_info_get_attribute_uint64 (info, "time::access");
+
+ date_range = nautilus_query_get_date_range (data->query);
+ if (found && date_range != NULL) {
+ NautilusQuerySearchType type;
+ guint64 current_file_time;
+
+ initial_date = g_ptr_array_index (date_range, 0);
+ end_date = g_ptr_array_index (date_range, 1);
+ type = nautilus_query_get_search_type (data->query);
+
+ 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);
+ g_ptr_array_unref (date_range);
+ }
+
+ if (found) {
+ NautilusSearchHit *hit;
+ GDateTime *date;
+ char *uri;
+
+ 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 (data);
+ }
+
+ if (data->engine->details->recursive && g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) {
+ 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 (data);
+ }
+
+ g_idle_add (search_thread_done_idle, 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->details->active_search != NULL) {
+ return;
+ }
+
+ DEBUG ("Simple engine start");
+
+ data = search_thread_data_new (simple, simple->details->query);
+
+ thread = g_thread_new ("nautilus-search-simple", search_thread_func, data);
+ simple->details->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;
+
+ simple = NAUTILUS_SEARCH_ENGINE_SIMPLE (provider);
+
+ if (simple->details->active_search != NULL) {
+ DEBUG ("Simple engine stop");
+ g_cancellable_cancel (simple->details->active_search->cancellable);
+ }
+}
+
+static void
+nautilus_search_engine_simple_set_query (NautilusSearchProvider *provider,
+ NautilusQuery *query)
+{
+ NautilusSearchEngineSimple *simple;
+
+ simple = NAUTILUS_SEARCH_ENGINE_SIMPLE (provider);
+
+ g_object_ref (query);
+ g_clear_object (&simple->details->query);
+ simple->details->query = query;
+}
+
+static gboolean
+nautilus_search_engine_simple_is_running (NautilusSearchProvider *provider)
+{
+ NautilusSearchEngineSimple *simple;
+
+ simple = NAUTILUS_SEARCH_ENGINE_SIMPLE (provider);
+
+ return simple->details->active_search != NULL;
+}
+
+static void
+nautilus_search_engine_simple_set_property (GObject *object,
+ guint arg_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusSearchEngineSimple *engine;
+
+ engine = NAUTILUS_SEARCH_ENGINE_SIMPLE (object);
+
+ switch (arg_id) {
+ case PROP_RECURSIVE:
+ engine->details->recursive = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, arg_id, pspec);
+ break;
+ }
+}
+
+static void
+nautilus_search_engine_simple_get_property (GObject *object,
+ guint arg_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusSearchEngineSimple *engine;
+
+ 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;
+ case PROP_RECURSIVE:
+ g_value_set_boolean (value, engine->details->recursive);
+ 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;
+ gobject_class->set_property = nautilus_search_engine_simple_set_property;
+
+ /**
+ * NautilusSearchEngineSimple::recursive:
+ *
+ * Whether the search is recursive or not.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_RECURSIVE,
+ g_param_spec_boolean ("recursive",
+ "recursive",
+ "recursive",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * NautilusSearchEngine::running:
+ *
+ * Whether the search engine is running a search.
+ */
+ g_object_class_override_property (gobject_class, PROP_RUNNING, "running");
+
+ g_type_class_add_private (class, sizeof (NautilusSearchEngineSimpleDetails));
+}
+
+static void
+nautilus_search_engine_simple_init (NautilusSearchEngineSimple *engine)
+{
+ engine->details = G_TYPE_INSTANCE_GET_PRIVATE (engine, NAUTILUS_TYPE_SEARCH_ENGINE_SIMPLE,
+ NautilusSearchEngineSimpleDetails);
+}
+
+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 000000000..b1f162f71
--- /dev/null
+++ b/src/nautilus-search-engine-simple.h
@@ -0,0 +1,47 @@
+/*
+ * 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>
+ *
+ */
+
+#ifndef NAUTILUS_SEARCH_ENGINE_SIMPLE_H
+#define NAUTILUS_SEARCH_ENGINE_SIMPLE_H
+
+#define NAUTILUS_TYPE_SEARCH_ENGINE_SIMPLE (nautilus_search_engine_simple_get_type ())
+#define NAUTILUS_SEARCH_ENGINE_SIMPLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_SEARCH_ENGINE_SIMPLE, NautilusSearchEngineSimple))
+#define NAUTILUS_SEARCH_ENGINE_SIMPLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_SEARCH_ENGINE_SIMPLE, NautilusSearchEngineSimpleClass))
+#define NAUTILUS_IS_SEARCH_ENGINE_SIMPLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_SEARCH_ENGINE_SIMPLE))
+#define NAUTILUS_IS_SEARCH_ENGINE_SIMPLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_SEARCH_ENGINE_SIMPLE))
+#define NAUTILUS_SEARCH_ENGINE_SIMPLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_SEARCH_ENGINE_SIMPLE, NautilusSearchEngineSimpleClass))
+
+typedef struct NautilusSearchEngineSimpleDetails NautilusSearchEngineSimpleDetails;
+
+typedef struct NautilusSearchEngineSimple {
+ GObject parent;
+ NautilusSearchEngineSimpleDetails *details;
+} NautilusSearchEngineSimple;
+
+typedef struct {
+ GObjectClass parent_class;
+} NautilusSearchEngineSimpleClass;
+
+GType nautilus_search_engine_simple_get_type (void);
+
+NautilusSearchEngineSimple* nautilus_search_engine_simple_new (void);
+
+#endif /* NAUTILUS_SEARCH_ENGINE_SIMPLE_H */
diff --git a/src/nautilus-search-engine-tracker.c b/src/nautilus-search-engine-tracker.c
new file mode 100644
index 000000000..680acf250
--- /dev/null
+++ b/src/nautilus-search-engine-tracker.c
@@ -0,0 +1,495 @@
+/*
+ * 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-global-preferences.h"
+#include "nautilus-search-hit.h"
+#include "nautilus-search-provider.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 NautilusSearchEngineTrackerDetails {
+ TrackerSparqlConnection *connection;
+ NautilusQuery *query;
+
+ gboolean query_pending;
+ GQueue *hits_pending;
+
+ gboolean recursive;
+
+ 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->details->cancellable) {
+ g_cancellable_cancel (tracker->details->cancellable);
+ g_clear_object (&tracker->details->cancellable);
+ }
+
+ g_clear_object (&tracker->details->query);
+ g_clear_object (&tracker->details->connection);
+ g_queue_free_full (tracker->details->hits_pending, g_object_unref);
+
+ 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->details->hits_pending) < BATCH_SIZE) {
+ return;
+ }
+
+ while ((hit = g_queue_pop_head (tracker->details->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->details->hits_pending, (GFunc) g_object_unref, NULL);
+ g_queue_clear (tracker->details->hits_pending);
+ }
+
+ tracker->details->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->details->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;
+ 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->details->query, basename);
+ nautilus_search_hit_set_fts_rank (hit, rank + match);
+ g_free (basename);
+
+ 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->details->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;
+}
+
+static void
+nautilus_search_engine_tracker_start (NautilusSearchProvider *provider)
+{
+ NautilusSearchEngineTracker *tracker;
+ gchar *query_text, *search_text, *location_uri, *downcase;
+ GFile *location;
+ GString *sparql;
+ GList *mimetypes, *l;
+ gint mime_count;
+ gboolean recursive;
+ GPtrArray *date_range;
+
+ tracker = NAUTILUS_SEARCH_ENGINE_TRACKER (provider);
+
+ if (tracker->details->query_pending) {
+ return;
+ }
+
+ DEBUG ("Tracker engine start");
+ g_object_ref (tracker);
+ tracker->details->query_pending = TRUE;
+
+ g_object_notify (G_OBJECT (provider), "running");
+
+ if (tracker->details->connection == NULL) {
+ g_idle_add (search_finished_idle, provider);
+ return;
+ }
+
+ recursive = g_settings_get_enum (nautilus_preferences, "recursive-search") == NAUTILUS_SPEED_TRADEOFF_LOCAL_ONLY ||
+ g_settings_get_enum (nautilus_preferences, "recursive-search") == NAUTILUS_SPEED_TRADEOFF_ALWAYS;
+ tracker->details->recursive = recursive;
+
+ query_text = nautilus_query_get_text (tracker->details->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->details->query);
+ location_uri = location ? g_file_get_uri (location) : NULL;
+ mimetypes = nautilus_query_get_mime_types (tracker->details->query);
+ mime_count = g_list_length (mimetypes);
+
+ sparql = g_string_new ("SELECT DISTINCT nie:url(?urn) fts:rank(?urn) nfo:fileLastModified(?urn) nfo:fileLastAccessed(?urn)\n"
+ "WHERE {"
+ " ?urn a nfo:FileDataObject;"
+ " nfo:fileLastModified ?mtime;"
+ " nfo:fileLastAccessed ?atime;"
+ " tracker:available true;");
+
+ g_string_append_printf (sparql, " fts:match '\"%s*\"'", search_text);
+
+ if (mime_count > 0) {
+ g_string_append (sparql, "; nie:mimeType ?mime");
+ }
+
+ g_string_append_printf (sparql, " . FILTER( ");
+
+ if (!tracker->details->recursive) {
+ g_string_append_printf (sparql, "tracker:uri-is-parent('%s', nie:url(?urn)) && ", location_uri);
+ } else {
+ g_string_append_printf (sparql, "tracker:uri-is-descendant('%s', nie:url(?urn)) && ", location_uri);
+ }
+
+ g_string_append_printf (sparql, "fn:contains(fn:lower-case(nfo:fileName(?urn)), '%s')", search_text);
+
+ date_range = nautilus_query_get_date_range (tracker->details->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->details->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 (mime_count > 0) {
+ g_string_append (sparql, " && (");
+
+ for (l = mimetypes; l != NULL; l = l->next) {
+ if (l != mimetypes) {
+ g_string_append (sparql, " || ");
+ }
+
+ g_string_append_printf (sparql, "fn:contains(?mime, '%s')",
+ (gchar *) l->data);
+ }
+ g_string_append (sparql, ")\n");
+ }
+
+ g_string_append (sparql, ")} ORDER BY DESC (fts:rank(?urn))");
+
+ tracker->details->cancellable = g_cancellable_new ();
+ tracker_sparql_connection_query_async (tracker->details->connection,
+ sparql->str,
+ tracker->details->cancellable,
+ query_callback,
+ tracker);
+ g_string_free (sparql, TRUE);
+
+ g_free (search_text);
+ g_free (location_uri);
+ g_list_free_full (mimetypes, g_free);
+ g_object_unref (location);
+}
+
+static void
+nautilus_search_engine_tracker_stop (NautilusSearchProvider *provider)
+{
+ NautilusSearchEngineTracker *tracker;
+
+ tracker = NAUTILUS_SEARCH_ENGINE_TRACKER (provider);
+
+ if (tracker->details->query_pending) {
+ DEBUG ("Tracker engine stop");
+ g_cancellable_cancel (tracker->details->cancellable);
+ g_clear_object (&tracker->details->cancellable);
+ tracker->details->query_pending = FALSE;
+
+ g_object_notify (G_OBJECT (provider), "running");
+ }
+}
+
+static void
+nautilus_search_engine_tracker_set_query (NautilusSearchProvider *provider,
+ NautilusQuery *query)
+{
+ NautilusSearchEngineTracker *tracker;
+
+ tracker = NAUTILUS_SEARCH_ENGINE_TRACKER (provider);
+
+ g_object_ref (query);
+ g_clear_object (&tracker->details->query);
+ tracker->details->query = query;
+}
+
+static gboolean
+nautilus_search_engine_tracker_is_running (NautilusSearchProvider *provider)
+{
+ NautilusSearchEngineTracker *tracker;
+
+ tracker = NAUTILUS_SEARCH_ENGINE_TRACKER (provider);
+
+ return tracker->details->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");
+
+ g_type_class_add_private (class, sizeof (NautilusSearchEngineTrackerDetails));
+}
+
+static void
+nautilus_search_engine_tracker_init (NautilusSearchEngineTracker *engine)
+{
+ GError *error = NULL;
+
+ engine->details = G_TYPE_INSTANCE_GET_PRIVATE (engine, NAUTILUS_TYPE_SEARCH_ENGINE_TRACKER,
+ NautilusSearchEngineTrackerDetails);
+ engine->details->hits_pending = g_queue_new ();
+
+ engine->details->connection = tracker_sparql_connection_get (NULL, &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 000000000..a196b5a51
--- /dev/null
+++ b/src/nautilus-search-engine-tracker.h
@@ -0,0 +1,49 @@
+/*
+ * 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)
+ *
+ */
+
+#ifndef NAUTILUS_SEARCH_ENGINE_TRACKER_H
+#define NAUTILUS_SEARCH_ENGINE_TRACKER_H
+
+#include "nautilus-search-engine.h"
+
+#define NAUTILUS_TYPE_SEARCH_ENGINE_TRACKER (nautilus_search_engine_tracker_get_type ())
+#define NAUTILUS_SEARCH_ENGINE_TRACKER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_SEARCH_ENGINE_TRACKER, NautilusSearchEngineTracker))
+#define NAUTILUS_SEARCH_ENGINE_TRACKER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_SEARCH_ENGINE_TRACKER, NautilusSearchEngineTrackerClass))
+#define NAUTILUS_IS_SEARCH_ENGINE_TRACKER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_SEARCH_ENGINE_TRACKER))
+#define NAUTILUS_IS_SEARCH_ENGINE_TRACKER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_SEARCH_ENGINE_TRACKER))
+#define NAUTILUS_SEARCH_ENGINE_TRACKER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_SEARCH_ENGINE_TRACKER, NautilusSearchEngineTrackerClass))
+
+typedef struct NautilusSearchEngineTrackerDetails NautilusSearchEngineTrackerDetails;
+
+typedef struct NautilusSearchEngineTracker {
+ GObject parent;
+ NautilusSearchEngineTrackerDetails *details;
+} NautilusSearchEngineTracker;
+
+typedef struct {
+ GObjectClass parent_class;
+} NautilusSearchEngineTrackerClass;
+
+GType nautilus_search_engine_tracker_get_type (void);
+
+NautilusSearchEngineTracker* nautilus_search_engine_tracker_new (void);
+
+#endif /* NAUTILUS_SEARCH_ENGINE_TRACKER_H */
diff --git a/src/nautilus-search-engine.c b/src/nautilus-search-engine.c
new file mode 100644
index 000000000..79404f0a1
--- /dev/null
+++ b/src/nautilus-search-engine.c
@@ -0,0 +1,376 @@
+/*
+ * 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 <glib/gi18n.h>
+#include "nautilus-search-provider.h"
+#include "nautilus-search-engine.h"
+#include "nautilus-search-engine-simple.h"
+#include "nautilus-search-engine-model.h"
+#define DEBUG_FLAG NAUTILUS_DEBUG_SEARCH
+#include "nautilus-debug.h"
+
+#ifdef ENABLE_TRACKER
+#include "nautilus-search-engine-tracker.h"
+#endif
+
+struct NautilusSearchEngineDetails
+{
+#ifdef ENABLE_TRACKER
+ NautilusSearchEngineTracker *tracker;
+#endif
+ NautilusSearchEngineSimple *simple;
+ NautilusSearchEngineModel *model;
+
+ GHashTable *uris;
+ guint providers_running;
+ guint providers_finished;
+ guint providers_error;
+
+ gboolean running;
+ gboolean restart;
+};
+
+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_IMPLEMENT_INTERFACE (NAUTILUS_TYPE_SEARCH_PROVIDER,
+ nautilus_search_provider_init))
+
+static void
+nautilus_search_engine_set_query (NautilusSearchProvider *provider,
+ NautilusQuery *query)
+{
+ NautilusSearchEngine *engine = NAUTILUS_SEARCH_ENGINE (provider);
+#ifdef ENABLE_TRACKER
+ nautilus_search_provider_set_query (NAUTILUS_SEARCH_PROVIDER (engine->details->tracker), query);
+#endif
+ nautilus_search_provider_set_query (NAUTILUS_SEARCH_PROVIDER (engine->details->model), query);
+ nautilus_search_provider_set_query (NAUTILUS_SEARCH_PROVIDER (engine->details->simple), query);
+}
+
+static void
+search_engine_start_real (NautilusSearchEngine *engine)
+{
+ engine->details->providers_running = 0;
+ engine->details->providers_finished = 0;
+ engine->details->providers_error = 0;
+
+ engine->details->restart = FALSE;
+
+ DEBUG ("Search engine start real");
+
+ g_object_ref (engine);
+
+#ifdef ENABLE_TRACKER
+ nautilus_search_provider_start (NAUTILUS_SEARCH_PROVIDER (engine->details->tracker));
+ engine->details->providers_running++;
+#endif
+ if (nautilus_search_engine_model_get_model (engine->details->model)) {
+ nautilus_search_provider_start (NAUTILUS_SEARCH_PROVIDER (engine->details->model));
+ engine->details->providers_running++;
+ }
+
+ nautilus_search_provider_start (NAUTILUS_SEARCH_PROVIDER (engine->details->simple));
+ engine->details->providers_running++;
+}
+
+static void
+nautilus_search_engine_start (NautilusSearchProvider *provider)
+{
+ NautilusSearchEngine *engine = NAUTILUS_SEARCH_ENGINE (provider);
+ gint num_finished;
+
+ DEBUG ("Search engine start");
+
+ num_finished = engine->details->providers_error + engine->details->providers_finished;
+
+ if (engine->details->running) {
+ if (num_finished == engine->details->providers_running &&
+ engine->details->restart) {
+ search_engine_start_real (engine);
+ }
+
+ return;
+ }
+
+ engine->details->running = TRUE;
+
+ g_object_notify (G_OBJECT (provider), "running");
+
+ if (num_finished < engine->details->providers_running) {
+ engine->details->restart = TRUE;
+ } else {
+ search_engine_start_real (engine);
+ }
+}
+
+static void
+nautilus_search_engine_stop (NautilusSearchProvider *provider)
+{
+ NautilusSearchEngine *engine = NAUTILUS_SEARCH_ENGINE (provider);
+
+ DEBUG ("Search engine stop");
+
+#ifdef ENABLE_TRACKER
+ nautilus_search_provider_stop (NAUTILUS_SEARCH_PROVIDER (engine->details->tracker));
+#endif
+ nautilus_search_provider_stop (NAUTILUS_SEARCH_PROVIDER (engine->details->model));
+ nautilus_search_provider_stop (NAUTILUS_SEARCH_PROVIDER (engine->details->simple));
+
+ engine->details->running = FALSE;
+ engine->details->restart = FALSE;
+
+ g_object_notify (G_OBJECT (provider), "running");
+}
+
+static void
+search_provider_hits_added (NautilusSearchProvider *provider,
+ GList *hits,
+ NautilusSearchEngine *engine)
+{
+ GList *added = NULL;
+ GList *l;
+
+ if (!engine->details->running || engine->details->restart) {
+ DEBUG ("Ignoring hits-added, since engine is %s",
+ !engine->details->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 (engine->details->uris, uri));
+ if (count == 0)
+ added = g_list_prepend (added, hit);
+ g_hash_table_replace (engine->details->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)
+{
+ gint num_finished = engine->details->providers_error + engine->details->providers_finished;
+
+ if (num_finished < engine->details->providers_running) {
+ return;
+ }
+
+ if (num_finished == engine->details->providers_error) {
+ DEBUG ("Search engine error");
+ nautilus_search_provider_error (NAUTILUS_SEARCH_PROVIDER (engine),
+ _("Unable to complete the requested search"));
+ } else {
+ if (engine->details->restart) {
+ DEBUG ("Search engine finished and restarting");
+ } else {
+ DEBUG ("Search engine finished");
+ }
+ nautilus_search_provider_finished (NAUTILUS_SEARCH_PROVIDER (engine),
+ engine->details->restart ? NAUTILUS_SEARCH_PROVIDER_STATUS_RESTARTING :
+ NAUTILUS_SEARCH_PROVIDER_STATUS_NORMAL);
+ }
+
+ engine->details->running = FALSE;
+ g_object_notify (G_OBJECT (engine), "running");
+
+ g_hash_table_remove_all (engine->details->uris);
+
+ if (engine->details->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)
+
+{
+ DEBUG ("Search provider error: %s", error_message);
+ engine->details->providers_error++;
+
+ check_providers_status (engine);
+}
+
+static void
+search_provider_finished (NautilusSearchProvider *provider,
+ NautilusSearchProviderStatus status,
+ NautilusSearchEngine *engine)
+
+{
+ DEBUG ("Search provider finished");
+ engine->details->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;
+
+ engine = NAUTILUS_SEARCH_ENGINE (provider);
+
+ return engine->details->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 = NAUTILUS_SEARCH_ENGINE (object);
+
+ g_hash_table_destroy (engine->details->uris);
+
+#ifdef ENABLE_TRACKER
+ g_clear_object (&engine->details->tracker);
+#endif
+ g_clear_object (&engine->details->model);
+ g_clear_object (&engine->details->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");
+
+ g_type_class_add_private (class, sizeof (NautilusSearchEngineDetails));
+}
+
+static void
+nautilus_search_engine_init (NautilusSearchEngine *engine)
+{
+ engine->details = G_TYPE_INSTANCE_GET_PRIVATE (engine,
+ NAUTILUS_TYPE_SEARCH_ENGINE,
+ NautilusSearchEngineDetails);
+
+ engine->details->uris = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+#ifdef ENABLE_TRACKER
+ engine->details->tracker = nautilus_search_engine_tracker_new ();
+ connect_provider_signals (engine, NAUTILUS_SEARCH_PROVIDER (engine->details->tracker));
+#endif
+ engine->details->model = nautilus_search_engine_model_new ();
+ connect_provider_signals (engine, NAUTILUS_SEARCH_PROVIDER (engine->details->model));
+
+ engine->details->simple = nautilus_search_engine_simple_new ();
+ connect_provider_signals (engine, NAUTILUS_SEARCH_PROVIDER (engine->details->simple));
+}
+
+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)
+{
+ return engine->details->model;
+}
+
+NautilusSearchEngineSimple *
+nautilus_search_engine_get_simple_provider (NautilusSearchEngine *engine)
+{
+ return engine->details->simple;
+}
diff --git a/src/nautilus-search-engine.h b/src/nautilus-search-engine.h
new file mode 100644
index 000000000..7018da5fa
--- /dev/null
+++ b/src/nautilus-search-engine.h
@@ -0,0 +1,57 @@
+/*
+ * 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>
+ *
+ */
+
+#ifndef NAUTILUS_SEARCH_ENGINE_H
+#define NAUTILUS_SEARCH_ENGINE_H
+
+#include <glib-object.h>
+
+#include "nautilus-directory.h"
+#include "nautilus-search-engine-model.h"
+#include "nautilus-search-engine-simple.h"
+
+#define NAUTILUS_TYPE_SEARCH_ENGINE (nautilus_search_engine_get_type ())
+#define NAUTILUS_SEARCH_ENGINE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_SEARCH_ENGINE, NautilusSearchEngine))
+#define NAUTILUS_SEARCH_ENGINE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_SEARCH_ENGINE, NautilusSearchEngineClass))
+#define NAUTILUS_IS_SEARCH_ENGINE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_SEARCH_ENGINE))
+#define NAUTILUS_IS_SEARCH_ENGINE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_SEARCH_ENGINE))
+#define NAUTILUS_SEARCH_ENGINE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_SEARCH_ENGINE, NautilusSearchEngineClass))
+
+typedef struct NautilusSearchEngineDetails NautilusSearchEngineDetails;
+
+typedef struct NautilusSearchEngine {
+ GObject parent;
+ NautilusSearchEngineDetails *details;
+} NautilusSearchEngine;
+
+typedef struct {
+ GObjectClass parent_class;
+} NautilusSearchEngineClass;
+
+GType nautilus_search_engine_get_type (void);
+
+NautilusSearchEngine *nautilus_search_engine_new (void);
+NautilusSearchEngineModel *
+ nautilus_search_engine_get_model_provider (NautilusSearchEngine *engine);
+NautilusSearchEngineSimple *
+ nautilus_search_engine_get_simple_provider (NautilusSearchEngine *engine);
+
+#endif /* NAUTILUS_SEARCH_ENGINE_H */
diff --git a/src/nautilus-search-hit.c b/src/nautilus-search-hit.c
new file mode 100644
index 000000000..5658872c8
--- /dev/null
+++ b/src/nautilus-search-hit.c
@@ -0,0 +1,327 @@
+/*
+ * 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 NautilusSearchHitDetails
+{
+ char *uri;
+
+ GDateTime *modification_time;
+ GDateTime *access_time;
+ gdouble fts_rank;
+
+ gdouble relevance;
+};
+
+enum {
+ PROP_URI = 1,
+ PROP_RELEVANCE,
+ PROP_MODIFICATION_TIME,
+ PROP_ACCESS_TIME,
+ PROP_FTS_RANK,
+ 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->details->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->details->modification_time != NULL)
+ m_diff = g_date_time_difference (now, hit->details->modification_time);
+ if (hit->details->access_time != NULL)
+ a_diff = g_date_time_difference (now, hit->details->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->details->fts_rank > 0) {
+ match_bonus = MIN (500, 10.0 * hit->details->fts_rank);
+ } else {
+ match_bonus = 0.0;
+ }
+
+ hit->details->relevance = recent_bonus + proximity_bonus + match_bonus;
+ DEBUG ("Hit %s computed relevance %.2f (%.2f + %.2f + %.2f)", hit->details->uri, hit->details->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->details->uri;
+}
+
+gdouble
+nautilus_search_hit_get_relevance (NautilusSearchHit *hit)
+{
+ return hit->details->relevance;
+}
+
+static void
+nautilus_search_hit_set_uri (NautilusSearchHit *hit,
+ const char *uri)
+{
+ g_free (hit->details->uri);
+ hit->details->uri = g_strdup (uri);
+}
+
+void
+nautilus_search_hit_set_fts_rank (NautilusSearchHit *hit,
+ gdouble rank)
+{
+ hit->details->fts_rank = rank;
+}
+
+void
+nautilus_search_hit_set_modification_time (NautilusSearchHit *hit,
+ GDateTime *date)
+{
+ if (hit->details->modification_time != NULL)
+ g_date_time_unref (hit->details->modification_time);
+ if (date != NULL)
+ hit->details->modification_time = g_date_time_ref (date);
+ else
+ hit->details->modification_time = NULL;
+}
+
+void
+nautilus_search_hit_set_access_time (NautilusSearchHit *hit,
+ GDateTime *date)
+{
+ if (hit->details->access_time != NULL)
+ g_date_time_unref (hit->details->access_time);
+ if (date != NULL)
+ hit->details->access_time = g_date_time_ref (date);
+ else
+ hit->details->access_time = NULL;
+}
+
+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->details->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;
+ 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->details->relevance);
+ break;
+ case PROP_FTS_RANK:
+ g_value_set_double (value, hit->details->fts_rank);
+ break;
+ case PROP_URI:
+ g_value_set_string (value, hit->details->uri);
+ break;
+ case PROP_MODIFICATION_TIME:
+ g_value_set_boxed (value, hit->details->modification_time);
+ break;
+ case PROP_ACCESS_TIME:
+ g_value_set_boxed (value, hit->details->access_time);
+ 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->details->uri);
+
+ if (hit->details->access_time != NULL) {
+ g_date_time_unref (hit->details->access_time);
+ }
+ if (hit->details->modification_time != NULL) {
+ g_date_time_unref (hit->details->modification_time);
+ }
+
+ 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_type_class_add_private (class, sizeof (NautilusSearchHitDetails));
+}
+
+static void
+nautilus_search_hit_init (NautilusSearchHit *hit)
+{
+ hit->details = G_TYPE_INSTANCE_GET_PRIVATE (hit,
+ NAUTILUS_TYPE_SEARCH_HIT,
+ NautilusSearchHitDetails);
+}
+
+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 000000000..d77040092
--- /dev/null
+++ b/src/nautilus-search-hit.h
@@ -0,0 +1,61 @@
+/*
+ * 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/>.
+ *
+ */
+
+#ifndef NAUTILUS_SEARCH_HIT_H
+#define NAUTILUS_SEARCH_HIT_H
+
+#include <glib-object.h>
+#include "nautilus-query.h"
+
+#define NAUTILUS_TYPE_SEARCH_HIT (nautilus_search_hit_get_type ())
+#define NAUTILUS_SEARCH_HIT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_SEARCH_HIT, NautilusSearchHit))
+#define NAUTILUS_SEARCH_HIT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_SEARCH_HIT, NautilusSearchHitClass))
+#define NAUTILUS_IS_SEARCH_HIT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_SEARCH_HIT))
+#define NAUTILUS_IS_SEARCH_HIT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_SEARCH_HIT))
+#define NAUTILUS_SEARCH_HIT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_SEARCH_HIT, NautilusSearchHitClass))
+
+typedef struct NautilusSearchHitDetails NautilusSearchHitDetails;
+
+typedef struct NautilusSearchHit {
+ GObject parent;
+ NautilusSearchHitDetails *details;
+} NautilusSearchHit;
+
+typedef struct {
+ GObjectClass parent_class;
+} NautilusSearchHitClass;
+
+GType nautilus_search_hit_get_type (void);
+
+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_compute_scores (NautilusSearchHit *hit,
+ NautilusQuery *query);
+
+const char * nautilus_search_hit_get_uri (NautilusSearchHit *hit);
+gdouble nautilus_search_hit_get_relevance (NautilusSearchHit *hit);
+
+#endif /* NAUTILUS_SEARCH_HIT_H */
diff --git a/src/nautilus-search-popover.c b/src/nautilus-search-popover.c
index 6369c522e..15e3a239e 100644
--- a/src/nautilus-search-popover.c
+++ b/src/nautilus-search-popover.c
@@ -21,9 +21,9 @@
#include "nautilus-mime-actions.h"
#include <glib/gi18n.h>
-#include <libnautilus-private/nautilus-file.h>
-#include <libnautilus-private/nautilus-ui-utilities.h>
-#include <libnautilus-private/nautilus-global-preferences.h>
+#include "nautilus-file.h"
+#include "nautilus-ui-utilities.h"
+#include "nautilus-global-preferences.h"
#define SEARCH_FILTER_MAX_YEARS 5
diff --git a/src/nautilus-search-popover.h b/src/nautilus-search-popover.h
index 6c80fc68f..1d872f1ac 100644
--- a/src/nautilus-search-popover.h
+++ b/src/nautilus-search-popover.h
@@ -22,7 +22,7 @@
#include <glib.h>
#include <gtk/gtk.h>
-#include <libnautilus-private/nautilus-query.h>
+#include "nautilus-query.h"
G_BEGIN_DECLS
diff --git a/src/nautilus-search-provider.c b/src/nautilus-search-provider.c
new file mode 100644
index 000000000..ccbd7854d
--- /dev/null
+++ b/src/nautilus-search-provider.c
@@ -0,0 +1,142 @@
+/*
+ * 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.c"
+
+#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 000000000..d019c6690
--- /dev/null
+++ b/src/nautilus-search-provider.h
@@ -0,0 +1,97 @@
+/*
+ * 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/>.
+ */
+
+#ifndef NAUTILUS_SEARCH_PROVIDER_H
+#define NAUTILUS_SEARCH_PROVIDER_H
+
+#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;
+
+
+#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
+
+#endif
diff --git a/src/nautilus-selection-canvas-item.c b/src/nautilus-selection-canvas-item.c
new file mode 100644
index 000000000..c1619ca8d
--- /dev/null
+++ b/src/nautilus-selection-canvas-item.c
@@ -0,0 +1,712 @@
+
+/* 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,
+ PROP_FILL_COLOR_RGBA,
+ PROP_OUTLINE_COLOR_RGBA,
+ PROP_OUTLINE_STIPPLING,
+ PROP_WIDTH_PIXELS,
+ 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 */
+ double width; /* Outline width */
+
+ GdkRGBA fill_color;
+ GdkRGBA outline_color;
+
+ gboolean outline_stippling;
+
+ /* Configuration flags */
+
+ unsigned int fill_set : 1; /* Is fill color set? */
+ unsigned int outline_set : 1; /* Is outline color set? */
+
+ double fade_out_fill_alpha;
+ double fade_out_outline_alpha;
+
+ gint64 fade_out_start_time;
+ gint64 fade_out_end_time;
+
+ guint fade_out_tick_id;
+};
+
+G_DEFINE_TYPE (NautilusSelectionCanvasItem, nautilus_selection_canvas_item, EEL_TYPE_CANVAS_ITEM);
+
+#define DASH_ON 0.8
+#define DASH_OFF 1.7
+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;
+
+ 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;
+ }
+
+ cairo_save (cr);
+
+ if (self->priv->fill_set) {
+ GdkRGBA actual_fill;
+
+ actual_fill = self->priv->fill_color;
+
+ if (self->priv->fade_out_tick_id != 0) {
+ actual_fill.alpha = self->priv->fade_out_fill_alpha;
+ }
+
+ gdk_cairo_set_source_rgba (cr, &actual_fill);
+ cairo_rectangle (cr,
+ cx1, cy1,
+ cx2 - cx1 + 1,
+ cy2 - cy1 + 1);
+ cairo_fill (cr);
+ }
+
+ if (self->priv->outline_set) {
+ GdkRGBA actual_outline;
+
+ actual_outline = self->priv->outline_color;
+
+ if (self->priv->fade_out_tick_id != 0) {
+ actual_outline.alpha = self->priv->fade_out_outline_alpha;
+ }
+
+ gdk_cairo_set_source_rgba (cr, &actual_outline);
+ cairo_set_line_width (cr, (int) self->priv->width);
+
+ if (self->priv->outline_stippling) {
+ double dash[2] = { DASH_ON, DASH_OFF };
+
+ cairo_set_dash (cr, dash, G_N_ELEMENTS (dash), 0);
+ }
+
+ cairo_rectangle (cr,
+ cx1 + 0.5, cy1 + 0.5,
+ cx2 - cx1,
+ cy2 - cy1);
+ cairo_stroke (cr);
+ }
+
+ cairo_restore (cr);
+}
+
+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;
+ double tmp;
+
+ 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;
+
+ if (self->priv->outline_set) {
+ hwidth = (self->priv->width / item->canvas->pixels_per_unit) / 2.0;
+
+ x1 -= hwidth;
+ y1 -= hwidth;
+ x2 += hwidth;
+ y2 += hwidth;
+ } else
+ hwidth = 0.0;
+
+ /* Is point inside rectangle (which can be hollow if it has no fill set)? */
+
+ if ((x >= x1) && (y >= y1) && (x <= x2) && (y <= y2)) {
+ if (self->priv->fill_set || !self->priv->outline_set)
+ return 0.0;
+
+ dx = x - x1;
+ tmp = x2 - x;
+ if (tmp < dx)
+ dx = tmp;
+
+ dy = y - y1;
+ tmp = y2 - y;
+ if (tmp < dy)
+ dy = tmp;
+
+ if (dy < dx)
+ dx = dy;
+
+ dx -= 2.0 * hwidth;
+
+ if (dx < 0.0)
+ return 0.0;
+ else
+ return dx;
+ }
+
+ /* 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)
+{
+ eel_canvas_request_redraw (canvas,
+ update_rect->x0, update_rect->y0,
+ update_rect->x1, update_rect->y0 + width);
+ eel_canvas_request_redraw (canvas,
+ update_rect->x0, update_rect->y1-width,
+ update_rect->x1, update_rect->y1);
+ eel_canvas_request_redraw (canvas,
+ update_rect->x0, update_rect->y0,
+ update_rect->x0+width, update_rect->y1);
+ 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;
+ int width_pixels;
+ int width_lt, width_rb;
+ 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;
+
+ if (priv->outline_set) {
+ /* Outline and bounding box */
+ width_pixels = (int) priv->width;
+ width_lt = width_pixels / 2;
+ width_rb = (width_pixels + 1) / 2;
+
+ cx1 -= width_lt;
+ cy1 -= width_lt;
+ cx2 += width_rb;
+ cy2 += width_rb;
+
+ update_rect = make_rect (cx1, cy1, cx2, cy2);
+ request_redraw_borders (item->canvas, &update_rect,
+ (width_lt + width_rb));
+ 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 = width_lt + width_rb;
+
+ item->x1 = cx1;
+ item->y1 = cy1;
+ item->x2 = cx2+1;
+ item->y2 = cy2+1;
+ } else {
+ item->x1 = cx1;
+ item->y1 = cy1;
+ item->x2 = cx2+1;
+ item->y2 = cy2+1;
+ }
+}
+
+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;
+ double hwidth;
+
+ self = NAUTILUS_SELECTION_CANVAS_ITEM (item);
+
+ hwidth = (self->priv->width / item->canvas->pixels_per_unit) / 2.0;
+
+ *x1 = self->priv->x1 - hwidth;
+ *y1 = self->priv->y1 - hwidth;
+ *x2 = self->priv->x2 + hwidth;
+ *y2 = self->priv->y2 + hwidth;
+}
+
+static gboolean
+fade_and_request_redraw (GtkWidget *canvas,
+ GdkFrameClock *frame_clock,
+ gpointer user_data)
+{
+ NautilusSelectionCanvasItem *self = user_data;
+ gint64 frame_time;
+ gdouble percentage;
+
+ frame_time = gdk_frame_clock_get_frame_time (frame_clock);
+ if (frame_time >= self->priv->fade_out_end_time) {
+ self->priv->fade_out_tick_id = 0;
+ eel_canvas_item_destroy (EEL_CANVAS_ITEM (self));
+
+ return G_SOURCE_REMOVE;
+ }
+
+ percentage = 1.0 - (gdouble) (frame_time - self->priv->fade_out_start_time) /
+ (gdouble) (self->priv->fade_out_end_time - self->priv->fade_out_start_time);
+
+ self->priv->fade_out_fill_alpha = self->priv->fill_color.alpha * percentage;
+ self->priv->fade_out_outline_alpha = self->priv->outline_color.alpha * percentage;
+
+ eel_canvas_item_request_redraw (EEL_CANVAS_ITEM (self));
+
+ return G_SOURCE_CONTINUE;
+}
+
+void
+nautilus_selection_canvas_item_fade_out (NautilusSelectionCanvasItem *self,
+ guint transition_time)
+{
+ EelCanvasItem *item = EEL_CANVAS_ITEM (self);
+ GtkWidget *widget;
+ GdkFrameClock *clock;
+
+ self->priv->fade_out_fill_alpha = self->priv->fill_color.alpha;
+ self->priv->fade_out_outline_alpha = self->priv->outline_color.alpha;
+
+ widget = GTK_WIDGET (item->canvas);
+ clock = gtk_widget_get_frame_clock (widget);
+ self->priv->fade_out_start_time = gdk_frame_clock_get_frame_time (clock);
+ self->priv->fade_out_end_time = self->priv->fade_out_start_time + 1000 * transition_time;
+
+ self->priv->fade_out_tick_id =
+ gtk_widget_add_tick_callback (GTK_WIDGET (item->canvas), fade_and_request_redraw, self, NULL);
+}
+
+static void
+nautilus_selection_canvas_item_dispose (GObject *obj)
+{
+ NautilusSelectionCanvasItem *self = NAUTILUS_SELECTION_CANVAS_ITEM (obj);
+
+ if (self->priv->fade_out_tick_id != 0) {
+ gtk_widget_remove_tick_callback (GTK_WIDGET (EEL_CANVAS_ITEM (self)->canvas), self->priv->fade_out_tick_id);
+ self->priv->fade_out_tick_id = 0;
+ }
+
+ G_OBJECT_CLASS (nautilus_selection_canvas_item_parent_class)->dispose (obj);
+}
+
+static void
+do_set_fill (NautilusSelectionCanvasItem *self,
+ gboolean fill_set)
+{
+ if (self->priv->fill_set != fill_set) {
+ self->priv->fill_set = fill_set;
+ eel_canvas_item_request_update (EEL_CANVAS_ITEM (self));
+ }
+}
+
+static void
+do_set_outline (NautilusSelectionCanvasItem *self,
+ gboolean outline_set)
+{
+ if (self->priv->outline_set != outline_set) {
+ self->priv->outline_set = outline_set;
+ eel_canvas_item_request_update (EEL_CANVAS_ITEM (self));
+ }
+}
+
+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;
+
+ case PROP_FILL_COLOR_RGBA: {
+ GdkRGBA *color;
+
+ color = g_value_get_boxed (value);
+
+ do_set_fill (self, color != NULL);
+
+ if (color != NULL) {
+ self->priv->fill_color = *color;
+ }
+
+ eel_canvas_item_request_redraw (item);
+ break;
+ }
+
+ case PROP_OUTLINE_COLOR_RGBA: {
+ GdkRGBA *color;
+
+ color = g_value_get_boxed (value);
+
+ do_set_outline (self, color != NULL);
+
+ if (color != NULL) {
+ self->priv->outline_color = *color;
+ }
+
+ eel_canvas_item_request_redraw (item);
+ break;
+ }
+
+ case PROP_OUTLINE_STIPPLING:
+ self->priv->outline_stippling = g_value_get_boolean (value);
+
+ eel_canvas_item_request_redraw (item);
+ break;
+
+ case PROP_WIDTH_PIXELS:
+ self->priv->width = g_value_get_uint (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;
+
+ case PROP_FILL_COLOR_RGBA:
+ g_value_set_boxed (value, &self->priv->fill_color);
+ break;
+
+ case PROP_OUTLINE_COLOR_RGBA:
+ g_value_set_boxed (value, &self->priv->outline_color);
+ break;
+
+ case PROP_OUTLINE_STIPPLING:
+ g_value_set_boolean (value, self->priv->outline_stippling);
+ break;
+ case PROP_WIDTH_PIXELS:
+ g_value_set_uint (value, self->priv->width);
+ 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;
+ gobject_class->dispose = nautilus_selection_canvas_item_dispose;
+
+ 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);
+ properties[PROP_FILL_COLOR_RGBA] =
+ g_param_spec_boxed ("fill-color-rgba", NULL, NULL,
+ GDK_TYPE_RGBA,
+ G_PARAM_READWRITE);
+ properties[PROP_OUTLINE_COLOR_RGBA] =
+ g_param_spec_boxed ("outline-color-rgba", NULL, NULL,
+ GDK_TYPE_RGBA,
+ G_PARAM_READWRITE);
+ properties[PROP_OUTLINE_STIPPLING] =
+ g_param_spec_boolean ("outline-stippling", NULL, NULL,
+ FALSE, G_PARAM_READWRITE);
+ properties[PROP_WIDTH_PIXELS] =
+ g_param_spec_uint ("width-pixels", NULL, NULL,
+ 0, G_MAXUINT, 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 000000000..c5c2778dd
--- /dev/null
+++ b/src/nautilus-selection-canvas-item.h
@@ -0,0 +1,65 @@
+
+/* 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>
+ */
+
+#ifndef __NAUTILUS_SELECTION_CANVAS_ITEM_H__
+#define __NAUTILUS_SELECTION_CANVAS_ITEM_H__
+
+#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
+
+#endif /* __NAUTILUS_SELECTION_CANVAS_ITEM_H__ */
diff --git a/src/nautilus-shell-search-provider.c b/src/nautilus-shell-search-provider.c
index dd91c29b6..41ee0d9ac 100644
--- a/src/nautilus-shell-search-provider.c
+++ b/src/nautilus-shell-search-provider.c
@@ -28,11 +28,11 @@
#include <glib/gi18n.h>
#include <gdk/gdkx.h>
-#include <libnautilus-private/nautilus-file.h>
-#include <libnautilus-private/nautilus-file-utilities.h>
-#include <libnautilus-private/nautilus-search-engine.h>
-#include <libnautilus-private/nautilus-search-provider.h>
-#include <libnautilus-private/nautilus-ui-utilities.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"
diff --git a/src/nautilus-signaller.c b/src/nautilus-signaller.c
new file mode 100644
index 000000000..b35877266
--- /dev/null
+++ b/src/nautilus-signaller.c
@@ -0,0 +1,92 @@
+
+/*
+ * 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 000000000..fee17195a
--- /dev/null
+++ b/src/nautilus-signaller.h
@@ -0,0 +1,44 @@
+
+/*
+ * 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.
+ */
+
+#ifndef NAUTILUS_SIGNALLER_H
+#define NAUTILUS_SIGNALLER_H
+
+#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);
+
+#endif /* NAUTILUS_SIGNALLER_H */
diff --git a/src/nautilus-thumbnails.c b/src/nautilus-thumbnails.c
new file mode 100644
index 000000000..5295df47e
--- /dev/null
+++ b/src/nautilus-thumbnails.c
@@ -0,0 +1,569 @@
+/*
+ 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>
+
+#include "nautilus-file-private.h"
+
+/* turn this on to see messages about thumbnail creation */
+#if 0
+#define DEBUG_THUMBNAILS
+#endif
+
+/* 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 GnomeDesktopThumbnailFactory *thumbnail_factory = 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;
+
+ /* Don't do this in thread, since g_object_ref is not threadsafe */
+ if (thumbnail_factory == NULL) {
+ thumbnail_factory = get_thumbnail_factory ();
+ }
+
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Main Thread) Creating thumbnails thread\n");
+#endif
+ /* 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;
+
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Remove from queue) Locking mutex\n");
+#endif
+ 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
+ *********************************/
+
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Remove from queue) Unlocking mutex\n");
+#endif
+ g_mutex_unlock (&thumbnails_mutex);
+}
+
+void
+nautilus_thumbnail_prioritize (const char *file_uri)
+{
+ GList *node;
+
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Prioritize) Locking mutex\n");
+#endif
+ 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
+ *********************************/
+
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Prioritize) Unlocking mutex\n");
+#endif
+ 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);
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Thumbnail Thread) Notifying file changed file:%p uri: %s\n", file, (char*) image_uri);
+#endif
+
+ 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_can_thumbnail_internally (NautilusFile *file)
+{
+ char *mime_type;
+ gboolean res;
+
+ mime_type = nautilus_file_get_mime_type (file);
+ res = pixbuf_can_load_type (mime_type);
+ g_free (mime_type);
+ return res;
+}
+
+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;
+
+
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Main Thread) Locking mutex\n");
+#endif
+ 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. */
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Main Thread) Adding thumbnail: %s\n",
+ info->image_uri);
+#endif
+ 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 {
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Main Thread) Updating non-current mtime: %s\n",
+ info->image_uri);
+#endif
+ /* 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
+ *********************************/
+
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Main Thread) Unlocking mutex\n");
+#endif
+ 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)
+{
+ NautilusThumbnailInfo *info = NULL;
+ GdkPixbuf *pixbuf;
+ time_t current_orig_mtime = 0;
+ time_t current_time;
+ GList *node;
+
+ /* We loop until there are no more thumbails to make, at which point
+ we exit the thread. */
+ for (;;) {
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Thumbnail Thread) Locking mutex\n");
+#endif
+ 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)) {
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Thumbnail Thread) Exiting\n");
+#endif
+ 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
+ *********************************/
+
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Thumbnail Thread) Unlocking mutex\n");
+#endif
+ 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) {
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Thumbnail Thread) Skipping: %s\n",
+ info->image_uri);
+#endif
+ /* 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. */
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Thumbnail Thread) Creating thumbnail: %s\n",
+ info->image_uri);
+#endif
+
+ pixbuf = gnome_desktop_thumbnail_factory_generate_thumbnail (thumbnail_factory,
+ info->image_uri,
+ info->mime_type);
+
+ if (pixbuf) {
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Thumbnail Thread) Saving thumbnail: %s\n",
+ info->image_uri);
+#endif
+ gnome_desktop_thumbnail_factory_save_thumbnail (thumbnail_factory,
+ pixbuf,
+ info->image_uri,
+ current_orig_mtime);
+ g_object_unref (pixbuf);
+ } else {
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Thumbnail Thread) Thumbnail failed: %s\n",
+ info->image_uri);
+#endif
+ 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 000000000..3544072ea
--- /dev/null
+++ b/src/nautilus-thumbnails.h
@@ -0,0 +1,40 @@
+/*
+ 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>
+*/
+
+#ifndef NAUTILUS_THUMBNAILS_H
+#define NAUTILUS_THUMBNAILS_H
+
+#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_can_thumbnail_internally (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);
+
+
+#endif /* NAUTILUS_THUMBNAILS_H */
diff --git a/src/nautilus-toolbar.c b/src/nautilus-toolbar.c
index 3c1255888..02e204b52 100644
--- a/src/nautilus-toolbar.c
+++ b/src/nautilus-toolbar.c
@@ -31,10 +31,10 @@
#include "nautilus-progress-info-widget.h"
#include "nautilus-application.h"
-#include <libnautilus-private/nautilus-global-preferences.h>
-#include <libnautilus-private/nautilus-ui-utilities.h>
-#include <libnautilus-private/nautilus-progress-info-manager.h>
-#include <libnautilus-private/nautilus-file-operations.h>
+#include "nautilus-global-preferences.h"
+#include "nautilus-ui-utilities.h"
+#include "nautilus-progress-info-manager.h"
+#include "nautilus-file-operations.h"
#include <glib/gi18n.h>
#include <math.h>
diff --git a/src/nautilus-trash-bar.c b/src/nautilus-trash-bar.c
index 9eeed44f6..dd4327a63 100644
--- a/src/nautilus-trash-bar.c
+++ b/src/nautilus-trash-bar.c
@@ -26,10 +26,10 @@
#include "nautilus-trash-bar.h"
#include "nautilus-files-view.h"
-#include <libnautilus-private/nautilus-file-operations.h>
-#include <libnautilus-private/nautilus-file-utilities.h>
-#include <libnautilus-private/nautilus-file.h>
-#include <libnautilus-private/nautilus-trash-monitor.h>
+#include "nautilus-file-operations.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-file.h"
+#include "nautilus-trash-monitor.h"
#define NAUTILUS_TRASH_BAR_GET_PRIVATE(o)\
(G_TYPE_INSTANCE_GET_PRIVATE ((o), NAUTILUS_TYPE_TRASH_BAR, NautilusTrashBarPrivate))
diff --git a/src/nautilus-trash-monitor.c b/src/nautilus-trash-monitor.c
new file mode 100644
index 000000000..fd26421aa
--- /dev/null
+++ b/src/nautilus-trash-monitor.c
@@ -0,0 +1,210 @@
+
+/*
+ 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 <config.h>
+#include "nautilus-trash-monitor.h"
+
+#include "nautilus-directory-notify.h"
+#include "nautilus-directory.h"
+#include "nautilus-file-attributes.h"
+#include <eel/eel-debug.h>
+#include <gio/gio.h>
+#include <string.h>
+
+struct NautilusTrashMonitorDetails {
+ gboolean empty;
+ GFileMonitor *file_monitor;
+};
+
+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->details->file_monitor) {
+ g_object_unref (trash_monitor->details->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,
+ G_STRUCT_OFFSET (NautilusTrashMonitorClass, trash_state_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__BOOLEAN,
+ G_TYPE_NONE, 1,
+ G_TYPE_BOOLEAN);
+
+ g_type_class_add_private (object_class, sizeof(NautilusTrashMonitorDetails));
+}
+
+static void
+update_empty_info (NautilusTrashMonitor *trash_monitor,
+ gboolean is_empty)
+{
+ if (trash_monitor->details->empty == is_empty) {
+ return;
+ }
+
+ trash_monitor->details->empty = is_empty;
+
+ /* trash got empty or full, notify everyone who cares */
+ g_signal_emit (trash_monitor,
+ signals[TRASH_STATE_CHANGED], 0,
+ trash_monitor->details->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)
+{
+ GFile *location;
+
+ 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));
+
+ 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->details = G_TYPE_INSTANCE_GET_PRIVATE (trash_monitor,
+ NAUTILUS_TYPE_TRASH_MONITOR,
+ NautilusTrashMonitorDetails);
+
+ trash_monitor->details->empty = TRUE;
+
+ location = g_file_new_for_uri ("trash:///");
+
+ trash_monitor->details->file_monitor = g_file_monitor_file (location, 0, NULL, NULL);
+
+ g_signal_connect (trash_monitor->details->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->details->empty;
+}
diff --git a/src/nautilus-trash-monitor.h b/src/nautilus-trash-monitor.h
new file mode 100644
index 000000000..a5282819a
--- /dev/null
+++ b/src/nautilus-trash-monitor.h
@@ -0,0 +1,63 @@
+
+/*
+ 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>
+*/
+
+#ifndef NAUTILUS_TRASH_MONITOR_H
+#define NAUTILUS_TRASH_MONITOR_H
+
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+
+typedef struct NautilusTrashMonitor NautilusTrashMonitor;
+typedef struct NautilusTrashMonitorClass NautilusTrashMonitorClass;
+typedef struct NautilusTrashMonitorDetails NautilusTrashMonitorDetails;
+
+#define NAUTILUS_TYPE_TRASH_MONITOR nautilus_trash_monitor_get_type()
+#define NAUTILUS_TRASH_MONITOR(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_TRASH_MONITOR, NautilusTrashMonitor))
+#define NAUTILUS_TRASH_MONITOR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_TRASH_MONITOR, NautilusTrashMonitorClass))
+#define NAUTILUS_IS_TRASH_MONITOR(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_TRASH_MONITOR))
+#define NAUTILUS_IS_TRASH_MONITOR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_TRASH_MONITOR))
+#define NAUTILUS_TRASH_MONITOR_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_TRASH_MONITOR, NautilusTrashMonitorClass))
+
+struct NautilusTrashMonitor {
+ GObject object;
+ NautilusTrashMonitorDetails *details;
+};
+
+struct NautilusTrashMonitorClass {
+ GObjectClass parent_class;
+
+ void (* trash_state_changed) (NautilusTrashMonitor *trash_monitor,
+ gboolean new_state);
+};
+
+GType nautilus_trash_monitor_get_type (void);
+
+NautilusTrashMonitor *nautilus_trash_monitor_get (void);
+gboolean nautilus_trash_monitor_is_empty (void);
+GIcon *nautilus_trash_monitor_get_icon (void);
+
+#endif
diff --git a/src/nautilus-tree-view-drag-dest.c b/src/nautilus-tree-view-drag-dest.c
new file mode 100644
index 000000000..97257dd94
--- /dev/null
+++ b/src/nautilus-tree-view-drag-dest.c
@@ -0,0 +1,1265 @@
+
+/*
+ * 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 "nautilus-link.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;
+ guint 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 && dest->details->highlight_id) {
+ g_signal_handler_disconnect (dest->details->tree_view,
+ dest->details->highlight_id);
+ dest->details->highlight_id = 0;
+ 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_target_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) {
+ if (nautilus_drag_selection_includes_special_link (dest->details->drag_list)) {
+ /* We only want to move the trash */
+ action = GDK_ACTION_MOVE;
+ } else {
+ 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),
+ x, y);
+
+ 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, 5,
+ G_TYPE_POINTER,
+ G_TYPE_STRING,
+ GDK_TYPE_DRAG_ACTION,
+ G_TYPE_INT,
+ G_TYPE_INT);
+ 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, 5,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ GDK_TYPE_DRAG_ACTION,
+ G_TYPE_INT,
+ G_TYPE_INT);
+ 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, 5,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ GDK_TYPE_DRAG_ACTION,
+ G_TYPE_INT,
+ G_TYPE_INT);
+ 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, 5,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ GDK_TYPE_DRAG_ACTION,
+ G_TYPE_INT,
+ G_TYPE_INT);
+ 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, 7,
+ G_TYPE_POINTER,
+ G_TYPE_INT,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ GDK_TYPE_DRAG_ACTION,
+ G_TYPE_INT,
+ G_TYPE_INT);
+ 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 000000000..92d2f70cc
--- /dev/null
+++ b/src/nautilus-tree-view-drag-dest.h
@@ -0,0 +1,99 @@
+
+/*
+ * 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
+ */
+
+#ifndef NAUTILUS_TREE_VIEW_DRAG_DEST_H
+#define NAUTILUS_TREE_VIEW_DRAG_DEST_H
+
+#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
+
+#endif
diff --git a/src/nautilus-ui-utilities.c b/src/nautilus-ui-utilities.c
new file mode 100644
index 000000000..c0eaab40b
--- /dev/null
+++ b/src/nautilus-ui-utilities.c
@@ -0,0 +1,435 @@
+
+/* 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 <eel/eel-graphic-effects.h>
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+#include <libgd/gd.h>
+#include <string.h>
+#include <glib/gi18n.h>
+
+static GMenuModel *
+find_gmenu_model (GMenuModel *model,
+ const gchar *model_id)
+{
+ gint i, n_items;
+ GMenuModel *insertion_model = NULL;
+
+ n_items = g_menu_model_get_n_items (model);
+
+ for (i = 0; i < n_items && !insertion_model; i++) {
+ gchar *id = NULL;
+ if (g_menu_model_get_item_attribute (model, i, "id", "s", &id) &&
+ g_strcmp0 (id, model_id) == 0) {
+ insertion_model = g_menu_model_get_item_link (model, i, G_MENU_LINK_SECTION);
+ if (!insertion_model)
+ insertion_model = g_menu_model_get_item_link (model, i, G_MENU_LINK_SUBMENU);
+ } else {
+ GMenuModel *submodel;
+ GMenuModel *submenu;
+ gint j, j_items;
+
+ submodel = g_menu_model_get_item_link (model, i, G_MENU_LINK_SECTION);
+
+ if (!submodel)
+ submodel = g_menu_model_get_item_link (model, i, G_MENU_LINK_SUBMENU);
+
+ if (!submodel)
+ continue;
+
+ j_items = g_menu_model_get_n_items (submodel);
+ for (j = 0; j < j_items; j++) {
+ submenu = g_menu_model_get_item_link (submodel, j, G_MENU_LINK_SUBMENU);
+ if (submenu) {
+ insertion_model = find_gmenu_model (submenu, model_id);
+ g_object_unref (submenu);
+ }
+
+ if (insertion_model)
+ break;
+ }
+
+ g_object_unref (submodel);
+ }
+
+ g_free (id);
+ }
+
+ return insertion_model;
+}
+
+/*
+ * The original GMenu is modified adding to the section @submodel_name
+ * the items in @gmenu_to_merge.
+ * @gmenu_to_merge should be a list of menu items.
+ */
+void
+nautilus_gmenu_merge (GMenu *original,
+ GMenu *gmenu_to_merge,
+ const gchar *submodel_name,
+ gboolean prepend)
+{
+ gint i, n_items;
+ GMenuModel *submodel;
+ GMenuItem *item;
+
+ g_return_if_fail (G_IS_MENU (original));
+ g_return_if_fail (G_IS_MENU (gmenu_to_merge));
+
+ submodel = find_gmenu_model (G_MENU_MODEL (original), submodel_name);
+
+ g_return_if_fail (submodel != NULL);
+
+ n_items = g_menu_model_get_n_items (G_MENU_MODEL (gmenu_to_merge));
+
+ for (i = 0; i < n_items; i++) {
+ item = g_menu_item_new_from_model (G_MENU_MODEL (gmenu_to_merge), i);
+ if (prepend)
+ g_menu_prepend_item (G_MENU (submodel), item);
+ else
+ g_menu_append_item (G_MENU (submodel), item);
+ g_object_unref (item);
+ }
+
+ g_object_unref (submodel);
+}
+
+/*
+ * The GMenu @menu is modified adding to the section @submodel_name
+ * the item @item.
+ */
+void
+nautilus_gmenu_add_item_in_submodel (GMenu *menu,
+ GMenuItem *item,
+ const gchar *submodel_name,
+ gboolean prepend)
+{
+ GMenuModel *submodel;
+
+ g_return_if_fail (G_IS_MENU (menu));
+ g_return_if_fail (G_IS_MENU_ITEM (item));
+
+ submodel = find_gmenu_model (G_MENU_MODEL (menu), submodel_name);
+
+ g_return_if_fail (submodel != NULL);
+ if (prepend)
+ g_menu_prepend_item (G_MENU (submodel), item);
+ else
+ g_menu_append_item (G_MENU (submodel), item);
+
+ g_object_unref (submodel);
+}
+
+void
+nautilus_gmenu_replace_section (GMenu *menu,
+ const gchar *section_id,
+ GMenuModel *section)
+{
+ GMenuModel *orig_section;
+ GMenuItem *item;
+ gint idx;
+
+ orig_section = find_gmenu_model (G_MENU_MODEL (menu), section_id);
+ g_return_if_fail (orig_section != NULL);
+
+ g_menu_remove_all (G_MENU (orig_section));
+
+ for (idx = 0; idx < g_menu_model_get_n_items (section); idx++) {
+ item = g_menu_item_new_from_model (section, idx);
+ g_menu_append_item (G_MENU (orig_section), item);
+ g_object_unref (item);
+ }
+
+ g_object_unref (orig_section);
+}
+
+void
+nautilus_pop_up_context_menu (GtkWidget *parent,
+ GMenu *menu,
+ GdkEventButton *event)
+{
+ GtkWidget *gtk_menu;
+
+ int button;
+
+ g_return_if_fail (G_IS_MENU (menu));
+ g_return_if_fail (GTK_IS_WIDGET (parent));
+
+ gtk_menu = gtk_menu_new_from_model (G_MENU_MODEL (menu));
+ gtk_menu_attach_to_widget (GTK_MENU (gtk_menu), parent, NULL);
+
+ /* The event button needs to be 0 if we're popping up this menu from
+ * a button release, else a 2nd click outside the menu with any button
+ * other than the one that invoked the menu will be ignored (instead
+ * of dismissing the menu). This is a subtle fragility of the GTK menu code.
+ */
+ if (event) {
+ button = event->type == GDK_BUTTON_RELEASE
+ ? 0
+ : event->button;
+ } else {
+ button = 0;
+ }
+
+ gtk_menu_popup (GTK_MENU (gtk_menu), /* menu */
+ NULL, /* parent_menu_shell */
+ NULL, /* parent_menu_item */
+ NULL, /* popup_position_func */
+ NULL, /* popup_position_data */
+ button, /* button */
+ event ? event->time : gtk_get_current_event_time ()); /* activate_time */
+
+ g_object_ref_sink (gtk_menu);
+ g_object_unref (gtk_menu);
+}
+
+char *
+nautilus_escape_action_name (const char *action_name,
+ const char *prefix)
+{
+ GString *s;
+
+ if (action_name == NULL) {
+ return NULL;
+ }
+
+ s = g_string_new (prefix);
+
+ while (*action_name != 0) {
+ switch (*action_name) {
+ case '\\':
+ g_string_append (s, "\\\\");
+ break;
+ case '/':
+ g_string_append (s, "\\s");
+ break;
+ case '&':
+ g_string_append (s, "\\a");
+ break;
+ case '"':
+ g_string_append (s, "\\q");
+ break;
+ case ' ':
+ g_string_append (s, "+");
+ break;
+ case '(':
+ g_string_append (s, "#");
+ break;
+ case ')':
+ g_string_append (s, "^");
+ break;
+ case ':':
+ g_string_append (s, "\\\\");
+ break;
+ default:
+ g_string_append_c (s, *action_name);
+ }
+
+ action_name ++;
+ }
+ return g_string_free (s, FALSE);
+}
+
+#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)
+{
+ if (days < 7)
+ {
+ /* days */
+ return ngettext ("%d day ago", "%d days ago", days);
+ }
+ else if (days < 30)
+ {
+ /* weeks */
+ return ngettext ("Last week", "%d weeks ago", days / 7);
+ }
+ else if (days < 365)
+ {
+ /* months */
+ return ngettext ("Last month", "%d months ago", days / 30);
+ }
+ else
+ {
+ /* years */
+ return ngettext ("Last year", "%d years ago", days / 365);
+ }
+}
+
+gchar*
+get_text_for_date_range (GPtrArray *date_range)
+{
+ 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;
+ }
+
+ label = g_strdup_printf (get_text_for_days_ago (days), normalized);
+ }
+
+ g_free (formatted_date);
+
+ return label;
+}
+
diff --git a/src/nautilus-ui-utilities.h b/src/nautilus-ui-utilities.h
new file mode 100644
index 000000000..46e820372
--- /dev/null
+++ b/src/nautilus-ui-utilities.h
@@ -0,0 +1,54 @@
+
+/* 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>
+*/
+#ifndef NAUTILUS_UI_UTILITIES_H
+#define NAUTILUS_UI_UTILITIES_H
+
+#include <gtk/gtk.h>
+#include <libnautilus-extension/nautilus-menu-item.h>
+
+
+void nautilus_gmenu_add_item_in_submodel (GMenu *menu,
+ GMenuItem *item,
+ const gchar *section_name,
+ gboolean prepend);
+void nautilus_gmenu_merge (GMenu *original,
+ GMenu *gmenu_to_merge,
+ const gchar *submodel_name,
+ gboolean prepend);
+void nautilus_gmenu_replace_section (GMenu *menu,
+ const gchar *section_id,
+ GMenuModel *section);
+void nautilus_pop_up_context_menu (GtkWidget *parent,
+ GMenu *menu,
+ GdkEventButton *event);
+
+char * nautilus_escape_action_name (const char *action_name,
+ const char *prefix);
+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);
+
+#endif /* NAUTILUS_UI_UTILITIES_H */
diff --git a/src/nautilus-undo-private.h b/src/nautilus-undo-private.h
new file mode 100644
index 000000000..84f03eee6
--- /dev/null
+++ b/src/nautilus-undo-private.h
@@ -0,0 +1,33 @@
+
+/* 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/>.
+ */
+
+#ifndef NAUTILUS_UNDO_PRIVATE_H
+#define NAUTILUS_UNDO_PRIVATE_H
+
+#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);
+
+#endif /* NAUTILUS_UNDO_PRIVATE_H */
diff --git a/src/nautilus-vfs-directory.c b/src/nautilus-vfs-directory.c
new file mode 100644
index 000000000..778102b98
--- /dev/null
+++ b/src/nautilus-vfs-directory.c
@@ -0,0 +1,152 @@
+/*
+ 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 gboolean
+vfs_contains_file (NautilusDirectory *directory,
+ NautilusFile *file)
+{
+ g_assert (NAUTILUS_IS_VFS_DIRECTORY (directory));
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ return file->details->directory == 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 gboolean
+vfs_are_all_files_seen (NautilusDirectory *directory)
+{
+ g_assert (NAUTILUS_IS_VFS_DIRECTORY (directory));
+
+ return directory->details->directory_loaded;
+}
+
+static gboolean
+vfs_is_not_empty (NautilusDirectory *directory)
+{
+ g_assert (NAUTILUS_IS_VFS_DIRECTORY (directory));
+ g_assert (nautilus_directory_is_anyone_monitoring_file_list (directory));
+
+ return directory->details->file_list != NULL;
+}
+
+static void
+nautilus_vfs_directory_class_init (NautilusVFSDirectoryClass *klass)
+{
+ NautilusDirectoryClass *directory_class = NAUTILUS_DIRECTORY_CLASS (klass);
+
+ directory_class->contains_file = vfs_contains_file;
+ 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;
+ directory_class->are_all_files_seen = vfs_are_all_files_seen;
+ directory_class->is_not_empty = vfs_is_not_empty;
+}
diff --git a/src/nautilus-vfs-directory.h b/src/nautilus-vfs-directory.h
new file mode 100644
index 000000000..621e4161c
--- /dev/null
+++ b/src/nautilus-vfs-directory.h
@@ -0,0 +1,52 @@
+/*
+ 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>
+*/
+
+#ifndef NAUTILUS_VFS_DIRECTORY_H
+#define NAUTILUS_VFS_DIRECTORY_H
+
+#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);
+
+#endif /* NAUTILUS_VFS_DIRECTORY_H */
diff --git a/src/nautilus-vfs-file.c b/src/nautilus-vfs-file.c
new file mode 100644
index 000000000..0a37be573
--- /dev/null
+++ b/src/nautilus-vfs-file.c
@@ -0,0 +1,696 @@
+/*
+ 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)
+{
+ nautilus_directory_monitor_add_internal
+ (file->details->directory, file,
+ client, TRUE, attributes, NULL, NULL);
+}
+
+static void
+vfs_file_monitor_remove (NautilusFile *file,
+ gconstpointer client)
+{
+ nautilus_directory_monitor_remove_internal
+ (file->details->directory, file, client);
+}
+
+static void
+vfs_file_call_when_ready (NautilusFile *file,
+ NautilusFileAttributes file_attributes,
+ NautilusFileCallback callback,
+ gpointer callback_data)
+
+{
+ nautilus_directory_call_when_ready_internal
+ (file->details->directory, file,
+ file_attributes, FALSE, NULL, callback, callback_data);
+}
+
+static void
+vfs_file_cancel_call_when_ready (NautilusFile *file,
+ NautilusFileCallback callback,
+ gpointer callback_data)
+{
+ nautilus_directory_cancel_callback_internal
+ (file->details->directory, file,
+ NULL, callback, callback_data);
+}
+
+static gboolean
+vfs_file_check_if_ready (NautilusFile *file,
+ NautilusFileAttributes file_attributes)
+{
+ return nautilus_directory_check_if_ready_internal
+ (file->details->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_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
+vfs_file_get_deep_counts (NautilusFile *file,
+ guint *directory_count,
+ guint *file_count,
+ guint *unreadable_directory_count,
+ goffset *total_size)
+{
+ GFileType type;
+
+ 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 (!nautilus_file_is_directory (file)) {
+ 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. */
+ type = nautilus_file_get_file_type (file);
+ 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 gboolean
+vfs_file_get_date (NautilusFile *file,
+ NautilusDateType date_type,
+ time_t *date)
+{
+ switch (date_type) {
+ case NAUTILUS_DATE_TYPE_ACCESSED:
+ /* Before we have info on a file, the date is unknown. */
+ if (file->details->atime == 0) {
+ return FALSE;
+ }
+ if (date != NULL) {
+ *date = file->details->atime;
+ }
+ return TRUE;
+ case NAUTILUS_DATE_TYPE_MODIFIED:
+ /* Before we have info on a file, the date is unknown. */
+ if (file->details->mtime == 0) {
+ return FALSE;
+ }
+ if (date != NULL) {
+ *date = file->details->mtime;
+ }
+ return TRUE;
+ case NAUTILUS_DATE_TYPE_TRASHED:
+ /* Before we have info on a file, the date is unknown. */
+ if (file->details->trash_time == 0) {
+ return FALSE;
+ }
+ if (date != NULL) {
+ *date = file->details->trash_time;
+ }
+ 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)
+{
+ NautilusFileOperation *op;
+ GError *error;
+ GFile *location;
+
+ if (file->details->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)
+{
+ NautilusFileOperation *op;
+ GError *error;
+ GFile *location;
+
+ if (file->details->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_item_count = vfs_file_get_item_count;
+ file_class->get_deep_counts = vfs_file_get_deep_counts;
+ 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 000000000..a6786d93e
--- /dev/null
+++ b/src/nautilus-vfs-file.h
@@ -0,0 +1,52 @@
+/*
+ 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>
+*/
+
+#ifndef NAUTILUS_VFS_FILE_H
+#define NAUTILUS_VFS_FILE_H
+
+#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);
+
+#endif /* NAUTILUS_VFS_FILE_H */
diff --git a/src/nautilus-video-mime-types.h b/src/nautilus-video-mime-types.h
new file mode 100644
index 000000000..e0d4aac93
--- /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.h b/src/nautilus-view.h
index 5dd07161e..2bc9a13f9 100644
--- a/src/nautilus-view.h
+++ b/src/nautilus-view.h
@@ -23,7 +23,7 @@
#include <glib.h>
#include <gtk/gtk.h>
-#include <libnautilus-private/nautilus-query.h>
+#include "nautilus-query.h"
G_BEGIN_DECLS
diff --git a/src/nautilus-window-slot-dnd.h b/src/nautilus-window-slot-dnd.h
index a4897b6ef..f9de9b0c8 100644
--- a/src/nautilus-window-slot-dnd.h
+++ b/src/nautilus-window-slot-dnd.h
@@ -29,7 +29,7 @@
#include <gio/gio.h>
#include <gtk/gtk.h>
-#include <libnautilus-private/nautilus-dnd.h>
+#include "nautilus-dnd.h"
#include "nautilus-window-slot.h"
diff --git a/src/nautilus-window-slot.c b/src/nautilus-window-slot.c
index 27af46e8c..76cfeef2e 100644
--- a/src/nautilus-window-slot.c
+++ b/src/nautilus-window-slot.c
@@ -36,12 +36,12 @@
#include <glib/gi18n.h>
#include <eel/eel-stock-dialogs.h>
-#include <libnautilus-private/nautilus-file.h>
-#include <libnautilus-private/nautilus-file-utilities.h>
-#include <libnautilus-private/nautilus-global-preferences.h>
-#include <libnautilus-private/nautilus-module.h>
-#include <libnautilus-private/nautilus-monitor.h>
-#include <libnautilus-private/nautilus-profile.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 <libnautilus-extension/nautilus-location-widget-provider.h>
enum {
@@ -775,7 +775,7 @@ nautilus_window_slot_init (NautilusWindowSlot *self)
}
#define DEBUG_FLAG NAUTILUS_DEBUG_WINDOW
-#include <libnautilus-private/nautilus-debug.h>
+#include "nautilus-debug.h"
static void begin_location_change (NautilusWindowSlot *slot,
GFile *location,
diff --git a/src/nautilus-window.c b/src/nautilus-window.c
index 2d2a1fc42..936db68e6 100644
--- a/src/nautilus-window.c
+++ b/src/nautilus-window.c
@@ -51,21 +51,21 @@
#ifdef HAVE_X11_XF86KEYSYM_H
#include <X11/XF86keysym.h>
#endif
-#include <libnautilus-private/nautilus-dnd.h>
-#include <libnautilus-private/nautilus-file-utilities.h>
-#include <libnautilus-private/nautilus-file-attributes.h>
-#include <libnautilus-private/nautilus-file-operations.h>
-#include <libnautilus-private/nautilus-file-undo-manager.h>
-#include <libnautilus-private/nautilus-global-preferences.h>
-#include <libnautilus-private/nautilus-metadata.h>
-#include <libnautilus-private/nautilus-profile.h>
-#include <libnautilus-private/nautilus-clipboard.h>
-#include <libnautilus-private/nautilus-signaller.h>
-#include <libnautilus-private/nautilus-trash-monitor.h>
-#include <libnautilus-private/nautilus-ui-utilities.h>
+#include "nautilus-dnd.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-file-attributes.h"
+#include "nautilus-file-operations.h"
+#include "nautilus-file-undo-manager.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-metadata.h"
+#include "nautilus-profile.h"
+#include "nautilus-clipboard.h"
+#include "nautilus-signaller.h"
+#include "nautilus-trash-monitor.h"
+#include "nautilus-ui-utilities.h"
#define DEBUG_FLAG NAUTILUS_DEBUG_WINDOW
-#include <libnautilus-private/nautilus-debug.h>
+#include "nautilus-debug.h"
#include <math.h>
#include <sys/time.h>
diff --git a/src/nautilus-window.h b/src/nautilus-window.h
index c803df96f..b2108db9c 100644
--- a/src/nautilus-window.h
+++ b/src/nautilus-window.h
@@ -29,8 +29,8 @@
#include <gtk/gtk.h>
#include <eel/eel-glib-extensions.h>
-#include <libnautilus-private/nautilus-bookmark.h>
-#include <libnautilus-private/nautilus-search-directory.h>
+#include "nautilus-bookmark.h"
+#include "nautilus-search-directory.h"
typedef struct NautilusWindow NautilusWindow;
typedef struct NautilusWindowClass NautilusWindowClass;
diff --git a/src/nautilus-x-content-bar.c b/src/nautilus-x-content-bar.c
index 137eb2413..af633106b 100644
--- a/src/nautilus-x-content-bar.c
+++ b/src/nautilus-x-content-bar.c
@@ -27,9 +27,9 @@
#include <string.h>
#include "nautilus-x-content-bar.h"
-#include <libnautilus-private/nautilus-icon-info.h>
-#include <libnautilus-private/nautilus-file-utilities.h>
-#include <libnautilus-private/nautilus-program-choosing.h>
+#include "nautilus-icon-info.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-program-choosing.h"
#define NAUTILUS_X_CONTENT_BAR_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NAUTILUS_TYPE_X_CONTENT_BAR, NautilusXContentBarPrivate))