summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorCarlos Soriano <csoriano@gnome.org>2016-04-22 17:02:38 +0200
committerCarlos Soriano <csoriano@gnome.org>2016-04-25 16:31:42 +0200
commit7e24f1b2a2b3b7860ee29820d69f5a1511fee994 (patch)
treea2ee175c5f9cd05ab518e768a073ef4a31fe8376 /src
parent2774b8552dcc89ae744700af5832dbf76c138a9e (diff)
downloadnautilus-7e24f1b2a2b3b7860ee29820d69f5a1511fee994.tar.gz
general: merge libnautilus-private to srcwip/csoriano/private-to-src
And fix make distcheck. Although libnautilus-private seem self contained, it was actually depending on the files on src/ for dnd. Not only that, but files in libnautilus-private also were depending on dnd files, which you can guess it's wrong. Before the desktop split, this was working because the files were distributed, but now was a problem since we reestructured the code, and now nautilus being a library make distcheck stop working. First solution was try to fix this inter dependency of files, but at some point I realized that there was no real point on splitting some of those files, because for example, is perfectly fine for dnd to need to access the window functions, and it's perfectly fine for the widgets in the private library to need to access to all dnd functions. So seems to me the private library of nautilus is somehow an artificial split, which provides more problems than solutions. We needed libnautilus-private to have a private library that we could isolate from extensions, but I don't think it worth given the problems it provides, and also, this not so good logical split. Right now, since with the desktop split we created a libnautilus to be used by the desktop part of nautilus, extensions have access to all the API of nautilus. We will think in future how this can be handled if we want. So for now, merge the libnautilus-private into src, and let's rethink a better logic to split the code and the private parts of nautilus than what we had. Thanks a lot to Rafael Fonseca for helping in get this done. https://bugzilla.gnome.org/show_bug.cgi?id=765543
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))