diff options
Diffstat (limited to 'src')
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, ×tamp, &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, ¤t); + + 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 (¤t_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 (¤t_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 (¤t_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)) |