summaryrefslogtreecommitdiff
path: root/libcaja-private
diff options
context:
space:
mode:
Diffstat (limited to 'libcaja-private')
-rw-r--r--libcaja-private/Makefile.am246
-rw-r--r--libcaja-private/README13
-rw-r--r--libcaja-private/apps_caja_preferences.schemas.in1205
-rw-r--r--libcaja-private/caja-autorun.c1434
-rw-r--r--libcaja-private/caja-autorun.h81
-rw-r--r--libcaja-private/caja-bookmark.c672
-rw-r--r--libcaja-private/caja-bookmark.h100
-rw-r--r--libcaja-private/caja-cell-renderer-pixbuf-emblem.c519
-rw-r--r--libcaja-private/caja-cell-renderer-pixbuf-emblem.h68
-rw-r--r--libcaja-private/caja-cell-renderer-text-ellipsized.c80
-rw-r--r--libcaja-private/caja-cell-renderer-text-ellipsized.h61
-rw-r--r--libcaja-private/caja-clipboard-monitor.c338
-rw-r--r--libcaja-private/caja-clipboard-monitor.h85
-rw-r--r--libcaja-private/caja-clipboard.c692
-rw-r--r--libcaja-private/caja-clipboard.h54
-rw-r--r--libcaja-private/caja-column-chooser.c670
-rw-r--r--libcaja-private/caja-column-chooser.h69
-rw-r--r--libcaja-private/caja-column-utilities.c327
-rw-r--r--libcaja-private/caja-column-utilities.h41
-rw-r--r--libcaja-private/caja-customization-data.c498
-rw-r--r--libcaja-private/caja-customization-data.h68
-rw-r--r--libcaja-private/caja-debug-log.c760
-rw-r--r--libcaja-private/caja-debug-log.h58
-rw-r--r--libcaja-private/caja-default-file-icon.c537
-rw-r--r--libcaja-private/caja-default-file-icon.h32
-rw-r--r--libcaja-private/caja-desktop-directory-file.c731
-rw-r--r--libcaja-private/caja-desktop-directory-file.h68
-rw-r--r--libcaja-private/caja-desktop-directory.c560
-rw-r--r--libcaja-private/caja-desktop-directory.h61
-rw-r--r--libcaja-private/caja-desktop-icon-file.c410
-rw-r--r--libcaja-private/caja-desktop-icon-file.h64
-rw-r--r--libcaja-private/caja-desktop-link-monitor.c575
-rw-r--r--libcaja-private/caja-desktop-link-monitor.h67
-rw-r--r--libcaja-private/caja-desktop-link.c479
-rw-r--r--libcaja-private/caja-desktop-link.h84
-rw-r--r--libcaja-private/caja-directory-async.c5410
-rw-r--r--libcaja-private/caja-directory-background.c731
-rw-r--r--libcaja-private/caja-directory-background.h36
-rw-r--r--libcaja-private/caja-directory-notify.h74
-rw-r--r--libcaja-private/caja-directory-private.h249
-rw-r--r--libcaja-private/caja-directory.c2004
-rw-r--r--libcaja-private/caja-directory.h244
-rw-r--r--libcaja-private/caja-dnd.c1446
-rw-r--r--libcaja-private/caja-dnd.h200
-rw-r--r--libcaja-private/caja-emblem-utils.c511
-rw-r--r--libcaja-private/caja-emblem-utils.h53
-rw-r--r--libcaja-private/caja-entry.c442
-rw-r--r--libcaja-private/caja-entry.h78
-rw-r--r--libcaja-private/caja-file-attributes.h47
-rw-r--r--libcaja-private/caja-file-changes-queue.c475
-rw-r--r--libcaja-private/caja-file-changes-queue.h42
-rw-r--r--libcaja-private/caja-file-conflict-dialog.c670
-rw-r--r--libcaja-private/caja-file-conflict-dialog.h80
-rw-r--r--libcaja-private/caja-file-dnd.c186
-rw-r--r--libcaja-private/caja-file-dnd.h44
-rw-r--r--libcaja-private/caja-file-operations.c6469
-rw-r--r--libcaja-private/caja-file-operations.h142
-rw-r--r--libcaja-private/caja-file-private.h316
-rw-r--r--libcaja-private/caja-file-queue.c133
-rw-r--r--libcaja-private/caja-file-queue.h52
-rw-r--r--libcaja-private/caja-file-utilities.c1449
-rw-r--r--libcaja-private/caja-file-utilities.h111
-rw-r--r--libcaja-private/caja-file.c8411
-rw-r--r--libcaja-private/caja-file.h590
-rw-r--r--libcaja-private/caja-global-preferences.c954
-rw-r--r--libcaja-private/caja-global-preferences.h242
-rw-r--r--libcaja-private/caja-horizontal-splitter.c307
-rw-r--r--libcaja-private/caja-horizontal-splitter.h76
-rw-r--r--libcaja-private/caja-icon-canvas-item.c3874
-rw-r--r--libcaja-private/caja-icon-canvas-item.h123
-rw-r--r--libcaja-private/caja-icon-container.c10612
-rw-r--r--libcaja-private/caja-icon-container.h371
-rw-r--r--libcaja-private/caja-icon-dnd.c2123
-rw-r--r--libcaja-private/caja-icon-dnd.h63
-rw-r--r--libcaja-private/caja-icon-info.c751
-rw-r--r--libcaja-private/caja-icon-info.h99
-rw-r--r--libcaja-private/caja-icon-names.h30
-rw-r--r--libcaja-private/caja-icon-private.h336
-rw-r--r--libcaja-private/caja-idle-queue.c156
-rw-r--r--libcaja-private/caja-idle-queue.h49
-rw-r--r--libcaja-private/caja-iso9660.h110
-rw-r--r--libcaja-private/caja-keep-last-vertical-box.c166
-rw-r--r--libcaja-private/caja-keep-last-vertical-box.h59
-rw-r--r--libcaja-private/caja-lib-self-check-functions.c38
-rw-r--r--libcaja-private/caja-lib-self-check-functions.h50
-rw-r--r--libcaja-private/caja-link.c642
-rw-r--r--libcaja-private/caja-link.h54
-rw-r--r--libcaja-private/caja-marshal.c2
-rw-r--r--libcaja-private/caja-marshal.list23
-rw-r--r--libcaja-private/caja-merged-directory.c735
-rw-r--r--libcaja-private/caja-merged-directory.h69
-rw-r--r--libcaja-private/caja-metadata.c80
-rw-r--r--libcaja-private/caja-metadata.h82
-rw-r--r--libcaja-private/caja-mime-actions.c2597
-rw-r--r--libcaja-private/caja-mime-actions.h62
-rw-r--r--libcaja-private/caja-mime-application-chooser.c746
-rw-r--r--libcaja-private/caja-mime-application-chooser.h57
-rw-r--r--libcaja-private/caja-module.c293
-rw-r--r--libcaja-private/caja-module.h46
-rw-r--r--libcaja-private/caja-monitor.c157
-rw-r--r--libcaja-private/caja-monitor.h38
-rw-r--r--libcaja-private/caja-open-with-dialog.c1183
-rw-r--r--libcaja-private/caja-open-with-dialog.h66
-rw-r--r--libcaja-private/caja-program-choosing.c509
-rw-r--r--libcaja-private/caja-program-choosing.h57
-rw-r--r--libcaja-private/caja-progress-info.c930
-rw-r--r--libcaja-private/caja-progress-info.h85
-rw-r--r--libcaja-private/caja-query.c395
-rw-r--r--libcaja-private/caja-query.h68
-rw-r--r--libcaja-private/caja-recent.c80
-rw-r--r--libcaja-private/caja-recent.h13
-rw-r--r--libcaja-private/caja-saved-search-file.c49
-rw-r--r--libcaja-private/caja-saved-search-file.h58
-rw-r--r--libcaja-private/caja-search-directory-file.c260
-rw-r--r--libcaja-private/caja-search-directory-file.h59
-rw-r--r--libcaja-private/caja-search-directory.c934
-rw-r--r--libcaja-private/caja-search-directory.h73
-rw-r--r--libcaja-private/caja-search-engine-beagle.c443
-rw-r--r--libcaja-private/caja-search-engine-beagle.h53
-rw-r--r--libcaja-private/caja-search-engine-simple.c464
-rw-r--r--libcaja-private/caja-search-engine-simple.h53
-rw-r--r--libcaja-private/caja-search-engine-tracker.c580
-rw-r--r--libcaja-private/caja-search-engine-tracker.h53
-rw-r--r--libcaja-private/caja-search-engine.c216
-rw-r--r--libcaja-private/caja-search-engine.h77
-rw-r--r--libcaja-private/caja-sidebar-provider.c74
-rw-r--r--libcaja-private/caja-sidebar-provider.h61
-rw-r--r--libcaja-private/caja-sidebar.c128
-rw-r--r--libcaja-private/caja-sidebar.h84
-rw-r--r--libcaja-private/caja-signaller.c115
-rw-r--r--libcaja-private/caja-signaller.h46
-rw-r--r--libcaja-private/caja-thumbnails.c1084
-rw-r--r--libcaja-private/caja-thumbnails.h80
-rw-r--r--libcaja-private/caja-trash-monitor.c250
-rw-r--r--libcaja-private/caja-trash-monitor.h70
-rw-r--r--libcaja-private/caja-tree-view-drag-dest.c1306
-rw-r--r--libcaja-private/caja-tree-view-drag-dest.h106
-rw-r--r--libcaja-private/caja-ui-utilities.c256
-rw-r--r--libcaja-private/caja-ui-utilities.h43
-rw-r--r--libcaja-private/caja-undo-manager.c320
-rw-r--r--libcaja-private/caja-undo-manager.h82
-rw-r--r--libcaja-private/caja-undo-private.h36
-rw-r--r--libcaja-private/caja-undo-signal-handlers.c345
-rw-r--r--libcaja-private/caja-undo-signal-handlers.h37
-rw-r--r--libcaja-private/caja-undo-transaction.c343
-rw-r--r--libcaja-private/caja-undo-transaction.h80
-rw-r--r--libcaja-private/caja-undo.c218
-rw-r--r--libcaja-private/caja-undo.h77
-rw-r--r--libcaja-private/caja-users-groups-cache.c295
-rw-r--r--libcaja-private/caja-users-groups-cache.h34
-rw-r--r--libcaja-private/caja-vfs-directory.c170
-rw-r--r--libcaja-private/caja-vfs-directory.h57
-rw-r--r--libcaja-private/caja-vfs-file.c781
-rw-r--r--libcaja-private/caja-vfs-file.h57
-rw-r--r--libcaja-private/caja-view-factory.c126
-rw-r--r--libcaja-private/caja-view-factory.h75
-rw-r--r--libcaja-private/caja-view.c330
-rw-r--r--libcaja-private/caja-view.h193
-rw-r--r--libcaja-private/caja-window-info.c291
-rw-r--r--libcaja-private/caja-window-info.h189
-rw-r--r--libcaja-private/caja-window-slot-info.c157
-rw-r--r--libcaja-private/caja-window-slot-info.h97
162 files changed, 83625 insertions, 0 deletions
diff --git a/libcaja-private/Makefile.am b/libcaja-private/Makefile.am
new file mode 100644
index 00000000..5d7bf9eb
--- /dev/null
+++ b/libcaja-private/Makefile.am
@@ -0,0 +1,246 @@
+include $(top_srcdir)/Makefile.shared
+
+noinst_LTLIBRARIES=libcaja-private.la
+
+INCLUDES = \
+ -I$(top_srcdir) \
+ -I$(top_builddir) \
+ -I$(top_srcdir)/cut-n-paste-code \
+ $(CORE_CFLAGS) \
+ $(WARNING_CFLAGS) \
+ $(DISABLE_DEPRECATED_CFLAGS) \
+ $(TRACKER_CFLAGS) \
+ $(BEAGLE_CFLAGS) \
+ -DDATADIR=\""$(datadir)"\" \
+ -DSYSCONFDIR=\""$(sysconfdir)"\" \
+ -DCAJA_DATADIR=\""$(datadir)/caja"\" \
+ -DCAJA_EXTENSIONDIR=\""$(libdir)/caja/extensions-2.0"\" \
+ $(UNIQUE_CFLAGS) \
+ $(NULL)
+
+dependency_static_libs = \
+ $(top_builddir)/cut-n-paste-code/libegg/libegg.la \
+ $(NULL)
+
+libcaja_private_la_LDFLAGS = \
+ -no-undefined \
+ $(NULL)
+
+libcaja_private_la_LIBADD = \
+ $(dependency_static_libs) \
+ $(SELINUX_LIBS) \
+ $(BEAGLE_LIBS) \
+ $(TRACKER_LIBS) \
+ $(top_builddir)/eel/libeel-2.la \
+ $(top_builddir)/libcaja-extension/libcaja-extension.la \
+ $(CORE_LIBS) \
+ $(NULL)
+
+marshal_sources = \
+ caja-marshal.h \
+ caja-marshal-guts.c \
+ $(NULL)
+
+libcaja_private_la_SOURCES = \
+ caja-autorun.c \
+ caja-autorun.h \
+ caja-bookmark.c \
+ caja-bookmark.h \
+ caja-cell-renderer-pixbuf-emblem.c \
+ caja-cell-renderer-pixbuf-emblem.h \
+ caja-cell-renderer-text-ellipsized.c \
+ caja-cell-renderer-text-ellipsized.h \
+ caja-clipboard-monitor.c \
+ caja-clipboard-monitor.h \
+ caja-clipboard.c \
+ caja-clipboard.h \
+ caja-column-chooser.c \
+ caja-column-chooser.h \
+ caja-column-utilities.c \
+ caja-column-utilities.h \
+ caja-customization-data.c \
+ caja-customization-data.h \
+ caja-debug-log.c \
+ caja-debug-log.h \
+ caja-default-file-icon.c \
+ caja-default-file-icon.h \
+ caja-desktop-directory-file.c \
+ caja-desktop-directory-file.h \
+ caja-desktop-directory.c \
+ caja-desktop-directory.h \
+ caja-desktop-icon-file.c \
+ caja-desktop-icon-file.h \
+ caja-desktop-link-monitor.c \
+ caja-desktop-link-monitor.h \
+ caja-desktop-link.c \
+ caja-desktop-link.h \
+ caja-directory-async.c \
+ caja-directory-background.c \
+ caja-directory-background.h \
+ caja-directory-notify.h \
+ caja-directory-private.h \
+ caja-directory.c \
+ caja-directory.h \
+ caja-dnd.c \
+ caja-dnd.h \
+ caja-emblem-utils.c \
+ caja-emblem-utils.h \
+ caja-entry.c \
+ caja-entry.h \
+ caja-file-attributes.h \
+ caja-file-changes-queue.c \
+ caja-file-changes-queue.h \
+ caja-file-conflict-dialog.c \
+ caja-file-conflict-dialog.h \
+ caja-file-dnd.c \
+ caja-file-dnd.h \
+ caja-file-operations.c \
+ caja-file-operations.h \
+ caja-file-private.h \
+ caja-file-queue.c \
+ caja-file-queue.h \
+ caja-file-utilities.c \
+ caja-file-utilities.h \
+ caja-file.c \
+ caja-file.h \
+ caja-global-preferences.c \
+ caja-global-preferences.h \
+ caja-horizontal-splitter.c \
+ caja-horizontal-splitter.h \
+ caja-icon-canvas-item.c \
+ caja-icon-canvas-item.h \
+ caja-icon-container.c \
+ caja-icon-container.h \
+ caja-icon-dnd.c \
+ caja-icon-dnd.h \
+ caja-icon-private.h \
+ caja-icon-info.c \
+ caja-icon-info.h \
+ caja-icon-names.h \
+ caja-idle-queue.c \
+ caja-idle-queue.h \
+ caja-iso9660.h \
+ caja-keep-last-vertical-box.c \
+ caja-keep-last-vertical-box.h \
+ caja-lib-self-check-functions.c \
+ caja-lib-self-check-functions.h \
+ caja-link.c \
+ caja-link.h \
+ caja-marshal.c \
+ caja-marshal.h \
+ caja-merged-directory.c \
+ caja-merged-directory.h \
+ caja-metadata.h \
+ caja-metadata.c \
+ caja-mime-actions.c \
+ caja-mime-actions.h \
+ caja-mime-application-chooser.c \
+ caja-mime-application-chooser.h \
+ caja-module.c \
+ caja-module.h \
+ caja-monitor.c \
+ caja-monitor.h \
+ caja-open-with-dialog.c \
+ caja-open-with-dialog.h \
+ caja-progress-info.c \
+ caja-progress-info.h \
+ caja-program-choosing.c \
+ caja-program-choosing.h \
+ caja-recent.c \
+ caja-recent.h \
+ caja-saved-search-file.c \
+ caja-saved-search-file.h \
+ caja-search-directory.c \
+ caja-search-directory.h \
+ caja-search-directory-file.c \
+ caja-search-directory-file.h \
+ caja-search-engine.c \
+ caja-search-engine.h \
+ caja-search-engine-simple.c \
+ caja-search-engine-simple.h \
+ caja-search-engine-beagle.c \
+ caja-search-engine-beagle.h \
+ caja-search-engine-tracker.c \
+ caja-search-engine-tracker.h \
+ caja-sidebar-provider.c \
+ caja-sidebar-provider.h \
+ caja-sidebar.c \
+ caja-sidebar.h \
+ caja-signaller.h \
+ caja-signaller.c \
+ caja-query.c \
+ caja-query.h \
+ caja-thumbnails.c \
+ caja-thumbnails.h \
+ caja-trash-monitor.c \
+ caja-trash-monitor.h \
+ caja-tree-view-drag-dest.c \
+ caja-tree-view-drag-dest.h \
+ caja-ui-utilities.c \
+ caja-ui-utilities.h \
+ caja-undo-manager.c \
+ caja-undo-manager.h \
+ caja-undo-private.h \
+ caja-undo-signal-handlers.c \
+ caja-undo-signal-handlers.h \
+ caja-undo-transaction.c \
+ caja-undo-transaction.h \
+ caja-undo.c \
+ caja-undo.h \
+ caja-users-groups-cache.c \
+ caja-users-groups-cache.h \
+ caja-vfs-directory.c \
+ caja-vfs-directory.h \
+ caja-vfs-file.c \
+ caja-vfs-file.h \
+ caja-view-factory.c \
+ caja-view-factory.h \
+ caja-view.c \
+ caja-view.h \
+ caja-window-info.c \
+ caja-window-info.h \
+ caja-window-slot-info.c \
+ caja-window-slot-info.h \
+ $(NULL)
+
+$(lib_LTLIBRARIES): $(dependency_static_libs)
+
+caja-marshal.h: caja-marshal.list $(GLIB_GENMARSHAL)
+ $(AM_V_GEN)$(GLIB_GENMARSHAL) $< --header --prefix=caja_marshal > $@
+caja-marshal-guts.c: caja-marshal.list $(GLIB_GENMARSHAL)
+ $(AM_V_GEN)$(GLIB_GENMARSHAL) $< --body --prefix=caja_marshal > $@
+
+$(libcaja_private_la_OBJECTS): $(marshal_sources)
+
+
+schema_in_files = apps_caja_preferences.schemas.in
+schemadir = $(MATECONF_SCHEMA_FILE_DIR)
+schema_DATA = $(schema_in_files:.schemas.in=.schemas)
+
+@INTLTOOL_SCHEMAS_RULE@
+
+# don't do this if we are building in eg. rpm
+if MATECONF_SCHEMAS_INSTALL
+install-data-local:
+ if test -z "$(DESTDIR)" ; then \
+ for p in $(schema_DATA) ; do \
+ MATECONF_CONFIG_SOURCE=$(MATECONF_SCHEMA_CONFIG_SOURCE) $(MATECONFTOOL) --makefile-install-rule $$p; \
+ done \
+ fi
+endif
+
+EXTRA_DIST = \
+ caja-marshal.list \
+ $(schema_in_files) \
+ $(BEAGLE_SOURCES) \
+ $(TRACKER_SOURCES) \
+ $(NULL)
+
+CLEANFILES = \
+ $(marshal_sources) \
+ $(schema_DATA) \
+ $(NULL)
+
+dist-hook:
+ cd $(distdir); rm -f $(CLEANFILES)
+
diff --git a/libcaja-private/README b/libcaja-private/README
new file mode 100644
index 00000000..94f6d9f5
--- /dev/null
+++ b/libcaja-private/README
@@ -0,0 +1,13 @@
+README for caja/libcaja-private
+
+This library, libcaja-private, is totally private to caja.
+
+If you are writing a caja component, you should not use this
+library or link with it. The code in here is internal to caja
+and not available for public consumption.
+
+If you think that there is something interesting in this library that
+you would like to use in a third party component, please send mail to
+the caja mailing list at:
+
diff --git a/libcaja-private/apps_caja_preferences.schemas.in b/libcaja-private/apps_caja_preferences.schemas.in
new file mode 100644
index 00000000..80b3c8a7
--- /dev/null
+++ b/libcaja-private/apps_caja_preferences.schemas.in
@@ -0,0 +1,1205 @@
+<mateconfschemafile>
+ <schemalist>
+
+ <!-- Keep the defaults in sync with the emergency fallbacks
+ in caja-global-preferences.c -->
+
+ <!-- General preferences -->
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/exit_with_last_window</key>
+ <applyto>/apps/caja/preferences/exit_with_last_window</applyto>
+ <owner>caja</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>Caja will exit when last window destroyed.</short>
+ <long>
+ If set to true, then Caja will exit when all windows are destroyed.
+ This is the default setting. If set to false, it can be started without
+ any window, so caja can serve as a daemon to monitor media automount,
+ or similar tasks.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/desktop/mate/file_views/show_hidden_files</key>
+ <applyto>/desktop/mate/file_views/show_hidden_files</applyto>
+ <owner>caja</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>Whether to show hidden files</short>
+ <long>
+ If set to true, then hidden files are shown in
+ the file manager. Hidden files are either dotfiles or are
+ listed in the folder's .hidden file.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/desktop/mate/file_views/show_backup_files</key>
+ <applyto>/desktop/mate/file_views/show_backup_files</applyto>
+ <owner>caja</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>Whether to show backup files</short>
+ <long>
+ If set to true, then backup files such as those created
+ by Emacs are displayed. Currently, only files ending in
+ a tilde (~) are considered backup files.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/always_use_browser</key>
+ <applyto>/apps/caja/preferences/always_use_browser</applyto>
+ <owner>caja</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Enables the classic Caja behavior, where all windows are browsers</short>
+ <long>
+ If set to true, then all Caja windows will be browser windows. This is how
+ Caja used to behave before version 2.6, and some people prefer this behavior.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/always_use_location_entry</key>
+ <applyto>/apps/caja/preferences/always_use_location_entry</applyto>
+ <owner>caja</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>Always use the location entry, instead of the pathbar</short>
+ <long>
+ If set to true, then Caja browser windows will always use a textual
+ input entry for the location toolbar, instead of the pathbar.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/desktop/mate/file_views/tabs_open_position</key>
+ <applyto>/desktop/mate/file_views/tabs_open_position</applyto>
+ <owner>caja</owner>
+ <type>string</type>
+ <default>after_current_tab</default>
+ <locale name="C">
+ <short>Where to position newly open tabs in browser windows.</short>
+ <long>
+ If set to "after_current_tab", then new tabs are inserted after the current tab.
+ If set to "end", then new tabs are appended to the end of the tab list.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/media_automount</key>
+ <applyto>/apps/caja/preferences/media_automount</applyto>
+ <owner>caja</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Whether to automatically mount media</short>
+ <long>
+ If set to true, then Caja will automatically mount media
+ such as user-visible hard disks and removable media on start-up
+ and media insertion.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/media_automount_open</key>
+ <applyto>/apps/caja/preferences/media_automount_open</applyto>
+ <owner>caja</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Whether to automatically open a folder for automounted media</short>
+ <long>
+ If set to true, then Caja will automatically open a folder when
+ media is automounted. This only applies to media where no known
+ x-content/* type was detected; for media where a known x-content
+ type is detected, the user configurable action will be taken instead.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/media_autorun_never</key>
+ <applyto>/apps/caja/preferences/media_autorun_never</applyto>
+ <owner>caja</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>Never prompt or autorun/autostart programs when media are inserted</short>
+ <long>
+ If set to true, then Caja will never prompt nor autorun/autostart
+ programs when a medium is inserted.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/media_autorun_x_content_start_app</key>
+ <applyto>/apps/caja/preferences/media_autorun_x_content_start_app</applyto>
+ <owner>caja</owner>
+ <type>list</type>
+ <list_type>string</list_type>
+ <default>[x-content/software]</default>
+ <locale name="C">
+ <short>List of x-content/* types where the preferred application will be launched</short>
+ <long>
+ List of x-content/* types for which the user have chosen to
+ start an application in the preference capplet. The
+ preferred application for the given type will be started on
+ insertion on media matching these types.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/media_autorun_x_content_ignore</key>
+ <applyto>/apps/caja/preferences/media_autorun_x_content_ignore</applyto>
+ <owner>caja</owner>
+ <type>list</type>
+ <list_type>string</list_type>
+ <default>[]</default>
+ <locale name="C">
+ <short>List of x-content/* types set to "Do Nothing"</short>
+ <long>
+ List of x-content/* types for which the user have chosen
+ "Do Nothing" in the preference capplet. No prompt will be
+ shown nor will any matching application be started on
+ insertion of media matching these types.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/media_autorun_x_content_open_folder</key>
+ <applyto>/apps/caja/preferences/media_autorun_x_content_open_folder</applyto>
+ <owner>caja</owner>
+ <type>list</type>
+ <list_type>string</list_type>
+ <default>[]</default>
+ <locale name="C">
+ <short>List of x-content/* types set to "Open Folder"</short>
+ <long>
+ List of x-content/* types for which the user have chosen
+ "Open Folder" in the preferences capplet. A folder window
+ will be opened on insertion of media matching these types.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/navigation_window_saved_geometry</key>
+ <applyto>/apps/caja/preferences/navigation_window_saved_geometry</applyto>
+ <owner>caja</owner>
+ <type>string</type>
+ <default></default>
+ <locale name="C">
+ <short>The geometry string for a navigation window.</short>
+ <long>
+ A string containing the saved geometry and coordinates string for
+ navigation windows.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/navigation_window_saved_maximized</key>
+ <applyto>/apps/caja/preferences/navigation_window_saved_maximized</applyto>
+ <owner>caja</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>Whether the navigation window should be maximized.</short>
+ <long>
+ Whether the navigation window should be maximized by default.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/confirm_trash</key>
+ <applyto>/apps/caja/preferences/confirm_trash</applyto>
+ <owner>caja</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Whether to ask for confirmation when deleting files, or emptying Trash</short>
+ <long>
+ If set to true, then Caja will ask for confirmation when
+ you attempt to delete files, or empty the Trash.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/enable_delete</key>
+ <applyto>/apps/caja/preferences/enable_delete</applyto>
+ <owner>caja</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>Whether to enable immediate deletion</short>
+ <long>
+ If set to true, then Caja will have a feature allowing
+ you to delete a file immediately and in-place, instead of moving it
+ to the trash. This feature can be dangerous, so use caution.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/show_icon_text</key>
+ <applyto>/apps/caja/preferences/show_icon_text</applyto>
+ <owner>caja</owner>
+ <type>string</type>
+ <default>local_only</default>
+ <locale name="C">
+ <short>When to show preview text in icons</short>
+ <long>
+ Speed tradeoff for when to show a preview of text file contents
+ in the file's icon.
+ If set to "always" then always show previews,
+ even if the folder is on a remote server.
+ If set to "local_only" then only show previews for local file systems.
+ If set to "never" then never bother to read preview data.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/show_directory_item_counts</key>
+ <applyto>/apps/caja/preferences/show_directory_item_counts</applyto>
+ <owner>caja</owner>
+ <type>string</type>
+ <default>local_only</default>
+ <locale name="C">
+ <short>When to show number of items in a folder</short>
+ <long>
+ Speed tradeoff for when to show the number of items in a
+ folder. If set to "always" then always show item counts,
+ even if the folder is on a remote server.
+ If set to "local_only" then only show counts for local file systems.
+ If set to "never" then never bother to compute item counts.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/click_policy</key>
+ <applyto>/apps/caja/preferences/click_policy</applyto>
+ <owner>caja</owner>
+ <type>string</type>
+ <default>double</default>
+ <locale name="C">
+ <short>Type of click used to launch/open files</short>
+ <long>
+ Possible values are "single" to launch files on a single click,
+ or "double" to launch them on a double click.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/executable_text_activation</key>
+ <applyto>/apps/caja/preferences/executable_text_activation</applyto>
+ <owner>caja</owner>
+ <type>string</type>
+ <default>ask</default>
+ <locale name="C">
+ <short>What to do with executable text files when activated</short>
+ <long>
+ What to do with executable text files when they are activated
+ (single or double clicked).
+ Possible values are "launch" to launch them as programs,
+ "ask" to ask what to do via a dialog, and "display" to display
+ them as text files.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/install_mime_activation</key>
+ <applyto>/apps/caja/preferences/install_mime_activation</applyto>
+ <owner>caja</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Show the package installer for unknown mime types</short>
+ <long>
+ Whether to show the user a package installer dialog in case an unknown
+ mime type is opened, in order to search for an application to handle it.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/mouse_use_extra_buttons</key>
+ <applyto>/apps/caja/preferences/mouse_use_extra_buttons</applyto>
+ <owner>caja</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Use extra mouse button events in Caja' browser window</short>
+ <long>
+ For users with mice that have "Forward" and "Back" buttons, this key will determine
+ if any action is taken inside of Caja when either is pressed.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/mouse_forward_button</key>
+ <applyto>/apps/caja/preferences/mouse_forward_button</applyto>
+ <owner>caja</owner>
+ <type>int</type>
+ <default>9</default>
+ <locale name="C">
+ <short>Mouse button to activate the "Forward" command in browser window</short>
+ <long>
+ For users with mice that have buttons for "Forward" and "Back", this key will set
+ which button activates the "Forward" command in a browser window.
+ Possible values range between 6 and 14.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/mouse_back_button</key>
+ <applyto>/apps/caja/preferences/mouse_back_button</applyto>
+ <owner>caja</owner>
+ <type>int</type>
+ <default>8</default>
+ <locale name="C">
+ <short>Mouse button to activate the "Back" command in browser window</short>
+ <long>
+ For users with mice that have buttons for "Forward" and "Back", this key will set
+ which button activates the "Back" command in a browser window.
+ Possible values range between 6 and 14.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/theme</key>
+ <applyto>/apps/caja/preferences/theme</applyto>
+ <owner>caja</owner>
+ <type>string</type>
+ <default>default</default>
+ <locale name="C">
+ <short>Current Caja theme (deprecated)</short>
+ <long>
+ Name of the Caja theme to use.
+ This has been deprecated as of Caja 2.2.
+ Please use the icon theme instead.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/show_image_thumbnails</key>
+ <applyto>/apps/caja/preferences/show_image_thumbnails</applyto>
+ <owner>caja</owner>
+ <type>string</type>
+ <default>local_only</default>
+ <locale name="C">
+ <short>When to show thumbnails of image files</short>
+ <long>
+ Speed tradeoff for when to show an image file as a thumbnail.
+ If set to "always" then always thumbnail,
+ even if the folder is on a remote server.
+ If set to "local_only" then only show thumbnails for local file systems.
+ If set to "never" then never bother to thumbnail images,
+ just use a generic icon.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/thumbnail_limit</key>
+ <applyto>/apps/caja/preferences/thumbnail_limit</applyto>
+ <owner>caja</owner>
+ <type>int</type>
+ <default>10485760</default>
+ <locale name="C">
+ <short>Maximum image size for thumbnailing</short>
+ <long>
+ Images over this size (in bytes) won't be
+ thumbnailed. The purpose of this setting is to
+ avoid thumbnailing large images that may
+ take a long time to load or use lots of memory.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/directory_limit</key>
+ <applyto>/apps/caja/preferences/directory_limit</applyto>
+ <owner>caja</owner>
+ <type>int</type>
+ <default>-1</default>
+ <locale name="C">
+ <short>Maximum handled files in a folder</short>
+ <long>
+ Folders over this size will be truncated to
+ around this size. The purpose of this is to avoid unintentionally
+ blowing the heap and killing Caja on massive folders.
+ A negative value denotes no limit. The limit is approximate due
+ to the reading of folders chunk-wise.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/preview_sound</key>
+ <applyto>/apps/caja/preferences/preview_sound</applyto>
+ <owner>caja</owner>
+ <type>string</type>
+ <default>local_only</default>
+ <locale name="C">
+ <short>Whether to preview sounds when mousing over an icon</short>
+ <long>
+ Speed tradeoff for when to preview a sound file when mousing
+ over a files icon.
+ If set to "always" then always plays the sound,
+ even if the file is on a remote server.
+ If set to "local_only" then only plays previews on local file systems.
+ If set to "never" then it never previews sound.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/show_advanced_permissions</key>
+ <applyto>/apps/caja/preferences/show_advanced_permissions</applyto>
+ <owner>caja</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>Show advanced permissions in the file property dialog</short>
+ <long>
+ If set to true, then Caja lets you edit and display file
+ permissions in a more unix-like way, accessing some more
+ esoteric options.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/sort_directories_first</key>
+ <applyto>/apps/caja/preferences/sort_directories_first</applyto>
+ <owner>caja</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Show folders first in windows</short>
+ <long>
+ If set to true, then Caja shows folders prior to
+ showing files in the icon and list views.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/show_desktop</key>
+ <applyto>/apps/caja/preferences/show_desktop</applyto>
+ <owner>caja</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Caja handles drawing the desktop</short>
+ <long>
+ If set to true, then Caja will draw the icons on the
+ desktop.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/desktop_font</key>
+ <applyto>/apps/caja/preferences/desktop_font</applyto>
+ <owner>caja</owner>
+ <type>string</type>
+ <default>Sans 10</default>
+ <locale name="C">
+ <default><!-- Translators: please note this can choose the size. e.g.
+"Sans 15". Please do not change "Sans", only change the size if you need to. In
+most cases, this should be left alone. -->Sans 10</default>
+ <short>Desktop font</short>
+ <long>
+ The font description used for the icons on the desktop.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/desktop_is_home_dir</key>
+ <applyto>/apps/caja/preferences/desktop_is_home_dir</applyto>
+ <owner>caja</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>Caja uses the users home folder as the desktop</short>
+ <long>
+ If set to true, then Caja will use the user's home
+ folder as the desktop. If it is false, then it will use
+ ~/Desktop as the desktop.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/search_bar_type</key>
+ <applyto>/apps/caja/preferences/search_bar_type</applyto>
+ <owner>caja</owner>
+ <type>string</type>
+ <default>search_by_text</default>
+ <locale name="C">
+ <short>Criteria for search bar searching</short>
+ <long>
+ Criteria when matching files searched for in the search bar.
+ If set to "search_by_text", then Caja will Search for files
+ by file name only.
+ If set to "search_by_text_and_properties", then Caja will
+ search for files by file name and file properties.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/background_set</key>
+ <applyto>/apps/caja/preferences/background_set</applyto>
+ <owner>caja</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>Custom Background</short>
+ <long>Whether a custom default folder background has been set.</long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/background_color</key>
+ <applyto>/apps/caja/preferences/background_color</applyto>
+ <owner>caja</owner>
+ <type>string</type>
+ <default>#ffffff</default>
+ <locale name="C">
+ <short>Default Background Color</short>
+ <long>Color for the default folder background. Only used if background_set is true.</long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/background_filename</key>
+ <applyto>/apps/caja/preferences/background_filename</applyto>
+ <owner>caja</owner>
+ <type>string</type>
+ <default></default>
+ <locale name="C">
+ <short>Default Background Filename</short>
+ <long>Filename for the default folder background. Only used if background_set is true.</long>
+ </locale>
+ </schema>
+
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/side_pane_background_set</key>
+ <applyto>/apps/caja/preferences/side_pane_background_set</applyto>
+ <owner>caja</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>Custom Side Pane Background Set</short>
+ <long>Whether a custom default side pane background has been set.</long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/side_pane_background_color</key>
+ <applyto>/apps/caja/preferences/side_pane_background_color</applyto>
+ <owner>caja</owner>
+ <type>string</type>
+ <default>#ffffff</default>
+ <locale name="C">
+ <short>Default Side Pane Background Color</short>
+ <long>Filename for the default side pane background. Only used if side_pane_background_set is true.</long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/side_pane_background_filename</key>
+ <applyto>/apps/caja/preferences/side_pane_background_filename</applyto>
+ <owner>caja</owner>
+ <type>string</type>
+ <default></default>
+ <locale name="C">
+ <short>Default Side Pane Background Filename</short>
+ <long>Filename for the default side pane background. Only used if side_pane_background_set is true.</long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/default_folder_viewer</key>
+ <applyto>/apps/caja/preferences/default_folder_viewer</applyto>
+ <owner>caja</owner>
+ <type>string</type>
+ <default>icon_view</default>
+ <locale name="C">
+ <short>Default folder viewer</short>
+ <long>
+ When a folder is visited this viewer is used unless you have selected
+ another view for that particular folder. Possible values are "list_view",
+ "icon_view" and "compact_view".
+ </long>
+ </locale>
+ </schema>
+
+ <!-- Icon View -->
+
+ <schema>
+ <key>/schemas/apps/caja/icon_view/captions</key>
+ <applyto>/apps/caja/icon_view/captions</applyto>
+ <owner>caja</owner>
+ <type>list</type>
+ <list_type>string</list_type>
+ <default>[none,size,date_modified]</default>
+ <locale name="C">
+ <short>List of possible captions on icons</short>
+ <long><!-- Translators: date_modified - mtime, the last time file contents were changed
+ date_changed - ctime, the last time file meta-information changed -->
+ A list of captions below an icon in the icon view and
+ the desktop. The actual number of captions shown depends on
+ the zoom level. Possible values are:
+ "size", "type", "date_modified", "date_changed", "date_accessed", "owner",
+ "group", "permissions", "octal_permissions" and "mime_type".
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/icon_view/default_sort_order</key>
+ <applyto>/apps/caja/icon_view/default_sort_order</applyto>
+ <owner>caja</owner>
+ <type>string</type>
+ <default>name</default>
+ <locale name="C">
+ <short>Default sort order</short>
+ <long>
+ The default sort-order for items in the icon view. Possible
+ values are "name", "size", "type", "modification_date", and "emblems".
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/icon_view/default_sort_in_reverse_order</key>
+ <applyto>/apps/caja/icon_view/default_sort_in_reverse_order</applyto>
+ <owner>caja</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>Reverse sort order in new windows</short>
+ <long>
+ If true, files in new windows will be sorted in reverse order.
+ ie, if sorted by name, then instead of sorting the files from
+ "a" to "z", they will be sorted from "z" to "a"; if sorted by
+ size, instead of being incrementally they will be sorted
+ decrementally.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/icon_view/default_use_tighter_layout</key>
+ <applyto>/apps/caja/icon_view/default_use_tighter_layout</applyto>
+ <owner>caja</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>Use tighter layout in new windows</short>
+ <long>
+ If true, icons will be laid out tighter by default in new windows.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/icon_view/labels_beside_icons</key>
+ <applyto>/apps/caja/icon_view/labels_beside_icons</applyto>
+ <owner>caja</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>Put labels beside icons</short>
+ <long>
+ If true, labels will be placed beside icons rather than
+ underneath them.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/icon_view/default_use_manual_layout</key>
+ <applyto>/apps/caja/icon_view/default_use_manual_layout</applyto>
+ <owner>caja</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>Use manual layout in new windows</short>
+ <long>
+ If true, new windows will use manual layout by default.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/icon_view/default_zoom_level</key>
+ <applyto>/apps/caja/icon_view/default_zoom_level</applyto>
+ <owner>caja</owner>
+ <type>string</type>
+ <default>standard</default>
+ <locale name="C">
+ <short>Default icon zoom level</short>
+ <long>
+ Default zoom level used by the icon view.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/icon_view/thumbnail_size</key>
+ <applyto>/apps/caja/icon_view/thumbnail_size</applyto>
+ <owner>caja</owner>
+ <type>int</type>
+ <default>64</default>
+ <locale name="C">
+ <short>Default Thumbnail Icon Size</short>
+ <long>
+ The default size of an icon for a thumbnail in the icon view.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/icon_view/text_ellipsis_limit</key>
+ <applyto>/apps/caja/icon_view/text_ellipsis_limit</applyto>
+ <type>list</type>
+ <list_type>string</list_type>
+ <default>[3]</default>
+ <locale name="C">
+ <short>Text Ellipsis Limit</short>
+ <long>
+ <!-- TRANSLATORS: don't translate the zoom levels between quotes -->
+
+ A string specifying how parts of overlong file names
+ should be replaced by ellipses, depending on the zoom
+ level.
+ Each of the list entries is of the form "Zoom Level:Integer".
+ For each specified zoom level, if the given integer is
+ larger than 0, the file name will not exceed the given number of lines.
+ If the integer is 0 or smaller, no limit is imposed on the specified zoom level.
+ A default entry of the form "Integer" without any specified zoom level
+ is also allowed. It defines the maximum number of lines for all other zoom levels.
+ Examples:
+ 0 - always display overlong file names;
+ 3 - shorten file names if they exceed three lines;
+ smallest:5,smaller:4,0 - shorten file names if they exceed five lines
+ for zoom level "smallest". Shorten file names if they exceed four lines
+ for zoom level "smaller". Do not shorten file names for other zoom levels.
+
+ Available zoom levels:
+ smallest (33%), smaller (50%), small (66%), standard (100%), large (150%),
+ larger (200%), largest (400%)
+ </long>
+ </locale>
+ </schema>
+
+ <!-- Icon View -->
+ <schema>
+ <key>/schemas/apps/caja/compact_view/default_zoom_level</key>
+ <applyto>/apps/caja/compact_view/default_zoom_level</applyto>
+ <owner>caja</owner>
+ <type>string</type>
+ <default>standard</default>
+ <locale name="C">
+ <short>Default compact view zoom level</short>
+ <long>
+ Default zoom level used by the compact view.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/compact_view/all_columns_have_same_width</key>
+ <applyto>/apps/caja/compact_view/all_columns_have_same_width</applyto>
+ <owner>caja</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>All columns have same width</short>
+ <long>
+ If this preference is set, all columns in the compact view have the same
+ width. Otherwise, the width of each column is determined seperately.
+ </long>
+ </locale>
+ </schema>
+
+ <!-- List View -->
+
+ <schema>
+ <key>/schemas/apps/caja/list_view/default_sort_order</key>
+ <applyto>/apps/caja/list_view/default_sort_order</applyto>
+ <owner>caja</owner>
+ <type>string</type>
+ <default>name</default>
+ <locale name="C">
+ <short>Default sort order</short>
+ <long>
+ The default sort-order for the items in the list view. Possible
+ values are "name", "size", "type", and "modification_date".
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/list_view/default_sort_in_reverse_order</key>
+ <applyto>/apps/caja/list_view/default_sort_in_reverse_order</applyto>
+ <owner>caja</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>Reverse sort order in new windows</short>
+ <long>
+ If true, files in new windows will be sorted in reverse order.
+ ie, if sorted by name, then instead of sorting the files from
+ "a" to "z", they will be sorted from "z" to "a".
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/list_view/default_zoom_level</key>
+ <applyto>/apps/caja/list_view/default_zoom_level</applyto>
+ <owner>caja</owner>
+ <type>string</type>
+ <default>smaller</default>
+ <locale name="C">
+ <short>Default list zoom level</short>
+ <long>
+ Default zoom level used by the list view.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/list_view/default_visible_columns</key>
+ <applyto>/apps/caja/list_view/default_visible_columns</applyto>
+ <owner>caja</owner>
+ <type>list</type>
+ <list_type>string</list_type>
+ <default>[name,size,type,date_modified]</default>
+ <locale name="C">
+ <short>Default list of columns visible in the list view</short>
+ <long>
+ Default list of columns visible in the list view.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/list_view/default_column_order</key>
+ <applyto>/apps/caja/list_view/default_column_order</applyto>
+ <owner>caja</owner>
+ <type>list</type>
+ <list_type>string</list_type>
+ <default>[name,size,type,date_modified]</default>
+ <locale name="C">
+ <short>Default column order in the list view</short>
+ <long>
+ Default column order in the list view.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/date_format</key>
+ <applyto>/apps/caja/preferences/date_format</applyto>
+ <owner>caja</owner>
+ <type>string</type>
+ <default>locale</default>
+ <locale name="C">
+ <short>Date Format</short>
+ <long>
+ The format of file dates. Possible values are "locale",
+ "iso", and "informal".
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/sidebar_width</key>
+ <applyto>/apps/caja/preferences/sidebar_width</applyto>
+ <owner>caja</owner>
+ <type>int</type>
+ <default>148</default>
+ <locale name="C">
+ <short>Width of the side pane</short>
+ <long>
+ The default width of the side pane in new windows.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/start_with_toolbar</key>
+ <applyto>/apps/caja/preferences/start_with_toolbar</applyto>
+ <owner>caja</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Show toolbar in new windows</short>
+ <long>
+ If set to true, newly opened windows will have toolbars visible.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/start_with_location_bar</key>
+ <applyto>/apps/caja/preferences/start_with_location_bar</applyto>
+ <owner>caja</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Show location bar in new windows</short>
+ <long>
+ If set to true, newly opened windows will have the
+ location bar visible.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/start_with_status_bar</key>
+ <applyto>/apps/caja/preferences/start_with_status_bar</applyto>
+ <owner>caja</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Show status bar in new windows</short>
+ <long>
+ If set to true, newly opened windows will have the status
+ bar visible.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/start_with_sidebar</key>
+ <applyto>/apps/caja/preferences/start_with_sidebar</applyto>
+ <owner>caja</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Show side pane in new windows</short>
+ <long>
+ If set to true, newly opened windows will have the side
+ pane visible.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/preferences/side_pane_view</key>
+ <applyto>/apps/caja/preferences/side_pane_view</applyto>
+ <owner>caja</owner>
+ <type>string</type>
+ <default></default>
+ <locale name="C">
+ <short>Side pane view</short>
+ <long>
+ The side pane view to show in newly opened windows.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/sidebar_panels/tree/show_only_directories</key>
+ <applyto>/apps/caja/sidebar_panels/tree/show_only_directories</applyto>
+ <owner>caja</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Only show folders in the tree side pane</short>
+ <long>
+ If set to true, Caja will only show folders
+ in the tree side pane. Otherwise it will show both folders
+ and files.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/desktop/home_icon_visible</key>
+ <applyto>/apps/caja/desktop/home_icon_visible</applyto>
+ <owner>caja</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Home icon visible on desktop</short>
+ <long>
+ If this is set to true, an icon linking to the home folder
+ will be put on the desktop.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/desktop/computer_icon_visible</key>
+ <applyto>/apps/caja/desktop/computer_icon_visible</applyto>
+ <owner>caja</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Computer icon visible on desktop</short>
+ <long>
+ If this is set to true, an icon linking to the computer location
+ will be put on the desktop.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/desktop/trash_icon_visible</key>
+ <applyto>/apps/caja/desktop/trash_icon_visible</applyto>
+ <owner>caja</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Trash icon visible on desktop</short>
+ <long>
+ If this is set to true, an icon linking to the trash
+ will be put on the desktop.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/desktop/volumes_visible</key>
+ <applyto>/apps/caja/desktop/volumes_visible</applyto>
+ <owner>caja</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Show mounted volumes on the desktop</short>
+ <long>
+ If this is set to true, icons linking to mounted
+ volumes will be put on the desktop.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/desktop/network_icon_visible</key>
+ <applyto>/apps/caja/desktop/network_icon_visible</applyto>
+ <owner>caja</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>Network Servers icon visible on the desktop</short>
+ <long>
+ If this is set to true, an icon linking to the Network Servers view
+ will be put on the desktop.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/desktop/computer_icon_name</key>
+ <applyto>/apps/caja/desktop/computer_icon_name</applyto>
+ <owner>caja</owner>
+ <type>string</type>
+ <locale name="C">
+ <short>Desktop computer icon name</short>
+ <long>
+ This name can be set if you want a custom name
+ for the computer icon on the desktop.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/desktop/home_icon_name</key>
+ <applyto>/apps/caja/desktop/home_icon_name</applyto>
+ <owner>caja</owner>
+ <type>string</type>
+ <locale name="C">
+ <short>Desktop home icon name</short>
+ <long>
+ This name can be set if you want a custom name
+ for the home icon on the desktop.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/desktop/trash_icon_name</key>
+ <applyto>/apps/caja/desktop/trash_icon_name</applyto>
+ <owner>caja</owner>
+ <type>string</type>
+ <locale name="C">
+ <short>Desktop trash icon name</short>
+ <long>
+ This name can be set if you want a custom name
+ for the trash icon on the desktop.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/desktop/network_icon_name</key>
+ <applyto>/apps/caja/desktop/network_icon_name</applyto>
+ <owner>caja</owner>
+ <type>string</type>
+ <locale name="C">
+ <short>Network servers icon name</short>
+ <long>
+ This name can be set if you want a custom name
+ for the network servers icon on the desktop.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/caja/desktop/text_ellipsis_limit</key>
+ <applyto>/apps/caja/desktop/text_ellipsis_limit</applyto>
+ <type>int</type>
+ <default>3</default>
+ <locale name="C">
+ <short>Text Ellipsis Limit</short>
+ <long>
+ An integer specifying how parts of overlong file names
+ should be replaced by ellipses on the desktop.
+ If the number is larger than 0, the file name will not exceed
+ the given number of lines. If the number is 0 or smaller, no
+ limit is imposed on the number of displayed lines.
+ </long>
+ </locale>
+ </schema>
+
+ </schemalist>
+</mateconfschemafile>
diff --git a/libcaja-private/caja-autorun.c b/libcaja-private/caja-autorun.c
new file mode 100644
index 00000000..eacef167
--- /dev/null
+++ b/libcaja-private/caja-autorun.c
@@ -0,0 +1,1434 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Author: David Zeuthen <[email protected]>
+ */
+
+#include <config.h>
+#include <string.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkx.h>
+#include <gio/gdesktopappinfo.h>
+#include <X11/XKBlib.h>
+#include <gdk/gdkkeysyms.h>
+
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-stock-dialogs.h>
+
+#include "caja-icon-info.h"
+#include "caja-global-preferences.h"
+#include "caja-file-operations.h"
+#include "caja-autorun.h"
+#include "caja-program-choosing.h"
+#include "caja-open-with-dialog.h"
+#include "caja-desktop-icon-file.h"
+#include "caja-file-utilities.h"
+
+enum
+{
+ AUTORUN_ASK,
+ AUTORUN_IGNORE,
+ AUTORUN_APP,
+ AUTORUN_OPEN_FOLDER,
+ AUTORUN_SEP,
+ AUTORUN_OTHER_APP,
+};
+enum
+{
+ COLUMN_AUTORUN_PIXBUF,
+ COLUMN_AUTORUN_NAME,
+ COLUMN_AUTORUN_APP_INFO,
+ COLUMN_AUTORUN_X_CONTENT_TYPE,
+ COLUMN_AUTORUN_ITEM_TYPE,
+};
+
+static gboolean should_autorun_mount (GMount *mount);
+
+static void caja_autorun_rebuild_combo_box (GtkWidget *combo_box);
+
+void
+caja_autorun_get_preferences (const char *x_content_type,
+ gboolean *pref_start_app,
+ gboolean *pref_ignore,
+ gboolean *pref_open_folder)
+{
+ char **x_content_start_app;
+ char **x_content_ignore;
+ char **x_content_open_folder;
+
+ g_return_if_fail (pref_start_app != NULL);
+ g_return_if_fail (pref_ignore != NULL);
+ g_return_if_fail (pref_open_folder != NULL);
+
+ *pref_start_app = FALSE;
+ *pref_ignore = FALSE;
+ *pref_open_folder = FALSE;
+ x_content_start_app = eel_preferences_get_string_array (CAJA_PREFERENCES_MEDIA_AUTORUN_X_CONTENT_START_APP);
+ x_content_ignore = eel_preferences_get_string_array (CAJA_PREFERENCES_MEDIA_AUTORUN_X_CONTENT_IGNORE);
+ x_content_open_folder = eel_preferences_get_string_array (CAJA_PREFERENCES_MEDIA_AUTORUN_X_CONTENT_OPEN_FOLDER);
+ if (x_content_start_app != NULL)
+ {
+ *pref_start_app = eel_g_strv_find (x_content_start_app, x_content_type) != -1;
+ }
+ if (x_content_ignore != NULL)
+ {
+ *pref_ignore = eel_g_strv_find (x_content_ignore, x_content_type) != -1;
+ }
+ if (x_content_open_folder != NULL)
+ {
+ *pref_open_folder = eel_g_strv_find (x_content_open_folder, x_content_type) != -1;
+ }
+ g_strfreev (x_content_ignore);
+ g_strfreev (x_content_start_app);
+ g_strfreev (x_content_open_folder);
+}
+
+static void
+remove_elem_from_str_array (char **v, const char *s)
+{
+ int n, m;
+
+ if (v == NULL)
+ {
+ return;
+ }
+
+ for (n = 0; v[n] != NULL; n++)
+ {
+ if (strcmp (v[n], s) == 0)
+ {
+ for (m = n + 1; v[m] != NULL; m++)
+ {
+ v[m - 1] = v[m];
+ }
+ v[m - 1] = NULL;
+ n--;
+ }
+ }
+}
+
+static char **
+add_elem_to_str_array (char **v, const char *s)
+{
+ guint len;
+ char **r;
+
+ len = v != NULL ? g_strv_length (v) : 0;
+ r = g_new0 (char *, len + 2);
+ memcpy (r, v, len * sizeof (char *));
+ r[len] = g_strdup (s);
+ r[len+1] = NULL;
+ g_free (v);
+
+ return r;
+}
+
+
+void
+caja_autorun_set_preferences (const char *x_content_type,
+ gboolean pref_start_app,
+ gboolean pref_ignore,
+ gboolean pref_open_folder)
+{
+ char **x_content_start_app;
+ char **x_content_ignore;
+ char **x_content_open_folder;
+
+ g_assert (x_content_type != NULL);
+
+ x_content_start_app = eel_preferences_get_string_array (CAJA_PREFERENCES_MEDIA_AUTORUN_X_CONTENT_START_APP);
+ x_content_ignore = eel_preferences_get_string_array (CAJA_PREFERENCES_MEDIA_AUTORUN_X_CONTENT_IGNORE);
+ x_content_open_folder = eel_preferences_get_string_array (CAJA_PREFERENCES_MEDIA_AUTORUN_X_CONTENT_OPEN_FOLDER);
+
+ remove_elem_from_str_array (x_content_start_app, x_content_type);
+ if (pref_start_app)
+ {
+ x_content_start_app = add_elem_to_str_array (x_content_start_app, x_content_type);
+ }
+ eel_preferences_set_string_array (CAJA_PREFERENCES_MEDIA_AUTORUN_X_CONTENT_START_APP, x_content_start_app);
+
+ remove_elem_from_str_array (x_content_ignore, x_content_type);
+ if (pref_ignore)
+ {
+ x_content_ignore = add_elem_to_str_array (x_content_ignore, x_content_type);
+ }
+ eel_preferences_set_string_array (CAJA_PREFERENCES_MEDIA_AUTORUN_X_CONTENT_IGNORE, x_content_ignore);
+
+ remove_elem_from_str_array (x_content_open_folder, x_content_type);
+ if (pref_open_folder)
+ {
+ x_content_open_folder = add_elem_to_str_array (x_content_open_folder, x_content_type);
+ }
+ eel_preferences_set_string_array (CAJA_PREFERENCES_MEDIA_AUTORUN_X_CONTENT_OPEN_FOLDER, x_content_open_folder);
+
+ g_strfreev (x_content_open_folder);
+ g_strfreev (x_content_ignore);
+ g_strfreev (x_content_start_app);
+
+}
+
+static gboolean
+combo_box_separator_func (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ char *str;
+
+ gtk_tree_model_get (model, iter,
+ 1, &str,
+ -1);
+ if (str != NULL)
+ {
+ g_free (str);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+typedef struct
+{
+ guint changed_signal_id;
+ GtkWidget *combo_box;
+
+ char *x_content_type;
+ gboolean include_ask;
+ gboolean include_open_with_other_app;
+
+ gboolean update_settings;
+ CajaAutorunComboBoxChanged changed_cb;
+ gpointer user_data;
+
+ gboolean other_application_selected;
+} CajaAutorunComboBoxData;
+
+static void
+caja_autorun_combobox_data_destroy (CajaAutorunComboBoxData *data)
+{
+ /* signal handler may be automatically disconnected by destroying the widget */
+ if (g_signal_handler_is_connected (G_OBJECT (data->combo_box), data->changed_signal_id))
+ {
+ g_signal_handler_disconnect (G_OBJECT (data->combo_box), data->changed_signal_id);
+ }
+ g_free (data->x_content_type);
+ g_free (data);
+}
+
+static void
+other_application_selected (CajaOpenWithDialog *dialog,
+ GAppInfo *app_info,
+ CajaAutorunComboBoxData *data)
+{
+ if (data->changed_cb != NULL)
+ {
+ data->changed_cb (TRUE, FALSE, FALSE, app_info, data->user_data);
+ }
+ if (data->update_settings)
+ {
+ caja_autorun_set_preferences (data->x_content_type, TRUE, FALSE, FALSE);
+ g_app_info_set_as_default_for_type (app_info,
+ data->x_content_type,
+ NULL);
+ data->other_application_selected = TRUE;
+ }
+
+ /* rebuild so we include and select the new application in the list */
+ caja_autorun_rebuild_combo_box (data->combo_box);
+}
+
+static void
+handle_dialog_closure (CajaAutorunComboBoxData *data)
+{
+ if (!data->other_application_selected)
+ {
+ /* reset combo box so we don't linger on "Open with other Application..." */
+ caja_autorun_rebuild_combo_box (data->combo_box);
+ }
+}
+
+static void
+dialog_response_cb (GtkDialog *dialog,
+ gint response,
+ CajaAutorunComboBoxData *data)
+{
+ handle_dialog_closure (data);
+}
+
+static void
+dialog_destroy_cb (GtkObject *object,
+ CajaAutorunComboBoxData *data)
+{
+ handle_dialog_closure (data);
+}
+
+static void
+combo_box_changed (GtkComboBox *combo_box,
+ CajaAutorunComboBoxData *data)
+{
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ GAppInfo *app_info;
+ char *x_content_type;
+ int type;
+
+ model = NULL;
+ app_info = NULL;
+ x_content_type = NULL;
+
+ if (!gtk_combo_box_get_active_iter (combo_box, &iter))
+ {
+ goto out;
+ }
+
+ model = gtk_combo_box_get_model (combo_box);
+ if (model == NULL)
+ {
+ goto out;
+ }
+
+ gtk_tree_model_get (model, &iter,
+ COLUMN_AUTORUN_APP_INFO, &app_info,
+ COLUMN_AUTORUN_X_CONTENT_TYPE, &x_content_type,
+ COLUMN_AUTORUN_ITEM_TYPE, &type,
+ -1);
+
+ switch (type)
+ {
+ case AUTORUN_ASK:
+ if (data->changed_cb != NULL)
+ {
+ data->changed_cb (TRUE, FALSE, FALSE, NULL, data->user_data);
+ }
+ if (data->update_settings)
+ {
+ caja_autorun_set_preferences (x_content_type, FALSE, FALSE, FALSE);
+ }
+ break;
+ case AUTORUN_IGNORE:
+ if (data->changed_cb != NULL)
+ {
+ data->changed_cb (FALSE, TRUE, FALSE, NULL, data->user_data);
+ }
+ if (data->update_settings)
+ {
+ caja_autorun_set_preferences (x_content_type, FALSE, TRUE, FALSE);
+ }
+ break;
+ case AUTORUN_OPEN_FOLDER:
+ if (data->changed_cb != NULL)
+ {
+ data->changed_cb (FALSE, FALSE, TRUE, NULL, data->user_data);
+ }
+ if (data->update_settings)
+ {
+ caja_autorun_set_preferences (x_content_type, FALSE, FALSE, TRUE);
+ }
+ break;
+
+ case AUTORUN_APP:
+ if (data->changed_cb != NULL)
+ {
+ /* TODO TODO?? */
+ data->changed_cb (TRUE, FALSE, FALSE, app_info, data->user_data);
+ }
+ if (data->update_settings)
+ {
+ caja_autorun_set_preferences (x_content_type, TRUE, FALSE, FALSE);
+ g_app_info_set_as_default_for_type (app_info,
+ x_content_type,
+ NULL);
+ }
+ break;
+
+ case AUTORUN_OTHER_APP:
+ {
+ GtkWidget *dialog;
+
+ data->other_application_selected = FALSE;
+
+ dialog = caja_add_application_dialog_new (NULL, x_content_type);
+ gtk_window_set_transient_for (GTK_WINDOW (dialog),
+ GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (combo_box))));
+ g_signal_connect (dialog, "application_selected",
+ G_CALLBACK (other_application_selected),
+ data);
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (dialog_response_cb), data);
+ g_signal_connect (dialog, "destroy",
+ G_CALLBACK (dialog_destroy_cb), data);
+ gtk_widget_show (GTK_WIDGET (dialog));
+
+ break;
+ }
+
+ }
+
+out:
+ if (app_info != NULL)
+ {
+ g_object_unref (app_info);
+ }
+ g_free (x_content_type);
+}
+
+static void
+caja_autorun_rebuild_combo_box (GtkWidget *combo_box)
+{
+ CajaAutorunComboBoxData *data;
+ char *x_content_type;
+
+ data = g_object_get_data (G_OBJECT (combo_box), "caja_autorun_combobox_data");
+ if (data == NULL)
+ {
+ g_warning ("no 'caja_autorun_combobox_data' data!");
+ return;
+ }
+
+ x_content_type = g_strdup (data->x_content_type);
+ caja_autorun_prepare_combo_box (combo_box,
+ x_content_type,
+ data->include_ask,
+ data->include_open_with_other_app,
+ data->update_settings,
+ data->changed_cb,
+ data->user_data);
+ g_free (x_content_type);
+}
+
+/* TODO: we need some kind of way to remove user-defined associations,
+ * e.g. the result of "Open with other Application...".
+ *
+ * However, this is a bit hard as
+ * g_app_info_can_remove_supports_type() will always return TRUE
+ * because we now have [Removed Applications] in the file
+ * ~/.local/share/applications/mimeapps.list.
+ *
+ * We need the API outlined in
+ *
+ * http://bugzilla.gnome.org/show_bug.cgi?id=545350
+ *
+ * to do this.
+ *
+ * Now, there's also the question about what the UI would look like
+ * given this API. Ideally we'd include a small button on the right
+ * side of the combo box that the user can press to delete an
+ * association, e.g.:
+ *
+ * +-------------------------------------+
+ * | Ask what to do |
+ * | Do Nothing |
+ * | Open Folder |
+ * +-------------------------------------+
+ * | Open Rhythmbox Music Player |
+ * | Open Audio CD Extractor |
+ * | Open Banshee Media Player |
+ * | Open Frobnicator App [x] |
+ * +-------------------------------------+
+ * | Open with other Application... |
+ * +-------------------------------------+
+ *
+ * where "Frobnicator App" have been set up using "Open with other
+ * Application...". However this is not accessible (which is a
+ * GTK+ issue) but probably not a big deal.
+ *
+ * And we only want show these buttons (e.g. [x]) for associations with
+ * GAppInfo instances that are deletable.
+ */
+
+void
+caja_autorun_prepare_combo_box (GtkWidget *combo_box,
+ const char *x_content_type,
+ gboolean include_ask,
+ gboolean include_open_with_other_app,
+ gboolean update_settings,
+ CajaAutorunComboBoxChanged changed_cb,
+ gpointer user_data)
+{
+ GList *l;
+ GList *app_info_list;
+ GAppInfo *default_app_info;
+ GtkListStore *list_store;
+ GtkTreeIter iter;
+ GdkPixbuf *pixbuf;
+ int icon_size;
+ int set_active;
+ int n;
+ int num_apps;
+ gboolean pref_ask;
+ gboolean pref_start_app;
+ gboolean pref_ignore;
+ gboolean pref_open_folder;
+ CajaAutorunComboBoxData *data;
+ GtkCellRenderer *renderer;
+ gboolean new_data;
+
+ caja_autorun_get_preferences (x_content_type, &pref_start_app, &pref_ignore, &pref_open_folder);
+ pref_ask = !pref_start_app && !pref_ignore && !pref_open_folder;
+
+ icon_size = caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU);
+
+ set_active = -1;
+ data = NULL;
+ new_data = TRUE;
+
+ app_info_list = g_app_info_get_all_for_type (x_content_type);
+ default_app_info = g_app_info_get_default_for_type (x_content_type, FALSE);
+ num_apps = g_list_length (app_info_list);
+
+ list_store = gtk_list_store_new (5,
+ GDK_TYPE_PIXBUF,
+ G_TYPE_STRING,
+ G_TYPE_APP_INFO,
+ G_TYPE_STRING,
+ G_TYPE_INT);
+
+ /* no apps installed */
+ if (num_apps == 0)
+ {
+ gtk_list_store_append (list_store, &iter);
+ pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
+ GTK_STOCK_DIALOG_ERROR,
+ icon_size,
+ 0,
+ NULL);
+
+ /* TODO: integrate with PackageKit-mate to find applications */
+
+ gtk_list_store_set (list_store, &iter,
+ COLUMN_AUTORUN_PIXBUF, pixbuf,
+ COLUMN_AUTORUN_NAME, _("No applications found"),
+ COLUMN_AUTORUN_APP_INFO, NULL,
+ COLUMN_AUTORUN_X_CONTENT_TYPE, x_content_type,
+ COLUMN_AUTORUN_ITEM_TYPE, AUTORUN_ASK,
+ -1);
+ g_object_unref (pixbuf);
+ }
+ else
+ {
+ if (include_ask)
+ {
+ gtk_list_store_append (list_store, &iter);
+ pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
+ GTK_STOCK_DIALOG_QUESTION,
+ icon_size,
+ 0,
+ NULL);
+ gtk_list_store_set (list_store, &iter,
+ COLUMN_AUTORUN_PIXBUF, pixbuf,
+ COLUMN_AUTORUN_NAME, _("Ask what to do"),
+ COLUMN_AUTORUN_APP_INFO, NULL,
+ COLUMN_AUTORUN_X_CONTENT_TYPE, x_content_type,
+ COLUMN_AUTORUN_ITEM_TYPE, AUTORUN_ASK,
+ -1);
+ g_object_unref (pixbuf);
+ }
+
+ gtk_list_store_append (list_store, &iter);
+ pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
+ GTK_STOCK_CLOSE,
+ icon_size,
+ 0,
+ NULL);
+ gtk_list_store_set (list_store, &iter,
+ COLUMN_AUTORUN_PIXBUF, pixbuf,
+ COLUMN_AUTORUN_NAME, _("Do Nothing"),
+ COLUMN_AUTORUN_APP_INFO, NULL,
+ COLUMN_AUTORUN_X_CONTENT_TYPE, x_content_type,
+ COLUMN_AUTORUN_ITEM_TYPE, AUTORUN_IGNORE,
+ -1);
+ g_object_unref (pixbuf);
+
+ gtk_list_store_append (list_store, &iter);
+ pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
+ "folder-open",
+ icon_size,
+ 0,
+ NULL);
+ gtk_list_store_set (list_store, &iter,
+ COLUMN_AUTORUN_PIXBUF, pixbuf,
+ COLUMN_AUTORUN_NAME, _("Open Folder"),
+ COLUMN_AUTORUN_APP_INFO, NULL,
+ COLUMN_AUTORUN_X_CONTENT_TYPE, x_content_type,
+ COLUMN_AUTORUN_ITEM_TYPE, AUTORUN_OPEN_FOLDER,
+ -1);
+ g_object_unref (pixbuf);
+
+ gtk_list_store_append (list_store, &iter);
+ gtk_list_store_set (list_store, &iter,
+ COLUMN_AUTORUN_PIXBUF, NULL,
+ COLUMN_AUTORUN_NAME, NULL,
+ COLUMN_AUTORUN_APP_INFO, NULL,
+ COLUMN_AUTORUN_X_CONTENT_TYPE, NULL,
+ COLUMN_AUTORUN_ITEM_TYPE, AUTORUN_SEP,
+ -1);
+
+ for (l = app_info_list, n = include_ask ? 4 : 3; l != NULL; l = l->next, n++)
+ {
+ GIcon *icon;
+ CajaIconInfo *icon_info;
+ char *open_string;
+ GAppInfo *app_info = l->data;
+
+ /* we deliberately ignore should_show because some apps might want
+ * to install special handlers that should be hidden in the regular
+ * application launcher menus
+ */
+
+ icon = g_app_info_get_icon (app_info);
+ icon_info = caja_icon_info_lookup (icon, icon_size);
+ pixbuf = caja_icon_info_get_pixbuf_at_size (icon_info, icon_size);
+ g_object_unref (icon_info);
+
+ open_string = g_strdup_printf (_("Open %s"), g_app_info_get_display_name (app_info));
+
+ gtk_list_store_append (list_store, &iter);
+ gtk_list_store_set (list_store, &iter,
+ COLUMN_AUTORUN_PIXBUF, pixbuf,
+ COLUMN_AUTORUN_NAME, open_string,
+ COLUMN_AUTORUN_APP_INFO, app_info,
+ COLUMN_AUTORUN_X_CONTENT_TYPE, x_content_type,
+ COLUMN_AUTORUN_ITEM_TYPE, AUTORUN_APP,
+ -1);
+ if (pixbuf != NULL)
+ {
+ g_object_unref (pixbuf);
+ }
+ g_free (open_string);
+
+ if (g_app_info_equal (app_info, default_app_info))
+ {
+ set_active = n;
+ }
+ }
+ }
+
+ if (include_open_with_other_app)
+ {
+ gtk_list_store_append (list_store, &iter);
+ gtk_list_store_set (list_store, &iter,
+ COLUMN_AUTORUN_PIXBUF, NULL,
+ COLUMN_AUTORUN_NAME, NULL,
+ COLUMN_AUTORUN_APP_INFO, NULL,
+ COLUMN_AUTORUN_X_CONTENT_TYPE, NULL,
+ COLUMN_AUTORUN_ITEM_TYPE, AUTORUN_SEP,
+ -1);
+
+ gtk_list_store_append (list_store, &iter);
+ pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
+ "application-x-executable",
+ icon_size,
+ 0,
+ NULL);
+ gtk_list_store_set (list_store, &iter,
+ COLUMN_AUTORUN_PIXBUF, pixbuf,
+ COLUMN_AUTORUN_NAME, _("Open with other Application..."),
+ COLUMN_AUTORUN_APP_INFO, NULL,
+ COLUMN_AUTORUN_X_CONTENT_TYPE, x_content_type,
+ COLUMN_AUTORUN_ITEM_TYPE, AUTORUN_OTHER_APP,
+ -1);
+ g_object_unref (pixbuf);
+ }
+
+ if (default_app_info != NULL)
+ {
+ g_object_unref (default_app_info);
+ }
+ eel_g_object_list_free (app_info_list);
+
+ gtk_combo_box_set_model (GTK_COMBO_BOX (combo_box), GTK_TREE_MODEL (list_store));
+ g_object_unref (G_OBJECT (list_store));
+
+ gtk_cell_layout_clear (GTK_CELL_LAYOUT (combo_box));
+
+ renderer = gtk_cell_renderer_pixbuf_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box), renderer, FALSE);
+ gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo_box), renderer,
+ "pixbuf", COLUMN_AUTORUN_PIXBUF,
+ NULL);
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box), renderer, TRUE);
+ gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo_box), renderer,
+ "text", COLUMN_AUTORUN_NAME,
+ NULL);
+ gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (combo_box), combo_box_separator_func, NULL, NULL);
+
+ if (num_apps == 0)
+ {
+ gtk_combo_box_set_active (GTK_COMBO_BOX (combo_box), 0);
+ gtk_widget_set_sensitive (combo_box, FALSE);
+ }
+ else
+ {
+ gtk_widget_set_sensitive (combo_box, TRUE);
+ if (pref_ask && include_ask)
+ {
+ gtk_combo_box_set_active (GTK_COMBO_BOX (combo_box), 0);
+ }
+ else if (pref_ignore)
+ {
+ gtk_combo_box_set_active (GTK_COMBO_BOX (combo_box), include_ask ? 1 : 0);
+ }
+ else if (pref_open_folder)
+ {
+ gtk_combo_box_set_active (GTK_COMBO_BOX (combo_box), include_ask ? 2 : 1);
+ }
+ else if (set_active != -1)
+ {
+ gtk_combo_box_set_active (GTK_COMBO_BOX (combo_box), set_active);
+ }
+ else
+ {
+ gtk_combo_box_set_active (GTK_COMBO_BOX (combo_box), include_ask ? 1 : 0);
+ }
+
+ /* See if we have an old data around */
+ data = g_object_get_data (G_OBJECT (combo_box), "caja_autorun_combobox_data");
+ if (data)
+ {
+ new_data = FALSE;
+ g_free (data->x_content_type);
+ }
+ else
+ {
+ data = g_new0 (CajaAutorunComboBoxData, 1);
+ }
+
+ data->x_content_type = g_strdup (x_content_type);
+ data->include_ask = include_ask;
+ data->include_open_with_other_app = include_open_with_other_app;
+ data->update_settings = update_settings;
+ data->changed_cb = changed_cb;
+ data->user_data = user_data;
+ data->combo_box = combo_box;
+ if (data->changed_signal_id == 0)
+ {
+ data->changed_signal_id = g_signal_connect (G_OBJECT (combo_box),
+ "changed",
+ G_CALLBACK (combo_box_changed),
+ data);
+ }
+ }
+
+ if (new_data)
+ {
+ g_object_set_data_full (G_OBJECT (combo_box),
+ "caja_autorun_combobox_data",
+ data,
+ (GDestroyNotify) caja_autorun_combobox_data_destroy);
+ }
+}
+
+static gboolean
+is_shift_pressed (void)
+{
+ gboolean ret;
+ XkbStateRec state;
+ Bool status;
+
+ ret = FALSE;
+
+ gdk_error_trap_push ();
+ status = XkbGetState (GDK_DISPLAY (), XkbUseCoreKbd, &state);
+ gdk_error_trap_pop ();
+
+ if (status == Success)
+ {
+ ret = state.mods & ShiftMask;
+ }
+
+ return ret;
+}
+
+enum
+{
+ AUTORUN_DIALOG_RESPONSE_EJECT = 0
+};
+
+typedef struct
+{
+ GtkWidget *dialog;
+
+ GMount *mount;
+ gboolean should_eject;
+
+ gboolean selected_ignore;
+ gboolean selected_open_folder;
+ GAppInfo *selected_app;
+
+ gboolean remember;
+
+ char *x_content_type;
+
+ CajaAutorunOpenWindow open_window_func;
+ gpointer user_data;
+} AutorunDialogData;
+
+
+void
+caja_autorun_launch_for_mount (GMount *mount, GAppInfo *app_info)
+{
+ GFile *root;
+ CajaFile *file;
+ GList *files;
+
+ root = g_mount_get_root (mount);
+ file = caja_file_get (root);
+ g_object_unref (root);
+ files = g_list_append (NULL, file);
+ caja_launch_application (app_info,
+ files,
+ NULL); /* TODO: what to set here? */
+ g_object_unref (file);
+ g_list_free (files);
+}
+
+static void autorun_dialog_mount_unmounted (GMount *mount, AutorunDialogData *data);
+
+static void
+autorun_dialog_destroy (AutorunDialogData *data)
+{
+ g_signal_handlers_disconnect_by_func (G_OBJECT (data->mount),
+ G_CALLBACK (autorun_dialog_mount_unmounted),
+ data);
+
+ gtk_widget_destroy (GTK_WIDGET (data->dialog));
+ if (data->selected_app != NULL)
+ {
+ g_object_unref (data->selected_app);
+ }
+ g_object_unref (data->mount);
+ g_free (data->x_content_type);
+ g_free (data);
+}
+
+static void
+autorun_dialog_mount_unmounted (GMount *mount, AutorunDialogData *data)
+{
+ /* remove the dialog if the media is unmounted */
+ autorun_dialog_destroy (data);
+}
+
+static void
+autorun_dialog_response (GtkDialog *dialog, gint response, AutorunDialogData *data)
+{
+ switch (response)
+ {
+ case AUTORUN_DIALOG_RESPONSE_EJECT:
+ caja_file_operations_unmount_mount (GTK_WINDOW (dialog),
+ data->mount,
+ data->should_eject,
+ FALSE);
+ break;
+
+ case GTK_RESPONSE_NONE:
+ /* window was closed */
+ break;
+ case GTK_RESPONSE_CANCEL:
+ break;
+ case GTK_RESPONSE_OK:
+ /* do the selected action */
+
+ if (data->remember)
+ {
+ /* make sure we don't ask again */
+ caja_autorun_set_preferences (data->x_content_type, TRUE, data->selected_ignore, data->selected_open_folder);
+ if (!data->selected_ignore && !data->selected_open_folder && data->selected_app != NULL)
+ {
+ g_app_info_set_as_default_for_type (data->selected_app,
+ data->x_content_type,
+ NULL);
+ }
+ }
+ else
+ {
+ /* make sure we do ask again */
+ caja_autorun_set_preferences (data->x_content_type, FALSE, FALSE, FALSE);
+ }
+
+ if (!data->selected_ignore && !data->selected_open_folder && data->selected_app != NULL)
+ {
+ caja_autorun_launch_for_mount (data->mount, data->selected_app);
+ }
+ else if (!data->selected_ignore && data->selected_open_folder)
+ {
+ if (data->open_window_func != NULL)
+ data->open_window_func (data->mount, data->user_data);
+ }
+ break;
+ }
+
+ autorun_dialog_destroy (data);
+}
+
+static void
+autorun_combo_changed (gboolean selected_ask,
+ gboolean selected_ignore,
+ gboolean selected_open_folder,
+ GAppInfo *selected_app,
+ gpointer user_data)
+{
+ AutorunDialogData *data = user_data;
+
+ if (data->selected_app != NULL)
+ {
+ g_object_unref (data->selected_app);
+ }
+ data->selected_app = selected_app != NULL ? g_object_ref (selected_app) : NULL;
+ data->selected_ignore = selected_ignore;
+ data->selected_open_folder = selected_open_folder;
+}
+
+
+static void
+autorun_always_toggled (GtkToggleButton *togglebutton, AutorunDialogData *data)
+{
+ data->remember = gtk_toggle_button_get_active (togglebutton);
+}
+
+static gboolean
+combo_box_enter_ok (GtkWidget *togglebutton, GdkEventKey *event, GtkDialog *dialog)
+{
+ if (event->keyval == GDK_KP_Enter || event->keyval == GDK_Return)
+ {
+ gtk_dialog_response (dialog, GTK_RESPONSE_OK);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/* returns TRUE if a folder window should be opened */
+static gboolean
+do_autorun_for_content_type (GMount *mount, const char *x_content_type, CajaAutorunOpenWindow open_window_func, gpointer user_data)
+{
+ AutorunDialogData *data;
+ GtkWidget *dialog;
+ GtkWidget *hbox;
+ GtkWidget *vbox;
+ GtkWidget *label;
+ GtkWidget *combo_box;
+ GtkWidget *always_check_button;
+ GtkWidget *eject_button;
+ GtkWidget *image;
+ char *markup;
+ char *content_description;
+ char *mount_name;
+ GIcon *icon;
+ GdkPixbuf *pixbuf;
+ CajaIconInfo *icon_info;
+ int icon_size;
+ gboolean user_forced_dialog;
+ gboolean pref_ask;
+ gboolean pref_start_app;
+ gboolean pref_ignore;
+ gboolean pref_open_folder;
+ char *media_greeting;
+ gboolean ret;
+
+ ret = FALSE;
+ mount_name = NULL;
+
+ if (g_content_type_is_a (x_content_type, "x-content/win32-software"))
+ {
+ /* don't pop up the dialog anyway if the content type says
+ * windows software.
+ */
+ goto out;
+ }
+
+ user_forced_dialog = is_shift_pressed ();
+
+ caja_autorun_get_preferences (x_content_type, &pref_start_app, &pref_ignore, &pref_open_folder);
+ pref_ask = !pref_start_app && !pref_ignore && !pref_open_folder;
+
+ if (user_forced_dialog)
+ {
+ goto show_dialog;
+ }
+
+ if (!pref_ask && !pref_ignore && !pref_open_folder)
+ {
+ GAppInfo *app_info;
+ app_info = g_app_info_get_default_for_type (x_content_type, FALSE);
+ if (app_info != NULL)
+ {
+ caja_autorun_launch_for_mount (mount, app_info);
+ }
+ goto out;
+ }
+
+ if (pref_open_folder)
+ {
+ ret = TRUE;
+ goto out;
+ }
+
+ if (pref_ignore)
+ {
+ goto out;
+ }
+
+show_dialog:
+
+ mount_name = g_mount_get_name (mount);
+
+ dialog = gtk_dialog_new ();
+
+ gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE);
+ hbox = gtk_hbox_new (FALSE, 12);
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), hbox, TRUE, TRUE, 0);
+ gtk_container_set_border_width (GTK_CONTAINER (hbox), 12);
+
+ icon = g_mount_get_icon (mount);
+ icon_size = caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_DIALOG);
+ icon_info = caja_icon_info_lookup (icon, icon_size);
+ pixbuf = caja_icon_info_get_pixbuf_at_size (icon_info, icon_size);
+ g_object_unref (icon_info);
+ g_object_unref (icon);
+ image = gtk_image_new_from_pixbuf (pixbuf);
+ gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0.0);
+ gtk_box_pack_start (GTK_BOX (hbox), image, TRUE, TRUE, 0);
+ /* also use the icon on the dialog */
+ gtk_window_set_title (GTK_WINDOW (dialog), mount_name);
+ gtk_window_set_icon (GTK_WINDOW (dialog), pixbuf);
+ gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER);
+ g_object_unref (pixbuf);
+
+ vbox = gtk_vbox_new (FALSE, 12);
+ gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
+
+ label = gtk_label_new (NULL);
+
+
+ /* Customize greeting for well-known x-content types */
+ if (strcmp (x_content_type, "x-content/audio-cdda") == 0)
+ {
+ media_greeting = _("You have just inserted an Audio CD.");
+ }
+ else if (strcmp (x_content_type, "x-content/audio-dvd") == 0)
+ {
+ media_greeting = _("You have just inserted an Audio DVD.");
+ }
+ else if (strcmp (x_content_type, "x-content/video-dvd") == 0)
+ {
+ media_greeting = _("You have just inserted a Video DVD.");
+ }
+ else if (strcmp (x_content_type, "x-content/video-vcd") == 0)
+ {
+ media_greeting = _("You have just inserted a Video CD.");
+ }
+ else if (strcmp (x_content_type, "x-content/video-svcd") == 0)
+ {
+ media_greeting = _("You have just inserted a Super Video CD.");
+ }
+ else if (strcmp (x_content_type, "x-content/blank-cd") == 0)
+ {
+ media_greeting = _("You have just inserted a blank CD.");
+ }
+ else if (strcmp (x_content_type, "x-content/blank-dvd") == 0)
+ {
+ media_greeting = _("You have just inserted a blank DVD.");
+ }
+ else if (strcmp (x_content_type, "x-content/blank-cd") == 0)
+ {
+ media_greeting = _("You have just inserted a blank Blu-Ray disc.");
+ }
+ else if (strcmp (x_content_type, "x-content/blank-cd") == 0)
+ {
+ media_greeting = _("You have just inserted a blank HD DVD.");
+ }
+ else if (strcmp (x_content_type, "x-content/image-photocd") == 0)
+ {
+ media_greeting = _("You have just inserted a Photo CD.");
+ }
+ else if (strcmp (x_content_type, "x-content/image-picturecd") == 0)
+ {
+ media_greeting = _("You have just inserted a Picture CD.");
+ }
+ else if (strcmp (x_content_type, "x-content/image-dcf") == 0)
+ {
+ media_greeting = _("You have just inserted a medium with digital photos.");
+ }
+ else if (strcmp (x_content_type, "x-content/audio-player") == 0)
+ {
+ media_greeting = _("You have just inserted a digital audio player.");
+ }
+ else if (g_content_type_is_a (x_content_type, "x-content/software"))
+ {
+ media_greeting = _("You have just inserted a medium with software intended to be automatically started.");
+ }
+ else
+ {
+ /* fallback to generic greeting */
+ media_greeting = _("You have just inserted a medium.");
+ }
+ markup = g_strdup_printf ("<big><b>%s %s</b></big>", media_greeting, _("Choose what application to launch."));
+ gtk_label_set_markup (GTK_LABEL (label), markup);
+ g_free (markup);
+ gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
+
+ label = gtk_label_new (NULL);
+ content_description = g_content_type_get_description (x_content_type);
+ markup = g_strdup_printf (_("Select how to open \"%s\" and whether to perform this action in the future for other media of type \"%s\"."), mount_name, content_description);
+ g_free (content_description);
+ gtk_label_set_markup (GTK_LABEL (label), markup);
+ g_free (markup);
+ gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
+
+ data = g_new0 (AutorunDialogData, 1);
+ data->dialog = dialog;
+ data->mount = g_object_ref (mount);
+ data->remember = !pref_ask;
+ data->selected_ignore = pref_ignore;
+ data->x_content_type = g_strdup (x_content_type);
+ data->selected_app = g_app_info_get_default_for_type (x_content_type, FALSE);
+ data->open_window_func = open_window_func;
+ data->user_data = user_data;
+
+ combo_box = gtk_combo_box_new ();
+ caja_autorun_prepare_combo_box (combo_box, x_content_type, FALSE, TRUE, FALSE, autorun_combo_changed, data);
+ g_signal_connect (G_OBJECT (combo_box),
+ "key-press-event",
+ G_CALLBACK (combo_box_enter_ok),
+ dialog);
+
+ gtk_box_pack_start (GTK_BOX (vbox), combo_box, TRUE, TRUE, 0);
+
+ always_check_button = gtk_check_button_new_with_mnemonic (_("_Always perform this action"));
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (always_check_button), data->remember);
+ g_signal_connect (G_OBJECT (always_check_button),
+ "toggled",
+ G_CALLBACK (autorun_always_toggled),
+ data);
+ gtk_box_pack_start (GTK_BOX (vbox), always_check_button, TRUE, TRUE, 0);
+
+ gtk_dialog_add_buttons (GTK_DIALOG (dialog),
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OK, GTK_RESPONSE_OK,
+ NULL);
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+
+ if (g_mount_can_eject (mount))
+ {
+ GtkWidget *eject_image;
+ eject_button = gtk_button_new_with_mnemonic (_("_Eject"));
+ pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
+ "media-eject",
+ caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_BUTTON),
+ 0,
+ NULL);
+ eject_image = gtk_image_new_from_pixbuf (pixbuf);
+ g_object_unref (pixbuf);
+ gtk_button_set_image (GTK_BUTTON (eject_button), eject_image);
+ data->should_eject = TRUE;
+ }
+ else
+ {
+ eject_button = gtk_button_new_with_mnemonic (_("_Unmount"));
+ data->should_eject = FALSE;
+ }
+ gtk_dialog_add_action_widget (GTK_DIALOG (dialog), eject_button, AUTORUN_DIALOG_RESPONSE_EJECT);
+ gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (gtk_dialog_get_action_area (GTK_DIALOG (dialog))), eject_button, TRUE);
+
+ /* show the dialog */
+ gtk_widget_show_all (dialog);
+
+ g_signal_connect (G_OBJECT (dialog),
+ "response",
+ G_CALLBACK (autorun_dialog_response),
+ data);
+
+ g_signal_connect (G_OBJECT (data->mount),
+ "unmounted",
+ G_CALLBACK (autorun_dialog_mount_unmounted),
+ data);
+
+out:
+ g_free (mount_name);
+ return ret;
+}
+
+typedef struct
+{
+ GMount *mount;
+ CajaAutorunOpenWindow open_window_func;
+ gpointer user_data;
+} AutorunData;
+
+static void
+autorun_guessed_content_type_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GError *error;
+ char **guessed_content_type;
+ AutorunData *data = user_data;
+ gboolean open_folder;
+
+ open_folder = FALSE;
+
+ error = NULL;
+ guessed_content_type = g_mount_guess_content_type_finish (G_MOUNT (source_object), res, &error);
+ g_object_set_data_full (source_object,
+ "caja-content-type-cache",
+ g_strdupv (guessed_content_type),
+ (GDestroyNotify)g_strfreev);
+ if (error != NULL)
+ {
+ g_warning ("Unabled to guess content type for mount: %s", error->message);
+ g_error_free (error);
+ }
+ else
+ {
+ if (guessed_content_type != NULL && g_strv_length (guessed_content_type) > 0)
+ {
+ int n;
+ for (n = 0; guessed_content_type[n] != NULL; n++)
+ {
+ if (do_autorun_for_content_type (data->mount, guessed_content_type[n],
+ data->open_window_func, data->user_data))
+ {
+ open_folder = TRUE;
+ }
+ }
+ g_strfreev (guessed_content_type);
+ }
+ else
+ {
+ if (eel_preferences_get_boolean (CAJA_PREFERENCES_MEDIA_AUTOMOUNT_OPEN))
+ open_folder = TRUE;
+ }
+ }
+
+ /* only open the folder once.. */
+ if (open_folder && data->open_window_func != NULL)
+ {
+ data->open_window_func (data->mount, data->user_data);
+ }
+
+ g_object_unref (data->mount);
+ g_free (data);
+}
+
+void
+caja_autorun (GMount *mount, CajaAutorunOpenWindow open_window_func, gpointer user_data)
+{
+ AutorunData *data;
+
+ if (!should_autorun_mount (mount) ||
+ eel_preferences_get_boolean (CAJA_PREFERENCES_MEDIA_AUTORUN_NEVER))
+ {
+ return;
+ }
+
+ data = g_new0 (AutorunData, 1);
+ data->mount = g_object_ref (mount);
+ data->open_window_func = open_window_func;
+ data->user_data = user_data;
+
+ g_mount_guess_content_type (mount,
+ FALSE,
+ NULL,
+ autorun_guessed_content_type_callback,
+ data);
+}
+
+typedef struct
+{
+ CajaAutorunGetContent 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,
+ "caja-content-type-cache",
+ g_strdupv (types),
+ (GDestroyNotify)g_strfreev);
+
+ if (data->callback)
+ {
+ data->callback (types, data->user_data);
+ }
+ g_strfreev (types);
+ g_free (data);
+}
+
+void
+caja_autorun_get_x_content_types_for_mount_async (GMount *mount,
+ CajaAutorunGetContent 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), "caja-content-type-cache");
+ if (cached != NULL)
+ {
+ if (callback)
+ {
+ callback (cached, user_data);
+ }
+ return;
+ }
+
+ data = g_new (GetContentTypesData, 1);
+ data->callback = callback;
+ data->user_data = user_data;
+
+ g_mount_guess_content_type (mount,
+ FALSE,
+ cancellable,
+ get_types_cb,
+ data);
+}
+
+
+char **
+caja_autorun_get_cached_x_content_types_for_mount (GMount *mount)
+{
+ char **cached;
+
+ if (mount == NULL)
+ {
+ return NULL;
+ }
+
+ cached = g_object_get_data (G_OBJECT (mount), "caja-content-type-cache");
+ if (cached != NULL)
+ {
+ return g_strdupv (cached);
+ }
+
+ return NULL;
+}
+
+static gboolean
+remove_allow_volume (gpointer data)
+{
+ GVolume *volume = data;
+
+ g_object_set_data (G_OBJECT (volume), "caja-allow-autorun", NULL);
+ return FALSE;
+}
+
+void
+caja_allow_autorun_for_volume (GVolume *volume)
+{
+ g_object_set_data (G_OBJECT (volume), "caja-allow-autorun", GINT_TO_POINTER (1));
+}
+
+#define INHIBIT_AUTORUN_SECONDS 10
+
+void
+caja_allow_autorun_for_volume_finish (GVolume *volume)
+{
+ if (g_object_get_data (G_OBJECT (volume), "caja-allow-autorun") != NULL)
+ {
+ g_timeout_add_seconds_full (0,
+ INHIBIT_AUTORUN_SECONDS,
+ remove_allow_volume,
+ g_object_ref (volume),
+ g_object_unref);
+ }
+}
+
+static gboolean
+should_skip_native_mount_root (GFile *root)
+{
+ char *path;
+ gboolean should_skip;
+
+ /* skip any mounts in hidden directory hierarchies */
+ path = g_file_get_path (root);
+ should_skip = strstr (path, "/.") != NULL;
+ g_free (path);
+
+ return should_skip;
+}
+
+static gboolean
+should_autorun_mount (GMount *mount)
+{
+ GFile *root;
+ GVolume *enclosing_volume;
+ gboolean ignore_autorun;
+
+ ignore_autorun = TRUE;
+ enclosing_volume = g_mount_get_volume (mount);
+ if (enclosing_volume != NULL)
+ {
+ if (g_object_get_data (G_OBJECT (enclosing_volume), "caja-allow-autorun") != NULL)
+ {
+ ignore_autorun = FALSE;
+ g_object_set_data (G_OBJECT (enclosing_volume), "caja-allow-autorun", NULL);
+ }
+ }
+
+ if (ignore_autorun)
+ {
+ if (enclosing_volume != NULL)
+ {
+ g_object_unref (enclosing_volume);
+ }
+ return FALSE;
+ }
+
+ root = g_mount_get_root (mount);
+
+ /* only do autorun on local files or files where g_volume_should_automount() returns TRUE */
+ ignore_autorun = TRUE;
+ if ((g_file_is_native (root) && !should_skip_native_mount_root (root)) ||
+ (enclosing_volume != NULL && g_volume_should_automount (enclosing_volume)))
+ {
+ ignore_autorun = FALSE;
+ }
+ if (enclosing_volume != NULL)
+ {
+ g_object_unref (enclosing_volume);
+ }
+ g_object_unref (root);
+
+ return !ignore_autorun;
+}
diff --git a/libcaja-private/caja-autorun.h b/libcaja-private/caja-autorun.h
new file mode 100644
index 00000000..cafecd7a
--- /dev/null
+++ b/libcaja-private/caja-autorun.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Author: David Zeuthen <[email protected]>
+ */
+
+/* TODO:
+ *
+ * - automount all user-visible media on startup
+ * - but avoid doing autorun for these
+ * - unmount all the media we've automounted on shutdown
+ * - finish x-content / * types
+ * - finalize the semi-spec
+ * - add probing/sniffing code
+ * - clean up code
+ * - implement missing features
+ * - "Open Folder when mounted"
+ * - Autorun spec (e.g. $ROOT/.autostart)
+ *
+ */
+
+#ifndef CAJA_AUTORUN_H
+#define CAJA_AUTORUN_H
+
+#include <gtk/gtk.h>
+#include <eel/eel-background.h>
+#include <libcaja-private/caja-file.h>
+
+typedef void (*CajaAutorunComboBoxChanged) (gboolean selected_ask,
+ gboolean selected_ignore,
+ gboolean selected_open_folder,
+ GAppInfo *selected_app,
+ gpointer user_data);
+
+typedef void (*CajaAutorunOpenWindow) (GMount *mount, gpointer user_data);
+typedef void (*CajaAutorunGetContent) (char **content, gpointer user_data);
+
+void caja_autorun_prepare_combo_box (GtkWidget *combo_box,
+ const char *x_content_type,
+ gboolean include_ask,
+ gboolean include_open_with_other_app,
+ gboolean update_settings,
+ CajaAutorunComboBoxChanged changed_cb,
+ gpointer user_data);
+
+void caja_autorun_set_preferences (const char *x_content_type, gboolean pref_ask, gboolean pref_ignore, gboolean pref_open_folder);
+void caja_autorun_get_preferences (const char *x_content_type, gboolean *pref_ask, gboolean *pref_ignore, gboolean *pref_open_folder);
+
+void caja_autorun (GMount *mount, CajaAutorunOpenWindow open_window_func, gpointer user_data);
+
+char **caja_autorun_get_cached_x_content_types_for_mount (GMount *mount);
+
+void caja_autorun_get_x_content_types_for_mount_async (GMount *mount,
+ CajaAutorunGetContent callback,
+ GCancellable *cancellable,
+ gpointer user_data);
+
+void caja_autorun_launch_for_mount (GMount *mount, GAppInfo *app_info);
+
+void caja_allow_autorun_for_volume (GVolume *volume);
+void caja_allow_autorun_for_volume_finish (GVolume *volume);
+
+#endif /* CAJA_AUTORUN_H */
diff --git a/libcaja-private/caja-bookmark.c b/libcaja-private/caja-bookmark.c
new file mode 100644
index 00000000..bf174247
--- /dev/null
+++ b/libcaja-private/caja-bookmark.c
@@ -0,0 +1,672 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-bookmark.c - implementation of individual bookmarks.
+
+ Copyright (C) 1999, 2000 Eazel, Inc.
+
+ The Mate 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 Mate 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 Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: John Sullivan <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-bookmark.h"
+
+#include "caja-file.h"
+#include <eel/eel-gdk-pixbuf-extensions.h>
+#include <eel/eel-gtk-extensions.h>
+#include <eel/eel-gtk-macros.h>
+#include <eel/eel-string.h>
+#include <eel/eel-vfs-extensions.h>
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+#include <libcaja-private/caja-file.h>
+#include <libcaja-private/caja-icon-names.h>
+
+enum
+{
+ APPEARANCE_CHANGED,
+ CONTENTS_CHANGED,
+ LAST_SIGNAL
+};
+
+#define ELLIPSISED_MENU_ITEM_MIN_CHARS 32
+
+static guint signals[LAST_SIGNAL];
+
+struct CajaBookmarkDetails
+{
+ char *name;
+ gboolean has_custom_name;
+ GFile *location;
+ GIcon *icon;
+ CajaFile *file;
+
+ char *scroll_file;
+};
+
+static void caja_bookmark_connect_file (CajaBookmark *file);
+static void caja_bookmark_disconnect_file (CajaBookmark *file);
+
+G_DEFINE_TYPE (CajaBookmark, caja_bookmark, G_TYPE_OBJECT);
+
+/* GObject methods. */
+
+static void
+caja_bookmark_finalize (GObject *object)
+{
+ CajaBookmark *bookmark;
+
+ g_assert (CAJA_IS_BOOKMARK (object));
+
+ bookmark = CAJA_BOOKMARK (object);
+
+ caja_bookmark_disconnect_file (bookmark);
+
+ g_free (bookmark->details->name);
+ g_object_unref (bookmark->details->location);
+ if (bookmark->details->icon)
+ {
+ g_object_unref (bookmark->details->icon);
+ }
+ g_free (bookmark->details->scroll_file);
+ g_free (bookmark->details);
+
+ G_OBJECT_CLASS (caja_bookmark_parent_class)->finalize (object);
+}
+
+/* Initialization. */
+
+static void
+caja_bookmark_class_init (CajaBookmarkClass *class)
+{
+ G_OBJECT_CLASS (class)->finalize = caja_bookmark_finalize;
+
+ signals[APPEARANCE_CHANGED] =
+ g_signal_new ("appearance_changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaBookmarkClass, appearance_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[CONTENTS_CHANGED] =
+ g_signal_new ("contents_changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaBookmarkClass, contents_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+}
+
+static void
+caja_bookmark_init (CajaBookmark *bookmark)
+{
+ bookmark->details = g_new0 (CajaBookmarkDetails, 1);
+}
+
+/**
+ * caja_bookmark_compare_with:
+ *
+ * Check whether two bookmarks are considered identical.
+ * @a: first CajaBookmark*.
+ * @b: second CajaBookmark*.
+ *
+ * Return value: 0 if @a and @b have same name and uri, 1 otherwise
+ * (GCompareFunc style)
+ **/
+int
+caja_bookmark_compare_with (gconstpointer a, gconstpointer b)
+{
+ CajaBookmark *bookmark_a;
+ CajaBookmark *bookmark_b;
+
+ g_return_val_if_fail (CAJA_IS_BOOKMARK (a), 1);
+ g_return_val_if_fail (CAJA_IS_BOOKMARK (b), 1);
+
+ bookmark_a = CAJA_BOOKMARK (a);
+ bookmark_b = CAJA_BOOKMARK (b);
+
+ if (eel_strcmp (bookmark_a->details->name,
+ bookmark_b->details->name) != 0)
+ {
+ return 1;
+ }
+
+ if (!g_file_equal (bookmark_a->details->location,
+ bookmark_b->details->location))
+ {
+ return 1;
+ }
+
+ return 0;
+}
+
+/**
+ * caja_bookmark_compare_uris:
+ *
+ * Check whether the uris of two bookmarks are for the same location.
+ * @a: first CajaBookmark*.
+ * @b: second CajaBookmark*.
+ *
+ * Return value: 0 if @a and @b have matching uri, 1 otherwise
+ * (GCompareFunc style)
+ **/
+int
+caja_bookmark_compare_uris (gconstpointer a, gconstpointer b)
+{
+ CajaBookmark *bookmark_a;
+ CajaBookmark *bookmark_b;
+
+ g_return_val_if_fail (CAJA_IS_BOOKMARK (a), 1);
+ g_return_val_if_fail (CAJA_IS_BOOKMARK (b), 1);
+
+ bookmark_a = CAJA_BOOKMARK (a);
+ bookmark_b = CAJA_BOOKMARK (b);
+
+ return !g_file_equal (bookmark_a->details->location,
+ bookmark_b->details->location);
+}
+
+CajaBookmark *
+caja_bookmark_copy (CajaBookmark *bookmark)
+{
+ g_return_val_if_fail (CAJA_IS_BOOKMARK (bookmark), NULL);
+
+ return caja_bookmark_new (
+ bookmark->details->location,
+ bookmark->details->name,
+ bookmark->details->has_custom_name,
+ bookmark->details->icon);
+}
+
+char *
+caja_bookmark_get_name (CajaBookmark *bookmark)
+{
+ g_return_val_if_fail(CAJA_IS_BOOKMARK (bookmark), NULL);
+
+ return g_strdup (bookmark->details->name);
+}
+
+
+gboolean
+caja_bookmark_get_has_custom_name (CajaBookmark *bookmark)
+{
+ g_return_val_if_fail(CAJA_IS_BOOKMARK (bookmark), FALSE);
+
+ return (bookmark->details->has_custom_name);
+}
+
+
+GdkPixbuf *
+caja_bookmark_get_pixbuf (CajaBookmark *bookmark,
+ GtkIconSize stock_size)
+{
+ GdkPixbuf *result;
+ GIcon *icon;
+ CajaIconInfo *info;
+ int pixel_size;
+
+
+ g_return_val_if_fail (CAJA_IS_BOOKMARK (bookmark), NULL);
+
+ icon = caja_bookmark_get_icon (bookmark);
+ if (icon == NULL)
+ {
+ return NULL;
+ }
+
+ pixel_size = caja_get_icon_size_for_stock_size (stock_size);
+ info = caja_icon_info_lookup (icon, pixel_size);
+ result = caja_icon_info_get_pixbuf_at_size (info, pixel_size);
+ g_object_unref (info);
+
+ g_object_unref (icon);
+
+ return result;
+}
+
+GIcon *
+caja_bookmark_get_icon (CajaBookmark *bookmark)
+{
+ g_return_val_if_fail (CAJA_IS_BOOKMARK (bookmark), NULL);
+
+ /* Try to connect a file in case file exists now but didn't earlier. */
+ caja_bookmark_connect_file (bookmark);
+
+ if (bookmark->details->icon)
+ {
+ return g_object_ref (bookmark->details->icon);
+ }
+ return NULL;
+}
+
+GFile *
+caja_bookmark_get_location (CajaBookmark *bookmark)
+{
+ g_return_val_if_fail(CAJA_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.
+ */
+ caja_bookmark_connect_file (bookmark);
+
+ return g_object_ref (bookmark->details->location);
+}
+
+char *
+caja_bookmark_get_uri (CajaBookmark *bookmark)
+{
+ GFile *file;
+ char *uri;
+
+ file = caja_bookmark_get_location (bookmark);
+ uri = g_file_get_uri (file);
+ g_object_unref (file);
+ return uri;
+}
+
+
+/**
+ * caja_bookmark_set_name:
+ *
+ * Change the user-displayed name of a bookmark.
+ * @new_name: The new user-displayed name for this bookmark, mustn't be NULL.
+ *
+ * Returns: TRUE if the name changed else FALSE.
+ **/
+gboolean
+caja_bookmark_set_name (CajaBookmark *bookmark, const char *new_name)
+{
+ g_return_val_if_fail (new_name != NULL, FALSE);
+ g_return_val_if_fail (CAJA_IS_BOOKMARK (bookmark), FALSE);
+
+ if (strcmp (new_name, bookmark->details->name) == 0)
+ {
+ return FALSE;
+ }
+ else if (!bookmark->details->has_custom_name)
+ {
+ bookmark->details->has_custom_name = TRUE;
+ }
+
+ g_free (bookmark->details->name);
+ bookmark->details->name = g_strdup (new_name);
+
+ g_signal_emit (bookmark, signals[APPEARANCE_CHANGED], 0);
+
+ if (bookmark->details->has_custom_name)
+ {
+ g_signal_emit (bookmark, signals[CONTENTS_CHANGED], 0);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+caja_bookmark_icon_is_different (CajaBookmark *bookmark,
+ GIcon *new_icon)
+{
+ g_assert (CAJA_IS_BOOKMARK (bookmark));
+ g_assert (new_icon != NULL);
+
+ if (bookmark->details->icon == NULL)
+ {
+ return TRUE;
+ }
+
+ return !g_icon_equal (bookmark->details->icon, new_icon) != 0;
+}
+
+/**
+ * Update icon if there's a better one available.
+ * Return TRUE if the icon changed.
+ */
+static gboolean
+caja_bookmark_update_icon (CajaBookmark *bookmark)
+{
+ GIcon *new_icon;
+
+ g_assert (CAJA_IS_BOOKMARK (bookmark));
+
+ if (bookmark->details->file == NULL)
+ {
+ return FALSE;
+ }
+
+ if (!caja_file_is_local (bookmark->details->file))
+ {
+ /* never update icons for remote bookmarks */
+ return FALSE;
+ }
+
+ if (!caja_file_is_not_yet_confirmed (bookmark->details->file) &&
+ caja_file_check_if_ready (bookmark->details->file,
+ CAJA_FILE_ATTRIBUTES_FOR_ICON))
+ {
+ new_icon = caja_file_get_gicon (bookmark->details->file, 0);
+ if (caja_bookmark_icon_is_different (bookmark, new_icon))
+ {
+ if (bookmark->details->icon)
+ {
+ g_object_unref (bookmark->details->icon);
+ }
+ bookmark->details->icon = new_icon;
+ return TRUE;
+ }
+ g_object_unref (new_icon);
+ }
+
+ return FALSE;
+}
+
+static void
+bookmark_file_changed_callback (CajaFile *file, CajaBookmark *bookmark)
+{
+ GFile *location;
+ gboolean should_emit_appearance_changed_signal;
+ gboolean should_emit_contents_changed_signal;
+ char *display_name;
+
+ g_assert (CAJA_IS_FILE (file));
+ g_assert (CAJA_IS_BOOKMARK (bookmark));
+ g_assert (file == bookmark->details->file);
+
+ should_emit_appearance_changed_signal = FALSE;
+ should_emit_contents_changed_signal = FALSE;
+ location = caja_file_get_location (file);
+
+ if (!g_file_equal (bookmark->details->location, location) &&
+ !caja_file_is_in_trash (file))
+ {
+ g_object_unref (bookmark->details->location);
+ bookmark->details->location = location;
+ should_emit_contents_changed_signal = TRUE;
+ }
+ else
+ {
+ g_object_unref (location);
+ }
+
+ if (caja_file_is_gone (file) ||
+ caja_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 CajaFile 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 CajaFile 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 CajaFile 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.
+ */
+ caja_bookmark_disconnect_file (bookmark);
+ }
+ else if (caja_bookmark_update_icon (bookmark))
+ {
+ /* File hasn't gone away, but it has changed
+ * in a way that affected its icon.
+ */
+ should_emit_appearance_changed_signal = TRUE;
+ }
+
+ if (!bookmark->details->has_custom_name)
+ {
+ display_name = caja_file_get_display_name (file);
+
+ if (strcmp (bookmark->details->name, display_name) != 0)
+ {
+ g_free (bookmark->details->name);
+ bookmark->details->name = display_name;
+ should_emit_appearance_changed_signal = TRUE;
+ }
+ else
+ {
+ g_free (display_name);
+ }
+ }
+
+ if (should_emit_appearance_changed_signal)
+ {
+ g_signal_emit (bookmark, signals[APPEARANCE_CHANGED], 0);
+ }
+
+ if (should_emit_contents_changed_signal)
+ {
+ g_signal_emit (bookmark, signals[CONTENTS_CHANGED], 0);
+ }
+}
+
+/**
+ * caja_bookmark_set_icon_to_default:
+ *
+ * Reset the icon to either the missing bookmark icon or the generic
+ * bookmark icon, depending on whether the file still exists.
+ */
+static void
+caja_bookmark_set_icon_to_default (CajaBookmark *bookmark)
+{
+ GIcon *icon, *emblemed_icon, *folder;
+ GEmblem *emblem;
+
+ if (bookmark->details->icon)
+ {
+ g_object_unref (bookmark->details->icon);
+ }
+
+ folder = g_themed_icon_new (CAJA_ICON_FOLDER);
+
+ if (caja_bookmark_uri_known_not_to_exist (bookmark))
+ {
+ icon = g_themed_icon_new (GTK_STOCK_DIALOG_WARNING);
+ emblem = g_emblem_new (icon);
+
+ emblemed_icon = g_emblemed_icon_new (folder, emblem);
+
+ g_object_unref (emblem);
+ g_object_unref (icon);
+ g_object_unref (folder);
+
+ folder = emblemed_icon;
+ }
+
+ bookmark->details->icon = folder;
+}
+
+static void
+caja_bookmark_disconnect_file (CajaBookmark *bookmark)
+{
+ g_assert (CAJA_IS_BOOKMARK (bookmark));
+
+ if (bookmark->details->file != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (bookmark->details->file,
+ G_CALLBACK (bookmark_file_changed_callback),
+ bookmark);
+ caja_file_unref (bookmark->details->file);
+ bookmark->details->file = NULL;
+ }
+
+ if (bookmark->details->icon != NULL)
+ {
+ g_object_unref (bookmark->details->icon);
+ bookmark->details->icon = NULL;
+ }
+}
+
+static void
+caja_bookmark_connect_file (CajaBookmark *bookmark)
+{
+ char *display_name;
+
+ g_assert (CAJA_IS_BOOKMARK (bookmark));
+
+ if (bookmark->details->file != NULL)
+ {
+ return;
+ }
+
+ if (!caja_bookmark_uri_known_not_to_exist (bookmark))
+ {
+ bookmark->details->file = caja_file_get (bookmark->details->location);
+ g_assert (!caja_file_is_gone (bookmark->details->file));
+
+ g_signal_connect_object (bookmark->details->file, "changed",
+ G_CALLBACK (bookmark_file_changed_callback), bookmark, 0);
+ }
+
+ /* Set icon based on available information; don't force network i/o
+ * to get any currently unknown information.
+ */
+ if (!caja_bookmark_update_icon (bookmark))
+ {
+ if (bookmark->details->icon == NULL || bookmark->details->file == NULL)
+ {
+ caja_bookmark_set_icon_to_default (bookmark);
+ }
+ }
+
+ if (!bookmark->details->has_custom_name &&
+ bookmark->details->file &&
+ caja_file_check_if_ready (bookmark->details->file, CAJA_FILE_ATTRIBUTE_INFO))
+ {
+ display_name = caja_file_get_display_name (bookmark->details->file);
+ if (strcmp (bookmark->details->name, display_name) != 0)
+ {
+ g_free (bookmark->details->name);
+ bookmark->details->name = display_name;
+ }
+ else
+ {
+ g_free (display_name);
+ }
+ }
+}
+
+CajaBookmark *
+caja_bookmark_new (GFile *location, const char *name, gboolean has_custom_name,
+ GIcon *icon)
+{
+ CajaBookmark *new_bookmark;
+
+ new_bookmark = CAJA_BOOKMARK (g_object_new (CAJA_TYPE_BOOKMARK, NULL));
+ g_object_ref_sink (new_bookmark);
+
+ new_bookmark->details->name = g_strdup (name);
+ new_bookmark->details->location = g_object_ref (location);
+ new_bookmark->details->has_custom_name = has_custom_name;
+ if (icon)
+ {
+ new_bookmark->details->icon = g_object_ref (icon);
+ }
+
+ caja_bookmark_connect_file (new_bookmark);
+
+ return new_bookmark;
+}
+
+static GtkWidget *
+create_image_widget_for_bookmark (CajaBookmark *bookmark)
+{
+ GdkPixbuf *pixbuf;
+ GtkWidget *widget;
+
+ pixbuf = caja_bookmark_get_pixbuf (bookmark, GTK_ICON_SIZE_MENU);
+ if (pixbuf == NULL)
+ {
+ return NULL;
+ }
+
+ widget = gtk_image_new_from_pixbuf (pixbuf);
+
+ g_object_unref (pixbuf);
+ return widget;
+}
+
+/**
+ * caja_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 *
+caja_bookmark_menu_item_new (CajaBookmark *bookmark)
+{
+ GtkWidget *menu_item;
+ GtkWidget *image_widget;
+ GtkLabel *label;
+
+ menu_item = gtk_image_menu_item_new_with_label (bookmark->details->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);
+
+ image_widget = create_image_widget_for_bookmark (bookmark);
+ if (image_widget != NULL)
+ {
+ gtk_widget_show (image_widget);
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item),
+ image_widget);
+ }
+
+ return menu_item;
+}
+
+gboolean
+caja_bookmark_uri_known_not_to_exist (CajaBookmark *bookmark)
+{
+ char *path_name;
+ gboolean exists;
+
+ /* Convert to a path, returning FALSE if not local. */
+ if (!g_file_is_native (bookmark->details->location))
+ {
+ return FALSE;
+ }
+ path_name = g_file_get_path (bookmark->details->location);
+
+ /* Now check if the file exists (sync. call OK because it is local). */
+ exists = g_file_test (path_name, G_FILE_TEST_EXISTS);
+ g_free (path_name);
+
+ return !exists;
+}
+
+void
+caja_bookmark_set_scroll_pos (CajaBookmark *bookmark,
+ const char *uri)
+{
+ g_free (bookmark->details->scroll_file);
+ bookmark->details->scroll_file = g_strdup (uri);
+}
+
+char *
+caja_bookmark_get_scroll_pos (CajaBookmark *bookmark)
+{
+ return g_strdup (bookmark->details->scroll_file);
+}
diff --git a/libcaja-private/caja-bookmark.h b/libcaja-private/caja-bookmark.h
new file mode 100644
index 00000000..2bf44208
--- /dev/null
+++ b/libcaja-private/caja-bookmark.h
@@ -0,0 +1,100 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-bookmark.h - interface for individual bookmarks.
+
+ Copyright (C) 1999, 2000 Eazel, Inc.
+
+ The Mate 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 Mate 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 Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: John Sullivan <[email protected]>
+*/
+
+#ifndef CAJA_BOOKMARK_H
+#define CAJA_BOOKMARK_H
+
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+typedef struct CajaBookmark CajaBookmark;
+
+#define CAJA_TYPE_BOOKMARK caja_bookmark_get_type()
+#define CAJA_BOOKMARK(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_BOOKMARK, CajaBookmark))
+#define CAJA_BOOKMARK_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_BOOKMARK, CajaBookmarkClass))
+#define CAJA_IS_BOOKMARK(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_BOOKMARK))
+#define CAJA_IS_BOOKMARK_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_BOOKMARK))
+#define CAJA_BOOKMARK_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_BOOKMARK, CajaBookmarkClass))
+
+typedef struct CajaBookmarkDetails CajaBookmarkDetails;
+
+struct CajaBookmark
+{
+ GObject object;
+ CajaBookmarkDetails *details;
+};
+
+struct CajaBookmarkClass
+{
+ GObjectClass parent_class;
+
+ /* Signals that clients can connect to. */
+
+ /* The appearance_changed signal is emitted when the bookmark's
+ * name or icon has changed.
+ */
+ void (* appearance_changed) (CajaBookmark *bookmark);
+
+ /* The contents_changed signal is emitted when the bookmark's
+ * URI has changed.
+ */
+ void (* contents_changed) (CajaBookmark *bookmark);
+};
+
+typedef struct CajaBookmarkClass CajaBookmarkClass;
+
+GType caja_bookmark_get_type (void);
+CajaBookmark * caja_bookmark_new (GFile *location,
+ const char *name,
+ gboolean has_custom_name,
+ GIcon *icon);
+CajaBookmark * caja_bookmark_copy (CajaBookmark *bookmark);
+char * caja_bookmark_get_name (CajaBookmark *bookmark);
+GFile * caja_bookmark_get_location (CajaBookmark *bookmark);
+char * caja_bookmark_get_uri (CajaBookmark *bookmark);
+GIcon * caja_bookmark_get_icon (CajaBookmark *bookmark);
+gboolean caja_bookmark_get_has_custom_name (CajaBookmark *bookmark);
+gboolean caja_bookmark_set_name (CajaBookmark *bookmark,
+ const char *new_name);
+gboolean caja_bookmark_uri_known_not_to_exist (CajaBookmark *bookmark);
+int caja_bookmark_compare_with (gconstpointer a,
+ gconstpointer b);
+int caja_bookmark_compare_uris (gconstpointer a,
+ gconstpointer b);
+
+void caja_bookmark_set_scroll_pos (CajaBookmark *bookmark,
+ const char *uri);
+char * caja_bookmark_get_scroll_pos (CajaBookmark *bookmark);
+
+
+/* Helper functions for displaying bookmarks */
+GdkPixbuf * caja_bookmark_get_pixbuf (CajaBookmark *bookmark,
+ GtkIconSize icon_size);
+GtkWidget * caja_bookmark_menu_item_new (CajaBookmark *bookmark);
+
+#endif /* CAJA_BOOKMARK_H */
diff --git a/libcaja-private/caja-cell-renderer-pixbuf-emblem.c b/libcaja-private/caja-cell-renderer-pixbuf-emblem.c
new file mode 100644
index 00000000..150e9eb2
--- /dev/null
+++ b/libcaja-private/caja-cell-renderer-pixbuf-emblem.c
@@ -0,0 +1,519 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-cell-renderer-pixbuf-emblem.c: cell renderer which can render
+ an emblem on top of a pixbuf (for use in FMListView and FMTreeView)
+
+ Copyright (C) 2003 Juerg Billeter
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with this program; if not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ This is based on GtkCellRendererPixbuf written by
+ Jonathan Blandford <[email protected]>
+
+ Author: Juerg Billeter <[email protected]>
+*/
+
+#include "caja-cell-renderer-pixbuf-emblem.h"
+
+static void caja_cell_renderer_pixbuf_emblem_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void caja_cell_renderer_pixbuf_emblem_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void caja_cell_renderer_pixbuf_emblem_init (CajaCellRendererPixbufEmblem *cellpixbuf);
+static void caja_cell_renderer_pixbuf_emblem_class_init (CajaCellRendererPixbufEmblemClass *klass);
+static void caja_cell_renderer_pixbuf_emblem_finalize (GObject *object);
+static void caja_cell_renderer_pixbuf_emblem_create_stock_pixbuf (CajaCellRendererPixbufEmblem *cellpixbuf,
+ GtkWidget *widget);
+static void caja_cell_renderer_pixbuf_emblem_get_size (GtkCellRenderer *cell,
+ GtkWidget *widget,
+ GdkRectangle *rectangle,
+ gint *x_offset,
+ gint *y_offset,
+ gint *width,
+ gint *height);
+static void caja_cell_renderer_pixbuf_emblem_render (GtkCellRenderer *cell,
+ GdkWindow *window,
+ GtkWidget *widget,
+ GdkRectangle *background_area,
+ GdkRectangle *cell_area,
+ GdkRectangle *expose_area,
+ GtkCellRendererState flags);
+
+enum
+{
+ PROP_ZERO,
+ PROP_PIXBUF,
+ PROP_PIXBUF_EXPANDER_OPEN,
+ PROP_PIXBUF_EXPANDER_CLOSED,
+ PROP_STOCK_ID,
+ PROP_STOCK_SIZE,
+ PROP_STOCK_DETAIL,
+ PROP_PIXBUF_EMBLEM
+};
+
+#define CELLINFO_KEY "caja-cell-renderer-pixbuf-emblem-info"
+
+typedef struct _CajaCellRendererPixbufEmblemInfo CajaCellRendererPixbufEmblemInfo;
+struct _CajaCellRendererPixbufEmblemInfo
+{
+ gchar *stock_id;
+ GtkIconSize stock_size;
+ gchar *stock_detail;
+};
+
+G_DEFINE_TYPE (CajaCellRendererPixbufEmblem, caja_cell_renderer_pixbuf_emblem, GTK_TYPE_CELL_RENDERER);
+
+static void
+caja_cell_renderer_pixbuf_emblem_init (CajaCellRendererPixbufEmblem *cellpixbuf)
+{
+ CajaCellRendererPixbufEmblemInfo *cellinfo;
+
+ cellinfo = g_new0 (CajaCellRendererPixbufEmblemInfo, 1);
+ cellinfo->stock_size = GTK_ICON_SIZE_MENU;
+ g_object_set_data (G_OBJECT (cellpixbuf), CELLINFO_KEY, cellinfo);
+}
+
+static void
+caja_cell_renderer_pixbuf_emblem_class_init (CajaCellRendererPixbufEmblemClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (klass);
+
+ object_class->finalize = caja_cell_renderer_pixbuf_emblem_finalize;
+
+ object_class->get_property = caja_cell_renderer_pixbuf_emblem_get_property;
+ object_class->set_property = caja_cell_renderer_pixbuf_emblem_set_property;
+
+ cell_class->get_size = caja_cell_renderer_pixbuf_emblem_get_size;
+ cell_class->render = caja_cell_renderer_pixbuf_emblem_render;
+
+ g_object_class_install_property (object_class,
+ PROP_PIXBUF,
+ g_param_spec_object ("pixbuf",
+ "Pixbuf Object",
+ "The pixbuf to render",
+ GDK_TYPE_PIXBUF,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE));
+
+ g_object_class_install_property (object_class,
+ PROP_PIXBUF_EXPANDER_OPEN,
+ g_param_spec_object ("pixbuf_expander_open",
+ "Pixbuf Expander Open",
+ "Pixbuf for open expander",
+ GDK_TYPE_PIXBUF,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE));
+
+ g_object_class_install_property (object_class,
+ PROP_PIXBUF_EXPANDER_CLOSED,
+ g_param_spec_object ("pixbuf_expander_closed",
+ "Pixbuf Expander Closed",
+ "Pixbuf for closed expander",
+ GDK_TYPE_PIXBUF,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE));
+
+ g_object_class_install_property (object_class,
+ PROP_STOCK_ID,
+ g_param_spec_string ("stock_id",
+ "Stock ID",
+ "The stock ID of the stock icon to render",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_STOCK_SIZE,
+ g_param_spec_enum ("stock_size",
+ "Size",
+ "The size of the rendered icon",
+ GTK_TYPE_ICON_SIZE,
+ GTK_ICON_SIZE_MENU,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_STOCK_DETAIL,
+ g_param_spec_string ("stock_detail",
+ "Detail",
+ "Render detail to pass to the theme engine",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_PIXBUF_EMBLEM,
+ g_param_spec_object ("pixbuf_emblem",
+ "Pixbuf Emblem Object",
+ "The emblem to overlay",
+ GDK_TYPE_PIXBUF,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE));
+}
+
+static void
+caja_cell_renderer_pixbuf_emblem_finalize (GObject *object)
+{
+ CajaCellRendererPixbufEmblem *cellpixbuf = CAJA_CELL_RENDERER_PIXBUF_EMBLEM (object);
+ CajaCellRendererPixbufEmblemInfo *cellinfo = g_object_get_data (object, CELLINFO_KEY);
+
+ if (cellpixbuf->pixbuf && cellinfo->stock_id)
+ {
+ g_object_unref (cellpixbuf->pixbuf);
+ }
+
+ if (cellinfo->stock_id)
+ {
+ g_free (cellinfo->stock_id);
+ }
+
+ if (cellinfo->stock_detail)
+ {
+ g_free (cellinfo->stock_detail);
+ }
+
+ g_free (cellinfo);
+ g_object_set_data (object, CELLINFO_KEY, NULL);
+
+ (* G_OBJECT_CLASS (caja_cell_renderer_pixbuf_emblem_parent_class)->finalize) (object);
+}
+
+static void
+caja_cell_renderer_pixbuf_emblem_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ CajaCellRendererPixbufEmblem *cellpixbuf = CAJA_CELL_RENDERER_PIXBUF_EMBLEM (object);
+ CajaCellRendererPixbufEmblemInfo *cellinfo = g_object_get_data (object, CELLINFO_KEY);
+
+ switch (param_id)
+ {
+ case PROP_PIXBUF:
+ g_value_set_object (value,
+ cellpixbuf->pixbuf ? G_OBJECT (cellpixbuf->pixbuf) : NULL);
+ break;
+ case PROP_PIXBUF_EXPANDER_OPEN:
+ g_value_set_object (value,
+ cellpixbuf->pixbuf_expander_open ? G_OBJECT (cellpixbuf->pixbuf_expander_open) : NULL);
+ break;
+ case PROP_PIXBUF_EXPANDER_CLOSED:
+ g_value_set_object (value,
+ cellpixbuf->pixbuf_expander_closed ? G_OBJECT (cellpixbuf->pixbuf_expander_closed) : NULL);
+ break;
+ case PROP_STOCK_ID:
+ g_value_set_string (value, cellinfo->stock_id);
+ break;
+ case PROP_STOCK_SIZE:
+ g_value_set_enum (value, cellinfo->stock_size);
+ break;
+ case PROP_STOCK_DETAIL:
+ g_value_set_string (value, cellinfo->stock_detail);
+ break;
+ case PROP_PIXBUF_EMBLEM:
+ g_value_set_object (value,
+ cellpixbuf->pixbuf_emblem ? G_OBJECT (cellpixbuf->pixbuf_emblem) : NULL);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+static void
+caja_cell_renderer_pixbuf_emblem_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GdkPixbuf *pixbuf;
+ CajaCellRendererPixbufEmblem *cellpixbuf = CAJA_CELL_RENDERER_PIXBUF_EMBLEM (object);
+ CajaCellRendererPixbufEmblemInfo *cellinfo = g_object_get_data (object, CELLINFO_KEY);
+
+ switch (param_id)
+ {
+ case PROP_PIXBUF:
+ pixbuf = (GdkPixbuf*) g_value_get_object (value);
+ if (pixbuf)
+ {
+ g_object_ref (pixbuf);
+ }
+ if (cellpixbuf->pixbuf)
+ {
+ g_object_unref (cellpixbuf->pixbuf);
+ }
+ cellpixbuf->pixbuf = pixbuf;
+ break;
+ case PROP_PIXBUF_EXPANDER_OPEN:
+ pixbuf = (GdkPixbuf*) g_value_get_object (value);
+ if (pixbuf)
+ {
+ g_object_ref (pixbuf);
+ }
+ if (cellpixbuf->pixbuf_expander_open)
+ {
+ g_object_unref (cellpixbuf->pixbuf_expander_open);
+ }
+ cellpixbuf->pixbuf_expander_open = pixbuf;
+ break;
+ case PROP_PIXBUF_EXPANDER_CLOSED:
+ pixbuf = (GdkPixbuf*) g_value_get_object (value);
+ if (pixbuf)
+ {
+ g_object_ref (pixbuf);
+ }
+ if (cellpixbuf->pixbuf_expander_closed)
+ {
+ g_object_unref (cellpixbuf->pixbuf_expander_closed);
+ }
+ cellpixbuf->pixbuf_expander_closed = pixbuf;
+ break;
+ case PROP_STOCK_ID:
+ if (cellinfo->stock_id)
+ {
+ g_free (cellinfo->stock_id);
+ }
+ cellinfo->stock_id = g_strdup (g_value_get_string (value));
+ break;
+ case PROP_STOCK_SIZE:
+ cellinfo->stock_size = g_value_get_enum (value);
+ break;
+ case PROP_STOCK_DETAIL:
+ if (cellinfo->stock_detail)
+ {
+ g_free (cellinfo->stock_detail);
+ }
+ cellinfo->stock_detail = g_strdup (g_value_get_string (value));
+ break;
+ case PROP_PIXBUF_EMBLEM:
+ pixbuf = (GdkPixbuf *) g_value_get_object (value);
+ if (pixbuf)
+ {
+ g_object_ref (pixbuf);
+ }
+ if (cellpixbuf->pixbuf_emblem)
+ {
+ g_object_unref (cellpixbuf->pixbuf_emblem);
+ }
+ cellpixbuf->pixbuf_emblem = pixbuf;
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+GtkCellRenderer *
+caja_cell_renderer_pixbuf_emblem_new (void)
+{
+ return g_object_new (CAJA_TYPE_CELL_RENDERER_PIXBUF_EMBLEM, NULL);
+}
+
+static void
+caja_cell_renderer_pixbuf_emblem_create_stock_pixbuf (CajaCellRendererPixbufEmblem *cellpixbuf,
+ GtkWidget *widget)
+{
+ CajaCellRendererPixbufEmblemInfo *cellinfo = g_object_get_data (G_OBJECT (cellpixbuf), CELLINFO_KEY);
+
+ if (cellpixbuf->pixbuf)
+ {
+ g_object_unref (cellpixbuf->pixbuf);
+ }
+
+ cellpixbuf->pixbuf = gtk_widget_render_icon (widget,
+ cellinfo->stock_id,
+ cellinfo->stock_size,
+ cellinfo->stock_detail);
+}
+
+static void
+caja_cell_renderer_pixbuf_emblem_get_size (GtkCellRenderer *cell,
+ GtkWidget *widget,
+ GdkRectangle *cell_area,
+ gint *x_offset,
+ gint *y_offset,
+ gint *width,
+ gint *height)
+{
+ CajaCellRendererPixbufEmblem *cellpixbuf = (CajaCellRendererPixbufEmblem *) cell;
+ CajaCellRendererPixbufEmblemInfo *cellinfo = g_object_get_data (G_OBJECT (cell), CELLINFO_KEY);
+ gint pixbuf_width = 0;
+ gint pixbuf_height = 0;
+ gint calc_width;
+ gint calc_height;
+ gint xpad, ypad;
+
+ if (!cellpixbuf->pixbuf && cellinfo->stock_id)
+ caja_cell_renderer_pixbuf_emblem_create_stock_pixbuf (cellpixbuf, widget);
+
+ if (cellpixbuf->pixbuf)
+ {
+ pixbuf_width = gdk_pixbuf_get_width (cellpixbuf->pixbuf);
+ pixbuf_height = gdk_pixbuf_get_height (cellpixbuf->pixbuf);
+ }
+ if (cellpixbuf->pixbuf_expander_open)
+ {
+ pixbuf_width = MAX (pixbuf_width, gdk_pixbuf_get_width (cellpixbuf->pixbuf_expander_open));
+ pixbuf_height = MAX (pixbuf_height, gdk_pixbuf_get_height (cellpixbuf->pixbuf_expander_open));
+ }
+ if (cellpixbuf->pixbuf_expander_closed)
+ {
+ pixbuf_width = MAX (pixbuf_width, gdk_pixbuf_get_width (cellpixbuf->pixbuf_expander_closed));
+ pixbuf_height = MAX (pixbuf_height, gdk_pixbuf_get_height (cellpixbuf->pixbuf_expander_closed));
+ }
+
+ gtk_cell_renderer_get_padding (cell, &xpad, &ypad);
+ calc_width = xpad * 2 + pixbuf_width;
+ calc_height = ypad * 2 + pixbuf_height;
+
+ if (x_offset) *x_offset = 0;
+ if (y_offset) *y_offset = 0;
+
+ if (cell_area && pixbuf_width > 0 && pixbuf_height > 0)
+ {
+ gfloat xalign, yalign;
+
+ gtk_cell_renderer_get_alignment (cell, &xalign, &yalign);
+ if (x_offset)
+ {
+ *x_offset = (((gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) ?
+ 1.0 - xalign : xalign) *
+ (cell_area->width - calc_width - 2 * xpad));
+ *x_offset = MAX (*x_offset, 0) + xpad;
+ }
+ if (y_offset)
+ {
+ *y_offset = (yalign *
+ (cell_area->height - calc_height - 2 * ypad));
+ *y_offset = MAX (*y_offset, 0) + ypad;
+ }
+ }
+
+ if (width)
+ *width = calc_width;
+
+ if (height)
+ *height = calc_height;
+}
+
+static void
+caja_cell_renderer_pixbuf_emblem_render (GtkCellRenderer *cell,
+ GdkWindow *window,
+ GtkWidget *widget,
+ GdkRectangle *background_area,
+ GdkRectangle *cell_area,
+ GdkRectangle *expose_area,
+ GtkCellRendererState flags)
+
+{
+ CajaCellRendererPixbufEmblem *cellpixbuf = (CajaCellRendererPixbufEmblem *) cell;
+ CajaCellRendererPixbufEmblemInfo *cellinfo = g_object_get_data (G_OBJECT (cell), CELLINFO_KEY);
+ GdkPixbuf *pixbuf;
+ GdkRectangle pix_rect;
+ GdkRectangle pix_emblem_rect;
+ GdkRectangle draw_rect;
+ gboolean stock_pixbuf = FALSE;
+ gint xpad, ypad;
+ gboolean is_expander, is_expanded;
+
+ pixbuf = cellpixbuf->pixbuf;
+ g_object_get (cell,
+ "is-expander", &is_expander,
+ "is-expanded", &is_expanded,
+ NULL);
+ if (is_expander)
+ {
+ if (is_expanded &&
+ cellpixbuf->pixbuf_expander_open != NULL)
+ {
+ pixbuf = cellpixbuf->pixbuf_expander_open;
+ }
+ else if (!is_expanded &&
+ cellpixbuf->pixbuf_expander_closed != NULL)
+ {
+ pixbuf = cellpixbuf->pixbuf_expander_closed;
+ }
+ }
+
+ if (!pixbuf && !cellinfo->stock_id)
+ {
+ return;
+ }
+ else if (!pixbuf && cellinfo->stock_id)
+ {
+ stock_pixbuf = TRUE;
+ }
+
+ caja_cell_renderer_pixbuf_emblem_get_size (cell, widget, cell_area,
+ &pix_rect.x,
+ &pix_rect.y,
+ &pix_rect.width,
+ &pix_rect.height);
+
+ if (stock_pixbuf)
+ pixbuf = cellpixbuf->pixbuf;
+
+ gtk_cell_renderer_get_padding (cell, &xpad, &ypad);
+ pix_rect.x += cell_area->x;
+ pix_rect.y += cell_area->y;
+ pix_rect.width -= xpad * 2;
+ pix_rect.height -= ypad * 2;
+
+ if (gdk_rectangle_intersect (cell_area, &pix_rect, &draw_rect) &&
+ gdk_rectangle_intersect (expose_area, &draw_rect, &draw_rect))
+ {
+ gdk_draw_pixbuf (window,
+ gtk_widget_get_style (widget)->black_gc,
+ pixbuf,
+ /* pixbuf 0, 0 is at pix_rect.x, pix_rect.y */
+ draw_rect.x - pix_rect.x,
+ draw_rect.y - pix_rect.y,
+ draw_rect.x,
+ draw_rect.y,
+ draw_rect.width,
+ draw_rect.height,
+ GDK_RGB_DITHER_NORMAL,
+ 0, 0);
+ }
+
+ if (cellpixbuf->pixbuf_emblem)
+ {
+ pix_emblem_rect.width = gdk_pixbuf_get_width (cellpixbuf->pixbuf_emblem);
+ pix_emblem_rect.height = gdk_pixbuf_get_height (cellpixbuf->pixbuf_emblem);
+ pix_emblem_rect.x = pix_rect.x;
+ pix_emblem_rect.y = pix_rect.y + pix_rect.height - pix_emblem_rect.height;
+ if (gdk_rectangle_intersect (cell_area, &pix_emblem_rect, &draw_rect) &&
+ gdk_rectangle_intersect (expose_area, &draw_rect, &draw_rect))
+ {
+ gdk_draw_pixbuf (window,
+ gtk_widget_get_style (widget)->black_gc,
+ cellpixbuf->pixbuf_emblem,
+ /* pixbuf 0, 0 is at pix_emblem_rect.x, pix_emblem_rect.y */
+ draw_rect.x - pix_emblem_rect.x,
+ draw_rect.y - pix_emblem_rect.y,
+ draw_rect.x,
+ draw_rect.y,
+ draw_rect.width,
+ draw_rect.height,
+ GDK_RGB_DITHER_NORMAL,
+ 0, 0);
+ }
+ }
+}
diff --git a/libcaja-private/caja-cell-renderer-pixbuf-emblem.h b/libcaja-private/caja-cell-renderer-pixbuf-emblem.h
new file mode 100644
index 00000000..404c0d64
--- /dev/null
+++ b/libcaja-private/caja-cell-renderer-pixbuf-emblem.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-cell-renderer-pixbuf-emblem.h: cell renderer which can render
+ an emblem on top of a pixbuf (for use in FMListView and FMTreeView)
+
+ Copyright (C) 2003 Juerg Billeter
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with this program; if not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ This is based on GtkCellRendererPixbuf written by
+ Jonathan Blandford <[email protected]>
+
+ Author: Juerg Billeter <[email protected]>
+*/
+
+#ifndef CAJA_CELL_RENDERER_PIXBUF_EMBLEM_H
+#define CAJA_CELL_RENDERER_PIXBUF_EMBLEM_H
+
+#include <gtk/gtk.h>
+
+#define CAJA_TYPE_CELL_RENDERER_PIXBUF_EMBLEM caja_cell_renderer_pixbuf_emblem_get_type()
+#define CAJA_CELL_RENDERER_PIXBUF_EMBLEM(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_CELL_RENDERER_PIXBUF_EMBLEM, CajaCellRendererPixbufEmblem))
+#define CAJA_CELL_RENDERER_PIXBUF_EMBLEM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_CELL_RENDERER_PIXBUF_EMBLEM, CajaCellRendererPixbufEmblemClass))
+#define CAJA_IS_CELL_RENDERER_PIXBUF_EMBLEM(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_CELL_RENDERER_PIXBUF_EMBLEM))
+#define CAJA_IS_CELL_RENDERER_PIXBUF_EMBLEM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_CELL_RENDERER_PIXBUF_EMBLEM))
+#define CAJA_CELL_RENDERER_PIXBUF_EMBLEM_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_CELL_RENDERER_PIXBUF_EMBLEM, CajaCellRendererPixbufEmblemClass))
+
+typedef struct _CajaCellRendererPixbufEmblem CajaCellRendererPixbufEmblem;
+typedef struct _CajaCellRendererPixbufEmblemClass CajaCellRendererPixbufEmblemClass;
+
+struct _CajaCellRendererPixbufEmblem
+{
+ GtkCellRenderer parent;
+
+ /*< private >*/
+ GdkPixbuf *pixbuf;
+ GdkPixbuf *pixbuf_expander_open;
+ GdkPixbuf *pixbuf_expander_closed;
+ GdkPixbuf *pixbuf_emblem;
+};
+
+struct _CajaCellRendererPixbufEmblemClass
+{
+ GtkCellRendererClass parent_class;
+};
+
+GType caja_cell_renderer_pixbuf_emblem_get_type (void);
+GtkCellRenderer *caja_cell_renderer_pixbuf_emblem_new (void);
+
+#endif /* CAJA_CELL_RENDERER_PIXBUF_EMBLEM_H */
diff --git a/libcaja-private/caja-cell-renderer-text-ellipsized.c b/libcaja-private/caja-cell-renderer-text-ellipsized.c
new file mode 100644
index 00000000..7b200d2e
--- /dev/null
+++ b/libcaja-private/caja-cell-renderer-text-ellipsized.c
@@ -0,0 +1,80 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-cell-renderer-text-ellipsized.c: Cell renderer for text which
+ will use pango ellipsization but deactivate it temporarily for the size
+ calculation to get the size based on the actual text length.
+
+ Copyright (C) 2007 Martin Wehner
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with this program; if not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Martin Wehner <[email protected]>
+*/
+
+#include "caja-cell-renderer-text-ellipsized.h"
+
+#define ELLIPSIZE_PROP "ellipsize"
+
+static void caja_cell_renderer_text_ellipsized_get_size (GtkCellRenderer *cell,
+ GtkWidget *widget,
+ GdkRectangle *rectangle,
+ gint *x_offset,
+ gint *y_offset,
+ gint *width,
+ gint *height);
+
+G_DEFINE_TYPE (CajaCellRendererTextEllipsized, caja_cell_renderer_text_ellipsized,
+ GTK_TYPE_CELL_RENDERER_TEXT);
+
+static void
+caja_cell_renderer_text_ellipsized_init (CajaCellRendererTextEllipsized *cell)
+{
+ g_object_set (cell, ELLIPSIZE_PROP, PANGO_ELLIPSIZE_END, NULL);
+}
+
+static void
+caja_cell_renderer_text_ellipsized_class_init (CajaCellRendererTextEllipsizedClass *klass)
+{
+ GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (klass);
+
+ cell_class->get_size = caja_cell_renderer_text_ellipsized_get_size;
+}
+
+GtkCellRenderer *
+caja_cell_renderer_text_ellipsized_new (void)
+{
+ return g_object_new (CAJA_TYPE_CELL_RENDERER_TEXT_ELLIPSIZED, NULL);
+}
+
+static void
+caja_cell_renderer_text_ellipsized_get_size (GtkCellRenderer *cell,
+ GtkWidget *widget,
+ GdkRectangle *cell_area,
+ gint *x_offset,
+ gint *y_offset,
+ gint *width,
+ gint *height)
+{
+ g_object_set (cell, ELLIPSIZE_PROP, PANGO_ELLIPSIZE_NONE, NULL);
+
+ (* GTK_CELL_RENDERER_CLASS (caja_cell_renderer_text_ellipsized_parent_class)->get_size)
+ (cell, widget, cell_area,
+ x_offset, y_offset,
+ width, height);
+
+ g_object_set (cell, ELLIPSIZE_PROP, PANGO_ELLIPSIZE_END, NULL);
+}
+
diff --git a/libcaja-private/caja-cell-renderer-text-ellipsized.h b/libcaja-private/caja-cell-renderer-text-ellipsized.h
new file mode 100644
index 00000000..7ff2e3a2
--- /dev/null
+++ b/libcaja-private/caja-cell-renderer-text-ellipsized.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-cell-renderer-text-ellipsized.c: Cell renderer for text which
+ will use pango ellipsization but deactivate it temporarily for the size
+ calculation to get the size based on the actual text length.
+
+ Copyright (C) 2007 Martin Wehner
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with this program; if not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Martin Wehner <[email protected]>
+*/
+
+#ifndef CAJA_CELL_RENDERER_TEXT_ELLIPSIZED_H
+#define CAJA_CELL_RENDERER_TEXT_ELLIPSIZED_H
+
+#include <gtk/gtk.h>
+
+#define CAJA_TYPE_CELL_RENDERER_TEXT_ELLIPSIZED caja_cell_renderer_text_ellipsized_get_type()
+#define CAJA_CELL_RENDERER_TEXT_ELLIPSIZED(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_CELL_RENDERER_TEXT_ELLIPSIZED, CajaCellRendererTextEllipsized))
+#define CAJA_CELL_RENDERER_TEXT_ELLIPSIZED_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_CELL_RENDERER_TEXT_ELLIPSIZED, CajaCellRendererTextEllipsizedClass))
+#define CAJA_IS_CELL_RENDERER_TEXT_ELLIPSIZED(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_CELL_RENDERER_TEXT_ELLIPSIZED))
+#define CAJA_IS_CELL_RENDERER_TEXT_ELLIPSIZED_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_CELL_RENDERER_TEXT_ELLIPSIZED))
+#define CAJA_CELL_RENDERER_TEXT_ELLIPSIZED_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_CELL_RENDERER_TEXT_ELLIPSIZED, CajaCellRendererTextEllipsizedClass))
+
+
+typedef struct _CajaCellRendererTextEllipsized CajaCellRendererTextEllipsized;
+typedef struct _CajaCellRendererTextEllipsizedClass CajaCellRendererTextEllipsizedClass;
+
+struct _CajaCellRendererTextEllipsized
+{
+ GtkCellRendererText parent;
+};
+
+struct _CajaCellRendererTextEllipsizedClass
+{
+ GtkCellRendererTextClass parent_class;
+};
+
+GType caja_cell_renderer_text_ellipsized_get_type (void);
+GtkCellRenderer *caja_cell_renderer_text_ellipsized_new (void);
+
+#endif /* CAJA_CELL_RENDERER_TEXT_ELLIPSIZED_H */
diff --git a/libcaja-private/caja-clipboard-monitor.c b/libcaja-private/caja-clipboard-monitor.c
new file mode 100644
index 00000000..a2258cae
--- /dev/null
+++ b/libcaja-private/caja-clipboard-monitor.c
@@ -0,0 +1,338 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Alexander Larsson <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-clipboard-monitor.h"
+#include "caja-file.h"
+
+#include <eel/eel-debug.h>
+#include <eel/eel-gtk-macros.h>
+#include <eel/eel-glib-extensions.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 CajaClipboardMonitorDetails
+{
+ CajaClipboardInfo *info;
+};
+
+static guint signals[LAST_SIGNAL];
+static GdkAtom copied_files_atom;
+
+G_DEFINE_TYPE (CajaClipboardMonitor, caja_clipboard_monitor, G_TYPE_OBJECT);
+
+static CajaClipboardMonitor *clipboard_monitor = NULL;
+
+static void
+destroy_clipboard_monitor (void)
+{
+ if (clipboard_monitor != NULL)
+ {
+ g_object_unref (clipboard_monitor);
+ }
+}
+
+CajaClipboardMonitor *
+caja_clipboard_monitor_get (void)
+{
+ GtkClipboard *clipboard;
+
+ if (clipboard_monitor == NULL)
+ {
+ clipboard_monitor = CAJA_CLIPBOARD_MONITOR (g_object_new (CAJA_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 (caja_clipboard_monitor_emit_changed), NULL);
+ }
+ return clipboard_monitor;
+}
+
+void
+caja_clipboard_monitor_emit_changed (void)
+{
+ CajaClipboardMonitor *monitor;
+
+ monitor = caja_clipboard_monitor_get ();
+
+ g_signal_emit (monitor, signals[CLIPBOARD_CHANGED], 0);
+}
+
+static CajaClipboardInfo *
+caja_clipboard_info_new (GList *files,
+ gboolean cut)
+{
+ CajaClipboardInfo *info;
+
+ info = g_slice_new0 (CajaClipboardInfo);
+ info->files = caja_file_list_copy (files);
+ info->cut = cut;
+
+ return info;
+}
+
+static CajaClipboardInfo *
+caja_clipboard_info_copy (CajaClipboardInfo *info)
+{
+ CajaClipboardInfo *new_info;
+
+ new_info = NULL;
+
+ if (info != NULL)
+ {
+ new_info = caja_clipboard_info_new (info->files,
+ info->cut);
+ }
+
+ return new_info;
+}
+
+static void
+caja_clipboard_info_free (CajaClipboardInfo *info)
+{
+ caja_file_list_free (info->files);
+
+ g_slice_free (CajaClipboardInfo, info);
+}
+
+static void
+caja_clipboard_monitor_init (CajaClipboardMonitor *monitor)
+{
+ monitor->details =
+ G_TYPE_INSTANCE_GET_PRIVATE (monitor, CAJA_TYPE_CLIPBOARD_MONITOR,
+ CajaClipboardMonitorDetails);
+}
+
+static void
+clipboard_monitor_finalize (GObject *object)
+{
+ CajaClipboardMonitor *monitor;
+
+ monitor = CAJA_CLIPBOARD_MONITOR (object);
+
+ if (monitor->details->info != NULL)
+ {
+ caja_clipboard_info_free (monitor->details->info);
+ monitor->details->info = NULL;
+ }
+
+ G_OBJECT_CLASS (caja_clipboard_monitor_parent_class)->finalize (object);
+}
+
+static void
+caja_clipboard_monitor_class_init (CajaClipboardMonitorClass *klass)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->finalize = clipboard_monitor_finalize;
+
+ copied_files_atom = gdk_atom_intern ("x-special/mate-copied-files", FALSE);
+
+ signals[CLIPBOARD_CHANGED] =
+ g_signal_new ("clipboard_changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaClipboardMonitorClass, 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 (CajaClipboardMonitorClass, clipboard_info),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE,
+ 1, G_TYPE_POINTER);
+
+ g_type_class_add_private (klass, sizeof (CajaClipboardMonitorDetails));
+}
+
+void
+caja_clipboard_monitor_set_clipboard_info (CajaClipboardMonitor *monitor,
+ CajaClipboardInfo *info)
+{
+ if (monitor->details->info != NULL)
+ {
+ caja_clipboard_info_free (monitor->details->info);
+ monitor->details->info = NULL;
+ }
+
+ monitor->details->info = caja_clipboard_info_copy (info);
+
+ g_signal_emit (monitor, signals[CLIPBOARD_INFO], 0, monitor->details->info);
+
+ caja_clipboard_monitor_emit_changed ();
+}
+
+CajaClipboardInfo *
+caja_clipboard_monitor_get_clipboard_info (CajaClipboardMonitor *monitor)
+{
+ return monitor->details->info;
+}
+
+void
+caja_clear_clipboard_callback (GtkClipboard *clipboard,
+ gpointer user_data)
+{
+ caja_clipboard_monitor_set_clipboard_info
+ (caja_clipboard_monitor_get (), NULL);
+}
+
+static char *
+convert_file_list_to_string (CajaClipboardInfo *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 = caja_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
+caja_get_clipboard_callback (GtkClipboard *clipboard,
+ GtkSelectionData *selection_data,
+ guint info,
+ gpointer user_data)
+{
+ char **uris;
+ GList *l;
+ int i;
+ CajaClipboardInfo *clipboard_info;
+ GdkAtom target;
+
+ clipboard_info =
+ caja_clipboard_monitor_get_clipboard_info (caja_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] = caja_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, str, len);
+ g_free (str);
+ }
+}
diff --git a/libcaja-private/caja-clipboard-monitor.h b/libcaja-private/caja-clipboard-monitor.h
new file mode 100644
index 00000000..19b9617d
--- /dev/null
+++ b/libcaja-private/caja-clipboard-monitor.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Alexander Larsson <[email protected]>
+*/
+
+#ifndef CAJA_CLIPBOARD_MONITOR_H
+#define CAJA_CLIPBOARD_MONITOR_H
+
+#include <gtk/gtk.h>
+
+#define CAJA_TYPE_CLIPBOARD_MONITOR caja_clipboard_monitor_get_type()
+#define CAJA_CLIPBOARD_MONITOR(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_CLIPBOARD_MONITOR, CajaClipboardMonitor))
+#define CAJA_CLIPBOARD_MONITOR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_CLIPBOARD_MONITOR, CajaClipboardMonitorClass))
+#define CAJA_IS_CLIPBOARD_MONITOR(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_CLIPBOARD_MONITOR))
+#define CAJA_IS_CLIPBOARD_MONITOR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_CLIPBOARD_MONITOR))
+#define CAJA_CLIPBOARD_MONITOR_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_CLIPBOARD_MONITOR, CajaClipboardMonitorClass))
+
+typedef struct CajaClipboardMonitorDetails CajaClipboardMonitorDetails;
+typedef struct CajaClipboardInfo CajaClipboardInfo;
+
+typedef struct
+{
+ GObject parent_slot;
+
+ CajaClipboardMonitorDetails *details;
+} CajaClipboardMonitor;
+
+typedef struct
+{
+ GObjectClass parent_slot;
+
+ void (* clipboard_changed) (CajaClipboardMonitor *monitor);
+ void (* clipboard_info) (CajaClipboardMonitor *monitor,
+ CajaClipboardInfo *info);
+} CajaClipboardMonitorClass;
+
+struct CajaClipboardInfo
+{
+ GList *files;
+ gboolean cut;
+};
+
+GType caja_clipboard_monitor_get_type (void);
+
+CajaClipboardMonitor * caja_clipboard_monitor_get (void);
+void caja_clipboard_monitor_set_clipboard_info (CajaClipboardMonitor *monitor,
+ CajaClipboardInfo *info);
+CajaClipboardInfo * caja_clipboard_monitor_get_clipboard_info (CajaClipboardMonitor *monitor);
+void caja_clipboard_monitor_emit_changed (void);
+
+void caja_clear_clipboard_callback (GtkClipboard *clipboard,
+ gpointer user_data);
+void caja_get_clipboard_callback (GtkClipboard *clipboard,
+ GtkSelectionData *selection_data,
+ guint info,
+ gpointer user_data);
+
+
+
+#endif /* CAJA_CLIPBOARD_MONITOR_H */
+
diff --git a/libcaja-private/caja-clipboard.c b/libcaja-private/caja-clipboard.c
new file mode 100644
index 00000000..53a3b8d9
--- /dev/null
+++ b/libcaja-private/caja-clipboard.c
@@ -0,0 +1,692 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-clipboard.c
+ *
+ * Caja 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Rebecca Schulman <[email protected]>,
+ * Darin Adler <[email protected]>
+ */
+
+#include <config.h>
+#include "caja-clipboard.h"
+#include "caja-file-utilities.h"
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <eel/eel-glib-extensions.h>
+#include <string.h>
+
+typedef struct _TargetCallbackData TargetCallbackData;
+
+typedef void (* SelectAllCallback) (gpointer target);
+typedef void (* ConnectCallbacksFunc) (GObject *object,
+ TargetCallbackData *target_data);
+
+static void selection_changed_callback (GtkWidget *widget,
+ gpointer callback_data);
+static void owner_change_callback (GtkClipboard *clipboard,
+ GdkEventOwnerChange *event,
+ gpointer callback_data);
+struct _TargetCallbackData
+{
+ GtkUIManager *ui_manager;
+ GtkActionGroup *action_group;
+ gboolean shares_selection_changes;
+
+ SelectAllCallback select_all_callback;
+
+ ConnectCallbacksFunc connect_callbacks;
+ ConnectCallbacksFunc disconnect_callbacks;
+};
+
+static void
+cut_callback (gpointer target)
+{
+ g_assert (target != NULL);
+
+ g_signal_emit_by_name (target, "cut-clipboard");
+}
+
+static void
+copy_callback (gpointer target)
+{
+ g_assert (target != NULL);
+
+ g_signal_emit_by_name (target, "copy-clipboard");
+}
+
+static void
+paste_callback (gpointer target)
+{
+ g_assert (target != NULL);
+
+ g_signal_emit_by_name (target, "paste-clipboard");
+}
+
+static void
+editable_select_all_callback (gpointer target)
+{
+ GtkEditable *editable;
+
+ editable = GTK_EDITABLE (target);
+ g_assert (editable != NULL);
+
+ gtk_editable_set_position (editable, -1);
+ gtk_editable_select_region (editable, 0, -1);
+}
+
+static void
+text_view_select_all_callback (gpointer target)
+{
+ g_assert (GTK_IS_TEXT_VIEW (target));
+
+ g_signal_emit_by_name (target, "select-all", TRUE);
+}
+
+static void
+action_cut_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ cut_callback (callback_data);
+}
+
+static void
+action_copy_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ copy_callback (callback_data);
+}
+
+static void
+action_paste_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ paste_callback (callback_data);
+}
+
+static void
+action_select_all_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ TargetCallbackData *target_data;
+
+ g_assert (callback_data != NULL);
+
+ target_data = g_object_get_data (callback_data, "Caja:clipboard_target_data");
+ g_assert (target_data != NULL);
+
+ target_data->select_all_callback (callback_data);
+}
+
+static void
+received_clipboard_contents (GtkClipboard *clipboard,
+ GtkSelectionData *selection_data,
+ gpointer data)
+{
+ GtkActionGroup *action_group;
+ GtkAction *action;
+
+ action_group = data;
+
+ action = gtk_action_group_get_action (action_group,
+ "Paste");
+ if (action != NULL)
+ {
+ gtk_action_set_sensitive (action,
+ gtk_selection_data_targets_include_text (selection_data));
+ }
+
+ g_object_unref (action_group);
+}
+
+
+static void
+set_paste_sensitive_if_clipboard_contains_data (GtkActionGroup *action_group)
+{
+ GtkAction *action;
+ if (gdk_display_supports_selection_notification (gdk_display_get_default ()))
+ {
+ gtk_clipboard_request_contents (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD),
+ gdk_atom_intern ("TARGETS", FALSE),
+ received_clipboard_contents,
+ g_object_ref (action_group));
+ }
+ else
+ {
+ /* If selection notification isn't supported, always activate Paste */
+ action = gtk_action_group_get_action (action_group,
+ "Paste");
+ gtk_action_set_sensitive (action, TRUE);
+ }
+}
+
+static void
+set_clipboard_menu_items_sensitive (GtkActionGroup *action_group)
+{
+ GtkAction *action;
+
+ action = gtk_action_group_get_action (action_group,
+ "Cut");
+ gtk_action_set_sensitive (action, TRUE);
+ action = gtk_action_group_get_action (action_group,
+ "Copy");
+ gtk_action_set_sensitive (action, TRUE);
+}
+
+static void
+set_clipboard_menu_items_insensitive (GtkActionGroup *action_group)
+{
+ GtkAction *action;
+
+ action = gtk_action_group_get_action (action_group,
+ "Cut");
+ gtk_action_set_sensitive (action, FALSE);
+ action = gtk_action_group_get_action (action_group,
+ "Copy");
+ gtk_action_set_sensitive (action, FALSE);
+}
+
+static gboolean
+clipboard_items_are_merged_in (GtkWidget *widget)
+{
+ return GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget),
+ "Caja:clipboard_menu_items_merged"));
+}
+
+static void
+set_clipboard_items_are_merged_in (GObject *widget_as_object,
+ gboolean merged_in)
+{
+ g_object_set_data (widget_as_object,
+ "Caja:clipboard_menu_items_merged",
+ GINT_TO_POINTER (merged_in));
+}
+
+static void
+editable_connect_callbacks (GObject *object,
+ TargetCallbackData *target_data)
+{
+ g_signal_connect_after (object, "selection_changed",
+ G_CALLBACK (selection_changed_callback), target_data);
+ selection_changed_callback (GTK_WIDGET (object),
+ target_data);
+}
+
+static void
+editable_disconnect_callbacks (GObject *object,
+ TargetCallbackData *target_data)
+{
+ g_signal_handlers_disconnect_matched (object,
+ G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL,
+ G_CALLBACK (selection_changed_callback),
+ target_data);
+}
+
+static void
+text_buffer_update_sensitivity (GtkTextBuffer *buffer,
+ TargetCallbackData *target_data)
+{
+ g_assert (GTK_IS_TEXT_BUFFER (buffer));
+ g_assert (target_data != NULL);
+
+ if (gtk_text_buffer_get_selection_bounds (buffer, NULL, NULL))
+ {
+ set_clipboard_menu_items_sensitive (target_data->action_group);
+ }
+ else
+ {
+ set_clipboard_menu_items_insensitive (target_data->action_group);
+ }
+}
+
+static void
+text_buffer_delete_range (GtkTextBuffer *buffer,
+ GtkTextIter *iter1,
+ GtkTextIter *iter2,
+ TargetCallbackData *target_data)
+{
+ text_buffer_update_sensitivity (buffer, target_data);
+}
+
+static void
+text_buffer_mark_set (GtkTextBuffer *buffer,
+ GtkTextIter *iter,
+ GtkTextMark *mark,
+ TargetCallbackData *target_data)
+{
+ /* anonymous marks with NULL names refer to cursor moves */
+ if (gtk_text_mark_get_name (mark) != NULL)
+ {
+ text_buffer_update_sensitivity (buffer, target_data);
+ }
+}
+
+static void
+text_view_connect_callbacks (GObject *object,
+ TargetCallbackData *target_data)
+{
+ GtkTextBuffer *buffer;
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (object));
+ g_assert (buffer);
+
+ g_signal_connect_after (buffer, "mark-set",
+ G_CALLBACK (text_buffer_mark_set), target_data);
+ g_signal_connect_after (buffer, "delete-range",
+ G_CALLBACK (text_buffer_delete_range), target_data);
+ text_buffer_update_sensitivity (buffer, target_data);
+}
+
+static void
+text_view_disconnect_callbacks (GObject *object,
+ TargetCallbackData *target_data)
+{
+ GtkTextBuffer *buffer;
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (object));
+ g_assert (buffer);
+
+ g_signal_handlers_disconnect_matched (buffer,
+ G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL,
+ target_data);
+}
+
+static void
+merge_in_clipboard_menu_items (GObject *widget_as_object,
+ TargetCallbackData *target_data)
+{
+ gboolean add_selection_callback;
+
+ g_assert (target_data != NULL);
+
+ add_selection_callback = target_data->shares_selection_changes;
+
+ gtk_ui_manager_insert_action_group (target_data->ui_manager,
+ target_data->action_group, 0);
+
+ set_paste_sensitive_if_clipboard_contains_data (target_data->action_group);
+
+ g_signal_connect (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD), "owner_change",
+ G_CALLBACK (owner_change_callback), target_data);
+
+ if (add_selection_callback)
+ {
+ target_data->connect_callbacks (widget_as_object, target_data);
+ }
+ else
+ {
+ /* If we don't use sensitivity, everything should be on */
+ set_clipboard_menu_items_sensitive (target_data->action_group);
+ }
+ set_clipboard_items_are_merged_in (widget_as_object, TRUE);
+}
+
+static void
+merge_out_clipboard_menu_items (GObject *widget_as_object,
+ TargetCallbackData *target_data)
+
+{
+ gboolean selection_callback_was_added;
+
+ g_assert (target_data != NULL);
+
+ gtk_ui_manager_remove_action_group (target_data->ui_manager,
+ target_data->action_group);
+
+ g_signal_handlers_disconnect_matched (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD),
+ G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL,
+ G_CALLBACK (owner_change_callback),
+ target_data);
+
+ selection_callback_was_added = target_data->shares_selection_changes;
+
+ if (selection_callback_was_added)
+ {
+ target_data->disconnect_callbacks (widget_as_object, target_data);
+ }
+ set_clipboard_items_are_merged_in (widget_as_object, FALSE);
+}
+
+static gboolean
+focus_changed_callback (GtkWidget *widget,
+ GdkEventAny *event,
+ gpointer callback_data)
+{
+ /* Connect the component to the container if the widget has focus. */
+ if (gtk_widget_has_focus (widget))
+ {
+ if (!clipboard_items_are_merged_in (widget))
+ {
+ merge_in_clipboard_menu_items (G_OBJECT (widget), callback_data);
+ }
+ }
+ else
+ {
+ if (clipboard_items_are_merged_in (widget))
+ {
+ merge_out_clipboard_menu_items (G_OBJECT (widget), callback_data);
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+selection_changed_callback (GtkWidget *widget,
+ gpointer callback_data)
+{
+ TargetCallbackData *target_data;
+ GtkEditable *editable;
+ int start, end;
+
+ target_data = (TargetCallbackData *) callback_data;
+ g_assert (target_data != NULL);
+
+ editable = GTK_EDITABLE (widget);
+ g_assert (editable != NULL);
+
+ if (gtk_editable_get_selection_bounds (editable, &start, &end) && start != end)
+ {
+ set_clipboard_menu_items_sensitive (target_data->action_group);
+ }
+ else
+ {
+ set_clipboard_menu_items_insensitive (target_data->action_group);
+ }
+}
+
+static void
+owner_change_callback (GtkClipboard *clipboard,
+ GdkEventOwnerChange *event,
+ gpointer callback_data)
+{
+ TargetCallbackData *target_data;
+
+ g_assert (callback_data != NULL);
+ target_data = callback_data;
+
+ set_paste_sensitive_if_clipboard_contains_data (target_data->action_group);
+}
+
+static void
+target_destroy_callback (GtkObject *object,
+ gpointer callback_data)
+{
+ TargetCallbackData *target_data;
+
+ g_assert (callback_data != NULL);
+ target_data = callback_data;
+
+ if (clipboard_items_are_merged_in (GTK_WIDGET(object)))
+ {
+ merge_out_clipboard_menu_items (G_OBJECT (object), callback_data);
+ }
+}
+
+static void
+target_data_free (TargetCallbackData *target_data)
+{
+ g_object_unref (target_data->action_group);
+ g_free (target_data);
+}
+
+static const GtkActionEntry clipboard_entries[] =
+{
+ /* name, stock id */ { "Cut", GTK_STOCK_CUT,
+ /* label, accelerator */ NULL, NULL,
+ /* tooltip */ N_("Cut the selected text to the clipboard"),
+ G_CALLBACK (action_cut_callback)
+ },
+ /* name, stock id */ { "Copy", GTK_STOCK_COPY,
+ /* label, accelerator */ NULL, NULL,
+ /* tooltip */ N_("Copy the selected text to the clipboard"),
+ G_CALLBACK (action_copy_callback)
+ },
+ /* name, stock id */ { "Paste", GTK_STOCK_PASTE,
+ /* label, accelerator */ NULL, NULL,
+ /* tooltip */ N_("Paste the text stored on the clipboard"),
+ G_CALLBACK (action_paste_callback)
+ },
+ /* name, stock id */ { "Select All", NULL,
+ /* label, accelerator */ N_("Select _All"), "<control>A",
+ /* tooltip */ N_("Select all the text in a text field"),
+ G_CALLBACK (action_select_all_callback)
+ },
+};
+
+static TargetCallbackData *
+initialize_clipboard_component_with_callback_data (GtkEditable *target,
+ GtkUIManager *ui_manager,
+ gboolean shares_selection_changes,
+ SelectAllCallback select_all_callback,
+ ConnectCallbacksFunc connect_callbacks,
+ ConnectCallbacksFunc disconnect_callbacks)
+{
+ GtkActionGroup *action_group;
+ TargetCallbackData *target_data;
+
+ action_group = gtk_action_group_new ("ClipboardActions");
+ gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
+ gtk_action_group_add_actions (action_group,
+ clipboard_entries, G_N_ELEMENTS (clipboard_entries),
+ target);
+
+ /* Do the actual connection of the UI to the container at
+ * focus time, and disconnect at both focus and destroy
+ * time.
+ */
+ target_data = g_new (TargetCallbackData, 1);
+ target_data->ui_manager = ui_manager;
+ target_data->action_group = action_group;
+ target_data->shares_selection_changes = shares_selection_changes;
+ target_data->select_all_callback = select_all_callback;
+ target_data->connect_callbacks = connect_callbacks;
+ target_data->disconnect_callbacks = disconnect_callbacks;
+
+ return target_data;
+}
+
+static void
+caja_clipboard_real_set_up (gpointer target,
+ GtkUIManager *ui_manager,
+ gboolean shares_selection_changes,
+ SelectAllCallback select_all_callback,
+ ConnectCallbacksFunc connect_callbacks,
+ ConnectCallbacksFunc disconnect_callbacks)
+{
+ TargetCallbackData *target_data;
+
+ if (g_object_get_data (G_OBJECT (target), "Caja:clipboard_target_data") != NULL)
+ {
+ return;
+ }
+
+ target_data = initialize_clipboard_component_with_callback_data
+ (target,
+ ui_manager,
+ shares_selection_changes,
+ select_all_callback,
+ connect_callbacks,
+ disconnect_callbacks);
+
+ g_signal_connect (target, "focus_in_event",
+ G_CALLBACK (focus_changed_callback), target_data);
+ g_signal_connect (target, "focus_out_event",
+ G_CALLBACK (focus_changed_callback), target_data);
+ g_signal_connect (target, "destroy",
+ G_CALLBACK (target_destroy_callback), target_data);
+
+ g_object_set_data_full (G_OBJECT (target), "Caja:clipboard_target_data",
+ target_data, (GDestroyNotify) target_data_free);
+
+ /* Call the focus changed callback once to merge if the window is
+ * already in focus.
+ */
+ focus_changed_callback (GTK_WIDGET (target), NULL, target_data);
+}
+
+void
+caja_clipboard_set_up_editable (GtkEditable *target,
+ GtkUIManager *ui_manager,
+ gboolean shares_selection_changes)
+{
+ g_return_if_fail (GTK_IS_EDITABLE (target));
+ g_return_if_fail (GTK_IS_UI_MANAGER (ui_manager));
+
+ caja_clipboard_real_set_up (target, ui_manager,
+ shares_selection_changes,
+ editable_select_all_callback,
+ editable_connect_callbacks,
+ editable_disconnect_callbacks);
+}
+
+void
+caja_clipboard_set_up_text_view (GtkTextView *target,
+ GtkUIManager *ui_manager)
+{
+ g_return_if_fail (GTK_IS_TEXT_VIEW (target));
+ g_return_if_fail (GTK_IS_UI_MANAGER (ui_manager));
+
+ caja_clipboard_real_set_up (target, ui_manager, TRUE,
+ text_view_select_all_callback,
+ text_view_connect_callbacks,
+ text_view_disconnect_callbacks);
+}
+
+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*
+caja_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
+ {
+ guchar *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 = (guchar *) 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 *
+caja_clipboard_get (GtkWidget *widget)
+{
+ return gtk_clipboard_get_for_display (gtk_widget_get_display (GTK_WIDGET (widget)),
+ GDK_SELECTION_CLIPBOARD);
+}
+
+void
+caja_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 (caja_clipboard_get (widget),
+ copied_files_atom);
+ if (data == NULL)
+ {
+ return;
+ }
+
+ clipboard_item_uris = caja_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 (caja_clipboard_get (widget));
+ }
+
+ if (clipboard_item_uris)
+ {
+ eel_g_list_free_deep (clipboard_item_uris);
+ }
+}
diff --git a/libcaja-private/caja-clipboard.h b/libcaja-private/caja-clipboard.h
new file mode 100644
index 00000000..22dcbde7
--- /dev/null
+++ b/libcaja-private/caja-clipboard.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Rebecca Schulman <[email protected]>
+ */
+
+#ifndef CAJA_CLIPBOARD_H
+#define CAJA_CLIPBOARD_H
+
+#include <gtk/gtk.h>
+
+/* This makes this editable or text view put clipboard commands into
+ * the passed UI manager when the editable/text view is in focus.
+ * Callers in Caja normally get the UI manager from
+ * caja_window_get_ui_manager. */
+/* The shares selection changes argument should be set to true if the
+ * widget uses the signal "selection_changed" to tell others about
+ * text selection changes. The CajaEntry widget
+ * is currently the only editable in caja that shares selection
+ * changes. */
+void caja_clipboard_set_up_editable (GtkEditable *target,
+ GtkUIManager *ui_manager,
+ gboolean shares_selection_changes);
+void caja_clipboard_set_up_text_view (GtkTextView *target,
+ GtkUIManager *ui_manager);
+void caja_clipboard_clear_if_colliding_uris (GtkWidget *widget,
+ const GList *item_uris,
+ GdkAtom copied_files_atom);
+GtkClipboard* caja_clipboard_get (GtkWidget *widget);
+GList* caja_clipboard_get_uri_list_from_selection_data
+(GtkSelectionData *selection_data,
+ gboolean *cut,
+ GdkAtom copied_files_atom);
+
+#endif /* CAJA_CLIPBOARD_H */
diff --git a/libcaja-private/caja-column-chooser.c b/libcaja-private/caja-column-chooser.c
new file mode 100644
index 00000000..0bc52a30
--- /dev/null
+++ b/libcaja-private/caja-column-chooser.c
@@ -0,0 +1,670 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-column-chooser.h - A column chooser widget
+
+ Copyright (C) 2004 Novell, Inc.
+
+ The Mate 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 Mate 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 Mate Library; see the column COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Dave Camp <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-column-chooser.h"
+
+#include <string.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+
+#include "caja-column-utilities.h"
+
+struct _CajaColumnChooserDetails
+{
+ GtkTreeView *view;
+ GtkListStore *store;
+
+ GtkWidget *move_up_button;
+ GtkWidget *move_down_button;
+ GtkWidget *use_default_button;
+
+ CajaFile *file;
+};
+
+enum
+{
+ COLUMN_VISIBLE,
+ COLUMN_LABEL,
+ COLUMN_NAME,
+ NUM_COLUMNS
+};
+
+enum
+{
+ PROP_FILE = 1,
+ NUM_PROPERTIES
+};
+
+enum
+{
+ CHANGED,
+ USE_DEFAULT,
+ LAST_SIGNAL
+};
+static guint signals[LAST_SIGNAL];
+
+
+G_DEFINE_TYPE(CajaColumnChooser, caja_column_chooser, GTK_TYPE_HBOX);
+
+static void caja_column_chooser_constructed (GObject *object);
+
+static void
+caja_column_chooser_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CajaColumnChooser *chooser;
+
+ chooser = CAJA_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
+caja_column_chooser_class_init (CajaColumnChooserClass *chooser_class)
+{
+ GObjectClass *oclass;
+
+ oclass = G_OBJECT_CLASS (chooser_class);
+
+ oclass->set_property = caja_column_chooser_set_property;
+ oclass->constructed = caja_column_chooser_constructed;
+
+ signals[CHANGED] = g_signal_new
+ ("changed",
+ G_TYPE_FROM_CLASS (chooser_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaColumnChooserClass,
+ 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 (CajaColumnChooserClass,
+ 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",
+ CAJA_TYPE_FILE,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_WRITABLE));
+
+ g_type_class_add_private (chooser_class, sizeof (CajaColumnChooserDetails));
+}
+
+static void
+update_buttons (CajaColumnChooser *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 (CajaColumnChooser *chooser)
+{
+ update_buttons (chooser);
+ g_signal_emit (chooser, signals[CHANGED], 0);
+}
+
+static void
+visible_toggled_callback (GtkCellRendererToggle *cell,
+ char *path_string,
+ gpointer user_data)
+{
+ CajaColumnChooser *chooser;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ gboolean visible;
+
+ chooser = CAJA_COLUMN_CHOOSER (user_data);
+
+ path = gtk_tree_path_new_from_string (path_string);
+ 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);
+ gtk_tree_path_free (path);
+ list_changed (chooser);
+}
+
+static void
+selection_changed_callback (GtkTreeSelection *selection, gpointer user_data)
+{
+ update_buttons (CAJA_COLUMN_CHOOSER (user_data));
+}
+
+static void
+row_deleted_callback (GtkTreeModel *model,
+ GtkTreePath *path,
+ gpointer user_data)
+{
+ list_changed (CAJA_COLUMN_CHOOSER (user_data));
+}
+
+static void
+add_tree_view (CajaColumnChooser *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);
+
+ 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);
+
+ 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,
+ NULL);
+
+ cell = gtk_cell_renderer_text_new ();
+
+ gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view),
+ -1, NULL,
+ cell,
+ "text", COLUMN_LABEL,
+ 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), scrolled, TRUE, TRUE, 0);
+}
+
+static void
+move_up_clicked_callback (GtkWidget *button, gpointer user_data)
+{
+ CajaColumnChooser *chooser;
+ GtkTreeIter iter;
+ GtkTreeSelection *selection;
+
+ chooser = CAJA_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)
+{
+ CajaColumnChooser *chooser;
+ GtkTreeIter iter;
+ GtkTreeSelection *selection;
+
+ chooser = CAJA_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 (CAJA_COLUMN_CHOOSER (user_data),
+ signals[USE_DEFAULT], 0);
+}
+
+static GtkWidget *
+button_new_with_mnemonic (const gchar *stockid, const gchar *str)
+{
+ GtkWidget *image;
+ GtkWidget *button;
+
+ button = gtk_button_new_with_mnemonic (str);
+ image = gtk_image_new_from_stock (stockid, GTK_ICON_SIZE_BUTTON);
+
+ gtk_button_set_image (GTK_BUTTON (button), image);
+
+ return button;
+}
+
+static void
+add_buttons (CajaColumnChooser *chooser)
+{
+ GtkWidget *box;
+ GtkWidget *separator;
+
+ box = gtk_vbox_new (FALSE, 8);
+ gtk_widget_show (box);
+
+ chooser->details->move_up_button = button_new_with_mnemonic (GTK_STOCK_GO_UP,
+ _("Move _Up"));
+ g_signal_connect (chooser->details->move_up_button,
+ "clicked", G_CALLBACK (move_up_clicked_callback),
+ chooser);
+ gtk_widget_show_all (chooser->details->move_up_button);
+ gtk_widget_set_sensitive (chooser->details->move_up_button, FALSE);
+ gtk_box_pack_start (GTK_BOX (box), chooser->details->move_up_button,
+ FALSE, FALSE, 0);
+
+ chooser->details->move_down_button = button_new_with_mnemonic (GTK_STOCK_GO_DOWN,
+ _("Move Dow_n"));
+ g_signal_connect (chooser->details->move_down_button,
+ "clicked", G_CALLBACK (move_down_clicked_callback),
+ chooser);
+ gtk_widget_show_all (chooser->details->move_down_button);
+ gtk_widget_set_sensitive (chooser->details->move_down_button, FALSE);
+ gtk_box_pack_start (GTK_BOX (box), chooser->details->move_down_button,
+ FALSE, FALSE, 0);
+
+ separator = gtk_hseparator_new ();
+ gtk_widget_show (separator);
+ gtk_box_pack_start (GTK_BOX (box), separator, FALSE, FALSE, 0);
+
+ chooser->details->use_default_button = gtk_button_new_with_mnemonic (_("Use De_fault"));
+ g_signal_connect (chooser->details->use_default_button,
+ "clicked", G_CALLBACK (use_default_clicked_callback),
+ chooser);
+ gtk_widget_show (chooser->details->use_default_button);
+ gtk_box_pack_start (GTK_BOX (box), chooser->details->use_default_button,
+ FALSE, FALSE, 0);
+
+ gtk_box_pack_start (GTK_BOX (chooser), box,
+ FALSE, FALSE, 0);
+}
+
+static void
+populate_tree (CajaColumnChooser *chooser)
+{
+ GList *columns;
+ GList *l;
+
+ columns = caja_get_columns_for_file (chooser->details->file);
+
+ for (l = columns; l != NULL; l = l->next)
+ {
+ GtkTreeIter iter;
+ CajaColumn *column;
+ char *name;
+ char *label;
+
+ column = CAJA_COLUMN (l->data);
+
+ g_object_get (G_OBJECT (column),
+ "name", &name, "label", &label,
+ NULL);
+
+ gtk_list_store_append (chooser->details->store, &iter);
+ gtk_list_store_set (chooser->details->store, &iter,
+ COLUMN_VISIBLE, FALSE,
+ COLUMN_LABEL, label,
+ COLUMN_NAME, name,
+ -1);
+
+ g_free (name);
+ g_free (label);
+ }
+
+ caja_column_list_free (columns);
+}
+
+static void
+caja_column_chooser_constructed (GObject *object)
+{
+ CajaColumnChooser *chooser;
+
+ chooser = CAJA_COLUMN_CHOOSER (object);
+
+ populate_tree (chooser);
+
+ g_signal_connect (chooser->details->store, "row_deleted",
+ G_CALLBACK (row_deleted_callback), chooser);
+}
+
+static void
+caja_column_chooser_init (CajaColumnChooser *chooser)
+{
+ chooser->details = G_TYPE_INSTANCE_GET_PRIVATE ((chooser), CAJA_TYPE_COLUMN_CHOOSER, CajaColumnChooserDetails);
+
+ g_object_set (G_OBJECT (chooser),
+ "homogeneous", FALSE,
+ "spacing", 8,
+ NULL);
+
+ add_tree_view (chooser);
+ add_buttons (chooser);
+}
+
+static void
+set_visible_columns (CajaColumnChooser *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);
+ 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 (CajaColumnChooser *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);
+ }
+
+ }
+ 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 (CajaColumnChooser *chooser,
+ CajaColumn *column,
+ GtkTreeIter *iter)
+{
+ char *column_name;
+
+ g_object_get (CAJA_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 (CajaColumnChooser *chooser,
+ char **column_order)
+
+{
+ GList *columns;
+ GList *l;
+ GtkTreePath *path;
+
+ columns = caja_get_columns_for_file (chooser->details->file);
+ columns = caja_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, CAJA_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);
+
+ caja_column_list_free (columns);
+}
+
+void
+caja_column_chooser_set_settings (CajaColumnChooser *chooser,
+ char **visible_columns,
+ char **column_order)
+{
+ g_return_if_fail (CAJA_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
+caja_column_chooser_get_settings (CajaColumnChooser *chooser,
+ char ***visible_columns,
+ char ***column_order)
+{
+ g_return_if_fail (CAJA_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 *
+caja_column_chooser_new (CajaFile *file)
+{
+ return g_object_new (CAJA_TYPE_COLUMN_CHOOSER, "file", file, NULL);
+}
+
diff --git a/libcaja-private/caja-column-chooser.h b/libcaja-private/caja-column-chooser.h
new file mode 100644
index 00000000..9c99078c
--- /dev/null
+++ b/libcaja-private/caja-column-chooser.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-column-choose.h - A column chooser widget
+
+ Copyright (C) 2004 Novell, Inc.
+
+ The Mate 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 Mate 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 Mate Library; see the column COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Dave Camp <[email protected]>
+*/
+
+#ifndef CAJA_COLUMN_CHOOSER_H
+#define CAJA_COLUMN_CHOOSER_H
+
+#include <gtk/gtk.h>
+#include <libcaja-private/caja-file.h>
+
+#define CAJA_TYPE_COLUMN_CHOOSER caja_column_chooser_get_type()
+#define CAJA_COLUMN_CHOOSER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_COLUMN_CHOOSER, CajaColumnChooser))
+#define CAJA_COLUMN_CHOOSER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_COLUMN_CHOOSER, CajaColumnChooserClass))
+#define CAJA_IS_COLUMN_CHOOSER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_COLUMN_CHOOSER))
+#define CAJA_IS_COLUMN_CHOOSER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_COLUMN_CHOOSER))
+#define CAJA_COLUMN_CHOOSER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_COLUMN_CHOOSER, CajaColumnChooserClass))
+
+typedef struct _CajaColumnChooserDetails CajaColumnChooserDetails;
+
+typedef struct
+{
+ GtkHBox parent;
+
+ CajaColumnChooserDetails *details;
+} CajaColumnChooser;
+
+typedef struct
+{
+ GtkHBoxClass parent_slot;
+
+ void (*changed) (CajaColumnChooser *chooser);
+ void (*use_default) (CajaColumnChooser *chooser);
+} CajaColumnChooserClass;
+
+GType caja_column_chooser_get_type (void);
+GtkWidget *caja_column_chooser_new (CajaFile *file);
+void caja_column_chooser_set_settings (CajaColumnChooser *chooser,
+ char **visible_columns,
+ char **column_order);
+void caja_column_chooser_get_settings (CajaColumnChooser *chooser,
+ char ***visible_columns,
+ char ***column_order);
+
+#endif /* CAJA_COLUMN_CHOOSER_H */
diff --git a/libcaja-private/caja-column-utilities.c b/libcaja-private/caja-column-utilities.c
new file mode 100644
index 00000000..19794891
--- /dev/null
+++ b/libcaja-private/caja-column-utilities.c
@@ -0,0 +1,327 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-column-utilities.h - Utilities related to column specifications
+
+ Copyright (C) 2004 Novell, Inc.
+
+ The Mate 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 Mate 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 Mate Library; see the column COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Dave Camp <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-column-utilities.h"
+
+#include <string.h>
+#include <eel/eel-glib-extensions.h>
+#include <glib/gi18n.h>
+#include <libcaja-extension/caja-column-provider.h>
+#include <libcaja-private/caja-module.h>
+
+static GList *
+get_builtin_columns (void)
+{
+ GList *columns;
+
+ columns = g_list_append (NULL,
+ g_object_new (CAJA_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 (CAJA_TYPE_COLUMN,
+ "name", "size",
+ "attribute", "size",
+ "label", _("Size"),
+ "description", _("The size of the file."),
+ "xalign", 1.0,
+ NULL));
+ columns = g_list_append (columns,
+ g_object_new (CAJA_TYPE_COLUMN,
+ "name", "type",
+ "attribute", "type",
+ "label", _("Type"),
+ "description", _("The type of the file."),
+ NULL));
+ columns = g_list_append (columns,
+ g_object_new (CAJA_TYPE_COLUMN,
+ "name", "date_modified",
+ "attribute", "date_modified",
+ "label", _("Date Modified"),
+ "description", _("The date the file was modified."),
+ NULL));
+
+ columns = g_list_append (columns,
+ g_object_new (CAJA_TYPE_COLUMN,
+ "name", "date_accessed",
+ "attribute", "date_accessed",
+ "label", _("Date Accessed"),
+ "description", _("The date the file was accessed."),
+ NULL));
+
+ columns = g_list_append (columns,
+ g_object_new (CAJA_TYPE_COLUMN,
+ "name", "owner",
+ "attribute", "owner",
+ "label", _("Owner"),
+ "description", _("The owner of the file."),
+ NULL));
+
+ columns = g_list_append (columns,
+ g_object_new (CAJA_TYPE_COLUMN,
+ "name", "group",
+ "attribute", "group",
+ "label", _("Group"),
+ "description", _("The group of the file."),
+ NULL));
+
+ columns = g_list_append (columns,
+ g_object_new (CAJA_TYPE_COLUMN,
+ "name", "permissions",
+ "attribute", "permissions",
+ "label", _("Permissions"),
+ "description", _("The permissions of the file."),
+ NULL));
+
+ columns = g_list_append (columns,
+ g_object_new (CAJA_TYPE_COLUMN,
+ "name", "octal_permissions",
+ "attribute", "octal_permissions",
+ "label", _("Octal Permissions"),
+ "description", _("The permissions of the file, in octal notation."),
+ NULL));
+
+ columns = g_list_append (columns,
+ g_object_new (CAJA_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 (CAJA_TYPE_COLUMN,
+ "name", "selinux_context",
+ "attribute", "selinux_context",
+ "label", _("SELinux Context"),
+ "description", _("The SELinux security context of the file."),
+ NULL));
+ columns = g_list_append (columns,
+ g_object_new (CAJA_TYPE_COLUMN,
+ "name", "where",
+ "attribute", "where",
+ "label", _("Location"),
+ /* TODO: Change after string freeze over */
+ "description", _("Location"),
+ NULL));
+
+ return columns;
+}
+
+static GList *
+get_extension_columns (void)
+{
+ GList *columns;
+ GList *providers;
+ GList *l;
+
+ providers = caja_module_get_extensions_for_type (CAJA_TYPE_COLUMN_PROVIDER);
+
+ columns = NULL;
+
+ for (l = providers; l != NULL; l = l->next)
+ {
+ CajaColumnProvider *provider;
+ GList *provider_columns;
+
+ provider = CAJA_COLUMN_PROVIDER (l->data);
+ provider_columns = caja_column_provider_get_columns (provider);
+ columns = g_list_concat (columns, provider_columns);
+ }
+
+ caja_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 (CAJA_TYPE_COLUMN,
+ "name", "trashed_on",
+ "attribute", "trashed_on",
+ "label", _("Trashed On"),
+ "description", _("Date when file was moved to the Trash"),
+ NULL));
+ columns = g_list_append (columns,
+ g_object_new (CAJA_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 caja_column_list_copy (columns);
+}
+
+GList *
+caja_get_common_columns (void)
+{
+ static GList *columns = NULL;
+
+ if (!columns)
+ {
+ columns = g_list_concat (get_builtin_columns (),
+ get_extension_columns ());
+ }
+
+ return caja_column_list_copy (columns);
+}
+
+GList *
+caja_get_all_columns (void)
+{
+ GList *columns = NULL;
+
+ columns = g_list_concat (caja_get_common_columns (),
+ get_trash_columns ());
+
+ return columns;
+}
+
+GList *
+caja_get_columns_for_file (CajaFile *file)
+{
+ GList *columns;
+
+ columns = caja_get_common_columns ();
+
+ if (file != NULL && caja_file_is_in_trash (file))
+ {
+ columns = g_list_concat (columns,
+ get_trash_columns ());
+ }
+
+ return columns;
+}
+
+GList *
+caja_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
+caja_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 (CajaColumn *a, CajaColumn *b, char **column_order)
+{
+ int index_a;
+ int index_b;
+ char *name;
+
+ g_object_get (G_OBJECT (a), "name", &name, NULL);
+ index_a = strv_index (column_order, name);
+ g_free (name);
+
+ g_object_get (G_OBJECT (b), "name", &name, NULL);
+ index_b = strv_index (column_order, name);
+ g_free (name);
+
+ if (index_a == index_b)
+ {
+ int ret;
+ 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);
+
+ return ret;
+ }
+ else if (index_a == -1)
+ {
+ return 1;
+ }
+ else if (index_b == -1)
+ {
+ return -1;
+ }
+ else
+ {
+ return index_a - index_b;
+ }
+}
+
+GList *
+caja_sort_columns (GList *columns,
+ char **column_order)
+{
+ if (!column_order)
+ {
+ return NULL;
+ }
+
+ return g_list_sort_with_data (columns,
+ (GCompareDataFunc)column_compare,
+ column_order);
+}
+
diff --git a/libcaja-private/caja-column-utilities.h b/libcaja-private/caja-column-utilities.h
new file mode 100644
index 00000000..2a6a99bd
--- /dev/null
+++ b/libcaja-private/caja-column-utilities.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-column-utilities.h - Utilities related to column specifications
+
+ Copyright (C) 2004 Novell, Inc.
+
+ The Mate 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 Mate 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 Mate Library; see the column COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Dave Camp <[email protected]>
+*/
+
+#ifndef CAJA_COLUMN_UTILITIES_H
+#define CAJA_COLUMN_UTILITIES_H
+
+#include <libcaja-extension/caja-column.h>
+#include <libcaja-private/caja-file.h>
+
+GList *caja_get_all_columns (void);
+GList *caja_get_common_columns (void);
+GList *caja_get_columns_for_file (CajaFile *file);
+GList *caja_column_list_copy (GList *columns);
+void caja_column_list_free (GList *columns);
+
+GList *caja_sort_columns (GList *columns,
+ char **column_order);
+
+
+#endif /* CAJA_COLUMN_UTILITIES_H */
diff --git a/libcaja-private/caja-customization-data.c b/libcaja-private/caja-customization-data.c
new file mode 100644
index 00000000..e96cd7c6
--- /dev/null
+++ b/libcaja-private/caja-customization-data.c
@@ -0,0 +1,498 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Author: Rebecca Schulman <[email protected]>
+ */
+
+/* caja-customization-data.c - functions to collect and load customization
+ names and imges */
+
+#include <config.h>
+#include "caja-customization-data.h"
+
+#include "caja-file-utilities.h"
+#include <eel/eel-gdk-extensions.h>
+#include <eel/eel-gdk-extensions.h>
+#include <eel/eel-pango-extensions.h>
+#include <eel/eel-gdk-pixbuf-extensions.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 <eel/eel-xml-extensions.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <libxml/parser.h>
+#include <stdlib.h>
+#include <string.h>
+
+typedef enum
+{
+ READ_PUBLIC_CUSTOMIZATIONS,
+ READ_PRIVATE_CUSTOMIZATIONS
+} CustomizationReadingMode;
+
+struct CajaCustomizationData
+{
+ char *customization_name;
+
+ GList *public_file_list;
+ GList *private_file_list;
+ GList *current_file_list;
+
+ GHashTable *name_map_hash;
+
+ GdkPixbuf *pattern_frame;
+
+ int maximum_icon_height;
+ int maximum_icon_width;
+
+ guint private_data_was_displayed : 1;
+ guint reading_mode : 2; /* enough bits for CustomizationReadingMode */
+};
+
+
+/* The Property here should be one of "emblems", "colors" or "patterns" */
+static char * get_global_customization_path (const char *customization_name);
+static char * get_private_customization_path (const char *customization_name);
+static char * get_file_path_for_mode (const CajaCustomizationData *data,
+ const char *file_name);
+static char* format_name_for_display (CajaCustomizationData *data, const char *name);
+static void load_name_map_hash_table (CajaCustomizationData *data);
+
+
+static gboolean
+read_all_children (char *filename,
+ const char *attributes,
+ GList **list_out)
+{
+ GFileEnumerator *enumerator;
+ GList *list;
+ GFile *file;
+ GFileInfo *info;
+
+ file = g_file_new_for_path (filename);
+
+ enumerator = g_file_enumerate_children (file, attributes, 0, NULL, NULL);
+ if (enumerator == NULL)
+ {
+ return FALSE;
+ }
+
+ list = NULL;
+ do
+ {
+ info = g_file_enumerator_next_file (enumerator, NULL, NULL);
+ if (info)
+ {
+ list = g_list_prepend (list, info);
+ }
+ }
+ while (info != NULL);
+
+ g_object_unref (enumerator);
+ g_object_unref (file);
+
+ *list_out = g_list_reverse (list);
+ return TRUE;
+}
+
+
+CajaCustomizationData*
+caja_customization_data_new (const char *customization_name,
+ gboolean show_public_customizations,
+ int maximum_icon_height,
+ int maximum_icon_width)
+{
+ CajaCustomizationData *data;
+ char *public_directory_path, *private_directory_path;
+ char *temp_str;
+ gboolean public_result, private_result;
+
+ data = g_new0 (CajaCustomizationData, 1);
+
+ public_result = TRUE;
+
+ if (show_public_customizations)
+ {
+ public_directory_path = get_global_customization_path (customization_name);
+
+ public_result = read_all_children (public_directory_path,
+ G_FILE_ATTRIBUTE_STANDARD_NAME ","
+ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
+ &data->public_file_list);
+ g_free (public_directory_path);
+ }
+
+ private_directory_path = get_private_customization_path (customization_name);
+ private_result = read_all_children (private_directory_path,
+ G_FILE_ATTRIBUTE_STANDARD_NAME ","
+ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
+ &data->private_file_list);
+ g_free (private_directory_path);
+ if (!public_result && !private_result)
+ {
+ g_warning ("Couldn't read any of the emblem directories\n");
+ g_free (data);
+ return NULL;
+ }
+ if (private_result)
+ {
+ data->reading_mode = READ_PRIVATE_CUSTOMIZATIONS;
+ data->current_file_list = data->private_file_list;
+ }
+ if (show_public_customizations && public_result)
+ {
+ data->reading_mode = READ_PUBLIC_CUSTOMIZATIONS;
+ data->current_file_list = data->public_file_list;
+ }
+
+ data->pattern_frame = NULL;
+
+ /* load the frame if necessary */
+ if (strcmp (customization_name, "patterns") == 0)
+ {
+ temp_str = caja_pixmap_file ("chit_frame.png");
+ if (temp_str != NULL)
+ {
+ data->pattern_frame = gdk_pixbuf_new_from_file (temp_str, NULL);
+ }
+ g_free (temp_str);
+ }
+
+ data->private_data_was_displayed = FALSE;
+ data->customization_name = g_strdup (customization_name);
+
+ data->maximum_icon_height = maximum_icon_height;
+ data->maximum_icon_width = maximum_icon_width;
+
+ load_name_map_hash_table (data);
+
+ return data;
+}
+
+gboolean
+caja_customization_data_get_next_element_for_display (CajaCustomizationData *data,
+ char **emblem_name,
+ GdkPixbuf **pixbuf_out,
+ char **label_out)
+{
+ GFileInfo *current_file_info;
+ char *image_file_name;
+ GdkPixbuf *pixbuf;
+ GdkPixbuf *orig_pixbuf;
+ gboolean is_reset_image;
+
+ g_return_val_if_fail (data != NULL, FALSE);
+ g_return_val_if_fail (emblem_name != NULL, FALSE);
+ g_return_val_if_fail (pixbuf_out != NULL, FALSE);
+ g_return_val_if_fail (label_out != NULL, FALSE);
+
+ if (data->current_file_list == NULL)
+ {
+ if (data->reading_mode == READ_PUBLIC_CUSTOMIZATIONS)
+ {
+ if (data->private_file_list == NULL)
+ {
+ return FALSE;
+ }
+ data->reading_mode = READ_PRIVATE_CUSTOMIZATIONS;
+ data->current_file_list = data->private_file_list;
+ return caja_customization_data_get_next_element_for_display (data,
+ emblem_name,
+ pixbuf_out,
+ label_out);
+ }
+ else
+ {
+ return FALSE;
+ }
+ }
+
+
+ current_file_info = data->current_file_list->data;
+ data->current_file_list = data->current_file_list->next;
+
+ g_assert (current_file_info != NULL);
+
+ if (!eel_istr_has_prefix (g_file_info_get_content_type (current_file_info), "image/")
+ || eel_istr_has_prefix (g_file_info_get_name (current_file_info), "."))
+ {
+ return caja_customization_data_get_next_element_for_display (data,
+ emblem_name,
+ pixbuf_out,
+ label_out);
+ }
+
+ image_file_name = get_file_path_for_mode (data,
+ g_file_info_get_name (current_file_info));
+ orig_pixbuf = gdk_pixbuf_new_from_file_at_scale (image_file_name,
+ data->maximum_icon_width,
+ data->maximum_icon_height,
+ TRUE,
+ NULL);
+ g_free (image_file_name);
+
+ if (orig_pixbuf == NULL)
+ {
+ return caja_customization_data_get_next_element_for_display (data,
+ emblem_name,
+ pixbuf_out,
+ label_out);
+ }
+
+ is_reset_image = eel_strcmp(g_file_info_get_name (current_file_info), RESET_IMAGE_NAME) == 0;
+
+ *emblem_name = g_strdup (g_file_info_get_name (current_file_info));
+
+ if (strcmp (data->customization_name, "patterns") == 0 &&
+ data->pattern_frame != NULL)
+ {
+ pixbuf = caja_customization_make_pattern_chit (orig_pixbuf, data->pattern_frame, FALSE, is_reset_image);
+ }
+ else
+ {
+ pixbuf = eel_gdk_pixbuf_scale_down_to_fit (orig_pixbuf,
+ data->maximum_icon_width,
+ data->maximum_icon_height);
+ }
+
+ g_object_unref (orig_pixbuf);
+
+ *pixbuf_out = pixbuf;
+
+ *label_out = format_name_for_display (data, g_file_info_get_name (current_file_info));
+
+ if (data->reading_mode == READ_PRIVATE_CUSTOMIZATIONS)
+ {
+ data->private_data_was_displayed = TRUE;
+ }
+ return TRUE;
+}
+
+gboolean
+caja_customization_data_private_data_was_displayed (CajaCustomizationData *data)
+{
+ return data->private_data_was_displayed;
+}
+
+void
+caja_customization_data_destroy (CajaCustomizationData *data)
+{
+ g_assert (data->public_file_list != NULL ||
+ data->private_file_list != NULL);
+
+ if (data->pattern_frame != NULL)
+ {
+ g_object_unref (data->pattern_frame);
+ }
+
+ eel_g_object_list_free (data->public_file_list);
+ eel_g_object_list_free (data->private_file_list);
+
+ if (data->name_map_hash != NULL)
+ {
+ g_hash_table_destroy (data->name_map_hash);
+ }
+
+ g_free (data->customization_name);
+ g_free (data);
+}
+
+
+/* get_global_customization_directory
+ Get the path where a property's pixmaps are stored
+ @customization_name : the name of the customization to get.
+ Should be one of "emblems", "colors", or "paterns"
+
+ Return value: The directory name where the customization's
+ public pixmaps are stored */
+static char *
+get_global_customization_path (const char *customization_name)
+{
+ return g_build_filename (CAJA_DATADIR,
+ customization_name,
+ NULL);
+}
+
+
+/* get_private_customization_directory
+ Get the path where a customization's pixmaps are stored
+ @customization_name : the name of the customization to get.
+ Should be one of "emblems", "colors", or "patterns"
+
+ Return value: The directory name where the customization's
+ user-specific pixmaps are stored */
+static char *
+get_private_customization_path (const char *customization_name)
+{
+ char *user_directory;
+ char *directory_path;
+
+ user_directory = caja_get_user_directory ();
+ directory_path = g_build_filename (user_directory,
+ customization_name,
+ NULL);
+ g_free (user_directory);
+
+ return directory_path;
+}
+
+
+static char *
+get_file_path_for_mode (const CajaCustomizationData *data,
+ const char *file_name)
+{
+ char *directory_path, *file;
+ if (data->reading_mode == READ_PUBLIC_CUSTOMIZATIONS)
+ {
+ directory_path = get_global_customization_path (data->customization_name);
+ }
+ else
+ {
+ directory_path = get_private_customization_path (data->customization_name);
+ }
+
+ file = g_build_filename (directory_path, file_name, NULL);
+ g_free (directory_path);
+
+ return file;
+}
+
+
+/* utility to make an attractive pattern image by compositing with a frame */
+GdkPixbuf*
+caja_customization_make_pattern_chit (GdkPixbuf *pattern_tile, GdkPixbuf *frame, gboolean dragging, gboolean is_reset)
+{
+ GdkPixbuf *pixbuf, *temp_pixbuf;
+ int frame_width, frame_height;
+ int pattern_width, pattern_height;
+
+ g_assert (pattern_tile != NULL);
+ g_assert (frame != NULL);
+
+ frame_width = gdk_pixbuf_get_width (frame);
+ frame_height = gdk_pixbuf_get_height (frame);
+ pattern_width = gdk_pixbuf_get_width (pattern_tile);
+ pattern_height = gdk_pixbuf_get_height (pattern_tile);
+
+ pixbuf = gdk_pixbuf_copy (frame);
+
+ /* scale the pattern tile to the proper size */
+ gdk_pixbuf_scale (pattern_tile,
+ pixbuf,
+ 2, 2, frame_width - 8, frame_height - 8,
+ 0, 0,
+ (double)(frame_width - 8 + 1)/pattern_width,
+ (double)(frame_height - 8 + 1)/pattern_height,
+ GDK_INTERP_BILINEAR);
+
+ /* if we're dragging, get rid of the light-colored halo */
+ if (dragging)
+ {
+ temp_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, frame_width - 8, frame_height - 8);
+ gdk_pixbuf_copy_area (pixbuf, 2, 2, frame_width - 8, frame_height - 8, temp_pixbuf, 0, 0);
+ g_object_unref (pixbuf);
+ pixbuf = temp_pixbuf;
+ }
+
+ return pixbuf;
+}
+
+
+/* utility to format the passed-in name for display by stripping the extension, mapping underscore
+ and capitalizing as necessary */
+
+static char*
+format_name_for_display (CajaCustomizationData *data, const char* name)
+{
+ char *formatted_str, *mapped_name;
+
+ if (!eel_strcmp(name, RESET_IMAGE_NAME))
+ {
+ return g_strdup (_("Reset"));
+ }
+
+ /* map file names to display names using the mappings defined in the hash table */
+
+ formatted_str = eel_filename_strip_extension (name);
+ if (data->name_map_hash != NULL)
+ {
+ mapped_name = g_hash_table_lookup (data->name_map_hash, formatted_str);
+ if (mapped_name)
+ {
+ g_free (formatted_str);
+ formatted_str = g_strdup (mapped_name);
+ }
+ }
+
+ return formatted_str;
+}
+
+/* utility routine to allocate a hash table and load it with the appropriate
+ * name mapping data from the browser xml file
+ */
+static void
+load_name_map_hash_table (CajaCustomizationData *data)
+{
+ char *xml_path;
+ char *filename, *display_name;
+
+ xmlDocPtr browser_data;
+ xmlNodePtr category_node, current_node;
+
+ /* allocate the hash table */
+ data->name_map_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+ /* build the path name to the browser.xml file and load it */
+ xml_path = g_build_filename (CAJA_DATADIR, "browser.xml", NULL);
+ if (xml_path)
+ {
+ browser_data = xmlParseFile (xml_path);
+ g_free (xml_path);
+
+ if (browser_data)
+ {
+ /* get the category node */
+ category_node = eel_xml_get_root_child_by_name_and_property (browser_data, "category", "name", data->customization_name);
+ current_node = category_node->children;
+
+ /* loop through the entries, adding a mapping to the hash table */
+ while (current_node != NULL)
+ {
+ display_name = eel_xml_get_property_translated (current_node, "display_name");
+ filename = xmlGetProp (current_node, "filename");
+ if (display_name && filename)
+ {
+ g_hash_table_replace (data->name_map_hash, g_strdup (filename), g_strdup (display_name));
+ }
+ xmlFree (filename);
+ xmlFree (display_name);
+ current_node = current_node->next;
+ }
+
+ /* close the xml file */
+ xmlFreeDoc (browser_data);
+ }
+ }
+}
diff --git a/libcaja-private/caja-customization-data.h b/libcaja-private/caja-customization-data.h
new file mode 100644
index 00000000..92d74af2
--- /dev/null
+++ b/libcaja-private/caja-customization-data.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Author: Rebecca Schulman <[email protected]>
+ */
+
+/* caja-customization-data.h - functions to collect and load property
+ names and imges */
+
+
+
+#ifndef CAJA_CUSTOMIZATION_DATA_H
+#define CAJA_CUSTOMIZATION_DATA_H
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gtk/gtk.h>
+
+#define RESET_IMAGE_NAME "reset.png"
+
+typedef struct CajaCustomizationData CajaCustomizationData;
+
+
+
+CajaCustomizationData* caja_customization_data_new (const char *customization_name,
+ gboolean show_public_customizations,
+ int maximum_icon_height,
+ int maximum_icon_width);
+
+/* Returns the following attrbiutes for a customization object (pattern, emblem)
+ *
+ * object_name - The name of the object. Usually what is used to represent it in storage.
+ * object_pixbuf - Pixbuf for graphical display of the object.
+ * object_label - Textual label display of the object.
+ */
+gboolean caja_customization_data_get_next_element_for_display (CajaCustomizationData *data,
+ char **object_name,
+ GdkPixbuf **object_pixbuf,
+ char **object_label);
+gboolean caja_customization_data_private_data_was_displayed (CajaCustomizationData *data);
+
+void caja_customization_data_destroy (CajaCustomizationData *data);
+
+
+
+GdkPixbuf* caja_customization_make_pattern_chit (GdkPixbuf *pattern_tile,
+ GdkPixbuf *frame,
+ gboolean dragging,
+ gboolean is_reset);
+
+#endif /* CAJA_CUSTOMIZATION_DATA_H */
diff --git a/libcaja-private/caja-debug-log.c b/libcaja-private/caja-debug-log.c
new file mode 100644
index 00000000..66192ced
--- /dev/null
+++ b/libcaja-private/caja-debug-log.c
@@ -0,0 +1,760 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-debug-log.c: Ring buffer for logging debug messages
+
+ Copyright (C) 2006 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Federico Mena-Quintero <[email protected]>
+*/
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <sys/time.h>
+#include <eel/eel-glib-extensions.h>
+#include "caja-debug-log.h"
+#include "caja-file.h"
+
+#define DEFAULT_RING_BUFFER_NUM_LINES 1000
+
+#define KEY_FILE_GROUP "debug log"
+#define KEY_FILE_DOMAINS_KEY "enable domains"
+#define KEY_FILE_MAX_LINES_KEY "max lines"
+
+#define MAX_URI_COUNT 20
+
+static GStaticMutex log_mutex = G_STATIC_MUTEX_INIT;
+
+static GHashTable *domains_hash;
+static char **ring_buffer;
+static int ring_buffer_next_index;
+static int ring_buffer_num_lines;
+static int ring_buffer_max_lines = DEFAULT_RING_BUFFER_NUM_LINES;
+
+static GSList *milestones_head;
+static GSList *milestones_tail;
+
+static void
+lock (void)
+{
+ g_static_mutex_lock (&log_mutex);
+}
+
+static void
+unlock (void)
+{
+ g_static_mutex_unlock (&log_mutex);
+}
+
+void
+caja_debug_log (gboolean is_milestone, const char *domain, const char *format, ...)
+{
+ va_list args;
+
+ va_start (args, format);
+ caja_debug_logv (is_milestone, domain, NULL, format, args);
+ va_end (args);
+}
+
+static gboolean
+is_domain_enabled (const char *domain)
+{
+ /* User actions are always logged */
+ if (strcmp (domain, CAJA_DEBUG_LOG_DOMAIN_USER) == 0)
+ return TRUE;
+
+ if (!domains_hash)
+ return FALSE;
+
+ return (g_hash_table_lookup (domains_hash, domain) != NULL);
+}
+
+static void
+ensure_ring (void)
+{
+ if (ring_buffer)
+ return;
+
+ ring_buffer = g_new0 (char *, ring_buffer_max_lines);
+ ring_buffer_next_index = 0;
+ ring_buffer_num_lines = 0;
+}
+
+static void
+add_to_ring (char *str)
+{
+ ensure_ring ();
+
+ g_assert (str != NULL);
+
+ if (ring_buffer_num_lines == ring_buffer_max_lines)
+ {
+ /* We have an overlap, and the ring_buffer_next_index points to
+ * the "first" item. Free it to make room for the new item.
+ */
+
+ g_assert (ring_buffer[ring_buffer_next_index] != NULL);
+ g_free (ring_buffer[ring_buffer_next_index]);
+ }
+ else
+ ring_buffer_num_lines++;
+
+ g_assert (ring_buffer_num_lines <= ring_buffer_max_lines);
+
+ ring_buffer[ring_buffer_next_index] = str;
+
+ ring_buffer_next_index++;
+ if (ring_buffer_next_index == ring_buffer_max_lines)
+ {
+ ring_buffer_next_index = 0;
+ g_assert (ring_buffer_num_lines == ring_buffer_max_lines);
+ }
+}
+
+static void
+add_to_milestones (const char *str)
+{
+ char *str_copy;
+
+ str_copy = g_strdup (str);
+
+ if (milestones_tail)
+ {
+ milestones_tail = g_slist_append (milestones_tail, str_copy);
+ milestones_tail = milestones_tail->next;
+ }
+ else
+ {
+ milestones_head = milestones_tail = g_slist_append (NULL, str_copy);
+ }
+
+ g_assert (milestones_head != NULL && milestones_tail != NULL);
+}
+
+void
+caja_debug_logv (gboolean is_milestone, const char *domain, const GList *uris, const char *format, va_list args)
+{
+ char *str;
+ char *debug_str;
+ struct timeval tv;
+ struct tm tm;
+
+ lock ();
+
+ if (!(is_milestone || is_domain_enabled (domain)))
+ goto out;
+
+ str = g_strdup_vprintf (format, args);
+ gettimeofday (&tv, NULL);
+
+ tm = *localtime (&tv.tv_sec);
+
+ debug_str = g_strdup_printf ("%p %04d/%02d/%02d %02d:%02d:%02d.%04d (%s): %s",
+ g_thread_self (),
+ tm.tm_year + 1900,
+ tm.tm_mon + 1,
+ tm.tm_mday,
+ tm.tm_hour,
+ tm.tm_min,
+ tm.tm_sec,
+ (int) (tv.tv_usec / 100),
+ domain,
+ str);
+ g_free (str);
+
+ if (uris)
+ {
+ int debug_str_len;
+ int uris_len;
+ const GList *l;
+ char *new_str;
+ char *p;
+ int count;
+
+ uris_len = 0;
+
+ count = 0;
+ for (l = uris; l; l = l->next)
+ {
+ const char *uri;
+
+ uri = l->data;
+ uris_len += strlen (uri) + 2; /* plus 2 for a tab and the newline */
+
+ if (count++ > MAX_URI_COUNT)
+ {
+ uris_len += 4; /* "...\n" */
+ break;
+ }
+
+ }
+
+ debug_str_len = strlen (debug_str);
+ new_str = g_new (char, debug_str_len + 1 + uris_len + 1); /* plus 1 for newline & zero */
+
+ p = g_stpcpy (new_str, debug_str);
+ *p++ = '\n';
+
+ count = 0;
+ for (l = uris; l; l = l->next)
+ {
+ const char *uri;
+
+ uri = l->data;
+
+ *p++ = '\t';
+
+ p = g_stpcpy (p, uri);
+
+ if (l->next)
+ *p++ = '\n';
+
+ if (count++ > MAX_URI_COUNT)
+ {
+ p = g_stpcpy (p, "...\n");
+ break;
+ }
+ }
+
+ g_free (debug_str);
+ debug_str = new_str;
+ }
+
+ add_to_ring (debug_str);
+ if (is_milestone)
+ add_to_milestones (debug_str);
+
+out:
+ unlock ();
+}
+
+void
+caja_debug_log_with_uri_list (gboolean is_milestone, const char *domain, const GList *uris,
+ const char *format, ...)
+{
+ va_list args;
+
+ va_start (args, format);
+ caja_debug_logv (is_milestone, domain, uris, format, args);
+ va_end (args);
+}
+
+void
+caja_debug_log_with_file_list (gboolean is_milestone, const char *domain, GList *files,
+ const char *format, ...)
+{
+ va_list args;
+ GList *uris;
+ GList *l;
+ int count;
+
+ /* Avoid conversion if debugging disabled */
+ if (!(is_milestone ||
+ caja_debug_log_is_domain_enabled (domain)))
+ {
+ return;
+ }
+
+ uris = NULL;
+
+ count = 0;
+ for (l = files; l; l = l->next)
+ {
+ CajaFile *file;
+ char *uri;
+
+ file = CAJA_FILE (l->data);
+ uri = caja_file_get_uri (file);
+
+ if (caja_file_is_gone (file))
+ {
+ char *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;
+ }
+ uris = g_list_prepend (uris, uri);
+
+ /* Avoid doing to much work, more than MAX_URI_COUNT uris
+ won't be shown anyway */
+ if (count++ > MAX_URI_COUNT + 1)
+ {
+ break;
+ }
+ }
+
+ uris = g_list_reverse (uris);
+
+ va_start (args, format);
+ caja_debug_logv (is_milestone, domain, uris, format, args);
+ va_end (args);
+
+ eel_g_list_free_deep (uris);
+}
+
+gboolean
+caja_debug_log_load_configuration (const char *filename, GError **error)
+{
+ GKeyFile *key_file;
+ char **strings;
+ gsize num_strings;
+ int num;
+ GError *my_error;
+
+ g_assert (filename != NULL);
+ g_assert (error == NULL || *error == NULL);
+
+ key_file = g_key_file_new ();
+
+ if (!g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, error))
+ {
+ g_key_file_free (key_file);
+ return FALSE;
+ }
+
+ /* Domains */
+
+ my_error = NULL;
+ strings = g_key_file_get_string_list (key_file, KEY_FILE_GROUP, KEY_FILE_DOMAINS_KEY, &num_strings, &my_error);
+ if (my_error)
+ g_error_free (my_error);
+ else
+ {
+ int i;
+
+ for (i = 0; i < num_strings; i++)
+ strings[i] = g_strstrip (strings[i]);
+
+ caja_debug_log_enable_domains ((const char **) strings, num_strings);
+ g_strfreev (strings);
+ }
+
+ /* Number of lines */
+
+ my_error = NULL;
+ num = g_key_file_get_integer (key_file, KEY_FILE_GROUP, KEY_FILE_MAX_LINES_KEY, &my_error);
+ if (my_error)
+ g_error_free (my_error);
+ else
+ caja_debug_log_set_max_lines (num);
+
+ g_key_file_free (key_file);
+ return TRUE;
+}
+
+void
+caja_debug_log_enable_domains (const char **domains, int n_domains)
+{
+ int i;
+
+ g_assert (domains != NULL);
+ g_assert (n_domains >= 0);
+
+ lock ();
+
+ if (!domains_hash)
+ domains_hash = g_hash_table_new (g_str_hash, g_str_equal);
+
+ for (i = 0; i < n_domains; i++)
+ {
+ g_assert (domains[i] != NULL);
+
+ if (strcmp (domains[i], CAJA_DEBUG_LOG_DOMAIN_USER) == 0)
+ continue; /* user actions are always enabled */
+
+ if (g_hash_table_lookup (domains_hash, domains[i]) == NULL)
+ {
+ char *domain;
+
+ domain = g_strdup (domains[i]);
+ g_hash_table_insert (domains_hash, domain, domain);
+ }
+ }
+
+ unlock ();
+}
+
+void
+caja_debug_log_disable_domains (const char **domains, int n_domains)
+{
+ int i;
+
+ g_assert (domains != NULL);
+ g_assert (n_domains >= 0);
+
+ lock ();
+
+ if (domains_hash)
+ {
+ for (i = 0; i < n_domains; i++)
+ {
+ char *domain;
+
+ g_assert (domains[i] != NULL);
+
+ if (strcmp (domains[i], CAJA_DEBUG_LOG_DOMAIN_USER) == 0)
+ continue; /* user actions are always enabled */
+
+ domain = g_hash_table_lookup (domains_hash, domains[i]);
+ if (domain)
+ {
+ g_hash_table_remove (domains_hash, domain);
+ g_free (domain);
+ }
+ }
+ } /* else, there is nothing to disable */
+
+ unlock ();
+}
+
+gboolean
+caja_debug_log_is_domain_enabled (const char *domain)
+{
+ gboolean retval;
+
+ g_assert (domain != NULL);
+
+ lock ();
+ retval = is_domain_enabled (domain);
+ unlock ();
+
+ return retval;
+}
+
+struct domains_dump_closure
+{
+ char **domains;
+ int num_domains;
+};
+
+static void
+domains_foreach_dump_cb (gpointer key, gpointer value, gpointer data)
+{
+ struct domains_dump_closure *closure;
+ char *domain;
+
+ closure = data;
+ domain = key;
+
+ closure->domains[closure->num_domains] = domain;
+ closure->num_domains++;
+}
+
+static GKeyFile *
+make_key_file_from_configuration (void)
+{
+ GKeyFile *key_file;
+ struct domains_dump_closure closure;
+ int num_domains;
+
+ key_file = g_key_file_new ();
+
+ /* domains */
+
+ if (domains_hash)
+ {
+ num_domains = g_hash_table_size (domains_hash);
+ if (num_domains != 0)
+ {
+ closure.domains = g_new (char *, num_domains);
+ closure.num_domains = 0;
+
+ g_hash_table_foreach (domains_hash, domains_foreach_dump_cb, &closure);
+ g_assert (num_domains == closure.num_domains);
+
+ g_key_file_set_string_list (key_file, KEY_FILE_GROUP, KEY_FILE_DOMAINS_KEY,
+ (const gchar * const *) closure.domains, closure.num_domains);
+ g_free (closure.domains);
+ }
+ }
+
+ /* max lines */
+
+ g_key_file_set_integer (key_file, KEY_FILE_GROUP, KEY_FILE_MAX_LINES_KEY, ring_buffer_max_lines);
+
+ return key_file;
+}
+
+static gboolean
+write_string (const char *filename, FILE *file, const char *str, GError **error)
+{
+ if (fputs (str, file) == EOF)
+ {
+ int saved_errno;
+
+ saved_errno = errno;
+ g_set_error (error,
+ G_FILE_ERROR,
+ g_file_error_from_errno (saved_errno),
+ "error when writing to log file %s", filename);
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+dump_configuration (const char *filename, FILE *file, GError **error)
+{
+ GKeyFile *key_file;
+ char *data;
+ gsize length;
+ gboolean success;
+
+ if (!write_string (filename, file,
+ "\n\n"
+ "This configuration for the debug log can be re-created\n"
+ "by putting the following in ~/caja-debug-log.conf\n"
+ "(use ';' to separate domain names):\n\n",
+ error))
+ {
+ return FALSE;
+ }
+
+ success = FALSE;
+
+ key_file = make_key_file_from_configuration ();
+
+ data = g_key_file_to_data (key_file, &length, error);
+ if (!data)
+ goto out;
+
+ if (!write_string (filename, file, data, error))
+ {
+ goto out;
+ }
+
+ success = TRUE;
+out:
+ g_key_file_free (key_file);
+ return success;
+}
+
+static gboolean
+dump_milestones (const char *filename, FILE *file, GError **error)
+{
+ GSList *l;
+
+ if (!write_string (filename, file, "===== BEGIN MILESTONES =====\n", error))
+ return FALSE;
+
+ for (l = milestones_head; l; l = l->next)
+ {
+ const char *str;
+
+ str = l->data;
+ if (!(write_string (filename, file, str, error)
+ && write_string (filename, file, "\n", error)))
+ return FALSE;
+ }
+
+ if (!write_string (filename, file, "===== END MILESTONES =====\n", error))
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean
+dump_ring_buffer (const char *filename, FILE *file, GError **error)
+{
+ int start_index;
+ int i;
+
+ if (!write_string (filename, file, "===== BEGIN RING BUFFER =====\n", error))
+ return FALSE;
+
+ if (ring_buffer_num_lines == ring_buffer_max_lines)
+ start_index = ring_buffer_next_index;
+ else
+ start_index = 0;
+
+ for (i = 0; i < ring_buffer_num_lines; i++)
+ {
+ int idx;
+
+ idx = (start_index + i) % ring_buffer_max_lines;
+
+ if (!(write_string (filename, file, ring_buffer[idx], error)
+ && write_string (filename, file, "\n", error)))
+ {
+ return FALSE;
+ }
+ }
+
+ if (!write_string (filename, file, "===== END RING BUFFER =====\n", error))
+ return FALSE;
+
+ return TRUE;
+}
+
+gboolean
+caja_debug_log_dump (const char *filename, GError **error)
+{
+ FILE *file;
+ gboolean success;
+
+ g_assert (error == NULL || *error == NULL);
+
+ lock ();
+
+ success = FALSE;
+
+ file = fopen (filename, "w");
+ if (!file)
+ {
+ int saved_errno;
+
+ saved_errno = errno;
+ g_set_error (error,
+ G_FILE_ERROR,
+ g_file_error_from_errno (saved_errno),
+ "could not open log file %s", filename);
+ goto out;
+ }
+
+ if (!(dump_milestones (filename, file, error)
+ && dump_ring_buffer (filename, file, error)
+ && dump_configuration (filename, file, error)))
+ {
+ goto do_close;
+ }
+
+ success = TRUE;
+
+do_close:
+
+ if (fclose (file) != 0)
+ {
+ int saved_errno;
+
+ saved_errno = errno;
+
+ if (error && *error)
+ {
+ g_error_free (*error);
+ *error = NULL;
+ }
+
+ g_set_error (error,
+ G_FILE_ERROR,
+ g_file_error_from_errno (saved_errno),
+ "error when closing log file %s", filename);
+ success = FALSE;
+ }
+
+out:
+
+ unlock ();
+ return success;
+}
+
+void
+caja_debug_log_set_max_lines (int num_lines)
+{
+ char **new_buffer;
+ int lines_to_copy;
+
+ g_assert (num_lines > 0);
+
+ lock ();
+
+ if (num_lines == ring_buffer_max_lines)
+ goto out;
+
+ new_buffer = g_new0 (char *, num_lines);
+
+ lines_to_copy = MIN (num_lines, ring_buffer_num_lines);
+
+ if (ring_buffer)
+ {
+ int start_index;
+ int i;
+
+ if (ring_buffer_num_lines == ring_buffer_max_lines)
+ start_index = (ring_buffer_next_index + ring_buffer_max_lines - lines_to_copy) % ring_buffer_max_lines;
+ else
+ start_index = ring_buffer_num_lines - lines_to_copy;
+
+ g_assert (start_index >= 0 && start_index < ring_buffer_max_lines);
+
+ for (i = 0; i < lines_to_copy; i++)
+ {
+ int idx;
+
+ idx = (start_index + i) % ring_buffer_max_lines;
+
+ new_buffer[i] = ring_buffer[idx];
+ ring_buffer[idx] = NULL;
+ }
+
+ for (i = 0; i < ring_buffer_max_lines; i++)
+ g_free (ring_buffer[i]);
+
+ g_free (ring_buffer);
+ }
+
+ ring_buffer = new_buffer;
+ ring_buffer_next_index = lines_to_copy;
+ ring_buffer_num_lines = lines_to_copy;
+ ring_buffer_max_lines = num_lines;
+
+out:
+
+ unlock ();
+}
+
+int
+caja_debug_log_get_max_lines (void)
+{
+ int retval;
+
+ lock ();
+ retval = ring_buffer_max_lines;
+ unlock ();
+
+ return retval;
+}
+
+void
+caja_debug_log_clear (void)
+{
+ int i;
+
+ lock ();
+
+ if (!ring_buffer)
+ goto out;
+
+ for (i = 0; i < ring_buffer_max_lines; i++)
+ {
+ g_free (ring_buffer[i]);
+ ring_buffer[i] = NULL;
+ }
+
+ ring_buffer_next_index = 0;
+ ring_buffer_num_lines = 0;
+
+out:
+ unlock ();
+}
diff --git a/libcaja-private/caja-debug-log.h b/libcaja-private/caja-debug-log.h
new file mode 100644
index 00000000..9f82b513
--- /dev/null
+++ b/libcaja-private/caja-debug-log.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-debug-log.h: Ring buffer for logging debug messages
+
+ Copyright (C) 2006 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Federico Mena-Quintero <[email protected]>
+*/
+
+#ifndef CAJA_DEBUG_LOG_H
+#define CAJA_DEBUG_LOG_H
+
+#include <glib.h>
+
+#define CAJA_DEBUG_LOG_DOMAIN_USER "USER" /* always enabled */
+#define CAJA_DEBUG_LOG_DOMAIN_ASYNC "async" /* when asynchronous notifications come in */
+#define CAJA_DEBUG_LOG_DOMAIN_GLOG "GLog" /* used for GLog messages; don't use it yourself */
+
+void caja_debug_log (gboolean is_milestone, const char *domain, const char *format, ...);
+
+void caja_debug_log_with_uri_list (gboolean is_milestone, const char *domain, const GList *uris,
+ const char *format, ...);
+void caja_debug_log_with_file_list (gboolean is_milestone, const char *domain, GList *files,
+ const char *format, ...);
+
+void caja_debug_logv (gboolean is_milestone, const char *domain, const GList *uris, const char *format, va_list args);
+
+gboolean caja_debug_log_load_configuration (const char *filename, GError **error);
+
+void caja_debug_log_enable_domains (const char **domains, int n_domains);
+void caja_debug_log_disable_domains (const char **domains, int n_domains);
+
+gboolean caja_debug_log_is_domain_enabled (const char *domain);
+
+gboolean caja_debug_log_dump (const char *filename, GError **error);
+
+void caja_debug_log_set_max_lines (int num_lines);
+int caja_debug_log_get_max_lines (void);
+
+/* For testing only */
+void caja_debug_log_clear (void);
+
+#endif /* CAJA_DEBUG_LOG_H */
diff --git a/libcaja-private/caja-default-file-icon.c b/libcaja-private/caja-default-file-icon.c
new file mode 100644
index 00000000..c9389471
--- /dev/null
+++ b/libcaja-private/caja-default-file-icon.c
@@ -0,0 +1,537 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Darin Adler <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-default-file-icon.h"
+
+const int caja_default_file_icon_width = 48;
+const int caja_default_file_icon_height = 48;
+const unsigned char caja_default_file_icon[] =
+ /* This is from text-x-preview.svg in the mate 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/libcaja-private/caja-default-file-icon.h b/libcaja-private/caja-default-file-icon.h
new file mode 100644
index 00000000..0fd31870
--- /dev/null
+++ b/libcaja-private/caja-default-file-icon.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Darin Adler <[email protected]>
+*/
+
+#ifndef CAJA_DEFAULT_FILE_ICON_H
+#define CAJA_DEFAULT_FILE_ICON_H
+
+extern const int caja_default_file_icon_width;
+extern const int caja_default_file_icon_height;
+extern const unsigned char caja_default_file_icon[];
+
+#endif /* CAJA_DEFAULT_FILE_ICON_H */
diff --git a/libcaja-private/caja-desktop-directory-file.c b/libcaja-private/caja-desktop-directory-file.c
new file mode 100644
index 00000000..ebfa47ce
--- /dev/null
+++ b/libcaja-private/caja-desktop-directory-file.c
@@ -0,0 +1,731 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-desktop-directory-file.c: Subclass of CajaFile to help implement the
+ virtual desktop.
+
+ 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Alexander Larsson <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-desktop-directory-file.h"
+
+#include "caja-directory-notify.h"
+#include "caja-directory-private.h"
+#include "caja-file-attributes.h"
+#include "caja-file-private.h"
+#include "caja-file-utilities.h"
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-gtk-macros.h>
+#include <mateconf/mateconf-client.h>
+#include <mateconf/mateconf-value.h>
+#include "caja-desktop-directory.h"
+#include "caja-metadata.h"
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <string.h>
+
+struct CajaDesktopDirectoryFileDetails
+{
+ CajaDesktopDirectory *desktop_directory;
+
+ CajaFile *real_dir_file;
+
+ GHashTable *callbacks;
+ GHashTable *monitors;
+};
+
+typedef struct
+{
+ CajaDesktopDirectoryFile *desktop_file;
+ CajaFileCallback callback;
+ gpointer callback_data;
+
+ CajaFileAttributes delegated_attributes;
+ CajaFileAttributes non_delegated_attributes;
+
+ GList *non_ready_files;
+
+ gboolean initializing;
+} DesktopCallback;
+
+typedef struct
+{
+ CajaDesktopDirectoryFile *desktop_file;
+
+ CajaFileAttributes delegated_attributes;
+ CajaFileAttributes non_delegated_attributes;
+} DesktopMonitor;
+
+
+static void caja_desktop_directory_file_init (gpointer object,
+ gpointer klass);
+static void caja_desktop_directory_file_class_init (gpointer klass);
+
+EEL_CLASS_BOILERPLATE (CajaDesktopDirectoryFile,
+ caja_desktop_directory_file,
+ CAJA_TYPE_FILE)
+
+static guint
+desktop_callback_hash (gconstpointer desktop_callback_as_pointer)
+{
+ const DesktopCallback *desktop_callback;
+
+ desktop_callback = desktop_callback_as_pointer;
+ return GPOINTER_TO_UINT (desktop_callback->callback)
+ ^ GPOINTER_TO_UINT (desktop_callback->callback_data);
+}
+
+static gboolean
+desktop_callback_equal (gconstpointer desktop_callback_as_pointer,
+ gconstpointer desktop_callback_as_pointer_2)
+{
+ const DesktopCallback *desktop_callback, *desktop_callback_2;
+
+ desktop_callback = desktop_callback_as_pointer;
+ desktop_callback_2 = desktop_callback_as_pointer_2;
+
+ return desktop_callback->callback == desktop_callback_2->callback
+ && desktop_callback->callback_data == desktop_callback_2->callback_data;
+}
+
+
+static void
+real_file_changed_callback (CajaFile *real_file,
+ gpointer callback_data)
+{
+ CajaDesktopDirectoryFile *desktop_file;
+
+ desktop_file = CAJA_DESKTOP_DIRECTORY_FILE (callback_data);
+ caja_file_changed (CAJA_FILE (desktop_file));
+}
+
+static CajaFileAttributes
+get_delegated_attributes_mask (void)
+{
+ return CAJA_FILE_ATTRIBUTE_DEEP_COUNTS |
+ CAJA_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT |
+ CAJA_FILE_ATTRIBUTE_DIRECTORY_ITEM_MIME_TYPES;
+}
+
+static void
+partition_attributes (CajaFileAttributes attributes,
+ CajaFileAttributes *delegated_attributes,
+ CajaFileAttributes *non_delegated_attributes)
+{
+ CajaFileAttributes mask;
+
+ mask = get_delegated_attributes_mask ();
+
+ *delegated_attributes = attributes & mask;
+ *non_delegated_attributes = attributes & ~mask;
+}
+
+static void
+desktop_directory_file_monitor_add (CajaFile *file,
+ gconstpointer client,
+ CajaFileAttributes attributes)
+{
+ CajaDesktopDirectoryFile *desktop_file;
+ DesktopMonitor *monitor;
+
+ desktop_file = CAJA_DESKTOP_DIRECTORY_FILE (file);
+
+ /* Map the client to a unique value so this doesn't interfere
+ * with direct monitoring of the file by the same client.
+ */
+ monitor = g_hash_table_lookup (desktop_file->details->monitors, client);
+ if (monitor != NULL)
+ {
+ g_assert (monitor->desktop_file == desktop_file);
+ }
+ else
+ {
+ monitor = g_new0 (DesktopMonitor, 1);
+ monitor->desktop_file = desktop_file;
+ g_hash_table_insert (desktop_file->details->monitors,
+ (gpointer) client, monitor);
+ }
+
+ partition_attributes (attributes,
+ &monitor->delegated_attributes,
+ &monitor->non_delegated_attributes);
+
+ /* Pawn off partioned attributes to real dir file */
+ caja_file_monitor_add (desktop_file->details->real_dir_file,
+ monitor, monitor->delegated_attributes);
+
+ /* Do the rest ourself */
+ caja_directory_monitor_add_internal
+ (file->details->directory, file,
+ client, TRUE, TRUE,
+ monitor->non_delegated_attributes,
+ NULL, NULL);
+}
+
+static void
+desktop_directory_file_monitor_remove (CajaFile *file,
+ gconstpointer client)
+{
+ CajaDesktopDirectoryFile *desktop_file;
+ DesktopMonitor *monitor;
+
+ desktop_file = CAJA_DESKTOP_DIRECTORY_FILE (file);
+
+ /* Map the client to the value used by the earlier add call. */
+ monitor = g_hash_table_lookup (desktop_file->details->monitors, client);
+ if (monitor == NULL)
+ {
+ return;
+ }
+
+ /* Call through to the real file remove calls. */
+ g_hash_table_remove (desktop_file->details->monitors, client);
+
+ /* Remove the locally handled parts */
+ caja_directory_monitor_remove_internal
+ (file->details->directory, file, client);
+}
+
+static void
+desktop_callback_destroy (DesktopCallback *desktop_callback)
+{
+ g_assert (desktop_callback != NULL);
+ g_assert (CAJA_IS_DESKTOP_DIRECTORY_FILE (desktop_callback->desktop_file));
+
+ caja_file_unref (CAJA_FILE (desktop_callback->desktop_file));
+ g_list_free (desktop_callback->non_ready_files);
+ g_free (desktop_callback);
+}
+
+static void
+desktop_callback_check_done (DesktopCallback *desktop_callback)
+{
+ /* Check if we are ready. */
+ if (desktop_callback->initializing ||
+ desktop_callback->non_ready_files != NULL)
+ {
+ return;
+ }
+
+ /* Remove from the hash table before sending it. */
+ g_hash_table_remove (desktop_callback->desktop_file->details->callbacks,
+ desktop_callback);
+
+ /* We are ready, so do the real callback. */
+ (* desktop_callback->callback) (CAJA_FILE (desktop_callback->desktop_file),
+ desktop_callback->callback_data);
+
+ /* And we are done. */
+ desktop_callback_destroy (desktop_callback);
+}
+
+static void
+desktop_callback_remove_file (DesktopCallback *desktop_callback,
+ CajaFile *file)
+{
+ desktop_callback->non_ready_files = g_list_remove
+ (desktop_callback->non_ready_files, file);
+ desktop_callback_check_done (desktop_callback);
+}
+
+static void
+ready_callback (CajaFile *file,
+ gpointer callback_data)
+{
+ DesktopCallback *desktop_callback;
+
+ g_assert (CAJA_IS_FILE (file));
+ g_assert (callback_data != NULL);
+
+ desktop_callback = callback_data;
+ g_assert (g_list_find (desktop_callback->non_ready_files, file) != NULL);
+
+ desktop_callback_remove_file (desktop_callback, file);
+}
+
+static void
+desktop_directory_file_call_when_ready (CajaFile *file,
+ CajaFileAttributes attributes,
+ CajaFileCallback callback,
+ gpointer callback_data)
+
+{
+ CajaDesktopDirectoryFile *desktop_file;
+ DesktopCallback search_key, *desktop_callback;
+
+ desktop_file = CAJA_DESKTOP_DIRECTORY_FILE (file);
+
+ /* Check to be sure we aren't overwriting. */
+ search_key.callback = callback;
+ search_key.callback_data = callback_data;
+ if (g_hash_table_lookup (desktop_file->details->callbacks, &search_key) != NULL)
+ {
+ g_warning ("tried to add a new callback while an old one was pending");
+ return;
+ }
+
+ /* Create a desktop_callback record. */
+ desktop_callback = g_new0 (DesktopCallback, 1);
+ caja_file_ref (file);
+ desktop_callback->desktop_file = desktop_file;
+ desktop_callback->callback = callback;
+ desktop_callback->callback_data = callback_data;
+ desktop_callback->initializing = TRUE;
+
+ partition_attributes (attributes,
+ &desktop_callback->delegated_attributes,
+ &desktop_callback->non_delegated_attributes);
+
+ desktop_callback->non_ready_files = g_list_prepend
+ (desktop_callback->non_ready_files, file);
+ desktop_callback->non_ready_files = g_list_prepend
+ (desktop_callback->non_ready_files, desktop_file->details->real_dir_file);
+
+ /* Put it in the hash table. */
+ g_hash_table_insert (desktop_file->details->callbacks,
+ desktop_callback, desktop_callback);
+
+ /* Now connect to each file's call_when_ready. */
+ caja_directory_call_when_ready_internal
+ (file->details->directory, file,
+ desktop_callback->non_delegated_attributes,
+ FALSE, NULL, ready_callback, desktop_callback);
+ caja_file_call_when_ready
+ (desktop_file->details->real_dir_file,
+ desktop_callback->delegated_attributes,
+ ready_callback, desktop_callback);
+
+ desktop_callback->initializing = FALSE;
+
+ /* Check if any files became read while we were connecting up
+ * the call_when_ready callbacks (also handles the pathological
+ * case where there are no files at all).
+ */
+ desktop_callback_check_done (desktop_callback);
+
+}
+
+static void
+desktop_directory_file_cancel_call_when_ready (CajaFile *file,
+ CajaFileCallback callback,
+ gpointer callback_data)
+{
+ CajaDesktopDirectoryFile *desktop_file;
+ DesktopCallback search_key, *desktop_callback;
+
+ desktop_file = CAJA_DESKTOP_DIRECTORY_FILE (file);
+
+ /* Find the entry in the table. */
+ search_key.callback = callback;
+ search_key.callback_data = callback_data;
+ desktop_callback = g_hash_table_lookup (desktop_file->details->callbacks, &search_key);
+ if (desktop_callback == NULL)
+ {
+ return;
+ }
+
+ /* Remove from the hash table before working with it. */
+ g_hash_table_remove (desktop_callback->desktop_file->details->callbacks, desktop_callback);
+
+ /* Tell the real directory to cancel the call. */
+ caja_directory_cancel_callback_internal
+ (file->details->directory, file,
+ NULL, ready_callback, desktop_callback);
+
+ caja_file_cancel_call_when_ready
+ (desktop_file->details->real_dir_file,
+ ready_callback, desktop_callback);
+
+ desktop_callback_destroy (desktop_callback);
+}
+
+static gboolean
+real_check_if_ready (CajaFile *file,
+ CajaFileAttributes attributes)
+{
+ return caja_directory_check_if_ready_internal
+ (file->details->directory, file,
+ attributes);
+}
+
+static gboolean
+desktop_directory_file_check_if_ready (CajaFile *file,
+ CajaFileAttributes attributes)
+{
+ CajaFileAttributes delegated_attributes, non_delegated_attributes;
+ CajaDesktopDirectoryFile *desktop_file;
+
+ desktop_file = CAJA_DESKTOP_DIRECTORY_FILE (file);
+
+ partition_attributes (attributes,
+ &delegated_attributes,
+ &non_delegated_attributes);
+
+ return real_check_if_ready (file, non_delegated_attributes) &&
+ caja_file_check_if_ready (desktop_file->details->real_dir_file,
+ delegated_attributes);
+}
+
+static gboolean
+desktop_directory_file_get_item_count (CajaFile *file,
+ guint *count,
+ gboolean *count_unreadable)
+{
+ CajaDesktopDirectoryFile *desktop_file;
+ gboolean got_count;
+
+ desktop_file = CAJA_DESKTOP_DIRECTORY_FILE (file);
+
+ got_count = caja_file_get_directory_item_count (desktop_file->details->real_dir_file,
+ count,
+ count_unreadable);
+
+ if (count)
+ {
+ *count += g_list_length (file->details->directory->details->file_list);
+ }
+
+ return got_count;
+}
+
+static CajaRequestStatus
+desktop_directory_file_get_deep_counts (CajaFile *file,
+ guint *directory_count,
+ guint *file_count,
+ guint *unreadable_directory_count,
+ goffset *total_size)
+{
+ CajaDesktopDirectoryFile *desktop_file;
+ CajaRequestStatus status;
+
+ desktop_file = CAJA_DESKTOP_DIRECTORY_FILE (file);
+
+ status = caja_file_get_deep_counts (desktop_file->details->real_dir_file,
+ directory_count,
+ file_count,
+ unreadable_directory_count,
+ total_size,
+ TRUE);
+
+ if (file_count)
+ {
+ *file_count += g_list_length (file->details->directory->details->file_list);
+ }
+
+ return status;
+}
+
+static gboolean
+desktop_directory_file_get_date (CajaFile *file,
+ CajaDateType date_type,
+ time_t *date)
+{
+ CajaDesktopDirectoryFile *desktop_file;
+
+ desktop_file = CAJA_DESKTOP_DIRECTORY_FILE (file);
+
+ return caja_file_get_date (desktop_file->details->real_dir_file,
+ date_type,
+ date);
+}
+
+static char *
+desktop_directory_file_get_where_string (CajaFile *file)
+{
+ return g_strdup (_("on the desktop"));
+}
+
+
+static void
+monitor_destroy (gpointer data)
+{
+ DesktopMonitor *monitor = data;
+
+ caja_file_monitor_remove
+ (CAJA_FILE (monitor->desktop_file->details->real_dir_file), monitor);
+ g_free (monitor);
+}
+
+static char *
+get_metadata_mateconf_path (const char *name,
+ const char *key)
+{
+ char *res, *escaped_name;
+
+ escaped_name = mateconf_escape_key (name, -1);
+ res = g_build_filename (CAJA_DESKTOP_METADATA_MATECONF_PATH, escaped_name, key, NULL);
+ g_free (escaped_name);
+
+ return res;
+}
+
+void
+caja_desktop_set_metadata_string (CajaFile *file,
+ const char *name,
+ const char *key,
+ const char *string)
+{
+ MateConfClient *client;
+ char *mateconf_key;
+
+ client = mateconf_client_get_default ();
+ mateconf_key = get_metadata_mateconf_path (name, key);
+
+ if (string)
+ {
+ mateconf_client_set_string (client, mateconf_key, string, NULL);
+ }
+ else
+ {
+ mateconf_client_unset (client, mateconf_key, NULL);
+ }
+
+ g_free (mateconf_key);
+ g_object_unref (client);
+
+ if (caja_desktop_update_metadata_from_mateconf (file, name))
+ {
+ caja_file_changed (file);
+ }
+}
+
+void
+caja_desktop_set_metadata_stringv (CajaFile *file,
+ const char *name,
+ const char *key,
+ char **stringv)
+{
+ MateConfClient *client;
+ char *mateconf_key;
+ GSList *list;
+ int i;
+
+ client = mateconf_client_get_default ();
+ mateconf_key = get_metadata_mateconf_path (name, key);
+
+ list = NULL;
+ for (i = 0; stringv[i] != NULL; i++)
+ {
+ list = g_slist_prepend (list, stringv[i]);
+ }
+ list = g_slist_reverse (list);
+
+ mateconf_client_set_list (client, mateconf_key,
+ MATECONF_VALUE_STRING,
+ list, NULL);
+
+ g_slist_free (list);
+ g_free (mateconf_key);
+ g_object_unref (client);
+
+ if (caja_desktop_update_metadata_from_mateconf (file, name))
+ {
+ caja_file_changed (file);
+ }
+}
+
+gboolean
+caja_desktop_update_metadata_from_mateconf (CajaFile *file,
+ const char *name)
+{
+ MateConfClient *client;
+ GSList *entries, *l;
+ char *dir;
+ const char *key;
+ MateConfEntry *entry;
+ MateConfValue *value;
+ GFileInfo *info;
+ gboolean changed;
+ char *gio_key;
+ GSList *value_list;
+ char **strv;
+ int i;
+
+ client = mateconf_client_get_default ();
+
+ dir = get_metadata_mateconf_path (name, NULL);
+ entries = mateconf_client_all_entries (client, dir, NULL);
+ g_free (dir);
+
+ info = g_file_info_new ();
+
+ for (l = entries; l != NULL; l = l->next)
+ {
+ entry = l->data;
+
+ key = mateconf_entry_get_key (entry);
+ value = mateconf_entry_get_value (entry);
+
+ if (value == NULL)
+ {
+ continue;
+ }
+ key = strrchr (key, '/') + 1;
+
+ gio_key = g_strconcat ("metadata::", key, NULL);
+ if (value->type == MATECONF_VALUE_STRING)
+ {
+ g_file_info_set_attribute_string (info, gio_key,
+ mateconf_value_get_string (value));
+ }
+ else if (value->type == MATECONF_VALUE_LIST &&
+ mateconf_value_get_list_type (value) == MATECONF_VALUE_STRING)
+ {
+ value_list = mateconf_value_get_list (value);
+ strv = g_new (char *, g_slist_length (value_list) + 1);
+ for (i = 0; value_list != NULL; i++, value_list = value_list->next)
+ {
+ strv[i] = l->data;
+ }
+ strv[i] = NULL;
+ g_file_info_set_attribute_stringv (info, gio_key, strv);
+ g_free (strv);
+ }
+
+ g_free (gio_key);
+
+ mateconf_entry_unref (entry);
+ }
+ g_slist_free (entries);
+
+ changed = caja_file_update_metadata_from_info (file, info);
+
+ g_object_unref (info);
+ g_object_unref (client);
+
+ return changed;
+}
+
+static void
+caja_desktop_directory_file_set_metadata (CajaFile *file,
+ const char *key,
+ const char *value)
+{
+ caja_desktop_set_metadata_string (file, "directory", key, value);
+}
+
+static void
+caja_desktop_directory_file_set_metadata_as_list (CajaFile *file,
+ const char *key,
+ char **value)
+{
+ caja_desktop_set_metadata_stringv (file, "directory", key, value);
+}
+
+static void
+caja_desktop_directory_file_init (gpointer object, gpointer klass)
+{
+ CajaDesktopDirectoryFile *desktop_file;
+ CajaDesktopDirectory *desktop_directory;
+ CajaDirectory *real_dir;
+ CajaFile *real_dir_file;
+
+ desktop_file = CAJA_DESKTOP_DIRECTORY_FILE (object);
+
+ desktop_directory = CAJA_DESKTOP_DIRECTORY (caja_directory_get_by_uri (EEL_DESKTOP_URI));
+
+ desktop_file->details = g_new0 (CajaDesktopDirectoryFileDetails, 1);
+ desktop_file->details->desktop_directory = desktop_directory;
+
+ desktop_file->details->callbacks = g_hash_table_new
+ (desktop_callback_hash, desktop_callback_equal);
+ desktop_file->details->monitors = g_hash_table_new_full (NULL, NULL,
+ NULL, monitor_destroy);
+
+ real_dir = caja_desktop_directory_get_real_directory (desktop_directory);
+ real_dir_file = caja_directory_get_corresponding_file (real_dir);
+ caja_directory_unref (real_dir);
+
+ desktop_file->details->real_dir_file = real_dir_file;
+
+ caja_desktop_update_metadata_from_mateconf (CAJA_FILE (desktop_file), "directory");
+
+ g_signal_connect_object (real_dir_file, "changed",
+ G_CALLBACK (real_file_changed_callback), desktop_file, 0);
+}
+
+
+static void
+desktop_callback_remove_file_cover (gpointer key,
+ gpointer value,
+ gpointer callback_data)
+{
+ desktop_callback_remove_file
+ (value, CAJA_FILE (callback_data));
+}
+
+
+static void
+desktop_finalize (GObject *object)
+{
+ CajaDesktopDirectoryFile *desktop_file;
+ CajaDesktopDirectory *desktop_directory;
+
+ desktop_file = CAJA_DESKTOP_DIRECTORY_FILE (object);
+ desktop_directory = desktop_file->details->desktop_directory;
+
+ /* Todo: ghash now safe? */
+ eel_g_hash_table_safe_for_each
+ (desktop_file->details->callbacks,
+ desktop_callback_remove_file_cover,
+ desktop_file->details->real_dir_file);
+
+ if (g_hash_table_size (desktop_file->details->callbacks) != 0)
+ {
+ g_warning ("call_when_ready still pending when desktop virtual file is destroyed");
+ }
+
+ g_hash_table_destroy (desktop_file->details->callbacks);
+ g_hash_table_destroy (desktop_file->details->monitors);
+
+ caja_file_unref (desktop_file->details->real_dir_file);
+
+ g_free (desktop_file->details);
+
+ caja_directory_unref (CAJA_DIRECTORY (desktop_directory));
+
+ EEL_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
+}
+
+static void
+caja_desktop_directory_file_class_init (gpointer klass)
+{
+ GObjectClass *object_class;
+ CajaFileClass *file_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ file_class = CAJA_FILE_CLASS (klass);
+
+ object_class->finalize = desktop_finalize;
+
+ file_class->default_file_type = G_FILE_TYPE_DIRECTORY;
+
+ file_class->monitor_add = desktop_directory_file_monitor_add;
+ file_class->monitor_remove = desktop_directory_file_monitor_remove;
+ file_class->call_when_ready = desktop_directory_file_call_when_ready;
+ file_class->cancel_call_when_ready = desktop_directory_file_cancel_call_when_ready;
+ file_class->check_if_ready = desktop_directory_file_check_if_ready;
+ file_class->get_item_count = desktop_directory_file_get_item_count;
+ file_class->get_deep_counts = desktop_directory_file_get_deep_counts;
+ file_class->get_date = desktop_directory_file_get_date;
+ file_class->get_where_string = desktop_directory_file_get_where_string;
+ file_class->set_metadata = caja_desktop_directory_file_set_metadata;
+ file_class->set_metadata_as_list = caja_desktop_directory_file_set_metadata_as_list;
+}
diff --git a/libcaja-private/caja-desktop-directory-file.h b/libcaja-private/caja-desktop-directory-file.h
new file mode 100644
index 00000000..ec6f3217
--- /dev/null
+++ b/libcaja-private/caja-desktop-directory-file.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-desktop-directory-file.h: Subclass of CajaFile to implement the
+ the case of the desktop 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Alexander Larsson <[email protected]>
+*/
+
+#ifndef CAJA_DESKTOP_DIRECTORY_FILE_H
+#define CAJA_DESKTOP_DIRECTORY_FILE_H
+
+#include <libcaja-private/caja-file.h>
+
+#define CAJA_TYPE_DESKTOP_DIRECTORY_FILE caja_desktop_directory_file_get_type()
+#define CAJA_DESKTOP_DIRECTORY_FILE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_DESKTOP_DIRECTORY_FILE, CajaDesktopDirectoryFile))
+#define CAJA_DESKTOP_DIRECTORY_FILE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_DESKTOP_DIRECTORY_FILE, CajaDesktopDirectoryFileClass))
+#define CAJA_IS_DESKTOP_DIRECTORY_FILE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_DESKTOP_DIRECTORY_FILE))
+#define CAJA_IS_DESKTOP_DIRECTORY_FILE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_DESKTOP_DIRECTORY_FILE))
+#define CAJA_DESKTOP_DIRECTORY_FILE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_DESKTOP_DIRECTORY_FILE, CajaDesktopDirectoryFileClass))
+
+typedef struct CajaDesktopDirectoryFileDetails CajaDesktopDirectoryFileDetails;
+
+typedef struct
+{
+ CajaFile parent_slot;
+ CajaDesktopDirectoryFileDetails *details;
+} CajaDesktopDirectoryFile;
+
+typedef struct
+{
+ CajaFileClass parent_slot;
+} CajaDesktopDirectoryFileClass;
+
+GType caja_desktop_directory_file_get_type (void);
+gboolean caja_desktop_update_metadata_from_mateconf (CajaFile *file,
+ const char *name);
+void caja_desktop_set_metadata_string (CajaFile *file,
+ const char *name,
+ const char *key,
+ const char *string);
+void caja_desktop_set_metadata_stringv (CajaFile *file,
+ const char *name,
+ const char *key,
+ char **stringv);
+
+#endif /* CAJA_DESKTOP_DIRECTORY_FILE_H */
diff --git a/libcaja-private/caja-desktop-directory.c b/libcaja-private/caja-desktop-directory.c
new file mode 100644
index 00000000..11f1f3c0
--- /dev/null
+++ b/libcaja-private/caja-desktop-directory.c
@@ -0,0 +1,560 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-desktop-directory.c: Subclass of CajaDirectory to implement
+ a virtual directory consisting of the desktop directory and the desktop
+ icons
+
+ 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Alexander Larsson <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-desktop-directory.h"
+
+#include "caja-directory-private.h"
+#include "caja-file.h"
+#include "caja-file-private.h"
+#include "caja-file-utilities.h"
+#include "caja-global-preferences.h"
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-gtk-macros.h>
+#include <gtk/gtk.h>
+
+struct CajaDesktopDirectoryDetails
+{
+ CajaDirectory *real_directory;
+ GHashTable *callbacks;
+ GHashTable *monitors;
+};
+
+typedef struct
+{
+ CajaDesktopDirectory *desktop_dir;
+ CajaDirectoryCallback callback;
+ gpointer callback_data;
+
+ CajaFileAttributes wait_for_attributes;
+ gboolean wait_for_file_list;
+
+ GList *non_ready_directories;
+ GList *merged_file_list;
+} MergedCallback;
+
+
+typedef struct
+{
+ CajaDesktopDirectory *desktop_dir;
+
+ gboolean monitor_hidden_files;
+ gboolean monitor_backup_files;
+ CajaFileAttributes monitor_attributes;
+} MergedMonitor;
+
+static void desktop_directory_changed_callback (gpointer data);
+
+G_DEFINE_TYPE (CajaDesktopDirectory, caja_desktop_directory,
+ CAJA_TYPE_DIRECTORY);
+#define parent_class caja_desktop_directory_parent_class
+
+static gboolean
+desktop_contains_file (CajaDirectory *directory,
+ CajaFile *file)
+{
+ CajaDesktopDirectory *desktop;
+
+ desktop = CAJA_DESKTOP_DIRECTORY (directory);
+
+ if (caja_directory_contains_file (desktop->details->real_directory, file))
+ {
+ return TRUE;
+ }
+
+ return file->details->directory == directory;
+}
+
+static guint
+merged_callback_hash (gconstpointer merged_callback_as_pointer)
+{
+ const MergedCallback *merged_callback;
+
+ merged_callback = merged_callback_as_pointer;
+ return GPOINTER_TO_UINT (merged_callback->callback)
+ ^ GPOINTER_TO_UINT (merged_callback->callback_data);
+}
+
+static gboolean
+merged_callback_equal (gconstpointer merged_callback_as_pointer,
+ gconstpointer merged_callback_as_pointer_2)
+{
+ const MergedCallback *merged_callback, *merged_callback_2;
+
+ merged_callback = merged_callback_as_pointer;
+ merged_callback_2 = merged_callback_as_pointer_2;
+
+ return merged_callback->callback == merged_callback_2->callback
+ && merged_callback->callback_data == merged_callback_2->callback_data;
+}
+
+static void
+merged_callback_destroy (MergedCallback *merged_callback)
+{
+ g_assert (merged_callback != NULL);
+ g_assert (CAJA_IS_DESKTOP_DIRECTORY (merged_callback->desktop_dir));
+
+ g_list_free (merged_callback->non_ready_directories);
+ caja_file_list_free (merged_callback->merged_file_list);
+ g_free (merged_callback);
+}
+
+static void
+merged_callback_check_done (MergedCallback *merged_callback)
+{
+ /* Check if we are ready. */
+ if (merged_callback->non_ready_directories != NULL)
+ {
+ return;
+ }
+
+ /* Remove from the hash table before sending it. */
+ g_hash_table_steal (merged_callback->desktop_dir->details->callbacks, merged_callback);
+
+ /* We are ready, so do the real callback. */
+ (* merged_callback->callback) (CAJA_DIRECTORY (merged_callback->desktop_dir),
+ merged_callback->merged_file_list,
+ merged_callback->callback_data);
+
+ /* And we are done. */
+ merged_callback_destroy (merged_callback);
+}
+
+static void
+merged_callback_remove_directory (MergedCallback *merged_callback,
+ CajaDirectory *directory)
+{
+ merged_callback->non_ready_directories = g_list_remove
+ (merged_callback->non_ready_directories, directory);
+ merged_callback_check_done (merged_callback);
+}
+
+static void
+directory_ready_callback (CajaDirectory *directory,
+ GList *files,
+ gpointer callback_data)
+{
+ MergedCallback *merged_callback;
+
+ g_assert (CAJA_IS_DIRECTORY (directory));
+ g_assert (callback_data != NULL);
+
+ merged_callback = callback_data;
+ g_assert (g_list_find (merged_callback->non_ready_directories, directory) != NULL);
+
+ /* Update based on this call. */
+ merged_callback->merged_file_list = g_list_concat
+ (merged_callback->merged_file_list,
+ caja_file_list_copy (files));
+
+ /* Check if we are ready. */
+ merged_callback_remove_directory (merged_callback, directory);
+}
+
+static void
+desktop_call_when_ready (CajaDirectory *directory,
+ CajaFileAttributes file_attributes,
+ gboolean wait_for_file_list,
+ CajaDirectoryCallback callback,
+ gpointer callback_data)
+{
+ CajaDesktopDirectory *desktop;
+ MergedCallback search_key, *merged_callback;
+
+ desktop = CAJA_DESKTOP_DIRECTORY (directory);
+
+ /* Check to be sure we aren't overwriting. */
+ search_key.callback = callback;
+ search_key.callback_data = callback_data;
+ if (g_hash_table_lookup (desktop->details->callbacks, &search_key) != NULL)
+ {
+ g_warning ("tried to add a new callback while an old one was pending");
+ return;
+ }
+
+ /* Create a merged_callback record. */
+ merged_callback = g_new0 (MergedCallback, 1);
+ merged_callback->desktop_dir = desktop;
+ merged_callback->callback = callback;
+ merged_callback->callback_data = callback_data;
+ merged_callback->wait_for_attributes = file_attributes;
+ merged_callback->wait_for_file_list = wait_for_file_list;
+ merged_callback->non_ready_directories = g_list_prepend
+ (merged_callback->non_ready_directories, directory);
+ merged_callback->non_ready_directories = g_list_prepend
+ (merged_callback->non_ready_directories, desktop->details->real_directory);
+
+
+ merged_callback->merged_file_list = g_list_concat (NULL,
+ caja_file_list_copy (directory->details->file_list));
+
+ /* Put it in the hash table. */
+ g_hash_table_insert (desktop->details->callbacks,
+ merged_callback, merged_callback);
+
+ /* Now tell all the directories about it. */
+ caja_directory_call_when_ready
+ (desktop->details->real_directory,
+ merged_callback->wait_for_attributes,
+ merged_callback->wait_for_file_list,
+ directory_ready_callback, merged_callback);
+ caja_directory_call_when_ready_internal
+ (directory,
+ NULL,
+ merged_callback->wait_for_attributes,
+ merged_callback->wait_for_file_list,
+ directory_ready_callback,
+ NULL,
+ merged_callback);
+
+}
+
+static void
+desktop_cancel_callback (CajaDirectory *directory,
+ CajaDirectoryCallback callback,
+ gpointer callback_data)
+{
+ CajaDesktopDirectory *desktop;
+ MergedCallback search_key, *merged_callback;
+ GList *node;
+
+ desktop = CAJA_DESKTOP_DIRECTORY (directory);
+
+ /* Find the entry in the table. */
+ search_key.callback = callback;
+ search_key.callback_data = callback_data;
+ merged_callback = g_hash_table_lookup (desktop->details->callbacks, &search_key);
+ if (merged_callback == NULL)
+ {
+ return;
+ }
+
+ /* Remove from the hash table before working with it. */
+ g_hash_table_steal (merged_callback->desktop_dir->details->callbacks, merged_callback);
+
+ /* Tell all the directories to cancel the call. */
+ for (node = merged_callback->non_ready_directories; node != NULL; node = node->next)
+ {
+ caja_directory_cancel_callback
+ (node->data,
+ directory_ready_callback, merged_callback);
+ }
+ merged_callback_destroy (merged_callback);
+}
+
+static void
+merged_monitor_destroy (MergedMonitor *monitor)
+{
+ CajaDesktopDirectory *desktop;
+
+ desktop = monitor->desktop_dir;
+
+ /* Call through to the real directory remove calls. */
+ caja_directory_file_monitor_remove (desktop->details->real_directory, monitor);
+
+ caja_directory_monitor_remove_internal (CAJA_DIRECTORY (desktop), NULL, monitor);
+
+ g_free (monitor);
+}
+
+static void
+build_merged_callback_list (CajaDirectory *directory,
+ GList *file_list,
+ gpointer callback_data)
+{
+ GList **merged_list;
+
+ merged_list = callback_data;
+ *merged_list = g_list_concat (*merged_list,
+ caja_file_list_copy (file_list));
+}
+
+static void
+desktop_monitor_add (CajaDirectory *directory,
+ gconstpointer client,
+ gboolean monitor_hidden_files,
+ gboolean monitor_backup_files,
+ CajaFileAttributes file_attributes,
+ CajaDirectoryCallback callback,
+ gpointer callback_data)
+{
+ CajaDesktopDirectory *desktop;
+ MergedMonitor *monitor;
+ GList *merged_callback_list;
+
+ desktop = CAJA_DESKTOP_DIRECTORY (directory);
+
+ /* Map the client to a unique value so this doesn't interfere
+ * with direct monitoring of the directory by the same client.
+ */
+ monitor = g_hash_table_lookup (desktop->details->monitors, client);
+ if (monitor != NULL)
+ {
+ g_assert (monitor->desktop_dir == desktop);
+ }
+ else
+ {
+ monitor = g_new0 (MergedMonitor, 1);
+ monitor->desktop_dir = desktop;
+ g_hash_table_insert (desktop->details->monitors,
+ (gpointer) client, monitor);
+ }
+ monitor->monitor_hidden_files = monitor_hidden_files;
+ monitor->monitor_backup_files = monitor_backup_files;
+ monitor->monitor_attributes = file_attributes;
+
+ /* Call through to the real directory add calls. */
+ merged_callback_list = NULL;
+
+ /* Call up to real dir */
+ caja_directory_file_monitor_add
+ (desktop->details->real_directory, monitor,
+ monitor_hidden_files, monitor_backup_files,
+ file_attributes,
+ build_merged_callback_list, &merged_callback_list);
+
+ /* Handle the desktop part */
+ merged_callback_list = g_list_concat (merged_callback_list,
+ caja_file_list_copy (directory->details->file_list));
+
+
+ if (callback != NULL)
+ {
+ (* callback) (directory, merged_callback_list, callback_data);
+ }
+ caja_file_list_free (merged_callback_list);
+}
+
+static void
+desktop_monitor_remove (CajaDirectory *directory,
+ gconstpointer client)
+{
+ CajaDesktopDirectory *desktop;
+ MergedMonitor *monitor;
+
+ desktop = CAJA_DESKTOP_DIRECTORY (directory);
+
+ monitor = g_hash_table_lookup (desktop->details->monitors, client);
+ if (monitor == NULL)
+ {
+ return;
+ }
+
+ g_hash_table_remove (desktop->details->monitors, client);
+}
+
+static void
+desktop_force_reload (CajaDirectory *directory)
+{
+ CajaDesktopDirectory *desktop;
+
+ desktop = CAJA_DESKTOP_DIRECTORY (directory);
+
+ caja_directory_force_reload (desktop->details->real_directory);
+
+ /* We don't invalidate the files in desktop, since they are always
+ up to date. (And we don't ever want to mark them invalid.) */
+}
+
+static gboolean
+desktop_are_all_files_seen (CajaDirectory *directory)
+{
+ CajaDesktopDirectory *desktop;
+
+ desktop = CAJA_DESKTOP_DIRECTORY (directory);
+
+ if (!caja_directory_are_all_files_seen (desktop->details->real_directory))
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+desktop_is_not_empty (CajaDirectory *directory)
+{
+ CajaDesktopDirectory *desktop;
+
+ desktop = CAJA_DESKTOP_DIRECTORY (directory);
+
+ if (caja_directory_is_not_empty (desktop->details->real_directory))
+ {
+ return TRUE;
+ }
+
+ return directory->details->file_list != NULL;
+}
+
+static GList *
+desktop_get_file_list (CajaDirectory *directory)
+{
+ GList *real_dir_file_list, *desktop_dir_file_list = NULL;
+
+ real_dir_file_list = caja_directory_get_file_list
+ (CAJA_DESKTOP_DIRECTORY (directory)->details->real_directory);
+ desktop_dir_file_list = EEL_CALL_PARENT_WITH_RETURN_VALUE (CAJA_DIRECTORY_CLASS, get_file_list, (directory));
+
+ return g_list_concat (real_dir_file_list, desktop_dir_file_list);
+}
+
+CajaDirectory *
+caja_desktop_directory_get_real_directory (CajaDesktopDirectory *desktop)
+{
+ caja_directory_ref (desktop->details->real_directory);
+ return desktop->details->real_directory;
+}
+
+
+static void
+desktop_finalize (GObject *object)
+{
+ CajaDesktopDirectory *desktop;
+
+ desktop = CAJA_DESKTOP_DIRECTORY (object);
+
+ caja_directory_unref (desktop->details->real_directory);
+
+ g_hash_table_destroy (desktop->details->callbacks);
+ g_hash_table_destroy (desktop->details->monitors);
+ g_free (desktop->details);
+
+ eel_preferences_remove_callback (CAJA_PREFERENCES_DESKTOP_IS_HOME_DIR,
+ desktop_directory_changed_callback,
+ desktop);
+
+ G_OBJECT_CLASS (caja_desktop_directory_parent_class)->finalize (object);
+}
+
+static void
+done_loading_callback (CajaDirectory *real_directory,
+ CajaDesktopDirectory *desktop)
+{
+ caja_directory_emit_done_loading (CAJA_DIRECTORY (desktop));
+}
+
+
+static void
+forward_files_added_cover (CajaDirectory *real_directory,
+ GList *files,
+ gpointer callback_data)
+{
+ caja_directory_emit_files_added (CAJA_DIRECTORY (callback_data), files);
+}
+
+static void
+forward_files_changed_cover (CajaDirectory *real_directory,
+ GList *files,
+ gpointer callback_data)
+{
+ caja_directory_emit_files_changed (CAJA_DIRECTORY (callback_data), files);
+}
+
+static void
+update_desktop_directory (CajaDesktopDirectory *desktop)
+{
+ char *desktop_path;
+ char *desktop_uri;
+ CajaDirectory *real_directory;
+
+ real_directory = desktop->details->real_directory;
+ if (real_directory != NULL)
+ {
+ g_hash_table_foreach_remove (desktop->details->callbacks, (GHRFunc) gtk_true, NULL);
+ g_hash_table_foreach_remove (desktop->details->monitors, (GHRFunc) gtk_true, NULL);
+
+ g_signal_handlers_disconnect_by_func (real_directory, done_loading_callback, desktop);
+ g_signal_handlers_disconnect_by_func (real_directory, forward_files_added_cover, desktop);
+ g_signal_handlers_disconnect_by_func (real_directory, forward_files_changed_cover, desktop);
+
+ caja_directory_unref (real_directory);
+ }
+
+ desktop_path = caja_get_desktop_directory ();
+ desktop_uri = g_filename_to_uri (desktop_path, NULL, NULL);
+ real_directory = caja_directory_get_by_uri (desktop_uri);
+ g_free (desktop_uri);
+ g_free (desktop_path);
+
+ g_signal_connect_object (real_directory, "done_loading",
+ G_CALLBACK (done_loading_callback), desktop, 0);
+ g_signal_connect_object (real_directory, "files_added",
+ G_CALLBACK (forward_files_added_cover), desktop, 0);
+ g_signal_connect_object (real_directory, "files_changed",
+ G_CALLBACK (forward_files_changed_cover), desktop, 0);
+
+ desktop->details->real_directory = real_directory;
+}
+
+static void
+desktop_directory_changed_callback (gpointer data)
+{
+ update_desktop_directory (CAJA_DESKTOP_DIRECTORY (data));
+ caja_directory_force_reload (CAJA_DIRECTORY (data));
+}
+
+static void
+caja_desktop_directory_init (CajaDesktopDirectory *desktop)
+{
+ desktop->details = g_new0 (CajaDesktopDirectoryDetails, 1);
+
+ desktop->details->callbacks = g_hash_table_new_full
+ (merged_callback_hash, merged_callback_equal,
+ NULL, (GDestroyNotify)merged_callback_destroy);
+ desktop->details->monitors = g_hash_table_new_full (NULL, NULL,
+ NULL, (GDestroyNotify)merged_monitor_destroy);
+
+ update_desktop_directory (CAJA_DESKTOP_DIRECTORY (desktop));
+
+ eel_preferences_add_callback (CAJA_PREFERENCES_DESKTOP_IS_HOME_DIR,
+ desktop_directory_changed_callback,
+ desktop);
+}
+
+static void
+caja_desktop_directory_class_init (CajaDesktopDirectoryClass *class)
+{
+ CajaDirectoryClass *directory_class;
+
+ directory_class = CAJA_DIRECTORY_CLASS (class);
+
+ G_OBJECT_CLASS (class)->finalize = desktop_finalize;
+
+ directory_class->contains_file = desktop_contains_file;
+ directory_class->call_when_ready = desktop_call_when_ready;
+ directory_class->cancel_callback = desktop_cancel_callback;
+ directory_class->file_monitor_add = desktop_monitor_add;
+ directory_class->file_monitor_remove = desktop_monitor_remove;
+ directory_class->force_reload = desktop_force_reload;
+ directory_class->are_all_files_seen = desktop_are_all_files_seen;
+ directory_class->is_not_empty = desktop_is_not_empty;
+ /* Override get_file_list so that we can return the list of files
+ * in CajaDesktopDirectory->details->real_directory,
+ * in addition to the list of standard desktop icons on the desktop.
+ */
+ directory_class->get_file_list = desktop_get_file_list;
+}
+
diff --git a/libcaja-private/caja-desktop-directory.h b/libcaja-private/caja-desktop-directory.h
new file mode 100644
index 00000000..06a050fc
--- /dev/null
+++ b/libcaja-private/caja-desktop-directory.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-desktop-directory.h: Subclass of CajaDirectory to implement
+ a virtual directory consisting of the desktop directory and the desktop
+ icons
+
+ 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Alexander Larsson <[email protected]>
+*/
+
+#ifndef CAJA_DESKTOP_DIRECTORY_H
+#define CAJA_DESKTOP_DIRECTORY_H
+
+#include <libcaja-private/caja-directory.h>
+
+#define CAJA_TYPE_DESKTOP_DIRECTORY caja_desktop_directory_get_type()
+#define CAJA_DESKTOP_DIRECTORY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_DESKTOP_DIRECTORY, CajaDesktopDirectory))
+#define CAJA_DESKTOP_DIRECTORY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_DESKTOP_DIRECTORY, CajaDesktopDirectoryClass))
+#define CAJA_IS_DESKTOP_DIRECTORY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_DESKTOP_DIRECTORY))
+#define CAJA_IS_DESKTOP_DIRECTORY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_DESKTOP_DIRECTORY))
+#define CAJA_DESKTOP_DIRECTORY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_DESKTOP_DIRECTORY, CajaDesktopDirectoryClass))
+
+typedef struct CajaDesktopDirectoryDetails CajaDesktopDirectoryDetails;
+
+typedef struct
+{
+ CajaDirectory parent_slot;
+ CajaDesktopDirectoryDetails *details;
+} CajaDesktopDirectory;
+
+typedef struct
+{
+ CajaDirectoryClass parent_slot;
+
+} CajaDesktopDirectoryClass;
+
+GType caja_desktop_directory_get_type (void);
+CajaDirectory * caja_desktop_directory_get_real_directory (CajaDesktopDirectory *desktop_directory);
+
+#endif /* CAJA_DESKTOP_DIRECTORY_H */
diff --git a/libcaja-private/caja-desktop-icon-file.c b/libcaja-private/caja-desktop-icon-file.c
new file mode 100644
index 00000000..5b216a45
--- /dev/null
+++ b/libcaja-private/caja-desktop-icon-file.c
@@ -0,0 +1,410 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-desktop-icon-file.c: Subclass of CajaFile to help implement the
+ virtual desktop icons.
+
+ 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Alexander Larsson <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-desktop-icon-file.h"
+
+#include "caja-desktop-directory-file.h"
+#include "caja-directory-notify.h"
+#include "caja-directory-private.h"
+#include "caja-file-attributes.h"
+#include "caja-file-private.h"
+#include "caja-file-utilities.h"
+#include "caja-file-operations.h"
+#include <eel/eel-glib-extensions.h>
+#include "caja-desktop-directory.h"
+#include <glib/gi18n.h>
+#include <string.h>
+#include <gio/gio.h>
+
+struct CajaDesktopIconFileDetails
+{
+ CajaDesktopLink *link;
+};
+
+G_DEFINE_TYPE(CajaDesktopIconFile, caja_desktop_icon_file, CAJA_TYPE_FILE)
+
+
+static void
+desktop_icon_file_monitor_add (CajaFile *file,
+ gconstpointer client,
+ CajaFileAttributes attributes)
+{
+ caja_directory_monitor_add_internal
+ (file->details->directory, file,
+ client, TRUE, TRUE, attributes, NULL, NULL);
+}
+
+static void
+desktop_icon_file_monitor_remove (CajaFile *file,
+ gconstpointer client)
+{
+ caja_directory_monitor_remove_internal
+ (file->details->directory, file, client);
+}
+
+static void
+desktop_icon_file_call_when_ready (CajaFile *file,
+ CajaFileAttributes attributes,
+ CajaFileCallback callback,
+ gpointer callback_data)
+{
+ caja_directory_call_when_ready_internal
+ (file->details->directory, file,
+ attributes, FALSE, NULL, callback, callback_data);
+}
+
+static void
+desktop_icon_file_cancel_call_when_ready (CajaFile *file,
+ CajaFileCallback callback,
+ gpointer callback_data)
+{
+ caja_directory_cancel_callback_internal
+ (file->details->directory, file,
+ NULL, callback, callback_data);
+}
+
+static gboolean
+desktop_icon_file_check_if_ready (CajaFile *file,
+ CajaFileAttributes attributes)
+{
+ return caja_directory_check_if_ready_internal
+ (file->details->directory, file,
+ attributes);
+}
+
+static gboolean
+desktop_icon_file_get_item_count (CajaFile *file,
+ guint *count,
+ gboolean *count_unreadable)
+{
+ if (count != NULL)
+ {
+ *count = 0;
+ }
+ if (count_unreadable != NULL)
+ {
+ *count_unreadable = FALSE;
+ }
+ return TRUE;
+}
+
+static CajaRequestStatus
+desktop_icon_file_get_deep_counts (CajaFile *file,
+ guint *directory_count,
+ guint *file_count,
+ guint *unreadable_directory_count,
+ goffset *total_size)
+{
+ 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;
+ }
+
+ return CAJA_REQUEST_DONE;
+}
+
+static gboolean
+desktop_icon_file_get_date (CajaFile *file,
+ CajaDateType date_type,
+ time_t *date)
+{
+ CajaDesktopIconFile *desktop_file;
+
+ desktop_file = CAJA_DESKTOP_ICON_FILE (file);
+
+ return caja_desktop_link_get_date (desktop_file->details->link,
+ date_type, date);
+}
+
+static char *
+desktop_icon_file_get_where_string (CajaFile *file)
+{
+ return g_strdup (_("on the desktop"));
+}
+
+static void
+caja_desktop_icon_file_init (CajaDesktopIconFile *desktop_file)
+{
+ desktop_file->details = G_TYPE_INSTANCE_GET_PRIVATE (desktop_file,
+ CAJA_TYPE_DESKTOP_ICON_FILE,
+ CajaDesktopIconFileDetails);
+}
+
+static void
+update_info_from_link (CajaDesktopIconFile *icon_file)
+{
+ CajaFile *file;
+ CajaDesktopLink *link;
+ char *display_name;
+ GMount *mount;
+
+ file = CAJA_FILE (icon_file);
+
+ link = icon_file->details->link;
+
+ if (link == NULL)
+ {
+ return;
+ }
+
+ eel_ref_str_unref (file->details->mime_type);
+ file->details->mime_type = eel_ref_str_get_unique ("application/x-caja-link");
+ file->details->type = G_FILE_TYPE_SHORTCUT;
+ file->details->size = 0;
+ file->details->has_permissions = FALSE;
+ file->details->can_read = TRUE;
+ file->details->can_write = TRUE;
+
+ file->details->can_mount = FALSE;
+ file->details->can_unmount = FALSE;
+ file->details->can_eject = FALSE;
+ if (file->details->mount)
+ {
+ g_object_unref (file->details->mount);
+ }
+ mount = caja_desktop_link_get_mount (link);
+ file->details->mount = mount;
+ if (mount)
+ {
+ file->details->can_unmount = g_mount_can_unmount (mount);
+ file->details->can_eject = g_mount_can_eject (mount);
+ }
+
+ file->details->file_info_is_up_to_date = TRUE;
+
+ display_name = caja_desktop_link_get_display_name (link);
+ caja_file_set_display_name (file,
+ display_name, NULL, TRUE);
+ g_free (display_name);
+
+ if (file->details->icon != NULL)
+ {
+ g_object_unref (file->details->icon);
+ }
+ file->details->icon = caja_desktop_link_get_icon (link);
+ g_free (file->details->activation_uri);
+ file->details->activation_uri = caja_desktop_link_get_activation_uri (link);
+ 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;
+}
+
+void
+caja_desktop_icon_file_update (CajaDesktopIconFile *icon_file)
+{
+ CajaFile *file;
+
+ update_info_from_link (icon_file);
+ file = CAJA_FILE (icon_file);
+ caja_file_changed (file);
+}
+
+void
+caja_desktop_icon_file_remove (CajaDesktopIconFile *icon_file)
+{
+ CajaFile *file;
+ GList list;
+
+ icon_file->details->link = NULL;
+
+ file = CAJA_FILE (icon_file);
+
+ /* 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.
+ */
+ caja_file_ref (file);
+
+ file->details->is_gone = TRUE;
+
+ list.data = file;
+ list.next = NULL;
+ list.prev = NULL;
+
+ caja_directory_remove_file (file->details->directory, file);
+ caja_directory_emit_change_signals (file->details->directory, &list);
+
+ caja_file_unref (file);
+}
+
+CajaDesktopIconFile *
+caja_desktop_icon_file_new (CajaDesktopLink *link)
+{
+ CajaFile *file;
+ CajaDirectory *directory;
+ CajaDesktopIconFile *icon_file;
+ GList list;
+ char *name;
+
+ directory = caja_directory_get_by_uri (EEL_DESKTOP_URI);
+
+ file = CAJA_FILE (g_object_new (CAJA_TYPE_DESKTOP_ICON_FILE, NULL));
+
+#ifdef CAJA_FILE_DEBUG_REF
+ printf("%10p ref'd\n", file);
+ eazel_dump_stack_trace ("\t", 10);
+#endif
+
+ file->details->directory = directory;
+
+ icon_file = CAJA_DESKTOP_ICON_FILE (file);
+ icon_file->details->link = link;
+
+ name = caja_desktop_link_get_file_name (link);
+ file->details->name = eel_ref_str_new (name);
+ g_free (name);
+
+ update_info_from_link (icon_file);
+
+ caja_desktop_update_metadata_from_mateconf (file, file->details->name);
+
+ caja_directory_add_file (directory, file);
+
+ list.data = file;
+ list.next = NULL;
+ list.prev = NULL;
+ caja_directory_emit_files_added (directory, &list);
+
+ return icon_file;
+}
+
+/* Note: This can return NULL if the link was recently removed (i.e. unmounted) */
+CajaDesktopLink *
+caja_desktop_icon_file_get_link (CajaDesktopIconFile *icon_file)
+{
+ if (icon_file->details->link)
+ return g_object_ref (icon_file->details->link);
+ else
+ return NULL;
+}
+
+static void
+caja_desktop_icon_file_unmount (CajaFile *file,
+ GMountOperation *mount_op,
+ GCancellable *cancellable,
+ CajaFileOperationCallback callback,
+ gpointer callback_data)
+{
+ CajaDesktopIconFile *desktop_file;
+ GMount *mount;
+
+ desktop_file = CAJA_DESKTOP_ICON_FILE (file);
+ if (desktop_file)
+ {
+ mount = caja_desktop_link_get_mount (desktop_file->details->link);
+ if (mount != NULL)
+ {
+ caja_file_operations_unmount_mount (NULL, mount, FALSE, TRUE);
+ }
+ }
+
+}
+
+static void
+caja_desktop_icon_file_eject (CajaFile *file,
+ GMountOperation *mount_op,
+ GCancellable *cancellable,
+ CajaFileOperationCallback callback,
+ gpointer callback_data)
+{
+ CajaDesktopIconFile *desktop_file;
+ GMount *mount;
+
+ desktop_file = CAJA_DESKTOP_ICON_FILE (file);
+ if (desktop_file)
+ {
+ mount = caja_desktop_link_get_mount (desktop_file->details->link);
+ if (mount != NULL)
+ {
+ caja_file_operations_unmount_mount (NULL, mount, TRUE, TRUE);
+ }
+ }
+}
+
+static void
+caja_desktop_icon_file_set_metadata (CajaFile *file,
+ const char *key,
+ const char *value)
+{
+ CajaDesktopIconFile *desktop_file;
+
+ desktop_file = CAJA_DESKTOP_ICON_FILE (file);
+ caja_desktop_set_metadata_string (file, file->details->name, key, value);
+}
+
+static void
+caja_desktop_icon_file_set_metadata_as_list (CajaFile *file,
+ const char *key,
+ char **value)
+{
+ CajaDesktopIconFile *desktop_file;
+
+ desktop_file = CAJA_DESKTOP_ICON_FILE (file);
+ caja_desktop_set_metadata_stringv (file, file->details->name, key, value);
+}
+
+static void
+caja_desktop_icon_file_class_init (CajaDesktopIconFileClass *klass)
+{
+ GObjectClass *object_class;
+ CajaFileClass *file_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ file_class = CAJA_FILE_CLASS (klass);
+
+ file_class->default_file_type = G_FILE_TYPE_DIRECTORY;
+
+ file_class->monitor_add = desktop_icon_file_monitor_add;
+ file_class->monitor_remove = desktop_icon_file_monitor_remove;
+ file_class->call_when_ready = desktop_icon_file_call_when_ready;
+ file_class->cancel_call_when_ready = desktop_icon_file_cancel_call_when_ready;
+ file_class->check_if_ready = desktop_icon_file_check_if_ready;
+ file_class->get_item_count = desktop_icon_file_get_item_count;
+ file_class->get_deep_counts = desktop_icon_file_get_deep_counts;
+ file_class->get_date = desktop_icon_file_get_date;
+ file_class->get_where_string = desktop_icon_file_get_where_string;
+ file_class->set_metadata = caja_desktop_icon_file_set_metadata;
+ file_class->set_metadata_as_list = caja_desktop_icon_file_set_metadata_as_list;
+ file_class->unmount = caja_desktop_icon_file_unmount;
+ file_class->eject = caja_desktop_icon_file_eject;
+
+ g_type_class_add_private (object_class, sizeof(CajaDesktopIconFileDetails));
+}
diff --git a/libcaja-private/caja-desktop-icon-file.h b/libcaja-private/caja-desktop-icon-file.h
new file mode 100644
index 00000000..bb0474e6
--- /dev/null
+++ b/libcaja-private/caja-desktop-icon-file.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-desktop-file.h: Subclass of CajaFile to implement the
+ the case of a desktop icon file
+
+ 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Alexander Larsson <[email protected]>
+*/
+
+#ifndef CAJA_DESKTOP_ICON_FILE_H
+#define CAJA_DESKTOP_ICON_FILE_H
+
+#include <libcaja-private/caja-file.h>
+#include <libcaja-private/caja-desktop-link.h>
+
+#define CAJA_TYPE_DESKTOP_ICON_FILE caja_desktop_icon_file_get_type()
+#define CAJA_DESKTOP_ICON_FILE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_DESKTOP_ICON_FILE, CajaDesktopIconFile))
+#define CAJA_DESKTOP_ICON_FILE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_DESKTOP_ICON_FILE, CajaDesktopIconFileClass))
+#define CAJA_IS_DESKTOP_ICON_FILE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_DESKTOP_ICON_FILE))
+#define CAJA_IS_DESKTOP_ICON_FILE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_DESKTOP_ICON_FILE))
+#define CAJA_DESKTOP_ICON_FILE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_DESKTOP_ICON_FILE, CajaDesktopIconFileClass))
+
+typedef struct CajaDesktopIconFileDetails CajaDesktopIconFileDetails;
+
+typedef struct
+{
+ CajaFile parent_slot;
+ CajaDesktopIconFileDetails *details;
+} CajaDesktopIconFile;
+
+typedef struct
+{
+ CajaFileClass parent_slot;
+} CajaDesktopIconFileClass;
+
+GType caja_desktop_icon_file_get_type (void);
+
+CajaDesktopIconFile *caja_desktop_icon_file_new (CajaDesktopLink *link);
+void caja_desktop_icon_file_update (CajaDesktopIconFile *icon_file);
+void caja_desktop_icon_file_remove (CajaDesktopIconFile *icon_file);
+CajaDesktopLink *caja_desktop_icon_file_get_link (CajaDesktopIconFile *icon_file);
+
+#endif /* CAJA_DESKTOP_ICON_FILE_H */
diff --git a/libcaja-private/caja-desktop-link-monitor.c b/libcaja-private/caja-desktop-link-monitor.c
new file mode 100644
index 00000000..a88a32ea
--- /dev/null
+++ b/libcaja-private/caja-desktop-link-monitor.c
@@ -0,0 +1,575 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-desktop-link-monitor.c: singleton thatn manages the links
+
+ 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Alexander Larsson <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-desktop-link-monitor.h"
+#include "caja-desktop-link.h"
+#include "caja-desktop-icon-file.h"
+#include "caja-directory.h"
+#include "caja-desktop-directory.h"
+#include "caja-global-preferences.h"
+
+#include <eel/eel-debug.h>
+#include <eel/eel-gtk-macros.h>
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-vfs-extensions.h>
+#include <eel/eel-stock-dialogs.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <libcaja-private/caja-trash-monitor.h>
+#include <string.h>
+
+struct CajaDesktopLinkMonitorDetails
+{
+ GVolumeMonitor *volume_monitor;
+ CajaDirectory *desktop_dir;
+
+ CajaDesktopLink *home_link;
+ CajaDesktopLink *computer_link;
+ CajaDesktopLink *trash_link;
+ CajaDesktopLink *network_link;
+
+ gulong mount_id;
+ gulong unmount_id;
+ gulong changed_id;
+
+ GList *mount_links;
+};
+
+
+static void caja_desktop_link_monitor_init (gpointer object,
+ gpointer klass);
+static void caja_desktop_link_monitor_class_init (gpointer klass);
+
+EEL_CLASS_BOILERPLATE (CajaDesktopLinkMonitor,
+ caja_desktop_link_monitor,
+ G_TYPE_OBJECT)
+
+static CajaDesktopLinkMonitor *the_link_monitor = NULL;
+
+static void
+destroy_desktop_link_monitor (void)
+{
+ if (the_link_monitor != NULL)
+ {
+ g_object_unref (the_link_monitor);
+ }
+}
+
+CajaDesktopLinkMonitor *
+caja_desktop_link_monitor_get (void)
+{
+ if (the_link_monitor == NULL)
+ {
+ g_object_new (CAJA_TYPE_DESKTOP_LINK_MONITOR, NULL);
+ eel_debug_call_at_shutdown (destroy_desktop_link_monitor);
+ }
+ return the_link_monitor;
+}
+
+static void
+volume_delete_dialog (GtkWidget *parent_view,
+ CajaDesktopLink *link)
+{
+ GMount *mount;
+ char *dialog_str;
+ char *display_name;
+
+ mount = caja_desktop_link_get_mount (link);
+
+ if (mount != NULL)
+ {
+ display_name = caja_desktop_link_get_display_name (link);
+ dialog_str = g_strdup_printf (_("You cannot move the volume \"%s\" to the trash."),
+ display_name);
+ g_free (display_name);
+
+ if (g_mount_can_eject (mount))
+ {
+ eel_run_simple_dialog
+ (parent_view,
+ FALSE,
+ GTK_MESSAGE_ERROR,
+ dialog_str,
+ _("If you want to eject the volume, please use \"Eject\" in the "
+ "popup menu of the volume."),
+ GTK_STOCK_OK, NULL);
+ }
+ else
+ {
+ eel_run_simple_dialog
+ (parent_view,
+ FALSE,
+ GTK_MESSAGE_ERROR,
+ dialog_str,
+ _("If you want to unmount the volume, please use \"Unmount Volume\" in the "
+ "popup menu of the volume."),
+ GTK_STOCK_OK, NULL);
+ }
+
+ g_object_unref (mount);
+ g_free (dialog_str);
+ }
+}
+
+void
+caja_desktop_link_monitor_delete_link (CajaDesktopLinkMonitor *monitor,
+ CajaDesktopLink *link,
+ GtkWidget *parent_view)
+{
+ switch (caja_desktop_link_get_link_type (link))
+ {
+ case CAJA_DESKTOP_LINK_HOME:
+ case CAJA_DESKTOP_LINK_COMPUTER:
+ case CAJA_DESKTOP_LINK_TRASH:
+ case CAJA_DESKTOP_LINK_NETWORK:
+ /* just ignore. We don't allow you to delete these */
+ break;
+ default:
+ volume_delete_dialog (parent_view, link);
+ break;
+ }
+}
+
+static gboolean
+volume_file_name_used (CajaDesktopLinkMonitor *monitor,
+ const char *name)
+{
+ GList *l;
+ char *other_name;
+ gboolean same;
+
+ for (l = monitor->details->mount_links; l != NULL; l = l->next)
+ {
+ other_name = caja_desktop_link_get_file_name (l->data);
+ same = strcmp (name, other_name) == 0;
+ g_free (other_name);
+
+ if (same)
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+char *
+caja_desktop_link_monitor_make_filename_unique (CajaDesktopLinkMonitor *monitor,
+ const char *filename)
+{
+ char *unique_name;
+ int i;
+
+ i = 2;
+ unique_name = g_strdup (filename);
+ while (volume_file_name_used (monitor, unique_name))
+ {
+ g_free (unique_name);
+ unique_name = g_strdup_printf ("%s.%d", filename, i++);
+ }
+ return unique_name;
+}
+
+static gboolean
+has_mount (CajaDesktopLinkMonitor *monitor,
+ GMount *mount)
+{
+ gboolean ret;
+ GMount *other_mount;
+ GList *l;
+
+ ret = FALSE;
+
+ for (l = monitor->details->mount_links; l != NULL; l = l->next)
+ {
+ other_mount = caja_desktop_link_get_mount (l->data);
+ if (mount == other_mount)
+ {
+ g_object_unref (other_mount);
+ ret = TRUE;
+ break;
+ }
+ g_object_unref (other_mount);
+ }
+
+ return ret;
+}
+
+static void
+create_mount_link (CajaDesktopLinkMonitor *monitor,
+ GMount *mount)
+{
+ CajaDesktopLink *link;
+
+ if (has_mount (monitor, mount))
+ return;
+
+ if ((!g_mount_is_shadowed (mount)) &&
+ eel_preferences_get_boolean (CAJA_PREFERENCES_DESKTOP_VOLUMES_VISIBLE))
+ {
+ link = caja_desktop_link_new_from_mount (mount);
+ monitor->details->mount_links = g_list_prepend (monitor->details->mount_links, link);
+ }
+}
+
+static void
+remove_mount_link (CajaDesktopLinkMonitor *monitor,
+ GMount *mount)
+{
+ GList *l;
+ CajaDesktopLink *link;
+ GMount *other_mount;
+
+ link = NULL;
+ for (l = monitor->details->mount_links; l != NULL; l = l->next)
+ {
+ other_mount = caja_desktop_link_get_mount (l->data);
+ if (mount == other_mount)
+ {
+ g_object_unref (other_mount);
+ link = l->data;
+ break;
+ }
+ g_object_unref (other_mount);
+ }
+
+ if (link)
+ {
+ monitor->details->mount_links = g_list_remove (monitor->details->mount_links, link);
+ g_object_unref (link);
+ }
+}
+
+
+
+static void
+mount_added_callback (GVolumeMonitor *volume_monitor,
+ GMount *mount,
+ CajaDesktopLinkMonitor *monitor)
+{
+ create_mount_link (monitor, mount);
+}
+
+
+static void
+mount_removed_callback (GVolumeMonitor *volume_monitor,
+ GMount *mount,
+ CajaDesktopLinkMonitor *monitor)
+{
+ remove_mount_link (monitor, mount);
+}
+
+static void
+mount_changed_callback (GVolumeMonitor *volume_monitor,
+ GMount *mount,
+ CajaDesktopLinkMonitor *monitor)
+{
+ /* TODO: update the mount with other details */
+
+ /* remove a mount if it goes into the shadows */
+ if (g_mount_is_shadowed (mount) && has_mount (monitor, mount))
+ {
+ remove_mount_link (monitor, mount);
+ }
+}
+
+static void
+update_link_visibility (CajaDesktopLinkMonitor *monitor,
+ CajaDesktopLink **link_ref,
+ CajaDesktopLinkType link_type,
+ const char *preference_key)
+{
+ if (eel_preferences_get_boolean (preference_key))
+ {
+ if (*link_ref == NULL)
+ {
+ *link_ref = caja_desktop_link_new (link_type);
+ }
+ }
+ else
+ {
+ if (*link_ref != NULL)
+ {
+ g_object_unref (*link_ref);
+ *link_ref = NULL;
+ }
+ }
+}
+
+static void
+desktop_home_visible_changed (gpointer callback_data)
+{
+ CajaDesktopLinkMonitor *monitor;
+
+ monitor = CAJA_DESKTOP_LINK_MONITOR (callback_data);
+
+ update_link_visibility (CAJA_DESKTOP_LINK_MONITOR (monitor),
+ &monitor->details->home_link,
+ CAJA_DESKTOP_LINK_HOME,
+ CAJA_PREFERENCES_DESKTOP_HOME_VISIBLE);
+}
+
+static void
+desktop_computer_visible_changed (gpointer callback_data)
+{
+ CajaDesktopLinkMonitor *monitor;
+
+ monitor = CAJA_DESKTOP_LINK_MONITOR (callback_data);
+
+ update_link_visibility (CAJA_DESKTOP_LINK_MONITOR (callback_data),
+ &monitor->details->computer_link,
+ CAJA_DESKTOP_LINK_COMPUTER,
+ CAJA_PREFERENCES_DESKTOP_COMPUTER_VISIBLE);
+}
+
+static void
+desktop_trash_visible_changed (gpointer callback_data)
+{
+ CajaDesktopLinkMonitor *monitor;
+
+ monitor = CAJA_DESKTOP_LINK_MONITOR (callback_data);
+
+ update_link_visibility (CAJA_DESKTOP_LINK_MONITOR (callback_data),
+ &monitor->details->trash_link,
+ CAJA_DESKTOP_LINK_TRASH,
+ CAJA_PREFERENCES_DESKTOP_TRASH_VISIBLE);
+}
+
+static void
+desktop_network_visible_changed (gpointer callback_data)
+{
+ CajaDesktopLinkMonitor *monitor;
+
+ monitor = CAJA_DESKTOP_LINK_MONITOR (callback_data);
+
+ update_link_visibility (CAJA_DESKTOP_LINK_MONITOR (callback_data),
+ &monitor->details->network_link,
+ CAJA_DESKTOP_LINK_NETWORK,
+ CAJA_PREFERENCES_DESKTOP_NETWORK_VISIBLE);
+}
+
+static void
+desktop_volumes_visible_changed (gpointer callback_data)
+{
+ CajaDesktopLinkMonitor *monitor;
+ GList *l, *mounts;
+
+ monitor = CAJA_DESKTOP_LINK_MONITOR (callback_data);
+
+ if (eel_preferences_get_boolean (CAJA_PREFERENCES_DESKTOP_VOLUMES_VISIBLE))
+ {
+ if (monitor->details->mount_links == NULL)
+ {
+ mounts = g_volume_monitor_get_mounts (monitor->details->volume_monitor);
+ for (l = mounts; l != NULL; l = l->next)
+ {
+ create_mount_link (monitor, l->data);
+ g_object_unref (l->data);
+ }
+ g_list_free (mounts);
+ }
+ }
+ else
+ {
+ g_list_foreach (monitor->details->mount_links, (GFunc)g_object_unref, NULL);
+ g_list_free (monitor->details->mount_links);
+ monitor->details->mount_links = NULL;
+ }
+}
+
+static void
+create_link_and_add_preference (CajaDesktopLink **link_ref,
+ CajaDesktopLinkType link_type,
+ const char *preference_key,
+ EelPreferencesCallback callback,
+ gpointer callback_data)
+{
+ if (eel_preferences_get_boolean (preference_key))
+ {
+ *link_ref = caja_desktop_link_new (link_type);
+ }
+
+ eel_preferences_add_callback (preference_key, callback, callback_data);
+}
+
+static void
+caja_desktop_link_monitor_init (gpointer object, gpointer klass)
+{
+ CajaDesktopLinkMonitor *monitor;
+ GList *l, *mounts;
+ GMount *mount;
+
+ monitor = CAJA_DESKTOP_LINK_MONITOR (object);
+
+ the_link_monitor = monitor;
+
+ monitor->details = g_new0 (CajaDesktopLinkMonitorDetails, 1);
+
+ monitor->details->volume_monitor = g_volume_monitor_get ();
+
+ /* We keep around a ref to the desktop dir */
+ monitor->details->desktop_dir = caja_directory_get_by_uri (EEL_DESKTOP_URI);
+
+ /* Default links */
+
+ create_link_and_add_preference (&monitor->details->home_link,
+ CAJA_DESKTOP_LINK_HOME,
+ CAJA_PREFERENCES_DESKTOP_HOME_VISIBLE,
+ desktop_home_visible_changed,
+ monitor);
+
+ create_link_and_add_preference (&monitor->details->computer_link,
+ CAJA_DESKTOP_LINK_COMPUTER,
+ CAJA_PREFERENCES_DESKTOP_COMPUTER_VISIBLE,
+ desktop_computer_visible_changed,
+ monitor);
+
+ create_link_and_add_preference (&monitor->details->trash_link,
+ CAJA_DESKTOP_LINK_TRASH,
+ CAJA_PREFERENCES_DESKTOP_TRASH_VISIBLE,
+ desktop_trash_visible_changed,
+ monitor);
+
+ create_link_and_add_preference (&monitor->details->network_link,
+ CAJA_DESKTOP_LINK_NETWORK,
+ CAJA_PREFERENCES_DESKTOP_NETWORK_VISIBLE,
+ desktop_network_visible_changed,
+ monitor);
+
+ /* Mount links */
+
+ mounts = g_volume_monitor_get_mounts (monitor->details->volume_monitor);
+ for (l = mounts; l != NULL; l = l->next)
+ {
+ mount = l->data;
+ create_mount_link (monitor, mount);
+ g_object_unref (mount);
+ }
+ g_list_free (mounts);
+
+ eel_preferences_add_callback (CAJA_PREFERENCES_DESKTOP_VOLUMES_VISIBLE,
+ desktop_volumes_visible_changed,
+ monitor);
+
+ monitor->details->mount_id =
+ g_signal_connect_object (monitor->details->volume_monitor, "mount_added",
+ G_CALLBACK (mount_added_callback), monitor, 0);
+ monitor->details->unmount_id =
+ g_signal_connect_object (monitor->details->volume_monitor, "mount_removed",
+ G_CALLBACK (mount_removed_callback), monitor, 0);
+ monitor->details->changed_id =
+ g_signal_connect_object (monitor->details->volume_monitor, "mount_changed",
+ G_CALLBACK (mount_changed_callback), monitor, 0);
+
+}
+
+static void
+remove_link_and_preference (CajaDesktopLink **link_ref,
+ const char *preference_key,
+ EelPreferencesCallback callback,
+ gpointer callback_data)
+{
+ if (*link_ref != NULL)
+ {
+ g_object_unref (*link_ref);
+ *link_ref = NULL;
+ }
+
+ eel_preferences_remove_callback (preference_key, callback, callback_data);
+}
+
+static void
+desktop_link_monitor_finalize (GObject *object)
+{
+ CajaDesktopLinkMonitor *monitor;
+
+ monitor = CAJA_DESKTOP_LINK_MONITOR (object);
+
+ g_object_unref (monitor->details->volume_monitor);
+
+ /* Default links */
+
+ remove_link_and_preference (&monitor->details->home_link,
+ CAJA_PREFERENCES_DESKTOP_HOME_VISIBLE,
+ desktop_home_visible_changed,
+ monitor);
+
+ remove_link_and_preference (&monitor->details->computer_link,
+ CAJA_PREFERENCES_DESKTOP_COMPUTER_VISIBLE,
+ desktop_computer_visible_changed,
+ monitor);
+
+ remove_link_and_preference (&monitor->details->trash_link,
+ CAJA_PREFERENCES_DESKTOP_TRASH_VISIBLE,
+ desktop_trash_visible_changed,
+ monitor);
+
+ remove_link_and_preference (&monitor->details->network_link,
+ CAJA_PREFERENCES_DESKTOP_NETWORK_VISIBLE,
+ desktop_network_visible_changed,
+ monitor);
+
+ /* Mounts */
+
+ g_list_foreach (monitor->details->mount_links, (GFunc)g_object_unref, NULL);
+ g_list_free (monitor->details->mount_links);
+ monitor->details->mount_links = NULL;
+
+ caja_directory_unref (monitor->details->desktop_dir);
+ monitor->details->desktop_dir = NULL;
+
+ eel_preferences_remove_callback (CAJA_PREFERENCES_DESKTOP_VOLUMES_VISIBLE,
+ desktop_volumes_visible_changed,
+ monitor);
+
+ if (monitor->details->mount_id != 0)
+ {
+ g_source_remove (monitor->details->mount_id);
+ }
+ if (monitor->details->unmount_id != 0)
+ {
+ g_source_remove (monitor->details->unmount_id);
+ }
+ if (monitor->details->changed_id != 0)
+ {
+ g_source_remove (monitor->details->changed_id);
+ }
+
+ g_free (monitor->details);
+
+ EEL_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
+}
+
+static void
+caja_desktop_link_monitor_class_init (gpointer klass)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = desktop_link_monitor_finalize;
+
+}
diff --git a/libcaja-private/caja-desktop-link-monitor.h b/libcaja-private/caja-desktop-link-monitor.h
new file mode 100644
index 00000000..e05599c3
--- /dev/null
+++ b/libcaja-private/caja-desktop-link-monitor.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-desktop-link-monitor.h: singleton that manages the desktop links
+
+ 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Alexander Larsson <[email protected]>
+*/
+
+#ifndef CAJA_DESKTOP_LINK_MONITOR_H
+#define CAJA_DESKTOP_LINK_MONITOR_H
+
+#include <gtk/gtk.h>
+#include <libcaja-private/caja-desktop-link.h>
+
+#define CAJA_TYPE_DESKTOP_LINK_MONITOR caja_desktop_link_monitor_get_type()
+#define CAJA_DESKTOP_LINK_MONITOR(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_DESKTOP_LINK_MONITOR, CajaDesktopLinkMonitor))
+#define CAJA_DESKTOP_LINK_MONITOR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_DESKTOP_LINK_MONITOR, CajaDesktopLinkMonitorClass))
+#define CAJA_IS_DESKTOP_LINK_MONITOR(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_DESKTOP_LINK_MONITOR))
+#define CAJA_IS_DESKTOP_LINK_MONITOR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_DESKTOP_LINK_MONITOR))
+#define CAJA_DESKTOP_LINK_MONITOR_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_DESKTOP_LINK_MONITOR, CajaDesktopLinkMonitorClass))
+
+typedef struct CajaDesktopLinkMonitorDetails CajaDesktopLinkMonitorDetails;
+
+typedef struct
+{
+ GObject parent_slot;
+ CajaDesktopLinkMonitorDetails *details;
+} CajaDesktopLinkMonitor;
+
+typedef struct
+{
+ GObjectClass parent_slot;
+} CajaDesktopLinkMonitorClass;
+
+GType caja_desktop_link_monitor_get_type (void);
+
+CajaDesktopLinkMonitor * caja_desktop_link_monitor_get (void);
+void caja_desktop_link_monitor_delete_link (CajaDesktopLinkMonitor *monitor,
+ CajaDesktopLink *link,
+ GtkWidget *parent_view);
+
+/* Used by caja-desktop-link.c */
+char * caja_desktop_link_monitor_make_filename_unique (CajaDesktopLinkMonitor *monitor,
+ const char *filename);
+
+#endif /* CAJA_DESKTOP_LINK_MONITOR_H */
diff --git a/libcaja-private/caja-desktop-link.c b/libcaja-private/caja-desktop-link.c
new file mode 100644
index 00000000..e632a5b0
--- /dev/null
+++ b/libcaja-private/caja-desktop-link.c
@@ -0,0 +1,479 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-desktop-link.c: Class that handles the links on the desktop
+
+ 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Alexander Larsson <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-desktop-link.h"
+#include "caja-desktop-link-monitor.h"
+#include "caja-desktop-icon-file.h"
+#include "caja-directory-private.h"
+#include "caja-desktop-directory.h"
+#include "caja-icon-names.h"
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <libcaja-private/caja-file-utilities.h>
+#include <libcaja-private/caja-trash-monitor.h>
+#include <libcaja-private/caja-global-preferences.h>
+#include <string.h>
+
+struct CajaDesktopLinkDetails
+{
+ CajaDesktopLinkType type;
+ char *filename;
+ char *display_name;
+ GFile *activation_location;
+ GIcon *icon;
+
+ CajaDesktopIconFile *icon_file;
+
+ GObject *signal_handler_obj;
+ gulong signal_handler;
+
+ /* Just for mount icons: */
+ GMount *mount;
+};
+
+G_DEFINE_TYPE(CajaDesktopLink, caja_desktop_link, G_TYPE_OBJECT)
+
+static void
+create_icon_file (CajaDesktopLink *link)
+{
+ link->details->icon_file = caja_desktop_icon_file_new (link);
+}
+
+static void
+caja_desktop_link_changed (CajaDesktopLink *link)
+{
+ if (link->details->icon_file != NULL)
+ {
+ caja_desktop_icon_file_update (link->details->icon_file);
+ }
+}
+
+static void
+mount_changed_callback (GMount *mount, CajaDesktopLink *link)
+{
+ g_free (link->details->display_name);
+ if (link->details->activation_location)
+ {
+ g_object_unref (link->details->activation_location);
+ }
+ if (link->details->icon)
+ {
+ g_object_unref (link->details->icon);
+ }
+
+ link->details->display_name = g_mount_get_name (mount);
+ link->details->activation_location = g_mount_get_default_location (mount);
+ link->details->icon = g_mount_get_icon (mount);
+
+ caja_desktop_link_changed (link);
+}
+
+static void
+trash_state_changed_callback (CajaTrashMonitor *trash_monitor,
+ gboolean state,
+ gpointer callback_data)
+{
+ CajaDesktopLink *link;
+
+ link = CAJA_DESKTOP_LINK (callback_data);
+ g_assert (link->details->type == CAJA_DESKTOP_LINK_TRASH);
+
+ if (link->details->icon)
+ {
+ g_object_unref (link->details->icon);
+ }
+ link->details->icon = caja_trash_monitor_get_icon ();
+
+ caja_desktop_link_changed (link);
+}
+
+static void
+home_name_changed (gpointer callback_data)
+{
+ CajaDesktopLink *link;
+
+ link = CAJA_DESKTOP_LINK (callback_data);
+ g_assert (link->details->type == CAJA_DESKTOP_LINK_HOME);
+
+ g_free (link->details->display_name);
+ link->details->display_name = eel_preferences_get (CAJA_PREFERENCES_DESKTOP_HOME_NAME);
+
+ caja_desktop_link_changed (link);
+}
+
+static void
+computer_name_changed (gpointer callback_data)
+{
+ CajaDesktopLink *link;
+
+ link = CAJA_DESKTOP_LINK (callback_data);
+ g_assert (link->details->type == CAJA_DESKTOP_LINK_COMPUTER);
+
+ g_free (link->details->display_name);
+ link->details->display_name = eel_preferences_get (CAJA_PREFERENCES_DESKTOP_COMPUTER_NAME);
+
+ caja_desktop_link_changed (link);
+}
+
+static void
+trash_name_changed (gpointer callback_data)
+{
+ CajaDesktopLink *link;
+
+ link = CAJA_DESKTOP_LINK (callback_data);
+ g_assert (link->details->type == CAJA_DESKTOP_LINK_TRASH);
+
+ g_free (link->details->display_name);
+ link->details->display_name = eel_preferences_get (CAJA_PREFERENCES_DESKTOP_TRASH_NAME);
+ caja_desktop_link_changed (link);
+}
+
+static void
+network_name_changed (gpointer callback_data)
+{
+ CajaDesktopLink *link;
+
+ link = CAJA_DESKTOP_LINK (callback_data);
+ g_assert (link->details->type == CAJA_DESKTOP_LINK_NETWORK);
+
+ g_free (link->details->display_name);
+ link->details->display_name = eel_preferences_get (CAJA_PREFERENCES_DESKTOP_NETWORK_NAME);
+ caja_desktop_link_changed (link);
+}
+
+CajaDesktopLink *
+caja_desktop_link_new (CajaDesktopLinkType type)
+{
+ CajaDesktopLink *link;
+
+ link = CAJA_DESKTOP_LINK (g_object_new (CAJA_TYPE_DESKTOP_LINK, NULL));
+
+ link->details->type = type;
+ switch (type)
+ {
+ case CAJA_DESKTOP_LINK_HOME:
+ link->details->filename = g_strdup ("home");
+ link->details->display_name = eel_preferences_get (CAJA_PREFERENCES_DESKTOP_HOME_NAME);
+ link->details->activation_location = g_file_new_for_path (g_get_home_dir ());
+ link->details->icon = g_themed_icon_new (CAJA_ICON_HOME);
+
+ eel_preferences_add_callback (CAJA_PREFERENCES_DESKTOP_HOME_NAME,
+ home_name_changed,
+ link);
+
+ break;
+
+ case CAJA_DESKTOP_LINK_COMPUTER:
+ link->details->filename = g_strdup ("computer");
+ link->details->display_name = eel_preferences_get (CAJA_PREFERENCES_DESKTOP_COMPUTER_NAME);
+ link->details->activation_location = g_file_new_for_uri ("computer:///");
+ /* TODO: This might need a different icon: */
+ link->details->icon = g_themed_icon_new (CAJA_ICON_COMPUTER);
+
+ eel_preferences_add_callback (CAJA_PREFERENCES_DESKTOP_COMPUTER_NAME,
+ computer_name_changed,
+ link);
+
+ break;
+
+ case CAJA_DESKTOP_LINK_TRASH:
+ link->details->filename = g_strdup ("trash");
+ link->details->display_name = eel_preferences_get (CAJA_PREFERENCES_DESKTOP_TRASH_NAME);
+ link->details->activation_location = g_file_new_for_uri (EEL_TRASH_URI);
+ link->details->icon = caja_trash_monitor_get_icon ();
+
+ eel_preferences_add_callback (CAJA_PREFERENCES_DESKTOP_TRASH_NAME,
+ trash_name_changed,
+ link);
+ link->details->signal_handler_obj = G_OBJECT (caja_trash_monitor_get ());
+ link->details->signal_handler =
+ g_signal_connect_object (caja_trash_monitor_get (), "trash_state_changed",
+ G_CALLBACK (trash_state_changed_callback), link, 0);
+ break;
+
+ case CAJA_DESKTOP_LINK_NETWORK:
+ link->details->filename = g_strdup ("network");
+ link->details->display_name = eel_preferences_get (CAJA_PREFERENCES_DESKTOP_NETWORK_NAME);
+ link->details->activation_location = g_file_new_for_uri ("network:///");
+ link->details->icon = g_themed_icon_new (CAJA_ICON_NETWORK);
+
+ eel_preferences_add_callback (CAJA_PREFERENCES_DESKTOP_NETWORK_NAME,
+ network_name_changed,
+ link);
+ break;
+
+ default:
+ case CAJA_DESKTOP_LINK_MOUNT:
+ g_assert_not_reached();
+ }
+
+ create_icon_file (link);
+
+ return link;
+}
+
+CajaDesktopLink *
+caja_desktop_link_new_from_mount (GMount *mount)
+{
+ CajaDesktopLink *link;
+ GVolume *volume;
+ char *name, *filename;
+
+ link = CAJA_DESKTOP_LINK (g_object_new (CAJA_TYPE_DESKTOP_LINK, NULL));
+
+ link->details->type = CAJA_DESKTOP_LINK_MOUNT;
+
+ link->details->mount = g_object_ref (mount);
+
+ /* We try to use the drive name to get somewhat stable filenames
+ for metadata */
+ volume = g_mount_get_volume (mount);
+ if (volume != NULL)
+ {
+ name = g_volume_get_name (volume);
+ g_object_unref (volume);
+ }
+ else
+ {
+ name = g_mount_get_name (mount);
+ }
+
+ /* Replace slashes in name */
+ filename = g_strconcat (g_strdelimit (name, "/", '-'), ".volume", NULL);
+ link->details->filename =
+ caja_desktop_link_monitor_make_filename_unique (caja_desktop_link_monitor_get (),
+ filename);
+ g_free (filename);
+ g_free (name);
+
+ link->details->display_name = g_mount_get_name (mount);
+
+ link->details->activation_location = g_mount_get_default_location (mount);
+ link->details->icon = g_mount_get_icon (mount);
+
+ link->details->signal_handler_obj = G_OBJECT (mount);
+ link->details->signal_handler =
+ g_signal_connect (mount, "changed",
+ G_CALLBACK (mount_changed_callback), link);
+
+ create_icon_file (link);
+
+ return link;
+}
+
+GMount *
+caja_desktop_link_get_mount (CajaDesktopLink *link)
+{
+ if (link->details->mount)
+ {
+ return g_object_ref (link->details->mount);
+ }
+ return NULL;
+}
+
+CajaDesktopLinkType
+caja_desktop_link_get_link_type (CajaDesktopLink *link)
+{
+ return link->details->type;
+}
+
+char *
+caja_desktop_link_get_file_name (CajaDesktopLink *link)
+{
+ return g_strdup (link->details->filename);
+}
+
+char *
+caja_desktop_link_get_display_name (CajaDesktopLink *link)
+{
+ return g_strdup (link->details->display_name);
+}
+
+GIcon *
+caja_desktop_link_get_icon (CajaDesktopLink *link)
+{
+ if (link->details->icon != NULL)
+ {
+ return g_object_ref (link->details->icon);
+ }
+ return NULL;
+}
+
+GFile *
+caja_desktop_link_get_activation_location (CajaDesktopLink *link)
+{
+ if (link->details->activation_location)
+ {
+ return g_object_ref (link->details->activation_location);
+ }
+ return NULL;
+}
+
+char *
+caja_desktop_link_get_activation_uri (CajaDesktopLink *link)
+{
+ if (link->details->activation_location)
+ {
+ return g_file_get_uri (link->details->activation_location);
+ }
+ return NULL;
+}
+
+
+gboolean
+caja_desktop_link_get_date (CajaDesktopLink *link,
+ CajaDateType date_type,
+ time_t *date)
+{
+ return FALSE;
+}
+
+gboolean
+caja_desktop_link_can_rename (CajaDesktopLink *link)
+{
+ return (link->details->type == CAJA_DESKTOP_LINK_HOME ||
+ link->details->type == CAJA_DESKTOP_LINK_TRASH ||
+ link->details->type == CAJA_DESKTOP_LINK_NETWORK ||
+ link->details->type == CAJA_DESKTOP_LINK_COMPUTER);
+}
+
+gboolean
+caja_desktop_link_rename (CajaDesktopLink *link,
+ const char *name)
+{
+ switch (link->details->type)
+ {
+ case CAJA_DESKTOP_LINK_HOME:
+ eel_preferences_set (CAJA_PREFERENCES_DESKTOP_HOME_NAME,
+ name);
+ break;
+ case CAJA_DESKTOP_LINK_COMPUTER:
+ eel_preferences_set (CAJA_PREFERENCES_DESKTOP_COMPUTER_NAME,
+ name);
+ break;
+ case CAJA_DESKTOP_LINK_TRASH:
+ eel_preferences_set (CAJA_PREFERENCES_DESKTOP_TRASH_NAME,
+ name);
+ break;
+ case CAJA_DESKTOP_LINK_NETWORK:
+ eel_preferences_set (CAJA_PREFERENCES_DESKTOP_NETWORK_NAME,
+ name);
+ break;
+ default:
+ g_assert_not_reached ();
+ /* FIXME: Do we want volume renaming?
+ * We didn't support that before. */
+ break;
+ }
+
+ return TRUE;
+}
+
+static void
+caja_desktop_link_init (CajaDesktopLink *link)
+{
+ link->details = G_TYPE_INSTANCE_GET_PRIVATE (link,
+ CAJA_TYPE_DESKTOP_LINK,
+ CajaDesktopLinkDetails);
+}
+
+static void
+desktop_link_finalize (GObject *object)
+{
+ CajaDesktopLink *link;
+
+ link = CAJA_DESKTOP_LINK (object);
+
+ if (link->details->signal_handler != 0)
+ {
+ g_signal_handler_disconnect (link->details->signal_handler_obj,
+ link->details->signal_handler);
+ }
+
+ if (link->details->icon_file != NULL)
+ {
+ caja_desktop_icon_file_remove (link->details->icon_file);
+ caja_file_unref (CAJA_FILE (link->details->icon_file));
+ link->details->icon_file = NULL;
+ }
+
+ if (link->details->type == CAJA_DESKTOP_LINK_HOME)
+ {
+ eel_preferences_remove_callback (CAJA_PREFERENCES_DESKTOP_HOME_NAME,
+ home_name_changed,
+ link);
+ }
+
+ if (link->details->type == CAJA_DESKTOP_LINK_COMPUTER)
+ {
+ eel_preferences_remove_callback (CAJA_PREFERENCES_DESKTOP_COMPUTER_NAME,
+ computer_name_changed,
+ link);
+ }
+
+ if (link->details->type == CAJA_DESKTOP_LINK_TRASH)
+ {
+ eel_preferences_remove_callback (CAJA_PREFERENCES_DESKTOP_TRASH_NAME,
+ trash_name_changed,
+ link);
+ }
+
+ if (link->details->type == CAJA_DESKTOP_LINK_NETWORK)
+ {
+ eel_preferences_remove_callback (CAJA_PREFERENCES_DESKTOP_NETWORK_NAME,
+ network_name_changed,
+ link);
+ }
+
+ if (link->details->type == CAJA_DESKTOP_LINK_MOUNT)
+ {
+ g_object_unref (link->details->mount);
+ }
+
+ g_free (link->details->filename);
+ g_free (link->details->display_name);
+ if (link->details->activation_location)
+ {
+ g_object_unref (link->details->activation_location);
+ }
+ if (link->details->icon)
+ {
+ g_object_unref (link->details->icon);
+ }
+
+ G_OBJECT_CLASS (caja_desktop_link_parent_class)->finalize (object);
+}
+
+static void
+caja_desktop_link_class_init (CajaDesktopLinkClass *klass)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = desktop_link_finalize;
+
+ g_type_class_add_private (object_class, sizeof(CajaDesktopLinkDetails));
+}
diff --git a/libcaja-private/caja-desktop-link.h b/libcaja-private/caja-desktop-link.h
new file mode 100644
index 00000000..001b2fcf
--- /dev/null
+++ b/libcaja-private/caja-desktop-link.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-desktop-link.h: Class that handles the links on the desktop
+
+ 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Alexander Larsson <[email protected]>
+*/
+
+#ifndef CAJA_DESKTOP_LINK_H
+#define CAJA_DESKTOP_LINK_H
+
+#include <libcaja-private/caja-file.h>
+#include <gio/gio.h>
+
+#define CAJA_TYPE_DESKTOP_LINK caja_desktop_link_get_type()
+#define CAJA_DESKTOP_LINK(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_DESKTOP_LINK, CajaDesktopLink))
+#define CAJA_DESKTOP_LINK_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_DESKTOP_LINK, CajaDesktopLinkClass))
+#define CAJA_IS_DESKTOP_LINK(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_DESKTOP_LINK))
+#define CAJA_IS_DESKTOP_LINK_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_DESKTOP_LINK))
+#define CAJA_DESKTOP_LINK_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_DESKTOP_LINK, CajaDesktopLinkClass))
+
+typedef struct CajaDesktopLinkDetails CajaDesktopLinkDetails;
+
+typedef struct
+{
+ GObject parent_slot;
+ CajaDesktopLinkDetails *details;
+} CajaDesktopLink;
+
+typedef struct
+{
+ GObjectClass parent_slot;
+} CajaDesktopLinkClass;
+
+typedef enum
+{
+ CAJA_DESKTOP_LINK_HOME,
+ CAJA_DESKTOP_LINK_COMPUTER,
+ CAJA_DESKTOP_LINK_TRASH,
+ CAJA_DESKTOP_LINK_MOUNT,
+ CAJA_DESKTOP_LINK_NETWORK
+} CajaDesktopLinkType;
+
+GType caja_desktop_link_get_type (void);
+
+CajaDesktopLink * caja_desktop_link_new (CajaDesktopLinkType type);
+CajaDesktopLink * caja_desktop_link_new_from_mount (GMount *mount);
+CajaDesktopLinkType caja_desktop_link_get_link_type (CajaDesktopLink *link);
+char * caja_desktop_link_get_file_name (CajaDesktopLink *link);
+char * caja_desktop_link_get_display_name (CajaDesktopLink *link);
+GIcon * caja_desktop_link_get_icon (CajaDesktopLink *link);
+GFile * caja_desktop_link_get_activation_location (CajaDesktopLink *link);
+char * caja_desktop_link_get_activation_uri (CajaDesktopLink *link);
+gboolean caja_desktop_link_get_date (CajaDesktopLink *link,
+ CajaDateType date_type,
+ time_t *date);
+GMount * caja_desktop_link_get_mount (CajaDesktopLink *link);
+gboolean caja_desktop_link_can_rename (CajaDesktopLink *link);
+gboolean caja_desktop_link_rename (CajaDesktopLink *link,
+ const char *name);
+
+
+#endif /* CAJA_DESKTOP_LINK_H */
diff --git a/libcaja-private/caja-directory-async.c b/libcaja-private/caja-directory-async.c
new file mode 100644
index 00000000..4451efc1
--- /dev/null
+++ b/libcaja-private/caja-directory-async.c
@@ -0,0 +1,5410 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-directory-async.c: Caja 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Darin Adler <[email protected]>
+*/
+
+#include <config.h>
+
+#include "caja-directory-notify.h"
+#include "caja-directory-private.h"
+#include "caja-file-attributes.h"
+#include "caja-file-private.h"
+#include "caja-file-utilities.h"
+#include "caja-signaller.h"
+#include "caja-global-preferences.h"
+#include "caja-link.h"
+#include "caja-marshal.h"
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-string.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
+{
+ CajaDirectory *directory;
+ CajaFile *file;
+ gboolean large;
+ GCancellable *cancellable;
+};
+
+struct LinkInfoReadState
+{
+ CajaDirectory *directory;
+ GCancellable *cancellable;
+ CajaFile *file;
+};
+
+struct ThumbnailState
+{
+ CajaDirectory *directory;
+ GCancellable *cancellable;
+ CajaFile *file;
+ gboolean trying_original;
+ gboolean tried_original;
+};
+
+struct MountState
+{
+ CajaDirectory *directory;
+ GCancellable *cancellable;
+ CajaFile *file;
+};
+
+struct FilesystemInfoState
+{
+ CajaDirectory *directory;
+ GCancellable *cancellable;
+ CajaFile *file;
+};
+
+struct DirectoryLoadState
+{
+ CajaDirectory *directory;
+ GCancellable *cancellable;
+ GFileEnumerator *enumerator;
+ GHashTable *load_mime_list_hash;
+ CajaFile *load_directory_file;
+ int load_file_count;
+};
+
+struct MimeListState
+{
+ CajaDirectory *directory;
+ CajaFile *mime_list_file;
+ GCancellable *cancellable;
+ GFileEnumerator *enumerator;
+ GHashTable *mime_list_hash;
+};
+
+struct GetInfoState
+{
+ CajaDirectory *directory;
+ GCancellable *cancellable;
+};
+
+struct NewFilesState
+{
+ CajaDirectory *directory;
+ GCancellable *cancellable;
+ int count;
+};
+
+struct DirectoryCountState
+{
+ CajaDirectory *directory;
+ CajaFile *count_file;
+ GCancellable *cancellable;
+ GFileEnumerator *enumerator;
+ int file_count;
+};
+
+struct DeepCountState
+{
+ CajaDirectory *directory;
+ GCancellable *cancellable;
+ GFileEnumerator *enumerator;
+ GFile *deep_count_location;
+ GList *deep_count_subdirectories;
+ GArray *seen_deep_count_inodes;
+};
+
+
+
+typedef struct
+{
+ CajaFile *file; /* Which file, NULL means all. */
+ union
+ {
+ CajaDirectoryCallback directory;
+ CajaFileCallback 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
+{
+ CajaFile *file; /* Which file, NULL means all. */
+ gboolean monitor_hidden_files; /* defines whether "all" includes hidden files */
+ gboolean monitor_backup_files; /* defines whether "all" includes backup files */
+ gconstpointer client;
+ Request request;
+} Monitor;
+
+typedef struct
+{
+ CajaDirectory *directory;
+ CajaInfoProvider *provider;
+ CajaOperationHandle *handle;
+ CajaOperationResult result;
+} InfoProviderResponse;
+
+typedef gboolean (* RequestCheck) (Request);
+typedef gboolean (* FileCheck) (CajaFile *);
+
+/* Current number of async. jobs. */
+static int async_job_count;
+static GHashTable *waiting_directories;
+#ifdef DEBUG_ASYNC_JOBS
+static GHashTable *async_jobs;
+#endif
+
+/* Hide kde trashcan directory */
+static char *kde_trash_dir_name = NULL;
+
+/* Forward declarations for functions that need them. */
+static void deep_count_load (DeepCountState *state,
+ GFile *location);
+static gboolean request_is_satisfied (CajaDirectory *directory,
+ CajaFile *file,
+ Request request);
+static void cancel_loading_attributes (CajaDirectory *directory,
+ CajaFileAttributes file_attributes);
+static void add_all_files_to_work_queue (CajaDirectory *directory);
+static void link_info_done (CajaDirectory *directory,
+ CajaFile *file,
+ const char *uri,
+ const char *name,
+ const char *icon,
+ gboolean is_launcher,
+ gboolean is_foreign);
+static void move_file_to_low_priority_queue (CajaDirectory *directory,
+ CajaFile *file);
+static void move_file_to_extension_queue (CajaDirectory *directory,
+ CajaFile *file);
+static void caja_directory_invalidate_file_attributes (CajaDirectory *directory,
+ CajaFileAttributes file_attributes);
+
+void
+caja_set_kde_trash_name (const char *trash_dir)
+{
+ g_free (kde_trash_dir_name);
+ kde_trash_dir_name = g_strdup (trash_dir);
+}
+
+/* Some helpers for case-insensitive strings.
+ * Move to caja-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
+caja_directory_verify_request_counts (CajaDirectory *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 (CajaDirectory *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 = eel_g_hash_table_new_free_at_exit
+ (NULL, NULL,
+ "caja-directory-async.c: waiting_directories");
+ }
+
+ g_hash_table_insert (waiting_directories,
+ directory,
+ directory);
+
+ return FALSE;
+ }
+
+#ifdef DEBUG_ASYNC_JOBS
+ {
+ char *uri;
+ if (async_jobs == NULL)
+ {
+ async_jobs = eel_g_hash_table_new_free_at_exit
+ (g_str_hash, g_str_equal,
+ "caja-directory-async.c: async_jobs");
+ }
+ uri = caja_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 (CajaDirectory *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 = caja_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);
+ caja_directory_async_state_changed
+ (CAJA_DIRECTORY (value));
+ }
+ already_waking_up = FALSE;
+}
+
+static void
+directory_count_cancel (CajaDirectory *directory)
+{
+ if (directory->details->count_in_progress != NULL)
+ {
+ g_cancellable_cancel (directory->details->count_in_progress->cancellable);
+ }
+}
+
+static void
+deep_count_cancel (CajaDirectory *directory)
+{
+ if (directory->details->deep_count_in_progress != NULL)
+ {
+ g_assert (CAJA_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 = CAJA_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 (CajaDirectory *directory)
+{
+ if (directory->details->mime_list_in_progress != NULL)
+ {
+ g_cancellable_cancel (directory->details->mime_list_in_progress->cancellable);
+ }
+}
+
+static void
+top_left_cancel (CajaDirectory *directory)
+{
+ if (directory->details->top_left_read_state != NULL)
+ {
+ g_cancellable_cancel (directory->details->top_left_read_state->cancellable);
+ directory->details->top_left_read_state->directory = NULL;
+ directory->details->top_left_read_state = NULL;
+
+ async_job_end (directory, "top left");
+ }
+}
+
+static void
+link_info_cancel (CajaDirectory *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 (CajaDirectory *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 (CajaDirectory *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 (CajaDirectory *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 (CajaDirectory *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 (CajaDirectory *directory,
+ CajaFile *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 (CajaDirectory *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 (CajaDirectory *directory,
+ CajaFile *file,
+ gconstpointer client)
+{
+ remove_monitor_link (directory, find_monitor (directory, file, client));
+}
+
+Request
+caja_directory_set_up_request (CajaFileAttributes file_attributes)
+{
+ Request request;
+
+ request = 0;
+
+ if ((file_attributes & CAJA_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT) != 0)
+ {
+ REQUEST_SET_TYPE (request, REQUEST_DIRECTORY_COUNT);
+ }
+
+ if ((file_attributes & CAJA_FILE_ATTRIBUTE_DEEP_COUNTS) != 0)
+ {
+ REQUEST_SET_TYPE (request, REQUEST_DEEP_COUNT);
+ }
+
+ if ((file_attributes & CAJA_FILE_ATTRIBUTE_DIRECTORY_ITEM_MIME_TYPES) != 0)
+ {
+ REQUEST_SET_TYPE (request, REQUEST_MIME_LIST);
+ }
+ if ((file_attributes & CAJA_FILE_ATTRIBUTE_INFO) != 0)
+ {
+ REQUEST_SET_TYPE (request, REQUEST_FILE_INFO);
+ }
+
+ if (file_attributes & CAJA_FILE_ATTRIBUTE_LINK_INFO)
+ {
+ REQUEST_SET_TYPE (request, REQUEST_FILE_INFO);
+ REQUEST_SET_TYPE (request, REQUEST_LINK_INFO);
+ }
+
+ if (file_attributes & CAJA_FILE_ATTRIBUTE_TOP_LEFT_TEXT)
+ {
+ REQUEST_SET_TYPE (request, REQUEST_TOP_LEFT_TEXT);
+ REQUEST_SET_TYPE (request, REQUEST_FILE_INFO);
+ }
+
+ if (file_attributes & CAJA_FILE_ATTRIBUTE_LARGE_TOP_LEFT_TEXT)
+ {
+ REQUEST_SET_TYPE (request, REQUEST_LARGE_TOP_LEFT_TEXT);
+ REQUEST_SET_TYPE (request, REQUEST_FILE_INFO);
+ }
+
+ if ((file_attributes & CAJA_FILE_ATTRIBUTE_EXTENSION_INFO) != 0)
+ {
+ REQUEST_SET_TYPE (request, REQUEST_EXTENSION_INFO);
+ }
+
+ if (file_attributes & CAJA_FILE_ATTRIBUTE_THUMBNAIL)
+ {
+ REQUEST_SET_TYPE (request, REQUEST_THUMBNAIL);
+ REQUEST_SET_TYPE (request, REQUEST_FILE_INFO);
+ }
+
+ if (file_attributes & CAJA_FILE_ATTRIBUTE_MOUNT)
+ {
+ REQUEST_SET_TYPE (request, REQUEST_MOUNT);
+ REQUEST_SET_TYPE (request, REQUEST_FILE_INFO);
+ }
+
+ if (file_attributes & CAJA_FILE_ATTRIBUTE_FILESYSTEM_INFO)
+ {
+ REQUEST_SET_TYPE (request, REQUEST_FILESYSTEM_INFO);
+ }
+
+ return request;
+}
+
+static void
+mime_db_changed_callback (GObject *ignore, CajaDirectory *dir)
+{
+ CajaFileAttributes attrs;
+
+ g_assert (dir != NULL);
+ g_assert (dir->details != NULL);
+
+ attrs = CAJA_FILE_ATTRIBUTE_INFO |
+ CAJA_FILE_ATTRIBUTE_LINK_INFO |
+ CAJA_FILE_ATTRIBUTE_DIRECTORY_ITEM_MIME_TYPES;
+
+ caja_directory_force_reload_internal (dir, attrs);
+}
+
+void
+caja_directory_monitor_add_internal (CajaDirectory *directory,
+ CajaFile *file,
+ gconstpointer client,
+ gboolean monitor_hidden_files,
+ gboolean monitor_backup_files,
+ CajaFileAttributes file_attributes,
+ CajaDirectoryCallback callback,
+ gpointer callback_data)
+{
+ Monitor *monitor;
+ GList *file_list;
+
+ g_assert (CAJA_IS_DIRECTORY (directory));
+
+ /* 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->monitor_backup_files = monitor_backup_files;
+ monitor->client = client;
+ monitor->request = caja_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 = caja_directory_get_file_list (directory);
+ (* callback) (directory, file_list, callback_data);
+ caja_file_list_free (file_list);
+ }
+
+ /* Start the "real" monitoring (FAM or whatever). */
+ /* We always monitor the whole directory since in practice
+ * caja 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 = caja_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 (caja_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)
+ {
+ caja_directory_add_file_to_work_queue (directory, file);
+ }
+ else
+ {
+ add_all_files_to_work_queue (directory);
+ }
+
+ /* Kick off I/O. */
+ caja_directory_async_state_changed (directory);
+}
+
+static void
+set_file_unconfirmed (CajaFile *file, gboolean unconfirmed)
+{
+ CajaDirectory *directory;
+
+ g_assert (CAJA_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 gboolean show_backup_files = TRUE;
+
+static void
+show_hidden_files_changed_callback (gpointer callback_data)
+{
+ show_hidden_files = eel_preferences_get_boolean (CAJA_PREFERENCES_SHOW_HIDDEN_FILES);
+}
+
+static void
+show_backup_files_changed_callback (gpointer callback_data)
+{
+ show_backup_files = eel_preferences_get_boolean (CAJA_PREFERENCES_SHOW_BACKUP_FILES);
+}
+
+static gboolean
+should_skip_file (CajaDirectory *directory, GFileInfo *info)
+{
+ static gboolean show_hidden_files_changed_callback_installed = FALSE;
+ static gboolean show_backup_files_changed_callback_installed = FALSE;
+
+ /* Add the callback once for the life of our process */
+ if (!show_hidden_files_changed_callback_installed)
+ {
+ eel_preferences_add_callback (CAJA_PREFERENCES_SHOW_HIDDEN_FILES,
+ show_hidden_files_changed_callback,
+ NULL);
+ show_hidden_files_changed_callback_installed = TRUE;
+
+ /* Peek for the first time */
+ show_hidden_files_changed_callback (NULL);
+ }
+
+ /* Add the callback once for the life of our process */
+ if (!show_backup_files_changed_callback_installed)
+ {
+ eel_preferences_add_callback (CAJA_PREFERENCES_SHOW_BACKUP_FILES,
+ show_backup_files_changed_callback,
+ NULL);
+ show_backup_files_changed_callback_installed = TRUE;
+
+ /* Peek for the first time */
+ show_backup_files_changed_callback (NULL);
+ }
+
+ if (!show_hidden_files &&
+ (g_file_info_get_is_hidden (info) ||
+ (directory != NULL && directory->details->hidden_file_hash != NULL &&
+ g_hash_table_lookup (directory->details->hidden_file_hash,
+ g_file_info_get_name (info)) != NULL)))
+ {
+ return TRUE;
+ }
+
+ if (!show_backup_files && g_file_info_get_is_backup (info))
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+dequeue_pending_idle_callback (gpointer callback_data)
+{
+ CajaDirectory *directory;
+ GList *pending_file_info;
+ GList *node, *next;
+ CajaFile *file;
+ GList *changed_files, *added_files;
+ GFileInfo *file_info;
+ const char *mimetype, *name;
+ DirectoryLoadState *dir_load_state;
+
+ directory = CAJA_DIRECTORY (callback_data);
+
+ caja_directory_ref (directory);
+
+ 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 (!caja_directory_is_file_list_monitored (directory))
+ {
+ caja_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 CajaFile 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 = caja_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 caja_file_get_by_uri()
+ * on a file in the folder before the add signal was
+ * emitted */
+ caja_file_ref (file);
+ file->details->is_added = TRUE;
+ added_files = g_list_prepend (added_files, file);
+ }
+ else if (caja_file_update_info (file, file_info))
+ {
+ /* File changed, notify about the change. */
+ caja_file_ref (file);
+ changed_files = g_list_prepend (changed_files, file);
+ }
+ }
+ else
+ {
+ /* new file, create a caja file object and add it to the list */
+ file = caja_file_new_from_info (directory, file_info);
+ caja_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 = CAJA_FILE (node->data);
+ next = node->next;
+
+ if (file->details->unconfirmed)
+ {
+ caja_file_ref (file);
+ changed_files = g_list_prepend (changed_files, file);
+
+ caja_file_mark_gone (file);
+ }
+ }
+ }
+
+ /* Send the changed and added signals. */
+ caja_directory_emit_change_signals (directory, changed_files);
+ caja_file_list_free (changed_files);
+ caja_directory_emit_files_added (directory, added_files);
+ caja_file_list_free (added_files);
+
+ if (directory->details->directory_loaded &&
+ !directory->details->directory_loaded_sent_notification)
+ {
+ /* Send the done_loading signal. */
+ caja_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;
+ eel_g_list_free_deep (file->details->mime_list);
+ file->details->mime_list = istr_set_get_as_list
+ (dir_load_state->load_mime_list_hash);
+
+ caja_file_changed (file);
+ }
+
+ caja_directory_async_state_changed (directory);
+
+ directory->details->directory_loaded_sent_notification = TRUE;
+ }
+
+drain:
+ eel_g_object_list_free (pending_file_info);
+
+ /* Get the state machine running again. */
+ caja_directory_async_state_changed (directory);
+
+ caja_directory_unref (directory);
+ return FALSE;
+}
+
+void
+caja_directory_schedule_dequeue_pending (CajaDirectory *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 (CajaDirectory *directory,
+ GFileInfo *info)
+{
+ if (info == NULL)
+ {
+ return;
+ }
+
+ if (g_file_info_get_name (info) == NULL)
+ {
+ char *uri;
+
+ uri = caja_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);
+ caja_directory_schedule_dequeue_pending (directory);
+}
+
+static void
+directory_load_cancel (CajaDirectory *directory)
+{
+ CajaFile *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)
+ {
+ caja_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 gboolean
+remove_callback (gpointer key, gpointer value, gpointer user_data)
+{
+ return TRUE;
+}
+
+static void
+file_list_cancel (CajaDirectory *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)
+ {
+ eel_g_object_list_free (directory->details->pending_file_info);
+ directory->details->pending_file_info = NULL;
+ }
+
+ if (directory->details->hidden_file_hash)
+ {
+ g_hash_table_foreach_remove (directory->details->hidden_file_hash, remove_callback, NULL);
+ }
+}
+
+static void
+directory_load_done (CajaDirectory *directory,
+ GError *error)
+{
+ GList *node;
+
+ 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 (CAJA_FILE (node->data), FALSE);
+ }
+
+ caja_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);
+}
+
+void
+caja_directory_monitor_remove_internal (CajaDirectory *directory,
+ CajaFile *file,
+ gconstpointer client)
+{
+ g_assert (CAJA_IS_DIRECTORY (directory));
+ g_assert (file == NULL || CAJA_IS_FILE (file));
+ g_assert (client != NULL);
+
+ remove_monitor (directory, file, client);
+
+ if (directory->details->monitor != NULL
+ && directory->details->monitor_list == NULL)
+ {
+ caja_monitor_cancel (directory->details->monitor);
+ directory->details->monitor = NULL;
+ }
+
+ /* XXX - do we need to remove anything from the work queue? */
+
+ caja_directory_async_state_changed (directory);
+}
+
+FileMonitors *
+caja_directory_remove_file_monitors (CajaDirectory *directory,
+ CajaFile *file)
+{
+ GList *result, **list, *node, *next;
+ Monitor *monitor;
+
+ g_assert (CAJA_IS_DIRECTORY (directory));
+ g_assert (CAJA_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? */
+
+ caja_directory_async_state_changed (directory);
+
+ return (FileMonitors *) result;
+}
+
+void
+caja_directory_add_file_monitors (CajaDirectory *directory,
+ CajaFile *file,
+ FileMonitors *monitors)
+{
+ GList **list;
+ GList *l;
+ Monitor *monitor;
+
+ g_assert (CAJA_IS_DIRECTORY (directory));
+ g_assert (CAJA_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);
+
+ caja_directory_add_file_to_work_queue (directory, file);
+
+ caja_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 (CajaDirectory *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 = caja_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);
+
+ caja_file_list_free (file_list);
+ }
+}
+
+void
+caja_directory_call_when_ready_internal (CajaDirectory *directory,
+ CajaFile *file,
+ CajaFileAttributes file_attributes,
+ gboolean wait_for_file_list,
+ CajaDirectoryCallback directory_callback,
+ CajaFileCallback file_callback,
+ gpointer callback_data)
+{
+ ReadyCallback callback;
+
+ g_assert (directory == NULL || CAJA_IS_DIRECTORY (directory));
+ g_assert (file == NULL || CAJA_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 = caja_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)
+ {
+ caja_directory_add_file_to_work_queue (directory, file);
+ }
+ else
+ {
+ add_all_files_to_work_queue (directory);
+ }
+
+ caja_directory_async_state_changed (directory);
+}
+
+gboolean
+caja_directory_check_if_ready_internal (CajaDirectory *directory,
+ CajaFile *file,
+ CajaFileAttributes file_attributes)
+{
+ Request request;
+
+ g_assert (CAJA_IS_DIRECTORY (directory));
+
+ request = caja_directory_set_up_request (file_attributes);
+ return request_is_satisfied (directory, file, request);
+}
+
+static void
+remove_callback_link_keep_data (CajaDirectory *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 (CajaDirectory *directory,
+ GList *link)
+{
+ ReadyCallback *callback;
+
+ callback = link->data;
+ remove_callback_link_keep_data (directory, link);
+ g_free (callback);
+}
+
+void
+caja_directory_cancel_callback_internal (CajaDirectory *directory,
+ CajaFile *file,
+ CajaDirectoryCallback directory_callback,
+ CajaFileCallback file_callback,
+ gpointer callback_data)
+{
+ ReadyCallback callback;
+ GList *node;
+
+ if (directory == NULL)
+ {
+ return;
+ }
+
+ g_assert (CAJA_IS_DIRECTORY (directory));
+ g_assert (file == NULL || CAJA_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);
+
+ caja_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)
+{
+ CajaDirectory *directory;
+ GFileInfo *info;
+ NewFilesState *state;
+
+ state = user_data;
+
+ if (state->directory == NULL)
+ {
+ /* Operation was cancelled. Bail out */
+ new_files_state_unref (state);
+ return;
+ }
+
+ directory = caja_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);
+
+ caja_directory_unref (directory);
+}
+
+void
+caja_directory_get_info_for_new_files (CajaDirectory *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,
+ CAJA_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
+caja_async_destroying_file (CajaFile *file)
+{
+ CajaDirectory *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->top_left_read_state != NULL
+ && directory->details->top_left_read_state->file == file)
+ {
+ directory->details->top_left_read_state->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)
+ {
+ caja_directory_async_state_changed (directory);
+ }
+}
+
+static gboolean
+lacks_directory_count (CajaFile *file)
+{
+ return !file->details->directory_count_is_up_to_date
+ && caja_file_should_show_directory_item_count (file);
+}
+
+static gboolean
+should_get_directory_count_now (CajaFile *file)
+{
+ return lacks_directory_count (file)
+ && !file->details->loading_directory;
+}
+
+static gboolean
+lacks_top_left (CajaFile *file)
+{
+ return file->details->file_info_is_up_to_date &&
+ !file->details->top_left_text_is_up_to_date
+ && caja_file_should_get_top_left_text (file);
+}
+
+static gboolean
+lacks_large_top_left (CajaFile *file)
+{
+ return file->details->file_info_is_up_to_date &&
+ (!file->details->top_left_text_is_up_to_date ||
+ file->details->got_large_top_left_text != file->details->got_top_left_text)
+ && caja_file_should_get_top_left_text (file);
+}
+static gboolean
+lacks_info (CajaFile *file)
+{
+ return !file->details->file_info_is_up_to_date
+ && !file->details->is_gone;
+}
+
+static gboolean
+lacks_filesystem_info (CajaFile *file)
+{
+ return !file->details->filesystem_info_is_up_to_date;
+}
+
+static gboolean
+lacks_deep_count (CajaFile *file)
+{
+ return file->details->deep_counts_status != CAJA_REQUEST_DONE;
+}
+
+static gboolean
+lacks_mime_list (CajaFile *file)
+{
+ return !file->details->mime_list_is_up_to_date;
+}
+
+static gboolean
+should_get_mime_list (CajaFile *file)
+{
+ return lacks_mime_list (file)
+ && !file->details->loading_directory;
+}
+
+static gboolean
+lacks_link_info (CajaFile *file)
+{
+ if (file->details->file_info_is_up_to_date &&
+ !file->details->link_info_is_up_to_date)
+ {
+ if (caja_file_is_caja_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 (CajaFile *file)
+{
+ return file->details->pending_info_providers != NULL;
+}
+
+static gboolean
+lacks_thumbnail (CajaFile *file)
+{
+ return caja_file_should_show_thumbnail (file) &&
+ file->details->thumbnail_path != NULL &&
+ !file->details->thumbnail_is_up_to_date;
+}
+
+static gboolean
+lacks_mount (CajaFile *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 &&
+ caja_file_is_self_owned (file)) ||
+
+ /* Mountable, could be a mountpoint */
+ (file->details->type == G_FILE_TYPE_MOUNTABLE)
+
+ )
+ );
+}
+
+static gboolean
+has_problem (CajaDirectory *directory, CajaFile *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 (CajaDirectory *directory,
+ CajaFile *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_TOP_LEFT_TEXT))
+ {
+ if (has_problem (directory, file, lacks_top_left))
+ {
+ return FALSE;
+ }
+ }
+
+ if (REQUEST_WANTS_TYPE (request, REQUEST_LARGE_TOP_LEFT_TEXT))
+ {
+ if (has_problem (directory, file, lacks_large_top_left))
+ {
+ 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)
+{
+ CajaDirectory *directory;
+ GList *node, *next;
+ ReadyCallback *callback;
+
+ directory = CAJA_DIRECTORY (callback_data);
+ directory->details->call_ready_idle_id = 0;
+
+ caja_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);
+ }
+
+ caja_directory_async_state_changed (directory);
+
+ caja_directory_unref (directory);
+
+ return FALSE;
+}
+
+static void
+schedule_call_ready_callbacks (CajaDirectory *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 (CajaDirectory *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
+caja_directory_has_active_request_for_file (CajaDirectory *directory,
+ CajaFile *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
+caja_directory_is_anyone_monitoring_file_list (CajaDirectory *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
+caja_directory_is_file_list_monitored (CajaDirectory *directory)
+{
+ return directory->details->file_list_monitored;
+}
+
+static void
+mark_all_files_unconfirmed (CajaDirectory *directory)
+{
+ GList *node;
+ CajaFile *file;
+
+ for (node = directory->details->file_list; node != NULL; node = node->next)
+ {
+ file = node->data;
+ set_file_unconfirmed (file, TRUE);
+ }
+}
+
+static void
+read_dot_hidden_file (CajaDirectory *directory)
+{
+ gsize file_size;
+ char *file_contents;
+ GFile *child;
+ GFileInfo *info;
+ GFileType type;
+ int i;
+
+
+ /* FIXME: We only support .hidden on file: uri's for the moment.
+ * Need to figure out if we should do this async or sync to extend
+ * it to all types of uris.
+ */
+ if (directory->details->location == NULL ||
+ !g_file_is_native (directory->details->location))
+ {
+ return;
+ }
+
+ child = g_file_get_child (directory->details->location, ".hidden");
+
+ type = G_FILE_TYPE_UNKNOWN;
+
+ info = g_file_query_info (child, G_FILE_ATTRIBUTE_STANDARD_TYPE, 0, NULL, NULL);
+ if (info != NULL)
+ {
+ type = g_file_info_get_file_type (info);
+ g_object_unref (info);
+ }
+
+ if (type != G_FILE_TYPE_REGULAR)
+ {
+ g_object_unref (child);
+ return;
+ }
+
+ if (!g_file_load_contents (child, NULL, &file_contents, &file_size, NULL, NULL))
+ {
+ g_object_unref (child);
+ return;
+ }
+
+ g_object_unref (child);
+
+ if (directory->details->hidden_file_hash == NULL)
+ {
+ directory->details->hidden_file_hash =
+ g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+ }
+
+ /* Now parse the data */
+ i = 0;
+ while (i < file_size)
+ {
+ int start;
+
+ start = i;
+ while (i < file_size && file_contents[i] != '\n')
+ {
+ i++;
+ }
+
+ if (i > start)
+ {
+ char *hidden_filename;
+
+ hidden_filename = g_strndup (file_contents + start, i - start);
+ g_hash_table_insert (directory->details->hidden_file_hash,
+ hidden_filename, hidden_filename);
+ }
+
+ i++;
+
+ }
+
+ g_free (file_contents);
+}
+
+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);
+ }
+ caja_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;
+ CajaDirectory *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 = caja_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 (caja_directory_file_list_length_reached (directory) ||
+ 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);
+ }
+
+ caja_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 (CajaDirectory *directory)
+{
+ DirectoryLoadState *state;
+
+ if (!directory->details->file_list_monitored)
+ {
+ g_assert (!directory->details->directory_load_in_progress);
+ directory->details->file_list_monitored = TRUE;
+ caja_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 =
+ caja_directory_get_corresponding_file (directory);
+ state->load_directory_file->details->loading_directory = TRUE;
+
+ read_dot_hidden_file (directory);
+
+ /* Hack to work around kde trash dir */
+ if (kde_trash_dir_name != NULL && caja_directory_is_desktop_directory (directory))
+ {
+ char *fn;
+
+ if (directory->details->hidden_file_hash == NULL)
+ {
+ directory->details->hidden_file_hash =
+ g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+ }
+
+ fn = g_strdup (kde_trash_dir_name);
+ g_hash_table_insert (directory->details->hidden_file_hash,
+ fn, fn);
+ }
+
+
+#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,
+ CAJA_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
+caja_directory_stop_monitoring_file_list (CajaDirectory *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);
+ caja_file_list_unref (directory->details->file_list);
+ directory->details->directory_loaded = FALSE;
+}
+
+static void
+file_list_start_or_stop (CajaDirectory *directory)
+{
+ if (caja_directory_is_anyone_monitoring_file_list (directory))
+ {
+ start_monitoring_file_list (directory);
+ }
+ else
+ {
+ caja_directory_stop_monitoring_file_list (directory);
+ }
+}
+
+void
+caja_file_invalidate_count_and_mime_list (CajaFile *file)
+{
+ CajaFileAttributes attributes;
+
+ attributes = CAJA_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT |
+ CAJA_FILE_ATTRIBUTE_DIRECTORY_ITEM_MIME_TYPES;
+
+ caja_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
+caja_directory_invalidate_count_and_mime_list (CajaDirectory *directory)
+{
+ CajaFile *file;
+
+ file = caja_directory_get_existing_corresponding_file (directory);
+ if (file != NULL)
+ {
+ caja_file_invalidate_count_and_mime_list (file);
+ }
+
+ caja_file_unref (file);
+}
+
+static void
+caja_directory_invalidate_file_attributes (CajaDirectory *directory,
+ CajaFileAttributes file_attributes)
+{
+ GList *node;
+
+ cancel_loading_attributes (directory, file_attributes);
+
+ for (node = directory->details->file_list; node != NULL; node = node->next)
+ {
+ caja_file_invalidate_attributes_internal (CAJA_FILE (node->data),
+ file_attributes);
+ }
+
+ if (directory->details->as_file != NULL)
+ {
+ caja_file_invalidate_attributes_internal (directory->details->as_file,
+ file_attributes);
+ }
+}
+
+void
+caja_directory_force_reload_internal (CajaDirectory *directory,
+ CajaFileAttributes file_attributes)
+{
+ /* invalidate attributes that are getting reloaded for all files */
+ caja_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. */
+ caja_directory_invalidate_count_and_mime_list (directory);
+
+ add_all_files_to_work_queue (directory);
+ caja_directory_async_state_changed (directory);
+}
+
+static gboolean
+monitor_includes_file (const Monitor *monitor,
+ CajaFile *file)
+{
+ if (monitor->file == file)
+ {
+ return TRUE;
+ }
+ if (monitor->file != NULL)
+ {
+ return FALSE;
+ }
+ if (file == file->details->directory->details->as_file)
+ {
+ return FALSE;
+ }
+ return caja_file_should_show (file,
+ monitor->monitor_hidden_files,
+ monitor->monitor_backup_files,
+ TRUE);
+}
+
+static gboolean
+is_needy (CajaFile *file,
+ FileCheck check_missing,
+ RequestType request_type_wanted)
+{
+ CajaDirectory *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 (CajaDirectory *directory)
+{
+ CajaFile *file;
+
+ if (directory->details->count_in_progress != NULL)
+ {
+ file = directory->details->count_in_progress->count_file;
+ if (file != NULL)
+ {
+ g_assert (CAJA_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 (CajaDirectory *directory,
+ CajaFile *count_file,
+ gboolean succeeded,
+ int count)
+{
+ g_assert (CAJA_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.
+ */
+ caja_file_changed (count_file);
+
+ /* Start up the next one. */
+ async_job_end (directory, "directory count");
+ caja_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);
+ caja_directory_unref (state->directory);
+ g_free (state);
+}
+
+static void
+count_more_files_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ DirectoryCountState *state;
+ CajaDirectory *directory;
+ GError *error;
+ GList *files;
+
+ state = user_data;
+ directory = state->directory;
+
+ if (g_cancellable_is_cancelled (state->cancellable))
+ {
+ /* Operation was cancelled. Bail out */
+ directory->details->count_in_progress = NULL;
+
+ async_job_end (directory, "directory count");
+ caja_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);
+ }
+
+ eel_g_object_list_free (files);
+
+ if (error)
+ {
+ g_error_free (error);
+ }
+}
+
+static void
+count_children_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ DirectoryCountState *state;
+ GFileEnumerator *enumerator;
+ CajaDirectory *directory;
+ GError *error;
+
+ state = user_data;
+
+ if (g_cancellable_is_cancelled (state->cancellable))
+ {
+ /* Operation was cancelled. Bail out */
+ directory = state->directory;
+ directory->details->count_in_progress = NULL;
+
+ async_job_end (directory, "directory count");
+ caja_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 (CajaDirectory *directory,
+ CajaFile *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 (!caja_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;
+
+ caja_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 = caja_directory_ref (directory);
+ state->cancellable = g_cancellable_new ();
+
+ directory->details->count_in_progress = state;
+
+ location = caja_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)
+{
+ CajaFile *file;
+ GFile *subdir;
+ gboolean is_seen_inode;
+
+ 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. */
+
+ 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);
+ }
+ eel_g_object_list_free (state->deep_count_subdirectories);
+ g_array_free (state->seen_deep_count_inodes, TRUE);
+ g_free (state);
+}
+
+static void
+deep_count_next_dir (DeepCountState *state)
+{
+ GFile *location;
+ CajaFile *file;
+ CajaDirectory *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 = CAJA_REQUEST_DONE;
+ directory->details->deep_count_file = NULL;
+ directory->details->deep_count_in_progress = NULL;
+ deep_count_state_free (state);
+ done = TRUE;
+ }
+
+ caja_file_updated_deep_count_in_progress (file);
+
+ if (done)
+ {
+ caja_file_changed (file);
+ async_job_end (directory, "deep count");
+ caja_directory_async_state_changed (directory);
+ }
+}
+
+static void
+deep_count_more_files_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ DeepCountState *state;
+ CajaDirectory *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 = caja_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);
+
+ caja_directory_unref (directory);
+}
+
+static void
+deep_count_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ DeepCountState *state;
+ GFileEnumerator *enumerator;
+ CajaFile *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)
+{
+ CajaDirectory *directory;
+
+ directory = state->directory;
+ 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_UNIX_INODE,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, /* flags */
+ G_PRIORITY_LOW, /* prio */
+ state->cancellable,
+ deep_count_callback,
+ state);
+}
+
+static void
+deep_count_stop (CajaDirectory *directory)
+{
+ CajaFile *file;
+
+ if (directory->details->deep_count_in_progress != NULL)
+ {
+ file = directory->details->deep_count_file;
+ if (file != NULL)
+ {
+ g_assert (CAJA_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_start (CajaDirectory *directory,
+ CajaFile *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 (!caja_file_is_directory (file))
+ {
+ file->details->deep_counts_status = CAJA_REQUEST_DONE;
+
+ caja_directory_async_state_changed (directory);
+ return;
+ }
+
+ if (!async_job_start (directory, "deep count"))
+ {
+ return;
+ }
+
+ /* Start counting. */
+ file->details->deep_counts_status = CAJA_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));
+
+ directory->details->deep_count_in_progress = state;
+
+ location = caja_file_get_location (file);
+ deep_count_load (state, location);
+ g_object_unref (location);
+}
+
+static void
+mime_list_stop (CajaDirectory *directory)
+{
+ CajaFile *file;
+
+ if (directory->details->mime_list_in_progress != NULL)
+ {
+ file = directory->details->mime_list_in_progress->mime_list_file;
+ if (file != NULL)
+ {
+ g_assert (CAJA_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);
+ caja_directory_unref (state->directory);
+ g_free (state);
+}
+
+
+static void
+mime_list_done (MimeListState *state, gboolean success)
+{
+ CajaFile *file;
+ CajaDirectory *directory;
+
+ directory = state->directory;
+ g_assert (directory != NULL);
+
+ file = state->mime_list_file;
+
+ file->details->mime_list_is_up_to_date = TRUE;
+ eel_g_list_free_deep (file->details->mime_list);
+ 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.
+ */
+ caja_file_changed (file);
+
+ /* Start up the next one. */
+ async_job_end (directory, "MIME list");
+ caja_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;
+ CajaDirectory *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");
+ caja_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;
+ CajaDirectory *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");
+ caja_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 (CajaDirectory *directory,
+ CajaFile *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 (!caja_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;
+
+ caja_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 = caja_directory_ref (directory);
+ state->cancellable = g_cancellable_new ();
+ state->mime_list_hash = istr_set_new ();
+
+ directory->details->mime_list_in_progress = state;
+
+ location = caja_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
+top_left_stop (CajaDirectory *directory)
+{
+ CajaFile *file;
+
+ if (directory->details->top_left_read_state != NULL)
+ {
+ file = directory->details->top_left_read_state->file;
+ if (file != NULL)
+ {
+ g_assert (CAJA_IS_FILE (file));
+ g_assert (file->details->directory == directory);
+ if (is_needy (file,
+ lacks_top_left,
+ REQUEST_TOP_LEFT_TEXT) ||
+ is_needy (file,
+ lacks_large_top_left,
+ REQUEST_LARGE_TOP_LEFT_TEXT))
+ {
+ return;
+ }
+ }
+
+ /* The top left is not wanted, so stop it. */
+ top_left_cancel (directory);
+ }
+}
+
+static void
+top_left_read_state_free (TopLeftTextReadState *state)
+{
+ g_object_unref (state->cancellable);
+ g_free (state);
+}
+
+static void
+top_left_read_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer callback_data)
+{
+ TopLeftTextReadState *state;
+ CajaDirectory *directory;
+ CajaFileDetails *file_details;
+ gsize file_size;
+ char *file_contents;
+
+ state = callback_data;
+
+ if (state->directory == NULL)
+ {
+ /* Operation was cancelled. Bail out */
+ top_left_read_state_free (state);
+ return;
+ }
+
+ directory = caja_directory_ref (state->directory);
+
+ file_details = state->file->details;
+
+ file_details->top_left_text_is_up_to_date = TRUE;
+ g_free (file_details->top_left_text);
+
+ if (g_file_load_partial_contents_finish (G_FILE (source_object),
+ res,
+ &file_contents, &file_size,
+ NULL, NULL))
+ {
+ file_details->top_left_text = caja_extract_top_left_text (file_contents, state->large, file_size);
+ file_details->got_top_left_text = TRUE;
+ file_details->got_large_top_left_text = state->large;
+ g_free (file_contents);
+ }
+ else
+ {
+ file_details->top_left_text = NULL;
+ file_details->got_top_left_text = FALSE;
+ file_details->got_large_top_left_text = FALSE;
+ }
+
+ caja_file_changed (state->file);
+
+ directory->details->top_left_read_state = NULL;
+ async_job_end (directory, "top left");
+
+ top_left_read_state_free (state);
+
+ caja_directory_async_state_changed (directory);
+
+ caja_directory_unref (directory);
+}
+
+static int
+count_lines (const char *text, int length)
+{
+ int count, i;
+
+ count = 0;
+ for (i = 0; i < length; i++)
+ {
+ count += *text++ == '\n';
+ }
+ return count;
+}
+
+static gboolean
+top_left_read_more_callback (const char *file_contents,
+ goffset bytes_read,
+ gpointer callback_data)
+{
+ TopLeftTextReadState *state;
+
+ state = callback_data;
+
+ /* Stop reading when we have enough. */
+ if (state->large)
+ {
+ return bytes_read < CAJA_FILE_LARGE_TOP_LEFT_TEXT_MAXIMUM_BYTES &&
+ count_lines (file_contents, bytes_read) <= CAJA_FILE_LARGE_TOP_LEFT_TEXT_MAXIMUM_LINES;
+ }
+ else
+ {
+ return bytes_read < CAJA_FILE_TOP_LEFT_TEXT_MAXIMUM_BYTES &&
+ count_lines (file_contents, bytes_read) <= CAJA_FILE_TOP_LEFT_TEXT_MAXIMUM_LINES;
+ }
+}
+
+static void
+top_left_start (CajaDirectory *directory,
+ CajaFile *file,
+ gboolean *doing_io)
+{
+ GFile *location;
+ gboolean needs_large;
+ TopLeftTextReadState *state;
+
+ if (directory->details->top_left_read_state != NULL)
+ {
+ *doing_io = TRUE;
+ return;
+ }
+
+ needs_large = FALSE;
+
+ if (is_needy (file,
+ lacks_large_top_left,
+ REQUEST_LARGE_TOP_LEFT_TEXT))
+ {
+ needs_large = TRUE;
+ }
+
+ /* Figure out which file to read the top left for. */
+ if (!(needs_large ||
+ is_needy (file,
+ lacks_top_left,
+ REQUEST_TOP_LEFT_TEXT)))
+ {
+ return;
+ }
+ *doing_io = TRUE;
+
+ if (!caja_file_contains_text (file))
+ {
+ g_free (file->details->top_left_text);
+ file->details->top_left_text = NULL;
+ file->details->got_top_left_text = FALSE;
+ file->details->got_large_top_left_text = FALSE;
+ file->details->top_left_text_is_up_to_date = TRUE;
+
+ caja_directory_async_state_changed (directory);
+ return;
+ }
+
+ if (!async_job_start (directory, "top left"))
+ {
+ return;
+ }
+
+ /* Start reading. */
+ state = g_new0 (TopLeftTextReadState, 1);
+ state->directory = directory;
+ state->cancellable = g_cancellable_new ();
+ state->large = needs_large;
+ state->file = file;
+
+ directory->details->top_left_read_state = state;
+
+ location = caja_file_get_location (file);
+ g_file_load_partial_contents_async (location,
+ state->cancellable,
+ top_left_read_more_callback,
+ top_left_read_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)
+{
+ CajaDirectory *directory;
+ CajaFile *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 = caja_directory_ref (state->directory);
+
+ get_info_file = directory->details->get_info_file;
+ g_assert (CAJA_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.
+ */
+ caja_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 */
+ caja_file_mark_gone (get_info_file);
+ }
+ get_info_file->details->file_info_is_up_to_date = TRUE;
+ caja_file_clear_info (get_info_file);
+ get_info_file->details->get_info_failed = TRUE;
+ get_info_file->details->get_info_error = error;
+ }
+ else
+ {
+ caja_file_update_info (get_info_file, info);
+ g_object_unref (info);
+ }
+
+ caja_file_changed (get_info_file);
+ caja_file_unref (get_info_file);
+
+ async_job_end (directory, "file info");
+ caja_directory_async_state_changed (directory);
+
+ caja_directory_unref (directory);
+
+ get_info_state_free (state);
+}
+
+static void
+file_info_stop (CajaDirectory *directory)
+{
+ CajaFile *file;
+
+ if (directory->details->get_info_in_progress != NULL)
+ {
+ file = directory->details->get_info_file;
+ if (file != NULL)
+ {
+ g_assert (CAJA_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 (CajaDirectory *directory,
+ CajaFile *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 = caja_file_get_location (file);
+ g_file_query_info_async (location,
+ CAJA_FILE_DEFAULT_ATTRIBUTES,
+ 0,
+ G_PRIORITY_DEFAULT,
+ state->cancellable, query_info_callback, state);
+ g_object_unref (location);
+}
+
+static gboolean
+is_link_trusted (CajaFile *file,
+ gboolean is_launcher)
+{
+ GFile *location;
+ gboolean res;
+
+ if (!is_launcher)
+ {
+ return TRUE;
+ }
+
+ if (caja_file_can_execute (file))
+ {
+ return TRUE;
+ }
+
+ res = FALSE;
+
+ if (caja_file_is_local (file))
+ {
+ location = caja_file_get_location (file);
+ res = caja_is_in_system_dir (location);
+ g_object_unref (location);
+ }
+
+ return res;
+}
+
+static void
+link_info_done (CajaDirectory *directory,
+ CajaFile *file,
+ const char *uri,
+ const char *name,
+ const char *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)
+ {
+ caja_file_set_display_name (file, name, name, TRUE);
+ }
+ else
+ {
+ caja_file_set_display_name (file, NULL, NULL, TRUE);
+ }
+
+ file->details->got_link_info = TRUE;
+ g_free (file->details->custom_icon);
+ file->details->custom_icon = NULL;
+ 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)
+ {
+ file->details->custom_icon = g_strdup (icon);
+ }
+ file->details->is_launcher = is_launcher;
+ file->details->is_foreign_link = is_foreign;
+ file->details->is_trusted_link = is_trusted;
+
+ caja_directory_async_state_changed (directory);
+}
+
+static gboolean
+should_read_link_info_sync (CajaFile *file)
+{
+#ifdef READ_LOCAL_LINKS_SYNC
+ return (caja_file_is_local (file) && !caja_file_is_directory (file));
+#else
+ return FALSE;
+#endif
+}
+
+static void
+link_info_stop (CajaDirectory *directory)
+{
+ CajaFile *file;
+
+ if (directory->details->link_info_read_state != NULL)
+ {
+ file = directory->details->link_info_read_state->file;
+
+ if (file != NULL)
+ {
+ g_assert (CAJA_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 (CajaDirectory *directory,
+ CajaFile *file,
+ gboolean result,
+ goffset bytes_read,
+ char *file_contents)
+{
+ char *link_uri, *uri, *name, *icon;
+ gboolean is_launcher;
+ gboolean is_foreign;
+
+ caja_directory_ref (directory);
+
+ uri = NULL;
+ name = NULL;
+ icon = NULL;
+ is_launcher = FALSE;
+ is_foreign = FALSE;
+
+ /* Handle the case where we read the Caja link. */
+ if (result)
+ {
+ link_uri = caja_file_get_uri (file);
+ caja_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. */
+ }
+
+ caja_file_ref (file);
+ link_info_done (directory, file, uri, name, icon, is_launcher, is_foreign);
+ caja_file_changed (file);
+ caja_file_unref (file);
+
+ g_free (uri);
+ g_free (name);
+ g_free (icon);
+
+ caja_directory_unref (directory);
+}
+
+static void
+link_info_read_state_free (LinkInfoReadState *state)
+{
+ g_object_unref (state->cancellable);
+ g_free (state);
+}
+
+static void
+link_info_caja_link_read_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ LinkInfoReadState *state;
+ gsize file_size;
+ char *file_contents;
+ gboolean result;
+ CajaDirectory *directory;
+
+ state = user_data;
+
+ if (state->directory == NULL)
+ {
+ /* Operation was cancelled. Bail out */
+ link_info_read_state_free (state);
+ return;
+ }
+
+ directory = caja_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);
+
+ caja_directory_unref (directory);
+}
+
+static void
+link_info_start (CajaDirectory *directory,
+ CajaFile *file,
+ gboolean *doing_io)
+{
+ GFile *location;
+ gboolean caja_style_link;
+ gsize file_size;
+ char *file_contents;
+ gboolean result;
+ 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. */
+ caja_style_link = caja_file_is_caja_link (file);
+ location = caja_file_get_location (file);
+
+ /* If it's not a link we are done. If it is, we need to read it. */
+ if (!caja_style_link)
+ {
+ link_info_done (directory, file, NULL, NULL, NULL, FALSE, FALSE);
+ }
+ else if (should_read_link_info_sync (file))
+ {
+ result = g_file_load_contents (location, NULL, &file_contents, &file_size, NULL, NULL);
+ link_info_got_data (directory, file, result, file_size, file_contents);
+ g_free (file_contents);
+ }
+ 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_caja_link_read_callback,
+ state);
+ }
+ g_object_unref (location);
+}
+
+static void
+thumbnail_done (CajaDirectory *directory,
+ CajaFile *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 (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;
+ }
+ }
+
+ caja_directory_async_state_changed (directory);
+}
+
+static void
+thumbnail_stop (CajaDirectory *directory)
+{
+ CajaFile *file;
+
+ if (directory->details->thumbnail_state != NULL)
+ {
+ file = directory->details->thumbnail_state->file;
+
+ if (file != NULL)
+ {
+ g_assert (CAJA_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 (CajaDirectory *directory,
+ CajaFile *file,
+ GdkPixbuf *pixbuf,
+ gboolean tried_original)
+{
+ caja_directory_ref (directory);
+
+ caja_file_ref (file);
+ thumbnail_done (directory, file, pixbuf, tried_original);
+ caja_file_changed (file);
+ caja_file_unref (file);
+
+ if (pixbuf)
+ {
+ g_object_unref (pixbuf);
+ }
+
+ caja_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. caja_file_get_icon() */
+ max_thumbnail_size = CAJA_ICON_SIZE_LARGEST * cached_thumbnail_size / CAJA_ICON_SIZE_STANDARD;
+ 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, 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;
+ CajaDirectory *directory;
+ GdkPixbuf *pixbuf;
+ GFile *location;
+
+ state = user_data;
+
+ if (state->directory == NULL)
+ {
+ /* Operation was cancelled. Bail out */
+ thumbnail_state_free (state);
+ return;
+ }
+
+ directory = caja_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);
+ }
+
+ caja_directory_unref (directory);
+}
+
+static void
+thumbnail_start (CajaDirectory *directory,
+ CajaFile *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 = caja_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 (CajaDirectory *directory)
+{
+ CajaFile *file;
+
+ if (directory->details->mount_state != NULL)
+ {
+ file = directory->details->mount_state->file;
+
+ if (file != NULL)
+ {
+ g_assert (CAJA_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)
+{
+ CajaDirectory *directory;
+ CajaFile *file;
+
+ directory = caja_directory_ref (state->directory);
+
+ state->directory->details->mount_state = NULL;
+ async_job_end (state->directory, "mount");
+
+ file = caja_file_ref (state->file);
+
+ file->details->mount_is_up_to_date = TRUE;
+ caja_file_set_mount (file, mount);
+
+ caja_directory_async_state_changed (directory);
+ caja_file_changed (file);
+
+ caja_file_unref (file);
+
+ caja_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 = caja_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);
+ }
+
+ eel_g_object_list_free (mounts);
+
+ g_object_unref (monitor);
+
+ return found;
+}
+
+static void
+mount_start (CajaDirectory *directory,
+ CajaFile *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 = caja_file_get_location (file);
+
+ directory->details->mount_state = state;
+
+ if (file->details->type == G_FILE_TYPE_MOUNTABLE)
+ {
+ GFile *target;
+ GMount *mount;
+
+ mount = NULL;
+ target = caja_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 (CajaDirectory *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 (CajaDirectory *directory)
+{
+ CajaFile *file;
+
+ if (directory->details->filesystem_info_state != NULL)
+ {
+ file = directory->details->filesystem_info_state->file;
+
+ if (file != NULL)
+ {
+ g_assert (CAJA_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)
+{
+ CajaDirectory *directory;
+ CajaFile *file;
+
+ /* careful here, info may be NULL */
+
+ directory = caja_directory_ref (state->directory);
+
+ state->directory->details->filesystem_info_state = NULL;
+ async_job_end (state->directory, "filesystem info");
+
+ file = caja_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);
+ }
+
+ caja_directory_async_state_changed (directory);
+ caja_file_changed (file);
+
+ caja_file_unref (file);
+
+ caja_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 (CajaDirectory *directory,
+ CajaFile *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 = caja_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_PRIORITY_DEFAULT,
+ state->cancellable,
+ query_filesystem_info_callback,
+ state);
+ g_object_unref (location);
+}
+
+static void
+extension_info_cancel (CajaDirectory *directory)
+{
+ if (directory->details->extension_info_in_progress != NULL)
+ {
+ if (directory->details->extension_info_idle)
+ {
+ g_source_remove (directory->details->extension_info_idle);
+ }
+ else
+ {
+ caja_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 (CajaDirectory *directory)
+{
+ if (directory->details->extension_info_in_progress != NULL)
+ {
+ CajaFile *file;
+
+ file = directory->details->extension_info_file;
+ if (file != NULL)
+ {
+ g_assert (CAJA_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 (CajaDirectory *directory,
+ CajaFile *file,
+ CajaInfoProvider *provider)
+{
+ file->details->pending_info_providers =
+ g_list_remove (file->details->pending_info_providers,
+ provider);
+ g_object_unref (provider);
+
+ caja_directory_async_state_changed (directory);
+
+ if (file->details->pending_info_providers == NULL)
+ {
+ caja_file_info_providers_done (file);
+ }
+}
+
+
+static gboolean
+info_provider_idle_callback (gpointer user_data)
+{
+ InfoProviderResponse *response;
+ CajaDirectory *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 Caja extension: handle=%p", response->handle);
+ }
+ else
+ {
+ CajaFile *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 (CajaInfoProvider *provider,
+ CajaOperationHandle *handle,
+ CajaOperationResult result,
+ gpointer user_data)
+{
+ InfoProviderResponse *response;
+
+ response = g_new0 (InfoProviderResponse, 1);
+ response->provider = provider;
+ response->handle = handle;
+ response->result = result;
+ response->directory = CAJA_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 (CajaDirectory *directory,
+ CajaFile *file,
+ gboolean *doing_io)
+{
+ CajaInfoProvider *provider;
+ CajaOperationResult result;
+ CajaOperationHandle *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,
+ caja_marshal_VOID__POINTER_ENUM);
+
+ result = caja_info_provider_update_file_info
+ (provider,
+ CAJA_FILE_INFO (file),
+ update_complete,
+ &handle);
+
+ g_closure_unref (update_complete);
+
+ if (result == CAJA_OPERATION_COMPLETE ||
+ result == CAJA_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 (CajaDirectory *directory)
+{
+ CajaFile *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);
+ top_left_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 (!caja_file_queue_is_empty (directory->details->high_priority_queue))
+ {
+ file = caja_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 (!caja_file_queue_is_empty (directory->details->low_priority_queue))
+ {
+ file = caja_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);
+ top_left_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 (!caja_file_queue_is_empty (directory->details->extension_queue))
+ {
+ file = caja_file_queue_head (directory->details->extension_queue);
+
+ /* Start getting attributes if possible */
+ extension_info_start (directory, file, &doing_io);
+ if (doing_io)
+ {
+ return;
+ }
+
+ caja_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
+caja_directory_async_state_changed (CajaDirectory *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;
+ caja_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;
+ caja_directory_unref (directory);
+
+ /* Check if any directories should wake up. */
+ async_job_wake_up ();
+}
+
+void
+caja_directory_cancel (CajaDirectory *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);
+ top_left_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 (CajaDirectory *directory,
+ CajaFile *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 (CajaDirectory *directory,
+ CajaFile *file)
+{
+ if (directory->details->deep_count_file == file)
+ {
+ deep_count_cancel (directory);
+ }
+}
+
+static void
+cancel_mime_list_for_file (CajaDirectory *directory,
+ CajaFile *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_top_left_text_for_file (CajaDirectory *directory,
+ CajaFile *file)
+{
+ if (directory->details->top_left_read_state != NULL &&
+ directory->details->top_left_read_state->file == file)
+ {
+ top_left_cancel (directory);
+ }
+}
+
+static void
+cancel_file_info_for_file (CajaDirectory *directory,
+ CajaFile *file)
+{
+ if (directory->details->get_info_file == file)
+ {
+ file_info_cancel (directory);
+ }
+}
+
+static void
+cancel_thumbnail_for_file (CajaDirectory *directory,
+ CajaFile *file)
+{
+ if (directory->details->thumbnail_state != NULL &&
+ directory->details->thumbnail_state->file == file)
+ {
+ thumbnail_cancel (directory);
+ }
+}
+
+static void
+cancel_mount_for_file (CajaDirectory *directory,
+ CajaFile *file)
+{
+ if (directory->details->mount_state != NULL &&
+ directory->details->mount_state->file == file)
+ {
+ mount_cancel (directory);
+ }
+}
+
+static void
+cancel_filesystem_info_for_file (CajaDirectory *directory,
+ CajaFile *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 (CajaDirectory *directory,
+ CajaFile *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 (CajaDirectory *directory,
+ CajaFileAttributes file_attributes)
+{
+ Request request;
+
+ request = caja_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_TOP_LEFT_TEXT))
+ {
+ top_left_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);
+ }
+
+ caja_directory_async_state_changed (directory);
+}
+
+void
+caja_directory_cancel_loading_file_attributes (CajaDirectory *directory,
+ CajaFile *file,
+ CajaFileAttributes file_attributes)
+{
+ Request request;
+
+ caja_directory_remove_file_from_work_queue (directory, file);
+
+ request = caja_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_TOP_LEFT_TEXT))
+ {
+ cancel_top_left_text_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);
+ }
+
+ caja_directory_async_state_changed (directory);
+}
+
+void
+caja_directory_add_file_to_work_queue (CajaDirectory *directory,
+ CajaFile *file)
+{
+ g_return_if_fail (file->details->directory == directory);
+
+ caja_file_queue_enqueue (directory->details->high_priority_queue,
+ file);
+}
+
+
+static void
+add_all_files_to_work_queue (CajaDirectory *directory)
+{
+ GList *node;
+ CajaFile *file;
+
+ for (node = directory->details->file_list; node != NULL; node = node->next)
+ {
+ file = CAJA_FILE (node->data);
+
+ caja_directory_add_file_to_work_queue (directory, file);
+ }
+}
+
+void
+caja_directory_remove_file_from_work_queue (CajaDirectory *directory,
+ CajaFile *file)
+{
+ caja_file_queue_remove (directory->details->high_priority_queue,
+ file);
+ caja_file_queue_remove (directory->details->low_priority_queue,
+ file);
+ caja_file_queue_remove (directory->details->extension_queue,
+ file);
+}
+
+
+static void
+move_file_to_low_priority_queue (CajaDirectory *directory,
+ CajaFile *file)
+{
+ /* Must add before removing to avoid ref underflow */
+ caja_file_queue_enqueue (directory->details->low_priority_queue,
+ file);
+ caja_file_queue_remove (directory->details->high_priority_queue,
+ file);
+}
+
+static void
+move_file_to_extension_queue (CajaDirectory *directory,
+ CajaFile *file)
+{
+ /* Must add before removing to avoid ref underflow */
+ caja_file_queue_enqueue (directory->details->extension_queue,
+ file);
+ caja_file_queue_remove (directory->details->low_priority_queue,
+ file);
+}
diff --git a/libcaja-private/caja-directory-background.c b/libcaja-private/caja-directory-background.c
new file mode 100644
index 00000000..ad1306f2
--- /dev/null
+++ b/libcaja-private/caja-directory-background.c
@@ -0,0 +1,731 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ caja-directory-background.c: Helper for the background of a widget
+ that is viewing a particular location.
+
+ 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Darin Adler <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-directory-background.h"
+
+#include <eel/eel-gdk-extensions.h>
+#include <eel/eel-gtk-extensions.h>
+#include <eel/eel-background.h>
+#include "caja-dnd.h"
+#include "caja-global-preferences.h"
+#include "caja-metadata.h"
+#include "caja-file-attributes.h"
+#include <gtk/gtk.h>
+#include <string.h>
+
+static void background_changed_callback (EelBackground *background,
+ GdkDragAction action,
+ CajaFile *file);
+static void background_reset_callback (EelBackground *background,
+ CajaFile *file);
+
+static void saved_settings_changed_callback (CajaFile *file,
+ EelBackground *background);
+
+static void caja_file_background_receive_mateconf_changes (EelBackground *background);
+
+static void caja_file_background_theme_changed (gpointer user_data);
+
+void
+caja_connect_desktop_background_to_file_metadata (CajaIconContainer *icon_container,
+ CajaFile *file)
+{
+ EelBackground *background;
+
+ background = eel_get_widget_background (GTK_WIDGET (icon_container));
+
+ eel_background_set_desktop (background, GTK_WIDGET (icon_container), TRUE);
+
+ /* Strictly speaking, we don't need to know about metadata changes, since
+ * the desktop setting aren't stored there. But, hooking up to metadata
+ * changes is actually a small part of what this fn does, and we do need
+ * the other stuff (hooked up to background & theme changes, initialize
+ * the background). Being notified of metadata changes on the file is a
+ * waste, but won't hurt, so I don't think it's worth refactoring the fn
+ * at this point.
+ */
+ caja_connect_background_to_file_metadata (GTK_WIDGET (icon_container), file, CAJA_DND_ACTION_SET_AS_FOLDER_BACKGROUND);
+
+ caja_file_background_receive_mateconf_changes (background);
+}
+
+static void
+caja_file_background_get_default_settings (char **color,
+ char **image,
+ EelBackgroundImagePlacement *placement)
+{
+ gboolean background_set;
+
+ background_set = eel_preferences_get_boolean
+ (CAJA_PREFERENCES_BACKGROUND_SET);
+
+ if (background_set && color)
+ {
+ *color = eel_preferences_get (CAJA_PREFERENCES_BACKGROUND_COLOR);
+ }
+
+ if (background_set && image)
+ {
+ *image = eel_preferences_get (CAJA_PREFERENCES_BACKGROUND_FILENAME);
+ }
+
+ if (placement)
+ {
+ *placement = EEL_BACKGROUND_TILED;
+ }
+}
+
+
+#define BG_PREFERENCES_DRAW_BACKGROUND "/desktop/mate/background/draw_background"
+#define BG_PREFERENCES_PRIMARY_COLOR "/desktop/mate/background/primary_color"
+#define BG_PREFERENCES_SECONDARY_COLOR "/desktop/mate/background/secondary_color"
+#define BG_PREFERENCES_COLOR_SHADING_TYPE "/desktop/mate/background/color_shading_type"
+#define BG_PREFERENCES_PICTURE_OPTIONS "/desktop/mate/background/picture_options"
+#define BG_PREFERENCES_PICTURE_OPACITY "/desktop/mate/background/picture_opacity"
+#define BG_PREFERENCES_PICTURE_FILENAME "/desktop/mate/background/picture_filename"
+
+static void
+read_color (MateConfClient *client, const char *key, GdkColor *color)
+{
+ gchar *tmp;
+
+ tmp = mateconf_client_get_string (client, key, NULL);
+
+ if (tmp != NULL)
+ {
+ if (!gdk_color_parse (tmp, color))
+ gdk_color_parse ("black", color);
+ g_free (tmp);
+ }
+ else
+ {
+ gdk_color_parse ("black", color);
+ }
+
+ gdk_rgb_find_color (gdk_rgb_get_colormap (), color);
+}
+
+static void
+caja_file_background_read_desktop_settings (char **color,
+ char **image,
+ EelBackgroundImagePlacement *placement)
+{
+ MateConfClient *client;
+ gboolean enabled;
+ GdkColor primary, secondary;
+ gchar *tmp, *filename;
+ char *end_color;
+ char *start_color;
+ gboolean use_gradient;
+ gboolean is_horizontal;
+
+ filename = NULL;
+
+ client = mateconf_client_get_default ();
+
+ /* Get the image filename */
+ enabled = mateconf_client_get_bool (client, BG_PREFERENCES_DRAW_BACKGROUND, NULL);
+ if (enabled)
+ {
+ tmp = mateconf_client_get_string (client, BG_PREFERENCES_PICTURE_FILENAME, NULL);
+ if (tmp != NULL)
+ {
+ if (g_utf8_validate (tmp, -1, NULL) && g_file_test (tmp, G_FILE_TEST_EXISTS))
+ {
+ filename = g_strdup (tmp);
+ }
+ else
+ {
+ filename = g_filename_from_utf8 (tmp, -1, NULL, NULL, NULL);
+ }
+ }
+ g_free (tmp);
+
+ if (filename != NULL && filename[0] != '\0')
+ {
+ *image = g_filename_to_uri (filename, NULL, NULL);
+ }
+ else
+ {
+ *image = NULL;
+ }
+ g_free (filename);
+ }
+ else
+ {
+ *image = NULL;
+ }
+
+ /* Get the placement */
+ tmp = mateconf_client_get_string (client, BG_PREFERENCES_PICTURE_OPTIONS, NULL);
+ if (tmp != NULL)
+ {
+ if (strcmp (tmp, "wallpaper") == 0)
+ {
+ *placement = EEL_BACKGROUND_TILED;
+ }
+ else if (strcmp (tmp, "centered") == 0)
+ {
+ *placement = EEL_BACKGROUND_CENTERED;
+ }
+ else if (strcmp (tmp, "scaled") == 0)
+ {
+ *placement = EEL_BACKGROUND_SCALED_ASPECT;
+ }
+ else if (strcmp (tmp, "stretched") == 0)
+ {
+ *placement = EEL_BACKGROUND_SCALED;
+ }
+ else if (strcmp (tmp, "zoom") == 0)
+ {
+ *placement = EEL_BACKGROUND_ZOOM;
+ }
+ else if (strcmp (tmp, "spanned") == 0)
+ {
+ *placement = EEL_BACKGROUND_SPANNED;
+ }
+ else if (strcmp (tmp, "none") == 0)
+ {
+ g_free (*image);
+
+ *placement = EEL_BACKGROUND_CENTERED;
+ *image = NULL;
+ }
+ else
+ {
+ *placement = EEL_BACKGROUND_CENTERED;
+ }
+ }
+ else
+ {
+ *placement = EEL_BACKGROUND_CENTERED;
+ }
+ g_free (tmp);
+
+ /* Get the color */
+ tmp = mateconf_client_get_string (client, BG_PREFERENCES_COLOR_SHADING_TYPE, NULL);
+ if (tmp != NULL)
+ {
+ if (strcmp (tmp, "solid") == 0)
+ {
+ use_gradient = FALSE;
+ is_horizontal = FALSE;
+ }
+ else if (strcmp (tmp, "vertical-gradient") == 0)
+ {
+ use_gradient = TRUE;
+ is_horizontal = FALSE;
+ }
+ else if (strcmp (tmp, "horizontal-gradient") == 0)
+ {
+ use_gradient = TRUE;
+ is_horizontal = TRUE;
+ }
+ else
+ {
+ use_gradient = FALSE;
+ is_horizontal = FALSE;
+ }
+ }
+ else
+ {
+ use_gradient = FALSE;
+ is_horizontal = FALSE;
+ }
+ g_free (tmp);
+
+ read_color (client, BG_PREFERENCES_PRIMARY_COLOR, &primary);
+ read_color (client, BG_PREFERENCES_SECONDARY_COLOR, &secondary);
+
+ start_color = eel_gdk_rgb_to_color_spec (eel_gdk_color_to_rgb (&primary));
+ end_color = eel_gdk_rgb_to_color_spec (eel_gdk_color_to_rgb (&secondary));
+
+ if (use_gradient)
+ {
+ *color = eel_gradient_new (start_color, end_color, is_horizontal);
+ }
+ else
+ {
+ *color = g_strdup (start_color);
+ }
+
+ g_free (start_color);
+ g_free (end_color);
+}
+
+static void
+caja_file_background_write_desktop_default_settings (void)
+{
+ /* We just unset all the mateconf keys so they go back to
+ * defaults
+ */
+ MateConfClient *client;
+ MateConfChangeSet *set;
+
+ client = mateconf_client_get_default ();
+ set = mateconf_change_set_new ();
+
+ /* the list of keys here has to be kept in sync with libmate
+ * schemas, which isn't the most maintainable thing ever.
+ */
+ mateconf_change_set_unset (set, "/desktop/mate/background/picture_options");
+ mateconf_change_set_unset (set, "/desktop/mate/background/picture_filename");
+ mateconf_change_set_unset (set, "/desktop/mate/background/picture_opacity");
+ mateconf_change_set_unset (set, "/desktop/mate/background/primary_color");
+ mateconf_change_set_unset (set, "/desktop/mate/background/secondary_color");
+ mateconf_change_set_unset (set, "/desktop/mate/background/color_shading_type");
+
+ /* this isn't atomic yet so it'll be a bit inefficient, but
+ * someday it might be atomic.
+ */
+ mateconf_client_commit_change_set (client, set, FALSE, NULL);
+
+ mateconf_change_set_unref (set);
+
+ g_object_unref (G_OBJECT (client));
+}
+
+static int
+call_settings_changed (EelBackground *background)
+{
+ CajaFile *file;
+ file = g_object_get_data (G_OBJECT (background), "eel_background_file");
+ if (file)
+ {
+ saved_settings_changed_callback (file, background);
+ }
+ g_object_set_data (G_OBJECT (background), "desktop_mateconf_notification_timeout", GUINT_TO_POINTER (0));
+ return FALSE;
+}
+
+static void
+desktop_background_destroyed_callback (EelBackground *background, void *georgeWBush)
+{
+ guint notification_id;
+ guint notification_timeout_id;
+
+ notification_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (background), "desktop_mateconf_notification"));
+ eel_mateconf_notification_remove (notification_id);
+
+ notification_timeout_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (background), "desktop_mateconf_notification_timeout"));
+ if (notification_timeout_id != 0)
+ {
+ g_source_remove (notification_timeout_id);
+ }
+}
+
+static void
+desktop_background_mateconf_notify_cb (MateConfClient *client, guint notification_id, MateConfEntry *entry, gpointer data)
+{
+ EelBackground *background;
+ guint notification_timeout_id;
+
+ background = EEL_BACKGROUND (data);
+
+ notification_timeout_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (background), "desktop_mateconf_notification_timeout"));
+
+ if (strcmp (entry->key, "/desktop/mate/background/stamp") == 0)
+ {
+ if (notification_timeout_id != 0)
+ g_source_remove (notification_timeout_id);
+
+ call_settings_changed (background);
+ }
+ else if (notification_timeout_id == 0)
+ {
+ notification_timeout_id = g_timeout_add (300, (GSourceFunc) call_settings_changed, background);
+
+ g_object_set_data (G_OBJECT (background), "desktop_mateconf_notification_timeout", GUINT_TO_POINTER (notification_timeout_id));
+ }
+}
+
+static void
+caja_file_background_receive_mateconf_changes (EelBackground *background)
+{
+ guint notification_id;
+
+ eel_mateconf_monitor_add ("/desktop/mate/background");
+ notification_id = eel_mateconf_notification_add ("/desktop/mate/background", desktop_background_mateconf_notify_cb, background);
+
+ g_object_set_data (G_OBJECT (background), "desktop_mateconf_notification", GUINT_TO_POINTER (notification_id));
+
+ g_signal_connect (background, "destroy",
+ G_CALLBACK (desktop_background_destroyed_callback), NULL);
+}
+
+/* return true if the background is not in the default state */
+gboolean
+caja_file_background_is_set (EelBackground *background)
+{
+ char *color;
+ char *image;
+
+ gboolean is_set;
+
+ color = eel_background_get_color (background);
+ image = eel_background_get_image_uri (background);
+
+ is_set = (color || image);
+
+ g_free (color);
+ g_free (image);
+
+ return is_set;
+}
+
+/* handle the background changed signal */
+static void
+background_changed_callback (EelBackground *background,
+ GdkDragAction action,
+ CajaFile *file)
+{
+ char *color;
+ char *image;
+
+ g_assert (EEL_IS_BACKGROUND (background));
+ g_assert (CAJA_IS_FILE (file));
+ g_assert (g_object_get_data (G_OBJECT (background), "eel_background_file") == file);
+
+
+ color = eel_background_get_color (background);
+ image = eel_background_get_image_uri (background);
+
+ if (eel_background_is_desktop (background))
+ {
+ eel_background_save_to_mateconf (background);
+ }
+ else
+ {
+ /* Block the other handler while we are writing metadata so it doesn't
+ * try to change the background.
+ */
+ g_signal_handlers_block_by_func (
+ file, G_CALLBACK (saved_settings_changed_callback), background);
+
+ if (action != (GdkDragAction) CAJA_DND_ACTION_SET_AS_FOLDER_BACKGROUND &&
+ action != (GdkDragAction) CAJA_DND_ACTION_SET_AS_GLOBAL_BACKGROUND)
+ {
+ GdkDragAction default_drag_action;
+
+ default_drag_action = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (background), "default_drag_action"));
+
+
+ action = default_drag_action;
+ }
+
+ if (action == (GdkDragAction) CAJA_DND_ACTION_SET_AS_GLOBAL_BACKGROUND)
+ {
+ caja_file_set_metadata (file,
+ CAJA_METADATA_KEY_LOCATION_BACKGROUND_COLOR,
+ NULL,
+ NULL);
+
+ caja_file_set_metadata (file,
+ CAJA_METADATA_KEY_LOCATION_BACKGROUND_IMAGE,
+ NULL,
+ NULL);
+
+ eel_preferences_set
+ (CAJA_PREFERENCES_BACKGROUND_COLOR, color ? color : "");
+ eel_preferences_set
+ (CAJA_PREFERENCES_BACKGROUND_FILENAME, image ? image : "");
+ eel_preferences_set_boolean
+ (CAJA_PREFERENCES_BACKGROUND_SET, TRUE);
+ }
+ else
+ {
+ caja_file_set_metadata (file,
+ CAJA_METADATA_KEY_LOCATION_BACKGROUND_COLOR,
+ NULL,
+ color);
+
+ caja_file_set_metadata (file,
+ CAJA_METADATA_KEY_LOCATION_BACKGROUND_IMAGE,
+ NULL,
+ image);
+ }
+
+ /* Unblock the handler. */
+ g_signal_handlers_unblock_by_func (
+ file, G_CALLBACK (saved_settings_changed_callback), background);
+ }
+
+ g_free (color);
+ g_free (image);
+}
+
+static void
+initialize_background_from_settings (CajaFile *file,
+ EelBackground *background)
+{
+ char *color;
+ char *image;
+ EelBackgroundImagePlacement placement;
+
+ g_assert (CAJA_IS_FILE (file));
+ g_assert (EEL_IS_BACKGROUND (background));
+ g_assert (g_object_get_data (G_OBJECT (background), "eel_background_file")
+ == file);
+
+ if (eel_background_is_desktop (background))
+ {
+ caja_file_background_read_desktop_settings (&color, &image, &placement);
+ }
+ else
+ {
+ color = caja_file_get_metadata (file,
+ CAJA_METADATA_KEY_LOCATION_BACKGROUND_COLOR,
+ NULL);
+ image = caja_file_get_metadata (file,
+ CAJA_METADATA_KEY_LOCATION_BACKGROUND_IMAGE,
+ NULL);
+ placement = EEL_BACKGROUND_TILED; /* non-tiled only avail for desktop, at least for now */
+
+ /* if there's none, read the default from the theme */
+ if (color == NULL && image == NULL)
+ {
+ caja_file_background_get_default_settings
+ (&color, &image, &placement);
+ }
+ }
+
+ /* Block the other handler while we are responding to changes
+ * in the metadata so it doesn't try to change the metadata.
+ */
+ g_signal_handlers_block_by_func
+ (background,
+ G_CALLBACK (background_changed_callback),
+ file);
+
+ eel_background_set_color (background, color);
+ eel_background_set_image_uri (background, image);
+ eel_background_set_image_placement (background, placement);
+
+ /* Unblock the handler. */
+ g_signal_handlers_unblock_by_func
+ (background,
+ G_CALLBACK (background_changed_callback),
+ file);
+
+ g_free (color);
+ g_free (image);
+}
+
+/* handle the file changed signal */
+static void
+saved_settings_changed_callback (CajaFile *file,
+ EelBackground *background)
+{
+ initialize_background_from_settings (file, background);
+}
+
+/* handle the theme changing */
+static void
+caja_file_background_theme_changed (gpointer user_data)
+{
+ CajaFile *file;
+ EelBackground *background;
+
+ background = EEL_BACKGROUND (user_data);
+ file = g_object_get_data (G_OBJECT (background), "eel_background_file");
+ if (file)
+ {
+ saved_settings_changed_callback (file, background);
+ }
+}
+
+/* handle the background reset signal by setting values from the current theme */
+static void
+background_reset_callback (EelBackground *background,
+ CajaFile *file)
+{
+ char *color;
+ char *image;
+
+ if (eel_background_is_desktop (background))
+ {
+ caja_file_background_write_desktop_default_settings ();
+ }
+ else
+ {
+ /* Block the other handler while we are writing metadata so it doesn't
+ * try to change the background.
+ */
+ g_signal_handlers_block_by_func (
+ file,
+ G_CALLBACK (saved_settings_changed_callback),
+ background);
+
+ color = caja_file_get_metadata (file,
+ CAJA_METADATA_KEY_LOCATION_BACKGROUND_COLOR,
+ NULL);
+ image = caja_file_get_metadata (file,
+ CAJA_METADATA_KEY_LOCATION_BACKGROUND_IMAGE,
+ NULL);
+ if (!color && !image)
+ {
+ eel_preferences_set_boolean (CAJA_PREFERENCES_BACKGROUND_SET,
+ FALSE);
+ }
+ else
+ {
+ /* reset the metadata */
+ caja_file_set_metadata (file,
+ CAJA_METADATA_KEY_LOCATION_BACKGROUND_COLOR,
+ NULL,
+ NULL);
+
+ caja_file_set_metadata (file,
+ CAJA_METADATA_KEY_LOCATION_BACKGROUND_IMAGE,
+ NULL,
+ NULL);
+ }
+ g_free (color);
+ g_free (image);
+
+ /* Unblock the handler. */
+ g_signal_handlers_unblock_by_func (
+ file,
+ G_CALLBACK (saved_settings_changed_callback),
+ background);
+ }
+
+ saved_settings_changed_callback (file, background);
+}
+
+/* handle the background destroyed signal */
+static void
+background_destroyed_callback (EelBackground *background,
+ CajaFile *file)
+{
+ g_signal_handlers_disconnect_by_func
+ (file,
+ G_CALLBACK (saved_settings_changed_callback), background);
+ caja_file_monitor_remove (file, background);
+ eel_preferences_remove_callback (CAJA_PREFERENCES_THEME,
+ caja_file_background_theme_changed,
+ background);
+ eel_preferences_remove_callback (CAJA_PREFERENCES_BACKGROUND_SET,
+ caja_file_background_theme_changed,
+ background);
+ eel_preferences_remove_callback (CAJA_PREFERENCES_BACKGROUND_COLOR,
+ caja_file_background_theme_changed,
+ background);
+ eel_preferences_remove_callback (CAJA_PREFERENCES_BACKGROUND_FILENAME,
+ caja_file_background_theme_changed,
+ background);
+}
+
+/* key routine that hooks up a background and location */
+void
+caja_connect_background_to_file_metadata (GtkWidget *widget,
+ CajaFile *file,
+ GdkDragAction default_drag_action)
+{
+ EelBackground *background;
+ gpointer old_file;
+
+ /* Get at the background object we'll be connecting. */
+ background = eel_get_widget_background (widget);
+
+ /* Check if it is already connected. */
+ old_file = g_object_get_data (G_OBJECT (background), "eel_background_file");
+ if (old_file == file)
+ {
+ return;
+ }
+
+ /* Disconnect old signal handlers. */
+ if (old_file != NULL)
+ {
+ g_assert (CAJA_IS_FILE (old_file));
+ g_signal_handlers_disconnect_by_func
+ (background,
+ G_CALLBACK (background_changed_callback), old_file);
+ g_signal_handlers_disconnect_by_func
+ (background,
+ G_CALLBACK (background_destroyed_callback), old_file);
+ g_signal_handlers_disconnect_by_func
+ (background,
+ G_CALLBACK (background_reset_callback), old_file);
+ g_signal_handlers_disconnect_by_func
+ (old_file,
+ G_CALLBACK (saved_settings_changed_callback), background);
+ caja_file_monitor_remove (old_file, background);
+ eel_preferences_remove_callback (CAJA_PREFERENCES_THEME,
+ caja_file_background_theme_changed,
+ background);
+ eel_preferences_remove_callback (CAJA_PREFERENCES_BACKGROUND_SET,
+ caja_file_background_theme_changed,
+ background);
+ eel_preferences_remove_callback (CAJA_PREFERENCES_BACKGROUND_COLOR,
+ caja_file_background_theme_changed,
+ background);
+ eel_preferences_remove_callback (CAJA_PREFERENCES_BACKGROUND_FILENAME,
+ caja_file_background_theme_changed,
+ background);
+
+ }
+
+ /* Attach the new directory. */
+ caja_file_ref (file);
+ g_object_set_data_full (G_OBJECT (background), "eel_background_file",
+ file, (GDestroyNotify) caja_file_unref);
+
+ g_object_set_data (G_OBJECT (background), "default_drag_action", GINT_TO_POINTER (default_drag_action));
+
+ /* Connect new signal handlers. */
+ if (file != NULL)
+ {
+ g_signal_connect_object (background, "settings_changed",
+ G_CALLBACK (background_changed_callback), file, 0);
+ g_signal_connect_object (background, "destroy",
+ G_CALLBACK (background_destroyed_callback), file, 0);
+ g_signal_connect_object (background, "reset",
+ G_CALLBACK (background_reset_callback), file, 0);
+ g_signal_connect_object (file, "changed",
+ G_CALLBACK (saved_settings_changed_callback), background, 0);
+
+ /* arrange to receive file metadata */
+ caja_file_monitor_add (file,
+ background,
+ CAJA_FILE_ATTRIBUTE_INFO);
+
+ /* arrange for notification when the theme changes */
+ eel_preferences_add_callback (CAJA_PREFERENCES_THEME,
+ caja_file_background_theme_changed, background);
+ eel_preferences_add_callback (CAJA_PREFERENCES_BACKGROUND_SET,
+ caja_file_background_theme_changed, background);
+ eel_preferences_add_callback (CAJA_PREFERENCES_BACKGROUND_COLOR,
+ caja_file_background_theme_changed, background);
+ eel_preferences_add_callback (CAJA_PREFERENCES_BACKGROUND_FILENAME,
+ caja_file_background_theme_changed, background);
+ }
+
+ /* Update the background based on the file metadata. */
+ initialize_background_from_settings (file, background);
+}
diff --git a/libcaja-private/caja-directory-background.h b/libcaja-private/caja-directory-background.h
new file mode 100644
index 00000000..a9a23bd7
--- /dev/null
+++ b/libcaja-private/caja-directory-background.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ caja-directory-background.h: Helper for the background of a widget
+ that is viewing a particular directory.
+
+ 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Darin Adler <[email protected]>
+*/
+
+#include <eel/eel-background.h>
+#include <libcaja-private/caja-file.h>
+#include <libcaja-private/caja-icon-container.h>
+
+void caja_connect_background_to_file_metadata (GtkWidget *widget,
+ CajaFile *file,
+ GdkDragAction default_drag_action);
+void caja_connect_desktop_background_to_file_metadata (CajaIconContainer *icon_container,
+ CajaFile *file);
+gboolean caja_file_background_is_set (EelBackground *background);
diff --git a/libcaja-private/caja-directory-notify.h b/libcaja-private/caja-directory-notify.h
new file mode 100644
index 00000000..82aa1f72
--- /dev/null
+++ b/libcaja-private/caja-directory-notify.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-directory-notify.h: Caja 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Darin Adler <[email protected]>
+*/
+
+#include <gdk/gdk.h>
+#include <libcaja-private/caja-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;
+} CajaFileChangesQueuePosition;
+
+/* Almost-public change notification calls */
+void caja_directory_notify_files_added (GList *files);
+void caja_directory_notify_files_moved (GList *file_pairs);
+void caja_directory_notify_files_changed (GList *files);
+void caja_directory_notify_files_removed (GList *files);
+
+void caja_directory_schedule_metadata_copy (GList *file_pairs);
+void caja_directory_schedule_metadata_move (GList *file_pairs);
+void caja_directory_schedule_metadata_remove (GList *files);
+
+/* Deprecated URI versions: to be converted */
+void caja_directory_notify_files_added_by_uri (GList *uris);
+void caja_directory_notify_files_changed_by_uri (GList *uris);
+void caja_directory_notify_files_moved_by_uri (GList *uri_pairs);
+void caja_directory_notify_files_removed_by_uri (GList *uris);
+
+void caja_directory_schedule_metadata_copy_by_uri (GList *uri_pairs);
+void caja_directory_schedule_metadata_move_by_uri (GList *uri_pairs);
+void caja_directory_schedule_metadata_remove_by_uri (GList *uris);
+void caja_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 caja_file_changed (CajaFile *file);
diff --git a/libcaja-private/caja-directory-private.h b/libcaja-private/caja-directory-private.h
new file mode 100644
index 00000000..207a5890
--- /dev/null
+++ b/libcaja-private/caja-directory-private.h
@@ -0,0 +1,249 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-directory-private.h: Caja 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Darin Adler <[email protected]>
+*/
+
+#include <gio/gio.h>
+#include <eel/eel-vfs-extensions.h>
+#include <libcaja-private/caja-directory.h>
+#include <libcaja-private/caja-file-queue.h>
+#include <libcaja-private/caja-file.h>
+#include <libcaja-private/caja-monitor.h>
+#include <libcaja-private/caja-idle-queue.h>
+#include <libcaja-extension/caja-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_TOP_LEFT_TEXT,
+ REQUEST_LARGE_TOP_LEFT_TEXT,
+ 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 CajaDirectoryDetails
+{
+ /* The location. */
+ GFile *location;
+
+ /* The file objects. */
+ CajaFile *as_file;
+ GList *file_list;
+ GHashTable *file_hash;
+
+ /* Queues of files needing some I/O done. */
+ CajaFileQueue *high_priority_queue;
+ CajaFileQueue *low_priority_queue;
+ CajaFileQueue *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;
+
+ CajaMonitor *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 MateVFSFileInfo'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;
+
+ CajaFile *deep_count_file;
+ DeepCountState *deep_count_in_progress;
+
+ MimeListState *mime_list_in_progress;
+
+ CajaFile *get_info_file;
+ GetInfoState *get_info_in_progress;
+
+ CajaFile *extension_info_file;
+ CajaInfoProvider *extension_info_provider;
+ CajaOperationHandle *extension_info_in_progress;
+ guint extension_info_idle;
+
+ ThumbnailState *thumbnail_state;
+
+ MountState *mount_state;
+
+ FilesystemInfoState *filesystem_info_state;
+
+ TopLeftTextReadState *top_left_read_state;
+
+ LinkInfoReadState *link_info_read_state;
+
+ GList *file_operations_in_progress; /* list of FileOperation * */
+
+ GHashTable *hidden_file_hash;
+
+ guint64 free_space; /* (guint)-1 for unknown */
+ time_t free_space_read; /* The time free_space was updated, or 0 for never */
+};
+
+CajaDirectory *caja_directory_get_existing (GFile *location);
+
+/* async. interface */
+void caja_directory_async_state_changed (CajaDirectory *directory);
+void caja_directory_call_when_ready_internal (CajaDirectory *directory,
+ CajaFile *file,
+ CajaFileAttributes file_attributes,
+ gboolean wait_for_file_list,
+ CajaDirectoryCallback directory_callback,
+ CajaFileCallback file_callback,
+ gpointer callback_data);
+gboolean caja_directory_check_if_ready_internal (CajaDirectory *directory,
+ CajaFile *file,
+ CajaFileAttributes file_attributes);
+void caja_directory_cancel_callback_internal (CajaDirectory *directory,
+ CajaFile *file,
+ CajaDirectoryCallback directory_callback,
+ CajaFileCallback file_callback,
+ gpointer callback_data);
+void caja_directory_monitor_add_internal (CajaDirectory *directory,
+ CajaFile *file,
+ gconstpointer client,
+ gboolean monitor_hidden_files,
+ gboolean monitor_backup_files,
+ CajaFileAttributes attributes,
+ CajaDirectoryCallback callback,
+ gpointer callback_data);
+void caja_directory_monitor_remove_internal (CajaDirectory *directory,
+ CajaFile *file,
+ gconstpointer client);
+void caja_directory_get_info_for_new_files (CajaDirectory *directory,
+ GList *vfs_uris);
+CajaFile * caja_directory_get_existing_corresponding_file (CajaDirectory *directory);
+void caja_directory_invalidate_count_and_mime_list (CajaDirectory *directory);
+gboolean caja_directory_is_file_list_monitored (CajaDirectory *directory);
+gboolean caja_directory_is_anyone_monitoring_file_list (CajaDirectory *directory);
+gboolean caja_directory_has_active_request_for_file (CajaDirectory *directory,
+ CajaFile *file);
+void caja_directory_remove_file_monitor_link (CajaDirectory *directory,
+ GList *link);
+void caja_directory_schedule_dequeue_pending (CajaDirectory *directory);
+void caja_directory_stop_monitoring_file_list (CajaDirectory *directory);
+void caja_directory_cancel (CajaDirectory *directory);
+void caja_async_destroying_file (CajaFile *file);
+void caja_directory_force_reload_internal (CajaDirectory *directory,
+ CajaFileAttributes file_attributes);
+void caja_directory_cancel_loading_file_attributes (CajaDirectory *directory,
+ CajaFile *file,
+ CajaFileAttributes file_attributes);
+
+/* Calls shared between directory, file, and async. code. */
+void caja_directory_emit_files_added (CajaDirectory *directory,
+ GList *added_files);
+void caja_directory_emit_files_changed (CajaDirectory *directory,
+ GList *changed_files);
+void caja_directory_emit_change_signals (CajaDirectory *directory,
+ GList *changed_files);
+void emit_change_signals_for_all_files (CajaDirectory *directory);
+void emit_change_signals_for_all_files_in_all_directories (void);
+void caja_directory_emit_done_loading (CajaDirectory *directory);
+void caja_directory_emit_load_error (CajaDirectory *directory,
+ GError *error);
+CajaDirectory *caja_directory_get_internal (GFile *location,
+ gboolean create);
+char * caja_directory_get_name_for_self_as_new_file (CajaDirectory *directory);
+Request caja_directory_set_up_request (CajaFileAttributes file_attributes);
+
+/* Interface to the file list. */
+CajaFile * caja_directory_find_file_by_name (CajaDirectory *directory,
+ const char *filename);
+CajaFile * caja_directory_find_file_by_internal_filename (CajaDirectory *directory,
+ const char *internal_filename);
+
+void caja_directory_add_file (CajaDirectory *directory,
+ CajaFile *file);
+void caja_directory_remove_file (CajaDirectory *directory,
+ CajaFile *file);
+FileMonitors * caja_directory_remove_file_monitors (CajaDirectory *directory,
+ CajaFile *file);
+void caja_directory_add_file_monitors (CajaDirectory *directory,
+ CajaFile *file,
+ FileMonitors *monitors);
+void caja_directory_add_file (CajaDirectory *directory,
+ CajaFile *file);
+GList * caja_directory_begin_file_name_change (CajaDirectory *directory,
+ CajaFile *file);
+void caja_directory_end_file_name_change (CajaDirectory *directory,
+ CajaFile *file,
+ GList *node);
+void caja_directory_moved (const char *from_uri,
+ const char *to_uri);
+/* Interface to the work queue. */
+
+void caja_directory_add_file_to_work_queue (CajaDirectory *directory,
+ CajaFile *file);
+void caja_directory_remove_file_from_work_queue (CajaDirectory *directory,
+ CajaFile *file);
+
+/* KDE compatibility hacks */
+
+void caja_set_kde_trash_name (const char *trash_dir);
+
+/* debugging functions */
+int caja_directory_number_outstanding (void);
diff --git a/libcaja-private/caja-directory.c b/libcaja-private/caja-directory.c
new file mode 100644
index 00000000..5c1aa80d
--- /dev/null
+++ b/libcaja-private/caja-directory.c
@@ -0,0 +1,2004 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-directory.c: Caja 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Darin Adler <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-directory-private.h"
+
+#include "caja-directory-notify.h"
+#include "caja-file-attributes.h"
+#include "caja-file-private.h"
+#include "caja-file-utilities.h"
+#include "caja-search-directory.h"
+#include "caja-global-preferences.h"
+#include "caja-lib-self-check-functions.h"
+#include "caja-marshal.h"
+#include "caja-metadata.h"
+#include "caja-desktop-directory.h"
+#include "caja-vfs-directory.h"
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-gtk-macros.h>
+#include <eel/eel-string.h>
+#include <gtk/gtk.h>
+
+enum
+{
+ FILES_ADDED,
+ FILES_CHANGED,
+ DONE_LOADING,
+ LOAD_ERROR,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+static GHashTable *directories;
+
+static void caja_directory_finalize (GObject *object);
+static void caja_directory_init (gpointer object,
+ gpointer klass);
+static void caja_directory_class_init (CajaDirectoryClass *klass);
+static CajaDirectory *caja_directory_new (GFile *location);
+static char * real_get_name_for_self_as_new_file (CajaDirectory *directory);
+static GList * real_get_file_list (CajaDirectory *directory);
+static gboolean real_is_editable (CajaDirectory *directory);
+static void set_directory_location (CajaDirectory *directory,
+ GFile *location);
+
+EEL_CLASS_BOILERPLATE (CajaDirectory,
+ caja_directory,
+ G_TYPE_OBJECT)
+
+static void
+caja_directory_class_init (CajaDirectoryClass *klass)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = caja_directory_finalize;
+
+ signals[FILES_ADDED] =
+ g_signal_new ("files_added",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaDirectoryClass, 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 (CajaDirectoryClass, 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 (CajaDirectoryClass, 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 (CajaDirectoryClass, load_error),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1, G_TYPE_POINTER);
+
+ klass->get_name_for_self_as_new_file = real_get_name_for_self_as_new_file;
+ klass->get_file_list = real_get_file_list;
+ klass->is_editable = real_is_editable;
+
+ g_type_class_add_private (klass, sizeof (CajaDirectoryDetails));
+}
+
+static void
+caja_directory_init (gpointer object, gpointer klass)
+{
+ CajaDirectory *directory;
+
+ directory = CAJA_DIRECTORY(object);
+
+ directory->details = G_TYPE_INSTANCE_GET_PRIVATE ((directory), CAJA_TYPE_DIRECTORY, CajaDirectoryDetails);
+ directory->details->file_hash = g_hash_table_new (g_str_hash, g_str_equal);
+ directory->details->high_priority_queue = caja_file_queue_new ();
+ directory->details->low_priority_queue = caja_file_queue_new ();
+ directory->details->extension_queue = caja_file_queue_new ();
+ directory->details->free_space = (guint64)-1;
+}
+
+CajaDirectory *
+caja_directory_ref (CajaDirectory *directory)
+{
+ if (directory == NULL)
+ {
+ return directory;
+ }
+
+ g_return_val_if_fail (CAJA_IS_DIRECTORY (directory), NULL);
+
+ g_object_ref (directory);
+ return directory;
+}
+
+void
+caja_directory_unref (CajaDirectory *directory)
+{
+ if (directory == NULL)
+ {
+ return;
+ }
+
+ g_return_if_fail (CAJA_IS_DIRECTORY (directory));
+
+ g_object_unref (directory);
+}
+
+static void
+caja_directory_finalize (GObject *object)
+{
+ CajaDirectory *directory;
+
+ directory = CAJA_DIRECTORY (object);
+
+ g_hash_table_remove (directories, directory->details->location);
+
+ caja_directory_cancel (directory);
+ g_assert (directory->details->count_in_progress == NULL);
+ g_assert (directory->details->top_left_read_state == NULL);
+
+ if (directory->details->monitor_list != NULL)
+ {
+ g_warning ("destroying a CajaDirectory while it's being monitored");
+ eel_g_list_free_deep (directory->details->monitor_list);
+ }
+
+ if (directory->details->monitor != NULL)
+ {
+ caja_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);
+
+ if (directory->details->hidden_file_hash)
+ {
+ g_hash_table_destroy (directory->details->hidden_file_hash);
+ }
+
+ caja_file_queue_destroy (directory->details->high_priority_queue);
+ caja_file_queue_destroy (directory->details->low_priority_queue);
+ caja_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);
+ eel_g_object_list_free (directory->details->pending_file_info);
+
+ EEL_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
+}
+
+static void
+invalidate_one_count (gpointer key, gpointer value, gpointer user_data)
+{
+ CajaDirectory *directory;
+
+ g_assert (key != NULL);
+ g_assert (CAJA_IS_DIRECTORY (value));
+ g_assert (user_data == NULL);
+
+ directory = CAJA_DIRECTORY (value);
+
+ caja_directory_invalidate_count_and_mime_list (directory);
+}
+
+static void
+filtering_changed_callback (gpointer callback_data)
+{
+ g_assert (callback_data == NULL);
+
+ /* Preference about which items to show has changed, so we
+ * can't trust any of our precomputed directory counts.
+ */
+ g_hash_table_foreach (directories, invalidate_one_count, NULL);
+}
+
+void
+emit_change_signals_for_all_files (CajaDirectory *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);
+ }
+
+ caja_file_list_ref (files);
+ caja_directory_emit_change_signals (directory, files);
+
+ caja_file_list_free (files);
+}
+
+static void
+collect_all_directories (gpointer key, gpointer value, gpointer callback_data)
+{
+ CajaDirectory *directory;
+ GList **dirs;
+ GFile *location;
+
+ location = (GFile *) key;
+ directory = CAJA_DIRECTORY (value);
+ dirs = callback_data;
+
+ *dirs = g_list_prepend (*dirs, caja_directory_ref (directory));
+}
+
+void
+emit_change_signals_for_all_files_in_all_directories (void)
+{
+ GList *dirs, *l;
+ CajaDirectory *directory;
+
+ dirs = NULL;
+ g_hash_table_foreach (directories,
+ collect_all_directories,
+ &dirs);
+
+ for (l = dirs; l != NULL; l = l->next)
+ {
+ directory = CAJA_DIRECTORY (l->data);
+ emit_change_signals_for_all_files (directory);
+ caja_directory_unref (directory);
+ }
+
+ g_list_free (dirs);
+}
+
+static void
+async_state_changed_one (gpointer key, gpointer value, gpointer user_data)
+{
+ CajaDirectory *directory;
+
+ g_assert (key != NULL);
+ g_assert (CAJA_IS_DIRECTORY (value));
+ g_assert (user_data == NULL);
+
+ directory = CAJA_DIRECTORY (value);
+
+ caja_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)
+{
+ caja_global_preferences_init ();
+
+ eel_preferences_add_callback (CAJA_PREFERENCES_SHOW_HIDDEN_FILES,
+ filtering_changed_callback,
+ NULL);
+ eel_preferences_add_callback (CAJA_PREFERENCES_SHOW_BACKUP_FILES,
+ filtering_changed_callback,
+ NULL);
+ eel_preferences_add_callback (CAJA_PREFERENCES_SHOW_TEXT_IN_ICONS,
+ async_data_preference_changed_callback,
+ NULL);
+ eel_preferences_add_callback (CAJA_PREFERENCES_SHOW_DIRECTORY_ITEM_COUNTS,
+ async_data_preference_changed_callback,
+ NULL);
+ eel_preferences_add_callback (CAJA_PREFERENCES_DATE_FORMAT,
+ async_data_preference_changed_callback,
+ NULL);
+}
+
+/**
+ * caja_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.
+ */
+CajaDirectory *
+caja_directory_get_internal (GFile *location, gboolean create)
+{
+ CajaDirectory *directory;
+
+ /* Create the hash table first time through. */
+ if (directories == NULL)
+ {
+ directories = eel_g_hash_table_new_free_at_exit
+ (g_file_hash, (GCompareFunc)g_file_equal, "caja-directory.c: directories");
+
+ 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)
+ {
+ caja_directory_ref (directory);
+ }
+ else if (create)
+ {
+ /* Create a new directory object instead. */
+ directory = caja_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;
+}
+
+CajaDirectory *
+caja_directory_get (GFile *location)
+{
+ if (location == NULL)
+ {
+ return NULL;
+ }
+
+ return caja_directory_get_internal (location, TRUE);
+}
+
+CajaDirectory *
+caja_directory_get_existing (GFile *location)
+{
+ if (location == NULL)
+ {
+ return NULL;
+ }
+
+ return caja_directory_get_internal (location, FALSE);
+}
+
+
+CajaDirectory *
+caja_directory_get_by_uri (const char *uri)
+{
+ CajaDirectory *directory;
+ GFile *location;
+
+ if (uri == NULL)
+ {
+ return NULL;
+ }
+
+ location = g_file_new_for_uri (uri);
+
+ directory = caja_directory_get_internal (location, TRUE);
+ g_object_unref (location);
+ return directory;
+}
+
+CajaDirectory *
+caja_directory_get_for_file (CajaFile *file)
+{
+ char *uri;
+ CajaDirectory *directory;
+
+ g_return_val_if_fail (CAJA_IS_FILE (file), NULL);
+
+ uri = caja_file_get_uri (file);
+ directory = caja_directory_get_by_uri (uri);
+ g_free (uri);
+ return directory;
+}
+
+/* Returns a reffed CajaFile object for this directory.
+ */
+CajaFile *
+caja_directory_get_corresponding_file (CajaDirectory *directory)
+{
+ CajaFile *file;
+ char *uri;
+
+ file = caja_directory_get_existing_corresponding_file (directory);
+ if (file == NULL)
+ {
+ uri = caja_directory_get_uri (directory);
+ file = caja_file_get_by_uri (uri);
+ g_free (uri);
+ }
+
+ return file;
+}
+
+/* Returns a reffed CajaFile object for this directory, but only if the
+ * CajaFile object has already been created.
+ */
+CajaFile *
+caja_directory_get_existing_corresponding_file (CajaDirectory *directory)
+{
+ CajaFile *file;
+ char *uri;
+
+ file = directory->details->as_file;
+ if (file != NULL)
+ {
+ caja_file_ref (file);
+ return file;
+ }
+
+ uri = caja_directory_get_uri (directory);
+ file = caja_file_get_existing_by_uri (uri);
+ g_free (uri);
+ return file;
+}
+
+/* caja_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 CajaDirectory.
+ */
+char *
+caja_directory_get_name_for_self_as_new_file (CajaDirectory *directory)
+{
+ g_return_val_if_fail (CAJA_IS_DIRECTORY (directory), NULL);
+
+ return EEL_CALL_METHOD_WITH_RETURN_VALUE
+ (CAJA_DIRECTORY_CLASS, directory,
+ get_name_for_self_as_new_file, (directory));
+}
+
+static char *
+real_get_name_for_self_as_new_file (CajaDirectory *directory)
+{
+ char *directory_uri;
+ char *name, *colon;
+
+ directory_uri = caja_directory_get_uri (directory);
+
+ colon = strchr (directory_uri, ':');
+ if (colon == NULL || colon == directory_uri)
+ {
+ name = g_strdup (directory_uri);
+ }
+ else
+ {
+ name = g_strndup (directory_uri, colon - directory_uri);
+ }
+ g_free (directory_uri);
+
+ return name;
+}
+
+char *
+caja_directory_get_uri (CajaDirectory *directory)
+{
+ g_return_val_if_fail (CAJA_IS_DIRECTORY (directory), NULL);
+
+ return g_file_get_uri (directory->details->location);
+}
+
+GFile *
+caja_directory_get_location (CajaDirectory *directory)
+{
+ g_return_val_if_fail (CAJA_IS_DIRECTORY (directory), NULL);
+
+ return g_object_ref (directory->details->location);
+}
+
+static CajaDirectory *
+caja_directory_new (GFile *location)
+{
+ CajaDirectory *directory;
+ char *uri;
+
+ uri = g_file_get_uri (location);
+
+ if (eel_uri_is_desktop (uri))
+ {
+ directory = CAJA_DIRECTORY (g_object_new (CAJA_TYPE_DESKTOP_DIRECTORY, NULL));
+ }
+ else if (eel_uri_is_search (uri))
+ {
+ directory = CAJA_DIRECTORY (g_object_new (CAJA_TYPE_SEARCH_DIRECTORY, NULL));
+ }
+ else if (g_str_has_suffix (uri, CAJA_SAVED_SEARCH_EXTENSION))
+ {
+ directory = CAJA_DIRECTORY (caja_search_directory_new_from_saved_search (uri));
+ }
+ else
+ {
+ directory = CAJA_DIRECTORY (g_object_new (CAJA_TYPE_VFS_DIRECTORY, NULL));
+ }
+
+ set_directory_location (directory, location);
+
+ g_free (uri);
+
+ return directory;
+}
+
+gboolean
+caja_directory_is_local (CajaDirectory *directory)
+{
+ g_return_val_if_fail (CAJA_IS_DIRECTORY (directory), FALSE);
+
+ if (directory->details->location == NULL)
+ {
+ return TRUE;
+ }
+
+ return caja_directory_is_in_trash (directory) ||
+ g_file_is_native (directory->details->location);
+}
+
+gboolean
+caja_directory_is_in_trash (CajaDirectory *directory)
+{
+ g_assert (CAJA_IS_DIRECTORY (directory));
+
+ if (directory->details->location == NULL)
+ {
+ return FALSE;
+ }
+
+ return g_file_has_uri_scheme (directory->details->location, "trash");
+}
+
+gboolean
+caja_directory_are_all_files_seen (CajaDirectory *directory)
+{
+ g_return_val_if_fail (CAJA_IS_DIRECTORY (directory), FALSE);
+
+ return EEL_CALL_METHOD_WITH_RETURN_VALUE
+ (CAJA_DIRECTORY_CLASS, directory,
+ are_all_files_seen, (directory));
+}
+
+static void
+add_to_hash_table (CajaDirectory *directory, CajaFile *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 (CajaDirectory *directory, CajaFile *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
+caja_directory_add_file (CajaDirectory *directory, CajaFile *file)
+{
+ GList *node;
+ gboolean add_to_work_queue;
+
+ g_assert (CAJA_IS_DIRECTORY (directory));
+ g_assert (CAJA_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 (caja_directory_is_file_list_monitored (directory))
+ {
+ /* Ref if we are monitoring, since monitoring owns the file list. */
+ caja_file_ref (file);
+ add_to_work_queue = TRUE;
+ }
+ else if (caja_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)
+ {
+ caja_directory_add_file_to_work_queue (directory, file);
+ }
+}
+
+void
+caja_directory_remove_file (CajaDirectory *directory, CajaFile *file)
+{
+ GList *node;
+
+ g_assert (CAJA_IS_DIRECTORY (directory));
+ g_assert (CAJA_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);
+
+ caja_directory_remove_file_from_work_queue (directory, file);
+
+ if (!file->details->unconfirmed)
+ {
+ directory->details->confirmed_file_count--;
+ }
+
+ /* Unref if we are monitoring. */
+ if (caja_directory_is_file_list_monitored (directory))
+ {
+ caja_file_unref (file);
+ }
+}
+
+#define CAJA_DIRECTORY_FILE_LIST_DEFAULT_LIMIT 4000
+
+gboolean
+caja_directory_file_list_length_reached (CajaDirectory *directory)
+{
+ static gboolean inited = FALSE;
+ static int directory_limit = 0;
+
+ if (!inited)
+ {
+ eel_preferences_add_auto_integer
+ ("/apps/caja/preferences/directory_limit",
+ &directory_limit);
+ inited = TRUE;
+ }
+ if (directory_limit < 0) /* unlimited */
+ {
+ return FALSE;
+ }
+ if (directory_limit == 0) /* dead mateconfd */
+ {
+ directory_limit = CAJA_DIRECTORY_FILE_LIST_DEFAULT_LIMIT;
+ }
+
+ return directory->details->confirmed_file_count >= directory_limit;
+}
+
+GList *
+caja_directory_begin_file_name_change (CajaDirectory *directory,
+ CajaFile *file)
+{
+ /* Find the list node in the hash table. */
+ return extract_from_hash_table (directory, file);
+}
+
+void
+caja_directory_end_file_name_change (CajaDirectory *directory,
+ CajaFile *file,
+ GList *node)
+{
+ /* Add the list node to the hash table. */
+ if (node != NULL)
+ {
+ add_to_hash_table (directory, file, node);
+ }
+}
+
+CajaFile *
+caja_directory_find_file_by_name (CajaDirectory *directory,
+ const char *name)
+{
+ GList *node;
+
+ g_return_val_if_fail (CAJA_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 : CAJA_FILE (node->data);
+}
+
+/* "." for the directory-as-file, otherwise the filename */
+CajaFile *
+caja_directory_find_file_by_internal_filename (CajaDirectory *directory,
+ const char *internal_filename)
+{
+ CajaFile *result;
+
+ if (eel_strcmp (internal_filename, ".") == 0)
+ {
+ result = caja_directory_get_existing_corresponding_file (directory);
+ if (result != NULL)
+ {
+ caja_file_unref (result);
+ }
+ }
+ else
+ {
+ result = caja_directory_find_file_by_name (directory, internal_filename);
+ }
+
+ return result;
+}
+
+void
+caja_directory_emit_files_added (CajaDirectory *directory,
+ GList *added_files)
+{
+ if (added_files != NULL)
+ {
+ g_signal_emit (directory,
+ signals[FILES_ADDED], 0,
+ added_files);
+ }
+}
+
+void
+caja_directory_emit_files_changed (CajaDirectory *directory,
+ GList *changed_files)
+{
+ if (changed_files != NULL)
+ {
+ g_signal_emit (directory,
+ signals[FILES_CHANGED], 0,
+ changed_files);
+ }
+}
+
+void
+caja_directory_emit_change_signals (CajaDirectory *directory,
+ GList *changed_files)
+{
+ GList *p;
+
+ for (p = changed_files; p != NULL; p = p->next)
+ {
+ caja_file_emit_changed (p->data);
+ }
+ caja_directory_emit_files_changed (directory, changed_files);
+}
+
+void
+caja_directory_emit_done_loading (CajaDirectory *directory)
+{
+ g_signal_emit (directory,
+ signals[DONE_LOADING], 0);
+}
+
+void
+caja_directory_emit_load_error (CajaDirectory *directory,
+ GError *error)
+{
+ g_signal_emit (directory,
+ signals[LOAD_ERROR], 0,
+ error);
+}
+
+/* Return a directory object for this one's parent. */
+static CajaDirectory *
+get_parent_directory (GFile *location)
+{
+ CajaDirectory *directory;
+ GFile *parent;
+
+ parent = g_file_get_parent (location);
+ if (parent)
+ {
+ directory = caja_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 CajaDirectory *
+get_parent_directory_if_exists (GFile *location)
+{
+ CajaDirectory *directory;
+ GFile *parent;
+
+ parent = g_file_get_parent (location);
+ if (parent)
+ {
+ directory = caja_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 (CAJA_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 (CajaDirectory *directory, GList *file_list)
+{
+ GList *node;
+ CajaFile *file;
+
+ for (node = file_list; node != NULL; node = node->next)
+ {
+ file = node->data;
+ if (file->details->directory == directory)
+ {
+ caja_directory_add_file_to_work_queue (directory,
+ file);
+ }
+ }
+ caja_directory_async_state_changed (directory);
+ caja_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 (CAJA_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 (CAJA_DIRECTORY (key), value);
+ caja_file_list_free (value);
+}
+
+static void
+call_get_file_info_free_list (gpointer key, gpointer value, gpointer user_data)
+{
+ CajaDirectory *directory;
+ GList *files;
+
+ g_assert (CAJA_IS_DIRECTORY (key));
+ g_assert (value != NULL);
+ g_assert (user_data == NULL);
+
+ directory = key;
+ files = value;
+
+ caja_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 (CAJA_IS_DIRECTORY (key));
+ g_assert (value == key);
+ g_assert (user_data == NULL);
+
+ caja_directory_invalidate_count_and_mime_list (key);
+ caja_directory_unref (key);
+}
+
+static void
+collect_parent_directories (GHashTable *hash_table, CajaDirectory *directory)
+{
+ g_assert (hash_table != NULL);
+ g_assert (CAJA_IS_DIRECTORY (directory));
+
+ if (g_hash_table_lookup (hash_table, directory) == NULL)
+ {
+ caja_directory_ref (directory);
+ g_hash_table_insert (hash_table, directory, directory);
+ }
+}
+
+void
+caja_directory_notify_files_added (GList *files)
+{
+ GHashTable *added_lists;
+ GList *p;
+ CajaDirectory *directory;
+ GHashTable *parent_directories;
+ CajaFile *file;
+ GFile *location, *parent;
+
+ /* 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 = caja_file_get_existing (parent);
+ g_object_unref (parent);
+ }
+
+ if (file != NULL)
+ {
+ caja_file_invalidate_count_and_mime_list (file);
+ caja_file_unref (file);
+ }
+
+ continue;
+ }
+
+ collect_parent_directories (parent_directories, directory);
+
+ /* If no one is monitoring files in the directory, nothing to do. */
+ if (!caja_directory_is_file_list_monitored (directory))
+ {
+ caja_directory_unref (directory);
+ continue;
+ }
+
+ file = caja_file_get_existing (location);
+ /* We check is_added here, because the file could have been added
+ * to the directory by a caja_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 */
+ caja_file_changed (file);
+ caja_file_unref (file);
+ }
+ else
+ {
+ hash_table_list_prepend (added_lists,
+ directory,
+ g_object_ref (location));
+ }
+ caja_directory_unref (directory);
+ }
+
+ /* Now get file info for the new files. This creates CajaFile
+ * 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);
+}
+
+static void
+g_file_pair_free (GFilePair *pair)
+{
+ g_object_unref (pair->to);
+ g_object_unref (pair->from);
+ g_free (pair);
+}
+
+static GList *
+uri_pairs_to_file_pairs (GList *uri_pairs)
+{
+ GList *l, *file_pair_list;
+ GFilePair *file_pair;
+ URIPair *uri_pair;
+
+ file_pair_list = NULL;
+
+ for (l = uri_pairs; l != NULL; l = l->next)
+ {
+ uri_pair = l->data;
+ file_pair = g_new (GFilePair, 1);
+ file_pair->from = g_file_new_for_uri (uri_pair->from_uri);
+ file_pair->to = g_file_new_for_uri (uri_pair->to_uri);
+
+ file_pair_list = g_list_prepend (file_pair_list, file_pair);
+ }
+ return g_list_reverse (file_pair_list);
+}
+
+void
+caja_directory_notify_files_added_by_uri (GList *uris)
+{
+ GList *files;
+
+ files = caja_file_list_from_uris (uris);
+ caja_directory_notify_files_added (files);
+ eel_g_object_list_free (files);
+}
+
+void
+caja_directory_notify_files_changed (GList *files)
+{
+ GHashTable *changed_lists;
+ GList *node;
+ GFile *location;
+ CajaFile *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 = caja_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->top_left_text_is_up_to_date = FALSE;
+ file->details->link_info_is_up_to_date = FALSE;
+ caja_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
+caja_directory_notify_files_changed_by_uri (GList *uris)
+{
+ GList *files;
+
+ files = caja_file_list_from_uris (uris);
+ caja_directory_notify_files_changed (files);
+ eel_g_object_list_free (files);
+}
+
+void
+caja_directory_notify_files_removed (GList *files)
+{
+ GHashTable *changed_lists;
+ GList *p;
+ CajaDirectory *directory;
+ GHashTable *parent_directories;
+ CajaFile *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);
+ caja_directory_unref (directory);
+ }
+
+ /* Find the file. */
+ file = caja_file_get_existing (location);
+ if (file != NULL && !caja_file_rename_in_progress (file))
+ {
+ /* Mark it gone and prepare to send the changed signal. */
+ caja_file_mark_gone (file);
+ hash_table_list_prepend (changed_lists,
+ file->details->directory,
+ caja_file_ref (file));
+ }
+ caja_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);
+}
+
+void
+caja_directory_notify_files_removed_by_uri (GList *uris)
+{
+ GList *files;
+
+ files = caja_file_list_from_uris (uris);
+ caja_directory_notify_files_changed (files);
+ eel_g_object_list_free (files);
+}
+
+static void
+set_directory_location (CajaDirectory *directory,
+ GFile *location)
+{
+ if (directory->details->location)
+ {
+ g_object_unref (directory->details->location);
+ }
+ directory->details->location = g_object_ref (location);
+
+}
+
+static void
+change_directory_location (CajaDirectory *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)
+{
+ CajaDirectory *directory;
+ CollectData *collect_data;
+ GFile *location;
+
+ location = (GFile *) key;
+ directory = CAJA_DIRECTORY (value);
+ collect_data = (CollectData *) callback_data;
+
+ if (g_file_has_prefix (location, collect_data->container) ||
+ g_file_equal (collect_data->container, location))
+ {
+ caja_directory_ref (directory);
+ collect_data->directories =
+ g_list_prepend (collect_data->directories,
+ directory);
+ }
+}
+
+static GList *
+caja_directory_moved_internal (GFile *old_location,
+ GFile *new_location)
+{
+ CollectData collection;
+ CajaDirectory *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 = CAJA_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,
+ caja_file_ref (directory->details->as_file));
+ }
+ affected_files = g_list_concat
+ (affected_files,
+ caja_file_list_copy (directory->details->file_list));
+ }
+
+ caja_directory_unref (directory);
+ }
+
+ g_list_free (collection.directories);
+
+ return affected_files;
+}
+
+void
+caja_directory_moved (const char *old_uri,
+ const char *new_uri)
+{
+ GList *list, *node;
+ GHashTable *hash;
+ CajaFile *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 = caja_directory_moved_internal (old_location, new_location);
+ for (node = list; node != NULL; node = node->next)
+ {
+ file = CAJA_FILE (node->data);
+ hash_table_list_prepend (hash,
+ file->details->directory,
+ caja_file_ref (file));
+ }
+ caja_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
+caja_directory_notify_files_moved (GList *file_pairs)
+{
+ GList *p, *affected_files, *node;
+ GFilePair *pair;
+ CajaFile *file;
+ CajaDirectory *old_directory, *new_directory;
+ GHashTable *parent_directories;
+ GList *new_files_list, *unref_list;
+ GHashTable *added_lists, *changed_lists;
+ char *name;
+ CajaFileAttributes 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 = caja_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 = caja_file_get_existing (to_location);
+ if (file != NULL)
+ {
+ /* Mark it gone and prepare to send the changed signal. */
+ caja_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 = caja_directory_moved_internal (from_location,
+ to_location);
+ for (node = affected_files; node != NULL; node = node->next)
+ {
+ file = CAJA_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 = caja_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 */
+ caja_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.
+ */
+ caja_directory_unref (new_directory);
+
+ /* Update the file's name and directory. */
+ name = g_file_get_basename (to_location);
+ caja_file_update_name_and_directory
+ (file, name, new_directory);
+ g_free (name);
+
+ /* Update file attributes */
+ caja_file_invalidate_attributes (file, CAJA_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 caja_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. */
+ caja_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. */
+ caja_directory_notify_files_added (new_files_list);
+ g_list_free (new_files_list);
+}
+
+void
+caja_directory_notify_files_moved_by_uri (GList *uri_pairs)
+{
+ GList *file_pairs;
+
+ file_pairs = uri_pairs_to_file_pairs (uri_pairs);
+ caja_directory_notify_files_moved (file_pairs);
+ g_list_foreach (file_pairs, (GFunc)g_file_pair_free, NULL);
+ g_list_free (file_pairs);
+}
+
+void
+caja_directory_schedule_position_set (GList *position_setting_list)
+{
+ GList *p;
+ const CajaFileChangesQueuePosition *item;
+ CajaFile *file;
+ char str[64];
+ time_t now;
+
+ time (&now);
+
+ for (p = position_setting_list; p != NULL; p = p->next)
+ {
+ item = (CajaFileChangesQueuePosition *) p->data;
+
+ file = caja_file_get (item->location);
+
+ if (item->set)
+ {
+ g_snprintf (str, sizeof (str), "%d,%d", item->point.x, item->point.y);
+ }
+ else
+ {
+ str[0] = 0;
+ }
+ caja_file_set_metadata
+ (file,
+ CAJA_METADATA_KEY_ICON_POSITION,
+ NULL,
+ str);
+
+ if (item->set)
+ {
+ caja_file_set_time_metadata
+ (file,
+ CAJA_METADATA_KEY_ICON_POSITION_TIMESTAMP,
+ now);
+ }
+ else
+ {
+ caja_file_set_time_metadata
+ (file,
+ CAJA_METADATA_KEY_ICON_POSITION_TIMESTAMP,
+ UNDEFINED_TIME);
+ }
+
+ if (item->set)
+ {
+ g_snprintf (str, sizeof (str), "%d", item->screen);
+ }
+ else
+ {
+ str[0] = 0;
+ }
+ caja_file_set_metadata
+ (file,
+ CAJA_METADATA_KEY_SCREEN,
+ NULL,
+ str);
+
+ caja_file_unref (file);
+ }
+}
+
+gboolean
+caja_directory_contains_file (CajaDirectory *directory,
+ CajaFile *file)
+{
+ g_return_val_if_fail (CAJA_IS_DIRECTORY (directory), FALSE);
+ g_return_val_if_fail (CAJA_IS_FILE (file), FALSE);
+
+ if (caja_file_is_gone (file))
+ {
+ return FALSE;
+ }
+
+ return EEL_CALL_METHOD_WITH_RETURN_VALUE
+ (CAJA_DIRECTORY_CLASS, directory,
+ contains_file, (directory, file));
+}
+
+char *
+caja_directory_get_file_uri (CajaDirectory *directory,
+ const char *file_name)
+{
+ GFile *child;
+ char *result;
+
+ g_return_val_if_fail (CAJA_IS_DIRECTORY (directory), NULL);
+ g_return_val_if_fail (file_name != NULL, NULL);
+
+ result = NULL;
+
+ child = g_file_get_child (directory->details->location, file_name);
+ result = g_file_get_uri (child);
+ g_object_unref (child);
+
+ return result;
+}
+
+void
+caja_directory_call_when_ready (CajaDirectory *directory,
+ CajaFileAttributes file_attributes,
+ gboolean wait_for_all_files,
+ CajaDirectoryCallback callback,
+ gpointer callback_data)
+{
+ g_return_if_fail (CAJA_IS_DIRECTORY (directory));
+ g_return_if_fail (callback != NULL);
+
+ EEL_CALL_METHOD
+ (CAJA_DIRECTORY_CLASS, directory,
+ call_when_ready, (directory, file_attributes, wait_for_all_files,
+ callback, callback_data));
+}
+
+void
+caja_directory_cancel_callback (CajaDirectory *directory,
+ CajaDirectoryCallback callback,
+ gpointer callback_data)
+{
+ g_return_if_fail (CAJA_IS_DIRECTORY (directory));
+ g_return_if_fail (callback != NULL);
+
+ EEL_CALL_METHOD
+ (CAJA_DIRECTORY_CLASS, directory,
+ cancel_callback, (directory, callback, callback_data));
+}
+
+void
+caja_directory_file_monitor_add (CajaDirectory *directory,
+ gconstpointer client,
+ gboolean monitor_hidden_files,
+ gboolean monitor_backup_files,
+ CajaFileAttributes file_attributes,
+ CajaDirectoryCallback callback,
+ gpointer callback_data)
+{
+ g_return_if_fail (CAJA_IS_DIRECTORY (directory));
+ g_return_if_fail (client != NULL);
+
+ EEL_CALL_METHOD
+ (CAJA_DIRECTORY_CLASS, directory,
+ file_monitor_add, (directory, client,
+ monitor_hidden_files,
+ monitor_backup_files,
+ file_attributes,
+ callback, callback_data));
+}
+
+void
+caja_directory_file_monitor_remove (CajaDirectory *directory,
+ gconstpointer client)
+{
+ g_return_if_fail (CAJA_IS_DIRECTORY (directory));
+ g_return_if_fail (client != NULL);
+
+ EEL_CALL_METHOD
+ (CAJA_DIRECTORY_CLASS, directory,
+ file_monitor_remove, (directory, client));
+}
+
+void
+caja_directory_force_reload (CajaDirectory *directory)
+{
+ g_return_if_fail (CAJA_IS_DIRECTORY (directory));
+
+ EEL_CALL_METHOD
+ (CAJA_DIRECTORY_CLASS, directory,
+ force_reload, (directory));
+}
+
+gboolean
+caja_directory_is_not_empty (CajaDirectory *directory)
+{
+ g_return_val_if_fail (CAJA_IS_DIRECTORY (directory), FALSE);
+
+ return EEL_CALL_METHOD_WITH_RETURN_VALUE
+ (CAJA_DIRECTORY_CLASS, directory,
+ is_not_empty, (directory));
+}
+
+static gboolean
+is_tentative (gpointer data, gpointer callback_data)
+{
+ CajaFile *file;
+
+ g_assert (callback_data == NULL);
+
+ file = CAJA_FILE (data);
+ /* 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 *
+caja_directory_get_file_list (CajaDirectory *directory)
+{
+ return EEL_CALL_METHOD_WITH_RETURN_VALUE
+ (CAJA_DIRECTORY_CLASS, directory,
+ get_file_list, (directory));
+}
+
+static GList *
+real_get_file_list (CajaDirectory *directory)
+{
+ GList *tentative_files, *non_tentative_files;
+
+ tentative_files = eel_g_list_partition
+ (g_list_copy (directory->details->file_list),
+ is_tentative, NULL, &non_tentative_files);
+ g_list_free (tentative_files);
+
+ caja_file_list_ref (non_tentative_files);
+ return non_tentative_files;
+}
+
+static gboolean
+real_is_editable (CajaDirectory *directory)
+{
+ return TRUE;
+}
+
+gboolean
+caja_directory_is_editable (CajaDirectory *directory)
+{
+ return EEL_CALL_METHOD_WITH_RETURN_VALUE
+ (CAJA_DIRECTORY_CLASS, directory,
+ is_editable, (directory));
+}
+
+GList *
+caja_directory_match_pattern (CajaDirectory *directory, const char *pattern)
+{
+ GList *files, *l, *ret;
+ GPatternSpec *spec;
+
+
+ ret = NULL;
+ spec = g_pattern_spec_new (pattern);
+
+ files = caja_directory_get_file_list (directory);
+ for (l = files; l; l = l->next)
+ {
+ CajaFile *file;
+ char *name;
+
+ file = CAJA_FILE (l->data);
+ name = caja_file_get_display_name (file);
+
+ if (g_pattern_match_string (spec, name))
+ {
+ ret = g_list_prepend(ret, caja_file_ref (file));
+ }
+
+ g_free (name);
+ }
+
+ g_pattern_spec_free (spec);
+ caja_file_list_free (files);
+
+ return ret;
+}
+
+/**
+ * caja_directory_list_ref
+ *
+ * Ref all the directories in a list.
+ * @list: GList of directories.
+ **/
+GList *
+caja_directory_list_ref (GList *list)
+{
+ g_list_foreach (list, (GFunc) caja_directory_ref, NULL);
+ return list;
+}
+
+/**
+ * caja_directory_list_unref
+ *
+ * Unref all the directories in a list.
+ * @list: GList of directories.
+ **/
+void
+caja_directory_list_unref (GList *list)
+{
+ g_list_foreach (list, (GFunc) caja_directory_unref, NULL);
+}
+
+/**
+ * caja_directory_list_free
+ *
+ * Free a list of directories after unrefing them.
+ * @list: GList of directories.
+ **/
+void
+caja_directory_list_free (GList *list)
+{
+ caja_directory_list_unref (list);
+ g_list_free (list);
+}
+
+/**
+ * caja_directory_list_copy
+ *
+ * Copy the list of directories, making a new ref of each,
+ * @list: GList of directories.
+ **/
+GList *
+caja_directory_list_copy (GList *list)
+{
+ return g_list_copy (caja_directory_list_ref (list));
+}
+
+static int
+compare_by_uri (CajaDirectory *a, CajaDirectory *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 (CAJA_DIRECTORY (a), CAJA_DIRECTORY (b));
+}
+
+/**
+ * caja_directory_list_sort_by_uri
+ *
+ * Sort the list of directories by directory uri.
+ * @list: GList of directories.
+ **/
+GList *
+caja_directory_list_sort_by_uri (GList *list)
+{
+ return g_list_sort (list, compare_by_uri_cover);
+}
+
+gboolean
+caja_directory_is_desktop_directory (CajaDirectory *directory)
+{
+ if (directory->details->location == NULL)
+ {
+ return FALSE;
+ }
+
+ return caja_is_desktop_directory (directory->details->location);
+}
+
+#if !defined (CAJA_OMIT_SELF_CHECK)
+
+#include <eel/eel-debug.h>
+#include "caja-file-attributes.h"
+
+static int data_dummy;
+static gboolean got_files_flag;
+
+static void
+got_files_callback (CajaDirectory *directory, GList *files, gpointer callback_data)
+{
+ g_assert (CAJA_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 CajaDirectories */
+int
+caja_directory_number_outstanding (void)
+{
+ return directories ? g_hash_table_size (directories) : 0;
+}
+
+void
+caja_self_check_directory (void)
+{
+ CajaDirectory *directory;
+ CajaFile *file;
+
+ directory = caja_directory_get_by_uri ("file:///etc");
+ file = caja_file_get_by_uri ("file:///etc/passwd");
+
+ EEL_CHECK_INTEGER_RESULT (g_hash_table_size (directories), 1);
+
+ caja_directory_file_monitor_add
+ (directory, &data_dummy,
+ TRUE, TRUE, 0, NULL, NULL);
+
+ /* FIXME: these need to be updated to the new metadata infrastructure
+ * as make check doesn't pass.
+ caja_file_set_metadata (file, "test", "default", "value");
+ EEL_CHECK_STRING_RESULT (caja_file_get_metadata (file, "test", "default"), "value");
+
+ caja_file_set_boolean_metadata (file, "test_boolean", TRUE, TRUE);
+ EEL_CHECK_BOOLEAN_RESULT (caja_file_get_boolean_metadata (file, "test_boolean", TRUE), TRUE);
+ caja_file_set_boolean_metadata (file, "test_boolean", TRUE, FALSE);
+ EEL_CHECK_BOOLEAN_RESULT (caja_file_get_boolean_metadata (file, "test_boolean", TRUE), FALSE);
+ EEL_CHECK_BOOLEAN_RESULT (caja_file_get_boolean_metadata (NULL, "test_boolean", TRUE), TRUE);
+
+ caja_file_set_integer_metadata (file, "test_integer", 0, 17);
+ EEL_CHECK_INTEGER_RESULT (caja_file_get_integer_metadata (file, "test_integer", 0), 17);
+ caja_file_set_integer_metadata (file, "test_integer", 0, -1);
+ EEL_CHECK_INTEGER_RESULT (caja_file_get_integer_metadata (file, "test_integer", 0), -1);
+ caja_file_set_integer_metadata (file, "test_integer", 42, 42);
+ EEL_CHECK_INTEGER_RESULT (caja_file_get_integer_metadata (file, "test_integer", 42), 42);
+ EEL_CHECK_INTEGER_RESULT (caja_file_get_integer_metadata (NULL, "test_integer", 42), 42);
+ EEL_CHECK_INTEGER_RESULT (caja_file_get_integer_metadata (file, "nonexistent_key", 42), 42);
+ */
+
+ EEL_CHECK_BOOLEAN_RESULT (caja_directory_get_by_uri ("file:///etc") == directory, TRUE);
+ caja_directory_unref (directory);
+
+ EEL_CHECK_BOOLEAN_RESULT (caja_directory_get_by_uri ("file:///etc/") == directory, TRUE);
+ caja_directory_unref (directory);
+
+ EEL_CHECK_BOOLEAN_RESULT (caja_directory_get_by_uri ("file:///etc////") == directory, TRUE);
+ caja_directory_unref (directory);
+
+ caja_file_unref (file);
+
+ caja_directory_file_monitor_remove (directory, &data_dummy);
+
+ caja_directory_unref (directory);
+
+ while (g_hash_table_size (directories) != 0)
+ {
+ gtk_main_iteration ();
+ }
+
+ EEL_CHECK_INTEGER_RESULT (g_hash_table_size (directories), 0);
+
+ directory = caja_directory_get_by_uri ("file:///etc");
+
+ got_files_flag = FALSE;
+
+ caja_directory_call_when_ready (directory,
+ CAJA_FILE_ATTRIBUTE_INFO |
+ CAJA_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 = caja_file_get_by_uri ("file:///etc/passwd");
+
+ /* EEL_CHECK_STRING_RESULT (caja_file_get_metadata (file, "test", "default"), "value"); */
+
+ caja_file_unref (file);
+
+ caja_directory_unref (directory);
+
+ EEL_CHECK_INTEGER_RESULT (g_hash_table_size (directories), 0);
+}
+
+#endif /* !CAJA_OMIT_SELF_CHECK */
diff --git a/libcaja-private/caja-directory.h b/libcaja-private/caja-directory.h
new file mode 100644
index 00000000..5ad935e9
--- /dev/null
+++ b/libcaja-private/caja-directory.h
@@ -0,0 +1,244 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-directory.h: Caja 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Darin Adler <[email protected]>
+*/
+
+#ifndef CAJA_DIRECTORY_H
+#define CAJA_DIRECTORY_H
+
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+#include <libcaja-private/caja-file-attributes.h>
+
+/* CajaDirectory is a class that manages the model for a directory,
+ real or virtual, for Caja, 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 CAJA_TYPE_DIRECTORY caja_directory_get_type()
+#define CAJA_DIRECTORY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_DIRECTORY, CajaDirectory))
+#define CAJA_DIRECTORY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_DIRECTORY, CajaDirectoryClass))
+#define CAJA_IS_DIRECTORY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_DIRECTORY))
+#define CAJA_IS_DIRECTORY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_DIRECTORY))
+#define CAJA_DIRECTORY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_DIRECTORY, CajaDirectoryClass))
+
+/* CajaFile is defined both here and in caja-file.h. */
+#ifndef CAJA_FILE_DEFINED
+#define CAJA_FILE_DEFINED
+typedef struct CajaFile CajaFile;
+#endif
+
+typedef struct CajaDirectoryDetails CajaDirectoryDetails;
+
+typedef struct
+{
+ GObject object;
+ CajaDirectoryDetails *details;
+} CajaDirectory;
+
+typedef void (*CajaDirectoryCallback) (CajaDirectory *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) (CajaDirectory *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
+ * caja_file_is_gone () to test for this case.
+ */
+ void (* files_changed) (CajaDirectory *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 `caja_directory_file_monitor_add' completes.
+ */
+ void (* done_loading) (CajaDirectory *directory);
+
+ void (* load_error) (CajaDirectory *directory,
+ GError *error);
+
+ /*** Virtual functions for subclasses to override. ***/
+ gboolean (* contains_file) (CajaDirectory *directory,
+ CajaFile *file);
+ void (* call_when_ready) (CajaDirectory *directory,
+ CajaFileAttributes file_attributes,
+ gboolean wait_for_file_list,
+ CajaDirectoryCallback callback,
+ gpointer callback_data);
+ void (* cancel_callback) (CajaDirectory *directory,
+ CajaDirectoryCallback callback,
+ gpointer callback_data);
+ void (* file_monitor_add) (CajaDirectory *directory,
+ gconstpointer client,
+ gboolean monitor_hidden_files,
+ gboolean monitor_backup_files,
+ CajaFileAttributes monitor_attributes,
+ CajaDirectoryCallback initial_files_callback,
+ gpointer callback_data);
+ void (* file_monitor_remove) (CajaDirectory *directory,
+ gconstpointer client);
+ void (* force_reload) (CajaDirectory *directory);
+ gboolean (* are_all_files_seen) (CajaDirectory *directory);
+ gboolean (* is_not_empty) (CajaDirectory *directory);
+ char * (* get_name_for_self_as_new_file) (CajaDirectory *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 CajaDesktopDirectory overrides this so that it can
+ * merge together the list of files in the $HOME/Desktop directory with
+ * the list of standard icons (Computer, Home, Trash) on the desktop.
+ */
+ GList * (* get_file_list) (CajaDirectory *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) (CajaDirectory *directory);
+} CajaDirectoryClass;
+
+/* Basic GObject requirements. */
+GType caja_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.
+ */
+CajaDirectory *caja_directory_get (GFile *location);
+CajaDirectory *caja_directory_get_by_uri (const char *uri);
+CajaDirectory *caja_directory_get_for_file (CajaFile *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,
+ */
+CajaDirectory *caja_directory_ref (CajaDirectory *directory);
+void caja_directory_unref (CajaDirectory *directory);
+
+/* Access to a URI. */
+char * caja_directory_get_uri (CajaDirectory *directory);
+GFile * caja_directory_get_location (CajaDirectory *directory);
+
+/* Is this file still alive and in this directory? */
+gboolean caja_directory_contains_file (CajaDirectory *directory,
+ CajaFile *file);
+
+/* Get the uri of the file in the directory, NULL if not found */
+char * caja_directory_get_file_uri (CajaDirectory *directory,
+ const char *file_name);
+
+/* Get (and ref) a CajaFile object for this directory. */
+CajaFile * caja_directory_get_corresponding_file (CajaDirectory *directory);
+
+/* Waiting for data that's read asynchronously.
+ * The file attribute and metadata keys are for files in the directory.
+ */
+void caja_directory_call_when_ready (CajaDirectory *directory,
+ CajaFileAttributes file_attributes,
+ gboolean wait_for_all_files,
+ CajaDirectoryCallback callback,
+ gpointer callback_data);
+void caja_directory_cancel_callback (CajaDirectory *directory,
+ CajaDirectoryCallback callback,
+ gpointer callback_data);
+
+
+/* Monitor the files in a directory. */
+void caja_directory_file_monitor_add (CajaDirectory *directory,
+ gconstpointer client,
+ gboolean monitor_hidden_files,
+ gboolean monitor_backup_files,
+ CajaFileAttributes attributes,
+ CajaDirectoryCallback initial_files_callback,
+ gpointer callback_data);
+void caja_directory_file_monitor_remove (CajaDirectory *directory,
+ gconstpointer client);
+void caja_directory_force_reload (CajaDirectory *directory);
+
+/* Get a list of all files currently known in the directory. */
+GList * caja_directory_get_file_list (CajaDirectory *directory);
+
+GList * caja_directory_match_pattern (CajaDirectory *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 caja_directory_are_all_files_seen (CajaDirectory *directory);
+
+/* Return true if the directory is local. */
+gboolean caja_directory_is_local (CajaDirectory *directory);
+
+gboolean caja_directory_is_in_trash (CajaDirectory *directory);
+
+/* Return false if directory contains anything besides a Caja metafile.
+ * Only valid if directory is monitored. Used by the Trash monitor.
+ */
+gboolean caja_directory_is_not_empty (CajaDirectory *directory);
+gboolean caja_directory_file_list_length_reached (CajaDirectory *directory);
+
+/* Convenience functions for dealing with a list of CajaDirectory objects that each have a ref.
+ * These are just convenient names for functions that work on lists of GtkObject *.
+ */
+GList * caja_directory_list_ref (GList *directory_list);
+void caja_directory_list_unref (GList *directory_list);
+void caja_directory_list_free (GList *directory_list);
+GList * caja_directory_list_copy (GList *directory_list);
+GList * caja_directory_list_sort_by_uri (GList *directory_list);
+
+/* Fast way to check if a directory is the desktop directory */
+gboolean caja_directory_is_desktop_directory (CajaDirectory *directory);
+
+gboolean caja_directory_is_editable (CajaDirectory *directory);
+
+
+#endif /* CAJA_DIRECTORY_H */
diff --git a/libcaja-private/caja-dnd.c b/libcaja-private/caja-dnd.c
new file mode 100644
index 00000000..9b794956
--- /dev/null
+++ b/libcaja-private/caja-dnd.c
@@ -0,0 +1,1446 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-dnd.c - Common Drag & drop handling code shared by the icon container
+ and the list view.
+
+ Copyright (C) 2000, 2001 Eazel, Inc.
+
+ The Mate 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 Mate 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 Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Pavel Cisler <[email protected]>,
+ Ettore Perazzoli <[email protected]>
+*/
+
+/* FIXME: This should really be back in Caja, not here in Eel. */
+
+#include <config.h>
+#include "caja-dnd.h"
+
+#include "caja-program-choosing.h"
+#include "caja-link.h"
+#include "caja-window-slot-info.h"
+#include "caja-window-info.h"
+#include "caja-view.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 <libcaja-private/caja-file-utilities.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
+caja_drag_init (CajaDragInfo *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,
+ CAJA_ICON_DND_TEXT);
+ }
+
+ drag_info->drop_occured = FALSE;
+ drag_info->need_to_destroy = FALSE;
+}
+
+void
+caja_drag_finalize (CajaDragInfo *drag_info)
+{
+ gtk_target_list_unref (drag_info->target_list);
+ caja_drag_destroy_selection_list (drag_info->selection_list);
+
+ g_free (drag_info);
+}
+
+
+/* Functions to deal with CajaDragSelectionItems. */
+
+CajaDragSelectionItem *
+caja_drag_selection_item_new (void)
+{
+ return g_new0 (CajaDragSelectionItem, 1);
+}
+
+static void
+drag_selection_item_destroy (CajaDragSelectionItem *item)
+{
+ g_free (item->uri);
+ g_free (item);
+}
+
+void
+caja_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);
+}
+
+char **
+caja_drag_uri_array_from_selection_list (const GList *selection_list)
+{
+ GList *uri_list;
+ char **uris;
+
+ uri_list = caja_drag_uri_list_from_selection_list (selection_list);
+ uris = caja_drag_uri_array_from_list (uri_list);
+ eel_g_list_free_deep (uri_list);
+
+ return uris;
+}
+
+GList *
+caja_drag_uri_list_from_selection_list (const GList *selection_list)
+{
+ CajaDragSelectionItem *selection_item;
+ GList *uri_list;
+ const GList *l;
+
+ uri_list = NULL;
+ for (l = selection_list; l != NULL; l = l->next)
+ {
+ selection_item = (CajaDragSelectionItem *) 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);
+}
+
+char **
+caja_drag_uri_array_from_list (const GList *uri_list)
+{
+ const GList *l;
+ char **uris;
+ int i;
+
+ if (uri_list == NULL)
+ {
+ return NULL;
+ }
+
+ uris = g_new0 (char *, g_list_length ((GList *) uri_list));
+ for (i = 0, l = uri_list; l != NULL; l = l->next)
+ {
+ uris[i++] = g_strdup ((char *) l->data);
+ }
+ uris[i] = NULL;
+
+ return uris;
+}
+
+GList *
+caja_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 *
+caja_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)
+ {
+ CajaDragSelectionItem *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 = caja_drag_selection_item_new ();
+
+ len = p - oldp;
+
+ item->uri = g_malloc (len + 1);
+ memcpy (item->uri, oldp, len);
+ item->uri[len] = 0;
+
+ p++;
+ if (*p == '\n' || *p == '\0')
+ {
+ result = g_list_prepend (result, item);
+ if (p == 0)
+ {
+ g_warning ("Invalid x-special/mate-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 (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/mate-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/mate-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
+caja_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
+caja_drag_uris_local (const char *target_uri,
+ const GList *source_uri_list)
+{
+ /* must have at least one item */
+ g_assert (source_uri_list);
+
+ return caja_drag_file_local_internal (target_uri, source_uri_list->data);
+}
+
+gboolean
+caja_drag_items_local (const char *target_uri_string,
+ const GList *selection_list)
+{
+ /* must have at least one item */
+ g_assert (selection_list);
+
+ return caja_drag_file_local_internal (target_uri_string,
+ ((CajaDragSelectionItem *)selection_list->data)->uri);
+}
+
+gboolean
+caja_drag_items_in_trash (const GList *selection_list)
+{
+ /* 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
+ */
+ return eel_uri_is_trash (((CajaDragSelectionItem *)selection_list->data)->uri);
+}
+
+gboolean
+caja_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 = ((CajaDragSelectionItem *)selection_list->data)->uri;
+ if (eel_uri_is_desktop (uri))
+ {
+ return TRUE;
+ }
+
+ desktop = caja_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
+caja_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 ASK */
+ if (gdk_drag_context_get_suggested_action (context) == GDK_ACTION_COPY &&
+ gdk_drag_context_get_actions (context) != GDK_ACTION_COPY)
+ {
+ return GDK_ACTION_ASK;
+ }
+ 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 (CajaFile *file1,
+ CajaFile *file2)
+{
+ char *id1, *id2;
+ gboolean result;
+
+ result = FALSE;
+
+ if (file1 != NULL && file2 != NULL)
+ {
+ id1 = caja_file_get_filesystem_id (file1);
+ id2 = caja_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)
+{
+ CajaFile *naut_file;
+ gboolean ret;
+
+ /* if there's no a cached CajaFile, it returns NULL */
+ naut_file = caja_file_get_existing (file);
+ if (naut_file == NULL)
+ {
+ return FALSE;
+ }
+
+ ret = caja_file_can_delete (naut_file);
+ caja_file_unref (naut_file);
+
+ return ret;
+}
+
+void
+caja_drag_default_drop_action_for_icons (GdkDragContext *context,
+ const char *target_uri_string, const GList *items,
+ int *action)
+{
+ gboolean same_fs;
+ gboolean target_is_source_parent;
+ gboolean source_deletable;
+ const char *dropped_uri;
+ GFile *target, *dropped, *dropped_directory;
+ GdkDragAction actions;
+ CajaFile *dropped_file, *target_file;
+
+ if (target_uri_string == NULL)
+ {
+ *action = 0;
+ return;
+ }
+
+ 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 = ((CajaDragSelectionItem *)items->data)->uri;
+ dropped_file = caja_file_get_existing_by_uri (dropped_uri);
+ target_file = caja_file_get_existing_by_uri (target_uri_string);
+
+ /*
+ * Check for trash URI. We do a find_directory for any Trash directory.
+ * Passing 0 permissions as mate-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;
+ }
+
+ caja_file_unref (dropped_file);
+ caja_file_unref (target_file);
+ return;
+
+ }
+ else if (dropped_file != NULL && caja_file_is_launcher (dropped_file))
+ {
+ if (actions & GDK_ACTION_MOVE)
+ {
+ *action = GDK_ACTION_MOVE;
+ }
+ caja_file_unref (dropped_file);
+ caja_file_unref (target_file);
+ return;
+ }
+ else if (eel_uri_is_desktop (target_uri_string))
+ {
+ target = caja_get_desktop_location ();
+
+ caja_file_unref (target_file);
+ target_file = caja_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);
+ caja_file_unref (dropped_file);
+ caja_file_unref (target_file);
+ return;
+ }
+ }
+ else if (target_file != NULL && caja_file_is_archive (target_file))
+ {
+ *action = GDK_ACTION_COPY;
+
+ caja_file_unref (dropped_file);
+ caja_file_unref (target_file);
+ return;
+ }
+ else
+ {
+ target = g_file_new_for_uri (target_uri_string);
+ }
+
+ same_fs = check_same_fs (target_file, dropped_file);
+
+ caja_file_unref (dropped_file);
+ caja_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
+caja_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/mate-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_mate_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);
+}
+
+/*
+ * Cf. #48423
+ */
+#ifdef THIS_WAS_REALLY_BROKEN
+static gboolean
+is_path_that_mate_uri_list_extract_filenames_can_parse (const char *path)
+{
+ if (path == NULL || path [0] == '\0')
+ {
+ return FALSE;
+ }
+
+ /* It strips leading and trailing spaces. So it can't handle
+ * file names with leading and trailing spaces.
+ */
+ if (g_ascii_isspace (path [0]))
+ {
+ return FALSE;
+ }
+ if (g_ascii_isspace (path [strlen (path) - 1]))
+ {
+ return FALSE;
+ }
+
+ /* # works as a comment delimiter, and \r and \n are used to
+ * separate the lines, so it can't handle file names with any
+ * of these.
+ */
+ if (strchr (path, '#') != NULL
+ || strchr (path, '\r') != NULL
+ || strchr (path, '\n') != NULL)
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* Encode a "text/plain" selection; this is a broken URL -- just
+ * "file:" with a path after it (no escaping or anything). We are
+ * trying to make the old mate_uri_list_extract_filenames function
+ * happy, so this is coded to its idiosyncrasises.
+ */
+static void
+add_one_compatible_uri (const char *uri, int x, int y, int w, int h, gpointer data)
+{
+ GString *result;
+ char *local_path;
+
+ result = (GString *) data;
+
+ /* For URLs that do not have a file: scheme, there's no harm
+ * in passing the real URL. But for URLs that do have a file:
+ * scheme, we have to send a URL that will work with the old
+ * mate-libs function or nothing will be able to understand
+ * it.
+ */
+ if (!eel_istr_has_prefix (uri, "file:"))
+ {
+ g_string_append (result, uri);
+ g_string_append (result, "\r\n");
+ }
+ else
+ {
+ local_path = g_filename_from_uri (uri, NULL, NULL);
+
+ /* Check for characters that confuse the old
+ * mate_uri_list_extract_filenames implementation, and just leave
+ * out any paths with those in them.
+ */
+ if (is_path_that_mate_uri_list_extract_filenames_can_parse (local_path))
+ {
+ g_string_append (result, "file:");
+ g_string_append (result, local_path);
+ g_string_append (result, "\r\n");
+ }
+
+ g_free (local_path);
+ }
+}
+#endif
+
+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");
+}
+
+/* Common function for drag_data_get_callback calls.
+ * Returns FALSE if it doesn't handle drag data */
+gboolean
+caja_drag_drag_data_get (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint32 time,
+ gpointer container_context,
+ CajaDragEachSelectedItemIterator each_selected_item_iterator)
+{
+ GString *result;
+
+ switch (info)
+ {
+ case CAJA_ICON_DND_MATE_ICON_LIST:
+ result = g_string_new (NULL);
+ (* each_selected_item_iterator) (add_one_mate_icon, container_context, result);
+ break;
+
+ case CAJA_ICON_DND_URI_LIST:
+ case CAJA_ICON_DND_TEXT:
+ result = g_string_new (NULL);
+ (* each_selected_item_iterator) (add_one_uri, container_context, result);
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ gtk_selection_data_set (selection_data,
+ gtk_selection_data_get_target (selection_data),
+ 8, 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
+caja_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);
+
+ append_drop_action_menu_item (menu, _("Set as _Background"),
+ CAJA_DND_ACTION_SET_AS_BACKGROUND,
+ (actions & CAJA_DND_ACTION_SET_AS_BACKGROUND) != 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;
+}
+
+GdkDragAction
+caja_drag_drop_background_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, _("Set as background for _all folders"),
+ CAJA_DND_ACTION_SET_AS_GLOBAL_BACKGROUND,
+ (actions & CAJA_DND_ACTION_SET_AS_GLOBAL_BACKGROUND) != 0,
+ &damd);
+
+ append_drop_action_menu_item (menu, _("Set as background for _this folder"),
+ CAJA_DND_ACTION_SET_AS_FOLDER_BACKGROUND,
+ (actions & CAJA_DND_ACTION_SET_AS_FOLDER_BACKGROUND) != 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
+caja_drag_autoscroll_in_scroll_region (GtkWidget *widget)
+{
+ float x_scroll_delta, y_scroll_delta;
+
+ caja_drag_autoscroll_calculate_delta (widget, &x_scroll_delta, &y_scroll_delta);
+
+ return x_scroll_delta != 0 || y_scroll_delta != 0;
+}
+
+
+void
+caja_drag_autoscroll_calculate_delta (GtkWidget *widget, float *x_scroll_delta, float *y_scroll_delta)
+{
+ GtkAllocation allocation;
+ int x, y;
+
+ g_assert (GTK_IS_WIDGET (widget));
+
+ gdk_window_get_pointer (gtk_widget_get_window (widget), &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
+caja_drag_autoscroll_start (CajaDragInfo *drag_info,
+ GtkWidget *widget,
+ GtkFunction callback,
+ gpointer user_data)
+{
+ if (caja_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 = eel_get_system_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
+caja_drag_autoscroll_stop (CajaDragInfo *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
+caja_drag_selection_includes_special_link (GList *selection_list)
+{
+ GList *node;
+ char *uri;
+
+ for (node = selection_list; node != NULL; node = node->next)
+ {
+ uri = ((CajaDragSelectionItem *) node->data)->uri;
+
+ if (eel_uri_is_desktop (uri))
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static gboolean
+slot_proxy_drag_motion (GtkWidget *widget,
+ GdkDragContext *context,
+ int x,
+ int y,
+ unsigned int time,
+ gpointer user_data)
+{
+ CajaDragSlotProxyInfo *drag_info;
+ CajaWindowSlotInfo *target_slot;
+ GtkWidget *window;
+ GdkAtom target;
+ int action;
+ char *target_uri;
+
+ drag_info = user_data;
+
+ action = 0;
+
+ if (gtk_drag_get_source_widget (context) == widget)
+ {
+ goto out;
+ }
+
+ window = gtk_widget_get_toplevel (widget);
+ g_assert (CAJA_IS_WINDOW_INFO (window));
+
+ if (!drag_info->have_data)
+ {
+ target = gtk_drag_dest_find_target (widget, context, NULL);
+
+ if (target == GDK_NONE)
+ {
+ goto out;
+ }
+
+ gtk_drag_get_data (widget, context, target, time);
+ }
+
+ target_uri = NULL;
+ if (drag_info->target_location != NULL)
+ {
+ target_uri = g_file_get_uri (drag_info->target_location);
+ }
+ else
+ {
+ if (drag_info->target_slot != NULL)
+ {
+ target_slot = drag_info->target_slot;
+ }
+ else
+ {
+ target_slot = caja_window_info_get_active_slot (CAJA_WINDOW_INFO (window));
+ }
+
+ if (target_slot != NULL)
+ {
+ target_uri = caja_window_slot_info_get_current_location (target_slot);
+ }
+ }
+
+ if (drag_info->have_data &&
+ drag_info->have_valid_data)
+ {
+ if (drag_info->info == CAJA_ICON_DND_MATE_ICON_LIST)
+ {
+ caja_drag_default_drop_action_for_icons (context, target_uri,
+ drag_info->data.selection_list,
+ &action);
+ }
+ else if (drag_info->info == CAJA_ICON_DND_URI_LIST)
+ {
+ action = caja_drag_default_drop_action_for_uri_list (context, target_uri);
+ }
+ else if (drag_info->info == CAJA_ICON_DND_NETSCAPE_URL)
+ {
+ action = caja_drag_default_drop_action_for_netscape_url (context);
+ }
+ }
+
+ g_free (target_uri);
+
+out:
+ if (action != 0)
+ {
+ gtk_drag_highlight (widget);
+ }
+ else
+ {
+ gtk_drag_unhighlight (widget);
+ }
+
+ gdk_drag_status (context, action, time);
+
+ return TRUE;
+}
+
+static void
+drag_info_clear (CajaDragSlotProxyInfo *drag_info)
+{
+ if (!drag_info->have_data)
+ {
+ goto out;
+ }
+
+ if (drag_info->info == CAJA_ICON_DND_MATE_ICON_LIST)
+ {
+ caja_drag_destroy_selection_list (drag_info->data.selection_list);
+ }
+ else if (drag_info->info == CAJA_ICON_DND_URI_LIST)
+ {
+ g_list_free (drag_info->data.uri_list);
+ }
+ else if (drag_info->info == CAJA_ICON_DND_NETSCAPE_URL)
+ {
+ g_free (drag_info->data.netscape_url);
+ }
+
+out:
+ drag_info->have_data = FALSE;
+ drag_info->have_valid_data = FALSE;
+
+ drag_info->drop_occured = FALSE;
+}
+
+static void
+slot_proxy_drag_leave (GtkWidget *widget,
+ GdkDragContext *context,
+ unsigned int time,
+ gpointer user_data)
+{
+ CajaDragSlotProxyInfo *drag_info;
+
+ drag_info = user_data;
+
+ gtk_drag_unhighlight (widget);
+ drag_info_clear (drag_info);
+}
+
+static gboolean
+slot_proxy_drag_drop (GtkWidget *widget,
+ GdkDragContext *context,
+ int x,
+ int y,
+ unsigned int time,
+ gpointer user_data)
+{
+ GdkAtom target;
+ CajaDragSlotProxyInfo *drag_info;
+
+ drag_info = user_data;
+ g_assert (!drag_info->have_data);
+
+ drag_info->drop_occured = TRUE;
+
+ target = gtk_drag_dest_find_target (widget, context, NULL);
+ gtk_drag_get_data (widget, context, target, time);
+
+ return TRUE;
+}
+
+
+static void
+slot_proxy_handle_drop (GtkWidget *widget,
+ GdkDragContext *context,
+ unsigned int time,
+ CajaDragSlotProxyInfo *drag_info)
+{
+ GtkWidget *window;
+ CajaWindowSlotInfo *target_slot;
+ CajaView *target_view;
+ char *target_uri;
+ GList *uri_list;
+
+ if (!drag_info->have_data ||
+ !drag_info->have_valid_data)
+ {
+ gtk_drag_finish (context, FALSE, FALSE, time);
+ drag_info_clear (drag_info);
+ return;
+ }
+
+ window = gtk_widget_get_toplevel (widget);
+ g_assert (CAJA_IS_WINDOW_INFO (window));
+
+ if (drag_info->target_slot != NULL)
+ {
+ target_slot = drag_info->target_slot;
+ }
+ else
+ {
+ target_slot = caja_window_info_get_active_slot (CAJA_WINDOW_INFO (window));
+ }
+
+ target_uri = NULL;
+ if (drag_info->target_location != NULL)
+ {
+ target_uri = g_file_get_uri (drag_info->target_location);
+ }
+ else if (target_slot != NULL)
+ {
+ target_uri = caja_window_slot_info_get_current_location (target_slot);
+ }
+
+ target_view = NULL;
+ if (target_slot != NULL)
+ {
+ target_view = caja_window_slot_info_get_current_view (target_slot);
+ }
+
+ if (target_slot != NULL && target_view != NULL)
+ {
+ if (drag_info->info == CAJA_ICON_DND_MATE_ICON_LIST)
+ {
+ uri_list = caja_drag_uri_list_from_selection_list (drag_info->data.selection_list);
+ g_assert (uri_list != NULL);
+
+ caja_view_drop_proxy_received_uris (target_view,
+ uri_list,
+ target_uri,
+ gdk_drag_context_get_selected_action (context));
+ eel_g_list_free_deep (uri_list);
+ }
+ else if (drag_info->info == CAJA_ICON_DND_URI_LIST)
+ {
+ caja_view_drop_proxy_received_uris (target_view,
+ drag_info->data.uri_list,
+ target_uri,
+ gdk_drag_context_get_selected_action (context));
+ }
+ if (drag_info->info == CAJA_ICON_DND_NETSCAPE_URL)
+ {
+ caja_view_drop_proxy_received_netscape_url (target_view,
+ drag_info->data.netscape_url,
+ target_uri,
+ gdk_drag_context_get_selected_action (context));
+ }
+
+
+ gtk_drag_finish (context, TRUE, FALSE, time);
+ }
+ else
+ {
+ gtk_drag_finish (context, FALSE, FALSE, time);
+ }
+
+ if (target_view != NULL)
+ {
+ g_object_unref (target_view);
+ }
+
+ g_free (target_uri);
+
+ drag_info_clear (drag_info);
+}
+
+static void
+slot_proxy_drag_data_received (GtkWidget *widget,
+ GdkDragContext *context,
+ int x,
+ int y,
+ GtkSelectionData *data,
+ unsigned int info,
+ unsigned int time,
+ gpointer user_data)
+{
+ CajaDragSlotProxyInfo *drag_info;
+ char **uris;
+
+ drag_info = user_data;
+
+ g_assert (!drag_info->have_data);
+
+ drag_info->have_data = TRUE;
+ drag_info->info = info;
+
+ if (gtk_selection_data_get_length (data) < 0)
+ {
+ drag_info->have_valid_data = FALSE;
+ return;
+ }
+
+ if (info == CAJA_ICON_DND_MATE_ICON_LIST)
+ {
+ drag_info->data.selection_list = caja_drag_build_selection_list (data);
+
+ drag_info->have_valid_data = drag_info->data.selection_list != NULL;
+ }
+ else if (info == CAJA_ICON_DND_URI_LIST)
+ {
+ uris = gtk_selection_data_get_uris (data);
+ drag_info->data.uri_list = caja_drag_uri_list_from_array ((const char **) uris);
+ g_strfreev (uris);
+
+ drag_info->have_valid_data = drag_info->data.uri_list != NULL;
+ }
+ else if (info == CAJA_ICON_DND_NETSCAPE_URL)
+ {
+ drag_info->data.netscape_url = g_strdup ((char *) gtk_selection_data_get_data (data));
+
+ drag_info->have_valid_data = drag_info->data.netscape_url != NULL;
+ }
+
+ if (drag_info->drop_occured)
+ {
+ slot_proxy_handle_drop (widget, context, time, drag_info);
+ }
+}
+
+void
+caja_drag_slot_proxy_init (GtkWidget *widget,
+ CajaDragSlotProxyInfo *drag_info)
+{
+ const GtkTargetEntry targets[] =
+ {
+ { CAJA_ICON_DND_MATE_ICON_LIST_TYPE, 0, CAJA_ICON_DND_MATE_ICON_LIST },
+ { CAJA_ICON_DND_NETSCAPE_URL_TYPE, 0, CAJA_ICON_DND_NETSCAPE_URL }
+ };
+ GtkTargetList *target_list;
+
+ g_assert (GTK_IS_WIDGET (widget));
+ g_assert (drag_info != NULL);
+
+ gtk_drag_dest_set (widget, 0,
+ NULL, 0,
+ GDK_ACTION_MOVE |
+ GDK_ACTION_COPY |
+ GDK_ACTION_LINK |
+ GDK_ACTION_ASK);
+
+ target_list = gtk_target_list_new (targets, G_N_ELEMENTS (targets));
+ gtk_target_list_add_uri_targets (target_list, CAJA_ICON_DND_URI_LIST);
+ gtk_drag_dest_set_target_list (widget, target_list);
+ gtk_target_list_unref (target_list);
+
+ g_signal_connect (widget, "drag-motion",
+ G_CALLBACK (slot_proxy_drag_motion),
+ drag_info);
+ g_signal_connect (widget, "drag-drop",
+ G_CALLBACK (slot_proxy_drag_drop),
+ drag_info);
+ g_signal_connect (widget, "drag-data-received",
+ G_CALLBACK (slot_proxy_drag_data_received),
+ drag_info);
+ g_signal_connect (widget, "drag-leave",
+ G_CALLBACK (slot_proxy_drag_leave),
+ drag_info);
+}
+
+
diff --git a/libcaja-private/caja-dnd.h b/libcaja-private/caja-dnd.h
new file mode 100644
index 00000000..1861c404
--- /dev/null
+++ b/libcaja-private/caja-dnd.h
@@ -0,0 +1,200 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-dnd.h - Common Drag & drop handling code shared by the icon container
+ and the list view.
+
+ Copyright (C) 2000 Eazel, Inc.
+
+ The Mate 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 Mate 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 Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Pavel Cisler <[email protected]>,
+ Ettore Perazzoli <[email protected]>
+*/
+
+#ifndef CAJA_DND_H
+#define CAJA_DND_H
+
+#include <gtk/gtk.h>
+#include <libcaja-private/caja-window-slot-info.h>
+
+/* Drag & Drop target names. */
+#define CAJA_ICON_DND_MATE_ICON_LIST_TYPE "x-special/mate-icon-list"
+#define CAJA_ICON_DND_URI_LIST_TYPE "text/uri-list"
+#define CAJA_ICON_DND_NETSCAPE_URL_TYPE "_NETSCAPE_URL"
+#define CAJA_ICON_DND_COLOR_TYPE "application/x-color"
+#define CAJA_ICON_DND_BGIMAGE_TYPE "property/bgimage"
+#define CAJA_ICON_DND_KEYWORD_TYPE "property/keyword"
+#define CAJA_ICON_DND_RESET_BACKGROUND_TYPE "x-special/mate-reset-background"
+#define CAJA_ICON_DND_ROOTWINDOW_DROP_TYPE "application/x-rootwindow-drop"
+#define CAJA_ICON_DND_XDNDDIRECTSAVE_TYPE "XdndDirectSave0" /* XDS Protocol Type */
+#define CAJA_ICON_DND_RAW_TYPE "application/octet-stream"
+
+/* Item of the drag selection list */
+typedef struct
+{
+ char *uri;
+ gboolean got_icon_position;
+ int icon_x, icon_y;
+ int icon_width, icon_height;
+} CajaDragSelectionItem;
+
+/* Standard Drag & Drop types. */
+typedef enum
+{
+ CAJA_ICON_DND_MATE_ICON_LIST,
+ CAJA_ICON_DND_URI_LIST,
+ CAJA_ICON_DND_NETSCAPE_URL,
+ CAJA_ICON_DND_COLOR,
+ CAJA_ICON_DND_BGIMAGE,
+ CAJA_ICON_DND_KEYWORD,
+ CAJA_ICON_DND_TEXT,
+ CAJA_ICON_DND_RESET_BACKGROUND,
+ CAJA_ICON_DND_XDNDDIRECTSAVE,
+ CAJA_ICON_DND_RAW,
+ CAJA_ICON_DND_ROOTWINDOW_DROP
+} CajaIconDndTargetType;
+
+typedef enum
+{
+ CAJA_DND_ACTION_FIRST = GDK_ACTION_ASK << 1,
+ CAJA_DND_ACTION_SET_AS_BACKGROUND = CAJA_DND_ACTION_FIRST << 0,
+ CAJA_DND_ACTION_SET_AS_FOLDER_BACKGROUND = CAJA_DND_ACTION_FIRST << 1,
+ CAJA_DND_ACTION_SET_AS_GLOBAL_BACKGROUND = CAJA_DND_ACTION_FIRST << 2
+} CajaDndAction;
+
+/* 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;
+ CajaIconDndTargetType data_type;
+ GtkSelectionData *selection_data;
+ char *direct_save_uri;
+
+ /* Start of the drag, in window coordinates. */
+ int start_x, start_y;
+
+ /* List of CajaDragSelectionItems, representing items being dragged, or NULL
+ * if data about them has not been received from the source yet.
+ */
+ GList *selection_list;
+
+ /* 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;
+
+} CajaDragInfo;
+
+typedef struct
+{
+ /* NB: the following elements are managed by us */
+ gboolean have_data;
+ gboolean have_valid_data;
+
+ gboolean drop_occured;
+
+ unsigned int info;
+ union
+ {
+ GList *selection_list;
+ GList *uri_list;
+ char *netscape_url;
+ } data;
+
+ /* NB: the following elements are managed by the caller of
+ * caja_drag_slot_proxy_init() */
+
+ /* a fixed location, or NULL to use slot's location */
+ GFile *target_location;
+ /* a fixed slot, or NULL to use the window's active slot */
+ CajaWindowSlotInfo *target_slot;
+} CajaDragSlotProxyInfo;
+
+typedef void (* CajaDragEachSelectedItemDataGet) (const char *url,
+ int x, int y, int w, int h,
+ gpointer data);
+typedef void (* CajaDragEachSelectedItemIterator) (CajaDragEachSelectedItemDataGet iteratee,
+ gpointer iterator_context,
+ gpointer data);
+
+void caja_drag_init (CajaDragInfo *drag_info,
+ const GtkTargetEntry *drag_types,
+ int drag_type_count,
+ gboolean add_text_targets);
+void caja_drag_finalize (CajaDragInfo *drag_info);
+CajaDragSelectionItem *caja_drag_selection_item_new (void);
+void caja_drag_destroy_selection_list (GList *selection_list);
+GList *caja_drag_build_selection_list (GtkSelectionData *data);
+
+char ** caja_drag_uri_array_from_selection_list (const GList *selection_list);
+GList * caja_drag_uri_list_from_selection_list (const GList *selection_list);
+
+char ** caja_drag_uri_array_from_list (const GList *uri_list);
+GList * caja_drag_uri_list_from_array (const char **uris);
+
+gboolean caja_drag_items_local (const char *target_uri,
+ const GList *selection_list);
+gboolean caja_drag_uris_local (const char *target_uri,
+ const GList *source_uri_list);
+gboolean caja_drag_items_in_trash (const GList *selection_list);
+gboolean caja_drag_items_on_desktop (const GList *selection_list);
+void caja_drag_default_drop_action_for_icons (GdkDragContext *context,
+ const char *target_uri,
+ const GList *items,
+ int *action);
+GdkDragAction caja_drag_default_drop_action_for_netscape_url (GdkDragContext *context);
+GdkDragAction caja_drag_default_drop_action_for_uri_list (GdkDragContext *context,
+ const char *target_uri_string);
+gboolean caja_drag_drag_data_get (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint32 time,
+ gpointer container_context,
+ CajaDragEachSelectedItemIterator each_selected_item_iterator);
+int caja_drag_modifier_based_action (int default_action,
+ int non_default_action);
+
+GdkDragAction caja_drag_drop_action_ask (GtkWidget *widget,
+ GdkDragAction possible_actions);
+GdkDragAction caja_drag_drop_background_ask (GtkWidget *widget,
+ GdkDragAction possible_actions);
+
+gboolean caja_drag_autoscroll_in_scroll_region (GtkWidget *widget);
+void caja_drag_autoscroll_calculate_delta (GtkWidget *widget,
+ float *x_scroll_delta,
+ float *y_scroll_delta);
+void caja_drag_autoscroll_start (CajaDragInfo *drag_info,
+ GtkWidget *widget,
+ GtkFunction callback,
+ gpointer user_data);
+void caja_drag_autoscroll_stop (CajaDragInfo *drag_info);
+
+gboolean caja_drag_selection_includes_special_link (GList *selection_list);
+
+void caja_drag_slot_proxy_init (GtkWidget *widget,
+ CajaDragSlotProxyInfo *drag_info);
+
+#endif
diff --git a/libcaja-private/caja-emblem-utils.c b/libcaja-private/caja-emblem-utils.c
new file mode 100644
index 00000000..b1813e10
--- /dev/null
+++ b/libcaja-private/caja-emblem-utils.c
@@ -0,0 +1,511 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-emblem-utils.c: Utilities for handling emblems
+
+ Copyright (C) 2002 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Alexander Larsson <[email protected]>
+*/
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <utime.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-gdk-pixbuf-extensions.h>
+#include <eel/eel-stock-dialogs.h>
+
+#include "caja-file.h"
+#include "caja-emblem-utils.h"
+
+#define EMBLEM_NAME_TRASH "emblem-trash"
+#define EMBLEM_NAME_SYMLINK "emblem-symbolic-link"
+#define EMBLEM_NAME_NOREAD "emblem-noread"
+#define EMBLEM_NAME_NOWRITE "emblem-nowrite"
+#define EMBLEM_NAME_NOTE "emblem-note"
+#define EMBLEM_NAME_SHARED "emblem-shared"
+
+GList *
+caja_emblem_list_available (void)
+{
+ GtkIconTheme *icon_theme;
+ GList *list;
+
+ icon_theme = gtk_icon_theme_get_default ();
+ list = gtk_icon_theme_list_icons (icon_theme, "Emblems");
+ return list;
+}
+
+void
+caja_emblem_refresh_list (void)
+{
+ GtkIconTheme *icon_theme;
+
+ icon_theme = gtk_icon_theme_get_default ();
+ gtk_icon_theme_rescan_if_needed (icon_theme);
+}
+
+char *
+caja_emblem_get_icon_name_from_keyword (const char *keyword)
+{
+ return g_strconcat ("emblem-", keyword, NULL);
+}
+
+
+/* check for reserved keywords */
+static gboolean
+is_reserved_keyword (const char *keyword)
+{
+ GList *available;
+ char *icon_name;
+ gboolean result;
+
+ g_assert (keyword != NULL);
+
+ /* check intrinsic emblems */
+ if (g_ascii_strcasecmp (keyword, CAJA_FILE_EMBLEM_NAME_TRASH) == 0)
+ {
+ return TRUE;
+ }
+ if (g_ascii_strcasecmp (keyword, CAJA_FILE_EMBLEM_NAME_CANT_READ) == 0)
+ {
+ return TRUE;
+ }
+ if (g_ascii_strcasecmp (keyword, CAJA_FILE_EMBLEM_NAME_CANT_WRITE) == 0)
+ {
+ return TRUE;
+ }
+ if (g_ascii_strcasecmp (keyword, CAJA_FILE_EMBLEM_NAME_SYMBOLIC_LINK) == 0)
+ {
+ return TRUE;
+ }
+ if (g_ascii_strcasecmp (keyword, CAJA_FILE_EMBLEM_NAME_NOTE) == 0)
+ {
+ return TRUE;
+ }
+ if (g_ascii_strcasecmp (keyword, CAJA_FILE_EMBLEM_NAME_SHARED) == 0)
+ {
+ return TRUE;
+ }
+
+ available = caja_emblem_list_available ();
+ icon_name = caja_emblem_get_icon_name_from_keyword (keyword);
+ /* see if the keyword already exists */
+ result = g_list_find_custom (available,
+ (char *) icon_name,
+ (GCompareFunc) g_ascii_strcasecmp) != NULL;
+ eel_g_list_free_deep (available);
+ g_free (icon_name);
+ return result;
+}
+
+gboolean
+caja_emblem_should_show_in_list (const char *emblem)
+{
+ if (strcmp (emblem, EMBLEM_NAME_TRASH) == 0)
+ {
+ return FALSE;
+ }
+ if (strcmp (emblem, EMBLEM_NAME_SYMLINK) == 0)
+ {
+ return FALSE;
+ }
+ if (strcmp (emblem, EMBLEM_NAME_NOREAD) == 0)
+ {
+ return FALSE;
+ }
+ if (strcmp (emblem, EMBLEM_NAME_NOWRITE) == 0)
+ {
+ return FALSE;
+ }
+ if (strcmp (emblem, EMBLEM_NAME_NOTE) == 0)
+ {
+ return FALSE;
+ }
+ if (strcmp (emblem, EMBLEM_NAME_SHARED) == 0)
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+char *
+caja_emblem_get_keyword_from_icon_name (const char *emblem)
+{
+ g_return_val_if_fail (emblem != NULL, NULL);
+
+ if (g_str_has_prefix (emblem, "emblem-"))
+ {
+ return g_strdup (&emblem[7]);
+ }
+ else
+ {
+ return g_strdup (emblem);
+ }
+}
+
+GdkPixbuf *
+caja_emblem_load_pixbuf_for_emblem (GFile *emblem)
+{
+ GInputStream *stream;
+ GdkPixbuf *pixbuf;
+ GdkPixbuf *scaled;
+
+ stream = (GInputStream *) g_file_read (emblem, NULL, NULL);
+ if (!stream)
+ {
+ return NULL;
+ }
+
+ pixbuf = eel_gdk_pixbuf_load_from_stream (stream);
+ g_return_val_if_fail (pixbuf != NULL, NULL);
+
+ scaled = eel_gdk_pixbuf_scale_down_to_fit (pixbuf,
+ CAJA_ICON_SIZE_STANDARD,
+ CAJA_ICON_SIZE_STANDARD);
+
+ g_object_unref (pixbuf);
+ g_object_unref (stream);
+
+ return scaled;
+}
+
+/* utility to make sure the passed-in keyword only contains alphanumeric characters */
+static gboolean
+emblem_keyword_valid (const char *keyword)
+{
+ const char *p;
+ gunichar c;
+
+ for (p = keyword; *p; p = g_utf8_next_char (p))
+ {
+ c = g_utf8_get_char (p);
+
+ if (!g_unichar_isalnum (c) &&
+ !g_unichar_isspace (c))
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+gboolean
+caja_emblem_verify_keyword (GtkWindow *parent_window,
+ const char *keyword,
+ const char *display_name)
+{
+ if (keyword == NULL || strlen (keyword) == 0)
+ {
+ eel_show_error_dialog (_("The emblem cannot be installed."),
+ _("Sorry, but you must specify a non-blank keyword for the new emblem."),
+ GTK_WINDOW (parent_window));
+ return FALSE;
+ }
+ else if (!emblem_keyword_valid (keyword))
+ {
+ eel_show_error_dialog (_("The emblem cannot be installed."),
+ _("Sorry, but emblem keywords can only contain letters, spaces and numbers."),
+ GTK_WINDOW (parent_window));
+ return FALSE;
+ }
+ else if (is_reserved_keyword (keyword))
+ {
+ char *error_string;
+
+ /* this really should never happen, as a user has no idea
+ * what a keyword is, and people should be passing a unique
+ * keyword to us anyway
+ */
+ error_string = g_strdup_printf (_("Sorry, but there is already an emblem named \"%s\"."), display_name);
+ eel_show_error_dialog (_("Please choose a different emblem name."), error_string,
+ GTK_WINDOW (parent_window));
+ g_free (error_string);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void
+caja_emblem_install_custom_emblem (GdkPixbuf *pixbuf,
+ const char *keyword,
+ const char *display_name,
+ GtkWindow *parent_window)
+{
+ char *basename, *path, *dir, *stat_dir;
+ struct stat stat_buf;
+ struct utimbuf ubuf;
+
+ g_return_if_fail (pixbuf != NULL);
+
+ if (!caja_emblem_verify_keyword (parent_window, keyword, display_name))
+ {
+ return;
+ }
+
+ dir = g_build_filename (g_get_home_dir (),
+ ".icons", "hicolor", "48x48", "emblems",
+ NULL);
+ stat_dir = g_build_filename (g_get_home_dir (),
+ ".icons", "hicolor",
+ NULL);
+
+ if (g_mkdir_with_parents (dir, 0755) != 0)
+ {
+ eel_show_error_dialog (_("The emblem cannot be installed."),
+ _("Sorry, unable to save custom emblem."),
+ GTK_WINDOW (parent_window));
+ g_free (dir);
+ g_free (stat_dir);
+ return;
+ }
+
+ basename = g_strdup_printf ("emblem-%s.png", keyword);
+ path = g_build_filename (dir, basename, NULL);
+ g_free (basename);
+
+ /* save the image */
+ if (eel_gdk_pixbuf_save_to_file (pixbuf, path) != TRUE)
+ {
+ eel_show_error_dialog (_("The emblem cannot be installed."),
+ _("Sorry, unable to save custom emblem."),
+ GTK_WINDOW (parent_window));
+ g_free (dir);
+ g_free (stat_dir);
+ g_free (path);
+ return;
+ }
+
+ g_free (path);
+
+ if (display_name != NULL)
+ {
+ char *contents;
+
+ basename = g_strdup_printf ("emblem-%s.icon", keyword);
+ path = g_build_filename (dir, basename, NULL);
+ g_free (basename);
+
+ contents = g_strdup_printf ("\n[Icon Data]\n\nDisplayName=%s\n",
+ display_name);
+
+ if (!g_file_set_contents (path, contents, strlen (contents), NULL))
+ {
+ eel_show_error_dialog (_("The emblem cannot be installed."),
+ _("Sorry, unable to save custom emblem name."),
+ GTK_WINDOW (parent_window));
+ g_free (contents);
+ g_free (path);
+ g_free (stat_dir);
+ g_free (dir);
+ return;
+ }
+
+ g_free (contents);
+ g_free (path);
+ }
+
+ /* Touch the toplevel dir */
+ if (g_stat (stat_dir, &stat_buf) == 0)
+ {
+ ubuf.actime = stat_buf.st_atime;
+ ubuf.modtime = time (NULL);
+ utime (stat_dir, &ubuf);
+ }
+
+ g_free (dir);
+ g_free (stat_dir);
+
+ return;
+}
+
+gboolean
+caja_emblem_can_remove_emblem (const char *keyword)
+{
+ char *path;
+ gboolean ret = TRUE;
+
+ path = g_strdup_printf ("%s/.icons/hicolor/48x48/emblems/emblem-%s.png",
+ g_get_home_dir (), keyword);
+
+ if (g_access (path, F_OK|W_OK) != 0)
+ {
+ ret = FALSE;
+ }
+
+ g_free (path);
+
+ return ret;
+}
+
+gboolean
+caja_emblem_can_rename_emblem (const char *keyword)
+{
+ char *path;
+ gboolean ret = TRUE;
+
+ path = g_strdup_printf ("%s/.icons/hicolor/48x48/emblems/emblem-%s.png",
+ g_get_home_dir (), keyword);
+
+ if (access (path, F_OK|R_OK) != 0)
+ {
+ ret = FALSE;
+ }
+
+ g_free (path);
+
+ return ret;
+}
+
+/* of course, this only works for custom installed emblems */
+gboolean
+caja_emblem_remove_emblem (const char *keyword)
+{
+ char *path, *dir, *stat_dir;
+ struct stat stat_buf;
+ struct utimbuf ubuf;
+
+
+ dir = g_strdup_printf ("%s/.icons/hicolor/48x48/emblems",
+ g_get_home_dir ());
+ stat_dir = g_strdup_printf ("%s/.icons/hicolor",
+ g_get_home_dir ());
+
+ path = g_strdup_printf ("%s/emblem-%s.png", dir, keyword);
+
+ /* delete the image */
+ if (unlink (path) != 0)
+ {
+ /* couldn't delete it */
+ g_free (dir);
+ g_free (stat_dir);
+ g_free (path);
+ return FALSE;
+ }
+
+ g_free (path);
+
+ path = g_strdup_printf ("%s/emblem-%s.icon", dir, keyword);
+
+ if (unlink (path) != 0)
+ {
+ g_free (dir);
+ g_free (stat_dir);
+ g_free (path);
+ return FALSE;
+ }
+
+ /* Touch the toplevel dir */
+ if (stat (stat_dir, &stat_buf) == 0)
+ {
+ ubuf.actime = stat_buf.st_atime;
+ ubuf.modtime = time (NULL);
+ utime (stat_dir, &ubuf);
+ }
+
+ g_free (dir);
+ g_free (stat_dir);
+
+ return TRUE;
+}
+
+/* this only works for custom emblems as well */
+gboolean
+caja_emblem_rename_emblem (const char *keyword, const char *name)
+{
+ char *path, *dir, *stat_dir, *icon_name;
+ struct stat stat_buf;
+ struct utimbuf ubuf;
+ FILE *file;
+
+ dir = g_strdup_printf ("%s/.icons/hicolor/48x48/emblems",
+ g_get_home_dir ());
+ stat_dir = g_strdup_printf ("%s/.icons/hicolor",
+ g_get_home_dir ());
+
+ path = g_strdup_printf ("%s/emblem-%s.icon", dir, keyword);
+
+ file = fopen (path, "w+");
+ g_free (path);
+
+ if (file == NULL)
+ {
+ g_free (dir);
+ g_free (stat_dir);
+ return FALSE;
+ }
+
+
+ /* write the new icon description */
+ fprintf (file, "\n[Icon Data]\n\nDisplayName=%s\n", name);
+ fflush (file);
+ fclose (file);
+
+ icon_name = caja_emblem_get_icon_name_from_keyword (keyword);
+ caja_icon_info_clear_caches (); /* A bit overkill, but this happens rarely */
+
+ g_free (icon_name);
+
+ /* Touch the toplevel dir */
+ if (stat (stat_dir, &stat_buf) == 0)
+ {
+ ubuf.actime = stat_buf.st_atime;
+ ubuf.modtime = time (NULL);
+ utime (stat_dir, &ubuf);
+ }
+
+ g_free (dir);
+ g_free (stat_dir);
+
+ return TRUE;
+}
+
+char *
+caja_emblem_create_unique_keyword (const char *base)
+{
+ char *keyword;
+ time_t t;
+ int i;
+
+ time (&t);
+ i=0;
+
+ keyword = NULL;
+ do
+ {
+ g_free (keyword);
+ keyword = g_strdup_printf ("user%s%d%d", base, (int)t, i++);
+ }
+ while (is_reserved_keyword (keyword));
+
+ return keyword;
+}
diff --git a/libcaja-private/caja-emblem-utils.h b/libcaja-private/caja-emblem-utils.h
new file mode 100644
index 00000000..ef679f8c
--- /dev/null
+++ b/libcaja-private/caja-emblem-utils.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-emblem-utils.h: Utilities for handling emblems
+
+ Copyright (C) 2002 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Alexander Larsson <[email protected]>
+*/
+
+#include <glib.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+GList * caja_emblem_list_available (void);
+void caja_emblem_refresh_list (void);
+gboolean caja_emblem_should_show_in_list (const char *emblem);
+gboolean caja_emblem_verify_keyword (GtkWindow *parent_window,
+ const char *keyword,
+ const char *display_name);
+void caja_emblem_install_custom_emblem (GdkPixbuf *pixbuf,
+ const char *keyword,
+ const char *display_name,
+ GtkWindow *parent_window);
+
+gboolean caja_emblem_remove_emblem (const char *keyword);
+gboolean caja_emblem_rename_emblem (const char *keyword,
+ const char *display_name);
+
+GdkPixbuf *caja_emblem_load_pixbuf_for_emblem (GFile *emblem);
+char * caja_emblem_get_keyword_from_icon_name (const char *emblem);
+char * caja_emblem_get_icon_name_from_keyword (const char *keyword);
+
+gboolean caja_emblem_can_remove_emblem (const char *keyword);
+gboolean caja_emblem_can_rename_emblem (const char *keyword);
+
+char * caja_emblem_create_unique_keyword (const char *base);
+
diff --git a/libcaja-private/caja-entry.c b/libcaja-private/caja-entry.c
new file mode 100644
index 00000000..5ebb62a6
--- /dev/null
+++ b/libcaja-private/caja-entry.c
@@ -0,0 +1,442 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* CajaEntry: 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 <[email protected]>
+ *
+ * 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "caja-entry.h"
+
+#include <string.h>
+#include "caja-global-preferences.h"
+#include "caja-undo-signal-handlers.h"
+#include <eel/eel-gdk-extensions.h>
+#include <eel/eel-gtk-macros.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+
+struct CajaEntryDetails
+{
+ gboolean user_edit;
+ gboolean special_tab_handling;
+
+ guint select_idle_id;
+};
+
+enum
+{
+ USER_CHANGED,
+ SELECTION_CHANGED,
+ LAST_SIGNAL
+};
+static guint signals[LAST_SIGNAL];
+
+static void caja_entry_editable_init (GtkEditableClass *iface);
+
+G_DEFINE_TYPE_WITH_CODE (CajaEntry, caja_entry, GTK_TYPE_ENTRY,
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE,
+ caja_entry_editable_init));
+
+static GtkEditableClass *parent_editable_interface = NULL;
+
+static void
+caja_entry_init (CajaEntry *entry)
+{
+ GtkWidget *widget;
+
+ widget = GTK_WIDGET (entry);
+ entry->details = g_new0 (CajaEntryDetails, 1);
+
+ entry->details->user_edit = TRUE;
+
+ caja_undo_set_up_caja_entry_for_undo (entry);
+}
+
+GtkWidget *
+caja_entry_new (void)
+{
+ return gtk_widget_new (CAJA_TYPE_ENTRY, NULL);
+}
+
+GtkWidget *
+caja_entry_new_with_max_length (guint16 max)
+{
+ GtkWidget *widget;
+
+ widget = gtk_widget_new (CAJA_TYPE_ENTRY, NULL);
+ gtk_entry_set_max_length (GTK_ENTRY (widget), max);
+
+ return widget;
+}
+
+static void
+caja_entry_finalize (GObject *object)
+{
+ CajaEntry *entry;
+
+ entry = CAJA_ENTRY (object);
+
+ if (entry->details->select_idle_id != 0)
+ {
+ g_source_remove (entry->details->select_idle_id);
+ }
+
+ g_free (entry->details);
+
+ G_OBJECT_CLASS (caja_entry_parent_class)->finalize (object);
+}
+
+static gboolean
+caja_entry_key_press (GtkWidget *widget, GdkEventKey *event)
+{
+ CajaEntry *entry;
+ GtkEditable *editable;
+ int position;
+ gboolean old_has, new_has;
+ gboolean result;
+
+ entry = CAJA_ENTRY (widget);
+ editable = GTK_EDITABLE (widget);
+
+ if (!gtk_editable_get_editable (editable))
+ {
+ return FALSE;
+ }
+
+ switch (event->keyval)
+ {
+ case GDK_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 (caja_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
+caja_entry_motion_notify (GtkWidget *widget, GdkEventMotion *event)
+{
+ int result;
+ gboolean old_had, new_had;
+ int old_start, old_end, new_start, new_end;
+ CajaEntry *entry;
+ GtkEditable *editable;
+
+ entry = CAJA_ENTRY (widget);
+ editable = GTK_EDITABLE (widget);
+
+ old_had = gtk_editable_get_selection_bounds (editable, &old_start, &old_end);
+
+ result = GTK_WIDGET_CLASS (caja_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;
+}
+
+/**
+ * caja_entry_select_all
+ *
+ * Select all text, leaving the text cursor position at the end.
+ *
+ * @entry: A CajaEntry
+ **/
+void
+caja_entry_select_all (CajaEntry *entry)
+{
+ g_return_if_fail (CAJA_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)
+{
+ CajaEntry *entry;
+
+ entry = CAJA_ENTRY (callback_data);
+
+ caja_entry_select_all (entry);
+
+ entry->details->select_idle_id = 0;
+
+ return FALSE;
+}
+
+/**
+ * caja_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 CajaEntry
+ **/
+void
+caja_entry_select_all_at_idle (CajaEntry *entry)
+{
+ g_return_if_fail (CAJA_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);
+ }
+}
+
+/**
+ * caja_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 CajaEntry
+ * @test: The text to set
+ **/
+
+void
+caja_entry_set_text (CajaEntry *entry, const gchar *text)
+{
+ g_return_if_fail (CAJA_IS_ENTRY (entry));
+
+ entry->details->user_edit = FALSE;
+ gtk_entry_set_text (GTK_ENTRY (entry), text);
+ entry->details->user_edit = TRUE;
+
+ g_signal_emit (entry, signals[SELECTION_CHANGED], 0);
+}
+
+static void
+caja_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
+caja_entry_button_press (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ gboolean result;
+
+ result = GTK_WIDGET_CLASS (caja_entry_parent_class)->button_press_event (widget, event);
+
+ if (result)
+ {
+ g_signal_emit (widget, signals[SELECTION_CHANGED], 0);
+ }
+
+ return result;
+}
+
+static gboolean
+caja_entry_button_release (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ gboolean result;
+
+ result = GTK_WIDGET_CLASS (caja_entry_parent_class)->button_release_event (widget, event);
+
+ if (result)
+ {
+ g_signal_emit (widget, signals[SELECTION_CHANGED], 0);
+ }
+
+ return result;
+}
+
+static void
+caja_entry_insert_text (GtkEditable *editable, const gchar *text,
+ int length, int *position)
+{
+ CajaEntry *entry;
+
+ entry = CAJA_ENTRY(editable);
+
+ /* Fire off user changed signals */
+ if (entry->details->user_edit)
+ {
+ g_signal_emit (editable, signals[USER_CHANGED], 0);
+ }
+
+ parent_editable_interface->insert_text (editable, text, length, position);
+
+ g_signal_emit (editable, signals[SELECTION_CHANGED], 0);
+}
+
+static void
+caja_entry_delete_text (GtkEditable *editable, int start_pos, int end_pos)
+{
+ CajaEntry *entry;
+
+ entry = CAJA_ENTRY (editable);
+
+ /* Fire off user changed signals */
+ if (entry->details->user_edit)
+ {
+ g_signal_emit (editable, signals[USER_CHANGED], 0);
+ }
+
+ 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 CajaLocationBar (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
+caja_entry_selection_clear (GtkWidget *widget,
+ GdkEventSelection *event)
+{
+ g_assert (CAJA_IS_ENTRY (widget));
+
+ if (gdk_selection_owner_get (event->selection) == gtk_widget_get_window (widget))
+ {
+ return FALSE;
+ }
+
+ return GTK_WIDGET_CLASS (caja_entry_parent_class)->selection_clear_event (widget, event);
+}
+
+static void
+caja_entry_editable_init (GtkEditableClass *iface)
+{
+ parent_editable_interface = g_type_interface_peek_parent (iface);
+
+ iface->insert_text = caja_entry_insert_text;
+ iface->delete_text = caja_entry_delete_text;
+ iface->set_selection_bounds = caja_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
+caja_entry_class_init (CajaEntryClass *class)
+{
+ GtkWidgetClass *widget_class;
+ GtkObjectClass *object_class;
+ GObjectClass *gobject_class;
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ gobject_class = G_OBJECT_CLASS (class);
+ object_class = GTK_OBJECT_CLASS (class);
+
+ widget_class->button_press_event = caja_entry_button_press;
+ widget_class->button_release_event = caja_entry_button_release;
+ widget_class->key_press_event = caja_entry_key_press;
+ widget_class->motion_notify_event = caja_entry_motion_notify;
+ widget_class->selection_clear_event = caja_entry_selection_clear;
+
+ gobject_class->finalize = caja_entry_finalize;
+
+ /* Set up signals */
+ signals[USER_CHANGED] = g_signal_new
+ ("user_changed",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaEntryClass,
+ user_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ signals[SELECTION_CHANGED] = g_signal_new
+ ("selection_changed",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaEntryClass,
+ selection_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+void
+caja_entry_set_special_tab_handling (CajaEntry *entry,
+ gboolean special_tab_handling)
+{
+ g_return_if_fail (CAJA_IS_ENTRY (entry));
+
+ entry->details->special_tab_handling = special_tab_handling;
+}
+
+
diff --git a/libcaja-private/caja-entry.h b/libcaja-private/caja-entry.h
new file mode 100644
index 00000000..5c774bda
--- /dev/null
+++ b/libcaja-private/caja-entry.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* CajaEntry: 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 <[email protected]>
+ *
+ * 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef CAJA_ENTRY_H
+#define CAJA_ENTRY_H
+
+#include <gtk/gtk.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define CAJA_TYPE_ENTRY caja_entry_get_type()
+#define CAJA_ENTRY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_ENTRY, CajaEntry))
+#define CAJA_ENTRY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_ENTRY, CajaEntryClass))
+#define CAJA_IS_ENTRY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_ENTRY))
+#define CAJA_IS_ENTRY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_ENTRY))
+#define CAJA_ENTRY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_ENTRY, CajaEntryClass))
+
+ typedef struct CajaEntryDetails CajaEntryDetails;
+
+ typedef struct
+ {
+ GtkEntry parent;
+ CajaEntryDetails *details;
+ } CajaEntry;
+
+ typedef struct
+ {
+ GtkEntryClass parent_class;
+
+ void (*user_changed) (CajaEntry *entry);
+ void (*selection_changed) (CajaEntry *entry);
+ } CajaEntryClass;
+
+ GType caja_entry_get_type (void);
+ GtkWidget *caja_entry_new (void);
+ GtkWidget *caja_entry_new_with_max_length (guint16 max);
+ void caja_entry_set_text (CajaEntry *entry,
+ const char *text);
+ void caja_entry_select_all (CajaEntry *entry);
+ void caja_entry_select_all_at_idle (CajaEntry *entry);
+ void caja_entry_set_special_tab_handling (CajaEntry *entry,
+ gboolean special_tab_handling);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CAJA_ENTRY_H */
diff --git a/libcaja-private/caja-file-attributes.h b/libcaja-private/caja-file-attributes.h
new file mode 100644
index 00000000..11d643c2
--- /dev/null
+++ b/libcaja-private/caja-file-attributes.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Darin Adler <[email protected]>
+*/
+
+#ifndef CAJA_FILE_ATTRIBUTES_H
+#define CAJA_FILE_ATTRIBUTES_H
+
+/* Names for CajaFile attributes. These are used when registering
+ * interest in changes to the attributes or when waiting for them.
+ */
+
+typedef enum
+{
+ CAJA_FILE_ATTRIBUTE_INFO = 1 << 0, /* All standard info */
+ CAJA_FILE_ATTRIBUTE_LINK_INFO = 1 << 1, /* info from desktop links */
+ CAJA_FILE_ATTRIBUTE_DEEP_COUNTS = 1 << 2,
+ CAJA_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT = 1 << 3,
+ CAJA_FILE_ATTRIBUTE_DIRECTORY_ITEM_MIME_TYPES = 1 << 4,
+ CAJA_FILE_ATTRIBUTE_TOP_LEFT_TEXT = 1 << 5,
+ CAJA_FILE_ATTRIBUTE_LARGE_TOP_LEFT_TEXT = 1 << 6,
+ CAJA_FILE_ATTRIBUTE_EXTENSION_INFO = 1 << 7,
+ CAJA_FILE_ATTRIBUTE_THUMBNAIL = 1 << 8,
+ CAJA_FILE_ATTRIBUTE_MOUNT = 1 << 9,
+ CAJA_FILE_ATTRIBUTE_FILESYSTEM_INFO = 1 << 10,
+} CajaFileAttributes;
+
+#endif /* CAJA_FILE_ATTRIBUTES_H */
diff --git a/libcaja-private/caja-file-changes-queue.c b/libcaja-private/caja-file-changes-queue.c
new file mode 100644
index 00000000..0be95302
--- /dev/null
+++ b/libcaja-private/caja-file-changes-queue.c
@@ -0,0 +1,475 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Pavel Cisler <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-file-changes-queue.h"
+
+#include "caja-directory-notify.h"
+#include <eel/eel-glib-extensions.h>
+
+#ifdef G_THREADS_ENABLED
+#define MUTEX_LOCK(a) if ((a) != NULL) g_mutex_lock (a)
+#define MUTEX_UNLOCK(a) if ((a) != NULL) g_mutex_unlock (a)
+#else
+#define MUTEX_LOCK(a)
+#define MUTEX_UNLOCK(a)
+#endif
+
+typedef enum
+{
+ CHANGE_FILE_INITIAL,
+ CHANGE_FILE_ADDED,
+ CHANGE_FILE_CHANGED,
+ CHANGE_FILE_REMOVED,
+ CHANGE_FILE_MOVED,
+ CHANGE_POSITION_SET,
+ CHANGE_POSITION_REMOVE
+} CajaFileChangeKind;
+
+typedef struct
+{
+ CajaFileChangeKind kind;
+ GFile *from;
+ GFile *to;
+ GdkPoint point;
+ int screen;
+} CajaFileChange;
+
+typedef struct
+{
+ GList *head;
+ GList *tail;
+#ifdef G_THREADS_ENABLED
+ GMutex *mutex;
+#endif
+} CajaFileChangesQueue;
+
+static CajaFileChangesQueue *
+caja_file_changes_queue_new (void)
+{
+ CajaFileChangesQueue *result;
+
+ result = g_new0 (CajaFileChangesQueue, 1);
+
+#ifdef G_THREADS_ENABLED
+ result->mutex = g_mutex_new ();
+#endif
+ return result;
+}
+
+static CajaFileChangesQueue *
+caja_file_changes_queue_get (void)
+{
+ static CajaFileChangesQueue *file_changes_queue;
+
+ if (file_changes_queue == NULL)
+ {
+ file_changes_queue = caja_file_changes_queue_new ();
+ }
+
+ return file_changes_queue;
+}
+
+#if 0 /* no public free call yet */
+
+static void
+caja_file_change_free (CajaFileChange *change)
+{
+ if (change->from)
+ {
+ g_object_unref (change->from);
+ }
+ if (change->to)
+ {
+ g_object_unref (change->to);
+ }
+}
+
+void
+caja_file_changes_queue_free (CajaFileChangesQueue *queue)
+{
+ GList *p;
+ if (queue == NULL)
+ {
+ return;
+ }
+
+#ifdef G_THREADS_ENABLED
+ /* if lock on a defunct mutex were defined (returning a failure)
+ * we would lock here
+ */
+#endif
+
+ for (p = queue->head; p != NULL; p = p->next)
+ {
+ caja_file_change_free (p->data);
+ }
+ g_list_free (queue->head);
+
+#ifdef G_THREADS_ENABLED
+ g_mutex_free (queue->mutex);
+#endif
+ g_free (queue);
+}
+
+#endif /* no public free call yet */
+
+static void
+caja_file_changes_queue_add_common (CajaFileChangesQueue *queue,
+ CajaFileChange *new_item)
+{
+ /* enqueue the new queue item while locking down the list */
+ MUTEX_LOCK (queue->mutex);
+
+ queue->head = g_list_prepend (queue->head, new_item);
+ if (queue->tail == NULL)
+ queue->tail = queue->head;
+
+ MUTEX_UNLOCK (queue->mutex);
+}
+
+void
+caja_file_changes_queue_file_added (GFile *location)
+{
+ CajaFileChange *new_item;
+ CajaFileChangesQueue *queue;
+
+ queue = caja_file_changes_queue_get();
+
+ new_item = g_new0 (CajaFileChange, 1);
+ new_item->kind = CHANGE_FILE_ADDED;
+ new_item->from = g_object_ref (location);
+ caja_file_changes_queue_add_common (queue, new_item);
+}
+
+void
+caja_file_changes_queue_file_changed (GFile *location)
+{
+ CajaFileChange *new_item;
+ CajaFileChangesQueue *queue;
+
+ queue = caja_file_changes_queue_get();
+
+ new_item = g_new0 (CajaFileChange, 1);
+ new_item->kind = CHANGE_FILE_CHANGED;
+ new_item->from = g_object_ref (location);
+ caja_file_changes_queue_add_common (queue, new_item);
+}
+
+void
+caja_file_changes_queue_file_removed (GFile *location)
+{
+ CajaFileChange *new_item;
+ CajaFileChangesQueue *queue;
+
+ queue = caja_file_changes_queue_get();
+
+ new_item = g_new0 (CajaFileChange, 1);
+ new_item->kind = CHANGE_FILE_REMOVED;
+ new_item->from = g_object_ref (location);
+ caja_file_changes_queue_add_common (queue, new_item);
+}
+
+void
+caja_file_changes_queue_file_moved (GFile *from,
+ GFile *to)
+{
+ CajaFileChange *new_item;
+ CajaFileChangesQueue *queue;
+
+ queue = caja_file_changes_queue_get ();
+
+ new_item = g_new (CajaFileChange, 1);
+ new_item->kind = CHANGE_FILE_MOVED;
+ new_item->from = g_object_ref (from);
+ new_item->to = g_object_ref (to);
+ caja_file_changes_queue_add_common (queue, new_item);
+}
+
+void
+caja_file_changes_queue_schedule_position_set (GFile *location,
+ GdkPoint point,
+ int screen)
+{
+ CajaFileChange *new_item;
+ CajaFileChangesQueue *queue;
+
+ queue = caja_file_changes_queue_get ();
+
+ new_item = g_new (CajaFileChange, 1);
+ new_item->kind = CHANGE_POSITION_SET;
+ new_item->from = g_object_ref (location);
+ new_item->point = point;
+ new_item->screen = screen;
+ caja_file_changes_queue_add_common (queue, new_item);
+}
+
+void
+caja_file_changes_queue_schedule_position_remove (GFile *location)
+{
+ CajaFileChange *new_item;
+ CajaFileChangesQueue *queue;
+
+ queue = caja_file_changes_queue_get ();
+
+ new_item = g_new (CajaFileChange, 1);
+ new_item->kind = CHANGE_POSITION_REMOVE;
+ new_item->from = g_object_ref (location);
+ caja_file_changes_queue_add_common (queue, new_item);
+}
+
+static CajaFileChange *
+caja_file_changes_queue_get_change (CajaFileChangesQueue *queue)
+{
+ GList *new_tail;
+ CajaFileChange *result;
+
+ g_assert (queue != NULL);
+
+ /* dequeue the tail item while locking down the list */
+ 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;
+ }
+
+ 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 */
+ eel_g_list_free_deep (pairs);
+}
+
+static void
+position_set_list_free (GList *list)
+{
+ GList *p;
+ CajaFileChangesQueuePosition *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 */
+ eel_g_list_free_deep (list);
+}
+
+/* go through changes in the change queue, send ones with the same kind
+ * in a list to the different caja_directory_notify calls
+ */
+void
+caja_file_changes_consume_changes (gboolean consume_all)
+{
+ CajaFileChange *change;
+ GList *additions, *changes, *deletions, *moves;
+ GList *position_set_requests;
+ GFilePair *pair;
+ CajaFileChangesQueuePosition *position_set;
+ guint chunk_count;
+ CajaFileChangesQueue *queue;
+ gboolean flush_needed;
+
+
+ additions = NULL;
+ changes = NULL;
+ deletions = NULL;
+ moves = NULL;
+ position_set_requests = NULL;
+
+ queue = caja_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 = caja_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);
+ caja_directory_notify_files_removed (deletions);
+ eel_g_object_list_free (deletions);
+ deletions = NULL;
+ }
+ if (moves != NULL)
+ {
+ moves = g_list_reverse (moves);
+ caja_directory_notify_files_moved (moves);
+ pairs_list_free (moves);
+ moves = NULL;
+ }
+ if (additions != NULL)
+ {
+ additions = g_list_reverse (additions);
+ caja_directory_notify_files_added (additions);
+ eel_g_object_list_free (additions);
+ additions = NULL;
+ }
+ if (changes != NULL)
+ {
+ changes = g_list_reverse (changes);
+ caja_directory_notify_files_changed (changes);
+ eel_g_object_list_free (changes);
+ changes = NULL;
+ }
+ if (position_set_requests != NULL)
+ {
+ position_set_requests = g_list_reverse (position_set_requests);
+ caja_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 (CajaFileChangesQueuePosition, 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 (CajaFileChangesQueuePosition, 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/libcaja-private/caja-file-changes-queue.h b/libcaja-private/caja-file-changes-queue.h
new file mode 100644
index 00000000..3b69bc75
--- /dev/null
+++ b/libcaja-private/caja-file-changes-queue.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Pavel Cisler <[email protected]>
+*/
+
+#ifndef CAJA_FILE_CHANGES_QUEUE_H
+#define CAJA_FILE_CHANGES_QUEUE_H
+
+#include <gdk/gdk.h>
+#include <gio/gio.h>
+
+void caja_file_changes_queue_file_added (GFile *location);
+void caja_file_changes_queue_file_changed (GFile *location);
+void caja_file_changes_queue_file_removed (GFile *location);
+void caja_file_changes_queue_file_moved (GFile *from,
+ GFile *to);
+void caja_file_changes_queue_schedule_position_set (GFile *location,
+ GdkPoint point,
+ int screen);
+void caja_file_changes_queue_schedule_position_remove (GFile *location);
+
+void caja_file_changes_consume_changes (gboolean consume_all);
+
+
+#endif /* CAJA_FILE_CHANGES_QUEUE_H */
diff --git a/libcaja-private/caja-file-conflict-dialog.c b/libcaja-private/caja-file-conflict-dialog.c
new file mode 100644
index 00000000..b37bb879
--- /dev/null
+++ b/libcaja-private/caja-file-conflict-dialog.c
@@ -0,0 +1,670 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Cosimo Cecchi <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-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 "caja-file.h"
+#include "caja-icon-info.h"
+
+struct _CajaFileConflictDialogDetails
+{
+ /* conflicting objects */
+ CajaFile *source;
+ CajaFile *destination;
+ CajaFile *dest_dir;
+
+ gchar *conflict_name;
+ CajaFileListHandle *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 (CajaFileConflictDialog,
+ caja_file_conflict_dialog,
+ GTK_TYPE_DIALOG);
+
+#define CAJA_FILE_CONFLICT_DIALOG_GET_PRIVATE(object) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((object), CAJA_TYPE_FILE_CONFLICT_DIALOG, \
+ CajaFileConflictDialogDetails))
+
+static void
+file_icons_changed (CajaFile *file,
+ CajaFileConflictDialog *fcd)
+{
+ GdkPixbuf *pixbuf;
+
+ pixbuf = caja_file_get_icon_pixbuf (fcd->details->destination,
+ CAJA_ICON_SIZE_LARGE,
+ TRUE,
+ CAJA_FILE_ICON_FLAGS_USE_THUMBNAILS);
+
+ gtk_image_set_from_pixbuf (GTK_IMAGE (fcd->details->dest_image), pixbuf);
+ g_object_unref (pixbuf);
+
+ pixbuf = caja_file_get_icon_pixbuf (fcd->details->source,
+ CAJA_ICON_SIZE_LARGE,
+ TRUE,
+ CAJA_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)
+{
+ CajaFileConflictDialog *fcd = user_data;
+ CajaFile *src, *dest, *dest_dir;
+ time_t src_mtime, dest_mtime;
+ GtkDialog *dialog;
+ gboolean source_is_dir, dest_is_dir, should_show_type;
+ CajaFileConflictDialogDetails *details;
+ char *primary_text, *message, *secondary_text;
+ const gchar *message_extra;
+ char *src_name, *dest_name, *dest_dir_name, *edit_name;
+ char *label_text;
+ char *size, *date, *type = NULL;
+ GdkPixbuf *pixbuf;
+ GtkWidget *label;
+ GString *str;
+ PangoFontDescription *desc;
+
+ dialog = GTK_DIALOG (fcd);
+ 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 = caja_file_get_mtime (src);
+ dest_mtime = caja_file_get_mtime (dest);
+
+ src_name = caja_file_get_display_name (src);
+ dest_name = caja_file_get_display_name (dest);
+ dest_dir_name = caja_file_get_display_name (dest_dir);
+
+ source_is_dir = caja_file_is_directory (src);
+ dest_is_dir = caja_file_is_directory (dest);
+
+ type = caja_file_get_mime_type (dest);
+ should_show_type = !caja_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_widget_set_size_request (label, 350, -1);
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_box_pack_start (GTK_BOX (details->titles_vbox),
+ label, FALSE, FALSE, 0);
+ gtk_widget_modify_font (label, NULL);
+ desc = pango_font_description_new ();
+ pango_font_description_set_weight (desc, PANGO_WEIGHT_BOLD);
+ pango_font_description_set_size (desc,
+ pango_font_description_get_size (gtk_widget_get_style (label)->font_desc) * PANGO_SCALE_LARGE);
+ gtk_widget_modify_font (label, desc);
+ pango_font_description_free (desc);
+ gtk_widget_show (label);
+
+ label = gtk_label_new (secondary_text);
+ gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+ gtk_widget_set_size_request (label, 350, -1);
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ 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 = caja_file_get_icon_pixbuf (dest,
+ CAJA_ICON_SIZE_LARGE,
+ TRUE,
+ CAJA_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 = caja_file_get_icon_pixbuf (src,
+ CAJA_ICON_SIZE_LARGE,
+ TRUE,
+ CAJA_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 = caja_file_get_string_attribute (dest,
+ "date_modified");
+ size = caja_file_get_string_attribute (dest, "size");
+
+ if (should_show_type)
+ {
+ type = caja_file_get_string_attribute (dest, "type");
+ }
+
+ str = g_string_new (NULL);
+ g_string_append_printf (str, "<b>%s</b>\n", _("Original file"));
+ g_string_append_printf (str, "<i>%s</i> %s\n", _("Size:"), size);
+
+ if (should_show_type)
+ {
+ g_string_append_printf (str, "<i>%s</i> %s\n", _("Type:"), type);
+ }
+
+ g_string_append_printf (str, "<i>%s</i> %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 = caja_file_get_string_attribute (src,
+ "date_modified");
+ size = caja_file_get_string_attribute (src, "size");
+
+ if (should_show_type)
+ {
+ type = caja_file_get_string_attribute (src, "type");
+ }
+
+ g_string_append_printf (str, "<b>%s</b>\n", _("Replace with"));
+ g_string_append_printf (str, "<i>%s</i> %s\n", _("Size:"), size);
+
+ if (should_show_type)
+ {
+ g_string_append_printf (str, "<i>%s</i> %s\n", _("Type:"), type);
+ }
+
+ g_string_append_printf (str, "<i>%s</i> %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 = caja_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"));
+ }
+
+ caja_file_monitor_add (src, fcd, CAJA_FILE_ATTRIBUTES_FOR_ICON);
+ caja_file_monitor_add (dest, fcd, CAJA_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 (CajaFileConflictDialog *fcd)
+{
+ GList *files = NULL;
+ CajaFileConflictDialogDetails *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);
+
+ caja_file_list_call_when_ready (files,
+ CAJA_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)
+{
+ CajaFileConflictDialog *dialog;
+ CajaFileConflictDialogDetails *details;
+
+ dialog = CAJA_FILE_CONFLICT_DIALOG (w);
+ details = dialog->details;
+
+ details->source = caja_file_get (source);
+ details->destination = caja_file_get (destination);
+ details->dest_dir = caja_file_get (dest_dir);
+
+ build_dialog_appearance (dialog);
+}
+
+static void
+entry_text_changed_cb (GtkEditable *entry,
+ CajaFileConflictDialog *dialog)
+{
+ CajaFileConflictDialogDetails *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,
+ CajaFileConflictDialog *dialog)
+{
+ CajaFileConflictDialogDetails *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,
+ CajaFileConflictDialog *dialog)
+{
+ CajaFileConflictDialogDetails *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,
+ CajaFileConflictDialog *dialog)
+{
+ CajaFileConflictDialogDetails *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
+caja_file_conflict_dialog_init (CajaFileConflictDialog *fcd)
+{
+ GtkWidget *hbox, *vbox, *vbox2, *alignment;
+ GtkWidget *widget, *dialog_area;
+ CajaFileConflictDialogDetails *details;
+ GtkDialog *dialog;
+
+ details = fcd->details = CAJA_FILE_CONFLICT_DIALOG_GET_PRIVATE (fcd);
+ dialog = GTK_DIALOG (fcd);
+
+ /* Setup the main hbox */
+ hbox = gtk_hbox_new (FALSE, 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_stock (GTK_STOCK_DIALOG_WARNING,
+ GTK_ICON_SIZE_DIALOG);
+ gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
+ gtk_misc_set_alignment (GTK_MISC (widget), 0.5, 0.0);
+
+ /* Setup the vbox containing the dialog body */
+ vbox = gtk_vbox_new (FALSE, 12);
+ gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 0);
+
+ /* Setup the vbox for the dialog labels */
+ widget = gtk_vbox_new (FALSE, 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_vbox_new (FALSE, 12);
+ gtk_container_add (GTK_CONTAINER (alignment), vbox2);
+ gtk_box_pack_start (GTK_BOX (vbox), alignment, FALSE, FALSE, 0);
+
+ hbox = gtk_hbox_new (FALSE, 12);
+ gtk_box_pack_start (GTK_BOX (vbox2), hbox, FALSE, FALSE, 0);
+ details->first_hbox = hbox;
+
+ hbox = gtk_hbox_new (FALSE, 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_hbox_new (FALSE, 6);
+ gtk_container_add (GTK_CONTAINER (details->expander), hbox);
+
+ widget = gtk_entry_new ();
+ gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 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_stock (GTK_STOCK_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"));
+ 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,
+ GTK_STOCK_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_dialog_set_has_separator (dialog, FALSE);
+
+ gtk_widget_show_all (dialog_area);
+}
+
+static void
+do_finalize (GObject *self)
+{
+ CajaFileConflictDialogDetails *details =
+ CAJA_FILE_CONFLICT_DIALOG (self)->details;
+
+ g_free (details->conflict_name);
+
+ if (details->handle != NULL)
+ {
+ caja_file_list_cancel_call_when_ready (details->handle);
+ }
+
+ if (details->src_handler_id)
+ {
+ g_signal_handler_disconnect (details->source, details->src_handler_id);
+ caja_file_monitor_remove (details->source, self);
+ }
+
+ if (details->dest_handler_id)
+ {
+ g_signal_handler_disconnect (details->destination, details->dest_handler_id);
+ caja_file_monitor_remove (details->destination, self);
+ }
+
+ caja_file_unref (details->source);
+ caja_file_unref (details->destination);
+ caja_file_unref (details->dest_dir);
+
+ G_OBJECT_CLASS (caja_file_conflict_dialog_parent_class)->finalize (self);
+}
+
+static void
+caja_file_conflict_dialog_class_init (CajaFileConflictDialogClass *klass)
+{
+ G_OBJECT_CLASS (klass)->finalize = do_finalize;
+
+ g_type_class_add_private (klass, sizeof (CajaFileConflictDialogDetails));
+}
+
+char *
+caja_file_conflict_dialog_get_new_name (CajaFileConflictDialog *dialog)
+{
+ return g_strdup (gtk_entry_get_text
+ (GTK_ENTRY (dialog->details->entry)));
+}
+
+gboolean
+caja_file_conflict_dialog_get_apply_to_all (CajaFileConflictDialog *dialog)
+{
+ return gtk_toggle_button_get_active
+ (GTK_TOGGLE_BUTTON (dialog->details->checkbox));
+}
+
+GtkWidget *
+caja_file_conflict_dialog_new (GtkWindow *parent,
+ GFile *source,
+ GFile *destination,
+ GFile *dest_dir)
+{
+ GtkWidget *dialog;
+
+ dialog = GTK_WIDGET (g_object_new (CAJA_TYPE_FILE_CONFLICT_DIALOG,
+ "title", _("File conflict"),
+ NULL));
+ set_source_and_destination (dialog,
+ source,
+ destination,
+ dest_dir);
+ gtk_window_set_transient_for (GTK_WINDOW (dialog),
+ parent);
+ return dialog;
+}
diff --git a/libcaja-private/caja-file-conflict-dialog.h b/libcaja-private/caja-file-conflict-dialog.h
new file mode 100644
index 00000000..b8af1596
--- /dev/null
+++ b/libcaja-private/caja-file-conflict-dialog.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Cosimo Cecchi <[email protected]>
+*/
+
+#ifndef CAJA_FILE_CONFLICT_DIALOG_H
+#define CAJA_FILE_CONFLICT_DIALOG_H
+
+#include <glib-object.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+#define CAJA_TYPE_FILE_CONFLICT_DIALOG \
+ (caja_file_conflict_dialog_get_type ())
+#define CAJA_FILE_CONFLICT_DIALOG(o) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((o), CAJA_TYPE_FILE_CONFLICT_DIALOG,\
+ CajaFileConflictDialog))
+#define CAJA_FILE_CONFLICT_DIALOG_CLASS(k) \
+ (G_TYPE_CHECK_CLASS_CAST((k), CAJA_TYPE_FILE_CONFLICT_DIALOG,\
+ CajaFileConflictDialogClass))
+#define CAJA_IS_FILE_CONFLICT_DIALOG(o) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((o), CAJA_TYPE_FILE_CONFLICT_DIALOG))
+#define CAJA_IS_FILE_CONFLICT_DIALOG_CLASS(k) \
+ (G_TYPE_CHECK_CLASS_TYPE ((k), CAJA_TYPE_FILE_CONFLICT_DIALOG))
+#define CAJA_FILE_CONFLICT_DIALOG_GET_CLASS(o) \
+ (G_TYPE_INSTANCE_GET_CLASS ((o), CAJA_TYPE_FILE_CONFLICT_DIALOG,\
+ CajaFileConflictDialogClass))
+
+typedef struct _CajaFileConflictDialog CajaFileConflictDialog;
+typedef struct _CajaFileConflictDialogClass CajaFileConflictDialogClass;
+typedef struct _CajaFileConflictDialogDetails CajaFileConflictDialogDetails;
+
+struct _CajaFileConflictDialog
+{
+ GtkDialog parent;
+ CajaFileConflictDialogDetails *details;
+};
+
+struct _CajaFileConflictDialogClass
+{
+ GtkDialogClass parent_class;
+};
+
+enum
+{
+ CONFLICT_RESPONSE_SKIP = 1,
+ CONFLICT_RESPONSE_REPLACE = 2,
+ CONFLICT_RESPONSE_RENAME = 3,
+};
+
+GType caja_file_conflict_dialog_get_type (void) G_GNUC_CONST;
+
+GtkWidget* caja_file_conflict_dialog_new (GtkWindow *parent,
+ GFile *source,
+ GFile *destination,
+ GFile *dest_dir);
+char* caja_file_conflict_dialog_get_new_name (CajaFileConflictDialog *dialog);
+gboolean caja_file_conflict_dialog_get_apply_to_all (CajaFileConflictDialog *dialog);
+
+#endif /* CAJA_FILE_CONFLICT_DIALOG_H */
diff --git a/libcaja-private/caja-file-dnd.c b/libcaja-private/caja-file-dnd.c
new file mode 100644
index 00000000..06a61daf
--- /dev/null
+++ b/libcaja-private/caja-file-dnd.c
@@ -0,0 +1,186 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-file-drag.c - Drag & drop handling code that operated on
+ CajaFile objects.
+
+ Copyright (C) 2000 Eazel, Inc.
+
+ The Mate 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 Mate 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 Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Pavel Cisler <[email protected]>,
+*/
+
+#include <config.h>
+#include "caja-file-dnd.h"
+#include "caja-desktop-icon-file.h"
+
+#include "caja-dnd.h"
+#include "caja-directory.h"
+#include "caja-file-utilities.h"
+#include <eel/eel-glib-extensions.h>
+#include <string.h>
+
+static gboolean
+caja_drag_can_accept_files (CajaFile *drop_target_item)
+{
+ CajaDirectory *directory;
+
+ if (caja_file_is_directory (drop_target_item))
+ {
+ gboolean res;
+
+ /* target is a directory, accept if editable */
+ directory = caja_directory_get_for_file (drop_target_item);
+ res = caja_directory_is_editable (directory);
+ caja_directory_unref (directory);
+ return res;
+ }
+
+ if (CAJA_IS_DESKTOP_ICON_FILE (drop_target_item))
+ {
+ return TRUE;
+ }
+
+ /* All Caja links are assumed to be links to directories.
+ * Therefore, they all can accept drags, like all other
+ * directories to. As with other directories, there can be
+ * errors when the actual copy is attempted due to
+ * permissions.
+ */
+ if (caja_file_is_caja_link (drop_target_item))
+ {
+ return TRUE;
+ }
+
+ if (caja_is_file_roller_installed () &&
+ caja_file_is_archive (drop_target_item))
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+gboolean
+caja_drag_can_accept_item (CajaFile *drop_target_item,
+ const char *item_uri)
+{
+ if (caja_file_matches_uri (drop_target_item, item_uri))
+ {
+ /* can't accept itself */
+ return FALSE;
+ }
+
+ return caja_drag_can_accept_files (drop_target_item);
+}
+
+gboolean
+caja_drag_can_accept_items (CajaFile *drop_target_item,
+ const GList *items)
+{
+ int max;
+
+ if (drop_target_item == NULL)
+ return FALSE;
+
+ g_assert (CAJA_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 (!caja_drag_can_accept_item (drop_target_item,
+ ((CajaDragSelectionItem *)items->data)->uri))
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+gboolean
+caja_drag_can_accept_info (CajaFile *drop_target_item,
+ CajaIconDndTargetType drag_type,
+ const GList *items)
+{
+ switch (drag_type)
+ {
+ case CAJA_ICON_DND_MATE_ICON_LIST:
+ return caja_drag_can_accept_items (drop_target_item, items);
+
+ case CAJA_ICON_DND_URI_LIST:
+ case CAJA_ICON_DND_NETSCAPE_URL:
+ case CAJA_ICON_DND_TEXT:
+ return caja_drag_can_accept_files (drop_target_item);
+
+ case CAJA_ICON_DND_XDNDDIRECTSAVE:
+ case CAJA_ICON_DND_RAW:
+ return caja_drag_can_accept_files (drop_target_item); /* Check if we can accept files at this location */
+
+ case CAJA_ICON_DND_KEYWORD:
+ return TRUE;
+
+ case CAJA_ICON_DND_ROOTWINDOW_DROP:
+ return FALSE;
+
+ /* TODO return TRUE for folders as soon as drop handling is implemented */
+ case CAJA_ICON_DND_COLOR:
+ case CAJA_ICON_DND_BGIMAGE:
+ case CAJA_ICON_DND_RESET_BACKGROUND:
+ return FALSE;
+
+ default:
+ g_assert_not_reached ();
+ return FALSE;
+ }
+}
+
+void
+caja_drag_file_receive_dropped_keyword (CajaFile *file,
+ const char *keyword)
+{
+ GList *keywords, *word;
+
+ g_return_if_fail (CAJA_IS_FILE (file));
+ g_return_if_fail (keyword != NULL);
+
+ /* special case the erase emblem */
+ if (strcmp (keyword, CAJA_FILE_DND_ERASE_KEYWORD) == 0)
+ {
+ keywords = NULL;
+ }
+ else
+ {
+ keywords = caja_file_get_keywords (file);
+ word = g_list_find_custom (keywords, keyword, (GCompareFunc) strcmp);
+ if (word == NULL)
+ {
+ keywords = g_list_prepend (keywords, g_strdup (keyword));
+ }
+ else
+ {
+ keywords = g_list_remove_link (keywords, word);
+ g_free (word->data);
+ g_list_free_1 (word);
+ }
+ }
+
+ caja_file_set_keywords (file, keywords);
+ eel_g_list_free_deep (keywords);
+}
diff --git a/libcaja-private/caja-file-dnd.h b/libcaja-private/caja-file-dnd.h
new file mode 100644
index 00000000..5bf66cf7
--- /dev/null
+++ b/libcaja-private/caja-file-dnd.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* caja-file-drag.h - Drag & drop handling code that operated on
+ CajaFile objects.
+
+ Copyright (C) 2000 Eazel, Inc.
+
+ The Mate 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 Mate 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 Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Pavel Cisler <[email protected]>,
+*/
+
+#ifndef CAJA_FILE_DND_H
+#define CAJA_FILE_DND_H
+
+#include <libcaja-private/caja-dnd.h>
+#include <libcaja-private/caja-file.h>
+
+#define CAJA_FILE_DND_ERASE_KEYWORD "erase"
+
+gboolean caja_drag_can_accept_item (CajaFile *drop_target_item,
+ const char *item_uri);
+gboolean caja_drag_can_accept_items (CajaFile *drop_target_item,
+ const GList *items);
+gboolean caja_drag_can_accept_info (CajaFile *drop_target_item,
+ CajaIconDndTargetType drag_type,
+ const GList *items);
+void caja_drag_file_receive_dropped_keyword (CajaFile *file,
+ const char *keyword);
+
+#endif /* CAJA_FILE_DND_H */
+
diff --git a/libcaja-private/caja-file-operations.c b/libcaja-private/caja-file-operations.c
new file mode 100644
index 00000000..90205b83
--- /dev/null
+++ b/libcaja-private/caja-file-operations.c
@@ -0,0 +1,6469 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-file-operations.c - Caja 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Alexander Larsson <[email protected]>
+ Ettore Perazzoli <[email protected]>
+ Pavel Cisler <[email protected]>
+ */
+
+#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 "caja-file-operations.h"
+
+#include "caja-debug-log.h"
+#include "caja-file-changes-queue.h"
+#include "caja-lib-self-check-functions.h"
+
+#include "caja-progress-info.h"
+
+#include <eel/eel-alert-dialog.h>
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-pango-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 "caja-file-changes-queue.h"
+#include "caja-file-private.h"
+#include "caja-desktop-icon-file.h"
+#include "caja-desktop-link-monitor.h"
+#include "caja-global-preferences.h"
+#include "caja-link.h"
+#include "caja-autorun.h"
+#include "caja-trash-monitor.h"
+#include "caja-file-utilities.h"
+#include "caja-file-conflict-dialog.h"
+
+static gboolean confirm_trash_auto_value;
+
+/* TODO: TESTING!!! */
+
+typedef struct {
+ GIOSchedulerJob *io_job;
+ GTimer *time;
+ GtkWindow *parent_window;
+ int screen_num;
+ int inhibit_cookie;
+ CajaProgressInfo *progress;
+ GCancellable *cancellable;
+ GHashTable *skip_files;
+ GHashTable *skip_readdir_error;
+ 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;
+ GdkPoint *icon_positions;
+ int n_icon_positions;
+ GHashTable *debuting_files;
+ CajaCopyCallback done_callback;
+ gpointer done_callback_data;
+} CopyMoveJob;
+
+typedef struct {
+ CommonJob common;
+ GList *files;
+ gboolean try_trash;
+ gboolean user_cancel;
+ CajaDeleteCallback 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;
+ CajaCreateCallback done_callback;
+ gpointer done_callback_data;
+} CreateJob;
+
+
+typedef struct {
+ CommonJob common;
+ GList *trash_dirs;
+ gboolean should_confirm;
+ CajaOpCallback done_callback;
+ gpointer done_callback_data;
+} EmptyTrashJob;
+
+typedef struct {
+ CommonJob common;
+ GFile *file;
+ gboolean interactive;
+ CajaOpCallback done_callback;
+ gpointer done_callback_data;
+} MarkTrustedJob;
+
+typedef struct {
+ CommonJob common;
+ GFile *file;
+ CajaOpCallback 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 15
+#define NSEC_PER_SEC 1000000000
+#define NSEC_PER_MSEC 1000000
+
+#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 SKIP _("_Skip")
+#define SKIP_ALL _("S_kip All")
+#define RETRY _("_Retry")
+#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 gboolean empty_trash_job (GIOSchedulerJob *io_job,
+ GCancellable *cancellable,
+ 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 = strchr (name + 1, '.');
+ 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;
+
+ file = va_arg (va, GFile *);
+
+ 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);
+
+ #if GLIB_CHECK_VERSION(2, 30, 0)
+ return g_format_size(size);
+ #else
+ return g_format_size_for_display(size);
+ #endif
+}
+
+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;
+ eel_add_weak_pointer (&common->parent_window);
+ }
+ common->progress = caja_progress_info_new ();
+ common->cancellable = caja_progress_info_get_cancellable (common->progress);
+ common->time = g_timer_new ();
+ common->inhibit_cookie = -1;
+ 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)
+{
+ caja_progress_info_finish (common->progress);
+
+ if (common->inhibit_cookie != -1) {
+ caja_uninhibit_power_manager (common->inhibit_cookie);
+ }
+
+ common->inhibit_cookie = -1;
+ g_timer_destroy (common->time);
+ eel_remove_weak_pointer (&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);
+ }
+ 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 void
+setup_autos (void)
+{
+ static gboolean setup_autos = FALSE;
+ if (!setup_autos) {
+ setup_autos = TRUE;
+ eel_preferences_add_auto_boolean (CAJA_PREFERENCES_CONFIRM_TRASH,
+ &confirm_trash_auto_value);
+ }
+}
+
+static gboolean
+can_delete_without_confirm (GFile *file)
+{
+ if (g_file_has_uri_scheme (file, "burn") ||
+ g_file_has_uri_scheme (file, "x-caja-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;
+} RunSimpleDialogData;
+
+static gboolean
+do_run_simple_dialog (gpointer _data)
+{
+ RunSimpleDialogData *data = _data;
+ const char *button_title;
+ GtkWidget *dialog;
+ int result;
+ int response_id;
+
+ /* Create the dialog. */
+ dialog = eel_alert_dialog_new (*data->parent_window,
+ 0,
+ data->message_type,
+ GTK_BUTTONS_NONE,
+ data->primary_text,
+ data->secondary_text);
+
+ 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_alert_dialog_set_details_label (EEL_ALERT_DIALOG (dialog),
+ data->details_text);
+ }
+
+ /* Run it. */
+ gtk_widget_show (dialog);
+ result = gtk_dialog_run (GTK_DIALOG (dialog));
+
+ while ((result == GTK_RESPONSE_NONE || result == GTK_RESPONSE_DELETE_EVENT) && data->ignore_close_box) {
+ gtk_widget_show (GTK_WIDGET (dialog));
+ result = gtk_dialog_run (GTK_DIALOG (dialog));
+ }
+
+ gtk_object_destroy (GTK_OBJECT (dialog));
+
+ data->result = result;
+
+ 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;
+
+ 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);
+
+ caja_progress_info_pause (job->progress);
+ g_io_scheduler_job_send_to_mainloop (job->io_job,
+ do_run_simple_dialog,
+ data,
+ NULL);
+ caja_progress_info_resume (job->progress);
+ res = data->result;
+
+ 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 void
+inhibit_power_manager (CommonJob *job, const char *message)
+{
+ job->inhibit_cookie = caja_inhibit_power_manager (message);
+}
+
+static void
+abort_job (CommonJob *job)
+{
+ g_cancellable_cancel (job->cancellable);
+
+}
+
+static gboolean
+job_aborted (CommonJob *job)
+{
+ return g_cancellable_is_cancelled (job->cancellable);
+}
+
+static gboolean
+confirm_delete_from_trash (CommonJob *job,
+ GList *files)
+{
+ char *prompt;
+ int file_count;
+ int response;
+
+ /* Just Say Yes if the preference says not to confirm. */
+ if (!confirm_trash_auto_value) {
+ 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,
+ GTK_STOCK_CANCEL, GTK_STOCK_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 (!confirm_trash_auto_value) {
+ 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,
+ GTK_STOCK_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 (!confirm_trash_auto_value) {
+ 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,
+ GTK_STOCK_CANCEL, GTK_STOCK_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;
+ guint64 now;
+ char *files_left_s;
+
+ now = g_thread_gettime ();
+ if (transfer_info->last_report_time != 0 &&
+ ABS ((gint64)(transfer_info->last_report_time - now)) < 100 * NSEC_PER_MSEC) {
+ return;
+ }
+ transfer_info->last_report_time = now;
+
+ 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 = 1;
+ }
+
+ files_left_s = f (ngettext ("%'d file left to delete",
+ "%'d files left to delete",
+ files_left),
+ files_left);
+
+ caja_progress_info_take_status (job->progress,
+ f (_("Deleting files")));
+
+ elapsed = g_timer_elapsed (job->time, NULL);
+ if (elapsed < SECONDS_NEEDED_FOR_RELIABLE_TRANSFER_RATE) {
+
+ caja_progress_info_set_details (job->progress, files_left_s);
+ } else {
+ char *details, *time_left_s;
+ transfer_rate = transfer_info->num_files / elapsed;
+ remaining_time = files_left / transfer_rate;
+
+ /* To translators: %T will expand to a time like "2 minutes".
+ * The singular/plural form will be used depending on the remaining time (i.e. the %T argument).
+ */
+ time_left_s = f (ngettext ("%T left",
+ "%T left",
+ seconds_count_format_time_units (remaining_time)),
+ remaining_time);
+
+ details = g_strconcat (files_left_s, "\xE2\x80\x94", time_left_s, NULL);
+ caja_progress_info_take_details (job->progress, details);
+
+ g_free (time_left_s);
+ }
+
+ g_free (files_left_s);
+
+ if (source_info->num_files != 0) {
+ caja_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,
+ GTK_STOCK_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,
+ GTK_STOCK_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_warning (job,
+ primary,
+ secondary,
+ details,
+ (source_info->num_files - transfer_info->num_files) > 1,
+ GTK_STOCK_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;
+ local_skipped_file = TRUE;
+ } else if (response == 2) { /* skip */
+ local_skipped_file = TRUE;
+ } else {
+ g_assert_not_reached ();
+ }
+
+ skip:
+ g_error_free (error);
+ } else {
+ caja_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)) {
+ caja_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_warning (job,
+ primary,
+ secondary,
+ details,
+ (source_info->num_files - transfer_info->num_files) > 1,
+ GTK_STOCK_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 ();
+ }
+ 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,
+ int files_trashed,
+ int total_files)
+{
+ int files_left;
+ char *s;
+
+ files_left = total_files - files_trashed;
+
+ caja_progress_info_take_status (job->progress,
+ f (_("Moving files to trash")));
+
+ s = f (ngettext ("%'d file left to trash",
+ "%'d files left to trash",
+ files_left),
+ files_left);
+ caja_progress_info_take_details (job->progress, s);
+
+ if (total_files != 0) {
+ caja_progress_info_set_progress (job->progress, files_trashed, total_files);
+ }
+}
+
+
+static void
+trash_files (CommonJob *job, GList *files, int *files_skipped)
+{
+ GList *l;
+ GFile *file;
+ GList *to_delete;
+ GError *error;
+ int total_files, files_trashed;
+ char *primary, *secondary, *details;
+ int response;
+
+ if (job_aborted (job)) {
+ return;
+ }
+
+ total_files = g_list_length (files);
+ files_trashed = 0;
+
+ report_trash_progress (job, files_trashed, total_files);
+
+ to_delete = NULL;
+ for (l = files;
+ l != NULL && !job_aborted (job);
+ l = l->next) {
+ file = l->data;
+
+ error = NULL;
+ if (!g_file_trash (file, job->cancellable, &error)) {
+ if (job->skip_all_error) {
+ (*files_skipped)++;
+ goto skip;
+ }
+
+ if (job->delete_all) {
+ to_delete = g_list_prepend (to_delete, file);
+ goto skip;
+ }
+
+ primary = f (_("Cannot move file to trash, do you want to delete immediately?"));
+ secondary = f (_("The file \"%B\" cannot be moved to the trash."), file);
+ details = NULL;
+ if (!IS_IO_ERROR (error, NOT_SUPPORTED)) {
+ details = error->message;
+ }
+
+ response = run_question (job,
+ primary,
+ secondary,
+ details,
+ (total_files - files_trashed) > 1,
+ GTK_STOCK_CANCEL, SKIP_ALL, SKIP, DELETE_ALL, GTK_STOCK_DELETE,
+ NULL);
+
+ if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
+ ((DeleteJob *) job)->user_cancel = TRUE;
+ abort_job (job);
+ } else if (response == 1) { /* skip all */
+ (*files_skipped)++;
+ job->skip_all_error = TRUE;
+ } else if (response == 2) { /* skip */
+ (*files_skipped)++;
+ } 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);
+ total_files--;
+ } else {
+ caja_file_changes_queue_file_removed (file);
+
+ files_trashed++;
+ report_trash_progress (job, files_trashed, total_files);
+ }
+ }
+
+ if (to_delete) {
+ to_delete = g_list_reverse (to_delete);
+ delete_files (job, to_delete, files_skipped);
+ g_list_free (to_delete);
+ }
+}
+
+static gboolean
+delete_job_done (gpointer user_data)
+{
+ DeleteJob *job;
+ GHashTable *debuting_uris;
+
+ job = user_data;
+
+ eel_g_object_list_free (job->files);
+
+ 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);
+
+ caja_file_changes_consume_changes (TRUE);
+
+ return FALSE;
+}
+
+static gboolean
+delete_job (GIOSchedulerJob *io_job,
+ GCancellable *cancellable,
+ gpointer user_data)
+{
+ DeleteJob *job = user_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;
+ common->io_job = io_job;
+
+ caja_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;
+ }
+
+ g_io_scheduler_job_send_to_mainloop_async (io_job,
+ delete_job_done,
+ job,
+ NULL);
+
+ return FALSE;
+}
+
+static void
+trash_or_delete_internal (GList *files,
+ GtkWindow *parent_window,
+ gboolean try_trash,
+ CajaDeleteCallback done_callback,
+ gpointer done_callback_data)
+{
+ DeleteJob *job;
+
+ setup_autos ();
+
+ /* TODO: special case desktop icon link files ... */
+
+ job = op_job_new (DeleteJob, parent_window);
+ job->files = eel_g_object_list_copy (files);
+ 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"));
+ }
+
+ g_io_scheduler_push_job (delete_job,
+ job,
+ NULL,
+ 0,
+ NULL);
+}
+
+void
+caja_file_operations_trash_or_delete (GList *files,
+ GtkWindow *parent_window,
+ CajaDeleteCallback done_callback,
+ gpointer done_callback_data)
+{
+ trash_or_delete_internal (files, parent_window,
+ TRUE,
+ done_callback, done_callback_data);
+}
+
+void
+caja_file_operations_delete (GList *files,
+ GtkWindow *parent_window,
+ CajaDeleteCallback 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;
+ GtkWindow *parent_window;
+ CajaUnmountCallback callback;
+ gpointer callback_data;
+} UnmountData;
+
+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);
+ }
+
+ eel_remove_weak_pointer (&data->parent_window);
+ g_object_unref (data->mount);
+ g_free (data);
+}
+
+static void
+do_unmount (UnmountData *data)
+{
+ GMountOperation *mount_op;
+
+ 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;
+ }
+ }
+
+ eel_g_object_list_free (dirs);
+
+ 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,
+ GTK_STOCK_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",
+ "Caja");
+
+ /* 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;
+}
+
+void
+caja_file_operations_unmount_mount_full (GtkWindow *parent_window,
+ GMount *mount,
+ gboolean eject,
+ gboolean check_trash,
+ CajaUnmountCallback 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;
+ eel_add_weak_pointer (&data->parent_window);
+
+ }
+ 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) {
+ 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 = (CajaOpCallback)do_unmount;
+ job->done_callback_data = data;
+ g_io_scheduler_push_job (empty_trash_job,
+ job,
+ NULL,
+ 0,
+ NULL);
+ return;
+ } else if (response == GTK_RESPONSE_CANCEL) {
+ if (callback) {
+ callback (callback_data);
+ }
+ eel_remove_weak_pointer (&data->parent_window);
+ g_object_unref (data->mount);
+ g_free (data);
+ return;
+ }
+ }
+
+ do_unmount (data);
+}
+
+void
+caja_file_operations_unmount_mount (GtkWindow *parent_window,
+ GMount *mount,
+ gboolean eject,
+ gboolean check_trash)
+{
+ caja_file_operations_unmount_mount_full (parent_window, mount, 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)
+{
+ CajaMountCallback mount_callback;
+ GObject *mount_callback_data_object;
+ GMountOperation *mount_op = user_data;
+ GError *error;
+ char *primary;
+ char *name;
+
+ error = NULL;
+ caja_allow_autorun_for_volume_finish (G_VOLUME (source_object));
+ if (!g_volume_mount_finish (G_VOLUME (source_object), res, &error)) {
+ if (error->code != G_IO_ERROR_FAILED_HANDLED) {
+ name = g_volume_get_name (G_VOLUME (source_object));
+ primary = g_strdup_printf (_("Unable to mount %s"), name);
+ g_free (name);
+ eel_show_error_dialog (primary,
+ error->message,
+ NULL);
+ g_free (primary);
+ }
+ g_error_free (error);
+ }
+
+ mount_callback = (CajaMountCallback)
+ 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),
+ 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
+caja_file_operations_mount_volume (GtkWindow *parent_window,
+ GVolume *volume,
+ gboolean allow_autorun)
+{
+ caja_file_operations_mount_volume_full (parent_window, volume,
+ allow_autorun, NULL, NULL);
+}
+
+void
+caja_file_operations_mount_volume_full (GtkWindow *parent_window,
+ GVolume *volume,
+ gboolean allow_autorun,
+ CajaMountCallback 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);
+
+ if (allow_autorun)
+ caja_allow_autorun_for_volume (volume);
+ g_volume_mount (volume, 0, mount_op, NULL, volume_mount_cb, mount_op);
+}
+
+static void
+report_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;
+ }
+
+ caja_progress_info_take_details (job->progress, s);
+ caja_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_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,
+ GTK_STOCK_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,
+ GTK_STOCK_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,
+ GTK_STOCK_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_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_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;
+ char *primary, *secondary, *details;
+ int response;
+ GFileType file_type;
+
+ 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,
+ 0,
+ 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,
+ GTK_STOCK_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_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."));
+
+ response = run_error (job,
+ primary,
+ secondary,
+ NULL,
+ FALSE,
+ GTK_STOCK_CANCEL,
+ NULL);
+
+ abort_job (job);
+ 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) {
+ 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 (_("There is %S available, but %S is required."), free_size, required_size);
+
+ response = run_warning (job,
+ primary,
+ secondary,
+ details,
+ FALSE,
+ GTK_STOCK_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."));
+
+ response = run_error (job,
+ primary,
+ secondary,
+ NULL,
+ FALSE,
+ GTK_STOCK_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;
+
+ job = (CommonJob *)copy_job;
+
+ is_move = copy_job->is_move;
+
+ now = g_thread_gettime ();
+
+ if (transfer_info->last_report_time != 0 &&
+ ABS ((gint64)(transfer_info->last_report_time - now)) < 100 * NSEC_PER_MSEC) {
+ return;
+ }
+ transfer_info->last_report_time = now;
+
+ 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 = 1;
+ }
+
+ 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) {
+ caja_progress_info_take_status (job->progress,
+ f (is_move ?
+ _("Moving \"%B\" to \"%B\""):
+ _("Copying \"%B\" to \"%B\""),
+ (GFile *)copy_job->files->data,
+ copy_job->destination));
+ } else {
+ caja_progress_info_take_status (job->progress,
+ f (_("Duplicating \"%B\""),
+ (GFile *)copy_job->files->data));
+ }
+ } else if (copy_job->files != NULL &&
+ copy_job->files->next == NULL) {
+ if (copy_job->destination != NULL) {
+ caja_progress_info_take_status (job->progress,
+ f (is_move?
+ ngettext ("Moving %'d file (in \"%B\") to \"%B\"",
+ "Moving %'d files (in \"%B\") to \"%B\"",
+ files_left)
+ :
+ ngettext ("Copying %'d file (in \"%B\") to \"%B\"",
+ "Copying %'d files (in \"%B\") to \"%B\"",
+ files_left),
+ files_left,
+ (GFile *)copy_job->files->data,
+ copy_job->destination));
+ } else {
+ caja_progress_info_take_status (job->progress,
+ f (ngettext ("Duplicating %'d file (in \"%B\")",
+ "Duplicating %'d files (in \"%B\")",
+ files_left),
+ files_left,
+ (GFile *)copy_job->files->data));
+ }
+ } else {
+ if (copy_job->destination != NULL) {
+ caja_progress_info_take_status (job->progress,
+ f (is_move?
+ ngettext ("Moving %'d file to \"%B\"",
+ "Moving %'d files to \"%B\"",
+ files_left)
+ :
+ ngettext ("Copying %'d file to \"%B\"",
+ "Copying %'d files to \"%B\"",
+ files_left),
+ files_left, copy_job->destination));
+ } else {
+ caja_progress_info_take_status (job->progress,
+ f (ngettext ("Duplicating %'d file",
+ "Duplicating %'d files",
+ files_left),
+ files_left));
+ }
+ }
+ }
+
+ total_size = MAX (source_info->num_bytes, transfer_info->num_bytes);
+
+ elapsed = g_timer_elapsed (job->time, NULL);
+ transfer_rate = 0;
+ if (elapsed > 0) {
+ transfer_rate = transfer_info->num_bytes / elapsed;
+ }
+
+ if (elapsed < SECONDS_NEEDED_FOR_RELIABLE_TRANSFER_RATE &&
+ transfer_rate > 0) {
+ char *s;
+ /* To translators: %S will expand to a size like "2 bytes" or "3 MB", so something like "4 kb of 4 MB" */
+ s = f (_("%S of %S"), transfer_info->num_bytes, total_size);
+ caja_progress_info_take_details (job->progress, s);
+ } else {
+ char *s;
+ remaining_time = (total_size - transfer_info->num_bytes) / transfer_rate;
+
+ /* 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 of 4 MB -- 2 hours left (4kb/sec)"
+ *
+ * The singular/plural form will be used depending on the remaining time (i.e. the %T argument).
+ */
+ s = f (ngettext ("%S of %S \xE2\x80\x94 %T left (%S/sec)",
+ "%S of %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);
+ caja_progress_info_take_details (job->progress, s);
+ }
+
+ caja_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
+str_replace (char *str,
+ const char *chars_to_replace,
+ char replacement)
+{
+ gboolean success;
+ int i;
+
+ success = FALSE;
+ for (i = 0; str[i] != '\0'; i++) {
+ if (strchr (chars_to_replace, str[i])) {
+ 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 = str_replace (filename, FAT_FORBIDDEN_CHARACTERS, '_');
+
+ 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 (GFile *src,
+ GFile *dest_dir,
+ const char *dest_fs_type,
+ gboolean same_fs)
+{
+ char *basename;
+ GFile *dest;
+ GFileInfo *info;
+ char *copyname;
+
+ dest = NULL;
+ if (!same_fs) {
+ info = g_file_query_info (src,
+ G_FILE_ATTRIBUTE_STANDARD_COPY_NAME,
+ 0, NULL, NULL);
+
+ if (info) {
+ 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 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 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;
+
+ 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;
+ if (!g_file_make_directory (*dest, job->cancellable, &error)) {
+ 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,
+ GTK_STOCK_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;
+ }
+ caja_file_changes_queue_file_added (*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 preceeding
+ * 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,
+ GTK_STOCK_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,
+ GTK_STOCK_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_warning (job,
+ primary,
+ secondary,
+ details,
+ (source_info->num_files - transfer_info->num_files) > 1,
+ GTK_STOCK_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;
+ 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,
+ GTK_STOCK_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,
+ GTK_STOCK_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;
+ }
+ caja_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;
+
+ f = g_file_dup (child);
+ while (f) {
+ if (g_file_equal (f, root)) {
+ g_object_unref (f);
+ return TRUE;
+ }
+ f = g_file_get_parent (f);
+ }
+ 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) &&
+ caja_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;
+} ConflictDialogData;
+
+static gboolean
+do_run_conflict_dialog (gpointer _data)
+{
+ ConflictDialogData *data = _data;
+ GtkWidget *dialog;
+ int response;
+
+ dialog = caja_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 =
+ caja_file_conflict_dialog_get_new_name (CAJA_FILE_CONFLICT_DIALOG (dialog));
+ } else if (response != GTK_RESPONSE_CANCEL ||
+ response != GTK_RESPONSE_NONE) {
+ data->resp_data->apply_to_all =
+ caja_file_conflict_dialog_get_apply_to_all
+ (CAJA_FILE_CONFLICT_DIALOG (dialog));
+ }
+
+ data->resp_data->id = response;
+
+ gtk_widget_destroy (dialog);
+
+ 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;
+
+ caja_progress_info_pause (job->progress);
+ g_io_scheduler_job_send_to_mainloop (job->io_job,
+ do_run_conflict_dialog,
+ data,
+ NULL);
+ caja_progress_info_resume (job->progress);
+
+ 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,
+ char *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 {
+ 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_warning (job,
+ primary,
+ secondary,
+ NULL,
+ (source_info->num_files - transfer_info->num_files) > 1,
+ GTK_STOCK_CANCEL, SKIP_ALL, SKIP,
+ NULL);
+
+ if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
+ abort_job (job);
+ } else if (response == 1) { /* skip all */
+ job->skip_all_error = TRUE;
+ } else if (response == 2) { /* skip */
+ /* do nothing */
+ } else {
+ g_assert_not_reached ();
+ }
+
+ goto out;
+ }
+
+ /* Don't allow 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_warning (job,
+ primary,
+ secondary,
+ NULL,
+ (source_info->num_files - transfer_info->num_files) > 1,
+ GTK_STOCK_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:
+
+ 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) {
+ transfer_info->num_files ++;
+ report_copy_progress (copy_job, source_info, transfer_info);
+
+ if (debuting_files) {
+ if (position) {
+ caja_file_changes_queue_schedule_position_set (dest, *position, job->screen_num);
+ } else {
+ caja_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) {
+ caja_file_changes_queue_file_moved (src, dest);
+ } else {
+ caja_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);
+ }
+
+ 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,
+ GTK_STOCK_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;
+ }
+ caja_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_warning (job,
+ primary,
+ secondary,
+ details,
+ (source_info->num_files - transfer_info->num_files) > 1,
+ GTK_STOCK_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:
+ *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 its 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 gboolean
+copy_job_done (gpointer user_data)
+{
+ CopyMoveJob *job;
+
+ job = user_data;
+ if (job->done_callback) {
+ job->done_callback (job->debuting_files, job->done_callback_data);
+ }
+
+ eel_g_object_list_free (job->files);
+ 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);
+
+ finalize_common ((CommonJob *)job);
+
+ caja_file_changes_consume_changes (TRUE);
+ return FALSE;
+}
+
+static gboolean
+copy_job (GIOSchedulerJob *io_job,
+ GCancellable *cancellable,
+ gpointer user_data)
+{
+ CopyMoveJob *job;
+ CommonJob *common;
+ SourceInfo source_info;
+ TransferInfo transfer_info;
+ char *dest_fs_id;
+ GFile *dest;
+
+ job = user_data;
+ common = &job->common;
+ common->io_job = io_job;
+
+ dest_fs_id = NULL;
+
+ caja_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);
+
+ g_io_scheduler_job_send_to_mainloop_async (io_job,
+ copy_job_done,
+ job,
+ NULL);
+
+ return FALSE;
+}
+
+void
+caja_file_operations_copy (GList *files,
+ GArray *relative_item_points,
+ GFile *target_dir,
+ GtkWindow *parent_window,
+ CajaCopyCallback done_callback,
+ gpointer done_callback_data)
+{
+ CopyMoveJob *job;
+
+ job = op_job_new (CopyMoveJob, parent_window);
+ job->desktop_location = caja_get_desktop_location ();
+ job->done_callback = done_callback;
+ job->done_callback_data = done_callback_data;
+ job->files = eel_g_object_list_copy (files);
+ job->destination = g_object_ref (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"));
+
+ g_io_scheduler_push_job (copy_job,
+ job,
+ NULL, /* destroy notify */
+ 0,
+ job->common.cancellable);
+}
+
+static void
+report_move_progress (CopyMoveJob *move_job, int total, int left)
+{
+ CommonJob *job;
+
+ job = (CommonJob *)move_job;
+
+ caja_progress_info_take_status (job->progress,
+ f (_("Preparing to Move to \"%B\""),
+ move_job->destination));
+
+ caja_progress_info_take_details (job->progress,
+ f (ngettext ("Preparing to move %'d file",
+ "Preparing to move %'d files",
+ left), left));
+
+ caja_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, renamed;
+ char *primary, *secondary, *details;
+ int response;
+ GFileCopyFlags flags;
+ MoveFileCopyFallback *fallback;
+ gboolean handled_invalid_filename;
+
+ overwrite = FALSE;
+ renamed = 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,
+ GTK_STOCK_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));
+ }
+
+ caja_file_changes_queue_file_moved (src, dest);
+
+ if (position) {
+ caja_file_changes_queue_schedule_position_set (dest, *position, job->screen_num);
+ } else {
+ caja_file_changes_queue_schedule_position_remove (dest);
+ }
+
+ return;
+ }
+
+ 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 (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 if (response->id == CONFLICT_RESPONSE_RENAME) {
+ g_object_unref (dest);
+ dest = get_target_file_for_display_name (dest_dir,
+ response->new_name);
+ renamed = TRUE;
+ 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) {
+ 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,
+ GTK_STOCK_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_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_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 gboolean
+move_job_done (gpointer user_data)
+{
+ CopyMoveJob *job;
+
+ job = user_data;
+ if (job->done_callback) {
+ job->done_callback (job->debuting_files, job->done_callback_data);
+ }
+
+ eel_g_object_list_free (job->files);
+ g_object_unref (job->destination);
+ g_hash_table_unref (job->debuting_files);
+ g_free (job->icon_positions);
+
+ finalize_common ((CommonJob *)job);
+
+ caja_file_changes_consume_changes (TRUE);
+ return FALSE;
+}
+
+static gboolean
+move_job (GIOSchedulerJob *io_job,
+ GCancellable *cancellable,
+ gpointer user_data)
+{
+ CopyMoveJob *job;
+ CommonJob *common;
+ GList *fallbacks;
+ SourceInfo source_info;
+ TransferInfo transfer_info;
+ char *dest_fs_id;
+ char *dest_fs_type;
+ GList *fallback_files;
+
+ job = user_data;
+ common = &job->common;
+ common->io_job = io_job;
+
+ dest_fs_id = NULL;
+ dest_fs_type = NULL;
+
+ fallbacks = NULL;
+
+ caja_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:
+ eel_g_list_free_deep (fallbacks);
+
+ g_free (dest_fs_id);
+ g_free (dest_fs_type);
+
+ g_io_scheduler_job_send_to_mainloop (io_job,
+ move_job_done,
+ job,
+ NULL);
+
+ return FALSE;
+}
+
+void
+caja_file_operations_move (GList *files,
+ GArray *relative_item_points,
+ GFile *target_dir,
+ GtkWindow *parent_window,
+ CajaCopyCallback done_callback,
+ gpointer done_callback_data)
+{
+ 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 = eel_g_object_list_copy (files);
+ job->destination = g_object_ref (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"));
+
+ g_io_scheduler_push_job (move_job,
+ job,
+ NULL, /* destroy notify */
+ 0,
+ job->common.cancellable);
+}
+
+static void
+report_link_progress (CopyMoveJob *link_job, int total, int left)
+{
+ CommonJob *job;
+
+ job = (CommonJob *)link_job;
+
+ caja_progress_info_take_status (job->progress,
+ f (_("Creating links in \"%B\""),
+ link_job->destination));
+
+ caja_progress_info_take_details (job->progress,
+ f (ngettext ("Making link to %'d file",
+ "Making links to %'d files",
+ left), left));
+
+ caja_progress_info_set_progress (job->progress, left, total);
+}
+
+static char *
+get_abs_path_for_symlink (GFile *file)
+{
+ GFile *root, *parent;
+ char *relative, *abs;
+
+ if (g_file_is_native (file)) {
+ 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);
+ if (path == NULL) {
+ not_local = TRUE;
+ } else if (g_file_make_symbolic_link (dest,
+ path,
+ common->cancellable,
+ &error)) {
+ g_free (path);
+ if (debuting_files) {
+ g_hash_table_replace (debuting_files, g_object_ref (dest), GINT_TO_POINTER (TRUE));
+ }
+
+ caja_file_changes_queue_file_added (dest);
+ if (position) {
+ caja_file_changes_queue_schedule_position_set (dest, *position, common->screen_num);
+ } else {
+ caja_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 (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,
+ GTK_STOCK_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 gboolean
+link_job_done (gpointer user_data)
+{
+ CopyMoveJob *job;
+
+ job = user_data;
+ if (job->done_callback) {
+ job->done_callback (job->debuting_files, job->done_callback_data);
+ }
+
+ eel_g_object_list_free (job->files);
+ g_object_unref (job->destination);
+ g_hash_table_unref (job->debuting_files);
+ g_free (job->icon_positions);
+
+ finalize_common ((CommonJob *)job);
+
+ caja_file_changes_consume_changes (TRUE);
+ return FALSE;
+}
+
+static gboolean
+link_job (GIOSchedulerJob *io_job,
+ GCancellable *cancellable,
+ gpointer user_data)
+{
+ CopyMoveJob *job;
+ CommonJob *common;
+ GList *copy_files;
+ GArray *copy_positions;
+ GFile *src;
+ GdkPoint *point;
+ char *dest_fs_type;
+ int total, left;
+ int i;
+ GList *l;
+
+ job = user_data;
+ common = &job->common;
+ common->io_job = io_job;
+
+ copy_files = NULL;
+ copy_positions = NULL;
+
+ dest_fs_type = NULL;
+
+ caja_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_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_link_progress (job, total, --left);
+ i++;
+
+ }
+
+ aborted:
+ g_free (dest_fs_type);
+
+ g_io_scheduler_job_send_to_mainloop (io_job,
+ link_job_done,
+ job,
+ NULL);
+
+ return FALSE;
+}
+
+void
+caja_file_operations_link (GList *files,
+ GArray *relative_item_points,
+ GFile *target_dir,
+ GtkWindow *parent_window,
+ CajaCopyCallback done_callback,
+ gpointer done_callback_data)
+{
+ CopyMoveJob *job;
+
+ job = op_job_new (CopyMoveJob, parent_window);
+ job->done_callback = done_callback;
+ job->done_callback_data = done_callback_data;
+ job->files = eel_g_object_list_copy (files);
+ job->destination = g_object_ref (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);
+
+ g_io_scheduler_push_job (link_job,
+ job,
+ NULL, /* destroy notify */
+ 0,
+ job->common.cancellable);
+}
+
+
+void
+caja_file_operations_duplicate (GList *files,
+ GArray *relative_item_points,
+ GtkWindow *parent_window,
+ CajaCopyCallback done_callback,
+ gpointer done_callback_data)
+{
+ CopyMoveJob *job;
+
+ job = op_job_new (CopyMoveJob, parent_window);
+ job->done_callback = done_callback;
+ job->done_callback_data = done_callback_data;
+ job->files = eel_g_object_list_copy (files);
+ job->destination = NULL;
+ 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);
+
+ g_io_scheduler_push_job (copy_job,
+ job,
+ NULL, /* destroy notify */
+ 0,
+ job->common.cancellable);
+}
+
+static gboolean
+set_permissions_job_done (gpointer user_data)
+{
+ SetPermissionsJob *job;
+
+ job = user_data;
+
+ g_object_unref (job->file);
+
+ if (job->done_callback) {
+ job->done_callback (job->done_callback_data);
+ }
+
+ finalize_common ((CommonJob *)job);
+ return FALSE;
+}
+
+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;
+
+ caja_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);
+ 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 gboolean
+set_permissions_job (GIOSchedulerJob *io_job,
+ GCancellable *cancellable,
+ gpointer user_data)
+{
+ SetPermissionsJob *job = user_data;
+ CommonJob *common;
+
+ common = (CommonJob *)job;
+ common->io_job = io_job;
+
+ caja_progress_info_set_status (common->progress,
+ _("Setting permissions"));
+
+ caja_progress_info_start (job->common.progress);
+
+ set_permissions_file (job, job->file, NULL);
+
+ g_io_scheduler_job_send_to_mainloop_async (io_job,
+ set_permissions_job_done,
+ job,
+ NULL);
+
+ return FALSE;
+}
+
+
+
+void
+caja_file_set_permissions_recursive (const char *directory,
+ guint32 file_permissions,
+ guint32 file_mask,
+ guint32 dir_permissions,
+ guint32 dir_mask,
+ CajaOpCallback callback,
+ gpointer callback_data)
+{
+ 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;
+
+ g_io_scheduler_push_job (set_permissions_job,
+ job,
+ NULL,
+ 0,
+ NULL);
+}
+
+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 {
+ CajaCopyCallback 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, data->real_data);
+ g_slice_free (MoveTrashCBData, data);
+}
+
+void
+caja_file_operations_copy_move (const GList *item_uris,
+ GArray *relative_item_points,
+ const char *target_dir,
+ GdkDragAction copy_action,
+ GtkWidget *parent_view,
+ CajaCopyCallback 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 = location_list_from_uri_list (item_uris); 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))) {
+ caja_file_operations_duplicate (locations,
+ relative_item_points,
+ parent_window,
+ done_callback, done_callback_data);
+ } else {
+ caja_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;
+ caja_file_operations_trash_or_delete (locations,
+ parent_window,
+ (CajaDeleteCallback) callback_for_move_to_trash,
+ cb_data);
+ } else {
+ caja_file_operations_move (locations,
+ relative_item_points,
+ dest,
+ parent_window,
+ done_callback, done_callback_data);
+ }
+ } else {
+ caja_file_operations_link (locations,
+ relative_item_points,
+ dest,
+ parent_window,
+ done_callback, done_callback_data);
+ }
+
+ eel_g_object_list_free (locations);
+ if (dest) {
+ g_object_unref (dest);
+ }
+}
+
+static gboolean
+create_job_done (gpointer user_data)
+{
+ CreateJob *job;
+
+ job = user_data;
+ if (job->done_callback) {
+ job->done_callback (job->created_file, 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);
+
+ caja_file_changes_consume_changes (TRUE);
+ return FALSE;
+}
+
+static gboolean
+create_job (GIOSchedulerJob *io_job,
+ GCancellable *cancellable,
+ gpointer user_data)
+{
+ CreateJob *job;
+ CommonJob *common;
+ int count;
+ GFile *dest;
+ char *filename, *filename2, *new_filename;
+ 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;
+
+ job = user_data;
+ common = &job->common;
+ common->io_job = io_job;
+
+ caja_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) {
+ filename = g_file_get_basename (job->src);
+ }
+ if (filename == NULL) {
+ /* localizers: the initial name of a new empty file */
+ filename = g_strdup (_("new file"));
+ 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);
+ } else {
+ if (job->src) {
+ res = g_file_copy (job->src,
+ dest,
+ G_FILE_COPY_NONE,
+ common->cancellable,
+ NULL, NULL,
+ &error);
+ } 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) {
+ 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);
+ }
+
+ /* 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);
+ caja_file_changes_queue_file_added (dest);
+ if (job->has_position) {
+ caja_file_changes_queue_schedule_position_set (dest, job->position, common->screen_num);
+ } else {
+ caja_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);
+
+ g_object_unref (dest);
+
+ if (count == 1) {
+ new_filename = g_strdup (filename);
+ } else if (job->make_dir) {
+ filename2 = g_strdup_printf ("%s %d", filename, count);
+
+ 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);
+ } else {
+ new_filename = get_duplicate_name (filename, count, max_length);
+ }
+
+ 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);
+ } else if (IS_IO_ERROR (error, EXISTS)) {
+ g_object_unref (dest);
+ dest = NULL;
+ if (job->make_dir) {
+ filename2 = g_strdup_printf ("%s %d", filename, ++count);
+ 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;
+ }
+ }
+ } else {
+ filename2 = get_duplicate_name (filename, count++, max_length);
+ }
+ 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_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,
+ GTK_STOCK_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);
+ g_io_scheduler_job_send_to_mainloop_async (io_job,
+ create_job_done,
+ job,
+ NULL);
+
+ return FALSE;
+}
+
+void
+caja_file_operations_new_folder (GtkWidget *parent_view,
+ GdkPoint *target_point,
+ const char *parent_dir,
+ CajaCreateCallback done_callback,
+ gpointer done_callback_data)
+{
+ 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->make_dir = TRUE;
+ if (target_point != NULL) {
+ job->position = *target_point;
+ job->has_position = TRUE;
+ }
+
+ g_io_scheduler_push_job (create_job,
+ job,
+ NULL, /* destroy notify */
+ 0,
+ job->common.cancellable);
+}
+
+void
+caja_file_operations_new_file_from_template (GtkWidget *parent_view,
+ GdkPoint *target_point,
+ const char *parent_dir,
+ const char *target_filename,
+ const char *template_uri,
+ CajaCreateCallback done_callback,
+ gpointer done_callback_data)
+{
+ 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);
+ }
+
+ g_io_scheduler_push_job (create_job,
+ job,
+ NULL, /* destroy notify */
+ 0,
+ job->common.cancellable);
+}
+
+void
+caja_file_operations_new_file (GtkWidget *parent_view,
+ GdkPoint *target_point,
+ const char *parent_dir,
+ const char *target_filename,
+ const char *initial_contents,
+ int length,
+ CajaCreateCallback done_callback,
+ gpointer done_callback_data)
+{
+ 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);
+
+ g_io_scheduler_push_job (create_job,
+ job,
+ NULL, /* destroy notify */
+ 0,
+ job->common.cancellable);
+}
+
+
+
+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 gboolean
+empty_trash_job_done (gpointer user_data)
+{
+ EmptyTrashJob *job;
+
+ job = user_data;
+
+ eel_g_object_list_free (job->trash_dirs);
+
+ if (job->done_callback) {
+ job->done_callback (job->done_callback_data);
+ }
+
+ finalize_common ((CommonJob *)job);
+ return FALSE;
+}
+
+static gboolean
+empty_trash_job (GIOSchedulerJob *io_job,
+ GCancellable *cancellable,
+ gpointer user_data)
+{
+ EmptyTrashJob *job = user_data;
+ CommonJob *common;
+ GList *l;
+ gboolean confirmed;
+
+ common = (CommonJob *)job;
+ common->io_job = io_job;
+
+ caja_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);
+ }
+ }
+
+ g_io_scheduler_job_send_to_mainloop_async (io_job,
+ empty_trash_job_done,
+ job,
+ NULL);
+
+ return FALSE;
+}
+
+void
+caja_file_operations_empty_trash (GtkWidget *parent_view)
+{
+ EmptyTrashJob *job;
+ GtkWindow *parent_window;
+
+ parent_window = NULL;
+ if (parent_view) {
+ parent_window = (GtkWindow *)gtk_widget_get_ancestor (parent_view, GTK_TYPE_WINDOW);
+ }
+
+ setup_autos ();
+
+ 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"));
+
+ g_io_scheduler_push_job (empty_trash_job,
+ job,
+ NULL,
+ 0,
+ NULL);
+}
+
+static gboolean
+mark_trusted_job_done (gpointer user_data)
+{
+ MarkTrustedJob *job = user_data;
+
+ g_object_unref (job->file);
+
+ if (job->done_callback) {
+ job->done_callback (job->done_callback_data);
+ }
+
+ finalize_common ((CommonJob *)job);
+ return FALSE;
+}
+
+#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,
+ GTK_STOCK_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,
+ GTK_STOCK_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,
+ GTK_STOCK_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,
+ GTK_STOCK_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 gboolean
+mark_trusted_job (GIOSchedulerJob *io_job,
+ GCancellable *cancellable,
+ gpointer user_data)
+{
+ MarkTrustedJob *job = user_data;
+ CommonJob *common;
+
+ common = (CommonJob *)job;
+ common->io_job = io_job;
+
+ caja_progress_info_start (job->common.progress);
+
+ mark_desktop_file_trusted (common,
+ cancellable,
+ job->file,
+ job->interactive);
+
+ g_io_scheduler_job_send_to_mainloop_async (io_job,
+ mark_trusted_job_done,
+ job,
+ NULL);
+
+ return FALSE;
+}
+
+void
+caja_file_mark_desktop_file_trusted (GFile *file,
+ GtkWindow *parent_window,
+ gboolean interactive,
+ CajaOpCallback done_callback,
+ gpointer done_callback_data)
+{
+ 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;
+
+ g_io_scheduler_push_job (mark_trusted_job,
+ job,
+ NULL,
+ 0,
+ NULL);
+}
+
+#if !defined (CAJA_OMIT_SELF_CHECK)
+
+void
+caja_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/libcaja-private/caja-file-operations.h b/libcaja-private/caja-file-operations.h
new file mode 100644
index 00000000..6d0e39d6
--- /dev/null
+++ b/libcaja-private/caja-file-operations.h
@@ -0,0 +1,142 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Ettore Perazzoli <[email protected]>,
+ Pavel Cisler <[email protected]>
+*/
+
+#ifndef CAJA_FILE_OPERATIONS_H
+#define CAJA_FILE_OPERATIONS_H
+
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+
+typedef void (* CajaCopyCallback) (GHashTable *debuting_uris,
+ gpointer callback_data);
+typedef void (* CajaCreateCallback) (GFile *new_file,
+ gpointer callback_data);
+typedef void (* CajaOpCallback) (gpointer callback_data);
+typedef void (* CajaDeleteCallback) (GHashTable *debuting_uris,
+ gboolean user_cancel,
+ gpointer callback_data);
+typedef void (* CajaMountCallback) (GVolume *volume,
+ GObject *callback_data_object);
+typedef void (* CajaUnmountCallback) (gpointer callback_data);
+
+/* FIXME: int copy_action should be an enum */
+
+void caja_file_operations_copy_move (const GList *item_uris,
+ GArray *relative_item_points,
+ const char *target_dir_uri,
+ GdkDragAction copy_action,
+ GtkWidget *parent_view,
+ CajaCopyCallback done_callback,
+ gpointer done_callback_data);
+void caja_file_operations_empty_trash (GtkWidget *parent_view);
+void caja_file_operations_new_folder (GtkWidget *parent_view,
+ GdkPoint *target_point,
+ const char *parent_dir_uri,
+ CajaCreateCallback done_callback,
+ gpointer done_callback_data);
+void caja_file_operations_new_file (GtkWidget *parent_view,
+ GdkPoint *target_point,
+ const char *parent_dir,
+ const char *target_filename,
+ const char *initial_contents,
+ int length,
+ CajaCreateCallback done_callback,
+ gpointer data);
+void caja_file_operations_new_file_from_template (GtkWidget *parent_view,
+ GdkPoint *target_point,
+ const char *parent_dir,
+ const char *target_filename,
+ const char *template_uri,
+ CajaCreateCallback done_callback,
+ gpointer data);
+
+void caja_file_operations_delete (GList *files,
+ GtkWindow *parent_window,
+ CajaDeleteCallback done_callback,
+ gpointer done_callback_data);
+void caja_file_operations_trash_or_delete (GList *files,
+ GtkWindow *parent_window,
+ CajaDeleteCallback done_callback,
+ gpointer done_callback_data);
+
+void caja_file_set_permissions_recursive (const char *directory,
+ guint32 file_permissions,
+ guint32 file_mask,
+ guint32 folder_permissions,
+ guint32 folder_mask,
+ CajaOpCallback callback,
+ gpointer callback_data);
+
+void caja_file_operations_unmount_mount (GtkWindow *parent_window,
+ GMount *mount,
+ gboolean eject,
+ gboolean check_trash);
+void caja_file_operations_unmount_mount_full (GtkWindow *parent_window,
+ GMount *mount,
+ gboolean eject,
+ gboolean check_trash,
+ CajaUnmountCallback callback,
+ gpointer callback_data);
+void caja_file_operations_mount_volume (GtkWindow *parent_window,
+ GVolume *volume,
+ gboolean allow_autorun);
+void caja_file_operations_mount_volume_full (GtkWindow *parent_window,
+ GVolume *volume,
+ gboolean allow_autorun,
+ CajaMountCallback mount_callback,
+ GObject *mount_callback_data_object);
+
+void caja_file_operations_copy (GList *files,
+ GArray *relative_item_points,
+ GFile *target_dir,
+ GtkWindow *parent_window,
+ CajaCopyCallback done_callback,
+ gpointer done_callback_data);
+void caja_file_operations_move (GList *files,
+ GArray *relative_item_points,
+ GFile *target_dir,
+ GtkWindow *parent_window,
+ CajaCopyCallback done_callback,
+ gpointer done_callback_data);
+void caja_file_operations_duplicate (GList *files,
+ GArray *relative_item_points,
+ GtkWindow *parent_window,
+ CajaCopyCallback done_callback,
+ gpointer done_callback_data);
+void caja_file_operations_link (GList *files,
+ GArray *relative_item_points,
+ GFile *target_dir,
+ GtkWindow *parent_window,
+ CajaCopyCallback done_callback,
+ gpointer done_callback_data);
+void caja_file_mark_desktop_file_trusted (GFile *file,
+ GtkWindow *parent_window,
+ gboolean interactive,
+ CajaOpCallback done_callback,
+ gpointer done_callback_data);
+
+
+#endif /* CAJA_FILE_OPERATIONS_H */
diff --git a/libcaja-private/caja-file-private.h b/libcaja-private/caja-file-private.h
new file mode 100644
index 00000000..937121bd
--- /dev/null
+++ b/libcaja-private/caja-file-private.h
@@ -0,0 +1,316 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Darin Adler <[email protected]>
+*/
+
+#ifndef CAJA_FILE_PRIVATE_H
+#define CAJA_FILE_PRIVATE_H
+
+#include <libcaja-private/caja-directory.h>
+#include <libcaja-private/caja-file.h>
+#include <libcaja-private/caja-monitor.h>
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-string.h>
+
+#define CAJA_FILE_LARGE_TOP_LEFT_TEXT_MAXIMUM_CHARACTERS_PER_LINE 80
+#define CAJA_FILE_LARGE_TOP_LEFT_TEXT_MAXIMUM_LINES 24
+#define CAJA_FILE_LARGE_TOP_LEFT_TEXT_MAXIMUM_BYTES 10000
+
+#define CAJA_FILE_TOP_LEFT_TEXT_MAXIMUM_CHARACTERS_PER_LINE 10
+#define CAJA_FILE_TOP_LEFT_TEXT_MAXIMUM_LINES 5
+#define CAJA_FILE_TOP_LEFT_TEXT_MAXIMUM_BYTES 1024
+
+#define CAJA_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;
+
+typedef struct
+{
+ char emblem_keywords[1];
+} CajaFileSortByEmblemCache;
+
+struct CajaFileDetails
+{
+ CajaDirectory *directory;
+
+ eel_ref_str name;
+
+ /* File info: */
+ GFileType type;
+
+ eel_ref_str display_name;
+ char *display_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 */
+ time_t ctime; /* 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;
+
+ GList *mime_list; /* If this is a directory, the list of MIME types in it. */
+ char *top_left_text;
+
+ /* Info you might get from a link (.desktop, .directory or caja link) */
+ char *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;
+
+ /* We use this to cache automatic emblems and emblem keywords
+ to speed up compare_by_emblems. */
+ CajaFileSortByEmblemCache *compare_by_emblem_cache;
+
+ /* CajaInfoProviders 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 CajaFile 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 CajaDirectory 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; /* CajaRequestStatus */
+ /* 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_top_left_text : 1;
+ eel_boolean_bit got_large_top_left_text : 1;
+ eel_boolean_bit top_left_text_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;
+
+ /* TRUE if the file is open in a spatial window */
+ eel_boolean_bit has_open_window : 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 is_backup : 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;
+
+ time_t trash_time; /* 0 is unknown */
+};
+
+typedef struct
+{
+ CajaFile *file;
+ GCancellable *cancellable;
+ CajaFileOperationCallback callback;
+ gpointer callback_data;
+ gboolean is_rename;
+
+ gpointer data;
+ GDestroyNotify free_data;
+} CajaFileOperation;
+
+
+CajaFile *caja_file_new_from_info (CajaDirectory *directory,
+ GFileInfo *info);
+void caja_file_emit_changed (CajaFile *file);
+void caja_file_mark_gone (CajaFile *file);
+char * caja_extract_top_left_text (const char *text,
+ gboolean large,
+ int length);
+void caja_file_set_directory (CajaFile *file,
+ CajaDirectory *directory);
+gboolean caja_file_get_date (CajaFile *file,
+ CajaDateType date_type,
+ time_t *date);
+void caja_file_updated_deep_count_in_progress (CajaFile *file);
+
+
+void caja_file_clear_info (CajaFile *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 caja_file_update_info (CajaFile *file,
+ GFileInfo *info);
+gboolean caja_file_update_name (CajaFile *file,
+ const char *name);
+gboolean caja_file_update_metadata_from_info (CajaFile *file,
+ GFileInfo *info);
+
+gboolean caja_file_update_name_and_directory (CajaFile *file,
+ const char *name,
+ CajaDirectory *directory);
+
+gboolean caja_file_set_display_name (CajaFile *file,
+ const char *display_name,
+ const char *edit_name,
+ gboolean custom);
+void caja_file_set_mount (CajaFile *file,
+ GMount *mount);
+
+/* Return true if the top lefts of files in this directory should be
+ * fetched, according to the preference settings.
+ */
+gboolean caja_file_should_get_top_left_text (CajaFile *file);
+
+/* Mark specified attributes for this file out of date without canceling current
+ * I/O or kicking off new I/O.
+ */
+void caja_file_invalidate_attributes_internal (CajaFile *file,
+ CajaFileAttributes file_attributes);
+CajaFileAttributes caja_file_get_all_attributes (void);
+gboolean caja_file_is_self_owned (CajaFile *file);
+void caja_file_invalidate_count_and_mime_list (CajaFile *file);
+gboolean caja_file_rename_in_progress (CajaFile *file);
+void caja_file_invalidate_extension_info_internal (CajaFile *file);
+void caja_file_info_providers_done (CajaFile *file);
+
+
+/* Thumbnailing: */
+void caja_file_set_is_thumbnailing (CajaFile *file,
+ gboolean is_thumbnailing);
+
+CajaFileOperation *caja_file_operation_new (CajaFile *file,
+ CajaFileOperationCallback callback,
+ gpointer callback_data);
+void caja_file_operation_free (CajaFileOperation *op);
+void caja_file_operation_complete (CajaFileOperation *op,
+ GFile *result_location,
+ GError *error);
+void caja_file_operation_cancel (CajaFileOperation *op);
+
+#endif
diff --git a/libcaja-private/caja-file-queue.c b/libcaja-private/caja-file-queue.c
new file mode 100644
index 00000000..bcd7d845
--- /dev/null
+++ b/libcaja-private/caja-file-queue.c
@@ -0,0 +1,133 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Maciej Stachowiak <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-file-queue.h"
+
+#include <glib.h>
+
+struct CajaFileQueue
+{
+ GList *head;
+ GList *tail;
+ GHashTable *item_to_link_map;
+};
+
+CajaFileQueue *
+caja_file_queue_new (void)
+{
+ CajaFileQueue *queue;
+
+ queue = g_new0 (CajaFileQueue, 1);
+ queue->item_to_link_map = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ return queue;
+}
+
+void
+caja_file_queue_destroy (CajaFileQueue *queue)
+{
+ g_hash_table_destroy (queue->item_to_link_map);
+ caja_file_list_free (queue->head);
+ g_free (queue);
+}
+
+void
+caja_file_queue_enqueue (CajaFileQueue *queue,
+ CajaFile *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;
+ }
+
+ caja_file_ref (file);
+ g_hash_table_insert (queue->item_to_link_map, file, queue->tail);
+}
+
+CajaFile *
+caja_file_queue_dequeue (CajaFileQueue *queue)
+{
+ CajaFile *file;
+
+ file = caja_file_queue_head (queue);
+ caja_file_queue_remove (queue, file);
+
+ return file;
+}
+
+
+void
+caja_file_queue_remove (CajaFileQueue *queue,
+ CajaFile *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);
+
+ caja_file_unref (file);
+}
+
+CajaFile *
+caja_file_queue_head (CajaFileQueue *queue)
+{
+ if (queue->head == NULL)
+ {
+ return NULL;
+ }
+
+ return CAJA_FILE (queue->head->data);
+}
+
+gboolean
+caja_file_queue_is_empty (CajaFileQueue *queue)
+{
+ return (queue->head == NULL);
+}
diff --git a/libcaja-private/caja-file-queue.h b/libcaja-private/caja-file-queue.h
new file mode 100644
index 00000000..31dd9e30
--- /dev/null
+++ b/libcaja-private/caja-file-queue.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Maciej Stachowiak <[email protected]>
+*/
+
+#ifndef CAJA_FILE_QUEUE_H
+#define CAJA_FILE_QUEUE_H
+
+#include <libcaja-private/caja-file.h>
+
+typedef struct CajaFileQueue CajaFileQueue;
+
+CajaFileQueue *caja_file_queue_new (void);
+void caja_file_queue_destroy (CajaFileQueue *queue);
+
+/* Add a file to the tail of the queue, unless it's already in the queue */
+void caja_file_queue_enqueue (CajaFileQueue *queue,
+ CajaFile *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.
+ */
+CajaFile * caja_file_queue_dequeue (CajaFileQueue *queue);
+
+/* Remove a file from an arbitrary point in the queue in constant time. */
+void caja_file_queue_remove (CajaFileQueue *queue,
+ CajaFile *file);
+
+/* Get the file at the head of the queue without removing or unrefing it. */
+CajaFile * caja_file_queue_head (CajaFileQueue *queue);
+
+gboolean caja_file_queue_is_empty (CajaFileQueue *queue);
+
+#endif /* CAJA_FILE_CHANGES_QUEUE_H */
diff --git a/libcaja-private/caja-file-utilities.c b/libcaja-private/caja-file-utilities.c
new file mode 100644
index 00000000..6c9a7de1
--- /dev/null
+++ b/libcaja-private/caja-file-utilities.c
@@ -0,0 +1,1449 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-file-utilities.c - implementation of file manipulation routines.
+
+ Copyright (C) 1999, 2000, 2001 Eazel, Inc.
+
+ The Mate 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 Mate 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 Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: John Sullivan <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-file-utilities.h"
+
+#include "caja-global-preferences.h"
+#include "caja-lib-self-check-functions.h"
+#include "caja-metadata.h"
+#include "caja-file.h"
+#include "caja-file-operations.h"
+#include "caja-search-directory.h"
+#include "caja-signaller.h"
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-stock-dialogs.h>
+#include <eel/eel-string.h>
+#include <eel/eel-debug.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gio/gio.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#define CAJA_USER_DIRECTORY_NAME ".config/caja"
+#define DEFAULT_CAJA_DIRECTORY_MODE (0755)
+
+#define DESKTOP_DIRECTORY_NAME "Desktop"
+#define LEGACY_DESKTOP_DIRECTORY_NAME ".mate-desktop"
+#define DEFAULT_DESKTOP_DIRECTORY_MODE (0755)
+
+static void update_xdg_dir_cache (void);
+static void schedule_user_dirs_changed (void);
+static void desktop_dir_changed (void);
+static GFile *caja_find_file_insensitive_next (GFile *parent, const gchar *name);
+
+char *
+caja_compute_title_for_location (GFile *location)
+{
+ CajaFile *file;
+ char *title;
+
+ /* TODO-gio: This doesn't really work all that great if the
+ info about the file isn't known atm... */
+
+ title = NULL;
+ if (location)
+ {
+ file = caja_file_get (location);
+ title = caja_file_get_description (file);
+ if (title == NULL)
+ {
+ title = caja_file_get_display_name (file);
+ }
+ caja_file_unref (file);
+ }
+
+ if (title == NULL)
+ {
+ title = g_strdup ("");
+ }
+
+ return title;
+}
+
+
+/**
+ * caja_get_user_directory:
+ *
+ * Get the path for the directory containing caja settings.
+ *
+ * Return value: the directory path.
+ **/
+char* caja_get_user_directory(void)
+{
+ /* FIXME bugzilla.gnome.org 41286:
+ * How should we handle the case where this mkdir fails?
+ * Note that caja_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 Caja was launched, and perhaps
+ * there is some bad side-effect of not handling that case.
+ * <<<
+ * Si alguien tiene tiempo, puede enviar este codigo a Nautilus.
+ * Obviamente, con los comentarios traducidos al Inglés.
+ */
+ char* user_directory = g_build_filename(g_get_home_dir(), ".config", "caja", NULL);
+ /* Se necesita que esta dirección sea una carpeta, con los permisos
+ * DEFAULT_CAJA_DIRECTORY_MODE. Pero si es un archivo, el programa intentará
+ * eliminar el archivo silenciosamente. */
+ if (g_file_test(user_directory, G_FILE_TEST_IS_DIR) == FALSE ||
+ g_access(user_directory, DEFAULT_CAJA_DIRECTORY_MODE) == -1)
+ {
+ /* Se puede obtener un enlace simbolico a una carpeta */
+ if (g_file_test(user_directory, G_FILE_TEST_IS_SYMLINK) == TRUE)
+ {
+ /* intentaremos saber si el enlace es una carpeta, y tiene los
+ * permisos adecuados */
+ char* link = g_file_read_link(user_directory, NULL);
+
+ if (link)
+ {
+ /* Si el enlace no es un directorio, o si falla al hacer chmod,
+ * se borra el enlace y se crea la carpeta */
+ if (g_file_test(link, G_FILE_TEST_IS_DIR) != TRUE ||
+ g_chmod(link, DEFAULT_CAJA_DIRECTORY_MODE) != 0)
+ {
+ /* podemos borrar el enlace y crear la carpeta */
+ g_unlink(user_directory);
+ g_mkdir(user_directory, DEFAULT_CAJA_DIRECTORY_MODE);
+ }
+
+ g_free(link);
+ }
+ }
+ else if (g_file_test(user_directory, G_FILE_TEST_IS_DIR) == TRUE)
+ {
+ g_chmod(user_directory, DEFAULT_CAJA_DIRECTORY_MODE);
+ }
+ else if (g_file_test(user_directory, G_FILE_TEST_EXISTS) == TRUE)
+ {
+ /* podemos borrar el enlace y crear la carpeta */
+ g_unlink(user_directory);
+ g_mkdir(user_directory, DEFAULT_CAJA_DIRECTORY_MODE);
+ }
+ else
+ {
+ /* Si no existe ningun archivo, se crea la carpeta */
+ g_mkdir_with_parents(user_directory, DEFAULT_CAJA_DIRECTORY_MODE);
+ }
+
+ /* Faltan permisos */
+ if (g_chmod(user_directory, DEFAULT_CAJA_DIRECTORY_MODE) != 0)
+ {
+ GtkWidget* dialog = gtk_message_dialog_new(
+ NULL,
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ "The path for the directory containing caja settings need read and write permissions: %s",
+ user_directory);
+
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+
+ exit(0);
+ }
+ }
+
+ return user_directory;
+}
+
+/**
+ * caja_get_accel_map_file:
+ *
+ * Get the path for the filename containing caja accelerator map.
+ * The filename need not exist.
+ *
+ * Return value: the filename path, or NULL if the home directory could not be found
+ **/
+char* caja_get_accel_map_file(void)
+{
+ const gchar* override;
+
+ override = g_getenv ("MATE22_USER_DIR");
+
+ if (override)
+ {
+ return g_build_filename(override, "accels", "caja", NULL);
+ }
+ else
+ {
+ return g_build_filename(g_get_home_dir(), ".config", "accels", "caja", NULL);
+ }
+}
+
+typedef struct {
+ char*type;
+ char*path;
+ CajaFile* file;
+} XdgDirEntry;
+
+
+static XdgDirEntry *
+parse_xdg_dirs (const char *config_file)
+{
+ GArray *array;
+ char *config_file_free = NULL;
+ XdgDirEntry dir;
+ char *data;
+ char **lines;
+ char *p, *d;
+ int i;
+ char *type_start, *type_end;
+ char *value, *unescaped;
+ gboolean relative;
+
+ array = g_array_new (TRUE, TRUE, sizeof (XdgDirEntry));
+
+ if (config_file == NULL)
+ {
+ config_file_free = g_build_filename (g_get_user_config_dir (),
+ "user-dirs.dirs", NULL);
+ config_file = (const char *)config_file_free;
+ }
+
+ if (g_file_get_contents (config_file, &data, NULL, NULL))
+ {
+ lines = g_strsplit (data, "\n", 0);
+ g_free (data);
+ for (i = 0; lines[i] != NULL; i++)
+ {
+ p = lines[i];
+ while (g_ascii_isspace (*p))
+ p++;
+
+ if (*p == '#')
+ continue;
+
+ value = strchr (p, '=');
+ if (value == NULL)
+ continue;
+ *value++ = 0;
+
+ g_strchug (g_strchomp (p));
+ if (!g_str_has_prefix (p, "XDG_"))
+ continue;
+ if (!g_str_has_suffix (p, "_DIR"))
+ continue;
+ type_start = p + 4;
+ type_end = p + strlen (p) - 4;
+
+ while (g_ascii_isspace (*value))
+ value++;
+
+ if (*value != '"')
+ continue;
+ value++;
+
+ relative = FALSE;
+ if (g_str_has_prefix (value, "$HOME"))
+ {
+ relative = TRUE;
+ value += 5;
+ while (*value == '/')
+ value++;
+ }
+ else if (*value != '/')
+ continue;
+
+ d = unescaped = g_malloc (strlen (value) + 1);
+ while (*value && *value != '"')
+ {
+ if ((*value == '\\') && (*(value + 1) != 0))
+ value++;
+ *d++ = *value++;
+ }
+ *d = 0;
+
+ *type_end = 0;
+ dir.type = g_strdup (type_start);
+ if (relative)
+ {
+ dir.path = g_build_filename (g_get_home_dir (), unescaped, NULL);
+ g_free (unescaped);
+ }
+ else
+ dir.path = unescaped;
+
+ g_array_append_val (array, dir);
+ }
+
+ g_strfreev (lines);
+ }
+
+ g_free (config_file_free);
+
+ return (XdgDirEntry *)g_array_free (array, FALSE);
+}
+
+static XdgDirEntry *cached_xdg_dirs = NULL;
+static GFileMonitor *cached_xdg_dirs_monitor = NULL;
+
+static void
+xdg_dir_changed (CajaFile *file,
+ XdgDirEntry *dir)
+{
+ GFile *location, *dir_location;
+ char *path;
+
+ location = caja_file_get_location (file);
+ dir_location = g_file_new_for_path (dir->path);
+ if (!g_file_equal (location, dir_location))
+ {
+ path = g_file_get_path (location);
+
+ if (path)
+ {
+ char *argv[5];
+ int i;
+
+ g_free (dir->path);
+ dir->path = path;
+
+ i = 0;
+ argv[i++] = "xdg-user-dirs-update";
+ argv[i++] = "--set";
+ argv[i++] = dir->type;
+ argv[i++] = dir->path;
+ argv[i++] = NULL;
+
+ /* We do this sync, to avoid possible race-conditions
+ if multiple dirs change at the same time. Its
+ blocking the main thread, but these updates should
+ be very rare and very fast. */
+ g_spawn_sync (NULL,
+ argv, NULL,
+ G_SPAWN_SEARCH_PATH |
+ G_SPAWN_STDOUT_TO_DEV_NULL |
+ G_SPAWN_STDERR_TO_DEV_NULL,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL);
+ g_reload_user_special_dirs_cache ();
+ schedule_user_dirs_changed ();
+ desktop_dir_changed ();
+ /* Icon might have changed */
+ caja_file_invalidate_attributes (file, CAJA_FILE_ATTRIBUTE_INFO);
+ }
+ }
+ g_object_unref (location);
+ g_object_unref (dir_location);
+}
+
+static void
+xdg_dir_cache_changed_cb (GFileMonitor *monitor,
+ GFile *file,
+ GFile *other_file,
+ GFileMonitorEvent event_type)
+{
+ if (event_type == G_FILE_MONITOR_EVENT_CHANGED ||
+ event_type == G_FILE_MONITOR_EVENT_CREATED)
+ {
+ update_xdg_dir_cache ();
+ }
+}
+
+static int user_dirs_changed_tag = 0;
+
+static gboolean
+emit_user_dirs_changed_idle (gpointer data)
+{
+ g_signal_emit_by_name (caja_signaller_get_current (),
+ "user_dirs_changed");
+ user_dirs_changed_tag = 0;
+ return FALSE;
+}
+
+static void
+schedule_user_dirs_changed (void)
+{
+ if (user_dirs_changed_tag == 0)
+ {
+ user_dirs_changed_tag = g_idle_add (emit_user_dirs_changed_idle, NULL);
+ }
+}
+
+static void
+unschedule_user_dirs_changed (void)
+{
+ if (user_dirs_changed_tag != 0)
+ {
+ g_source_remove (user_dirs_changed_tag);
+ user_dirs_changed_tag = 0;
+ }
+}
+
+static void
+free_xdg_dir_cache (void)
+{
+ int i;
+
+ if (cached_xdg_dirs != NULL)
+ {
+ for (i = 0; cached_xdg_dirs[i].type != NULL; i++)
+ {
+ if (cached_xdg_dirs[i].file != NULL)
+ {
+ caja_file_monitor_remove (cached_xdg_dirs[i].file,
+ &cached_xdg_dirs[i]);
+ g_signal_handlers_disconnect_by_func (cached_xdg_dirs[i].file,
+ G_CALLBACK (xdg_dir_changed),
+ &cached_xdg_dirs[i]);
+ caja_file_unref (cached_xdg_dirs[i].file);
+ }
+ g_free (cached_xdg_dirs[i].type);
+ g_free (cached_xdg_dirs[i].path);
+ }
+ g_free (cached_xdg_dirs);
+ }
+}
+
+static void
+destroy_xdg_dir_cache (void)
+{
+ free_xdg_dir_cache ();
+ unschedule_user_dirs_changed ();
+ desktop_dir_changed ();
+
+ if (cached_xdg_dirs_monitor != NULL)
+ {
+ g_object_unref (cached_xdg_dirs_monitor);
+ cached_xdg_dirs_monitor = NULL;
+ }
+}
+
+static void
+update_xdg_dir_cache (void)
+{
+ GFile *file;
+ char *config_file, *uri;
+ int i;
+
+ free_xdg_dir_cache ();
+ g_reload_user_special_dirs_cache ();
+ schedule_user_dirs_changed ();
+ desktop_dir_changed ();
+
+ cached_xdg_dirs = parse_xdg_dirs (NULL);
+
+ for (i = 0 ; cached_xdg_dirs[i].type != NULL; i++)
+ {
+ cached_xdg_dirs[i].file = NULL;
+ if (strcmp (cached_xdg_dirs[i].path, g_get_home_dir ()) != 0)
+ {
+ uri = g_filename_to_uri (cached_xdg_dirs[i].path, NULL, NULL);
+ cached_xdg_dirs[i].file = caja_file_get_by_uri (uri);
+ caja_file_monitor_add (cached_xdg_dirs[i].file,
+ &cached_xdg_dirs[i],
+ CAJA_FILE_ATTRIBUTE_INFO);
+ g_signal_connect (cached_xdg_dirs[i].file,
+ "changed", G_CALLBACK (xdg_dir_changed), &cached_xdg_dirs[i]);
+ g_free (uri);
+ }
+ }
+
+ if (cached_xdg_dirs_monitor == NULL)
+ {
+ config_file = g_build_filename (g_get_user_config_dir (),
+ "user-dirs.dirs", NULL);
+ file = g_file_new_for_path (config_file);
+ cached_xdg_dirs_monitor = g_file_monitor_file (file, 0, NULL, NULL);
+ g_signal_connect (cached_xdg_dirs_monitor, "changed",
+ G_CALLBACK (xdg_dir_cache_changed_cb), NULL);
+ g_object_unref (file);
+ g_free (config_file);
+
+ eel_debug_call_at_shutdown (destroy_xdg_dir_cache);
+ }
+}
+
+char *
+caja_get_xdg_dir (const char *type)
+{
+ int i;
+
+ if (cached_xdg_dirs == NULL)
+ {
+ update_xdg_dir_cache ();
+ }
+
+ for (i = 0 ; cached_xdg_dirs != NULL && cached_xdg_dirs[i].type != NULL; i++)
+ {
+ if (strcmp (cached_xdg_dirs[i].type, type) == 0)
+ {
+ return g_strdup (cached_xdg_dirs[i].path);
+ }
+ }
+ if (strcmp ("DESKTOP", type) == 0)
+ {
+ return g_build_filename (g_get_home_dir (), DESKTOP_DIRECTORY_NAME, NULL);
+ }
+ if (strcmp ("TEMPLATES", type) == 0)
+ {
+ return g_build_filename (g_get_home_dir (), "Templates", NULL);
+ }
+
+ return g_strdup (g_get_home_dir ());
+}
+
+static char *
+get_desktop_path (void)
+{
+ if (eel_preferences_get_boolean (CAJA_PREFERENCES_DESKTOP_IS_HOME_DIR))
+ {
+ return g_strdup (g_get_home_dir());
+ }
+ else
+ {
+ return caja_get_xdg_dir ("DESKTOP");
+ }
+}
+
+/**
+ * caja_get_desktop_directory:
+ *
+ * Get the path for the directory containing files on the desktop.
+ *
+ * Return value: the directory path.
+ **/
+char *
+caja_get_desktop_directory (void)
+{
+ char *desktop_directory;
+
+ desktop_directory = get_desktop_path ();
+
+ /* Don't try to create a home directory */
+ if (!eel_preferences_get_boolean (CAJA_PREFERENCES_DESKTOP_IS_HOME_DIR))
+ {
+ 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 caja_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 Caja was launched, and perhaps
+ * there is some bad side-effect of not handling that case.
+ */
+ }
+ }
+
+ return desktop_directory;
+}
+
+GFile *
+caja_get_desktop_location (void)
+{
+ char *desktop_directory;
+ GFile *res;
+
+ desktop_directory = get_desktop_path ();
+
+ res = g_file_new_for_path (desktop_directory);
+ g_free (desktop_directory);
+ return res;
+}
+
+
+/**
+ * caja_get_desktop_directory_uri:
+ *
+ * Get the uri for the directory containing files on the desktop.
+ *
+ * Return value: the directory path.
+ **/
+char *
+caja_get_desktop_directory_uri (void)
+{
+ char *desktop_path;
+ char *desktop_uri;
+
+ desktop_path = caja_get_desktop_directory ();
+ desktop_uri = g_filename_to_uri (desktop_path, NULL, NULL);
+ g_free (desktop_path);
+
+ return desktop_uri;
+}
+
+char *
+caja_get_desktop_directory_uri_no_create (void)
+{
+ char *desktop_path;
+ char *desktop_uri;
+
+ desktop_path = get_desktop_path ();
+ desktop_uri = g_filename_to_uri (desktop_path, NULL, NULL);
+ g_free (desktop_path);
+
+ return desktop_uri;
+}
+
+char *
+caja_get_home_directory_uri (void)
+{
+ return g_filename_to_uri (g_get_home_dir (), NULL, NULL);
+}
+
+
+gboolean
+caja_should_use_templates_directory (void)
+{
+ char *dir;
+ gboolean res;
+
+ dir = caja_get_xdg_dir ("TEMPLATES");
+ res = strcmp (dir, g_get_home_dir ()) != 0;
+ g_free (dir);
+ return res;
+}
+
+char *
+caja_get_templates_directory (void)
+{
+ return caja_get_xdg_dir ("TEMPLATES");
+}
+
+void
+caja_create_templates_directory (void)
+{
+ char *dir;
+
+ dir = caja_get_templates_directory ();
+ if (!g_file_test (dir, G_FILE_TEST_EXISTS))
+ {
+ g_mkdir (dir, DEFAULT_CAJA_DIRECTORY_MODE);
+ }
+ g_free (dir);
+}
+
+char *
+caja_get_templates_directory_uri (void)
+{
+ char *directory, *uri;
+
+ directory = caja_get_templates_directory ();
+ uri = g_filename_to_uri (directory, NULL, NULL);
+ g_free (directory);
+ return uri;
+}
+
+char *
+caja_get_searches_directory (void)
+{
+ char *user_dir;
+ char *searches_dir;
+
+ user_dir = caja_get_user_directory ();
+ searches_dir = g_build_filename (user_dir, "searches", NULL);
+ g_free (user_dir);
+
+ if (!g_file_test (searches_dir, G_FILE_TEST_EXISTS))
+ g_mkdir (searches_dir, DEFAULT_CAJA_DIRECTORY_MODE);
+
+ return searches_dir;
+}
+
+/* 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 gboolean desktop_dir_changed_callback_installed = FALSE;
+
+
+static void
+desktop_dir_changed (void)
+{
+ if (desktop_dir)
+ {
+ g_object_unref (desktop_dir);
+ }
+ if (desktop_dir_dir)
+ {
+ g_object_unref (desktop_dir_dir);
+ }
+ g_free (desktop_dir_filename);
+ desktop_dir = NULL;
+ desktop_dir_dir = NULL;
+ desktop_dir_filename = NULL;
+}
+
+static void
+desktop_dir_changed_callback (gpointer callback_data)
+{
+ desktop_dir_changed ();
+}
+
+static void
+update_desktop_dir (void)
+{
+ 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);
+ g_free (path);
+}
+
+gboolean
+caja_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
+caja_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
+caja_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
+caja_is_desktop_directory_file (GFile *dir,
+ const char *file)
+{
+
+ if (!desktop_dir_changed_callback_installed)
+ {
+ eel_preferences_add_callback (CAJA_PREFERENCES_DESKTOP_IS_HOME_DIR,
+ desktop_dir_changed_callback,
+ NULL);
+ desktop_dir_changed_callback_installed = TRUE;
+ }
+
+ if (desktop_dir == NULL)
+ {
+ update_desktop_dir ();
+ }
+
+ return (g_file_equal (dir, desktop_dir_dir) &&
+ strcmp (file, desktop_dir_filename) == 0);
+}
+
+gboolean
+caja_is_desktop_directory (GFile *dir)
+{
+
+ if (!desktop_dir_changed_callback_installed)
+ {
+ eel_preferences_add_callback (CAJA_PREFERENCES_DESKTOP_IS_HOME_DIR,
+ desktop_dir_changed_callback,
+ NULL);
+ desktop_dir_changed_callback_installed = TRUE;
+ }
+
+ if (desktop_dir == NULL)
+ {
+ update_desktop_dir ();
+ }
+
+ return g_file_equal (dir, desktop_dir);
+}
+
+
+/**
+ * caja_get_gmc_desktop_directory:
+ *
+ * Get the path for the directory containing the legacy gmc desktop.
+ *
+ * Return value: the directory path.
+ **/
+char *
+caja_get_gmc_desktop_directory (void)
+{
+ return g_build_filename (g_get_home_dir (), LEGACY_DESKTOP_DIRECTORY_NAME, NULL);
+}
+
+/**
+ * caja_get_pixmap_directory
+ *
+ * Get the path for the directory containing Caja pixmaps.
+ *
+ * Return value: the directory path.
+ **/
+char *
+caja_get_pixmap_directory (void)
+{
+ return g_strdup (DATADIR "/pixmaps/caja");
+}
+
+/* FIXME bugzilla.gnome.org 42423:
+ * Callers just use this and dereference so we core dump if
+ * pixmaps are missing. That is lame.
+ */
+char *
+caja_pixmap_file (const char *partial_path)
+{
+ char *path;
+
+ path = g_build_filename (DATADIR "/pixmaps/caja", partial_path, NULL);
+ if (g_file_test (path, G_FILE_TEST_EXISTS))
+ {
+ return path;
+ }
+ else
+ {
+ char *tmp;
+ tmp = caja_get_pixmap_directory ();
+ g_debug ("Failed to locate \"%s\" in Caja pixmap path \"%s\". Incomplete installation?", partial_path, tmp);
+ g_free (tmp);
+ }
+ g_free (path);
+ return NULL;
+}
+
+char *
+caja_get_data_file_path (const char *partial_path)
+{
+ char *path;
+ char *user_directory;
+
+ /* first try the user's home directory */
+ user_directory = caja_get_user_directory ();
+ path = g_build_filename (user_directory, partial_path, NULL);
+ g_free (user_directory);
+ if (g_file_test (path, G_FILE_TEST_EXISTS))
+ {
+ return path;
+ }
+ g_free (path);
+
+ /* next try the shared directory */
+ path = g_build_filename (CAJA_DATADIR, partial_path, NULL);
+ if (g_file_test (path, G_FILE_TEST_EXISTS))
+ {
+ return path;
+ }
+ g_free (path);
+
+ return NULL;
+}
+
+char *
+caja_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;
+}
+
+char *
+caja_unique_temporary_file_name (void)
+{
+ const char *prefix = "/tmp/caja-temp-file";
+ char *file_name;
+ int fd;
+
+ file_name = g_strdup_printf ("%sXXXXXX", prefix);
+
+ fd = g_mkstemp (file_name);
+ if (fd == -1)
+ {
+ g_free (file_name);
+ file_name = NULL;
+ }
+ else
+ {
+ close (fd);
+ }
+
+ return file_name;
+}
+
+GFile *
+caja_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;
+}
+
+/**
+ * caja_find_file_insensitive
+ *
+ * Attempt to find a file case-insentively. If the path can be found, the
+ * returned file maps directly to it. Otherwise, a file using the
+ * originally-cased path is returned. This function performs might perform
+ * I/O.
+ *
+ * Return value: a #GFile to a child specified by @name.
+ **/
+GFile *
+caja_find_file_insensitive (GFile *parent, const gchar *name)
+{
+ gchar **split_path;
+ gchar *component;
+ GFile *file, *next;
+ gint i;
+
+ split_path = g_strsplit (name, G_DIR_SEPARATOR_S, -1);
+
+ file = g_object_ref (parent);
+
+ for (i = 0; (component = split_path[i]) != NULL; i++)
+ {
+ if (!(next = caja_find_file_insensitive_next (file,
+ component)))
+ {
+ /* File does not exist */
+ g_object_unref (file);
+ file = NULL;
+ break;
+ }
+ g_object_unref (file);
+ file = next;
+ }
+ g_strfreev (split_path);
+
+ if (file)
+ {
+ return file;
+ }
+ return g_file_get_child (parent, name);
+}
+
+static GFile *
+caja_find_file_insensitive_next (GFile *parent, const gchar *name)
+{
+ GFileEnumerator *children;
+ GFileInfo *info;
+ gboolean use_utf8, found;
+ char *filename, *case_folded_name, *utf8_collation_key, *ascii_collation_key, *child_key;
+ GFile *file;
+ const char *child_name, *compare_key;
+
+ /* First check the given version */
+ file = g_file_get_child (parent, name);
+ if (g_file_query_exists (file, NULL))
+ {
+ return file;
+ }
+ g_object_unref (file);
+
+ ascii_collation_key = g_ascii_strdown (name, -1);
+ use_utf8 = g_utf8_validate (name, -1, NULL);
+ utf8_collation_key = NULL;
+ if (use_utf8)
+ {
+ case_folded_name = g_utf8_casefold (name, -1);
+ utf8_collation_key = g_utf8_collate_key (case_folded_name, -1);
+ g_free (case_folded_name);
+ }
+
+ /* Enumerate and compare insensitive */
+ filename = NULL;
+ children = g_file_enumerate_children (parent,
+ G_FILE_ATTRIBUTE_STANDARD_NAME,
+ 0, NULL, NULL);
+ if (children != NULL)
+ {
+ while ((info = g_file_enumerator_next_file (children, NULL, NULL)))
+ {
+ child_name = g_file_info_get_name (info);
+
+ if (use_utf8 && g_utf8_validate (child_name, -1, NULL))
+ {
+ gchar *case_folded;
+
+ case_folded = g_utf8_casefold (child_name, -1);
+ child_key = g_utf8_collate_key (case_folded, -1);
+ g_free (case_folded);
+ compare_key = utf8_collation_key;
+ }
+ else
+ {
+ child_key = g_ascii_strdown (child_name, -1);
+ compare_key = ascii_collation_key;
+ }
+
+ found = strcmp (child_key, compare_key) == 0;
+ g_free (child_key);
+ if (found)
+ {
+ filename = g_strdup (child_name);
+ break;
+ }
+ }
+ g_file_enumerator_close (children, NULL, NULL);
+ g_object_unref (children);
+ }
+
+ g_free (ascii_collation_key);
+ g_free (utf8_collation_key);
+
+ if (filename)
+ {
+ file = g_file_get_child (parent, filename);
+ g_free (filename);
+ return file;
+ }
+
+ return NULL;
+}
+
+gboolean
+caja_is_file_roller_installed (void)
+{
+ static int installed = - 1;
+
+ if (installed < 0)
+ {
+ if (g_find_program_in_path ("file-roller"))
+ {
+ installed = 1;
+ }
+ else
+ {
+ installed = 0;
+ }
+ }
+
+ return installed > 0 ? TRUE : FALSE;
+}
+
+#define GSM_NAME "org.mate.SessionManager"
+#define GSM_PATH "/org/mate/SessionManager"
+#define GSM_INTERFACE "org.mate.SessionManager"
+
+/* The following values come from
+ * http://www.gnome.org/~mccann/mate-session/docs/mate-session.html#org.mate.SessionManager.Inhibit
+ */
+#define INHIBIT_LOGOUT (1U)
+#define INHIBIT_SUSPEND (4U)
+
+static GDBusConnection *
+get_dbus_connection (void)
+{
+ static GDBusConnection *conn = NULL;
+
+ if (conn == NULL)
+ {
+ GError *error = NULL;
+
+ conn = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+
+ if (conn == NULL)
+ {
+ g_warning ("Could not connect to session bus: %s", error->message);
+ g_error_free (error);
+ }
+ }
+
+ return conn;
+}
+
+/**
+ * caja_inhibit_power_manager:
+ * @message: a human readable message for the reason why power management
+ * is being suspended.
+ *
+ * Inhibits the power manager from logging out or suspending the machine
+ * (e.g. whenever Caja is doing file operations).
+ *
+ * Returns: an integer cookie, which must be passed to
+ * caja_uninhibit_power_manager() to resume
+ * normal power management.
+ */
+int
+caja_inhibit_power_manager (const char *message)
+{
+ GDBusConnection *connection;
+ GVariant *result;
+ GError *error = NULL;
+ guint cookie = 0;
+
+ g_return_val_if_fail (message != NULL, -1);
+
+ connection = get_dbus_connection ();
+
+ if (connection == NULL)
+ {
+ return -1;
+ }
+
+ result = g_dbus_connection_call_sync (connection,
+ GSM_NAME,
+ GSM_PATH,
+ GSM_INTERFACE,
+ "Inhibit",
+ g_variant_new ("(susu)",
+ "Caja",
+ (guint) 0,
+ message,
+ (guint) (INHIBIT_LOGOUT | INHIBIT_SUSPEND)),
+ G_VARIANT_TYPE ("(u)"),
+ G_DBUS_CALL_FLAGS_NO_AUTO_START,
+ -1,
+ NULL,
+ &error);
+
+ if (error != NULL)
+ {
+ g_warning ("Could not inhibit power management: %s", error->message);
+ g_error_free (error);
+ return -1;
+ }
+
+ g_variant_get (result, "(u)", &cookie);
+ g_variant_unref (result);
+
+ return (int) cookie;
+}
+
+/**
+ * caja_uninhibit_power_manager:
+ * @cookie: the cookie value returned by caja_inhibit_power_manager()
+ *
+ * Uninhibits power management. This function must be called after the task
+ * which inhibited power management has finished, or the system will not
+ * return to normal power management.
+ */
+void
+caja_uninhibit_power_manager (gint cookie)
+{
+ GDBusConnection *connection;
+ GVariant *result;
+ GError *error = NULL;
+
+ g_return_if_fail (cookie > 0);
+
+ connection = get_dbus_connection ();
+
+ if (connection == NULL)
+ {
+ return;
+ }
+
+ result = g_dbus_connection_call_sync (connection,
+ GSM_NAME,
+ GSM_PATH,
+ GSM_INTERFACE,
+ "Uninhibit",
+ g_variant_new ("(u)", (guint) cookie),
+ NULL,
+ G_DBUS_CALL_FLAGS_NO_AUTO_START,
+ -1,
+ NULL,
+ &error);
+
+ if (result == NULL)
+ {
+ g_warning ("Could not uninhibit power management: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ g_variant_unref (result);
+}
+
+/* Returns TRUE if the file is in XDG_DATA_DIRS or
+ in "~/.mate2/". This is used for deciding
+ if a desktop file is "trusted" based on the path */
+gboolean
+caja_is_in_system_dir (GFile *file)
+{
+ const char * const * data_dirs;
+ char *path, *mate2;
+ 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;
+ }
+
+ }
+
+ if (!res)
+ {
+ /* Panel desktop files are here, trust them */
+ mate2 = g_build_filename(g_get_home_dir(), ".mate2", NULL);
+
+ if (g_str_has_prefix (path, mate2))
+ {
+ res = TRUE;
+ }
+
+ g_free(mate2);
+ }
+ g_free (path);
+
+ return res;
+}
+
+GHashTable *
+caja_trashed_files_get_original_directories (GList *files,
+ GList **unhandled_files)
+{
+ GHashTable *directories;
+ CajaFile *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 = CAJA_FILE (l->data);
+ original_file = caja_file_get_trash_original_file (file);
+
+ original_dir = NULL;
+ if (original_file != NULL)
+ {
+ original_dir = caja_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) caja_file_unref,
+ (GDestroyNotify) caja_file_list_unref);
+ }
+ caja_file_ref (original_dir);
+ m = g_hash_table_lookup (directories, original_dir);
+ if (m != NULL)
+ {
+ g_hash_table_steal (directories, original_dir);
+ caja_file_unref (original_dir);
+ }
+ m = g_list_append (m, caja_file_ref (file));
+ g_hash_table_insert (directories, original_dir, m);
+ }
+ else if (unhandled_files != NULL)
+ {
+ *unhandled_files = g_list_append (*unhandled_files, caja_file_ref (file));
+ }
+
+ if (original_file != NULL)
+ {
+ caja_file_unref (original_file);
+ }
+
+ if (original_dir != NULL)
+ {
+ caja_file_unref (original_dir);
+ }
+ }
+
+ return directories;
+}
+
+static GList *
+locations_from_file_list (GList *file_list)
+{
+ CajaFile *file;
+ GList *l, *ret;
+
+ ret = NULL;
+
+ for (l = file_list; l != NULL; l = l->next)
+ {
+ file = CAJA_FILE (l->data);
+ ret = g_list_prepend (ret, caja_file_get_location (file));
+ }
+
+ return g_list_reverse (ret);
+}
+
+void
+caja_restore_files_from_trash (GList *files,
+ GtkWindow *parent_window)
+{
+ CajaFile *file, *original_dir;
+ GHashTable *original_dirs_hash;
+ GList *original_dirs, *unhandled_files;
+ GFile *original_dir_location;
+ GList *locations, *l;
+ char *message, *file_name;
+
+ original_dirs_hash = caja_trashed_files_get_original_directories (files, &unhandled_files);
+
+ for (l = unhandled_files; l != NULL; l = l->next)
+ {
+ file = CAJA_FILE (l->data);
+ file_name = caja_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)
+ {
+ original_dirs = g_hash_table_get_keys (original_dirs_hash);
+ for (l = original_dirs; l != NULL; l = l->next)
+ {
+ original_dir = CAJA_FILE (l->data);
+ original_dir_location = caja_file_get_location (original_dir);
+
+ files = g_hash_table_lookup (original_dirs_hash, original_dir);
+ locations = locations_from_file_list (files);
+
+ caja_file_operations_move
+ (locations, NULL,
+ original_dir_location,
+ parent_window,
+ NULL, NULL);
+
+ eel_g_object_list_free (locations);
+ g_object_unref (original_dir_location);
+ }
+
+ g_list_free (original_dirs);
+ g_hash_table_destroy (original_dirs_hash);
+ }
+
+ caja_file_list_unref (unhandled_files);
+}
+
+#if !defined (CAJA_OMIT_SELF_CHECK)
+
+void
+caja_self_check_file_utilities (void)
+{
+}
+
+#endif /* !CAJA_OMIT_SELF_CHECK */
diff --git a/libcaja-private/caja-file-utilities.h b/libcaja-private/caja-file-utilities.h
new file mode 100644
index 00000000..2e2a5746
--- /dev/null
+++ b/libcaja-private/caja-file-utilities.h
@@ -0,0 +1,111 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-file-utilities.h - interface for file manipulation routines.
+
+ Copyright (C) 1999, 2000, 2001 Eazel, Inc.
+
+ The Mate 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 Mate 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 Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: John Sullivan <[email protected]>
+*/
+
+#ifndef CAJA_FILE_UTILITIES_H
+#define CAJA_FILE_UTILITIES_H
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+#define CAJA_SAVED_SEARCH_EXTENSION ".savedSearch"
+#define CAJA_SAVED_SEARCH_MIMETYPE "application/x-mate-saved-search"
+
+/* Recognizing special file names. */
+gboolean caja_file_name_matches_hidden_pattern (const char *name_or_relative_uri);
+gboolean caja_file_name_matches_backup_pattern (const char *name_or_relative_uri);
+
+/* These functions all return something something that needs to be
+ * freed with g_free, is not NULL, and is guaranteed to exist.
+ */
+char * caja_get_xdg_dir (const char *type);
+char * caja_get_user_directory (void);
+char * caja_get_desktop_directory (void);
+GFile * caja_get_desktop_location (void);
+char * caja_get_desktop_directory_uri (void);
+char * caja_get_home_directory_uri (void);
+gboolean caja_is_desktop_directory_file (GFile *dir,
+ const char *filename);
+gboolean caja_is_root_directory (GFile *dir);
+gboolean caja_is_desktop_directory (GFile *dir);
+gboolean caja_is_home_directory (GFile *dir);
+gboolean caja_is_home_directory_file (GFile *dir,
+ const char *filename);
+gboolean caja_is_in_system_dir (GFile *location);
+char * caja_get_gmc_desktop_directory (void);
+char * caja_get_pixmap_directory (void);
+
+gboolean caja_should_use_templates_directory (void);
+char * caja_get_templates_directory (void);
+char * caja_get_templates_directory_uri (void);
+void caja_create_templates_directory (void);
+
+char * caja_get_searches_directory (void);
+
+char * caja_compute_title_for_location (GFile *file);
+
+/* This function returns something that needs to be freed with g_free,
+ * is not NULL, but is not garaunteed to exist */
+char * caja_get_desktop_directory_uri_no_create (void);
+
+/* A version of mate's mate_pixmap_file that works for the caja prefix.
+ * Otherwise similar to mate_pixmap_file in that it checks to see if the file
+ * exists and returns NULL if it doesn't.
+ */
+/* FIXME bugzilla.gnome.org 42425:
+ * We might not need this once we get on mate-libs 2.0 which handles
+ * mate_pixmap_file better, using MATE_PATH.
+ */
+char * caja_pixmap_file (const char *partial_path);
+
+/* Locate a file in either the uers directory or the datadir. */
+char * caja_get_data_file_path (const char *partial_path);
+
+gboolean caja_is_file_roller_installed (void);
+
+/* Inhibit/Uninhibit MATE Power Manager */
+int caja_inhibit_power_manager (const char *message) G_GNUC_WARN_UNUSED_RESULT;
+void caja_uninhibit_power_manager (int cookie);
+
+/* 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 * caja_ensure_unique_file_name (const char *directory_uri,
+ const char *base_name,
+ const char *extension);
+char * caja_unique_temporary_file_name (void);
+
+GFile * caja_find_existing_uri_in_hierarchy (GFile *location);
+
+GFile *
+caja_find_file_insensitive (GFile *parent, const gchar *name);
+
+char * caja_get_accel_map_file (void);
+
+GHashTable * caja_trashed_files_get_original_directories (GList *files,
+ GList **unhandled_files);
+void caja_restore_files_from_trash (GList *files,
+ GtkWindow *parent_window);
+
+#endif /* CAJA_FILE_UTILITIES_H */
diff --git a/libcaja-private/caja-file.c b/libcaja-private/caja-file.c
new file mode 100644
index 00000000..4b229ca6
--- /dev/null
+++ b/libcaja-private/caja-file.c
@@ -0,0 +1,8411 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-file.c: Caja 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Darin Adler <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-file.h"
+
+#include "caja-directory-notify.h"
+#include "caja-directory-private.h"
+#include "caja-signaller.h"
+#include "caja-desktop-directory.h"
+#include "caja-desktop-directory-file.h"
+#include "caja-desktop-icon-file.h"
+#include "caja-file-attributes.h"
+#include "caja-file-private.h"
+#include "caja-file-operations.h"
+#include "caja-file-utilities.h"
+#include "caja-global-preferences.h"
+#include "caja-lib-self-check-functions.h"
+#include "caja-link.h"
+#include "caja-metadata.h"
+#include "caja-module.h"
+#include "caja-search-directory.h"
+#include "caja-search-directory-file.h"
+#include "caja-thumbnails.h"
+#include "caja-users-groups-cache.h"
+#include "caja-vfs-file.h"
+#include "caja-saved-search-file.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-gtk-macros.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 <libcaja-extension/caja-file-info.h>
+#include <libcaja-extension/caja-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
+
+/* Time in seconds to cache getpwuid results */
+#define GETPWUID_CACHE_TIME (5*60)
+
+#define ICON_NAME_THUMBNAIL_LOADING "image-loading"
+
+#undef CAJA_FILE_DEBUG_REF
+#undef CAJA_FILE_DEBUG_REF_VALGRIND
+
+#ifdef CAJA_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 Caja trash directories */
+#define TRASH_DIRECTORY_NAME ".Trash"
+
+#define METADATA_ID_IS_LIST_MASK (1<<31)
+
+typedef enum {
+ SHOW_HIDDEN = 1 << 0,
+ SHOW_BACKUP = 1 << 1
+} FilterOptions;
+
+typedef void (* ModifyListFunction) (GList **list, CajaFile *file);
+
+enum {
+ CHANGED,
+ UPDATED_DEEP_COUNT_IN_PROGRESS,
+ LAST_SIGNAL
+};
+
+static int date_format_pref;
+
+static guint signals[LAST_SIGNAL];
+
+static GHashTable *symbolic_links;
+
+static GQuark attribute_name_q,
+ attribute_size_q,
+ attribute_type_q,
+ attribute_modification_date_q,
+ attribute_date_modified_q,
+ attribute_accessed_date_q,
+ attribute_date_accessed_q,
+ attribute_emblems_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_date_changed_q,
+ attribute_trashed_on_q,
+ attribute_trash_orig_path_q,
+ attribute_date_permissions_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 caja_file_info_iface_init (CajaFileInfoIface *iface);
+static char * caja_file_get_owner_as_string (CajaFile *file,
+ gboolean include_real_name);
+static char * caja_file_get_type_as_string (CajaFile *file);
+static gboolean update_info_and_name (CajaFile *file,
+ GFileInfo *info);
+static const char * caja_file_peek_display_name (CajaFile *file);
+static const char * caja_file_peek_display_name_collation_key (CajaFile *file);
+static void file_mount_unmounted (GMount *mount, gpointer data);
+static void metadata_hash_free (GHashTable *hash);
+
+G_DEFINE_TYPE_WITH_CODE (CajaFile, caja_file, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (CAJA_TYPE_FILE_INFO,
+ caja_file_info_iface_init));
+
+static void
+caja_file_init (CajaFile *file)
+{
+ file->details = G_TYPE_INSTANCE_GET_PRIVATE ((file), CAJA_TYPE_FILE, CajaFileDetails);
+
+ caja_file_clear_info (file);
+ caja_file_invalidate_extension_info_internal (file);
+}
+
+static GObject*
+caja_file_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_params)
+{
+ GObject *object;
+ CajaFile *file;
+
+ object = (* G_OBJECT_CLASS (caja_file_parent_class)->constructor) (type,
+ n_construct_properties,
+ construct_params);
+
+ file = CAJA_FILE (object);
+
+ /* Set to default type after full construction */
+ if (CAJA_FILE_GET_CLASS (file)->default_file_type != G_FILE_TYPE_UNKNOWN) {
+ file->details->type = CAJA_FILE_GET_CLASS (file)->default_file_type;
+ }
+
+ return object;
+}
+
+gboolean
+caja_file_set_display_name (CajaFile *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;
+ caja_file_invalidate_attributes (file,
+ CAJA_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 (eel_strcmp (eel_ref_str_peek (file->details->display_name), display_name) != 0) {
+ changed = TRUE;
+
+ eel_ref_str_unref (file->details->display_name);
+
+ if (eel_strcmp (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 (eel_strcmp (eel_ref_str_peek (file->details->edit_name), edit_name) != 0) {
+ changed = TRUE;
+
+ eel_ref_str_unref (file->details->edit_name);
+ if (eel_strcmp (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
+caja_file_clear_display_name (CajaFile *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 (CajaFile *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 = caja_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
+caja_file_update_metadata_from_info (CajaFile *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
+caja_file_clear_info (CajaFile *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 = CAJA_FILE_GET_CLASS (file)->default_file_type;
+
+ if (!file->details->got_custom_display_name) {
+ caja_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_backup = 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->ctime = 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);
+}
+
+static CajaFile *
+caja_file_new_from_filename (CajaDirectory *directory,
+ const char *filename,
+ gboolean self_owned)
+{
+ CajaFile *file;
+
+ g_assert (CAJA_IS_DIRECTORY (directory));
+ g_assert (filename != NULL);
+ g_assert (filename[0] != '\0');
+
+ if (CAJA_IS_DESKTOP_DIRECTORY (directory)) {
+ if (self_owned) {
+ file = CAJA_FILE (g_object_new (CAJA_TYPE_DESKTOP_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 = CAJA_FILE (g_object_new (CAJA_TYPE_VFS_FILE, NULL));
+ }
+ } else if (CAJA_IS_SEARCH_DIRECTORY (directory)) {
+ if (self_owned) {
+ file = CAJA_FILE (g_object_new (CAJA_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 = CAJA_FILE (g_object_new (CAJA_TYPE_VFS_FILE, NULL));
+ }
+ } else if (g_str_has_suffix (filename, CAJA_SAVED_SEARCH_EXTENSION)) {
+ file = CAJA_FILE (g_object_new (CAJA_TYPE_SAVED_SEARCH_FILE, NULL));
+ } else {
+ file = CAJA_FILE (g_object_new (CAJA_TYPE_VFS_FILE, NULL));
+ }
+
+ file->details->directory = caja_directory_ref (directory);
+
+ file->details->name = eel_ref_str_new (filename);
+
+#ifdef CAJA_FILE_DEBUG_REF
+ DEBUG_REF_PRINTF("%10p ref'd", file);
+#endif
+
+ return file;
+}
+
+static void
+modify_link_hash_table (CajaFile *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) {
+ return;
+ }
+
+ /* Create the hash table first time through. */
+ if (symbolic_links == NULL) {
+ symbolic_links = eel_g_hash_table_new_free_at_exit
+ (g_str_hash, g_str_equal, "caja-file.c: symbolic_links");
+ }
+
+ target_uri = caja_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, CajaFile *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 (CajaFile *file)
+{
+ modify_link_hash_table (file, add_to_link_hash_table_list);
+}
+
+static void
+remove_from_link_hash_table_list (GList **list, CajaFile *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 (CajaFile *file)
+{
+ modify_link_hash_table (file, remove_from_link_hash_table_list);
+}
+
+CajaFile *
+caja_file_new_from_info (CajaDirectory *directory,
+ GFileInfo *info)
+{
+ CajaFile *file;
+ const char *mime_type;
+
+ g_return_val_if_fail (CAJA_IS_DIRECTORY (directory), NULL);
+ g_return_val_if_fail (info != NULL, NULL);
+
+ mime_type = g_file_info_get_content_type (info);
+ if (mime_type &&
+ strcmp (mime_type, CAJA_SAVED_SEARCH_MIMETYPE) == 0) {
+ g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY);
+ file = CAJA_FILE (g_object_new (CAJA_TYPE_SAVED_SEARCH_FILE, NULL));
+ } else {
+ file = CAJA_FILE (g_object_new (CAJA_TYPE_VFS_FILE, NULL));
+ }
+
+ file->details->directory = caja_directory_ref (directory);
+
+ update_info_and_name (file, info);
+
+#ifdef CAJA_FILE_DEBUG_REF
+ DEBUG_REF_PRINTF("%10p ref'd", file);
+#endif
+
+ return file;
+}
+
+static CajaFile *
+caja_file_get_internal (GFile *location, gboolean create)
+{
+ gboolean self_owned;
+ CajaDirectory *directory;
+ CajaFile *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 = caja_directory_get_internal (parent, create);
+
+ g_object_unref (parent);
+
+ /* Get the name for the file. */
+ if (self_owned && directory != NULL) {
+ basename = caja_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 = caja_directory_find_file_by_name (directory, basename);
+ }
+
+ /* Ref or create the file. */
+ if (file != NULL) {
+ caja_file_ref (file);
+ } else if (create) {
+ file = caja_file_new_from_filename (directory, basename, self_owned);
+ if (self_owned) {
+ g_assert (directory->details->as_file == NULL);
+ directory->details->as_file = file;
+ } else {
+ caja_directory_add_file (directory, file);
+ }
+ }
+
+ g_free (basename);
+ caja_directory_unref (directory);
+
+ return file;
+}
+
+CajaFile *
+caja_file_get (GFile *location)
+{
+ return caja_file_get_internal (location, TRUE);
+}
+
+CajaFile *
+caja_file_get_existing (GFile *location)
+{
+ return caja_file_get_internal (location, FALSE);
+}
+
+CajaFile *
+caja_file_get_existing_by_uri (const char *uri)
+{
+ GFile *location;
+ CajaFile *file;
+
+ location = g_file_new_for_uri (uri);
+ file = caja_file_get_internal (location, FALSE);
+ g_object_unref (location);
+
+ return file;
+}
+
+CajaFile *
+caja_file_get_by_uri (const char *uri)
+{
+ GFile *location;
+ CajaFile *file;
+
+ location = g_file_new_for_uri (uri);
+ file = caja_file_get_internal (location, TRUE);
+ g_object_unref (location);
+
+ return file;
+}
+
+gboolean
+caja_file_is_self_owned (CajaFile *file)
+{
+ return file->details->directory->details->as_file == file;
+}
+
+static void
+finalize (GObject *object)
+{
+ CajaDirectory *directory;
+ CajaFile *file;
+ char *uri;
+
+ file = CAJA_FILE (object);
+
+ g_assert (file->details->operations_in_progress == NULL);
+
+ if (file->details->is_thumbnailing) {
+ uri = caja_file_get_uri (file);
+ caja_thumbnail_remove_from_queue (uri);
+ g_free (uri);
+ }
+
+ caja_async_destroying_file (file);
+
+ remove_from_link_hash_table (file);
+
+ directory = file->details->directory;
+
+ if (caja_file_is_self_owned (file)) {
+ directory->details->as_file = NULL;
+ } else {
+ if (!file->details->is_gone) {
+ caja_directory_remove_file (directory, file);
+ }
+ }
+
+ if (file->details->get_info_error) {
+ g_error_free (file->details->get_info_error);
+ }
+
+ caja_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);
+ 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->top_left_text);
+ g_free (file->details->custom_icon);
+ g_free (file->details->activation_uri);
+ g_free (file->details->compare_by_emblem_cache);
+
+ if (file->details->thumbnail) {
+ g_object_unref (file->details->thumbnail);
+ }
+ if (file->details->mount) {
+ g_signal_handlers_disconnect_by_func (file->details->mount, file_mount_unmounted, file);
+ g_object_unref (file->details->mount);
+ }
+
+ eel_ref_str_unref (file->details->filesystem_id);
+
+ eel_g_list_free_deep (file->details->mime_list);
+
+ eel_g_list_free_deep (file->details->pending_extension_emblems);
+ eel_g_list_free_deep (file->details->extension_emblems);
+ eel_g_object_list_free (file->details->pending_info_providers);
+
+ 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 (caja_file_parent_class)->finalize (object);
+}
+
+CajaFile *
+caja_file_ref (CajaFile *file)
+{
+ if (file == NULL) {
+ return NULL;
+ }
+ g_return_val_if_fail (CAJA_IS_FILE (file), NULL);
+
+#ifdef CAJA_FILE_DEBUG_REF
+ DEBUG_REF_PRINTF("%10p ref'd", file);
+#endif
+
+ return g_object_ref (file);
+}
+
+void
+caja_file_unref (CajaFile *file)
+{
+ if (file == NULL) {
+ return;
+ }
+
+ g_return_if_fail (CAJA_IS_FILE (file));
+
+#ifdef CAJA_FILE_DEBUG_REF
+ DEBUG_REF_PRINTF("%10p unref'd", file);
+#endif
+
+ g_object_unref (file);
+}
+
+/**
+ * caja_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 *
+caja_file_get_parent_uri_for_display (CajaFile *file)
+{
+ GFile *parent;
+ char *result;
+
+ g_assert (CAJA_IS_FILE (file));
+
+ parent = caja_file_get_parent_location (file);
+ if (parent) {
+ result = g_file_get_parse_name (parent);
+ g_object_unref (parent);
+ } else {
+ result = g_strdup ("");
+ }
+
+ return result;
+}
+
+/**
+ * caja_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 caja_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 *
+caja_file_get_parent_uri (CajaFile *file)
+{
+ g_assert (CAJA_IS_FILE (file));
+
+ if (caja_file_is_self_owned (file)) {
+ /* Callers expect an empty string, not a NULL. */
+ return g_strdup ("");
+ }
+
+ return caja_directory_get_uri (file->details->directory);
+}
+
+GFile *
+caja_file_get_parent_location (CajaFile *file)
+{
+ g_assert (CAJA_IS_FILE (file));
+
+ if (caja_file_is_self_owned (file)) {
+ /* Callers expect an empty string, not a NULL. */
+ return NULL;
+ }
+
+ return caja_directory_get_location (file->details->directory);
+}
+
+CajaFile *
+caja_file_get_parent (CajaFile *file)
+{
+ g_assert (CAJA_IS_FILE (file));
+
+ if (caja_file_is_self_owned (file)) {
+ return NULL;
+ }
+
+ return caja_directory_get_corresponding_file (file->details->directory);
+}
+
+/**
+ * caja_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
+caja_file_can_read (CajaFile *file)
+{
+ g_return_val_if_fail (CAJA_IS_FILE (file), FALSE);
+
+ return file->details->can_read;
+}
+
+/**
+ * caja_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
+caja_file_can_write (CajaFile *file)
+{
+ g_return_val_if_fail (CAJA_IS_FILE (file), FALSE);
+
+ return file->details->can_write;
+}
+
+/**
+ * caja_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
+caja_file_can_execute (CajaFile *file)
+{
+ g_return_val_if_fail (CAJA_IS_FILE (file), FALSE);
+
+ return file->details->can_execute;
+}
+
+gboolean
+caja_file_can_mount (CajaFile *file)
+{
+ g_return_val_if_fail (CAJA_IS_FILE (file), FALSE);
+
+ return file->details->can_mount;
+}
+
+gboolean
+caja_file_can_unmount (CajaFile *file)
+{
+ g_return_val_if_fail (CAJA_IS_FILE (file), FALSE);
+
+ return file->details->can_unmount ||
+ (file->details->mount != NULL &&
+ g_mount_can_unmount (file->details->mount));
+}
+
+gboolean
+caja_file_can_eject (CajaFile *file)
+{
+ g_return_val_if_fail (CAJA_IS_FILE (file), FALSE);
+
+ return file->details->can_eject ||
+ (file->details->mount != NULL &&
+ g_mount_can_eject (file->details->mount));
+}
+
+gboolean
+caja_file_can_start (CajaFile *file)
+{
+ gboolean ret;
+ GDrive *drive;
+
+ g_return_val_if_fail (CAJA_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
+caja_file_can_start_degraded (CajaFile *file)
+{
+ gboolean ret;
+ GDrive *drive;
+
+ g_return_val_if_fail (CAJA_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
+caja_file_can_poll_for_media (CajaFile *file)
+{
+ gboolean ret;
+ GDrive *drive;
+
+ g_return_val_if_fail (CAJA_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
+caja_file_is_media_check_automatic (CajaFile *file)
+{
+ gboolean ret;
+ GDrive *drive;
+
+ g_return_val_if_fail (CAJA_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
+caja_file_can_stop (CajaFile *file)
+{
+ gboolean ret;
+ GDrive *drive;
+
+ g_return_val_if_fail (CAJA_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
+caja_file_get_start_stop_type (CajaFile *file)
+{
+ GDriveStartStopType ret;
+ GDrive *drive;
+
+ g_return_val_if_fail (CAJA_IS_FILE (file), FALSE);
+
+ ret = G_DRIVE_START_STOP_TYPE_UNKNOWN;
+
+ 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
+caja_file_mount (CajaFile *file,
+ GMountOperation *mount_op,
+ GCancellable *cancellable,
+ CajaFileOperationCallback callback,
+ gpointer callback_data)
+{
+ GError *error;
+
+ if (CAJA_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 {
+ CAJA_FILE_GET_CLASS (file)->mount (file, mount_op, cancellable, callback, callback_data);
+ }
+}
+
+typedef struct {
+ CajaFile *file;
+ CajaFileOperationCallback 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);
+ }
+ caja_file_unref (data->file);
+ g_free (data);
+}
+
+void
+caja_file_unmount (CajaFile *file,
+ GMountOperation *mount_op,
+ GCancellable *cancellable,
+ CajaFileOperationCallback callback,
+ gpointer callback_data)
+{
+ GError *error;
+ UnmountData *data;
+
+ if (file->details->can_unmount) {
+ if (CAJA_FILE_GET_CLASS (file)->unmount != NULL) {
+ CAJA_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 = caja_file_ref (file);
+ data->callback = callback;
+ data->callback_data = callback_data;
+ caja_file_operations_unmount_mount_full (NULL, file->details->mount, FALSE, TRUE, unmount_done, data);
+ } else if (callback) {
+ callback (file, NULL, NULL, callback_data);
+ }
+}
+
+void
+caja_file_eject (CajaFile *file,
+ GMountOperation *mount_op,
+ GCancellable *cancellable,
+ CajaFileOperationCallback callback,
+ gpointer callback_data)
+{
+ GError *error;
+ UnmountData *data;
+
+ if (file->details->can_eject) {
+ if (CAJA_FILE_GET_CLASS (file)->eject != NULL) {
+ CAJA_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 = caja_file_ref (file);
+ data->callback = callback;
+ data->callback_data = callback_data;
+ caja_file_operations_unmount_mount_full (NULL, file->details->mount, TRUE, TRUE, unmount_done, data);
+ } else if (callback) {
+ callback (file, NULL, NULL, callback_data);
+ }
+}
+
+void
+caja_file_start (CajaFile *file,
+ GMountOperation *start_op,
+ GCancellable *cancellable,
+ CajaFileOperationCallback callback,
+ gpointer callback_data)
+{
+ GError *error;
+
+ if ((file->details->can_start || file->details->can_start_degraded) &&
+ CAJA_FILE_GET_CLASS (file)->start != NULL) {
+ CAJA_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)
+{
+ CajaFileOperation *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;
+ }
+
+ caja_file_operation_complete (op, NULL, error);
+ if (error) {
+ g_error_free (error);
+ }
+}
+
+void
+caja_file_stop (CajaFile *file,
+ GMountOperation *mount_op,
+ GCancellable *cancellable,
+ CajaFileOperationCallback callback,
+ gpointer callback_data)
+{
+ GError *error;
+
+ if (CAJA_FILE_GET_CLASS (file)->stop != NULL) {
+ if (file->details->can_stop) {
+ CAJA_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)) {
+ CajaFileOperation *op;
+
+ op = caja_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
+caja_file_poll_for_media (CajaFile *file)
+{
+ if (file->details->can_poll_for_media) {
+ if (CAJA_FILE_GET_CLASS (file)->stop != NULL) {
+ CAJA_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);
+ }
+ }
+}
+
+/**
+ * caja_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
+caja_file_is_desktop_directory (CajaFile *file)
+{
+ GFile *dir;
+
+ dir = file->details->directory->details->location;
+
+ if (dir == NULL) {
+ return FALSE;
+ }
+
+ return caja_is_desktop_directory_file (dir, eel_ref_str_peek (file->details->name));
+}
+
+static gboolean
+is_desktop_file (CajaFile *file)
+{
+ return caja_file_is_mime_type (file, "application/x-desktop");
+}
+
+static gboolean
+can_rename_desktop_file (CajaFile *file)
+{
+ GFile *location;
+ gboolean res;
+
+ location = caja_file_get_location (file);
+ res = g_file_is_native (location);
+ g_object_unref (location);
+ return res;
+}
+
+/**
+ * caja_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
+caja_file_can_rename (CajaFile *file)
+{
+ gboolean can_rename;
+
+ g_return_val_if_fail (CAJA_IS_FILE (file), FALSE);
+
+ /* Nonexistent files can't be renamed. */
+ if (caja_file_is_gone (file)) {
+ return FALSE;
+ }
+
+ /* Self-owned files can't be renamed */
+ if (caja_file_is_self_owned (file)) {
+ return FALSE;
+ }
+
+ if ((is_desktop_file (file) && !can_rename_desktop_file (file)) ||
+ caja_file_is_home (file)) {
+ return FALSE;
+ }
+
+ can_rename = TRUE;
+
+ /* Certain types of links can't be renamed */
+ if (CAJA_IS_DESKTOP_ICON_FILE (file)) {
+ CajaDesktopLink *link;
+
+ link = caja_desktop_icon_file_get_link (CAJA_DESKTOP_ICON_FILE (file));
+
+ if (link != NULL) {
+ can_rename = caja_desktop_link_can_rename (link);
+ g_object_unref (link);
+ }
+ }
+
+ if (!can_rename) {
+ return FALSE;
+ }
+
+ return file->details->can_rename;
+}
+
+gboolean
+caja_file_can_delete (CajaFile *file)
+{
+ g_return_val_if_fail (CAJA_IS_FILE (file), FALSE);
+
+ /* Nonexistent files can't be deleted. */
+ if (caja_file_is_gone (file)) {
+ return FALSE;
+ }
+
+ /* Self-owned files can't be deleted */
+ if (caja_file_is_self_owned (file)) {
+ return FALSE;
+ }
+
+ return file->details->can_delete;
+}
+
+gboolean
+caja_file_can_trash (CajaFile *file)
+{
+ g_return_val_if_fail (CAJA_IS_FILE (file), FALSE);
+
+ /* Nonexistent files can't be deleted. */
+ if (caja_file_is_gone (file)) {
+ return FALSE;
+ }
+
+ /* Self-owned files can't be deleted */
+ if (caja_file_is_self_owned (file)) {
+ return FALSE;
+ }
+
+ return file->details->can_trash;
+}
+
+GFile *
+caja_file_get_location (CajaFile *file)
+{
+ GFile *dir;
+
+ g_return_val_if_fail (CAJA_IS_FILE (file), NULL);
+
+ dir = file->details->directory->details->location;
+
+ if (caja_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 *
+caja_file_get_uri (CajaFile *file)
+{
+ char *uri;
+ GFile *loc;
+
+ g_return_val_if_fail (CAJA_IS_FILE (file), NULL);
+
+ loc = caja_file_get_location (file);
+ uri = g_file_get_uri (loc);
+ g_object_unref (loc);
+
+ return uri;
+}
+
+char *
+caja_file_get_uri_scheme (CajaFile *file)
+{
+ GFile *loc;
+ char *scheme;
+
+ g_return_val_if_fail (CAJA_IS_FILE (file), NULL);
+
+ if (file->details->directory == NULL ||
+ file->details->directory->details->location == NULL) {
+ return NULL;
+ }
+
+ loc = caja_directory_get_location (file->details->directory);
+ scheme = g_file_get_uri_scheme (loc);
+ g_object_unref (loc);
+
+ return scheme;
+}
+
+CajaFileOperation *
+caja_file_operation_new (CajaFile *file,
+ CajaFileOperationCallback callback,
+ gpointer callback_data)
+{
+ CajaFileOperation *op;
+
+ op = g_new0 (CajaFileOperation, 1);
+ op->file = caja_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
+caja_file_operation_remove (CajaFileOperation *op)
+{
+ op->file->details->operations_in_progress = g_list_remove
+ (op->file->details->operations_in_progress, op);
+}
+
+void
+caja_file_operation_free (CajaFileOperation *op)
+{
+ caja_file_operation_remove (op);
+ caja_file_unref (op->file);
+ g_object_unref (op->cancellable);
+ if (op->free_data) {
+ op->free_data (op->data);
+ }
+ g_free (op);
+}
+
+void
+caja_file_operation_complete (CajaFileOperation *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".
+ */
+ caja_file_operation_remove (op);
+ caja_file_changed (op->file);
+ if (op->callback) {
+ (* op->callback) (op->file, result_file, error, op->callback_data);
+ }
+ caja_file_operation_free (op);
+}
+
+void
+caja_file_operation_cancel (CajaFileOperation *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)
+{
+ CajaFileOperation *op;
+ CajaDirectory *directory;
+ CajaFile *existing_file;
+ char *old_name;
+ 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, mark it gone.
+ */
+ existing_file = caja_directory_find_file_by_name (directory, new_name);
+ if (existing_file != NULL) {
+ caja_file_mark_gone (existing_file);
+ caja_file_changed (existing_file);
+ }
+
+ old_uri = caja_file_get_uri (op->file);
+ old_name = g_strdup (eel_ref_str_peek (op->file->details->name));
+
+ update_info_and_name (op->file, new_info);
+
+ g_free (old_name);
+
+ new_uri = caja_file_get_uri (op->file);
+ caja_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) {
+ caja_file_invalidate_attributes (op->file,
+ CAJA_FILE_ATTRIBUTE_INFO |
+ CAJA_FILE_ATTRIBUTE_LINK_INFO);
+ }
+
+ g_object_unref (new_info);
+ }
+ caja_file_operation_complete (op, NULL, error);
+ if (error) {
+ g_error_free (error);
+ }
+}
+
+static void
+rename_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer callback_data)
+{
+ CajaFileOperation *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) {
+ g_file_query_info_async (new_file,
+ CAJA_FILE_DEFAULT_ATTRIBUTES,
+ 0,
+ G_PRIORITY_DEFAULT,
+ op->cancellable,
+ rename_get_info_callback, op);
+ } else {
+ caja_file_operation_complete (op, NULL, error);
+ g_error_free (error);
+ }
+}
+
+static gboolean
+name_is (CajaFile *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
+caja_file_rename (CajaFile *file,
+ const char *new_name,
+ CajaFileOperationCallback callback,
+ gpointer callback_data)
+{
+ CajaFileOperation *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 (CAJA_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 (caja_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".
+ */
+ caja_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;
+ }
+
+ /* 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 (!CAJA_IS_DESKTOP_ICON_FILE (file) &&
+ !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 (caja_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".
+ */
+ caja_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 (CAJA_IS_DESKTOP_ICON_FILE (file)) {
+ CajaDesktopLink *link;
+
+ link = caja_desktop_icon_file_get_link (CAJA_DESKTOP_ICON_FILE (file));
+ old_name = caja_file_get_display_name (file);
+
+ if ((old_name != NULL && strcmp (new_name, old_name) == 0)) {
+ success = TRUE;
+ } else {
+ success = (link != NULL && caja_desktop_link_rename (link, new_name));
+ }
+
+ if (success) {
+ (* callback) (file, NULL, NULL, callback_data);
+ } else {
+ error = g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Unable to rename desktop icon"));
+ (* callback) (file, NULL, error, callback_data);
+ g_error_free (error);
+ }
+
+ g_free (old_name);
+ g_object_unref (link);
+ 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 = caja_file_get_uri (file);
+ old_name = caja_link_local_get_text (uri);
+ if (old_name != NULL && strcmp (new_name, old_name) == 0) {
+ success = TRUE;
+ name_changed = FALSE;
+ } else {
+ success = caja_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,
+ _("Unable to rename desktop file"));
+ (* 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) {
+ caja_file_invalidate_attributes (file,
+ CAJA_FILE_ATTRIBUTE_INFO |
+ CAJA_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 = caja_file_operation_new (file, callback, callback_data);
+ op->is_rename = TRUE;
+
+ /* Do the renaming. */
+
+ location = caja_file_get_location (file);
+ 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
+caja_file_rename_in_progress (CajaFile *file)
+{
+ GList *node;
+ CajaFileOperation *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
+caja_file_cancel (CajaFile *file,
+ CajaFileOperationCallback callback,
+ gpointer callback_data)
+{
+ GList *node, *next;
+ CajaFileOperation *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) {
+ caja_file_operation_cancel (op);
+ }
+ }
+}
+
+gboolean
+caja_file_matches_uri (CajaFile *file, const char *match_uri)
+{
+ GFile *match_file, *location;
+ gboolean result;
+
+ g_return_val_if_fail (CAJA_IS_FILE (file), FALSE);
+ g_return_val_if_fail (match_uri != NULL, FALSE);
+
+ location = caja_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
+caja_file_compare_location (CajaFile *file_1,
+ CajaFile *file_2)
+{
+ GFile *loc_a, *loc_b;
+ gboolean res;
+
+ loc_a = caja_file_get_location (file_1);
+ loc_b = caja_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
+caja_file_is_local (CajaFile *file)
+{
+ g_return_val_if_fail (CAJA_IS_FILE (file), FALSE);
+
+ return caja_directory_is_local (file->details->directory);
+}
+
+static void
+update_link (CajaFile *link_file, CajaFile *target_file)
+{
+ g_assert (CAJA_IS_FILE (link_file));
+ g_assert (CAJA_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 (CajaFile *target_file)
+{
+ char *uri;
+ GList **link_files;
+
+ if (symbolic_links == NULL) {
+ link_files = NULL;
+ } else {
+ uri = caja_file_get_uri (target_file);
+ link_files = g_hash_table_lookup (symbolic_links, uri);
+ g_free (uri);
+ }
+ if (link_files) {
+ return caja_file_list_copy (*link_files);
+ }
+ return NULL;
+}
+
+static void
+update_links_if_target (CajaFile *target_file)
+{
+ GList *link_files, *p;
+
+ link_files = get_link_files (target_file);
+ for (p = link_files; p != NULL; p = p->next) {
+ update_link (CAJA_FILE (p->data), target_file);
+ }
+ caja_file_list_free (link_files);
+}
+
+static gboolean
+update_info_internal (CajaFile *file,
+ GFileInfo *info,
+ gboolean update_name)
+{
+ GList *node;
+ gboolean changed;
+ gboolean is_symlink, is_hidden, is_backup, 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, ctime;
+ 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) {
+ caja_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 |= caja_file_set_display_name (file,
+ g_file_info_get_display_name (info),
+ g_file_info_get_edit_name (info),
+ FALSE);
+
+ file_type = g_file_info_get_file_type (info);
+ if (file->details->type != file_type) {
+ changed = TRUE;
+ }
+ file->details->type = file_type;
+
+ if (!file->details->got_custom_activation_uri) {
+ 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);
+ if (file->details->is_hidden != is_hidden) {
+ changed = TRUE;
+ }
+ file->details->is_hidden = is_hidden;
+
+ is_backup = g_file_info_get_is_backup (info);
+ if (file->details->is_backup != is_backup) {
+ changed = TRUE;
+ }
+ file->details->is_backup = is_backup;
+
+ 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_trash = TRUE;
+ can_rename = TRUE;
+ 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 (eel_strcmp (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 (eel_strcmp (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 (eel_strcmp (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);
+ ctime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_CHANGED);
+ mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
+ if (file->details->atime != atime ||
+ file->details->mtime != mtime ||
+ file->details->ctime != ctime) {
+ if (file->details->thumbnail == NULL) {
+ file->details->thumbnail_is_up_to_date = FALSE;
+ }
+
+ changed = TRUE;
+ }
+ file->details->atime = atime;
+ file->details->ctime = ctime;
+ 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 (eel_strcmp (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 (eel_strcmp (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 (eel_strcmp (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 (eel_strcmp (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 (eel_strcmp (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 (eel_strcmp (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 (eel_strcmp (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 |=
+ caja_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 = caja_directory_begin_file_name_change
+ (file->details->directory, file);
+
+ eel_ref_str_unref (file->details->name);
+ if (eel_strcmp (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,
+ * caja_file_set_display_name() did
+ * not unset the display name.
+ */
+ caja_file_clear_display_name (file);
+ }
+
+ caja_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 (CajaFile *file,
+ GFileInfo *info)
+{
+ return update_info_internal (file, info, TRUE);
+}
+
+gboolean
+caja_file_update_info (CajaFile *file,
+ GFileInfo *info)
+{
+ return update_info_internal (file, info, FALSE);
+}
+
+static gboolean
+update_name_internal (CajaFile *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 = caja_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) {
+ caja_file_clear_display_name (file);
+ }
+
+ if (in_directory) {
+ caja_directory_end_file_name_change
+ (file->details->directory, file, node);
+ }
+
+ return TRUE;
+}
+
+gboolean
+caja_file_update_name (CajaFile *file, const char *name)
+{
+ gboolean ret;
+
+ ret = update_name_internal (file, name, TRUE);
+
+ if (ret) {
+ update_links_if_target (file);
+ }
+
+ return ret;
+}
+
+gboolean
+caja_file_update_name_and_directory (CajaFile *file,
+ const char *name,
+ CajaDirectory *new_directory)
+{
+ CajaDirectory *old_directory;
+ FileMonitors *monitors;
+
+ g_return_val_if_fail (CAJA_IS_FILE (file), FALSE);
+ g_return_val_if_fail (CAJA_IS_DIRECTORY (file->details->directory), FALSE);
+ g_return_val_if_fail (!file->details->is_gone, FALSE);
+ g_return_val_if_fail (!caja_file_is_self_owned (file), FALSE);
+ g_return_val_if_fail (CAJA_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;
+ }
+ }
+
+ caja_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 = caja_directory_remove_file_monitors (old_directory, file);
+ caja_directory_remove_file (old_directory, file);
+
+ file->details->directory = caja_directory_ref (new_directory);
+ caja_directory_unref (old_directory);
+
+ if (name) {
+ update_name_internal (file, name, FALSE);
+ }
+
+ caja_directory_add_file (new_directory, file);
+ caja_directory_add_file_monitors (new_directory, file, monitors);
+
+ add_to_link_hash_table (file);
+
+ update_links_if_target (file);
+
+ caja_file_unref (file);
+
+ return TRUE;
+}
+
+void
+caja_file_set_directory (CajaFile *file,
+ CajaDirectory *new_directory)
+{
+ caja_file_update_name_and_directory (file, NULL, new_directory);
+}
+
+static Knowledge
+get_item_count (CajaFile *file,
+ guint *count)
+{
+ gboolean known, unreadable;
+
+ known = caja_file_get_directory_item_count
+ (file, count, &unreadable);
+ if (!known) {
+ return UNKNOWN;
+ }
+ if (unreadable) {
+ return UNKNOWABLE;
+ }
+ return KNOWN;
+}
+
+static Knowledge
+get_size (CajaFile *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 mate-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 (CajaFile *file,
+ time_t *time_out,
+ CajaDateType 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;
+ }
+
+ time = 0;
+ switch (type) {
+ case CAJA_DATE_TYPE_MODIFIED:
+ time = file->details->mtime;
+ break;
+ case CAJA_DATE_TYPE_ACCESSED:
+ time = file->details->atime;
+ break;
+ case CAJA_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
+ * mate-vfs is concerned, so "unknowable".
+ */
+ if (time == 0) {
+ return UNKNOWABLE;
+ }
+ return KNOWN;
+}
+
+static int
+compare_directories_by_count (CajaFile *file_1, CajaFile *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 (CajaFile *file_1, CajaFile *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, size_2;
+
+ 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 (CajaFile *file_1, CajaFile *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 = caja_file_is_directory (file_1);
+ is_directory_2 = caja_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 (CajaFile *file_1, CajaFile *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 = caja_file_peek_display_name (file_1);
+ name_2 = caja_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 = caja_file_peek_display_name_collation_key (file_1);
+ key_2 = caja_file_peek_display_name_collation_key (file_2);
+ compare = strcmp (key_1, key_2);
+ }
+
+ return compare;
+}
+
+static int
+compare_by_directory_name (CajaFile *file_1, CajaFile *file_2)
+{
+ char *directory_1, *directory_2;
+ int compare;
+
+ if (file_1->details->directory == file_2->details->directory) {
+ return 0;
+ }
+
+ directory_1 = caja_file_get_parent_uri_for_display (file_1);
+ directory_2 = caja_file_get_parent_uri_for_display (file_2);
+
+ compare = g_utf8_collate (directory_1, directory_2);
+
+ g_free (directory_1);
+ g_free (directory_2);
+
+ return compare;
+}
+
+static gboolean
+file_has_note (CajaFile *file)
+{
+ char *note;
+ gboolean res;
+
+ note = caja_file_get_metadata (file, CAJA_METADATA_KEY_ANNOTATION, NULL);
+ res = note != NULL && note[0] != 0;
+ g_free (note);
+
+ return res;
+}
+
+static GList *
+prepend_automatic_keywords (CajaFile *file,
+ GList *names)
+{
+ /* Prepend in reverse order. */
+ CajaFile *parent;
+
+ parent = caja_file_get_parent (file);
+
+#ifdef TRASH_IS_FAST_ENOUGH
+ if (caja_file_is_in_trash (file)) {
+ names = g_list_prepend
+ (names, g_strdup (CAJA_FILE_EMBLEM_NAME_TRASH));
+ }
+#endif
+ if (file_has_note (file)) {
+ names = g_list_prepend
+ (names, g_strdup (CAJA_FILE_EMBLEM_NAME_NOTE));
+ }
+
+ /* Trash files are assumed to be read-only,
+ * so we want to ignore them here. */
+ if (!caja_file_can_write (file) &&
+ !caja_file_is_in_trash (file) &&
+ (parent == NULL || caja_file_can_write (parent))) {
+ names = g_list_prepend
+ (names, g_strdup (CAJA_FILE_EMBLEM_NAME_CANT_WRITE));
+ }
+ if (!caja_file_can_read (file)) {
+ names = g_list_prepend
+ (names, g_strdup (CAJA_FILE_EMBLEM_NAME_CANT_READ));
+ }
+ if (caja_file_is_symbolic_link (file)) {
+ names = g_list_prepend
+ (names, g_strdup (CAJA_FILE_EMBLEM_NAME_SYMBOLIC_LINK));
+ }
+
+ if (parent) {
+ caja_file_unref (parent);
+ }
+
+
+ return names;
+}
+
+static void
+fill_emblem_cache_if_needed (CajaFile *file)
+{
+ GList *node, *keywords;
+ char *scanner;
+ size_t length;
+
+ if (file->details->compare_by_emblem_cache != NULL) {
+ /* Got a cache already. */
+ return;
+ }
+
+ keywords = caja_file_get_keywords (file);
+
+ /* Add up the keyword string lengths */
+ length = 1;
+ for (node = keywords; node != NULL; node = node->next) {
+ length += strlen ((const char *) node->data) + 1;
+ }
+
+ /* Now that we know how large the cache struct needs to be, allocate it. */
+ file->details->compare_by_emblem_cache = g_malloc (sizeof(CajaFileSortByEmblemCache) + length);
+
+ /* Copy them into the cache. */
+ scanner = file->details->compare_by_emblem_cache->emblem_keywords;
+ for (node = keywords; node != NULL; node = node->next) {
+ length = strlen ((const char *) node->data) + 1;
+ memcpy (scanner, (const char *) node->data, length);
+ scanner += length;
+ }
+
+ /* Zero-terminate so we can tell where the list ends. */
+ *scanner = 0;
+
+ eel_g_list_free_deep (keywords);
+}
+
+static int
+compare_by_emblems (CajaFile *file_1, CajaFile *file_2)
+{
+ const char *keyword_cache_1, *keyword_cache_2;
+ size_t length;
+ int compare_result;
+
+ fill_emblem_cache_if_needed (file_1);
+ fill_emblem_cache_if_needed (file_2);
+
+ /* We ignore automatic emblems, and only sort by user-added keywords. */
+ compare_result = 0;
+ keyword_cache_1 = file_1->details->compare_by_emblem_cache->emblem_keywords;
+ keyword_cache_2 = file_2->details->compare_by_emblem_cache->emblem_keywords;
+ for (; *keyword_cache_1 != '\0' && *keyword_cache_2 != '\0';) {
+ compare_result = g_utf8_collate (keyword_cache_1, keyword_cache_2);
+ if (compare_result != 0) {
+ return compare_result;
+ }
+
+ /* Advance to the next keyword */
+ length = strlen (keyword_cache_1);
+ keyword_cache_1 += length + 1;
+ keyword_cache_2 += length + 1;
+ }
+
+
+ /* One or both is now NULL. */
+ if (*keyword_cache_1 != '\0') {
+ g_assert (*keyword_cache_2 == '\0');
+ return -1;
+ } else if (*keyword_cache_2 != '\0') {
+ return +1;
+ }
+
+ return 0;
+}
+
+static int
+compare_by_type (CajaFile *file_1, CajaFile *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 = caja_file_is_directory (file_1);
+ is_directory_2 = caja_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 = caja_file_get_type_as_string (file_1);
+ type_string_2 = caja_file_get_type_as_string (file_2);
+
+ result = g_utf8_collate (type_string_1, type_string_2);
+
+ g_free (type_string_1);
+ g_free (type_string_2);
+
+ return result;
+}
+
+static int
+compare_by_time (CajaFile *file_1, CajaFile *file_2, CajaDateType 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 (CajaFile *file_1, CajaFile *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
+caja_file_compare_for_sort_internal (CajaFile *file_1,
+ CajaFile *file_2,
+ gboolean directories_first,
+ gboolean reversed)
+{
+ gboolean is_directory_1, is_directory_2;
+
+ if (directories_first) {
+ is_directory_1 = caja_file_is_directory (file_1);
+ is_directory_2 = caja_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;
+}
+
+/**
+ * caja_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
+caja_file_compare_for_sort (CajaFile *file_1,
+ CajaFile *file_2,
+ CajaFileSortType sort_type,
+ gboolean directories_first,
+ gboolean reversed)
+{
+ int result;
+
+ if (file_1 == file_2) {
+ return 0;
+ }
+
+ result = caja_file_compare_for_sort_internal (file_1, file_2, directories_first, reversed);
+
+ if (result == 0) {
+ switch (sort_type) {
+ case CAJA_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 CAJA_FILE_SORT_BY_DIRECTORY:
+ result = compare_by_full_path (file_1, file_2);
+ break;
+ case CAJA_FILE_SORT_BY_SIZE:
+ /* Compare directory sizes ourselves, then if necessary
+ * use MateVFS 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 CAJA_FILE_SORT_BY_TYPE:
+ /* MateVFS 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 CAJA_FILE_SORT_BY_MTIME:
+ result = compare_by_time (file_1, file_2, CAJA_DATE_TYPE_MODIFIED);
+ if (result == 0) {
+ result = compare_by_full_path (file_1, file_2);
+ }
+ break;
+ case CAJA_FILE_SORT_BY_ATIME:
+ result = compare_by_time (file_1, file_2, CAJA_DATE_TYPE_ACCESSED);
+ if (result == 0) {
+ result = compare_by_full_path (file_1, file_2);
+ }
+ break;
+ case CAJA_FILE_SORT_BY_TRASHED_TIME:
+ result = compare_by_time (file_1, file_2, CAJA_DATE_TYPE_TRASHED);
+ if (result == 0) {
+ result = compare_by_full_path (file_1, file_2);
+ }
+ break;
+ case CAJA_FILE_SORT_BY_EMBLEMS:
+ /* MateVFS doesn't know squat about our emblems, so
+ * we handle comparing them here, before falling back
+ * to tie-breakers.
+ */
+ result = compare_by_emblems (file_1, file_2);
+ if (result == 0) {
+ result = compare_by_full_path (file_1, file_2);
+ }
+ break;
+ default:
+ g_return_val_if_reached (0);
+ }
+
+ if (reversed) {
+ result = -result;
+ }
+ }
+
+ return result;
+}
+
+int
+caja_file_compare_for_sort_by_attribute_q (CajaFile *file_1,
+ CajaFile *file_2,
+ GQuark attribute,
+ gboolean directories_first,
+ gboolean reversed)
+{
+ int result;
+
+ if (file_1 == file_2) {
+ return 0;
+ }
+
+ /* Convert certain attributes into CajaFileSortTypes and use
+ * caja_file_compare_for_sort()
+ */
+ if (attribute == 0 || attribute == attribute_name_q) {
+ return caja_file_compare_for_sort (file_1, file_2,
+ CAJA_FILE_SORT_BY_DISPLAY_NAME,
+ directories_first,
+ reversed);
+ } else if (attribute == attribute_size_q) {
+ return caja_file_compare_for_sort (file_1, file_2,
+ CAJA_FILE_SORT_BY_SIZE,
+ directories_first,
+ reversed);
+ } else if (attribute == attribute_type_q) {
+ return caja_file_compare_for_sort (file_1, file_2,
+ CAJA_FILE_SORT_BY_TYPE,
+ directories_first,
+ reversed);
+ } else if (attribute == attribute_modification_date_q || attribute == attribute_date_modified_q) {
+ return caja_file_compare_for_sort (file_1, file_2,
+ CAJA_FILE_SORT_BY_MTIME,
+ directories_first,
+ reversed);
+ } else if (attribute == attribute_accessed_date_q || attribute == attribute_date_accessed_q) {
+ return caja_file_compare_for_sort (file_1, file_2,
+ CAJA_FILE_SORT_BY_ATIME,
+ directories_first,
+ reversed);
+ } else if (attribute == attribute_trashed_on_q) {
+ return caja_file_compare_for_sort (file_1, file_2,
+ CAJA_FILE_SORT_BY_TRASHED_TIME,
+ directories_first,
+ reversed);
+ } else if (attribute == attribute_emblems_q) {
+ return caja_file_compare_for_sort (file_1, file_2,
+ CAJA_FILE_SORT_BY_EMBLEMS,
+ directories_first,
+ reversed);
+ }
+
+ /* it is a normal attribute, compare by strings */
+
+ result = caja_file_compare_for_sort_internal (file_1, file_2, directories_first, reversed);
+
+ if (result == 0) {
+ char *value_1;
+ char *value_2;
+
+ value_1 = caja_file_get_string_attribute_q (file_1,
+ attribute);
+ value_2 = caja_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
+caja_file_compare_for_sort_by_attribute (CajaFile *file_1,
+ CajaFile *file_2,
+ const char *attribute,
+ gboolean directories_first,
+ gboolean reversed)
+{
+ return caja_file_compare_for_sort_by_attribute_q (file_1, file_2,
+ g_quark_from_string (attribute),
+ directories_first,
+ reversed);
+}
+
+
+/**
+ * caja_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
+caja_file_compare_display_name (CajaFile *file,
+ const char *pattern)
+{
+ const char *name;
+ int result;
+
+ g_return_val_if_fail (pattern != NULL, -1);
+
+ name = caja_file_peek_display_name (file);
+ result = g_utf8_collate (name, pattern);
+ return result;
+}
+
+
+gboolean
+caja_file_is_hidden_file (CajaFile *file)
+{
+ return file->details->is_hidden;
+}
+
+gboolean
+caja_file_is_backup_file (CajaFile *file)
+{
+ return file->details->is_backup;
+}
+
+static gboolean
+is_file_hidden (CajaFile *file)
+{
+ return file->details->directory->details->hidden_file_hash != NULL &&
+ g_hash_table_lookup (file->details->directory->details->hidden_file_hash,
+ eel_ref_str_peek (file->details->name)) != NULL;
+
+}
+
+/**
+ * caja_file_should_show:
+ * @file: the file to check.
+ * @show_hidden: whether we want to show hidden files or not.
+ * @show_backup: whether we want to show backup files or not.
+ *
+ * Determines if a #CajaFile 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
+caja_file_should_show (CajaFile *file,
+ gboolean show_hidden,
+ gboolean show_backup,
+ gboolean show_foreign)
+{
+ /* Never hide any files in trash. */
+ if (caja_file_is_in_trash (file)) {
+ return TRUE;
+ } else {
+ return (show_hidden || (!caja_file_is_hidden_file (file) && !is_file_hidden (file))) &&
+ (show_backup || !caja_file_is_backup_file (file)) &&
+ (show_foreign || !(caja_file_is_in_desktop (file) && caja_file_is_foreign_link (file)));
+ }
+}
+
+gboolean
+caja_file_is_home (CajaFile *file)
+{
+ GFile *dir;
+
+ dir = file->details->directory->details->location;
+ if (dir == NULL) {
+ return FALSE;
+ }
+
+ return caja_is_home_directory_file (dir,
+ eel_ref_str_peek (file->details->name));
+}
+
+gboolean
+caja_file_is_in_desktop (CajaFile *file)
+{
+ if (file->details->directory->details->location) {
+ return caja_is_desktop_directory (file->details->directory->details->location);
+ }
+ return FALSE;
+
+}
+
+static gboolean
+filter_hidden_and_backup_partition_callback (gpointer data,
+ gpointer callback_data)
+{
+ CajaFile *file;
+ FilterOptions options;
+
+ file = CAJA_FILE (data);
+ options = GPOINTER_TO_INT (callback_data);
+
+ return caja_file_should_show (file,
+ options & SHOW_HIDDEN,
+ options & SHOW_BACKUP,
+ TRUE);
+}
+
+GList *
+caja_file_list_filter_hidden_and_backup (GList *files,
+ gboolean show_hidden,
+ gboolean show_backup)
+{
+ GList *filtered_files;
+ GList *removed_files;
+
+ /* FIXME bugzilla.gnome.org 40653:
+ * Eventually this should become a generic filtering thingy.
+ */
+
+ filtered_files = caja_file_list_copy (files);
+ filtered_files = eel_g_list_partition (filtered_files,
+ filter_hidden_and_backup_partition_callback,
+ GINT_TO_POINTER ((show_hidden ? SHOW_HIDDEN : 0) |
+ (show_backup ? SHOW_BACKUP : 0)),
+ &removed_files);
+ caja_file_list_free (removed_files);
+
+ return filtered_files;
+}
+
+char *
+caja_file_get_metadata (CajaFile *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 (CAJA_IS_FILE (file), g_strdup (default_metadata));
+
+ id = caja_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 *
+caja_file_get_metadata_list (CajaFile *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 (CAJA_IS_FILE (file), NULL);
+
+ id = caja_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
+caja_file_set_metadata (CajaFile *file,
+ const char *key,
+ const char *default_metadata,
+ const char *metadata)
+{
+ const char *val;
+
+ g_return_if_fail (CAJA_IS_FILE (file));
+ g_return_if_fail (key != NULL);
+ g_return_if_fail (key[0] != '\0');
+
+ val = metadata;
+ if (val == NULL) {
+ val = default_metadata;
+ }
+
+ EEL_CALL_METHOD
+ (CAJA_FILE_CLASS, file,
+ set_metadata, (file, key, val));
+}
+
+void
+caja_file_set_metadata_list (CajaFile *file,
+ const char *key,
+ GList *list)
+{
+ char **val;
+ int len, i;
+ GList *l;
+
+ g_return_if_fail (CAJA_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;
+
+ EEL_CALL_METHOD
+ (CAJA_FILE_CLASS, file,
+ set_metadata_as_list, (file, key, val));
+
+ g_free (val);
+}
+
+
+gboolean
+caja_file_get_boolean_metadata (CajaFile *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 (CAJA_IS_FILE (file), default_metadata);
+
+ result_as_string = caja_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
+caja_file_get_integer_metadata (CajaFile *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 (CAJA_IS_FILE (file), default_metadata);
+
+ g_snprintf (default_as_string, sizeof (default_as_string), "%d", default_metadata);
+ result_as_string = caja_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
+caja_file_get_time_metadata (CajaFile *file,
+ const char *key)
+{
+ time_t time;
+ char *time_string;
+
+ time_string = caja_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
+caja_file_set_time_metadata (CajaFile *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;
+ }
+
+ caja_file_set_metadata (file, key, NULL, metadata);
+}
+
+
+void
+caja_file_set_boolean_metadata (CajaFile *file,
+ const char *key,
+ gboolean default_metadata,
+ gboolean metadata)
+{
+ g_return_if_fail (CAJA_IS_FILE (file));
+ g_return_if_fail (key != NULL);
+ g_return_if_fail (key[0] != '\0');
+
+ caja_file_set_metadata (file, key,
+ default_metadata ? "true" : "false",
+ metadata ? "true" : "false");
+}
+
+void
+caja_file_set_integer_metadata (CajaFile *file,
+ const char *key,
+ int default_metadata,
+ int metadata)
+{
+ char value_as_string[32];
+ char default_as_string[32];
+
+ g_return_if_fail (CAJA_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);
+
+ caja_file_set_metadata (file, key,
+ default_as_string, value_as_string);
+}
+
+static const char *
+caja_file_peek_display_name_collation_key (CajaFile *file)
+{
+ const char *res;
+
+ res = file->details->display_name_collation_key;
+ if (res == NULL)
+ res = "";
+
+ return res;
+}
+
+static const char *
+caja_file_peek_display_name (CajaFile *file)
+{
+ const char *name;
+ char *escaped_name;
+
+ /* 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)) {
+ caja_file_set_display_name (file,
+ name,
+ NULL,
+ FALSE);
+ } else {
+ escaped_name = g_uri_escape_string (name, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, TRUE);
+ caja_file_set_display_name (file,
+ escaped_name,
+ NULL,
+ FALSE);
+ g_free (escaped_name);
+ }
+ }
+
+ return eel_ref_str_peek (file->details->display_name);
+}
+
+char *
+caja_file_get_display_name (CajaFile *file)
+{
+ return g_strdup (caja_file_peek_display_name (file));
+}
+
+char *
+caja_file_get_edit_name (CajaFile *file)
+{
+ const char *res;
+
+ res = eel_ref_str_peek (file->details->edit_name);
+ if (res == NULL)
+ res = "";
+
+ return g_strdup (res);
+}
+
+char *
+caja_file_get_name (CajaFile *file)
+{
+ return g_strdup (eel_ref_str_peek (file->details->name));
+}
+
+/**
+ * caja_file_get_description:
+ * @file: a #CajaFile.
+ *
+ * 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 *
+caja_file_get_description (CajaFile *file)
+{
+ return g_strdup (file->details->description);
+}
+
+void
+caja_file_monitor_add (CajaFile *file,
+ gconstpointer client,
+ CajaFileAttributes attributes)
+{
+ g_return_if_fail (CAJA_IS_FILE (file));
+ g_return_if_fail (client != NULL);
+
+ EEL_CALL_METHOD
+ (CAJA_FILE_CLASS, file,
+ monitor_add, (file, client, attributes));
+}
+
+void
+caja_file_monitor_remove (CajaFile *file,
+ gconstpointer client)
+{
+ g_return_if_fail (CAJA_IS_FILE (file));
+ g_return_if_fail (client != NULL);
+
+ EEL_CALL_METHOD
+ (CAJA_FILE_CLASS, file,
+ monitor_remove, (file, client));
+}
+
+gboolean
+caja_file_is_launcher (CajaFile *file)
+{
+ return file->details->is_launcher;
+}
+
+gboolean
+caja_file_is_foreign_link (CajaFile *file)
+{
+ return file->details->is_foreign_link;
+}
+
+gboolean
+caja_file_is_trusted_link (CajaFile *file)
+{
+ return file->details->is_trusted_link;
+}
+
+gboolean
+caja_file_has_activation_uri (CajaFile *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 caja
+ * xml link file.
+ */
+char *
+caja_file_get_activation_uri (CajaFile *file)
+{
+ g_return_val_if_fail (CAJA_IS_FILE (file), NULL);
+
+ if (file->details->activation_uri != NULL) {
+ return g_strdup (file->details->activation_uri);
+ }
+
+ return caja_file_get_uri (file);
+}
+
+GFile *
+caja_file_get_activation_location (CajaFile *file)
+{
+ g_return_val_if_fail (CAJA_IS_FILE (file), NULL);
+
+ if (file->details->activation_uri != NULL) {
+ return g_file_new_for_uri (file->details->activation_uri);
+ }
+
+ return caja_file_get_location (file);
+}
+
+
+char *
+caja_file_get_drop_target_uri (CajaFile *file)
+{
+ char *uri, *target_uri;
+ GFile *location;
+ CajaDesktopLink *link;
+
+ g_return_val_if_fail (CAJA_IS_FILE (file), NULL);
+
+ if (CAJA_IS_DESKTOP_ICON_FILE (file)) {
+ link = caja_desktop_icon_file_get_link (CAJA_DESKTOP_ICON_FILE (file));
+
+ if (link != NULL) {
+ location = caja_desktop_link_get_activation_location (link);
+ g_object_unref (link);
+ if (location != NULL) {
+ uri = g_file_get_uri (location);
+ g_object_unref (location);
+ return uri;
+ }
+ }
+ }
+
+ uri = caja_file_get_uri (file);
+
+ /* Check for Caja link */
+ if (caja_file_is_caja_link (file)) {
+ location = caja_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 = caja_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 (CajaFile *file)
+{
+ char *custom_icon_uri;
+ char *uri;
+ char *dir_uri;
+
+ uri = caja_file_get_metadata (file, CAJA_METADATA_KEY_CUSTOM_ICON, NULL);
+ if (uri != NULL &&
+ caja_file_is_directory (file) &&
+ is_uri_relative (uri)) {
+ dir_uri = caja_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 GIcon *
+get_custom_icon (CajaFile *file)
+{
+ char *custom_icon_uri;
+ GFile *icon_file;
+ GIcon *icon;
+
+ if (file == NULL) {
+ return NULL;
+ }
+
+ icon = NULL;
+
+ /* Metadata takes precedence */
+ 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 && file->details->got_link_info && file->details->custom_icon != NULL) {
+ if (g_path_is_absolute (file->details->custom_icon)) {
+ icon_file = g_file_new_for_path (file->details->custom_icon);
+ icon = g_file_icon_new (icon_file);
+ g_object_unref (icon_file);
+ } else {
+ icon = g_themed_icon_new (file->details->custom_icon);
+ }
+ }
+
+ return icon;
+}
+
+
+static guint cached_thumbnail_limit;
+int cached_thumbnail_size;
+static int show_image_thumbs;
+
+GFilesystemPreviewType
+caja_file_get_filesystem_use_preview (CajaFile *file)
+{
+ GFilesystemPreviewType use_preview;
+ CajaFile *parent;
+
+ parent = caja_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;
+}
+
+gboolean
+caja_file_should_show_thumbnail (CajaFile *file)
+{
+ const char *mime_type;
+ GFilesystemPreviewType use_preview;
+
+ use_preview = caja_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 (caja_thumbnail_is_mimetype_limited_by_size (mime_type) &&
+ file->details->thumbnail_path == NULL &&
+ caja_file_get_size (file) > cached_thumbnail_limit) {
+ return FALSE;
+ }
+
+ if (show_image_thumbs == CAJA_SPEED_TRADEOFF_ALWAYS) {
+ if (use_preview == G_FILESYSTEM_PREVIEW_TYPE_NEVER) {
+ return FALSE;
+ } else {
+ return TRUE;
+ }
+ } else if (show_image_thumbs == CAJA_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 caja_file_is_local (file);
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+prepend_icon_name (const char *name,
+ GThemedIcon *icon)
+{
+ g_themed_icon_prepend_name(icon, name);
+}
+
+GIcon *
+caja_file_get_gicon (CajaFile *file,
+ CajaFileIconFlags flags)
+{
+ const char * const * names;
+ const char *name;
+ GPtrArray *prepend_array;
+ GMount *mount;
+ GIcon *icon, *mount_icon = NULL, *emblemed_icon;
+ GEmblem *emblem;
+ int i;
+ gboolean is_folder = FALSE, is_preview = FALSE, is_inode_directory = FALSE;
+
+ if (file == NULL) {
+ return NULL;
+ }
+
+ if (file->details->icon) {
+ icon = NULL;
+
+ /* fetch the mount icon here, we'll use it later */
+ if (flags & CAJA_FILE_ICON_FLAGS_USE_MOUNT_ICON ||
+ flags & CAJA_FILE_ICON_FLAGS_USE_MOUNT_ICON_AS_EMBLEM) {
+ mount = caja_file_get_mount (file);
+
+ if (mount != NULL) {
+ mount_icon = g_mount_get_icon (mount);
+ g_object_unref (mount);
+ }
+ }
+
+ if (((flags & CAJA_FILE_ICON_FLAGS_EMBEDDING_TEXT) ||
+ (flags & CAJA_FILE_ICON_FLAGS_FOR_DRAG_ACCEPT) ||
+ (flags & CAJA_FILE_ICON_FLAGS_FOR_OPEN_FOLDER) ||
+ (flags & CAJA_FILE_ICON_FLAGS_USE_MOUNT_ICON) ||
+ (flags & CAJA_FILE_ICON_FLAGS_USE_MOUNT_ICON_AS_EMBLEM) ||
+ ((flags & CAJA_FILE_ICON_FLAGS_IGNORE_VISITING) == 0 &&
+ caja_file_has_open_window (file))) &&
+ 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;
+ }
+ if (strcmp (name, "text-x-generic") == 0 &&
+ (flags & CAJA_FILE_ICON_FLAGS_EMBEDDING_TEXT)) {
+ is_preview = TRUE;
+ }
+ }
+
+ /* Here, we add icons in reverse order of precedence,
+ * because they are later prepended */
+ if (is_preview) {
+ g_ptr_array_add (prepend_array, "text-x-preview");
+ }
+
+ /* "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 & CAJA_FILE_ICON_FLAGS_FOR_OPEN_FOLDER)) {
+ g_ptr_array_add (prepend_array, "folder-open");
+ }
+ if (is_folder &&
+ (flags & CAJA_FILE_ICON_FLAGS_IGNORE_VISITING) == 0 &&
+ caja_file_has_open_window (file)) {
+ g_ptr_array_add (prepend_array, "folder-visiting");
+ }
+ if (is_folder &&
+ (flags & CAJA_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);
+ }
+
+ if ((flags & CAJA_FILE_ICON_FLAGS_USE_MOUNT_ICON) &&
+ mount_icon != NULL) {
+ g_object_unref (icon);
+ icon = mount_icon;
+ } else if ((flags & CAJA_FILE_ICON_FLAGS_USE_MOUNT_ICON_AS_EMBLEM) &&
+ mount_icon != NULL && !g_icon_equal (mount_icon, icon)) {
+
+ emblem = g_emblem_new (mount_icon);
+ emblemed_icon = g_emblemed_icon_new (icon, emblem);
+
+ g_object_unref (emblem);
+ g_object_unref (icon);
+ g_object_unref (mount_icon);
+
+ icon = emblemed_icon;
+ } else if (mount_icon != NULL) {
+ g_object_unref (mount_icon);
+ }
+
+ return icon;
+ }
+
+ return g_themed_icon_new ("text-x-generic");
+}
+
+static GIcon *
+get_default_file_icon (CajaFileIconFlags flags)
+{
+ static GIcon *fallback_icon = NULL;
+ static GIcon *fallback_icon_preview = NULL;
+ if (fallback_icon == NULL) {
+ fallback_icon = g_themed_icon_new ("text-x-generic");
+ fallback_icon_preview = g_themed_icon_new ("text-x-preview");
+ g_themed_icon_append_name (G_THEMED_ICON (fallback_icon_preview), "text-x-generic");
+ }
+ if (flags & CAJA_FILE_ICON_FLAGS_EMBEDDING_TEXT) {
+ return fallback_icon_preview;
+ } else {
+ return fallback_icon;
+ }
+}
+
+CajaIconInfo *
+caja_file_get_icon (CajaFile *file,
+ int size,
+ CajaFileIconFlags flags)
+{
+ CajaIconInfo *icon;
+ GIcon *gicon;
+ GdkPixbuf *raw_pixbuf, *scaled_pixbuf;
+ int modified_size;
+
+ if (file == NULL) {
+ return NULL;
+ }
+
+ gicon = get_custom_icon (file);
+ if (gicon) {
+ icon = caja_icon_info_lookup (gicon, size);
+ g_object_unref (gicon);
+ return icon;
+ }
+
+ if (flags & CAJA_FILE_ICON_FLAGS_FORCE_THUMBNAIL_SIZE) {
+ modified_size = size;
+ } else {
+ modified_size = size * cached_thumbnail_size / CAJA_ICON_SIZE_STANDARD;
+ }
+ if (flags & CAJA_FILE_ICON_FLAGS_USE_THUMBNAILS &&
+ caja_file_should_show_thumbnail (file)) {
+ if (file->details->thumbnail) {
+ int w, h, s;
+ double scale;
+
+ raw_pixbuf = g_object_ref (file->details->thumbnail);
+
+ w = gdk_pixbuf_get_width (raw_pixbuf);
+ h = gdk_pixbuf_get_height (raw_pixbuf);
+
+ s = MAX (w, h);
+ /* Don't scale up small thumbnails in the standard view */
+ if (s <= cached_thumbnail_size) {
+ scale = (double)size / CAJA_ICON_SIZE_STANDARD;
+ }
+ else {
+ scale = (double)modified_size / s;
+ }
+ /* Make sure that icons don't get smaller than CAJA_ICON_SIZE_SMALLEST */
+ if (s*scale <= CAJA_ICON_SIZE_SMALLEST) {
+ scale = (double) CAJA_ICON_SIZE_SMALLEST / s;
+ }
+
+ scaled_pixbuf = gdk_pixbuf_scale_simple (raw_pixbuf,
+ w * scale, h * scale,
+ GDK_INTERP_BILINEAR);
+
+ /* We don't want frames around small icons */
+ if (!gdk_pixbuf_get_has_alpha(raw_pixbuf) || s >= 128) {
+ caja_thumbnail_frame_image (&scaled_pixbuf);
+ }
+ g_object_unref (raw_pixbuf);
+
+ /* 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 &&
+ !file->details->thumbnail_wants_original &&
+ caja_can_thumbnail_internally (file)) {
+ /* Invalidate if we resize upward */
+ file->details->thumbnail_wants_original = TRUE;
+ caja_file_invalidate_attributes (file, CAJA_FILE_ATTRIBUTE_THUMBNAIL);
+ }
+
+ icon = caja_icon_info_new_for_pixbuf (scaled_pixbuf);
+ g_object_unref (scaled_pixbuf);
+ return icon;
+ } else if (file->details->thumbnail_path == NULL &&
+ file->details->can_read &&
+ !file->details->is_thumbnailing &&
+ !file->details->thumbnailing_failed) {
+ if (caja_can_thumbnail (file)) {
+ caja_create_thumbnail (file);
+ }
+ }
+ }
+
+ if (file->details->is_thumbnailing &&
+ flags & CAJA_FILE_ICON_FLAGS_USE_THUMBNAILS)
+ gicon = g_themed_icon_new (ICON_NAME_THUMBNAIL_LOADING);
+ else
+ gicon = caja_file_get_gicon (file, flags);
+
+ if (gicon) {
+ icon = caja_icon_info_lookup (gicon, size);
+ if (caja_icon_info_is_fallback (icon)) {
+ g_object_unref (icon);
+ icon = caja_icon_info_lookup (get_default_file_icon (flags), size);
+ }
+ g_object_unref (gicon);
+ return icon;
+ } else {
+ return caja_icon_info_lookup (get_default_file_icon (flags), size);
+ }
+}
+
+GdkPixbuf *
+caja_file_get_icon_pixbuf (CajaFile *file,
+ int size,
+ gboolean force_size,
+ CajaFileIconFlags flags)
+{
+ CajaIconInfo *info;
+ GdkPixbuf *pixbuf;
+
+ info = caja_file_get_icon (file, size, flags);
+ if (force_size) {
+ pixbuf = caja_icon_info_get_pixbuf_at_size (info, size);
+ } else {
+ pixbuf = caja_icon_info_get_pixbuf (info);
+ }
+ g_object_unref (info);
+
+ return pixbuf;
+}
+
+char *
+caja_file_get_custom_icon (CajaFile *file)
+{
+ char *custom_icon;
+
+ if (file == NULL) {
+ return NULL;
+ }
+
+ /* Metadata takes precedence */
+ custom_icon = get_custom_icon_metadata_uri (file);
+
+ if (custom_icon == NULL && file->details->got_link_info) {
+ custom_icon = g_strdup (file->details->custom_icon);
+ }
+
+ return custom_icon;
+}
+
+
+gboolean
+caja_file_get_date (CajaFile *file,
+ CajaDateType date_type,
+ time_t *date)
+{
+ if (date != NULL) {
+ *date = 0;
+ }
+
+ g_return_val_if_fail (date_type == CAJA_DATE_TYPE_CHANGED
+ || date_type == CAJA_DATE_TYPE_ACCESSED
+ || date_type == CAJA_DATE_TYPE_MODIFIED
+ || date_type == CAJA_DATE_TYPE_TRASHED
+ || date_type == CAJA_DATE_TYPE_PERMISSIONS_CHANGED, FALSE);
+
+ if (file == NULL) {
+ return FALSE;
+ }
+
+ g_return_val_if_fail (CAJA_IS_FILE (file), FALSE);
+
+ return EEL_CALL_METHOD_WITH_RETURN_VALUE
+ (CAJA_FILE_CLASS, file,
+ get_date, (file, date_type, date));
+}
+
+static char *
+caja_file_get_where_string (CajaFile *file)
+{
+ if (file == NULL) {
+ return NULL;
+ }
+
+ g_return_val_if_fail (CAJA_IS_FILE (file), NULL);
+
+ return EEL_CALL_METHOD_WITH_RETURN_VALUE
+ (CAJA_FILE_CLASS, file,
+ get_where_string, (file));
+}
+
+static const char *TODAY_TIME_FORMATS [] = {
+ /* Today, use special word.
+ * strftime patterns preceeded with the widest
+ * possible resulting string for that pattern.
+ *
+ * Note to localizers: You can look at man strftime
+ * for details on the format, but you should only use
+ * the specifiers from the C standard, not extensions.
+ * These include "%" followed by one of
+ * "aAbBcdHIjmMpSUwWxXyYZ". There are two extensions
+ * in the Caja version of strftime that can be
+ * used (and match GNU extensions). Putting a "-"
+ * between the "%" and any numeric directive will turn
+ * off zero padding, and putting a "_" there will use
+ * space padding instead of zero padding.
+ */
+ N_("today at 00:00:00 PM"),
+ N_("today at %-I:%M:%S %p"),
+
+ N_("today at 00:00 PM"),
+ N_("today at %-I:%M %p"),
+
+ N_("today, 00:00 PM"),
+ N_("today, %-I:%M %p"),
+
+ N_("today"),
+ N_("today"),
+
+ NULL
+};
+
+static const char *YESTERDAY_TIME_FORMATS [] = {
+ /* Yesterday, use special word.
+ * Note to localizers: Same issues as "today" string.
+ */
+ N_("yesterday at 00:00:00 PM"),
+ N_("yesterday at %-I:%M:%S %p"),
+
+ N_("yesterday at 00:00 PM"),
+ N_("yesterday at %-I:%M %p"),
+
+ N_("yesterday, 00:00 PM"),
+ N_("yesterday, %-I:%M %p"),
+
+ N_("yesterday"),
+ N_("yesterday"),
+
+ NULL
+};
+
+static const char *CURRENT_WEEK_TIME_FORMATS [] = {
+ /* Current week, include day of week.
+ * Note to localizers: Same issues as "today" string.
+ * The width measurement templates correspond to
+ * the day/month name with the most letters.
+ */
+ N_("Wednesday, September 00 0000 at 00:00:00 PM"),
+ N_("%A, %B %-d %Y at %-I:%M:%S %p"),
+
+ N_("Mon, Oct 00 0000 at 00:00:00 PM"),
+ N_("%a, %b %-d %Y at %-I:%M:%S %p"),
+
+ N_("Mon, Oct 00 0000 at 00:00 PM"),
+ N_("%a, %b %-d %Y at %-I:%M %p"),
+
+ N_("Oct 00 0000 at 00:00 PM"),
+ N_("%b %-d %Y at %-I:%M %p"),
+
+ N_("Oct 00 0000, 00:00 PM"),
+ N_("%b %-d %Y, %-I:%M %p"),
+
+ N_("00/00/00, 00:00 PM"),
+ N_("%m/%-d/%y, %-I:%M %p"),
+
+ N_("00/00/00"),
+ N_("%m/%d/%y"),
+
+ NULL
+};
+
+static char *
+caja_file_fit_date_as_string (CajaFile *file,
+ CajaDateType date_type,
+ int width,
+ CajaWidthMeasureCallback measure_callback,
+ CajaTruncateCallback truncate_callback,
+ void *measure_context)
+{
+ time_t file_time_raw;
+ struct tm *file_time;
+ const char **formats;
+ const char *width_template;
+ const char *format;
+ char *date_string;
+ char *result;
+ GDate *today;
+ GDate *file_date;
+ guint32 file_date_age;
+ int i;
+
+ if (!caja_file_get_date (file, date_type, &file_time_raw)) {
+ return NULL;
+ }
+
+ file_time = localtime (&file_time_raw);
+
+ if (date_format_pref == CAJA_DATE_FORMAT_LOCALE) {
+ return eel_strdup_strftime ("%c", file_time);
+ } else if (date_format_pref == CAJA_DATE_FORMAT_ISO) {
+ return eel_strdup_strftime ("%Y-%m-%d %H:%M:%S", file_time);
+ }
+
+ file_date = eel_g_date_new_tm (file_time);
+
+ today = g_date_new ();
+ g_date_set_time_t (today, time (NULL));
+
+ /* Overflow results in a large number; fine for our purposes. */
+ file_date_age = (g_date_get_julian (today) -
+ g_date_get_julian (file_date));
+
+ g_date_free (file_date);
+ g_date_free (today);
+
+ /* Format varies depending on how old the date is. This minimizes
+ * the length (and thus clutter & complication) of typical dates
+ * while providing sufficient detail for recent dates to make
+ * them maximally understandable at a glance. Keep all format
+ * strings separate rather than combining bits & pieces for
+ * internationalization's sake.
+ */
+
+ if (file_date_age == 0) {
+ formats = TODAY_TIME_FORMATS;
+ } else if (file_date_age == 1) {
+ formats = YESTERDAY_TIME_FORMATS;
+ } else if (file_date_age < 7) {
+ formats = CURRENT_WEEK_TIME_FORMATS;
+ } else {
+ formats = CURRENT_WEEK_TIME_FORMATS;
+ }
+
+ /* Find the date format that just fits the required width. Instead of measuring
+ * the resulting string width directly, measure the width of a template that represents
+ * the widest possible version of a date in a given format. This is done by using M, m
+ * and 0 for the variable letters/digits respectively.
+ */
+ format = NULL;
+
+ for (i = 0; ; i += 2) {
+ width_template = (formats [i] ? _(formats [i]) : NULL);
+ if (width_template == NULL) {
+ /* no more formats left */
+ g_assert (format != NULL);
+
+ /* Can't fit even the shortest format -- return an ellipsized form in the
+ * shortest format
+ */
+
+ date_string = eel_strdup_strftime (format, file_time);
+
+ if (truncate_callback == NULL) {
+ return date_string;
+ }
+
+ result = (* truncate_callback) (date_string, width, measure_context);
+ g_free (date_string);
+ return result;
+ }
+
+ format = _(formats [i + 1]);
+
+ if (measure_callback == NULL) {
+ /* don't care about fitting the width */
+ break;
+ }
+
+ if ((* measure_callback) (width_template, measure_context) <= width) {
+ /* The template fits, this is the format we can fit. */
+ break;
+ }
+ }
+
+ return eel_strdup_strftime (format, file_time);
+
+}
+
+/**
+ * caja_file_fit_modified_date_as_string:
+ *
+ * Get a user-displayable string representing a file modification date,
+ * truncated to @width using the measuring and truncating callbacks.
+ * @file: CajaFile representing the file in question.
+ * @width: The desired resulting string width.
+ * @measure_callback: The callback used to measure the string width.
+ * @truncate_callback: The callback used to truncate the string to a desired width.
+ * @measure_context: Data neede when measuring and truncating.
+ *
+ * Returns: Newly allocated string ready to display to the user.
+ *
+ **/
+char *
+caja_file_fit_modified_date_as_string (CajaFile *file,
+ int width,
+ CajaWidthMeasureCallback measure_callback,
+ CajaTruncateCallback truncate_callback,
+ void *measure_context)
+{
+ return caja_file_fit_date_as_string (file, CAJA_DATE_TYPE_MODIFIED,
+ width, measure_callback, truncate_callback, measure_context);
+}
+
+static char *
+caja_file_get_trash_original_file_parent_as_string (CajaFile *file)
+{
+ CajaFile *orig_file, *parent;
+ GFile *location;
+ char *filename;
+
+ if (file->details->trash_orig_path != NULL) {
+ orig_file = caja_file_get_trash_original_file (file);
+ parent = caja_file_get_parent (orig_file);
+ location = caja_file_get_location (parent);
+
+ filename = g_file_get_parse_name (location);
+
+ g_object_unref (location);
+ caja_file_unref (parent);
+ caja_file_unref (orig_file);
+
+ return filename;
+ }
+
+ return NULL;
+}
+
+/**
+ * caja_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: CajaFile representing the file in question.
+ *
+ * Returns: Newly allocated string ready to display to the user.
+ *
+ **/
+static char *
+caja_file_get_date_as_string (CajaFile *file, CajaDateType date_type)
+{
+ return caja_file_fit_date_as_string (file, date_type,
+ 0, NULL, NULL, NULL);
+}
+
+static CajaSpeedTradeoffValue show_directory_item_count;
+static CajaSpeedTradeoffValue show_text_in_icons;
+
+static void
+show_text_in_icons_changed_callback (gpointer callback_data)
+{
+ show_text_in_icons = eel_preferences_get_enum (CAJA_PREFERENCES_SHOW_TEXT_IN_ICONS);
+}
+
+static void
+show_directory_item_count_changed_callback (gpointer callback_data)
+{
+ show_directory_item_count = eel_preferences_get_enum (CAJA_PREFERENCES_SHOW_DIRECTORY_ITEM_COUNTS);
+}
+
+static gboolean
+get_speed_tradeoff_preference_for_file (CajaFile *file, CajaSpeedTradeoffValue value)
+{
+ GFilesystemPreviewType use_preview;
+
+ g_return_val_if_fail (CAJA_IS_FILE (file), FALSE);
+
+ use_preview = caja_file_get_filesystem_use_preview (file);
+
+ if (value == CAJA_SPEED_TRADEOFF_ALWAYS) {
+ if (use_preview == G_FILESYSTEM_PREVIEW_TYPE_NEVER) {
+ return FALSE;
+ } else {
+ return TRUE;
+ }
+ }
+
+ if (value == CAJA_SPEED_TRADEOFF_NEVER) {
+ return FALSE;
+ }
+
+ g_assert (value == CAJA_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 caja_file_is_local (file);
+ }
+}
+
+gboolean
+caja_file_should_show_directory_item_count (CajaFile *file)
+{
+ static gboolean show_directory_item_count_callback_added = FALSE;
+
+ g_return_val_if_fail (CAJA_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) {
+ eel_preferences_add_callback (CAJA_PREFERENCES_SHOW_DIRECTORY_ITEM_COUNTS,
+ 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);
+}
+
+gboolean
+caja_file_should_show_type (CajaFile *file)
+{
+ char *uri;
+ gboolean ret;
+
+ g_return_val_if_fail (CAJA_IS_FILE (file), FALSE);
+
+ uri = caja_file_get_uri (file);
+ ret = ((strcmp (uri, "computer:///") != 0) &&
+ (strcmp (uri, "network:///") != 0) &&
+ (strcmp (uri, "smb:///") != 0));
+ g_free (uri);
+
+ return ret;
+}
+
+gboolean
+caja_file_should_get_top_left_text (CajaFile *file)
+{
+ static gboolean show_text_in_icons_callback_added = FALSE;
+
+ g_return_val_if_fail (CAJA_IS_FILE (file), FALSE);
+
+ /* Add the callback once for the life of our process */
+ if (!show_text_in_icons_callback_added) {
+ eel_preferences_add_callback (CAJA_PREFERENCES_SHOW_TEXT_IN_ICONS,
+ show_text_in_icons_changed_callback,
+ NULL);
+ show_text_in_icons_callback_added = TRUE;
+
+ /* Peek for the first time */
+ show_text_in_icons_changed_callback (NULL);
+ }
+
+ if (show_text_in_icons == CAJA_SPEED_TRADEOFF_ALWAYS) {
+ return TRUE;
+ }
+
+ if (show_text_in_icons == CAJA_SPEED_TRADEOFF_NEVER) {
+ return FALSE;
+ }
+
+ return get_speed_tradeoff_preference_for_file (file, show_text_in_icons);
+}
+
+/**
+ * caja_file_get_directory_item_count
+ *
+ * Get the number of items in a directory.
+ * @file: CajaFile 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
+caja_file_get_directory_item_count (CajaFile *file,
+ guint *count,
+ gboolean *count_unreadable)
+{
+ if (count != NULL) {
+ *count = 0;
+ }
+ if (count_unreadable != NULL) {
+ *count_unreadable = FALSE;
+ }
+
+ g_return_val_if_fail (CAJA_IS_FILE (file), FALSE);
+
+ if (!caja_file_is_directory (file)) {
+ return FALSE;
+ }
+
+ if (!caja_file_should_show_directory_item_count (file)) {
+ return FALSE;
+ }
+
+ return EEL_CALL_METHOD_WITH_RETURN_VALUE
+ (CAJA_FILE_CLASS, file,
+ get_item_count, (file, count, count_unreadable));
+}
+
+/**
+ * caja_file_get_deep_counts
+ *
+ * Get the statistics about items inside a directory.
+ * @file: CajaFile 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
+ * caja_file_should_show_directory_item_count returns FALSE
+ * for this file.
+ *
+ * Returns: Status to indicate whether sizes are available.
+ *
+ **/
+CajaRequestStatus
+caja_file_get_deep_counts (CajaFile *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 (CAJA_IS_FILE (file), CAJA_REQUEST_DONE);
+
+ if (!force && !caja_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 = CAJA_REQUEST_NOT_STARTED;
+ return file->details->deep_counts_status;
+ }
+
+ return EEL_CALL_METHOD_WITH_RETURN_VALUE
+ (CAJA_FILE_CLASS, file,
+ get_deep_counts, (file,
+ directory_count,
+ file_count,
+ unreadable_directory_count,
+ total_size));
+}
+
+void
+caja_file_recompute_deep_counts (CajaFile *file)
+{
+ if (file->details->deep_counts_status != CAJA_REQUEST_IN_PROGRESS) {
+ file->details->deep_counts_status = CAJA_REQUEST_NOT_STARTED;
+ if (file->details->directory != NULL) {
+ caja_directory_add_file_to_work_queue (file->details->directory, file);
+ caja_directory_async_state_changed (file->details->directory);
+ }
+ }
+}
+
+
+/**
+ * caja_file_get_directory_item_mime_types
+ *
+ * Get the list of mime-types present in a directory.
+ * @file: CajaFile representing a directory. It is an error to
+ * call this function on a file that is not a directory.
+ * @mime_list: Place to put the list of mime-types.
+ *
+ * Returns: TRUE if mime-type list is available.
+ *
+ **/
+gboolean
+caja_file_get_directory_item_mime_types (CajaFile *file,
+ GList **mime_list)
+{
+ g_return_val_if_fail (CAJA_IS_FILE (file), FALSE);
+ g_return_val_if_fail (mime_list != NULL, FALSE);
+
+ if (!caja_file_is_directory (file)
+ || !file->details->got_mime_list) {
+ *mime_list = NULL;
+ return FALSE;
+ }
+
+ *mime_list = eel_g_str_list_copy (file->details->mime_list);
+ return TRUE;
+}
+
+gboolean
+caja_file_can_get_size (CajaFile *file)
+{
+ return file->details->size == -1;
+}
+
+
+/**
+ * caja_file_get_size
+ *
+ * Get the file size.
+ * @file: CajaFile representing the file in question.
+ *
+ * Returns: Size in bytes.
+ *
+ **/
+goffset
+caja_file_get_size (CajaFile *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
+caja_file_get_mtime (CajaFile *file)
+{
+ return file->details->mtime;
+}
+
+
+static void
+set_attributes_get_info_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer callback_data)
+{
+ CajaFileOperation *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 (caja_file_update_info (op->file, new_info)) {
+ caja_file_changed (op->file);
+ }
+ g_object_unref (new_info);
+ }
+ caja_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)
+{
+ CajaFileOperation *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),
+ CAJA_FILE_DEFAULT_ATTRIBUTES,
+ 0,
+ G_PRIORITY_DEFAULT,
+ op->cancellable,
+ set_attributes_get_info_callback, op);
+ } else {
+ caja_file_operation_complete (op, NULL, error);
+ g_error_free (error);
+ }
+}
+
+void
+caja_file_set_attributes (CajaFile *file,
+ GFileInfo *attributes,
+ CajaFileOperationCallback callback,
+ gpointer callback_data)
+{
+ CajaFileOperation *op;
+ GFile *location;
+
+ op = caja_file_operation_new (file, callback, callback_data);
+
+ location = caja_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);
+}
+
+
+/**
+ * caja_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
+caja_file_can_get_permissions (CajaFile *file)
+{
+ return file->details->has_permissions;
+}
+
+/**
+ * caja_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
+caja_file_can_set_permissions (CajaFile *file)
+{
+ uid_t user_id;
+
+ if (file->details->uid != -1 &&
+ caja_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
+caja_file_get_permissions (CajaFile *file)
+{
+ g_return_val_if_fail (caja_file_can_get_permissions (file), 0);
+
+ return file->details->permissions;
+}
+
+/**
+ * caja_file_set_permissions:
+ *
+ * Change a file's permissions. This should only be called if
+ * caja_file_can_set_permissions returned TRUE.
+ *
+ * @file: CajaFile representing the file in question.
+ * @new_permissions: New permissions value. This is the whole
+ * set of permissions, not a delta.
+ **/
+void
+caja_file_set_permissions (CajaFile *file,
+ guint32 new_permissions,
+ CajaFileOperationCallback callback,
+ gpointer callback_data)
+{
+ GFileInfo *info;
+ GError *error;
+
+ if (!caja_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".
+ */
+ caja_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;
+ }
+
+ info = g_file_info_new ();
+ g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE, new_permissions);
+ caja_file_set_attributes (file, info, callback, callback_data);
+ g_object_unref (info);
+}
+
+/**
+ * caja_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
+caja_file_can_get_selinux_context (CajaFile *file)
+{
+ return file->details->selinux_context != NULL;
+}
+
+
+/**
+ * caja_file_get_selinux_context:
+ *
+ * Get a user-displayable string representing a file's selinux
+ * context
+ * @file: CajaFile representing the file in question.
+ *
+ * Returns: Newly allocated string ready to display to the user.
+ *
+ **/
+char *
+caja_file_get_selinux_context (CajaFile *file)
+{
+ char *translated;
+ char *raw;
+
+ g_return_val_if_fail (CAJA_IS_FILE (file), NULL);
+
+ if (!caja_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 (eel_str_is_empty (real_name)
+ || eel_strcmp (name, real_name) == 0
+ || eel_strcmp (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;
+}
+
+/**
+ * caja_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
+caja_file_can_get_owner (CajaFile *file)
+{
+ /* Before we have info on a file, the owner is unknown. */
+ return file->details->uid != -1;
+}
+
+/**
+ * caja_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 *
+caja_file_get_owner_name (CajaFile *file)
+{
+ return caja_file_get_owner_as_string (file, FALSE);
+}
+
+/**
+ * caja_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
+caja_file_can_set_owner (CajaFile *file)
+{
+ /* Not allowed to set the owner if we can't
+ * even read it. This can happen on non-UNIX file
+ * systems.
+ */
+ if (!caja_file_can_get_owner (file)) {
+ return FALSE;
+ }
+
+ /* Only root is also allowed to set the owner. */
+ return geteuid() == 0;
+}
+
+/**
+ * caja_file_set_owner:
+ *
+ * Set the owner of a file. This will only have any effect if
+ * caja_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
+caja_file_set_owner (CajaFile *file,
+ const char *user_name_or_id,
+ CajaFileOperationCallback callback,
+ gpointer callback_data)
+{
+ GError *error;
+ GFileInfo *info;
+ uid_t new_id;
+
+ if (!caja_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".
+ */
+ caja_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".
+ */
+ caja_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;
+ }
+
+ info = g_file_info_new ();
+ g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID, new_id);
+ caja_file_set_attributes (file, info, callback, callback_data);
+ g_object_unref (info);
+}
+
+/**
+ * caja_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 *
+caja_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 eel_g_str_list_alphabetize (list);
+}
+
+/**
+ * caja_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
+caja_file_can_get_group (CajaFile *file)
+{
+ /* Before we have info on a file, the group is unknown. */
+ return file->details->gid != -1;
+}
+
+/**
+ * caja_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 *
+caja_file_get_group_name (CajaFile *file)
+{
+ return g_strdup (eel_ref_str_peek (file->details->group));
+}
+
+/**
+ * caja_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
+caja_file_can_set_group (CajaFile *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 (!caja_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 *
+caja_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 eel_g_str_list_alphabetize (list);
+}
+
+/**
+ * caja_get_group_names:
+ *
+ * Get a list of all group names.
+ */
+GList *
+caja_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 eel_g_str_list_alphabetize (list);
+}
+
+/**
+ * caja_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 CajaFile in question.
+ */
+GList *
+caja_file_get_settable_group_names (CajaFile *file)
+{
+ uid_t user_id;
+ GList *result;
+
+ if (!caja_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 = caja_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 = caja_get_group_names_for_user ();
+ } else {
+ g_warning ("unhandled case in caja_get_settable_group_names");
+ result = NULL;
+ }
+
+ return result;
+}
+
+/**
+ * caja_file_set_group:
+ *
+ * Set the group of a file. This will only have any effect if
+ * caja_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
+caja_file_set_group (CajaFile *file,
+ const char *group_name_or_id,
+ CajaFileOperationCallback callback,
+ gpointer callback_data)
+{
+ GError *error;
+ GFileInfo *info;
+ uid_t new_id;
+
+ if (!caja_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".
+ */
+ caja_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".
+ */
+ caja_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;
+ }
+
+
+ info = g_file_info_new ();
+ g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_GID, new_id);
+ caja_file_set_attributes (file, info, callback, callback_data);
+ g_object_unref (info);
+}
+
+/**
+ * caja_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: CajaFile representing the file in question.
+ *
+ * Returns: Newly allocated string ready to display to the user.
+ *
+ **/
+static char *
+caja_file_get_octal_permissions_as_string (CajaFile *file)
+{
+ guint32 permissions;
+
+ g_assert (CAJA_IS_FILE (file));
+
+ if (!caja_file_can_get_permissions (file)) {
+ return NULL;
+ }
+
+ permissions = file->details->permissions;
+ return g_strdup_printf ("%03o", permissions);
+}
+
+/**
+ * caja_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: CajaFile representing the file in question.
+ *
+ * Returns: Newly allocated string ready to display to the user.
+ *
+ **/
+static char *
+caja_file_get_permissions_as_string (CajaFile *file)
+{
+ guint32 permissions;
+ gboolean is_directory;
+ gboolean is_link;
+ gboolean suid, sgid, sticky;
+
+ if (!caja_file_can_get_permissions (file)) {
+ return NULL;
+ }
+
+ g_assert (CAJA_IS_FILE (file));
+
+ permissions = file->details->permissions;
+ is_directory = caja_file_is_directory (file);
+ is_link = caja_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' : '-'));
+}
+
+/**
+ * caja_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: CajaFile 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 *
+caja_file_get_owner_as_string (CajaFile *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 (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_printf ("%s - %s",
+ eel_ref_str_peek (file->details->owner),
+ 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);
+}
+
+/**
+ * caja_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: CajaFile representing the file in question.
+ *
+ * Returns: Newly allocated string ready to display to the user.
+ *
+ **/
+static char *
+caja_file_get_size_as_string (CajaFile *file)
+{
+ guint item_count;
+ gboolean count_unreadable;
+
+ if (file == NULL) {
+ return NULL;
+ }
+
+ g_assert (CAJA_IS_FILE (file));
+
+ if (caja_file_is_directory (file)) {
+ if (!caja_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;
+ }
+
+ #if GLIB_CHECK_VERSION(2, 30, 0)
+ return g_format_size(file->details->size);
+ #else // Since 2.16
+ return g_format_size_for_display(file->details->size);
+ #endif
+}
+
+/**
+ * caja_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: CajaFile representing the file in question.
+ *
+ * Returns: Newly allocated string ready to display to the user.
+ *
+ **/
+static char *
+caja_file_get_size_as_string_with_real_size (CajaFile *file)
+{
+ guint item_count;
+ gboolean count_unreadable;
+ char * formated;
+ char * formated_plus_real;
+ char * real_size;
+
+ if (file == NULL) {
+ return NULL;
+ }
+
+ g_assert (CAJA_IS_FILE (file));
+
+ if (caja_file_is_directory (file)) {
+ if (!caja_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;
+ }
+
+ #if GLIB_CHECK_VERSION(2, 30, 0)
+ formated = g_format_size(file->details->size);
+ #else
+ formated = g_format_size_for_display(file->details->size);
+ #endif
+
+ /* Do this in a separate stage so that we don't have to put G_GUINT64_FORMAT in the translated string */
+ real_size = g_strdup_printf (_("%"G_GUINT64_FORMAT), (guint64) file->details->size);
+ formated_plus_real = g_strdup_printf (_("%s (%s bytes)"), formated, real_size);
+ g_free (real_size);
+ g_free (formated);
+ return formated_plus_real;
+}
+
+
+static char *
+caja_file_get_deep_count_as_string_internal (CajaFile *file,
+ gboolean report_size,
+ gboolean report_directory_count,
+ gboolean report_file_count)
+{
+ CajaRequestStatus 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 (CAJA_IS_FILE (file));
+ g_assert (caja_file_is_directory (file));
+
+ status = caja_file_get_deep_counts
+ (file, &directory_count, &file_count, &unreadable_count, &total_size, FALSE);
+
+ /* Check whether any info is available. */
+ if (status == CAJA_REQUEST_NOT_STARTED) {
+ return NULL;
+ }
+
+ total_count = file_count + directory_count;
+
+ if (total_count == 0) {
+ switch (status) {
+ case CAJA_REQUEST_IN_PROGRESS:
+ /* Don't return confident "zero" until we're finished looking,
+ * because of next case.
+ */
+ return NULL;
+ case CAJA_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 caja_file_get_deep_counts
+ * directly if desired.
+ */
+ if (report_size)
+ {
+ #if GLIB_CHECK_VERSION(2, 30, 0)
+ return g_format_size(total_size);
+ #else
+ return g_format_size_for_display(total_size);
+ #endif
+ }
+
+ return format_item_count_for_display (report_directory_count
+ ? (report_file_count ? total_count : directory_count)
+ : file_count,
+ report_directory_count, report_file_count);
+}
+
+/**
+ * caja_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: CajaFile representing the file in question.
+ *
+ * Returns: Newly allocated string ready to display to the user.
+ *
+ **/
+static char *
+caja_file_get_deep_size_as_string (CajaFile *file)
+{
+ return caja_file_get_deep_count_as_string_internal (file, TRUE, FALSE, FALSE);
+}
+
+/**
+ * caja_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: CajaFile representing the file in question.
+ *
+ * Returns: Newly allocated string ready to display to the user.
+ *
+ **/
+static char *
+caja_file_get_deep_total_count_as_string (CajaFile *file)
+{
+ return caja_file_get_deep_count_as_string_internal (file, FALSE, TRUE, TRUE);
+}
+
+/**
+ * caja_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: CajaFile representing the file in question.
+ *
+ * Returns: Newly allocated string ready to display to the user.
+ *
+ **/
+static char *
+caja_file_get_deep_file_count_as_string (CajaFile *file)
+{
+ return caja_file_get_deep_count_as_string_internal (file, FALSE, FALSE, TRUE);
+}
+
+/**
+ * caja_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: CajaFile representing the file in question.
+ *
+ * Returns: Newly allocated string ready to display to the user.
+ *
+ **/
+static char *
+caja_file_get_deep_directory_count_as_string (CajaFile *file)
+{
+ return caja_file_get_deep_count_as_string_internal (file, FALSE, TRUE, FALSE);
+}
+
+/**
+ * caja_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
+ * caja_file_get_string_attribute_with_default if you want a non-NULL
+ * default.
+ *
+ * @file: CajaFile representing the file in question.
+ * @attribute_name: The name of the desired attribute. The currently supported
+ * set includes "name", "type", "mime_type", "size", "deep_size", "deep_directory_count",
+ * "deep_file_count", "deep_total_count", "date_modified", "date_changed", "date_accessed",
+ * "date_permissions", "owner", "group", "permissions", "octal_permissions", "uri", "where",
+ * "link_target", "volume", "free_space", "selinux_context", "trashed_on", "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 *
+caja_file_get_string_attribute_q (CajaFile *file, GQuark attribute_q)
+{
+ char *extension_attribute;
+
+ if (attribute_q == attribute_name_q) {
+ return caja_file_get_display_name (file);
+ }
+ if (attribute_q == attribute_type_q) {
+ return caja_file_get_type_as_string (file);
+ }
+ if (attribute_q == attribute_mime_type_q) {
+ return caja_file_get_mime_type (file);
+ }
+ if (attribute_q == attribute_size_q) {
+ return caja_file_get_size_as_string (file);
+ }
+ if (attribute_q == attribute_size_detail_q) {
+ return caja_file_get_size_as_string_with_real_size (file);
+ }
+ if (attribute_q == attribute_deep_size_q) {
+ return caja_file_get_deep_size_as_string (file);
+ }
+ if (attribute_q == attribute_deep_file_count_q) {
+ return caja_file_get_deep_file_count_as_string (file);
+ }
+ if (attribute_q == attribute_deep_directory_count_q) {
+ return caja_file_get_deep_directory_count_as_string (file);
+ }
+ if (attribute_q == attribute_deep_total_count_q) {
+ return caja_file_get_deep_total_count_as_string (file);
+ }
+ if (attribute_q == attribute_trash_orig_path_q) {
+ return caja_file_get_trash_original_file_parent_as_string (file);
+ }
+ if (attribute_q == attribute_date_modified_q) {
+ return caja_file_get_date_as_string (file,
+ CAJA_DATE_TYPE_MODIFIED);
+ }
+ if (attribute_q == attribute_date_changed_q) {
+ return caja_file_get_date_as_string (file,
+ CAJA_DATE_TYPE_CHANGED);
+ }
+ if (attribute_q == attribute_date_accessed_q) {
+ return caja_file_get_date_as_string (file,
+ CAJA_DATE_TYPE_ACCESSED);
+ }
+ if (attribute_q == attribute_trashed_on_q) {
+ return caja_file_get_date_as_string (file,
+ CAJA_DATE_TYPE_TRASHED);
+ }
+ if (attribute_q == attribute_date_permissions_q) {
+ return caja_file_get_date_as_string (file,
+ CAJA_DATE_TYPE_PERMISSIONS_CHANGED);
+ }
+ if (attribute_q == attribute_permissions_q) {
+ return caja_file_get_permissions_as_string (file);
+ }
+ if (attribute_q == attribute_selinux_context_q) {
+ return caja_file_get_selinux_context (file);
+ }
+ if (attribute_q == attribute_octal_permissions_q) {
+ return caja_file_get_octal_permissions_as_string (file);
+ }
+ if (attribute_q == attribute_owner_q) {
+ return caja_file_get_owner_as_string (file, TRUE);
+ }
+ if (attribute_q == attribute_group_q) {
+ return caja_file_get_group_name (file);
+ }
+ if (attribute_q == attribute_uri_q) {
+ return caja_file_get_uri (file);
+ }
+ if (attribute_q == attribute_where_q) {
+ return caja_file_get_where_string (file);
+ }
+ if (attribute_q == attribute_link_target_q) {
+ return caja_file_get_symbolic_link_target_path (file);
+ }
+ if (attribute_q == attribute_volume_q) {
+ return caja_file_get_volume_name (file);
+ }
+ if (attribute_q == attribute_free_space_q) {
+ return caja_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 *
+caja_file_get_string_attribute (CajaFile *file, const char *attribute_name)
+{
+ return caja_file_get_string_attribute_q (file, g_quark_from_string (attribute_name));
+}
+
+
+/**
+ * caja_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
+ * caja_file_get_string_attribute if you want NULL instead of a default
+ * result.
+ *
+ * @file: CajaFile representing the file in question.
+ * @attribute_name: The name of the desired attribute. See the description of
+ * caja_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 *
+caja_file_get_string_attribute_with_default_q (CajaFile *file, GQuark attribute_q)
+{
+ char *result;
+ guint item_count;
+ gboolean count_unreadable;
+ CajaRequestStatus status;
+
+ result = caja_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 (!caja_file_should_show_directory_item_count (file)) {
+ return g_strdup ("--");
+ }
+ count_unreadable = FALSE;
+ if (caja_file_is_directory (file)) {
+ caja_file_get_directory_item_count (file, &item_count, &count_unreadable);
+ }
+ return g_strdup (count_unreadable ? _("? items") : "...");
+ }
+ if (attribute_q == attribute_deep_size_q) {
+ status = caja_file_get_deep_counts (file, NULL, NULL, NULL, NULL, FALSE);
+ if (status == CAJA_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 = caja_file_get_deep_counts (file, NULL, NULL, NULL, NULL, FALSE);
+ if (status == CAJA_REQUEST_DONE) {
+ /* This means no contents at all were readable */
+ return g_strdup (_("? items"));
+ }
+ return g_strdup ("...");
+ }
+ if (attribute_q == attribute_type_q) {
+ return g_strdup (_("unknown type"));
+ }
+ if (attribute_q == attribute_mime_type_q) {
+ return g_strdup (_("unknown MIME type"));
+ }
+ if (attribute_q == attribute_trashed_on_q) {
+ /* If n/a */
+ return g_strdup ("");
+ }
+ if (attribute_q == attribute_trash_orig_path_q) {
+ /* If n/a */
+ return g_strdup ("");
+ }
+
+ /* Fallback, use for both unknown attributes and attributes
+ * for which we have no more appropriate default.
+ */
+ return g_strdup (_("unknown"));
+}
+
+char *
+caja_file_get_string_attribute_with_default (CajaFile *file, const char *attribute_name)
+{
+ return caja_file_get_string_attribute_with_default_q (file, g_quark_from_string (attribute_name));
+}
+
+gboolean
+caja_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_accessed_date_q ||
+ attribute_q == attribute_date_accessed_q ||
+ attribute_q == attribute_date_changed_q ||
+ attribute_q == attribute_trashed_on_q ||
+ attribute_q == attribute_date_permissions_q) {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * get_description:
+ *
+ * Get a user-displayable string representing a file type. The caller
+ * is responsible for g_free-ing this string.
+ * @file: CajaFile representing the file in question.
+ *
+ * Returns: Newly allocated string ready to display to the user.
+ *
+ **/
+static char *
+get_description (CajaFile *file)
+{
+ const char *mime_type;
+ char *description;
+
+ g_assert (CAJA_IS_FILE (file));
+
+ mime_type = eel_ref_str_peek (file->details->mime_type);
+ if (eel_str_is_empty (mime_type)) {
+ return NULL;
+ }
+
+ if (g_content_type_is_unknown (mime_type) &&
+ caja_file_is_executable (file)) {
+ return g_strdup (_("program"));
+ }
+
+ description = g_content_type_get_description (mime_type);
+ if (!eel_str_is_empty (description)) {
+ return description;
+ }
+
+ return g_strdup (mime_type);
+}
+
+/* Takes ownership of string */
+static char *
+update_description_for_link (CajaFile *file, char *string)
+{
+ char *res;
+
+ if (caja_file_is_symbolic_link (file)) {
+ g_assert (!caja_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 *
+caja_file_get_type_as_string (CajaFile *file)
+{
+ if (file == NULL) {
+ return NULL;
+ }
+
+ if (caja_file_is_broken_symbolic_link (file)) {
+ return g_strdup (_("link (broken)"));
+ }
+
+ return update_description_for_link (file, get_description (file));
+}
+
+/**
+ * caja_file_get_file_type
+ *
+ * Return this file's type.
+ * @file: CajaFile representing the file in question.
+ *
+ * Returns: The type.
+ *
+ **/
+GFileType
+caja_file_get_file_type (CajaFile *file)
+{
+ if (file == NULL) {
+ return G_FILE_TYPE_UNKNOWN;
+ }
+
+ return file->details->type;
+}
+
+/**
+ * caja_file_get_mime_type
+ *
+ * Return this file's default mime type.
+ * @file: CajaFile representing the file in question.
+ *
+ * Returns: The mime type.
+ *
+ **/
+char *
+caja_file_get_mime_type (CajaFile *file)
+{
+ if (file != NULL) {
+ g_return_val_if_fail (CAJA_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");
+}
+
+/**
+ * caja_file_is_mime_type
+ *
+ * Check whether a file is of a particular MIME type, or inherited
+ * from it.
+ * @file: CajaFile 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
+caja_file_is_mime_type (CajaFile *file, const char *mime_type)
+{
+ g_return_val_if_fail (CAJA_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);
+}
+
+gboolean
+caja_file_is_launchable (CajaFile *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 &&
+ caja_file_can_get_permissions (file) &&
+ caja_file_can_execute (file) &&
+ caja_file_is_executable (file) &&
+ !caja_file_is_directory (file);
+}
+
+
+/**
+ * caja_file_get_emblem_icons
+ *
+ * Return the list of names of emblems that this file should display,
+ * in canonical order.
+ * @file: CajaFile representing the file in question.
+ *
+ * Returns: A list of emblem names.
+ *
+ **/
+GList *
+caja_file_get_emblem_icons (CajaFile *file,
+ char **exclude)
+{
+ GList *keywords, *l;
+ GList *icons;
+ char *icon_names[2];
+ char *keyword;
+ int i;
+ GIcon *icon;
+
+ if (file == NULL) {
+ return NULL;
+ }
+
+ g_return_val_if_fail (CAJA_IS_FILE (file), NULL);
+
+ keywords = caja_file_get_keywords (file);
+ keywords = prepend_automatic_keywords (file, keywords);
+
+ icons = NULL;
+ for (l = keywords; l != NULL; l = l->next) {
+ keyword = l->data;
+
+#ifdef TRASH_IS_FAST_ENOUGH
+ if (strcmp (keyword, CAJA_FILE_EMBLEM_NAME_TRASH) == 0) {
+ char *uri;
+ gboolean file_is_trash;
+ /* Leave out the trash emblem for the trash itself, since
+ * putting a trash emblem on a trash icon is gilding the
+ * lily.
+ */
+ uri = caja_file_get_uri (file);
+ file_is_trash = strcmp (uri, EEL_TRASH_URI) == 0;
+ g_free (uri);
+ if (file_is_trash) {
+ continue;
+ }
+ }
+#endif
+ if (exclude) {
+ for (i = 0; exclude[i] != NULL; i++) {
+ if (strcmp (exclude[i], keyword) == 0) {
+ continue;
+ }
+ }
+ }
+
+
+ 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);
+ }
+
+ eel_g_list_free_deep (keywords);
+
+ return icons;
+}
+
+GList *
+caja_file_get_emblem_pixbufs (CajaFile *file,
+ int size,
+ gboolean force_size,
+ char **exclude)
+{
+ GList *icons, *l;
+ GList *pixbufs;
+ GIcon *icon;
+ GdkPixbuf *pixbuf;
+ CajaIconInfo *icon_info;
+
+ icons = caja_file_get_emblem_icons (file, exclude);
+ pixbufs = NULL;
+
+ for (l = icons; l != NULL; l = l->next) {
+ icon = l->data;
+
+ icon_info = caja_icon_info_lookup (icon, size);
+ if (force_size) {
+ pixbuf = caja_icon_info_get_pixbuf_nodefault_at_size (icon_info, size);
+ } else {
+ pixbuf = caja_icon_info_get_pixbuf_nodefault (icon_info);
+ }
+
+ if (pixbuf) {
+ pixbufs = g_list_prepend (pixbufs, pixbuf);
+ }
+
+
+ g_object_unref (icon_info);
+ g_object_unref (icon);
+ }
+ g_list_free (icons);
+
+ return g_list_reverse (pixbufs);
+
+
+}
+
+static GList *
+sort_keyword_list_and_remove_duplicates (GList *keywords)
+{
+ GList *p;
+ GList *duplicate_link;
+
+ if (keywords != NULL) {
+ keywords = eel_g_str_list_alphabetize (keywords);
+
+ 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);
+ eel_g_list_free_deep (duplicate_link);
+ } else {
+ p = p->next;
+ }
+ }
+ }
+
+ return keywords;
+}
+
+/**
+ * caja_file_get_keywords
+ *
+ * Return this file's keywords.
+ * @file: CajaFile representing the file in question.
+ *
+ * Returns: A list of keywords.
+ *
+ **/
+GList *
+caja_file_get_keywords (CajaFile *file)
+{
+ GList *keywords;
+
+ if (file == NULL) {
+ return NULL;
+ }
+
+ g_return_val_if_fail (CAJA_IS_FILE (file), NULL);
+
+ /* Put all the keywords into a list. */
+ keywords = caja_file_get_metadata_list
+ (file, CAJA_METADATA_KEY_EMBLEMS);
+
+ keywords = g_list_concat (keywords, eel_g_str_list_copy (file->details->extension_emblems));
+ keywords = g_list_concat (keywords, eel_g_str_list_copy (file->details->pending_extension_emblems));
+
+ return sort_keyword_list_and_remove_duplicates (keywords);
+}
+
+/**
+ * caja_file_set_keywords
+ *
+ * Change this file's keywords.
+ * @file: CajaFile representing the file in question.
+ * @keywords: New set of keywords (a GList of strings).
+ *
+ **/
+void
+caja_file_set_keywords (CajaFile *file, GList *keywords)
+{
+ GList *canonical_keywords;
+
+ /* Invalidate the emblem compare cache */
+ g_free (file->details->compare_by_emblem_cache);
+ file->details->compare_by_emblem_cache = NULL;
+
+ g_return_if_fail (CAJA_IS_FILE (file));
+
+ canonical_keywords = sort_keyword_list_and_remove_duplicates
+ (g_list_copy (keywords));
+ caja_file_set_metadata_list
+ (file, CAJA_METADATA_KEY_EMBLEMS, canonical_keywords);
+ g_list_free (canonical_keywords);
+}
+
+/**
+ * caja_file_is_symbolic_link
+ *
+ * Check if this file is a symbolic link.
+ * @file: CajaFile representing the file in question.
+ *
+ * Returns: True if the file is a symbolic link.
+ *
+ **/
+gboolean
+caja_file_is_symbolic_link (CajaFile *file)
+{
+ return file->details->is_symlink;
+}
+
+gboolean
+caja_file_is_mountpoint (CajaFile *file)
+{
+ return file->details->is_mountpoint;
+}
+
+GMount *
+caja_file_get_mount (CajaFile *file)
+{
+ if (file->details->mount) {
+ return g_object_ref (file->details->mount);
+ }
+ return NULL;
+}
+
+static void
+file_mount_unmounted (GMount *mount,
+ gpointer data)
+{
+ CajaFile *file;
+
+ file = CAJA_FILE (data);
+
+ caja_file_invalidate_attributes (file, CAJA_FILE_ATTRIBUTE_MOUNT);
+}
+
+void
+caja_file_set_mount (CajaFile *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);
+ }
+}
+
+/**
+ * caja_file_is_broken_symbolic_link
+ *
+ * Check if this file is a symbolic link with a missing target.
+ * @file: CajaFile representing the file in question.
+ *
+ * Returns: True if the file is a symbolic link with a missing target.
+ *
+ **/
+gboolean
+caja_file_is_broken_symbolic_link (CajaFile *file)
+{
+ if (file == NULL) {
+ return FALSE;
+ }
+
+ g_return_val_if_fail (CAJA_IS_FILE (file), FALSE);
+
+ /* Non-broken symbolic links return the target's type for get_file_type. */
+ return caja_file_get_file_type (file) == G_FILE_TYPE_SYMBOLIC_LINK;
+}
+
+static void
+get_fs_free_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ CajaDirectory *directory;
+ CajaFile *file;
+ guint64 free_space;
+ GFileInfo *info;
+
+ directory = CAJA_DIRECTORY (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 (directory->details->free_space != free_space) {
+ directory->details->free_space = free_space;
+ file = caja_directory_get_existing_corresponding_file (directory);
+ if (file) {
+ caja_file_emit_changed (file);
+ caja_file_unref (file);
+ }
+ }
+ caja_directory_unref (directory);
+}
+
+/**
+ * caja_file_get_volume_free_space
+ * Get a nicely formatted char with free space on the file's volume
+ * @file: CajaFile representing the file in question.
+ *
+ * Returns: newly-allocated copy of file size in a formatted string
+ */
+char *
+caja_file_get_volume_free_space (CajaFile *file)
+{
+ CajaDirectory *directory;
+ GFile *location;
+ char *res;
+ time_t now;
+
+ directory = caja_directory_get_for_file (file);
+
+ now = time (NULL);
+ /* Update first time and then every 2 seconds */
+ if (directory->details->free_space_read == 0 ||
+ (now - directory->details->free_space_read) > 2) {
+ directory->details->free_space_read = now;
+ location = caja_file_get_location (file);
+ g_file_query_filesystem_info_async (location,
+ G_FILE_ATTRIBUTE_FILESYSTEM_FREE,
+ 0, NULL,
+ get_fs_free_cb,
+ directory); /* Inherits ref */
+ g_object_unref (location);
+ } else {
+ caja_directory_unref (directory);
+ }
+
+
+ res = NULL;
+
+ if (directory->details->free_space != (guint64) -1)
+ {
+ #if GLIB_CHECK_VERSION(2, 30, 0)
+ res = g_format_size(directory->details->free_space);
+ #else
+ res = g_format_size_for_display(directory->details->free_space);
+ #endif
+ }
+
+ return res;
+}
+
+/**
+ * caja_file_get_volume_name
+ * Get the path of the volume the file resides on
+ * @file: CajaFile 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 *
+caja_file_get_volume_name (CajaFile *file)
+{
+ GFile *location;
+ char *res;
+ GMount *mount;
+
+ res = NULL;
+
+ location = caja_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;
+}
+
+/**
+ * caja_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: CajaFile representing the symbolic link in question.
+ *
+ * Returns: newly-allocated copy of the file path of the target of the symbolic link.
+ */
+char *
+caja_file_get_symbolic_link_target_path (CajaFile *file)
+{
+ if (!caja_file_is_symbolic_link (file)) {
+ g_warning ("File has symlink target, but is not marked as symlink");
+ }
+
+ return g_strdup (file->details->symlink_name);
+}
+
+/**
+ * caja_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: CajaFile representing the symbolic link in question.
+ *
+ * Returns: newly-allocated copy of the uri of the target of the symbolic link.
+ */
+char *
+caja_file_get_symbolic_link_target_uri (CajaFile *file)
+{
+ GFile *location, *parent, *target;
+ char *target_uri;
+
+ if (!caja_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 = caja_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;
+ }
+}
+
+/**
+ * caja_file_is_caja_link
+ *
+ * Check if this file is a "caja link", meaning a historical
+ * caja xml link file or a desktop file.
+ * @file: CajaFile representing the file in question.
+ *
+ * Returns: True if the file is a caja link.
+ *
+ **/
+gboolean
+caja_file_is_caja_link (CajaFile *file)
+{
+ /* NOTE: I removed the historical link here, because i don't think we
+ even detect that mimetype anymore */
+ return caja_file_is_mime_type (file, "application/x-desktop");
+}
+
+/**
+ * caja_file_is_directory
+ *
+ * Check if this file is a directory.
+ * @file: CajaFile representing the file in question.
+ *
+ * Returns: TRUE if @file is a directory.
+ *
+ **/
+gboolean
+caja_file_is_directory (CajaFile *file)
+{
+ return caja_file_get_file_type (file) == G_FILE_TYPE_DIRECTORY;
+}
+
+/**
+ * caja_file_is_user_special_directory
+ *
+ * Check if this file is a special platform directory.
+ * @file: CajaFile 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
+caja_file_is_user_special_directory (CajaFile *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 = caja_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
+caja_file_is_archive (CajaFile *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 = caja_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;
+}
+
+
+/**
+ * caja_file_is_in_trash
+ *
+ * Check if this file is a file in trash.
+ * @file: CajaFile representing the file in question.
+ *
+ * Returns: TRUE if @file is in a trash.
+ *
+ **/
+gboolean
+caja_file_is_in_trash (CajaFile *file)
+{
+ g_assert (CAJA_IS_FILE (file));
+
+ return caja_directory_is_in_trash (file->details->directory);
+}
+
+GError *
+caja_file_get_file_info_error (CajaFile *file)
+{
+ if (!file->details->get_info_failed) {
+ return NULL;
+ }
+
+ return file->details->get_info_error;
+}
+
+/**
+ * caja_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: CajaFile representing the file in question.
+ *
+ * Returns: TRUE if @file has a text MIME type.
+ *
+ **/
+gboolean
+caja_file_contains_text (CajaFile *file)
+{
+ if (file == NULL) {
+ return FALSE;
+ }
+
+ /* All text files inherit from text/plain */
+ return caja_file_is_mime_type (file, "text/plain");
+}
+
+/**
+ * caja_file_is_executable
+ *
+ * Check if this file is executable at all.
+ * @file: CajaFile 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
+caja_file_is_executable (CajaFile *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;
+}
+
+/**
+ * caja_file_peek_top_left_text
+ *
+ * Peek at the text from the top left of the file.
+ * @file: CajaFile representing the file in question.
+ *
+ * Returns: NULL if there is no text readable, otherwise, the text.
+ * This string is owned by the file object and should not
+ * be kept around or freed.
+ *
+ **/
+char *
+caja_file_peek_top_left_text (CajaFile *file,
+ gboolean need_large_text,
+ gboolean *needs_loading)
+{
+ g_return_val_if_fail (CAJA_IS_FILE (file), NULL);
+
+ if (!caja_file_should_get_top_left_text (file)) {
+ if (needs_loading) {
+ *needs_loading = FALSE;
+ }
+ return NULL;
+ }
+
+ if (needs_loading) {
+ *needs_loading = !file->details->top_left_text_is_up_to_date;
+ if (need_large_text) {
+ *needs_loading |= file->details->got_top_left_text != file->details->got_large_top_left_text;
+ }
+ }
+
+ /* Show " ..." in the file until we read the contents in. */
+ if (!file->details->got_top_left_text) {
+
+ if (caja_file_contains_text (file)) {
+ return " ...";
+ }
+ return NULL;
+ }
+
+ /* Show what we read in. */
+ return file->details->top_left_text;
+}
+
+/**
+ * caja_file_get_top_left_text
+ *
+ * Get the text from the top left of the file.
+ * @file: CajaFile representing the file in question.
+ *
+ * Returns: NULL if there is no text readable, otherwise, the text.
+ *
+ **/
+char *
+caja_file_get_top_left_text (CajaFile *file)
+{
+ return g_strdup (caja_file_peek_top_left_text (file, FALSE, NULL));
+}
+
+char *
+caja_file_get_filesystem_id (CajaFile *file)
+{
+ return g_strdup (eel_ref_str_peek (file->details->filesystem_id));
+}
+
+CajaFile *
+caja_file_get_trash_original_file (CajaFile *file)
+{
+ GFile *location;
+ CajaFile *original_file;
+ char *filename;
+
+ original_file = NULL;
+
+ if (file->details->trash_orig_path != NULL) {
+ /* file name is stored in URL encoding */
+ filename = g_uri_unescape_string (file->details->trash_orig_path, "");
+ location = g_file_new_for_path (filename);
+ original_file = caja_file_get (location);
+ g_object_unref (G_OBJECT (location));
+ g_free (filename);
+ }
+
+ return original_file;
+
+}
+
+void
+caja_file_mark_gone (CajaFile *file)
+{
+ CajaDirectory *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 (!caja_file_is_self_owned (file)) {
+ caja_directory_remove_file (directory, file);
+ }
+
+ caja_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.
+ */
+}
+
+/**
+ * caja_file_changed
+ *
+ * Notify the user that this file has changed.
+ * @file: CajaFile representing the file in question.
+ **/
+void
+caja_file_changed (CajaFile *file)
+{
+ GList fake_list;
+
+ g_return_if_fail (CAJA_IS_FILE (file));
+
+ if (caja_file_is_self_owned (file)) {
+ caja_file_emit_changed (file);
+ } else {
+ fake_list.data = file;
+ fake_list.next = NULL;
+ fake_list.prev = NULL;
+ caja_directory_emit_change_signals
+ (file->details->directory, &fake_list);
+ }
+}
+
+/**
+ * caja_file_updated_deep_count_in_progress
+ *
+ * Notify clients that a newer deep count is available for
+ * the directory in question.
+ */
+void
+caja_file_updated_deep_count_in_progress (CajaFile *file) {
+ GList *link_files, *node;
+
+ g_assert (CAJA_IS_FILE (file));
+ g_assert (caja_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) {
+ caja_file_updated_deep_count_in_progress (CAJA_FILE (node->data));
+ }
+ caja_file_list_free (link_files);
+}
+
+/**
+ * caja_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: CajaFile representing the file in question.
+ **/
+void
+caja_file_emit_changed (CajaFile *file)
+{
+ GList *link_files, *p;
+
+ g_assert (CAJA_IS_FILE (file));
+
+
+ /* Invalidate the emblem compare cache. -- This is not the cleanest
+ * place to do it but it is the one guaranteed bottleneck through
+ * which all change notifications pass.
+ */
+ g_free (file->details->compare_by_emblem_cache);
+ file->details->compare_by_emblem_cache = NULL;
+
+ /* 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) {
+ caja_file_changed (CAJA_FILE (p->data));
+ }
+ }
+ caja_file_list_free (link_files);
+}
+
+/**
+ * caja_file_is_gone
+ *
+ * Check if a file has already been deleted.
+ * @file: CajaFile representing the file in question.
+ *
+ * Returns: TRUE if the file is already gone.
+ **/
+gboolean
+caja_file_is_gone (CajaFile *file)
+{
+ g_return_val_if_fail (CAJA_IS_FILE (file), FALSE);
+
+ return file->details->is_gone;
+}
+
+/**
+ * caja_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: CajaFile representing the file in question.
+ *
+ * Returns: TRUE if the file is already gone.
+ **/
+gboolean
+caja_file_is_not_yet_confirmed (CajaFile *file)
+{
+ g_return_val_if_fail (CAJA_IS_FILE (file), FALSE);
+
+ return !file->details->got_file_info;
+}
+
+/**
+ * caja_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
+caja_file_check_if_ready (CajaFile *file,
+ CajaFileAttributes file_attributes)
+{
+ /* To be parallel with call_when_ready, return
+ * TRUE for NULL file.
+ */
+ if (file == NULL) {
+ return TRUE;
+ }
+
+ g_return_val_if_fail (CAJA_IS_FILE (file), FALSE);
+
+ return EEL_CALL_METHOD_WITH_RETURN_VALUE
+ (CAJA_FILE_CLASS, file,
+ check_if_ready, (file, file_attributes));
+}
+
+void
+caja_file_call_when_ready (CajaFile *file,
+ CajaFileAttributes file_attributes,
+ CajaFileCallback callback,
+ gpointer callback_data)
+
+{
+ if (file == NULL) {
+ (* callback) (file, callback_data);
+ return;
+ }
+
+ g_return_if_fail (CAJA_IS_FILE (file));
+
+ EEL_CALL_METHOD
+ (CAJA_FILE_CLASS, file,
+ call_when_ready, (file, file_attributes,
+ callback, callback_data));
+}
+
+void
+caja_file_cancel_call_when_ready (CajaFile *file,
+ CajaFileCallback callback,
+ gpointer callback_data)
+{
+ g_return_if_fail (callback != NULL);
+
+ if (file == NULL) {
+ return;
+ }
+
+ g_return_if_fail (CAJA_IS_FILE (file));
+
+ EEL_CALL_METHOD
+ (CAJA_FILE_CLASS, file,
+ cancel_call_when_ready, (file, callback, callback_data));
+}
+
+static void
+invalidate_directory_count (CajaFile *file)
+{
+ file->details->directory_count_is_up_to_date = FALSE;
+}
+
+static void
+invalidate_deep_counts (CajaFile *file)
+{
+ file->details->deep_counts_status = CAJA_REQUEST_NOT_STARTED;
+}
+
+static void
+invalidate_mime_list (CajaFile *file)
+{
+ file->details->mime_list_is_up_to_date = FALSE;
+}
+
+static void
+invalidate_top_left_text (CajaFile *file)
+{
+ file->details->top_left_text_is_up_to_date = FALSE;
+}
+
+static void
+invalidate_file_info (CajaFile *file)
+{
+ file->details->file_info_is_up_to_date = FALSE;
+}
+
+static void
+invalidate_link_info (CajaFile *file)
+{
+ file->details->link_info_is_up_to_date = FALSE;
+}
+
+static void
+invalidate_thumbnail (CajaFile *file)
+{
+ file->details->thumbnail_is_up_to_date = FALSE;
+}
+
+static void
+invalidate_mount (CajaFile *file)
+{
+ file->details->mount_is_up_to_date = FALSE;
+}
+
+void
+caja_file_invalidate_extension_info_internal (CajaFile *file)
+{
+ if (file->details->pending_info_providers)
+ eel_g_object_list_free (file->details->pending_info_providers);
+
+ file->details->pending_info_providers =
+ caja_module_get_extensions_for_type (CAJA_TYPE_INFO_PROVIDER);
+}
+
+void
+caja_file_invalidate_attributes_internal (CajaFile *file,
+ CajaFileAttributes file_attributes)
+{
+ Request request;
+
+ if (file == NULL) {
+ return;
+ }
+
+ if (CAJA_IS_DESKTOP_ICON_FILE (file)) {
+ /* Desktop icon files are always up to date.
+ * If we invalidate their attributes they
+ * will lose data, so we just ignore them.
+ */
+ return;
+ }
+
+ request = caja_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_TOP_LEFT_TEXT)) {
+ invalidate_top_left_text (file);
+ }
+ if (REQUEST_WANTS_TYPE (request, REQUEST_LINK_INFO)) {
+ invalidate_link_info (file);
+ }
+ if (REQUEST_WANTS_TYPE (request, REQUEST_EXTENSION_INFO)) {
+ caja_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
+caja_file_has_open_window (CajaFile *file)
+{
+ return file->details->has_open_window;
+}
+
+void
+caja_file_set_has_open_window (CajaFile *file,
+ gboolean has_open_window)
+{
+ has_open_window = (has_open_window != FALSE);
+
+ if (file->details->has_open_window != has_open_window) {
+ file->details->has_open_window = has_open_window;
+ caja_file_changed (file);
+ }
+}
+
+
+gboolean
+caja_file_is_thumbnailing (CajaFile *file)
+{
+ g_return_val_if_fail (CAJA_IS_FILE (file), FALSE);
+
+ return file->details->is_thumbnailing;
+}
+
+void
+caja_file_set_is_thumbnailing (CajaFile *file,
+ gboolean is_thumbnailing)
+{
+ g_return_if_fail (CAJA_IS_FILE (file));
+
+ file->details->is_thumbnailing = is_thumbnailing;
+}
+
+
+/**
+ * caja_file_invalidate_attributes
+ *
+ * Invalidate the specified attributes and force a reload.
+ * @file: CajaFile representing the file in question.
+ * @file_attributes: attributes to froget.
+ **/
+
+void
+caja_file_invalidate_attributes (CajaFile *file,
+ CajaFileAttributes file_attributes)
+{
+ /* Cancel possible in-progress loads of any of these attributes */
+ caja_directory_cancel_loading_file_attributes (file->details->directory,
+ file,
+ file_attributes);
+
+ /* Actually invalidate the values */
+ caja_file_invalidate_attributes_internal (file, file_attributes);
+
+ caja_directory_add_file_to_work_queue (file->details->directory, file);
+
+ /* Kick off I/O if necessary */
+ caja_directory_async_state_changed (file->details->directory);
+}
+
+CajaFileAttributes
+caja_file_get_all_attributes (void)
+{
+ return CAJA_FILE_ATTRIBUTE_INFO |
+ CAJA_FILE_ATTRIBUTE_LINK_INFO |
+ CAJA_FILE_ATTRIBUTE_DEEP_COUNTS |
+ CAJA_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT |
+ CAJA_FILE_ATTRIBUTE_DIRECTORY_ITEM_MIME_TYPES |
+ CAJA_FILE_ATTRIBUTE_TOP_LEFT_TEXT |
+ CAJA_FILE_ATTRIBUTE_LARGE_TOP_LEFT_TEXT |
+ CAJA_FILE_ATTRIBUTE_EXTENSION_INFO |
+ CAJA_FILE_ATTRIBUTE_THUMBNAIL |
+ CAJA_FILE_ATTRIBUTE_MOUNT;
+}
+
+void
+caja_file_invalidate_all_attributes (CajaFile *file)
+{
+ CajaFileAttributes all_attributes;
+
+ all_attributes = caja_file_get_all_attributes ();
+ caja_file_invalidate_attributes (file, all_attributes);
+}
+
+
+/**
+ * caja_file_dump
+ *
+ * Debugging call, prints out the contents of the file
+ * fields.
+ *
+ * @file: file to dump.
+ **/
+void
+caja_file_dump (CajaFile *file)
+{
+ long size = file->details->deep_size;
+ char *uri;
+ const char *file_kind;
+
+ uri = caja_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);
+}
+
+/**
+ * caja_file_list_ref
+ *
+ * Ref all the files in a list.
+ * @list: GList of files.
+ **/
+GList *
+caja_file_list_ref (GList *list)
+{
+ g_list_foreach (list, (GFunc) caja_file_ref, NULL);
+ return list;
+}
+
+/**
+ * caja_file_list_unref
+ *
+ * Unref all the files in a list.
+ * @list: GList of files.
+ **/
+void
+caja_file_list_unref (GList *list)
+{
+ g_list_foreach (list, (GFunc) caja_file_unref, NULL);
+}
+
+/**
+ * caja_file_list_free
+ *
+ * Free a list of files after unrefing them.
+ * @list: GList of files.
+ **/
+void
+caja_file_list_free (GList *list)
+{
+ caja_file_list_unref (list);
+ g_list_free (list);
+}
+
+/**
+ * caja_file_list_copy
+ *
+ * Copy the list of files, making a new ref of each,
+ * @list: GList of files.
+ **/
+GList *
+caja_file_list_copy (GList *list)
+{
+ return g_list_copy (caja_file_list_ref (list));
+}
+
+GList *
+caja_file_list_from_uris (GList *uri_list)
+{
+ GList *l, *file_list;
+ const char *uri;
+ GFile *file;
+
+ file_list = NULL;
+
+ for (l = uri_list; l != NULL; l = l->next) {
+ uri = l->data;
+ file = g_file_new_for_uri (uri);
+ file_list = g_list_prepend (file_list, file);
+ }
+ return g_list_reverse (file_list);
+}
+
+static gboolean
+get_attributes_for_default_sort_type (CajaFile *file,
+ gboolean *is_download,
+ gboolean *is_trash)
+{
+ gboolean is_download_dir, is_desktop_dir, is_trash_dir, retval;
+
+ *is_download = FALSE;
+ *is_trash = FALSE;
+ retval = FALSE;
+
+ /* special handling for certain directories */
+ if (file && caja_file_is_directory (file)) {
+ is_download_dir =
+ caja_file_is_user_special_directory (file, G_USER_DIRECTORY_DOWNLOAD);
+ is_desktop_dir =
+ caja_file_is_user_special_directory (file, G_USER_DIRECTORY_DESKTOP);
+ is_trash_dir =
+ caja_file_is_in_trash (file);
+
+ if (is_download_dir && !is_desktop_dir) {
+ *is_download = TRUE;
+ retval = TRUE;
+ } else if (is_trash_dir) {
+ *is_trash = TRUE;
+ retval = TRUE;
+ }
+ }
+
+ return retval;
+}
+
+CajaFileSortType
+caja_file_get_default_sort_type (CajaFile *file,
+ gboolean *reversed)
+{
+ CajaFileSortType retval;
+ gboolean is_download, is_trash, res;
+
+ retval = CAJA_FILE_SORT_NONE;
+ is_download = is_trash = FALSE;
+ res = get_attributes_for_default_sort_type (file, &is_download, &is_trash);
+
+ if (res) {
+ if (is_download) {
+ retval = CAJA_FILE_SORT_BY_MTIME;
+ } else if (is_trash) {
+ retval = CAJA_FILE_SORT_BY_TRASHED_TIME;
+ }
+
+ if (reversed != NULL) {
+ *reversed = res;
+ }
+ }
+
+ return retval;
+}
+
+const gchar *
+caja_file_get_default_sort_attribute (CajaFile *file,
+ gboolean *reversed)
+{
+ const gchar *retval;
+ gboolean is_download, is_trash, res;
+
+ retval = NULL;
+ is_download = is_trash = FALSE;
+ res = get_attributes_for_default_sort_type (file, &is_download, &is_trash);
+
+ if (res) {
+ if (is_download) {
+ retval = g_quark_to_string (attribute_date_modified_q);
+ } else if (is_trash) {
+ retval = g_quark_to_string (attribute_trashed_on_q);
+ }
+
+ if (reversed != NULL) {
+ *reversed = res;
+ }
+ }
+
+ return retval;
+}
+
+static int
+compare_by_display_name_cover (gconstpointer a, gconstpointer b)
+{
+ return compare_by_display_name (CAJA_FILE (a), CAJA_FILE (b));
+}
+
+/**
+ * caja_file_list_sort_by_display_name
+ *
+ * Sort the list of files by file name.
+ * @list: GList of files.
+ **/
+GList *
+caja_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;
+ CajaFileListCallback 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);
+
+ caja_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,
+ CajaFileListCallback callback,
+ gpointer callback_data)
+{
+ FileListReadyData *data;
+
+ data = g_new0 (FileListReadyData, 1);
+ data->file_list = caja_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 (CajaFile *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
+caja_file_list_call_when_ready (GList *file_list,
+ CajaFileAttributes attributes,
+ CajaFileListHandle **handle,
+ CajaFileListCallback callback,
+ gpointer callback_data)
+{
+ GList *l;
+ FileListReadyData *data;
+ CajaFile *file;
+
+ g_return_if_fail (file_list != NULL);
+
+ data = file_list_ready_data_new
+ (file_list, callback, callback_data);
+
+ if (handle) {
+ *handle = (CajaFileListHandle *) data;
+ }
+
+
+ l = file_list;
+ while (l != NULL) {
+ file = CAJA_FILE (l->data);
+ /* Need to do this here, as the list can be modified by this call */
+ l = l->next;
+ caja_file_call_when_ready (file,
+ attributes,
+ file_list_file_ready_callback,
+ data);
+ }
+}
+
+void
+caja_file_list_cancel_call_when_ready (CajaFileListHandle *handle)
+{
+ GList *l;
+ CajaFile *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 = CAJA_FILE (l->data);
+
+ EEL_CALL_METHOD
+ (CAJA_FILE_CLASS, file,
+ cancel_call_when_ready, (file, file_list_file_ready_callback, data));
+ }
+
+ file_list_ready_data_free (data);
+ }
+}
+
+static char *
+try_to_make_utf8 (const char *text, int *length)
+{
+ static const char *encodings_to_try[2];
+ static int n_encodings_to_try = 0;
+ gsize converted_length;
+ GError *conversion_error;
+ char *utf8_text;
+ int i;
+
+ if (n_encodings_to_try == 0) {
+ const char *charset;
+ gboolean charset_is_utf8;
+
+ charset_is_utf8 = g_get_charset (&charset);
+ if (!charset_is_utf8) {
+ encodings_to_try[n_encodings_to_try++] = charset;
+ }
+
+ if (g_ascii_strcasecmp (charset, "ISO-8859-1") != 0) {
+ encodings_to_try[n_encodings_to_try++] = "ISO-8859-1";
+ }
+ }
+
+ utf8_text = NULL;
+ for (i = 0; i < n_encodings_to_try; i++) {
+ conversion_error = NULL;
+ utf8_text = g_convert (text, *length,
+ "UTF-8", encodings_to_try[i],
+ NULL, &converted_length, &conversion_error);
+ if (utf8_text != NULL) {
+ *length = converted_length;
+ break;
+ }
+ g_error_free (conversion_error);
+ }
+
+ return utf8_text;
+}
+
+
+
+/* Extract the top left part of the read-in text. */
+char *
+caja_extract_top_left_text (const char *text,
+ gboolean large,
+ int length)
+{
+ GString* buffer;
+ const gchar *in;
+ const gchar *end;
+ int line, i;
+ gunichar c;
+ char *text_copy;
+ const char *utf8_end;
+ gboolean validated;
+ int max_bytes, max_lines, max_cols;
+
+ if (large) {
+ max_bytes = CAJA_FILE_LARGE_TOP_LEFT_TEXT_MAXIMUM_BYTES;
+ max_lines = CAJA_FILE_LARGE_TOP_LEFT_TEXT_MAXIMUM_LINES;
+ max_cols = CAJA_FILE_LARGE_TOP_LEFT_TEXT_MAXIMUM_CHARACTERS_PER_LINE;
+ } else {
+ max_bytes = CAJA_FILE_TOP_LEFT_TEXT_MAXIMUM_BYTES;
+ max_lines = CAJA_FILE_TOP_LEFT_TEXT_MAXIMUM_LINES;
+ max_cols = CAJA_FILE_TOP_LEFT_TEXT_MAXIMUM_CHARACTERS_PER_LINE;
+ }
+
+
+
+ text_copy = NULL;
+ if (text != NULL) {
+ /* Might be a partial utf8 character at the end if we didn't read whole file */
+ validated = g_utf8_validate (text, length, &utf8_end);
+ if (!validated &&
+ !(length >= max_bytes &&
+ text + length - utf8_end < 6)) {
+ text_copy = try_to_make_utf8 (text, &length);
+ text = text_copy;
+ } else if (!validated) {
+ length = utf8_end - text;
+ }
+ }
+
+ if (text == NULL || length == 0) {
+ return NULL;
+ }
+
+ buffer = g_string_new ("");
+ end = text + length; in = text;
+
+ for (line = 0; line < max_lines; line++) {
+ /* Extract one line. */
+ for (i = 0; i < max_cols; ) {
+ if (*in == '\n') {
+ break;
+ }
+
+ c = g_utf8_get_char (in);
+
+ if (g_unichar_isprint (c)) {
+ g_string_append_unichar (buffer, c);
+ i++;
+ }
+
+ in = g_utf8_next_char (in);
+ if (in == end) {
+ goto done;
+ }
+ }
+
+ /* Skip the rest of the line. */
+ while (*in != '\n') {
+ if (++in == end) {
+ goto done;
+ }
+ }
+ if (++in == end) {
+ goto done;
+ }
+
+ /* Put a new-line separator in. */
+ g_string_append_c(buffer, '\n');
+ }
+ done:
+ g_free (text_copy);
+
+ return g_string_free(buffer, FALSE);
+}
+
+static void
+thumbnail_limit_changed_callback (gpointer user_data)
+{
+ cached_thumbnail_limit = eel_preferences_get_uint (CAJA_PREFERENCES_IMAGE_FILE_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 = eel_preferences_get_integer (CAJA_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_image_thumbs = eel_preferences_get_enum (CAJA_PREFERENCES_SHOW_IMAGE_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 */
+ caja_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
+caja_file_class_init (CajaFileClass *class)
+{
+ GtkIconTheme *icon_theme;
+
+ caja_file_info_getter = caja_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_modification_date_q = g_quark_from_static_string ("modification_date");
+ attribute_date_modified_q = g_quark_from_static_string ("date_modified");
+ attribute_accessed_date_q = g_quark_from_static_string ("accessed_date");
+ attribute_date_accessed_q = g_quark_from_static_string ("date_accessed");
+ attribute_emblems_q = g_quark_from_static_string ("emblems");
+ 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_date_changed_q = g_quark_from_static_string ("date_changed");
+ attribute_trashed_on_q = g_quark_from_static_string ("trashed_on");
+ attribute_trash_orig_path_q = g_quark_from_static_string ("trash_orig_path");
+ attribute_date_permissions_q = g_quark_from_static_string ("date_permissions");
+ 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 = caja_file_constructor;
+
+ signals[CHANGED] =
+ g_signal_new ("changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaFileClass, 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 (CajaFileClass, updated_deep_count_in_progress),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ g_type_class_add_private (class, sizeof (CajaFileDetails));
+
+
+ eel_preferences_add_auto_enum (CAJA_PREFERENCES_DATE_FORMAT,
+ &date_format_pref);
+
+ thumbnail_limit_changed_callback (NULL);
+ eel_preferences_add_callback (CAJA_PREFERENCES_IMAGE_FILE_THUMBNAIL_LIMIT,
+ thumbnail_limit_changed_callback,
+ NULL);
+ thumbnail_size_changed_callback (NULL);
+ eel_preferences_add_callback (CAJA_PREFERENCES_ICON_VIEW_THUMBNAIL_SIZE,
+ thumbnail_size_changed_callback,
+ NULL);
+ show_thumbnails_changed_callback (NULL);
+ eel_preferences_add_callback (CAJA_PREFERENCES_SHOW_IMAGE_FILE_THUMBNAILS,
+ 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 (caja_signaller_get_current (),
+ "mime_data_changed",
+ G_CALLBACK (mime_type_data_changed_callback),
+ NULL);
+}
+
+static void
+caja_file_add_emblem (CajaFile *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));
+ }
+
+ caja_file_changed (file);
+}
+
+static void
+caja_file_add_string_attribute (CajaFile *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));
+ }
+
+ caja_file_changed (file);
+}
+
+static void
+caja_file_invalidate_extension_info (CajaFile *file)
+{
+ caja_file_invalidate_attributes (file, CAJA_FILE_ATTRIBUTE_EXTENSION_INFO);
+}
+
+void
+caja_file_info_providers_done (CajaFile *file)
+{
+ eel_g_list_free_deep (file->details->extension_emblems);
+ 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;
+
+ caja_file_changed (file);
+}
+
+static void
+caja_file_info_iface_init (CajaFileInfoIface *iface)
+{
+ iface->is_gone = caja_file_is_gone;
+ iface->get_name = caja_file_get_name;
+ iface->get_file_type = caja_file_get_file_type;
+ iface->get_location = caja_file_get_location;
+ iface->get_uri = caja_file_get_uri;
+ iface->get_parent_location = caja_file_get_parent_location;
+ iface->get_parent_uri = caja_file_get_parent_uri;
+ iface->get_parent_info = caja_file_get_parent;
+ iface->get_mount = caja_file_get_mount;
+ iface->get_uri_scheme = caja_file_get_uri_scheme;
+ iface->get_activation_uri = caja_file_get_activation_uri;
+ iface->get_mime_type = caja_file_get_mime_type;
+ iface->is_mime_type = caja_file_is_mime_type;
+ iface->is_directory = caja_file_is_directory;
+ iface->can_write = caja_file_can_write;
+ iface->add_emblem = caja_file_add_emblem;
+ iface->get_string_attribute = caja_file_get_string_attribute;
+ iface->add_string_attribute = caja_file_add_string_attribute;
+ iface->invalidate_extension_info = caja_file_invalidate_extension_info;
+}
+
+#if !defined (CAJA_OMIT_SELF_CHECK)
+
+void
+caja_self_check_file (void)
+{
+ CajaFile *file_1;
+ CajaFile *file_2;
+ GList *list;
+
+ /* refcount checks */
+
+ EEL_CHECK_INTEGER_RESULT (caja_directory_number_outstanding (), 0);
+
+ file_1 = caja_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 (caja_directory_number_outstanding (), 1);
+
+ caja_file_unref (file_1);
+
+ EEL_CHECK_INTEGER_RESULT (caja_directory_number_outstanding (), 0);
+
+ file_1 = caja_file_get_by_uri ("file:///etc");
+ file_2 = caja_file_get_by_uri ("file:///usr");
+
+ list = NULL;
+ list = g_list_prepend (list, file_1);
+ list = g_list_prepend (list, file_2);
+
+ caja_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);
+
+ caja_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);
+
+ caja_file_list_free (list);
+
+ EEL_CHECK_INTEGER_RESULT (caja_directory_number_outstanding (), 0);
+
+
+ /* name checks */
+ file_1 = caja_file_get_by_uri ("file:///home/");
+
+ EEL_CHECK_STRING_RESULT (caja_file_get_name (file_1), "home");
+
+ EEL_CHECK_BOOLEAN_RESULT (caja_file_get_by_uri ("file:///home/") == file_1, TRUE);
+ caja_file_unref (file_1);
+
+ EEL_CHECK_BOOLEAN_RESULT (caja_file_get_by_uri ("file:///home") == file_1, TRUE);
+ caja_file_unref (file_1);
+
+ caja_file_unref (file_1);
+
+ file_1 = caja_file_get_by_uri ("file:///home");
+ EEL_CHECK_STRING_RESULT (caja_file_get_name (file_1), "home");
+ caja_file_unref (file_1);
+
+#if 0
+ /* 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 = caja_file_get_by_uri (":");
+ EEL_CHECK_STRING_RESULT (caja_file_get_name (file_1), ":");
+ caja_file_unref (file_1);
+#endif
+
+ file_1 = caja_file_get_by_uri ("eazel:");
+ EEL_CHECK_STRING_RESULT (caja_file_get_name (file_1), "eazel");
+ caja_file_unref (file_1);
+
+ /* sorting */
+ file_1 = caja_file_get_by_uri ("file:///etc");
+ file_2 = caja_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 (caja_file_compare_for_sort (file_1, file_2, CAJA_FILE_SORT_BY_DISPLAY_NAME, FALSE, FALSE) < 0, TRUE);
+ EEL_CHECK_BOOLEAN_RESULT (caja_file_compare_for_sort (file_1, file_2, CAJA_FILE_SORT_BY_DISPLAY_NAME, FALSE, TRUE) > 0, TRUE);
+ EEL_CHECK_BOOLEAN_RESULT (caja_file_compare_for_sort (file_1, file_1, CAJA_FILE_SORT_BY_DISPLAY_NAME, FALSE, FALSE) == 0, TRUE);
+ EEL_CHECK_BOOLEAN_RESULT (caja_file_compare_for_sort (file_1, file_1, CAJA_FILE_SORT_BY_DISPLAY_NAME, TRUE, FALSE) == 0, TRUE);
+ EEL_CHECK_BOOLEAN_RESULT (caja_file_compare_for_sort (file_1, file_1, CAJA_FILE_SORT_BY_DISPLAY_NAME, FALSE, TRUE) == 0, TRUE);
+ EEL_CHECK_BOOLEAN_RESULT (caja_file_compare_for_sort (file_1, file_1, CAJA_FILE_SORT_BY_DISPLAY_NAME, TRUE, TRUE) == 0, TRUE);
+
+ caja_file_unref (file_1);
+ caja_file_unref (file_2);
+}
+
+#endif /* !CAJA_OMIT_SELF_CHECK */
diff --git a/libcaja-private/caja-file.h b/libcaja-private/caja-file.h
new file mode 100644
index 00000000..ec0845c0
--- /dev/null
+++ b/libcaja-private/caja-file.h
@@ -0,0 +1,590 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-file.h: Caja 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Darin Adler <[email protected]>
+*/
+
+#ifndef CAJA_FILE_H
+#define CAJA_FILE_H
+
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+#include <libcaja-private/caja-file-attributes.h>
+#include <libcaja-private/caja-icon-info.h>
+
+/* CajaFile is an object used to represent a single element of a
+ * CajaDirectory. It's lightweight and relies on CajaDirectory
+ * to do most of the work.
+ */
+
+/* CajaFile is defined both here and in caja-directory.h. */
+#ifndef CAJA_FILE_DEFINED
+#define CAJA_FILE_DEFINED
+typedef struct CajaFile CajaFile;
+#endif
+
+#define CAJA_TYPE_FILE caja_file_get_type()
+#define CAJA_FILE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_FILE, CajaFile))
+#define CAJA_FILE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_FILE, CajaFileClass))
+#define CAJA_IS_FILE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_FILE))
+#define CAJA_IS_FILE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_FILE))
+#define CAJA_FILE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_FILE, CajaFileClass))
+
+typedef enum
+{
+ CAJA_FILE_SORT_NONE,
+ CAJA_FILE_SORT_BY_DISPLAY_NAME,
+ CAJA_FILE_SORT_BY_DIRECTORY,
+ CAJA_FILE_SORT_BY_SIZE,
+ CAJA_FILE_SORT_BY_TYPE,
+ CAJA_FILE_SORT_BY_MTIME,
+ CAJA_FILE_SORT_BY_ATIME,
+ CAJA_FILE_SORT_BY_EMBLEMS,
+ CAJA_FILE_SORT_BY_TRASHED_TIME
+} CajaFileSortType;
+
+typedef enum
+{
+ CAJA_REQUEST_NOT_STARTED,
+ CAJA_REQUEST_IN_PROGRESS,
+ CAJA_REQUEST_DONE
+} CajaRequestStatus;
+
+typedef enum
+{
+ CAJA_FILE_ICON_FLAGS_NONE = 0,
+ CAJA_FILE_ICON_FLAGS_USE_THUMBNAILS = (1<<0),
+ CAJA_FILE_ICON_FLAGS_IGNORE_VISITING = (1<<1),
+ CAJA_FILE_ICON_FLAGS_EMBEDDING_TEXT = (1<<2),
+ CAJA_FILE_ICON_FLAGS_FOR_DRAG_ACCEPT = (1<<3),
+ CAJA_FILE_ICON_FLAGS_FOR_OPEN_FOLDER = (1<<4),
+ /* whether the thumbnail size must match the display icon size */
+ CAJA_FILE_ICON_FLAGS_FORCE_THUMBNAIL_SIZE = (1<<5),
+ /* uses the icon of the mount if present */
+ CAJA_FILE_ICON_FLAGS_USE_MOUNT_ICON = (1<<6),
+ /* render the mount icon as an emblem over the regular one */
+ CAJA_FILE_ICON_FLAGS_USE_MOUNT_ICON_AS_EMBLEM = (1<<7)
+} CajaFileIconFlags;
+
+/* Emblems sometimes displayed for CajaFiles. Do not localize. */
+#define CAJA_FILE_EMBLEM_NAME_SYMBOLIC_LINK "symbolic-link"
+#define CAJA_FILE_EMBLEM_NAME_CANT_READ "noread"
+#define CAJA_FILE_EMBLEM_NAME_CANT_WRITE "nowrite"
+#define CAJA_FILE_EMBLEM_NAME_TRASH "trash"
+#define CAJA_FILE_EMBLEM_NAME_NOTE "note"
+#define CAJA_FILE_EMBLEM_NAME_DESKTOP "desktop"
+#define CAJA_FILE_EMBLEM_NAME_SHARED "shared"
+
+typedef void (*CajaFileCallback) (CajaFile *file,
+ gpointer callback_data);
+typedef void (*CajaFileListCallback) (GList *file_list,
+ gpointer callback_data);
+typedef void (*CajaFileOperationCallback) (CajaFile *file,
+ GFile *result_location,
+ GError *error,
+ gpointer callback_data);
+typedef int (*CajaWidthMeasureCallback) (const char *string,
+ void *context);
+typedef char * (*CajaTruncateCallback) (const char *string,
+ int width,
+ void *context);
+
+
+#define CAJA_FILE_ATTRIBUTES_FOR_ICON (CAJA_FILE_ATTRIBUTE_INFO | CAJA_FILE_ATTRIBUTE_LINK_INFO | CAJA_FILE_ATTRIBUTE_THUMBNAIL)
+
+typedef void CajaFileListHandle;
+
+/* GObject requirements. */
+GType caja_file_get_type (void);
+
+/* Getting at a single file. */
+CajaFile * caja_file_get (GFile *location);
+CajaFile * caja_file_get_by_uri (const char *uri);
+
+/* Get a file only if the caja version already exists */
+CajaFile * caja_file_get_existing (GFile *location);
+CajaFile * caja_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,
+ */
+CajaFile * caja_file_ref (CajaFile *file);
+void caja_file_unref (CajaFile *file);
+
+/* Monitor the file. */
+void caja_file_monitor_add (CajaFile *file,
+ gconstpointer client,
+ CajaFileAttributes attributes);
+void caja_file_monitor_remove (CajaFile *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 caja_file_call_when_ready (CajaFile *file,
+ CajaFileAttributes attributes,
+ CajaFileCallback callback,
+ gpointer callback_data);
+void caja_file_cancel_call_when_ready (CajaFile *file,
+ CajaFileCallback callback,
+ gpointer callback_data);
+gboolean caja_file_check_if_ready (CajaFile *file,
+ CajaFileAttributes attributes);
+void caja_file_invalidate_attributes (CajaFile *file,
+ CajaFileAttributes attributes);
+void caja_file_invalidate_all_attributes (CajaFile *file);
+
+/* Basic attributes for file objects. */
+gboolean caja_file_contains_text (CajaFile *file);
+char * caja_file_get_display_name (CajaFile *file);
+char * caja_file_get_edit_name (CajaFile *file);
+char * caja_file_get_name (CajaFile *file);
+GFile * caja_file_get_location (CajaFile *file);
+char * caja_file_get_description (CajaFile *file);
+char * caja_file_get_uri (CajaFile *file);
+char * caja_file_get_uri_scheme (CajaFile *file);
+CajaFile * caja_file_get_parent (CajaFile *file);
+GFile * caja_file_get_parent_location (CajaFile *file);
+char * caja_file_get_parent_uri (CajaFile *file);
+char * caja_file_get_parent_uri_for_display (CajaFile *file);
+gboolean caja_file_can_get_size (CajaFile *file);
+goffset caja_file_get_size (CajaFile *file);
+time_t caja_file_get_mtime (CajaFile *file);
+GFileType caja_file_get_file_type (CajaFile *file);
+char * caja_file_get_mime_type (CajaFile *file);
+gboolean caja_file_is_mime_type (CajaFile *file,
+ const char *mime_type);
+gboolean caja_file_is_launchable (CajaFile *file);
+gboolean caja_file_is_symbolic_link (CajaFile *file);
+gboolean caja_file_is_mountpoint (CajaFile *file);
+GMount * caja_file_get_mount (CajaFile *file);
+char * caja_file_get_volume_free_space (CajaFile *file);
+char * caja_file_get_volume_name (CajaFile *file);
+char * caja_file_get_symbolic_link_target_path (CajaFile *file);
+char * caja_file_get_symbolic_link_target_uri (CajaFile *file);
+gboolean caja_file_is_broken_symbolic_link (CajaFile *file);
+gboolean caja_file_is_caja_link (CajaFile *file);
+gboolean caja_file_is_executable (CajaFile *file);
+gboolean caja_file_is_directory (CajaFile *file);
+gboolean caja_file_is_user_special_directory (CajaFile *file,
+ GUserDirectory special_directory);
+gboolean caja_file_is_archive (CajaFile *file);
+gboolean caja_file_is_in_trash (CajaFile *file);
+gboolean caja_file_is_in_desktop (CajaFile *file);
+gboolean caja_file_is_home (CajaFile *file);
+gboolean caja_file_is_desktop_directory (CajaFile *file);
+GError * caja_file_get_file_info_error (CajaFile *file);
+gboolean caja_file_get_directory_item_count (CajaFile *file,
+ guint *count,
+ gboolean *count_unreadable);
+void caja_file_recompute_deep_counts (CajaFile *file);
+CajaRequestStatus caja_file_get_deep_counts (CajaFile *file,
+ guint *directory_count,
+ guint *file_count,
+ guint *unreadable_directory_count,
+ goffset *total_size,
+ gboolean force);
+gboolean caja_file_should_show_thumbnail (CajaFile *file);
+gboolean caja_file_should_show_directory_item_count (CajaFile *file);
+gboolean caja_file_should_show_type (CajaFile *file);
+GList * caja_file_get_keywords (CajaFile *file);
+void caja_file_set_keywords (CajaFile *file,
+ GList *keywords);
+GList * caja_file_get_emblem_icons (CajaFile *file,
+ char **exclude);
+GList * caja_file_get_emblem_pixbufs (CajaFile *file,
+ int size,
+ gboolean force_size,
+ char **exclude);
+char * caja_file_get_top_left_text (CajaFile *file);
+char * caja_file_peek_top_left_text (CajaFile *file,
+ gboolean need_large_text,
+ gboolean *got_top_left_text);
+gboolean caja_file_get_directory_item_mime_types (CajaFile *file,
+ GList **mime_list);
+
+void caja_file_set_attributes (CajaFile *file,
+ GFileInfo *attributes,
+ CajaFileOperationCallback callback,
+ gpointer callback_data);
+GFilesystemPreviewType caja_file_get_filesystem_use_preview (CajaFile *file);
+
+char * caja_file_get_filesystem_id (CajaFile *file);
+
+CajaFile * caja_file_get_trash_original_file (CajaFile *file);
+
+/* Permissions. */
+gboolean caja_file_can_get_permissions (CajaFile *file);
+gboolean caja_file_can_set_permissions (CajaFile *file);
+guint caja_file_get_permissions (CajaFile *file);
+gboolean caja_file_can_get_owner (CajaFile *file);
+gboolean caja_file_can_set_owner (CajaFile *file);
+gboolean caja_file_can_get_group (CajaFile *file);
+gboolean caja_file_can_set_group (CajaFile *file);
+char * caja_file_get_owner_name (CajaFile *file);
+char * caja_file_get_group_name (CajaFile *file);
+GList * caja_get_user_names (void);
+GList * caja_get_all_group_names (void);
+GList * caja_file_get_settable_group_names (CajaFile *file);
+gboolean caja_file_can_get_selinux_context (CajaFile *file);
+char * caja_file_get_selinux_context (CajaFile *file);
+
+/* "Capabilities". */
+gboolean caja_file_can_read (CajaFile *file);
+gboolean caja_file_can_write (CajaFile *file);
+gboolean caja_file_can_execute (CajaFile *file);
+gboolean caja_file_can_rename (CajaFile *file);
+gboolean caja_file_can_delete (CajaFile *file);
+gboolean caja_file_can_trash (CajaFile *file);
+
+gboolean caja_file_can_mount (CajaFile *file);
+gboolean caja_file_can_unmount (CajaFile *file);
+gboolean caja_file_can_eject (CajaFile *file);
+gboolean caja_file_can_start (CajaFile *file);
+gboolean caja_file_can_start_degraded (CajaFile *file);
+gboolean caja_file_can_stop (CajaFile *file);
+GDriveStartStopType caja_file_get_start_stop_type (CajaFile *file);
+gboolean caja_file_can_poll_for_media (CajaFile *file);
+gboolean caja_file_is_media_check_automatic (CajaFile *file);
+
+void caja_file_mount (CajaFile *file,
+ GMountOperation *mount_op,
+ GCancellable *cancellable,
+ CajaFileOperationCallback callback,
+ gpointer callback_data);
+void caja_file_unmount (CajaFile *file,
+ GMountOperation *mount_op,
+ GCancellable *cancellable,
+ CajaFileOperationCallback callback,
+ gpointer callback_data);
+void caja_file_eject (CajaFile *file,
+ GMountOperation *mount_op,
+ GCancellable *cancellable,
+ CajaFileOperationCallback callback,
+ gpointer callback_data);
+
+void caja_file_start (CajaFile *file,
+ GMountOperation *start_op,
+ GCancellable *cancellable,
+ CajaFileOperationCallback callback,
+ gpointer callback_data);
+void caja_file_stop (CajaFile *file,
+ GMountOperation *mount_op,
+ GCancellable *cancellable,
+ CajaFileOperationCallback callback,
+ gpointer callback_data);
+void caja_file_poll_for_media (CajaFile *file);
+
+/* Basic operations for file objects. */
+void caja_file_set_owner (CajaFile *file,
+ const char *user_name_or_id,
+ CajaFileOperationCallback callback,
+ gpointer callback_data);
+void caja_file_set_group (CajaFile *file,
+ const char *group_name_or_id,
+ CajaFileOperationCallback callback,
+ gpointer callback_data);
+void caja_file_set_permissions (CajaFile *file,
+ guint32 permissions,
+ CajaFileOperationCallback callback,
+ gpointer callback_data);
+void caja_file_rename (CajaFile *file,
+ const char *new_name,
+ CajaFileOperationCallback callback,
+ gpointer callback_data);
+void caja_file_cancel (CajaFile *file,
+ CajaFileOperationCallback 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 caja_file_is_gone (CajaFile *file);
+
+/* Return true if this file is not confirmed to have ever really
+ * existed. This is true when the CajaFile object has been created, but no I/O
+ * has yet confirmed the existence of a file by that name.
+ */
+gboolean caja_file_is_not_yet_confirmed (CajaFile *file);
+
+/* Simple getting and setting top-level metadata. */
+char * caja_file_get_metadata (CajaFile *file,
+ const char *key,
+ const char *default_metadata);
+GList * caja_file_get_metadata_list (CajaFile *file,
+ const char *key);
+void caja_file_set_metadata (CajaFile *file,
+ const char *key,
+ const char *default_metadata,
+ const char *metadata);
+void caja_file_set_metadata_list (CajaFile *file,
+ const char *key,
+ GList *list);
+
+/* Covers for common data types. */
+gboolean caja_file_get_boolean_metadata (CajaFile *file,
+ const char *key,
+ gboolean default_metadata);
+void caja_file_set_boolean_metadata (CajaFile *file,
+ const char *key,
+ gboolean default_metadata,
+ gboolean metadata);
+int caja_file_get_integer_metadata (CajaFile *file,
+ const char *key,
+ int default_metadata);
+void caja_file_set_integer_metadata (CajaFile *file,
+ const char *key,
+ int default_metadata,
+ int metadata);
+
+#define UNDEFINED_TIME ((time_t) (-1))
+
+time_t caja_file_get_time_metadata (CajaFile *file,
+ const char *key);
+void caja_file_set_time_metadata (CajaFile *file,
+ const char *key,
+ time_t time);
+
+
+/* Attributes for file objects as user-displayable strings. */
+char * caja_file_get_string_attribute (CajaFile *file,
+ const char *attribute_name);
+char * caja_file_get_string_attribute_q (CajaFile *file,
+ GQuark attribute_q);
+char * caja_file_get_string_attribute_with_default (CajaFile *file,
+ const char *attribute_name);
+char * caja_file_get_string_attribute_with_default_q (CajaFile *file,
+ GQuark attribute_q);
+char * caja_file_fit_modified_date_as_string (CajaFile *file,
+ int width,
+ CajaWidthMeasureCallback measure_callback,
+ CajaTruncateCallback truncate_callback,
+ void *measure_truncate_context);
+
+/* Matching with another URI. */
+gboolean caja_file_matches_uri (CajaFile *file,
+ const char *uri);
+
+/* Is the file local? */
+gboolean caja_file_is_local (CajaFile *file);
+
+/* Comparing two file objects for sorting */
+CajaFileSortType caja_file_get_default_sort_type (CajaFile *file,
+ gboolean *reversed);
+const gchar * caja_file_get_default_sort_attribute (CajaFile *file,
+ gboolean *reversed);
+
+int caja_file_compare_for_sort (CajaFile *file_1,
+ CajaFile *file_2,
+ CajaFileSortType sort_type,
+ gboolean directories_first,
+ gboolean reversed);
+int caja_file_compare_for_sort_by_attribute (CajaFile *file_1,
+ CajaFile *file_2,
+ const char *attribute,
+ gboolean directories_first,
+ gboolean reversed);
+int caja_file_compare_for_sort_by_attribute_q (CajaFile *file_1,
+ CajaFile *file_2,
+ GQuark attribute,
+ gboolean directories_first,
+ gboolean reversed);
+gboolean caja_file_is_date_sort_attribute_q (GQuark attribute);
+
+int caja_file_compare_display_name (CajaFile *file_1,
+ const char *pattern);
+int caja_file_compare_location (CajaFile *file_1,
+ CajaFile *file_2);
+
+/* filtering functions for use by various directory views */
+gboolean caja_file_is_hidden_file (CajaFile *file);
+gboolean caja_file_is_backup_file (CajaFile *file);
+gboolean caja_file_should_show (CajaFile *file,
+ gboolean show_hidden,
+ gboolean show_backup,
+ gboolean show_foreign);
+GList *caja_file_list_filter_hidden_and_backup (GList *files,
+ gboolean show_hidden,
+ gboolean show_backup);
+
+
+/* Get the URI that's used when activating the file.
+ * Getting this can require reading the contents of the file.
+ */
+gboolean caja_file_is_launcher (CajaFile *file);
+gboolean caja_file_is_foreign_link (CajaFile *file);
+gboolean caja_file_is_trusted_link (CajaFile *file);
+gboolean caja_file_has_activation_uri (CajaFile *file);
+char * caja_file_get_activation_uri (CajaFile *file);
+GFile * caja_file_get_activation_location (CajaFile *file);
+
+char * caja_file_get_drop_target_uri (CajaFile *file);
+
+/* Get custom icon (if specified by metadata or link contents) */
+char * caja_file_get_custom_icon (CajaFile *file);
+
+
+GIcon * caja_file_get_gicon (CajaFile *file,
+ CajaFileIconFlags flags);
+CajaIconInfo * caja_file_get_icon (CajaFile *file,
+ int size,
+ CajaFileIconFlags flags);
+GdkPixbuf * caja_file_get_icon_pixbuf (CajaFile *file,
+ int size,
+ gboolean force_size,
+ CajaFileIconFlags flags);
+
+gboolean caja_file_has_open_window (CajaFile *file);
+void caja_file_set_has_open_window (CajaFile *file,
+ gboolean has_open_window);
+
+/* Thumbnailing handling */
+gboolean caja_file_is_thumbnailing (CajaFile *file);
+
+/* Convenience functions for dealing with a list of CajaFile objects that each have a ref.
+ * These are just convenient names for functions that work on lists of GtkObject *.
+ */
+GList * caja_file_list_ref (GList *file_list);
+void caja_file_list_unref (GList *file_list);
+void caja_file_list_free (GList *file_list);
+GList * caja_file_list_copy (GList *file_list);
+GList * caja_file_list_from_uris (GList *uri_list);
+GList * caja_file_list_sort_by_display_name (GList *file_list);
+void caja_file_list_call_when_ready (GList *file_list,
+ CajaFileAttributes attributes,
+ CajaFileListHandle **handle,
+ CajaFileListCallback callback,
+ gpointer callback_data);
+void caja_file_list_cancel_call_when_ready (CajaFileListHandle *handle);
+
+/* Debugging */
+void caja_file_dump (CajaFile *file);
+
+typedef struct CajaFileDetails CajaFileDetails;
+
+struct CajaFile
+{
+ GObject parent_slot;
+ CajaFileDetails *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
+{
+ CAJA_DATE_TYPE_MODIFIED,
+ CAJA_DATE_TYPE_CHANGED,
+ CAJA_DATE_TYPE_ACCESSED,
+ CAJA_DATE_TYPE_PERMISSIONS_CHANGED,
+ CAJA_DATE_TYPE_TRASHED
+} CajaDateType;
+
+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" CajaFile subclass that you can't actually get real
+ information about. For exaple CajaDesktopDirectoryFile. */
+ GFileType default_file_type;
+
+ /* Called when the file notices any change. */
+ void (* changed) (CajaFile *file);
+
+ /* Called periodically while directory deep count is being computed. */
+ void (* updated_deep_count_in_progress) (CajaFile *file);
+
+ /* Virtual functions (mainly used for trash directory). */
+ void (* monitor_add) (CajaFile *file,
+ gconstpointer client,
+ CajaFileAttributes attributes);
+ void (* monitor_remove) (CajaFile *file,
+ gconstpointer client);
+ void (* call_when_ready) (CajaFile *file,
+ CajaFileAttributes attributes,
+ CajaFileCallback callback,
+ gpointer callback_data);
+ void (* cancel_call_when_ready) (CajaFile *file,
+ CajaFileCallback callback,
+ gpointer callback_data);
+ gboolean (* check_if_ready) (CajaFile *file,
+ CajaFileAttributes attributes);
+ gboolean (* get_item_count) (CajaFile *file,
+ guint *count,
+ gboolean *count_unreadable);
+ CajaRequestStatus (* get_deep_counts) (CajaFile *file,
+ guint *directory_count,
+ guint *file_count,
+ guint *unreadable_directory_count,
+ goffset *total_size);
+ gboolean (* get_date) (CajaFile *file,
+ CajaDateType type,
+ time_t *date);
+ char * (* get_where_string) (CajaFile *file);
+
+ void (* set_metadata) (CajaFile *file,
+ const char *key,
+ const char *value);
+ void (* set_metadata_as_list) (CajaFile *file,
+ const char *key,
+ char **value);
+
+ void (* mount) (CajaFile *file,
+ GMountOperation *mount_op,
+ GCancellable *cancellable,
+ CajaFileOperationCallback callback,
+ gpointer callback_data);
+ void (* unmount) (CajaFile *file,
+ GMountOperation *mount_op,
+ GCancellable *cancellable,
+ CajaFileOperationCallback callback,
+ gpointer callback_data);
+ void (* eject) (CajaFile *file,
+ GMountOperation *mount_op,
+ GCancellable *cancellable,
+ CajaFileOperationCallback callback,
+ gpointer callback_data);
+
+ void (* start) (CajaFile *file,
+ GMountOperation *start_op,
+ GCancellable *cancellable,
+ CajaFileOperationCallback callback,
+ gpointer callback_data);
+ void (* stop) (CajaFile *file,
+ GMountOperation *mount_op,
+ GCancellable *cancellable,
+ CajaFileOperationCallback callback,
+ gpointer callback_data);
+
+ void (* poll_for_media) (CajaFile *file);
+} CajaFileClass;
+
+#endif /* CAJA_FILE_H */
diff --git a/libcaja-private/caja-global-preferences.c b/libcaja-private/caja-global-preferences.c
new file mode 100644
index 00000000..de2abd0a
--- /dev/null
+++ b/libcaja-private/caja-global-preferences.c
@@ -0,0 +1,954 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-global-preferences.c - Caja specific preference keys and
+ functions.
+
+ Copyright (C) 1999, 2000, 2001 Eazel, Inc.
+
+ The Mate 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 Mate 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 Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Ramiro Estrugo <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-global-preferences.h"
+
+#include "caja-file-utilities.h"
+#include "caja-file.h"
+#include <eel/eel-enumeration.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>
+
+/* Constants */
+#define STRING_ARRAY_DEFAULT_TOKENS_DELIMETER ","
+#define PREFERENCES_SORT_ORDER_MANUALLY 100
+
+/* Path for mate-vfs preferences */
+static const char *EXTRA_MONITOR_PATHS[] = { "/desktop/mate/file_views",
+ "/desktop/mate/background",
+ "/desktop/mate/lockdown",
+ NULL
+ };
+
+/* Forward declarations */
+static void global_preferences_install_defaults (void);
+static void global_preferences_register_enumerations (void);
+static gpointer default_font_callback (void);
+static gpointer default_home_link_name (void);
+static gpointer default_computer_link_name (void);
+static gpointer default_trash_link_name (void);
+static gpointer default_network_link_name (void);
+
+
+/* An enumeration used for installing type specific preferences defaults. */
+typedef enum
+{
+ PREFERENCE_BOOLEAN = 1,
+ PREFERENCE_INTEGER,
+ PREFERENCE_STRING,
+ PREFERENCE_STRING_ARRAY
+} PreferenceType;
+
+/* Enumerations used to qualify some INTEGER preferences */
+static EelEnumerationEntry speed_tradeoff_enum_entries[] =
+{
+ { "always", N_("_Always"), CAJA_SPEED_TRADEOFF_ALWAYS },
+ { "local_only", N_("_Local File Only"), CAJA_SPEED_TRADEOFF_LOCAL_ONLY },
+ { "never", N_("_Never"), CAJA_SPEED_TRADEOFF_NEVER }
+};
+
+static EelEnumerationEntry default_zoom_level_enum_entries[] =
+{
+ /* xgettext:no-c-format */
+ { "smallest", N_("25%"), CAJA_ZOOM_LEVEL_SMALLEST },
+ /* xgettext:no-c-format */
+ { "smaller", N_("50%"), CAJA_ZOOM_LEVEL_SMALLER },
+ /* xgettext:no-c-format */
+ { "small", N_("75%"), CAJA_ZOOM_LEVEL_SMALL },
+ /* xgettext:no-c-format */
+ { "standard", N_("100%"), CAJA_ZOOM_LEVEL_STANDARD },
+ /* xgettext:no-c-format */
+ { "large", N_("150%"), CAJA_ZOOM_LEVEL_LARGE },
+ /* xgettext:no-c-format */
+ { "larger", N_("200%"), CAJA_ZOOM_LEVEL_LARGER },
+ /* xgettext:no-c-format */
+ { "largest", N_("400%"), CAJA_ZOOM_LEVEL_LARGEST }
+};
+
+static EelEnumerationEntry file_size_enum_entries[] =
+{
+ { "102400", N_("100 K"), 102400 },
+ { "512000", N_("500 K"), 512000 },
+ { "1048576", N_("1 MB"), 1048576 },
+ { "3145728", N_("3 MB"), 3145728 },
+ { "5242880", N_("5 MB"), 5242880 },
+ { "10485760", N_("10 MB"), 10485760 },
+ { "104857600", N_("100 MB"), 104857600 },
+ { "1073741824", N_("1 GB"), 1073741824 },
+ { "2147483648", N_("2 GB"), 2147483648U },
+ { "4294967295", N_("4 GB"), 4294967295U }
+};
+
+static EelEnumerationEntry click_policy_enum_entries[] =
+{
+ {
+ "single",
+ N_("Activate items with a _single click"),
+ CAJA_CLICK_POLICY_SINGLE
+ },
+ {
+ "double",
+ N_("Activate items with a _double click"),
+ CAJA_CLICK_POLICY_DOUBLE
+ }
+};
+
+static EelEnumerationEntry executable_text_activation_enum_entries[] =
+{
+ {
+ "launch",
+ N_("E_xecute files when they are clicked"),
+ CAJA_EXECUTABLE_TEXT_LAUNCH
+ },
+ {
+ "display",
+ N_("Display _files when they are clicked"),
+ CAJA_EXECUTABLE_TEXT_DISPLAY
+ },
+ {
+ "ask",
+ N_("_Ask each time"),
+ CAJA_EXECUTABLE_TEXT_ASK
+ }
+};
+
+static EelEnumerationEntry search_bar_type_enum_entries[] =
+{
+ {
+ "search by text",
+ N_("Search for files by file name only"),
+ CAJA_SIMPLE_SEARCH_BAR
+ },
+ {
+ "search by text and properties",
+ N_("Search for files by file name and file properties"),
+ CAJA_COMPLEX_SEARCH_BAR
+ }
+};
+
+static EelEnumerationEntry default_folder_viewer_enum_entries[] =
+{
+ { "icon_view", N_("Icon View"), CAJA_DEFAULT_FOLDER_VIEWER_ICON_VIEW },
+ { "compact_view", N_("Compact View"), CAJA_DEFAULT_FOLDER_VIEWER_COMPACT_VIEW },
+ { "list_view", N_("List View"), CAJA_DEFAULT_FOLDER_VIEWER_LIST_VIEW }
+};
+
+static EelEnumerationEntry default_icon_view_sort_order_enum_entries[] =
+{
+ { "manually", N_("Manually"), PREFERENCES_SORT_ORDER_MANUALLY },
+ { "--------", "--------" },
+ { "name", N_("By Name"), CAJA_FILE_SORT_BY_DISPLAY_NAME },
+ { "size", N_("By Size"), CAJA_FILE_SORT_BY_SIZE },
+ { "type", N_("By Type"), CAJA_FILE_SORT_BY_TYPE },
+ { "modification_date", N_("By Modification Date"), CAJA_FILE_SORT_BY_MTIME },
+ { "emblems", N_("By Emblems"), CAJA_FILE_SORT_BY_EMBLEMS }
+};
+
+static EelEnumerationEntry standard_font_size_entries[] =
+{
+ { "8", N_("8"), 8 },
+ { "10", N_("10"), 10 },
+ { "12", N_("12"), 12 },
+ { "14", N_("14"), 14 },
+ { "16", N_("16"), 16 },
+ { "18", N_("18"), 18 },
+ { "20", N_("20"), 20 },
+ { "22", N_("22"), 22 },
+ { "24", N_("24"), 24 }
+};
+
+/* These are not translated, because the text is not used in the ui */
+static EelEnumerationEntry date_format_entries[] =
+{
+ { "locale", "Locale Default", CAJA_DATE_FORMAT_LOCALE },
+ { "iso", "ISO Format", CAJA_DATE_FORMAT_ISO },
+ { "informal", "Informal", CAJA_DATE_FORMAT_INFORMAL }
+};
+
+static EelEnumerationEntry new_tab_position_entries[] =
+{
+ { "after_current_tab", "After Current Tab", CAJA_NEW_TAB_POSITION_AFTER_CURRENT_TAB },
+ { "end", "End", CAJA_NEW_TAB_POSITION_END }
+};
+
+/*
+ * A callback which can be used to fetch dynamic fallback values.
+ * For example, values that are dependent on the environment (such as user name)
+ * cannot be specified as constants.
+ */
+typedef gpointer (*PreferencesDefaultValueCallback) (void);
+
+/* A structure that describes a single preference including defaults and visibility. */
+typedef struct
+{
+ const char *name;
+ PreferenceType type;
+ const gpointer fallback_value;
+ PreferencesDefaultValueCallback fallback_callback;
+ GFreeFunc fallback_callback_result_free_function;
+ const char *enumeration_id;
+} PreferenceDefault;
+
+/* The following table defines the default values and user level visibilities of
+ * Caja preferences. Each of these preferences does not necessarily need to
+ * have a UI item in the preferences dialog. To add an item to the preferences
+ * dialog, see the CajaPreferencesItemDescription tables later in this file.
+ *
+ * Field definitions:
+ *
+ * 1. name
+ *
+ * The name of the preference. Usually defined in
+ * caja-global-preferences.h
+ *
+ * 2. type
+ * The preference type. One of:
+ *
+ * PREFERENCE_BOOLEAN
+ * PREFERENCE_INTEGER
+ * PREFERENCE_STRING
+ * PREFERENCE_STRING_ARRAY
+ *
+ * 3. fallback_value
+ * Emergency fallback value if our mateconf schemas are hosed somehow.
+ *
+ * 4. fallback_callback
+ * callback to get dynamic fallback
+ *
+ * 5. fallback_callback_result_free_function
+ * free result of fallback_callback
+ *
+ * 6. enumeration_id
+ * An an enumeration id is a unique string that identifies an enumeration.
+ * If given, an enumeration id can be used to qualify a INTEGER preference.
+ * The preferences dialog widgetry will use this enumeration id to find out
+ * what choices and descriptions of choices to present to the user.
+ */
+
+/* NOTE THAT THE FALLBACKS HERE ARE NOT SUPPOSED TO BE USED -
+ * YOU SHOULD EDIT THE SCHEMAS FILE TO CHANGE DEFAULTS.
+ */
+static const PreferenceDefault preference_defaults[] =
+{
+ {
+ CAJA_PREFERENCES_EXIT_WITH_LAST_WINDOW,
+ PREFERENCE_BOOLEAN,
+ GINT_TO_POINTER (TRUE)
+ },
+ {
+ CAJA_PREFERENCES_SHOW_HIDDEN_FILES,
+ PREFERENCE_BOOLEAN,
+ GINT_TO_POINTER (FALSE)
+ },
+ {
+ CAJA_PREFERENCES_SHOW_BACKUP_FILES,
+ PREFERENCE_BOOLEAN,
+ GINT_TO_POINTER (FALSE)
+ },
+ {
+ CAJA_PREFERENCES_CONFIRM_TRASH,
+ PREFERENCE_BOOLEAN,
+ GINT_TO_POINTER (TRUE)
+ },
+ {
+ CAJA_PREFERENCES_ENABLE_DELETE,
+ PREFERENCE_BOOLEAN,
+ GINT_TO_POINTER (FALSE)
+ },
+ {
+ CAJA_PREFERENCES_SHOW_TEXT_IN_ICONS,
+ PREFERENCE_STRING,
+ "local_only",
+ NULL, NULL,
+ "speed_tradeoff"
+ },
+ /* Don't show remote directory item counts by default
+ * because computing them can be annoyingly slow, especially
+ * for FTP. If we make this fast enough for FTP in particular,
+ * we should change this default to ALWAYS.
+ */
+ {
+ CAJA_PREFERENCES_SHOW_DIRECTORY_ITEM_COUNTS,
+ PREFERENCE_STRING,
+ "local_only",
+ NULL, NULL,
+ "speed_tradeoff"
+ },
+ {
+ CAJA_PREFERENCES_CLICK_POLICY,
+ PREFERENCE_STRING,
+ "double",
+ NULL, NULL,
+ "click_policy"
+ },
+ {
+ CAJA_PREFERENCES_EXECUTABLE_TEXT_ACTIVATION,
+ PREFERENCE_STRING,
+ "ask",
+ NULL, NULL,
+ "executable_text_activation"
+ },
+ {
+ CAJA_PREFERENCES_INSTALL_MIME_ACTIVATION,
+ PREFERENCE_BOOLEAN,
+ GINT_TO_POINTER (TRUE)
+ },
+ {
+ CAJA_PREFERENCES_THEME,
+ PREFERENCE_STRING,
+ "default"
+ },
+ {
+ CAJA_PREFERENCES_SHOW_IMAGE_FILE_THUMBNAILS,
+ PREFERENCE_STRING,
+ "local_only",
+ NULL, NULL,
+ "speed_tradeoff"
+ },
+ {
+ CAJA_PREFERENCES_IMAGE_FILE_THUMBNAIL_LIMIT,
+ PREFERENCE_INTEGER,
+ GINT_TO_POINTER(10485760),
+ NULL, NULL,
+ "file_size"
+ },
+ {
+ CAJA_PREFERENCES_PREVIEW_SOUND,
+ PREFERENCE_STRING,
+ "local_only",
+ NULL, NULL,
+ "speed_tradeoff"
+ },
+ {
+ CAJA_PREFERENCES_SHOW_ADVANCED_PERMISSIONS,
+ PREFERENCE_BOOLEAN,
+ GINT_TO_POINTER (FALSE)
+ },
+ {
+ CAJA_PREFERENCES_SORT_DIRECTORIES_FIRST,
+ PREFERENCE_BOOLEAN,
+ GINT_TO_POINTER (FALSE)
+ },
+ {
+ CAJA_PREFERENCES_SHOW_DESKTOP,
+ PREFERENCE_BOOLEAN,
+ GINT_TO_POINTER (TRUE)
+ },
+ {
+ CAJA_PREFERENCES_DESKTOP_IS_HOME_DIR,
+ PREFERENCE_BOOLEAN,
+ GINT_TO_POINTER (FALSE)
+ },
+ {
+ CAJA_PREFERENCES_SEARCH_BAR_TYPE,
+ PREFERENCE_STRING,
+ "search_by_text",
+ NULL, NULL,
+ "search_bar_type"
+ },
+ {
+ CAJA_PREFERENCES_ICON_VIEW_CAPTIONS,
+ PREFERENCE_STRING_ARRAY,
+ "size,date_modified,type",
+ NULL, NULL,
+ NULL
+ },
+ {
+ CAJA_PREFERENCES_SIDEBAR_WIDTH,
+ PREFERENCE_INTEGER,
+ GINT_TO_POINTER (148)
+ },
+ {
+ CAJA_PREFERENCES_ALWAYS_USE_BROWSER,
+ PREFERENCE_BOOLEAN,
+ GINT_TO_POINTER (TRUE)
+ },
+ {
+ CAJA_PREFERENCES_NEW_TAB_POSITION,
+ PREFERENCE_STRING,
+ "after_current_tab",
+ NULL, NULL,
+ "new_tab_position"
+ },
+ {
+ CAJA_PREFERENCES_START_WITH_TOOLBAR,
+ PREFERENCE_BOOLEAN,
+ GINT_TO_POINTER (TRUE)
+ },
+ {
+ CAJA_PREFERENCES_START_WITH_LOCATION_BAR,
+ PREFERENCE_BOOLEAN,
+ GINT_TO_POINTER (TRUE)
+ },
+ {
+ CAJA_PREFERENCES_ALWAYS_USE_LOCATION_ENTRY,
+ PREFERENCE_BOOLEAN,
+ GINT_TO_POINTER (FALSE)
+ },
+ {
+ CAJA_PREFERENCES_START_WITH_STATUS_BAR,
+ PREFERENCE_BOOLEAN,
+ GINT_TO_POINTER (TRUE)
+ },
+ {
+ CAJA_PREFERENCES_START_WITH_SIDEBAR,
+ PREFERENCE_BOOLEAN,
+ GINT_TO_POINTER (TRUE)
+ },
+ {
+ CAJA_PREFERENCES_NAVIGATION_WINDOW_SAVED_GEOMETRY,
+ PREFERENCE_STRING,
+ ""
+ },
+ {
+ CAJA_PREFERENCES_NAVIGATION_WINDOW_MAXIMIZED,
+ PREFERENCE_BOOLEAN,
+ GINT_TO_POINTER (FALSE)
+ },
+ {
+ CAJA_PREFERENCES_TREE_SHOW_ONLY_DIRECTORIES,
+ PREFERENCE_BOOLEAN,
+ GINT_TO_POINTER (TRUE)
+ },
+ {
+ CAJA_PREFERENCES_DATE_FORMAT,
+ PREFERENCE_STRING,
+ "locale",
+ NULL, NULL,
+ "date_format"
+ },
+ {
+ CAJA_PREFERENCES_DEFAULT_FOLDER_VIEWER,
+ PREFERENCE_INTEGER,
+ GINT_TO_POINTER (CAJA_DEFAULT_FOLDER_VIEWER_ICON_VIEW),
+ NULL, NULL,
+ "default_folder_viewer"
+ },
+ {
+ CAJA_PREFERENCES_DESKTOP_FONT,
+ PREFERENCE_STRING,
+ NULL, default_font_callback, g_free
+ },
+
+ /* Icon View Default Preferences */
+ {
+ CAJA_PREFERENCES_ICON_VIEW_DEFAULT_SORT_ORDER,
+ PREFERENCE_STRING,
+ "name",
+ NULL, NULL,
+ "default_icon_view_sort_order"
+ },
+ {
+ CAJA_PREFERENCES_ICON_VIEW_DEFAULT_SORT_ORDER_OR_MANUAL_LAYOUT,
+ PREFERENCE_STRING,
+ "name",
+ NULL, NULL,
+ "default_icon_view_sort_order"
+ },
+ {
+ CAJA_PREFERENCES_ICON_VIEW_DEFAULT_SORT_IN_REVERSE_ORDER,
+ PREFERENCE_BOOLEAN,
+ GINT_TO_POINTER (FALSE)
+ },
+ {
+ CAJA_PREFERENCES_ICON_VIEW_DEFAULT_USE_TIGHTER_LAYOUT,
+ PREFERENCE_BOOLEAN,
+ GINT_TO_POINTER (FALSE)
+ },
+ {
+ CAJA_PREFERENCES_ICON_VIEW_DEFAULT_USE_MANUAL_LAYOUT,
+ PREFERENCE_BOOLEAN,
+ GINT_TO_POINTER (FALSE)
+ },
+ {
+ CAJA_PREFERENCES_ICON_VIEW_DEFAULT_ZOOM_LEVEL,
+ PREFERENCE_STRING,
+ "standard",
+ NULL, NULL,
+ "default_zoom_level"
+ },
+ {
+ CAJA_PREFERENCES_ICON_VIEW_THUMBNAIL_SIZE,
+ PREFERENCE_INTEGER,
+ GINT_TO_POINTER (96)
+ },
+ {
+ CAJA_PREFERENCES_ICON_VIEW_TEXT_ELLIPSIS_LIMIT,
+ PREFERENCE_STRING_ARRAY,
+ "3",
+ NULL,NULL,
+ NULL,
+ },
+ /* Compact Icon View Default Preferences */
+ {
+ CAJA_PREFERENCES_COMPACT_VIEW_DEFAULT_ZOOM_LEVEL,
+ PREFERENCE_STRING,
+ "standard",
+ NULL, NULL,
+ "default_zoom_level"
+ },
+
+ /* List View Default Preferences */
+ {
+ CAJA_PREFERENCES_LIST_VIEW_DEFAULT_SORT_ORDER,
+ PREFERENCE_STRING,
+ "name",
+ NULL, NULL,
+ NULL,
+ },
+ {
+ CAJA_PREFERENCES_LIST_VIEW_DEFAULT_SORT_IN_REVERSE_ORDER,
+ PREFERENCE_BOOLEAN,
+ GINT_TO_POINTER (FALSE)
+ },
+ {
+ CAJA_PREFERENCES_LIST_VIEW_DEFAULT_ZOOM_LEVEL,
+ PREFERENCE_STRING,
+ "smaller",
+ NULL, NULL,
+ "default_zoom_level"
+ },
+
+ /* Desktop Preferences */
+ {
+ CAJA_PREFERENCES_DESKTOP_HOME_VISIBLE,
+ PREFERENCE_BOOLEAN,
+ GINT_TO_POINTER (FALSE)
+ },
+
+ {
+ CAJA_PREFERENCES_DESKTOP_HOME_NAME,
+ PREFERENCE_STRING,
+ NULL,
+ default_home_link_name, g_free,
+ },
+
+ {
+ CAJA_PREFERENCES_DESKTOP_COMPUTER_VISIBLE,
+ PREFERENCE_BOOLEAN,
+ GINT_TO_POINTER (TRUE)
+ },
+
+ {
+ CAJA_PREFERENCES_DESKTOP_COMPUTER_NAME,
+ PREFERENCE_STRING,
+ NULL,
+ default_computer_link_name, g_free,
+ },
+
+ {
+ CAJA_PREFERENCES_DESKTOP_TRASH_VISIBLE,
+ PREFERENCE_BOOLEAN,
+ GINT_TO_POINTER (TRUE)
+ },
+
+ {
+ CAJA_PREFERENCES_DESKTOP_TRASH_NAME,
+ PREFERENCE_STRING,
+ NULL,
+ default_trash_link_name, g_free,
+ },
+
+ {
+ CAJA_PREFERENCES_DESKTOP_VOLUMES_VISIBLE,
+ PREFERENCE_BOOLEAN,
+ GINT_TO_POINTER (TRUE)
+ },
+
+ {
+ CAJA_PREFERENCES_DESKTOP_NETWORK_VISIBLE,
+ PREFERENCE_BOOLEAN,
+ GINT_TO_POINTER (FALSE)
+ },
+
+ {
+ CAJA_PREFERENCES_DESKTOP_NETWORK_NAME,
+ PREFERENCE_STRING,
+ NULL,
+ default_network_link_name, g_free,
+ },
+
+ {
+ CAJA_PREFERENCES_MEDIA_AUTOMOUNT,
+ PREFERENCE_BOOLEAN,
+ GINT_TO_POINTER (TRUE)
+ },
+ {
+ CAJA_PREFERENCES_MEDIA_AUTOMOUNT_OPEN,
+ PREFERENCE_BOOLEAN,
+ GINT_TO_POINTER (TRUE)
+ },
+ {
+ CAJA_PREFERENCES_MEDIA_AUTORUN_NEVER,
+ PREFERENCE_BOOLEAN,
+ GINT_TO_POINTER (FALSE)
+ },
+ {
+ CAJA_PREFERENCES_MEDIA_AUTORUN_X_CONTENT_START_APP,
+ PREFERENCE_STRING_ARRAY,
+ "", NULL, NULL, NULL
+ },
+ {
+ CAJA_PREFERENCES_MEDIA_AUTORUN_X_CONTENT_IGNORE,
+ PREFERENCE_STRING_ARRAY,
+ "", NULL, NULL, NULL
+ },
+ {
+ CAJA_PREFERENCES_MEDIA_AUTORUN_X_CONTENT_OPEN_FOLDER,
+ PREFERENCE_STRING_ARRAY,
+ "", NULL, NULL, NULL
+ },
+ {
+ CAJA_PREFERENCES_DESKTOP_TEXT_ELLIPSIS_LIMIT,
+ PREFERENCE_INTEGER,
+ GINT_TO_POINTER (3)
+ },
+ {
+ CAJA_PREFERENCES_MOUSE_USE_EXTRA_BUTTONS,
+ PREFERENCE_BOOLEAN,
+ GINT_TO_POINTER (TRUE)
+ },
+ {
+ CAJA_PREFERENCES_MOUSE_FORWARD_BUTTON,
+ PREFERENCE_INTEGER,
+ GINT_TO_POINTER (9)
+ },
+ {
+ CAJA_PREFERENCES_MOUSE_BACK_BUTTON,
+ PREFERENCE_INTEGER,
+ GINT_TO_POINTER (8)
+ },
+ {
+ CAJA_PREFERENCES_LOCKDOWN_COMMAND_LINE,
+ PREFERENCE_BOOLEAN,
+ GINT_TO_POINTER (FALSE)
+ },
+ { NULL }
+};
+
+static gpointer
+default_home_link_name (void)
+{
+ /* Note to translators: If it's hard to compose a good home
+ * icon name from the user name, you can use a string without
+ * an "%s" here, in which case the home icon name will not
+ * include the user's name, which should be fine. To avoid a
+ * warning, put "%.0s" somewhere in the string, which will
+ * match the user name string passed by the C code, but not
+ * put the user name in the final string.
+ */
+ return g_strdup_printf (_("%s's Home"), g_get_user_name ());
+}
+
+static gpointer
+default_computer_link_name (void)
+{
+ return g_strdup (_("Computer"));
+}
+
+static gpointer
+default_trash_link_name (void)
+{
+ return g_strdup (_("Trash"));
+}
+
+static gpointer
+default_network_link_name (void)
+{
+ return g_strdup (_("Network Servers"));
+}
+
+
+
+/**
+ * global_preferences_register_enumerations
+ *
+ * Register enumerations for INTEGER preferences that need them.
+ *
+ * This function needs to be called before the preferences dialog
+ * panes are populated, as they use the registered information to
+ * create enumeration item widgets.
+ */
+static void
+global_preferences_register_enumerations (void)
+{
+ guint i;
+
+ /* Register the enumerations.
+ * These enumerations are used in the preferences dialog to
+ * populate widgets and route preferences changes between the
+ * storage (MateConf) and the displayed values.
+ */
+ eel_enumeration_register ("click_policy",
+ click_policy_enum_entries,
+ G_N_ELEMENTS (click_policy_enum_entries));
+ eel_enumeration_register ("default_folder_viewer",
+ default_folder_viewer_enum_entries,
+ G_N_ELEMENTS (default_folder_viewer_enum_entries));
+ eel_enumeration_register ("default_icon_view_sort_order",
+ default_icon_view_sort_order_enum_entries,
+ G_N_ELEMENTS (default_icon_view_sort_order_enum_entries));
+ eel_enumeration_register ("default_zoom_level",
+ default_zoom_level_enum_entries,
+ G_N_ELEMENTS (default_zoom_level_enum_entries));
+ eel_enumeration_register ("executable_text_activation",
+ executable_text_activation_enum_entries,
+ G_N_ELEMENTS (executable_text_activation_enum_entries));
+ eel_enumeration_register ("file_size",
+ file_size_enum_entries,
+ G_N_ELEMENTS (file_size_enum_entries));
+ eel_enumeration_register ("search_bar_type",
+ search_bar_type_enum_entries,
+ G_N_ELEMENTS (search_bar_type_enum_entries));
+ eel_enumeration_register ("speed_tradeoff",
+ speed_tradeoff_enum_entries,
+ G_N_ELEMENTS (speed_tradeoff_enum_entries));
+ eel_enumeration_register ("standard_font_size",
+ standard_font_size_entries,
+ G_N_ELEMENTS (standard_font_size_entries));
+ eel_enumeration_register ("date_format",
+ date_format_entries,
+ G_N_ELEMENTS (date_format_entries));
+ eel_enumeration_register ("new_tab_position",
+ new_tab_position_entries,
+ G_N_ELEMENTS (new_tab_position_entries));
+
+ /* Set the enumeration ids for preferences that need them */
+ for (i = 0; preference_defaults[i].name != NULL; i++)
+ {
+ if (eel_strlen (preference_defaults[i].enumeration_id) > 0)
+ {
+ g_assert (preference_defaults[i].type == PREFERENCE_STRING
+ || preference_defaults[i].type == PREFERENCE_STRING_ARRAY
+ || preference_defaults[i].type == PREFERENCE_INTEGER);
+ eel_preferences_set_enumeration_id (preference_defaults[i].name,
+ preference_defaults[i].enumeration_id);
+ }
+ }
+}
+
+static void
+global_preferences_install_one_default (const char *preference_name,
+ PreferenceType preference_type,
+ const PreferenceDefault *preference_default)
+{
+ gpointer value = NULL;
+ char **string_array_value;
+
+ g_return_if_fail (preference_name != NULL);
+ g_return_if_fail (preference_type >= PREFERENCE_BOOLEAN);
+ g_return_if_fail (preference_type <= PREFERENCE_STRING_ARRAY);
+ g_return_if_fail (preference_default != NULL);
+
+ /* If a callback is given, use that to fetch the default value */
+ if (preference_default->fallback_callback != NULL)
+ {
+ value = (* preference_default->fallback_callback) ();
+ }
+ else
+ {
+ value = preference_default->fallback_value;
+ }
+
+ switch (preference_type)
+ {
+ case PREFERENCE_BOOLEAN:
+ eel_preferences_set_emergency_fallback_boolean (preference_name,
+ GPOINTER_TO_INT (value));
+ break;
+
+ case PREFERENCE_INTEGER:
+ eel_preferences_set_emergency_fallback_integer (preference_name,
+
+ GPOINTER_TO_INT (value));
+ break;
+
+ case PREFERENCE_STRING:
+ eel_preferences_set_emergency_fallback_string (preference_name,
+ value);
+ break;
+
+ case PREFERENCE_STRING_ARRAY:
+ string_array_value = g_strsplit (value,
+ STRING_ARRAY_DEFAULT_TOKENS_DELIMETER,
+ -1);
+ eel_preferences_set_emergency_fallback_string_array (preference_name,
+ string_array_value);
+ g_strfreev (string_array_value);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ /* Free the dynamic default value if needed */
+ if (preference_default->fallback_callback != NULL
+ && preference_default->fallback_callback_result_free_function != NULL)
+ {
+ (* preference_default->fallback_callback_result_free_function) (value);
+ }
+}
+
+/**
+ * global_preferences_install_defaults
+ *
+ * Install defaults and visibilities.
+ *
+ * Most of the defaults come from the preference_defaults table above.
+ *
+ * Many preferences require their defaults to be computed, and so there
+ * are special functions to install those.
+ */
+static void
+global_preferences_install_defaults (void)
+{
+ guint i;
+
+ for (i = 0; preference_defaults[i].name != NULL; i++)
+ {
+ global_preferences_install_one_default (preference_defaults[i].name,
+ preference_defaults[i].type,
+ &preference_defaults[i]);
+ }
+}
+
+static gpointer
+default_font_callback (void)
+{
+ return g_strdup ("sans 12");
+}
+
+/*
+ * Public functions
+ */
+char *
+caja_global_preferences_get_default_folder_viewer_preference_as_iid (void)
+{
+ int preference_value;
+ const char *viewer_iid;
+
+ preference_value =
+ eel_preferences_get_enum (CAJA_PREFERENCES_DEFAULT_FOLDER_VIEWER);
+
+ if (preference_value == CAJA_DEFAULT_FOLDER_VIEWER_LIST_VIEW)
+ {
+ viewer_iid = CAJA_LIST_VIEW_IID;
+ }
+ else if (preference_value == CAJA_DEFAULT_FOLDER_VIEWER_COMPACT_VIEW)
+ {
+ viewer_iid = CAJA_COMPACT_VIEW_IID;
+ }
+ else
+ {
+ viewer_iid = CAJA_ICON_VIEW_IID;
+ }
+
+ return g_strdup (viewer_iid);
+}
+
+/* The icon view uses 2 variables to store the sort order and
+ * whether to use manual layout. However, the UI for these
+ * preferences presensts them as single option menu. So we
+ * use the following preference as a proxy for the other two.
+ * In caja-global-preferences.c we install callbacks for
+ * the proxy preference and update the other 2 when it changes
+ */
+static void
+default_icon_view_sort_order_or_manual_layout_changed_callback (gpointer callback_data)
+{
+ int default_sort_order_or_manual_layout;
+ int default_sort_order;
+
+ default_sort_order_or_manual_layout =
+ eel_preferences_get_enum (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_SORT_ORDER_OR_MANUAL_LAYOUT);
+
+ eel_preferences_set_boolean (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_USE_MANUAL_LAYOUT,
+ default_sort_order_or_manual_layout == PREFERENCES_SORT_ORDER_MANUALLY);
+
+ if (default_sort_order_or_manual_layout != PREFERENCES_SORT_ORDER_MANUALLY)
+ {
+ default_sort_order = default_sort_order_or_manual_layout;
+
+ g_return_if_fail (default_sort_order >= CAJA_FILE_SORT_BY_DISPLAY_NAME);
+ g_return_if_fail (default_sort_order <= CAJA_FILE_SORT_BY_EMBLEMS);
+
+ eel_preferences_set_enum (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_SORT_ORDER,
+ default_sort_order);
+ }
+}
+
+void
+caja_global_preferences_init (void)
+{
+ static gboolean initialized = FALSE;
+ int i;
+
+ if (initialized)
+ {
+ return;
+ }
+
+ initialized = TRUE;
+
+ eel_preferences_init ("/apps/caja");
+
+ /* Install defaults */
+ global_preferences_install_defaults ();
+
+ global_preferences_register_enumerations ();
+
+ /* Add monitors for any other MateConf paths we have keys in */
+ for (i=0; EXTRA_MONITOR_PATHS[i] != NULL; i++)
+ {
+ eel_preferences_monitor_directory (EXTRA_MONITOR_PATHS[i]);
+ }
+
+ /* Set up storage for values accessed in this file */
+ eel_preferences_add_callback (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_SORT_ORDER_OR_MANUAL_LAYOUT,
+ default_icon_view_sort_order_or_manual_layout_changed_callback,
+ NULL);
+
+ /* Preload everything in a big batch */
+ eel_mateconf_preload_cache ("/apps/caja/preferences",
+ MATECONF_CLIENT_PRELOAD_ONELEVEL);
+ eel_mateconf_preload_cache ("/desktop/mate/file_views",
+ MATECONF_CLIENT_PRELOAD_ONELEVEL);
+ eel_mateconf_preload_cache ("/desktop/mate/background",
+ MATECONF_CLIENT_PRELOAD_ONELEVEL);
+ eel_mateconf_preload_cache ("/desktop/mate/lockdown",
+ MATECONF_CLIENT_PRELOAD_ONELEVEL);
+
+ /* These are always needed for the desktop */
+ eel_mateconf_preload_cache ("/apps/caja/desktop",
+ MATECONF_CLIENT_PRELOAD_ONELEVEL);
+ eel_mateconf_preload_cache ("/apps/caja/icon_view",
+ MATECONF_CLIENT_PRELOAD_ONELEVEL);
+ eel_mateconf_preload_cache ("/apps/caja/desktop-metadata",
+ MATECONF_CLIENT_PRELOAD_RECURSIVE);
+}
diff --git a/libcaja-private/caja-global-preferences.h b/libcaja-private/caja-global-preferences.h
new file mode 100644
index 00000000..be9d57a3
--- /dev/null
+++ b/libcaja-private/caja-global-preferences.h
@@ -0,0 +1,242 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-global-preferences.h - Caja 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,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Ramiro Estrugo <[email protected]>
+*/
+
+#ifndef CAJA_GLOBAL_PREFERENCES_H
+#define CAJA_GLOBAL_PREFERENCES_H
+
+#include <eel/eel-preferences.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+ /* Whether exit when last window destroyed */
+#define CAJA_PREFERENCES_EXIT_WITH_LAST_WINDOW "preferences/exit_with_last_window"
+
+ /* Which theme is active */
+#define CAJA_PREFERENCES_THEME "/desktop/mate/file_views/icon_theme"
+
+ /* Desktop Background options */
+#define CAJA_PREFERENCES_BACKGROUND_SET "preferences/background_set"
+#define CAJA_PREFERENCES_BACKGROUND_COLOR "preferences/background_color"
+#define CAJA_PREFERENCES_BACKGROUND_FILENAME "preferences/background_filename"
+
+ /* Side Pane Background options */
+#define CAJA_PREFERENCES_SIDE_PANE_BACKGROUND_SET "preferences/side_pane_background_set"
+#define CAJA_PREFERENCES_SIDE_PANE_BACKGROUND_COLOR "preferences/side_pane_background_color"
+#define CAJA_PREFERENCES_SIDE_PANE_BACKGROUND_FILENAME "preferences/side_pane_background_filename"
+
+ /* How wide the sidebar is (or how wide it will be when expanded) */
+#define CAJA_PREFERENCES_SIDEBAR_WIDTH "preferences/sidebar_width"
+
+ /* Automount options */
+#define CAJA_PREFERENCES_MEDIA_AUTOMOUNT "preferences/media_automount"
+#define CAJA_PREFERENCES_MEDIA_AUTOMOUNT_OPEN "preferences/media_automount_open"
+
+ /* Autorun options */
+#define CAJA_PREFERENCES_MEDIA_AUTORUN_NEVER "preferences/media_autorun_never"
+#define CAJA_PREFERENCES_MEDIA_AUTORUN_X_CONTENT_START_APP "preferences/media_autorun_x_content_start_app"
+#define CAJA_PREFERENCES_MEDIA_AUTORUN_X_CONTENT_IGNORE "preferences/media_autorun_x_content_ignore"
+#define CAJA_PREFERENCES_MEDIA_AUTORUN_X_CONTENT_OPEN_FOLDER "preferences/media_autorun_x_content_open_folder"
+
+ /* Trash options */
+#define CAJA_PREFERENCES_CONFIRM_TRASH "preferences/confirm_trash"
+#define CAJA_PREFERENCES_ENABLE_DELETE "preferences/enable_delete"
+
+ /* Desktop options */
+#define CAJA_PREFERENCES_SHOW_DESKTOP "preferences/show_desktop"
+#define CAJA_PREFERENCES_DESKTOP_IS_HOME_DIR "preferences/desktop_is_home_dir"
+#define CAJA_PREFERENCES_DESKTOP_FONT "preferences/desktop_font"
+
+ /* Display */
+#define CAJA_PREFERENCES_SHOW_HIDDEN_FILES "/desktop/mate/file_views/show_hidden_files"
+#define CAJA_PREFERENCES_SHOW_BACKUP_FILES "/desktop/mate/file_views/show_backup_files"
+#define CAJA_PREFERENCES_SHOW_ADVANCED_PERMISSIONS "preferences/show_advanced_permissions"
+#define CAJA_PREFERENCES_DATE_FORMAT "preferences/date_format"
+
+ /* Mouse */
+#define CAJA_PREFERENCES_MOUSE_USE_EXTRA_BUTTONS "preferences/mouse_use_extra_buttons"
+#define CAJA_PREFERENCES_MOUSE_FORWARD_BUTTON "preferences/mouse_forward_button"
+#define CAJA_PREFERENCES_MOUSE_BACK_BUTTON "preferences/mouse_back_button"
+
+ typedef enum
+ {
+ CAJA_DATE_FORMAT_LOCALE,
+ CAJA_DATE_FORMAT_ISO,
+ CAJA_DATE_FORMAT_INFORMAL
+ }
+ CajaDateFormat;
+
+ typedef enum
+ {
+ CAJA_NEW_TAB_POSITION_AFTER_CURRENT_TAB,
+ CAJA_NEW_TAB_POSITION_END,
+ } CajaNewTabPosition;
+
+ /* Sidebar panels */
+#define CAJA_PREFERENCES_TREE_SHOW_ONLY_DIRECTORIES "sidebar_panels/tree/show_only_directories"
+
+ /* Single/Double click preference */
+#define CAJA_PREFERENCES_CLICK_POLICY "preferences/click_policy"
+
+ /* Activating executable text files */
+#define CAJA_PREFERENCES_EXECUTABLE_TEXT_ACTIVATION "preferences/executable_text_activation"
+
+ /* Installing new packages when unknown mime type activated */
+#define CAJA_PREFERENCES_INSTALL_MIME_ACTIVATION "preferences/install_mime_activation"
+
+ /* Spatial or browser mode */
+#define CAJA_PREFERENCES_ALWAYS_USE_BROWSER "preferences/always_use_browser"
+#define CAJA_PREFERENCES_NEW_TAB_POSITION "preferences/tabs_open_position"
+
+ /* Which views should be displayed for new windows */
+#define CAJA_PREFERENCES_START_WITH_LOCATION_BAR "preferences/start_with_location_bar"
+#define CAJA_PREFERENCES_ALWAYS_USE_LOCATION_ENTRY "preferences/always_use_location_entry"
+#define CAJA_PREFERENCES_START_WITH_STATUS_BAR "preferences/start_with_status_bar"
+#define CAJA_PREFERENCES_START_WITH_SIDEBAR "preferences/start_with_sidebar"
+#define CAJA_PREFERENCES_START_WITH_TOOLBAR "preferences/start_with_toolbar"
+#define CAJA_PREFERENCES_SIDE_PANE_VIEW "preferences/side_pane_view"
+#define CAJA_PREFERENCES_NAVIGATION_WINDOW_SAVED_GEOMETRY "preferences/navigation_window_saved_geometry"
+#define CAJA_PREFERENCES_NAVIGATION_WINDOW_MAXIMIZED "preferences/navigation_window_saved_maximized"
+
+ /* Sorting order */
+#define CAJA_PREFERENCES_SORT_DIRECTORIES_FIRST "preferences/sort_directories_first"
+
+ /* The default folder viewer - one of the two enums below */
+#define CAJA_PREFERENCES_DEFAULT_FOLDER_VIEWER "preferences/default_folder_viewer"
+
+ enum
+ {
+ CAJA_DEFAULT_FOLDER_VIEWER_ICON_VIEW,
+ CAJA_DEFAULT_FOLDER_VIEWER_COMPACT_VIEW,
+ CAJA_DEFAULT_FOLDER_VIEWER_LIST_VIEW,
+ CAJA_DEFAULT_FOLDER_VIEWER_OTHER
+ };
+
+ /* These IIDs are used by the preferences code and in caja-application.c */
+#define CAJA_ICON_VIEW_IID "OAFIID:Caja_File_Manager_Icon_View"
+#define CAJA_COMPACT_VIEW_IID "OAFIID:Caja_File_Manager_Compact_View"
+#define CAJA_LIST_VIEW_IID "OAFIID:Caja_File_Manager_List_View"
+
+
+ /* Icon View */
+#define CAJA_PREFERENCES_ICON_VIEW_DEFAULT_SORT_IN_REVERSE_ORDER "icon_view/default_sort_in_reverse_order"
+#define CAJA_PREFERENCES_ICON_VIEW_DEFAULT_SORT_ORDER "icon_view/default_sort_order"
+#define CAJA_PREFERENCES_ICON_VIEW_DEFAULT_USE_TIGHTER_LAYOUT "icon_view/default_use_tighter_layout"
+#define CAJA_PREFERENCES_ICON_VIEW_DEFAULT_ZOOM_LEVEL "icon_view/default_zoom_level"
+#define CAJA_PREFERENCES_ICON_VIEW_DEFAULT_USE_MANUAL_LAYOUT "icon_view/default_use_manual_layout"
+
+#define CAJA_PREFERENCES_ICON_VIEW_LABELS_BESIDE_ICONS "icon_view/labels_beside_icons"
+
+
+ /* The icon view uses 2 variables to store the sort order and
+ * whether to use manual layout. However, the UI for these
+ * preferences presensts them as single option menu. So we
+ * use the following preference as a proxy for the other two.
+ * In caja-global-preferences.c we install callbacks for
+ * the proxy preference and update the other 2 when it changes
+ */
+#define CAJA_PREFERENCES_ICON_VIEW_DEFAULT_SORT_ORDER_OR_MANUAL_LAYOUT "icon_view/default_sort_order_or_manual_layout"
+
+ /* Which text attributes appear beneath icon names */
+#define CAJA_PREFERENCES_ICON_VIEW_CAPTIONS "icon_view/captions"
+
+ /* The default size for thumbnail icons */
+#define CAJA_PREFERENCES_ICON_VIEW_THUMBNAIL_SIZE "icon_view/thumbnail_size"
+
+ /* ellipsization preferences */
+#define CAJA_PREFERENCES_ICON_VIEW_TEXT_ELLIPSIS_LIMIT "icon_view/text_ellipsis_limit"
+#define CAJA_PREFERENCES_DESKTOP_TEXT_ELLIPSIS_LIMIT "desktop/text_ellipsis_limit"
+
+ /* Compact View */
+#define CAJA_PREFERENCES_COMPACT_VIEW_DEFAULT_ZOOM_LEVEL "compact_view/default_zoom_level"
+#define CAJA_PREFERENCES_COMPACT_VIEW_ALL_COLUMNS_SAME_WIDTH "compact_view/all_columns_have_same_width"
+
+ /* List View */
+#define CAJA_PREFERENCES_LIST_VIEW_DEFAULT_SORT_IN_REVERSE_ORDER "list_view/default_sort_in_reverse_order"
+#define CAJA_PREFERENCES_LIST_VIEW_DEFAULT_SORT_ORDER "list_view/default_sort_order"
+#define CAJA_PREFERENCES_LIST_VIEW_DEFAULT_ZOOM_LEVEL "list_view/default_zoom_level"
+#define CAJA_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS "list_view/default_visible_columns"
+#define CAJA_PREFERENCES_LIST_VIEW_DEFAULT_COLUMN_ORDER "list_view/default_column_order"
+
+ /* News panel */
+#define CAJA_PREFERENCES_NEWS_MAX_ITEMS "news/max_items"
+#define CAJA_PREFERENCES_NEWS_UPDATE_INTERVAL "news/update_interval"
+
+ /* File Indexing */
+#define CAJA_PREFERENCES_SEARCH_BAR_TYPE "preferences/search_bar_type"
+
+ enum
+ {
+ CAJA_CLICK_POLICY_SINGLE,
+ CAJA_CLICK_POLICY_DOUBLE
+ };
+
+ enum
+ {
+ CAJA_EXECUTABLE_TEXT_LAUNCH,
+ CAJA_EXECUTABLE_TEXT_DISPLAY,
+ CAJA_EXECUTABLE_TEXT_ASK
+ };
+
+ typedef enum
+ {
+ CAJA_SPEED_TRADEOFF_ALWAYS,
+ CAJA_SPEED_TRADEOFF_LOCAL_ONLY,
+ CAJA_SPEED_TRADEOFF_NEVER
+ } CajaSpeedTradeoffValue;
+
+#define CAJA_PREFERENCES_SHOW_TEXT_IN_ICONS "preferences/show_icon_text"
+#define CAJA_PREFERENCES_SHOW_DIRECTORY_ITEM_COUNTS "preferences/show_directory_item_counts"
+#define CAJA_PREFERENCES_SHOW_IMAGE_FILE_THUMBNAILS "preferences/show_image_thumbnails"
+#define CAJA_PREFERENCES_IMAGE_FILE_THUMBNAIL_LIMIT "preferences/thumbnail_limit"
+#define CAJA_PREFERENCES_PREVIEW_SOUND "preferences/preview_sound"
+
+ typedef enum
+ {
+ CAJA_COMPLEX_SEARCH_BAR,
+ CAJA_SIMPLE_SEARCH_BAR
+ } CajaSearchBarMode;
+
+#define CAJA_PREFERENCES_DESKTOP_HOME_VISIBLE "desktop/home_icon_visible"
+#define CAJA_PREFERENCES_DESKTOP_HOME_NAME "desktop/home_icon_name"
+#define CAJA_PREFERENCES_DESKTOP_COMPUTER_VISIBLE "desktop/computer_icon_visible"
+#define CAJA_PREFERENCES_DESKTOP_COMPUTER_NAME "desktop/computer_icon_name"
+#define CAJA_PREFERENCES_DESKTOP_TRASH_VISIBLE "desktop/trash_icon_visible"
+#define CAJA_PREFERENCES_DESKTOP_TRASH_NAME "desktop/trash_icon_name"
+#define CAJA_PREFERENCES_DESKTOP_VOLUMES_VISIBLE "desktop/volumes_visible"
+#define CAJA_PREFERENCES_DESKTOP_NETWORK_VISIBLE "desktop/network_icon_visible"
+#define CAJA_PREFERENCES_DESKTOP_NETWORK_NAME "desktop/network_icon_name"
+
+ /* Lockdown */
+#define CAJA_PREFERENCES_LOCKDOWN_COMMAND_LINE "/desktop/mate/lockdown/disable_command_line"
+
+ void caja_global_preferences_init (void);
+ char *caja_global_preferences_get_default_folder_viewer_preference_as_iid (void);
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CAJA_GLOBAL_PREFERENCES_H */
diff --git a/libcaja-private/caja-horizontal-splitter.c b/libcaja-private/caja-horizontal-splitter.c
new file mode 100644
index 00000000..e31d8a6a
--- /dev/null
+++ b/libcaja-private/caja-horizontal-splitter.c
@@ -0,0 +1,307 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-horizontal-splitter.c - A horizontal splitter with a semi gradient look
+
+ Copyright (C) 1999, 2000 Eazel, Inc.
+
+ The Mate 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 Mate 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 Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Ramiro Estrugo <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-horizontal-splitter.h"
+
+#include <eel/eel-gtk-macros.h>
+#include <stdlib.h>
+
+struct CajaHorizontalSplitterDetails
+{
+ guint32 press_time;
+ int press_position;
+ int saved_size;
+};
+
+#define CLOSED_THRESHOLD 4
+#define NOMINAL_SIZE 148
+#define SPLITTER_CLICK_SLOP 4
+#define SPLITTER_CLICK_TIMEOUT 400
+
+static void caja_horizontal_splitter_class_init (CajaHorizontalSplitterClass *horizontal_splitter_class);
+static void caja_horizontal_splitter_init (CajaHorizontalSplitter *horizontal_splitter);
+
+EEL_CLASS_BOILERPLATE (CajaHorizontalSplitter,
+ caja_horizontal_splitter,
+ GTK_TYPE_HPANED)
+
+static void
+caja_horizontal_splitter_init (CajaHorizontalSplitter *horizontal_splitter)
+{
+ horizontal_splitter->details = g_new0 (CajaHorizontalSplitterDetails, 1);
+}
+
+static void
+caja_horizontal_splitter_finalize (GObject *object)
+{
+ CajaHorizontalSplitter *horizontal_splitter;
+
+ horizontal_splitter = CAJA_HORIZONTAL_SPLITTER (object);
+
+ g_free (horizontal_splitter->details);
+
+ EEL_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
+}
+
+static void
+splitter_expand (CajaHorizontalSplitter *splitter, int position)
+{
+ g_assert (CAJA_IS_HORIZONTAL_SPLITTER (splitter));
+
+ if (position >= CLOSED_THRESHOLD)
+ {
+ return;
+ }
+
+ position = splitter->details->saved_size;
+ if (position < CLOSED_THRESHOLD)
+ {
+ position = NOMINAL_SIZE;
+ }
+
+ gtk_paned_set_position (GTK_PANED (splitter), position);
+}
+
+static void
+splitter_collapse (CajaHorizontalSplitter *splitter, int position)
+{
+ g_assert (CAJA_IS_HORIZONTAL_SPLITTER (splitter));
+
+ splitter->details->saved_size = position;
+ gtk_paned_set_position (GTK_PANED (splitter), 0);
+}
+
+static void
+splitter_toggle (CajaHorizontalSplitter *splitter, int position)
+{
+ g_assert (CAJA_IS_HORIZONTAL_SPLITTER (splitter));
+
+ if (gtk_paned_get_position (GTK_PANED (splitter)) >= CLOSED_THRESHOLD)
+ {
+ caja_horizontal_splitter_collapse (splitter);
+ }
+ else
+ {
+ caja_horizontal_splitter_expand (splitter);
+ }
+}
+
+static void
+splitter_hide (CajaHorizontalSplitter *splitter)
+{
+ GtkPaned *parent;
+
+ parent = GTK_PANED (splitter);
+
+ gtk_widget_hide (gtk_paned_get_child1 (parent));
+}
+
+static void
+splitter_show (CajaHorizontalSplitter *splitter)
+{
+ GtkPaned *parent;
+
+ parent = GTK_PANED (splitter);
+
+ gtk_widget_show (gtk_paned_get_child1 (parent));
+}
+
+static gboolean
+splitter_is_hidden (CajaHorizontalSplitter *splitter)
+{
+ GtkPaned *parent;
+
+ parent = GTK_PANED (splitter);
+
+ return gtk_widget_get_visible (gtk_paned_get_child1 (parent));
+}
+
+void
+caja_horizontal_splitter_expand (CajaHorizontalSplitter *splitter)
+{
+ splitter_expand (splitter, gtk_paned_get_position (GTK_PANED (splitter)));
+}
+
+void
+caja_horizontal_splitter_hide (CajaHorizontalSplitter *splitter)
+{
+ splitter_hide (splitter);
+}
+
+void
+caja_horizontal_splitter_show (CajaHorizontalSplitter *splitter)
+{
+ splitter_show (splitter);
+}
+
+gboolean
+caja_horizontal_splitter_is_hidden (CajaHorizontalSplitter *splitter)
+{
+ return splitter_is_hidden (splitter);
+}
+
+void
+caja_horizontal_splitter_collapse (CajaHorizontalSplitter *splitter)
+{
+ splitter_collapse (splitter, gtk_paned_get_position (GTK_PANED (splitter)));
+}
+
+/* routine to toggle the open/closed state of the splitter */
+void
+caja_horizontal_splitter_toggle_position (CajaHorizontalSplitter *splitter)
+{
+ splitter_toggle (splitter, gtk_paned_get_position (GTK_PANED (splitter)));
+}
+
+/* CajaHorizontalSplitter public methods */
+GtkWidget *
+caja_horizontal_splitter_new (void)
+{
+ return gtk_widget_new (caja_horizontal_splitter_get_type (), NULL);
+}
+
+/* handle mouse downs by remembering the position and the time */
+static gboolean
+caja_horizontal_splitter_button_press (GtkWidget *widget, GdkEventButton *event)
+{
+ gboolean result;
+ CajaHorizontalSplitter *splitter;
+ int position;
+
+ splitter = CAJA_HORIZONTAL_SPLITTER (widget);
+
+ position = gtk_paned_get_position (GTK_PANED (widget));
+
+ result = EEL_CALL_PARENT_WITH_RETURN_VALUE
+ (GTK_WIDGET_CLASS, button_press_event, (widget, event));
+
+ if (result)
+ {
+ splitter->details->press_time = event->time;
+ splitter->details->press_position = position;
+ }
+
+ return result;
+}
+
+/* handle mouse ups by seeing if it was a tap and toggling the open state accordingly */
+static gboolean
+caja_horizontal_splitter_button_release (GtkWidget *widget, GdkEventButton *event)
+{
+ gboolean result;
+ CajaHorizontalSplitter *splitter;
+ int position, delta, delta_time;
+ splitter = CAJA_HORIZONTAL_SPLITTER (widget);
+
+ position = gtk_paned_get_position (GTK_PANED (widget));
+
+ result = EEL_CALL_PARENT_WITH_RETURN_VALUE
+ (GTK_WIDGET_CLASS, button_release_event, (widget, event));
+
+ if (result)
+ {
+ delta = abs (position - splitter->details->press_position);
+ delta_time = event->time - splitter->details->press_time;
+ if (delta < SPLITTER_CLICK_SLOP && delta_time < SPLITTER_CLICK_TIMEOUT)
+ {
+ caja_horizontal_splitter_toggle_position (splitter);
+ }
+ }
+
+ return result;
+}
+
+static void
+caja_horizontal_splitter_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ gint border_width;
+ GtkPaned *paned;
+ GtkAllocation child_allocation;
+ GtkRequisition child_requisition;
+
+ paned = GTK_PANED (widget);
+ border_width = gtk_container_get_border_width (GTK_CONTAINER (paned));
+
+ gtk_widget_set_allocation (widget, allocation);
+
+ if (gtk_paned_get_child2 (paned) != NULL && gtk_widget_get_visible (gtk_paned_get_child2 (paned)))
+ {
+ EEL_CALL_PARENT (GTK_WIDGET_CLASS, size_allocate,
+ (widget, allocation));
+ }
+ else if (gtk_paned_get_child1 (paned) && gtk_widget_get_visible (gtk_paned_get_child1 (paned)))
+ {
+
+ if (gtk_widget_get_realized (widget))
+ {
+ gdk_window_hide (gtk_paned_get_handle_window (paned));
+ }
+
+ gtk_widget_get_child_requisition (gtk_paned_get_child1 (paned),
+ &child_requisition);
+
+ child_allocation.x = allocation->x + border_width;
+ child_allocation.y = allocation->y + border_width;
+ child_allocation.width = MIN (child_requisition.width,
+ allocation->width - 2 * border_width);
+ child_allocation.height = MIN (child_requisition.height,
+ allocation->height - 2 * border_width);
+
+ gtk_widget_size_allocate (gtk_paned_get_child1 (paned), &child_allocation);
+ }
+ else if (gtk_widget_get_realized (widget))
+ {
+ gdk_window_hide (gtk_paned_get_handle_window (paned));
+ }
+
+}
+
+static void
+caja_horizontal_splitter_class_init (CajaHorizontalSplitterClass *class)
+{
+ GtkWidgetClass *widget_class;
+
+ widget_class = GTK_WIDGET_CLASS (class);
+
+ G_OBJECT_CLASS (class)->finalize = caja_horizontal_splitter_finalize;
+
+ widget_class->size_allocate = caja_horizontal_splitter_size_allocate;
+ widget_class->button_press_event = caja_horizontal_splitter_button_press;
+ widget_class->button_release_event = caja_horizontal_splitter_button_release;
+}
+
+void
+caja_horizontal_splitter_pack2 (CajaHorizontalSplitter *splitter,
+ GtkWidget *child2)
+{
+ GtkPaned *paned;
+
+ g_return_if_fail (GTK_IS_WIDGET (child2));
+ g_return_if_fail (CAJA_IS_HORIZONTAL_SPLITTER (splitter));
+
+ paned = GTK_PANED (splitter);
+ gtk_paned_pack2 (paned, child2, TRUE, FALSE);
+}
diff --git a/libcaja-private/caja-horizontal-splitter.h b/libcaja-private/caja-horizontal-splitter.h
new file mode 100644
index 00000000..75d60ef4
--- /dev/null
+++ b/libcaja-private/caja-horizontal-splitter.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-horizontal-splitter.h - A horizontal splitter with a semi gradient look
+
+ Copyright (C) 1999, 2000 Eazel, Inc.
+
+ The Mate 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 Mate 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 Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Ramiro Estrugo <[email protected]>
+*/
+
+#ifndef CAJA_HORIZONTAL_SPLITTER_H
+#define CAJA_HORIZONTAL_SPLITTER_H
+
+#include <gtk/gtk.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define CAJA_TYPE_HORIZONTAL_SPLITTER caja_horizontal_splitter_get_type()
+#define CAJA_HORIZONTAL_SPLITTER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_HORIZONTAL_SPLITTER, CajaHorizontalSplitter))
+#define CAJA_HORIZONTAL_SPLITTER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_HORIZONTAL_SPLITTER, CajaHorizontalSplitterClass))
+#define CAJA_IS_HORIZONTAL_SPLITTER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_HORIZONTAL_SPLITTER))
+#define CAJA_IS_HORIZONTAL_SPLITTER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_HORIZONTAL_SPLITTER))
+#define CAJA_HORIZONTAL_SPLITTER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_HORIZONTAL_SPLITTER, CajaHorizontalSplitterClass))
+
+ typedef struct CajaHorizontalSplitterDetails CajaHorizontalSplitterDetails;
+
+ typedef struct
+ {
+ GtkHPaned parent_slot;
+ CajaHorizontalSplitterDetails *details;
+ } CajaHorizontalSplitter;
+
+ typedef struct
+ {
+ GtkHPanedClass parent_slot;
+ } CajaHorizontalSplitterClass;
+
+ /* CajaHorizontalSplitter public methods */
+ GType caja_horizontal_splitter_get_type (void);
+ GtkWidget *caja_horizontal_splitter_new (void);
+
+ gboolean caja_horizontal_splitter_is_hidden (CajaHorizontalSplitter *splitter);
+ void caja_horizontal_splitter_collapse (CajaHorizontalSplitter *splitter);
+ void caja_horizontal_splitter_hide (CajaHorizontalSplitter *splitter);
+ void caja_horizontal_splitter_show (CajaHorizontalSplitter *splitter);
+ void caja_horizontal_splitter_expand (CajaHorizontalSplitter *splitter);
+ void caja_horizontal_splitter_toggle_position (CajaHorizontalSplitter *splitter);
+ void caja_horizontal_splitter_pack2 (CajaHorizontalSplitter *splitter,
+ GtkWidget *child2);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CAJA_HORIZONTAL_SPLITTER_H */
diff --git a/libcaja-private/caja-icon-canvas-item.c b/libcaja-private/caja-icon-canvas-item.c
new file mode 100644
index 00000000..7362809b
--- /dev/null
+++ b/libcaja-private/caja-icon-canvas-item.c
@@ -0,0 +1,3874 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* Caja - Icon canvas item class for icon container.
+ *
+ * Copyright (C) 2000 Eazel, Inc
+ *
+ * Author: Andy Hertzfeld <[email protected]>
+ *
+ * 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <math.h>
+#include "caja-icon-canvas-item.h"
+
+#include <glib/gi18n.h>
+
+#include "caja-file-utilities.h"
+#include "caja-global-preferences.h"
+#include "caja-icon-private.h"
+#include <eel/eel-art-extensions.h>
+#include <eel/eel-gdk-extensions.h>
+#include <eel/eel-gdk-pixbuf-extensions.h>
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-mate-extensions.h>
+#include <eel/eel-graphic-effects.h>
+#include <eel/eel-gtk-macros.h>
+#include <eel/eel-pango-extensions.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 <eel/eel-canvas-util.h>
+#include <atk/atkimage.h>
+#include <atk/atkcomponent.h>
+#include <atk/atknoopobject.h>
+#include <stdio.h>
+#include <string.h>
+
+#define EMBLEM_SPACING 2
+
+/* gap between bottom of icon and start of text box */
+#define LABEL_OFFSET 1
+#define LABEL_LINE_SPACING 0
+
+#define MAX_TEXT_WIDTH_STANDARD 135
+#define MAX_TEXT_WIDTH_TIGHTER 80
+#define MAX_TEXT_WIDTH_BESIDE 90
+#define MAX_TEXT_WIDTH_BESIDE_TOP_TO_BOTTOM 150
+
+/* 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 icon grid layout algorithms.
+ * “sane amount” of text.
+ * “sane amount“ as of
+ * + hard-coded to three lines in text-below-icon mode.
+ * + unlimited in text-besides-icon mode (see VOODOO-TODO)
+ *
+ * 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 CajaIconCanvasItem structure. */
+struct CajaIconCanvasItemDetails
+{
+ /* The image, text, font. */
+ double x, y;
+ GdkPixbuf *pixbuf;
+ GdkPixbuf *rendered_pixbuf;
+ GList *emblem_pixbufs;
+ 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. */
+ GdkPoint *attach_points;
+ int n_attach_points;
+
+ /* 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;
+
+ /* preview state */
+ guint is_active : 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_active : 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 is_renaming : 1;
+
+ guint bounds_cached : 1;
+
+ guint is_visible : 1;
+
+ GdkRectangle embedded_text_rect;
+ char *embedded_text;
+
+ /* Cached PangoLayouts. Only used if the icon is visible */
+ PangoLayout *editable_text_layout;
+ PangoLayout *additional_text_layout;
+ PangoLayout *embedded_text_layout;
+
+ /* Cached rectangle in canvas coordinates */
+ EelIRect canvas_rect;
+ EelIRect text_rect;
+ EelIRect emblem_rect;
+
+ EelIRect bounds_cache;
+ EelIRect bounds_cache_for_layout;
+ EelIRect bounds_cache_for_entire_item;
+
+ /* 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;
+
+enum
+{
+ ACTION_OPEN,
+ ACTION_MENU,
+ LAST_ACTION
+};
+
+typedef struct
+{
+ char *action_descriptions[LAST_ACTION];
+ char *image_description;
+ char *description;
+} CajaIconCanvasItemAccessiblePrivate;
+
+typedef struct
+{
+ CajaIconCanvasItem *item;
+ gint action_number;
+} CajaIconCanvasItemAccessibleActionContext;
+
+typedef struct
+{
+ CajaIconCanvasItem *icon_item;
+ EelIRect icon_rect;
+ RectangleSide side;
+ int position;
+ int index;
+ GList *emblem;
+} EmblemLayout;
+
+static int click_policy_auto_value;
+
+static void caja_icon_canvas_item_text_interface_init (EelAccessibleTextIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (CajaIconCanvasItem, caja_icon_canvas_item, EEL_TYPE_CANVAS_ITEM,
+ G_IMPLEMENT_INTERFACE (EEL_TYPE_ACCESSIBLE_TEXT,
+ caja_icon_canvas_item_text_interface_init));
+
+/* private */
+static void draw_label_text (CajaIconCanvasItem *item,
+ GdkDrawable *drawable,
+ gboolean create_mask,
+ EelIRect icon_rect);
+static void measure_label_text (CajaIconCanvasItem *item);
+static void get_icon_canvas_rectangle (CajaIconCanvasItem *item,
+ EelIRect *rect);
+static void emblem_layout_reset (EmblemLayout *layout,
+ CajaIconCanvasItem *icon_item,
+ EelIRect icon_rect,
+ gboolean is_rtl);
+static gboolean emblem_layout_next (EmblemLayout *layout,
+ GdkPixbuf **emblem_pixbuf,
+ EelIRect *emblem_rect,
+ gboolean is_rtl);
+static void draw_pixbuf (GdkPixbuf *pixbuf,
+ GdkDrawable *drawable,
+ int x,
+ int y);
+static PangoLayout *get_label_layout (PangoLayout **layout,
+ CajaIconCanvasItem *item,
+ const char *text);
+static void draw_label_layout (CajaIconCanvasItem *item,
+ GdkDrawable *drawable,
+ PangoLayout *layout,
+ gboolean highlight,
+ GdkColor *label_color,
+ int x,
+ int y,
+ GdkGC *gc);
+static gboolean hit_test_stretch_handle (CajaIconCanvasItem *item,
+ EelIRect canvas_rect,
+ GtkCornerType *corner);
+static void draw_embedded_text (CajaIconCanvasItem *icon_item,
+ GdkDrawable *drawable,
+ int x,
+ int y);
+
+static void caja_icon_canvas_item_ensure_bounds_up_to_date (CajaIconCanvasItem *icon_item);
+
+
+static gpointer accessible_parent_class = NULL;
+
+static GQuark accessible_private_data_quark = 0;
+
+static const char *caja_icon_canvas_item_accessible_action_names[] =
+{
+ "open",
+ "menu",
+ NULL
+};
+
+static const char *caja_icon_canvas_item_accessible_action_descriptions[] =
+{
+ "Open item",
+ "Popup context menu",
+ NULL
+};
+
+
+/* Object initialization function for the icon item. */
+static void
+caja_icon_canvas_item_init (CajaIconCanvasItem *icon_item)
+{
+ static gboolean setup_auto_enums = FALSE;
+
+ if (!setup_auto_enums)
+ {
+ eel_preferences_add_auto_enum
+ (CAJA_PREFERENCES_CLICK_POLICY,
+ &click_policy_auto_value);
+ setup_auto_enums = TRUE;
+ }
+
+ icon_item->details = G_TYPE_INSTANCE_GET_PRIVATE ((icon_item), CAJA_TYPE_ICON_CANVAS_ITEM, CajaIconCanvasItemDetails);
+ caja_icon_canvas_item_invalidate_label_size (icon_item);
+}
+
+static void
+caja_icon_canvas_item_finalize (GObject *object)
+{
+ CajaIconCanvasItemDetails *details;
+
+ g_assert (CAJA_IS_ICON_CANVAS_ITEM (object));
+
+ details = CAJA_ICON_CANVAS_ITEM (object)->details;
+
+ if (details->pixbuf != NULL)
+ {
+ g_object_unref (details->pixbuf);
+ }
+
+ if (details->text_util != NULL)
+ {
+ g_object_unref (details->text_util);
+ }
+
+ eel_gdk_pixbuf_list_free (details->emblem_pixbufs);
+ g_free (details->editable_text);
+ g_free (details->additional_text);
+ g_free (details->attach_points);
+
+ if (details->rendered_pixbuf != NULL)
+ {
+ g_object_unref (details->rendered_pixbuf);
+ }
+
+ 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);
+ }
+
+ if (details->embedded_text_layout != NULL)
+ {
+ g_object_unref (details->embedded_text_layout);
+ }
+
+ g_free (details->embedded_text);
+
+ G_OBJECT_CLASS (caja_icon_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
+caja_icon_canvas_item_invalidate_bounds_cache (CajaIconCanvasItem *item)
+{
+ item->details->bounds_cached = FALSE;
+}
+
+/* invalidate the text width and height cached in the item details. */
+void
+caja_icon_canvas_item_invalidate_label_size (CajaIconCanvasItem *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);
+ }
+ if (item->details->embedded_text_layout != NULL)
+ {
+ pango_layout_context_changed (item->details->embedded_text_layout);
+ }
+ caja_icon_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 icon item. */
+static void
+caja_icon_canvas_item_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CajaIconCanvasItem *item;
+ CajaIconCanvasItemDetails *details;
+
+ item = CAJA_ICON_CANVAS_ITEM (object);
+ details = item->details;
+
+ switch (property_id)
+ {
+
+ case PROP_EDITABLE_TEXT:
+ if (eel_strcmp (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)
+ {
+ AtkObject *accessible;
+
+ gail_text_util_text_setup (details->text_util,
+ details->editable_text);
+ accessible = eel_accessibility_get_atk_object (item);
+ g_object_notify (G_OBJECT(accessible), "accessible-name");
+ }
+
+ caja_icon_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 (eel_strcmp (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));
+
+ caja_icon_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);
+ caja_icon_canvas_item_invalidate_label_size (item);
+ 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)
+ {
+ AtkObject *atk_object = eel_accessibility_for_object (object);
+ atk_focus_tracker_notify (atk_object);
+ }
+ 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 ("caja_icons_view_item_item_set_arg on unknown argument");
+ return;
+ }
+
+ eel_canvas_item_request_update (EEL_CANVAS_ITEM (object));
+}
+
+/* Get property handler for the icon item */
+static void
+caja_icon_canvas_item_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ CajaIconCanvasItemDetails *details;
+
+ details = CAJA_ICON_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;
+ }
+}
+
+GdkPixmap *
+caja_icon_canvas_item_get_image (CajaIconCanvasItem *item,
+ GdkBitmap **mask,
+ GdkColormap *colormap)
+{
+ GdkPixmap *pixmap;
+ EelCanvas *canvas;
+ GdkScreen *screen;
+ GdkGC *gc;
+ int width, height;
+ int item_offset_x, item_offset_y;
+ EelIRect icon_rect;
+ EelIRect emblem_rect;
+ GdkPixbuf *pixbuf;
+ GdkPixbuf *emblem_pixbuf;
+ EmblemLayout emblem_layout;
+ double item_x, item_y;
+ gboolean is_rtl;
+ cairo_t *cr;
+
+ g_return_val_if_fail (CAJA_IS_ICON_CANVAS_ITEM (item), NULL);
+
+ canvas = EEL_CANVAS_ITEM (item)->canvas;
+ screen = gdk_colormap_get_screen (colormap);
+
+ /* 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;
+
+ pixmap = gdk_pixmap_new (gdk_screen_get_root_window (screen),
+ width, height,
+ gdk_visual_get_depth (gdk_colormap_get_visual (colormap)));
+ gdk_drawable_set_colormap (GDK_DRAWABLE (pixmap), colormap);
+
+ pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+ TRUE,
+ gdk_pixbuf_get_bits_per_sample (item->details->pixbuf),
+ width, height);
+ gdk_pixbuf_fill (pixbuf, 0x00000000);
+
+ gdk_pixbuf_composite (item->details->pixbuf, pixbuf,
+ item_offset_x, item_offset_y,
+ gdk_pixbuf_get_width (item->details->pixbuf),
+ gdk_pixbuf_get_height (item->details->pixbuf),
+ item_offset_x, item_offset_y, 1.0, 1.0,
+ GDK_INTERP_BILINEAR, 255);
+
+ icon_rect.x0 = item_offset_x;
+ icon_rect.y0 = item_offset_y;
+ icon_rect.x1 = item_offset_x + gdk_pixbuf_get_width (item->details->pixbuf);
+ icon_rect.y1 = item_offset_y + gdk_pixbuf_get_height (item->details->pixbuf);
+
+
+ is_rtl = caja_icon_container_is_layout_rtl (CAJA_ICON_CONTAINER (canvas));
+
+ emblem_layout_reset (&emblem_layout, item, icon_rect, is_rtl);
+ while (emblem_layout_next (&emblem_layout, &emblem_pixbuf, &emblem_rect, is_rtl))
+ {
+ gdk_pixbuf_composite (emblem_pixbuf, pixbuf,
+ emblem_rect.x0, emblem_rect.y0,
+ gdk_pixbuf_get_width (emblem_pixbuf),
+ gdk_pixbuf_get_height (emblem_pixbuf),
+ emblem_rect.x0, emblem_rect.y0,
+ 1.0, 1.0,
+ GDK_INTERP_BILINEAR, 255);
+ }
+
+ /* clear the pixmap */
+ cr = gdk_cairo_create (pixmap);
+ cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
+ cairo_paint (cr);
+ cairo_destroy (cr);
+
+ gc = gdk_gc_new (pixmap);
+ gdk_draw_pixbuf (pixmap, gc, pixbuf,
+ 0, 0, 0, 0,
+ gdk_pixbuf_get_width (pixbuf), gdk_pixbuf_get_height (pixbuf),
+ GDK_RGB_DITHER_NORMAL,
+ 0, 0);
+ g_object_unref (gc);
+
+ *mask = gdk_pixmap_new (gdk_screen_get_root_window (screen),
+ width, height,
+ 1);
+ gc = gdk_gc_new (*mask);
+ gdk_draw_rectangle (*mask, gc,
+ TRUE,
+ 0, 0,
+ width, height);
+ g_object_unref (gc);
+
+ gdk_pixbuf_render_threshold_alpha (pixbuf, *mask,
+ 0, 0, 0, 0,
+ gdk_pixbuf_get_width (pixbuf), gdk_pixbuf_get_height (pixbuf),
+ 128);
+
+ draw_embedded_text (item, GDK_DRAWABLE (pixmap),
+ item_offset_x, item_offset_y);
+
+ draw_label_text (item, GDK_DRAWABLE (pixmap), FALSE, icon_rect);
+ draw_label_text (item, GDK_DRAWABLE (*mask), TRUE, icon_rect);
+
+ g_object_unref (pixbuf);
+
+ return pixmap;
+}
+
+void
+caja_icon_canvas_item_set_image (CajaIconCanvasItem *item,
+ GdkPixbuf *image)
+{
+ CajaIconCanvasItemDetails *details;
+
+ g_return_if_fail (CAJA_IS_ICON_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_pixbuf != NULL)
+ {
+ g_object_unref (details->rendered_pixbuf);
+ details->rendered_pixbuf = NULL;
+ }
+
+ details->pixbuf = image;
+
+ caja_icon_canvas_item_invalidate_bounds_cache (item);
+ eel_canvas_item_request_update (EEL_CANVAS_ITEM (item));
+}
+
+void
+caja_icon_canvas_item_set_emblems (CajaIconCanvasItem *item,
+ GList *emblem_pixbufs)
+{
+ GList *p;
+
+ g_return_if_fail (CAJA_IS_ICON_CANVAS_ITEM (item));
+
+ g_assert (item->details->emblem_pixbufs != emblem_pixbufs || emblem_pixbufs == NULL);
+
+ /* The case where the emblems are identical is fairly common,
+ * so lets take the time to check for it.
+ */
+ if (eel_g_list_equal (item->details->emblem_pixbufs, emblem_pixbufs))
+ {
+ return;
+ }
+
+ /* Check if they are acceptable. */
+ for (p = emblem_pixbufs; p != NULL; p = p->next)
+ {
+ g_return_if_fail (pixbuf_is_acceptable (p->data));
+ }
+
+ /* Take in the new list of emblems. */
+ eel_gdk_pixbuf_list_ref (emblem_pixbufs);
+ eel_gdk_pixbuf_list_free (item->details->emblem_pixbufs);
+ item->details->emblem_pixbufs = g_list_copy (emblem_pixbufs);
+
+ caja_icon_canvas_item_invalidate_bounds_cache (item);
+ eel_canvas_item_request_update (EEL_CANVAS_ITEM (item));
+}
+
+void
+caja_icon_canvas_item_set_attach_points (CajaIconCanvasItem *item,
+ GdkPoint *attach_points,
+ int n_attach_points)
+{
+ g_free (item->details->attach_points);
+ item->details->attach_points = NULL;
+ item->details->n_attach_points = 0;
+
+ if (attach_points != NULL && n_attach_points != 0)
+ {
+ item->details->attach_points = g_memdup (attach_points, n_attach_points * sizeof (GdkPoint));
+ item->details->n_attach_points = n_attach_points;
+ }
+
+ caja_icon_canvas_item_invalidate_bounds_cache (item);
+}
+
+void
+caja_icon_canvas_item_set_embedded_text_rect (CajaIconCanvasItem *item,
+ const GdkRectangle *text_rect)
+{
+ item->details->embedded_text_rect = *text_rect;
+
+ caja_icon_canvas_item_invalidate_bounds_cache (item);
+ eel_canvas_item_request_update (EEL_CANVAS_ITEM (item));
+}
+
+void
+caja_icon_canvas_item_set_embedded_text (CajaIconCanvasItem *item,
+ const char *text)
+{
+ g_free (item->details->embedded_text);
+ item->details->embedded_text = g_strdup (text);
+
+ if (item->details->embedded_text_layout != NULL)
+ {
+ if (text != NULL)
+ {
+ pango_layout_set_text (item->details->embedded_text_layout, text, -1);
+ }
+ else
+ {
+ pango_layout_set_text (item->details->embedded_text_layout, "", -1);
+ }
+ }
+
+ eel_canvas_item_request_update (EEL_CANVAS_ITEM (item));
+}
+
+
+/* Recomputes the bounding box of a icon 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 (CajaIconCanvasItem *icon_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;
+ EelDPoint top_left, bottom_right;
+
+ item = EEL_CANVAS_ITEM (icon_item);
+
+ eel_canvas_item_get_bounds (item,
+ &top_left.x, &top_left.y,
+ &bottom_right.x, &bottom_right.y);
+
+ top_left.x += i2w_dx;
+ top_left.y += i2w_dy;
+ bottom_right.x += i2w_dx;
+ bottom_right.y += i2w_dy;
+ eel_canvas_w2c_d (item->canvas,
+ top_left.x, top_left.y,
+ &item->x1, &item->y1);
+ eel_canvas_w2c_d (item->canvas,
+ bottom_right.x, bottom_right.y,
+ &item->x2, &item->y2);
+}
+
+static EelIRect
+compute_text_rectangle (const CajaIconCanvasItem *item,
+ EelIRect icon_rectangle,
+ gboolean canvas_coords,
+ CajaIconCanvasItemBoundsUsage 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, text_dx;
+
+ 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;
+ text_dx = item->details->text_dx;
+ }
+ 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_dx = item->details->text_dx / pixels_per_unit;
+ }
+
+ if (CAJA_ICON_CONTAINER (EEL_CANVAS_ITEM (item)->canvas)->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
+ {
+ if (!caja_icon_container_is_layout_rtl (CAJA_ICON_CONTAINER (EEL_CANVAS_ITEM (item)->canvas)))
+ {
+ text_rectangle.x0 = icon_rectangle.x1;
+ text_rectangle.x1 = text_rectangle.x0 + text_dx + text_width;
+ }
+ else
+ {
+ text_rectangle.x1 = icon_rectangle.x0;
+ text_rectangle.x0 = text_rectangle.x1 - text_dx - text_width;
+ }
+
+ /* VOODOO-TODO */
+#if 0
+ if (for_layout)
+ {
+ /* in this case, we should be more smart and calculate the size according to the maximum
+ * number of lines fitting next to the icon. However, this requires a more complex layout logic.
+ * It would mean that when measuring the label, the icon dimensions must be known already,
+ * and we
+ * 1. start with an unlimited layout
+ * 2. measure how many lines of this layout fit next to the icon
+ * 3. limit the number of lines to the given number of fitting lines
+ */
+ real_text_height = VOODOO();
+ }
+ else
+ {
+#endif
+ real_text_height = text_height_for_entire_text;
+#if 0
+ }
+#endif
+
+ text_rectangle.y0 = (icon_rectangle.y0 + icon_rectangle.y1) / 2- (int) real_text_height / 2;
+ text_rectangle.y1 = text_rectangle.y0 + real_text_height;
+ }
+ else
+ {
+ 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
+caja_icon_canvas_item_update_bounds (CajaIconCanvasItem *item,
+ double i2w_dx, double i2w_dy)
+{
+ EelIRect before, after, emblem_rect;
+ EmblemLayout emblem_layout;
+ EelCanvasItem *canvas_item;
+ GdkPixbuf *emblem_pixbuf;
+ gboolean is_rtl;
+
+ 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;
+ }
+
+ is_rtl = caja_icon_container_is_layout_rtl (CAJA_ICON_CONTAINER (canvas_item->canvas));
+
+ /* Update canvas and text rect cache */
+ get_icon_canvas_rectangle (item, &item->details->canvas_rect);
+ item->details->text_rect = compute_text_rectangle (item, item->details->canvas_rect,
+ TRUE, BOUNDS_USAGE_FOR_DISPLAY);
+
+ /* Update emblem rect cache */
+ item->details->emblem_rect.x0 = 0;
+ item->details->emblem_rect.x1 = 0;
+ item->details->emblem_rect.y0 = 0;
+ item->details->emblem_rect.y1 = 0;
+ emblem_layout_reset (&emblem_layout, item, item->details->canvas_rect, is_rtl);
+ while (emblem_layout_next (&emblem_layout, &emblem_pixbuf, &emblem_rect, is_rtl))
+ {
+ eel_irect_union (&item->details->emblem_rect, &item->details->emblem_rect, &emblem_rect);
+ }
+
+ /* queue a redraw. */
+ eel_canvas_request_redraw (canvas_item->canvas,
+ before.x0, before.y0,
+ before.x1 + 1, before.y1 + 1);
+}
+
+/* Update handler for the icon canvas item. */
+static void
+caja_icon_canvas_item_update (EelCanvasItem *item,
+ double i2w_dx, double i2w_dy,
+ gint flags)
+{
+ caja_icon_canvas_item_update_bounds (CAJA_ICON_CANVAS_ITEM (item), i2w_dx, i2w_dy);
+
+ eel_canvas_item_request_redraw (EEL_CANVAS_ITEM (item));
+
+ EEL_CANVAS_ITEM_CLASS (caja_icon_canvas_item_parent_class)->update (item, i2w_dx, i2w_dy, flags);
+}
+
+/* Rendering */
+static gboolean
+in_single_click_mode (void)
+{
+ return click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE;
+}
+
+
+/* Utility routine to create a rectangle with rounded corners.
+ * This could possibly move to Eel as a general purpose routine.
+ */
+static void
+make_round_rect (cairo_t *cr,
+ double x,
+ double y,
+ double width,
+ double height,
+ double radius)
+{
+ double cx, cy;
+
+ width -= 2 * radius;
+ height -= 2 * radius;
+
+ cairo_move_to (cr, x + radius, y);
+
+ cairo_rel_line_to (cr, width, 0.0);
+
+ cairo_get_current_point (cr, &cx, &cy);
+ cairo_arc (cr, cx, cy + radius, radius, 3.0 * G_PI_2, 0);
+
+ cairo_rel_line_to (cr, 0.0, height);
+
+ cairo_get_current_point (cr, &cx, &cy);
+ cairo_arc (cr, cx - radius, cy, radius, 0, G_PI_2);
+
+ cairo_rel_line_to (cr, - width, 0.0);
+
+ cairo_get_current_point (cr, &cx, &cy);
+ cairo_arc (cr, cx, cy - radius, radius, G_PI_2, G_PI);
+
+ cairo_rel_line_to (cr, 0.0, -height);
+
+ cairo_get_current_point (cr, &cx, &cy);
+ cairo_arc (cr, cx + radius, cy, radius, G_PI, 3.0 * G_PI_2);
+
+ cairo_close_path (cr);
+}
+
+static void
+draw_frame (CajaIconCanvasItem *item,
+ GdkDrawable *drawable,
+ guint color,
+ gboolean create_mask,
+ int x,
+ int y,
+ int width,
+ int height)
+{
+ CajaIconContainer *container;
+ cairo_t *cr;
+
+ container = CAJA_ICON_CONTAINER (EEL_CANVAS_ITEM (item)->canvas);
+
+ /* Get a cairo context */
+ cr = gdk_cairo_create (drawable);
+
+ /* Set the rounded rect clip region. Magic rounding value taken
+ * from old code.
+ */
+ make_round_rect (cr, x, y, width, height, 5);
+
+ if (create_mask)
+ {
+ /* Dunno how to do this with cairo...
+ * It used to threshold the rendering so that the
+ * bitmask didn't show white where alpha < 0.5
+ */
+ }
+
+ cairo_set_source_rgba (cr,
+ EEL_RGBA_COLOR_GET_R (color) / 255.0,
+ EEL_RGBA_COLOR_GET_G (color) / 255.0,
+ EEL_RGBA_COLOR_GET_B (color) / 255.0,
+ EEL_RGBA_COLOR_GET_A (color) / 255.0);
+
+ /* Paint into drawable now that we have set up the color and opacity */
+ cairo_fill (cr);
+
+ /* Clean up now that drawing is complete */
+ cairo_destroy (cr);
+}
+
+/* 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);
+ /* VOODOO-TODO, determine number of lines based on the icon size for text besides icon.
+ * cf. compute_text_rectangle() */
+ 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);
+ }
+}
+
+#define IS_COMPACT_VIEW(container) \
+ ((container->details->layout_mode == CAJA_ICON_LAYOUT_T_B_L_R || \
+ container->details->layout_mode == CAJA_ICON_LAYOUT_T_B_R_L) && \
+ container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
+
+#define TEXT_BACK_PADDING_X 4
+#define TEXT_BACK_PADDING_Y 1
+
+static void
+prepare_pango_layout_width (CajaIconCanvasItem *item,
+ PangoLayout *layout)
+{
+ if (caja_icon_canvas_item_get_max_text_width (item) < 0)
+ {
+ pango_layout_set_width (layout, -1);
+ }
+ else
+ {
+ pango_layout_set_width (layout, floor (caja_icon_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 (CajaIconCanvasItem *item,
+ PangoLayout *layout)
+{
+ CajaIconContainer *container;
+
+ prepare_pango_layout_width (item, layout);
+
+ container = CAJA_ICON_CONTAINER (EEL_CANVAS_ITEM (item)->canvas);
+
+ if (IS_COMPACT_VIEW (container))
+ {
+ pango_layout_set_height (layout, -1);
+ }
+ else
+ {
+ pango_layout_set_height (layout, G_MININT);
+ }
+}
+
+static void
+prepare_pango_layout_for_draw (CajaIconCanvasItem *item,
+ PangoLayout *layout)
+{
+ CajaIconCanvasItemDetails *details;
+ CajaIconContainer *container;
+ gboolean needs_highlight;
+
+ prepare_pango_layout_width (item, layout);
+
+ container = CAJA_ICON_CONTAINER (EEL_CANVAS_ITEM (item)->canvas);
+ details = item->details;
+
+ needs_highlight = details->is_highlighted_for_selection || details->is_highlighted_for_drop;
+
+ if (IS_COMPACT_VIEW (container))
+ {
+ pango_layout_set_height (layout, -1);
+ }
+ else if (needs_highlight ||
+ details->is_prelit ||
+ details->is_highlighted_as_keyboard_focus ||
+ details->entire_text ||
+ container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
+ {
+ /* 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,
+ caja_icon_container_get_max_layout_lines_for_pango (container));
+ }
+}
+
+static void
+measure_label_text (CajaIconCanvasItem *item)
+{
+ CajaIconCanvasItemDetails *details;
+ CajaIconContainer *container;
+ gint editable_height, editable_height_for_layout, editable_height_for_entire_text, editable_width, editable_dx;
+ gint additional_height, additional_width, additional_dx;
+ EelCanvasItem *canvas_item;
+ PangoLayout *editable_layout;
+ PangoLayout *additional_layout;
+ gboolean have_editable, have_additional, needs_highlight;
+ int max_text_width;
+
+ /* 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;
+ needs_highlight = details->is_highlighted_for_selection || details->is_highlighted_for_drop;
+
+ 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
+
+ canvas_item = EEL_CANVAS_ITEM (item);
+
+ 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;
+
+ max_text_width = floor (caja_icon_canvas_item_get_max_text_width (item));
+
+ container = CAJA_ICON_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,
+ caja_icon_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 (CajaIconCanvasItem *item,
+ GdkDrawable *drawable,
+ gboolean create_mask,
+ EelIRect icon_rect)
+{
+ EelCanvasItem *canvas_item;
+ CajaIconCanvasItemDetails *details;
+ CajaIconContainer *container;
+ PangoLayout *editable_layout;
+ PangoLayout *additional_layout;
+ GdkColor *label_color;
+ GdkGC *gc;
+ gboolean have_editable, have_additional;
+ gboolean needs_frame, needs_highlight, prelight_label, is_rtl_label_beside;
+ EelIRect text_rect;
+ int x;
+ int max_text_width;
+
+#ifdef PERFORMANCE_TEST_DRAW_DISABLE
+ return;
+#endif
+
+ gc = NULL;
+
+ canvas_item = EEL_CANVAS_ITEM (item);
+ details = item->details;
+
+ measure_label_text (item);
+ if (details->text_height == 0 ||
+ details->text_width == 0)
+ {
+ return;
+ }
+
+ container = CAJA_ICON_CONTAINER (EEL_CANVAS_ITEM (item)->canvas);
+
+ 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;
+ is_rtl_label_beside = caja_icon_container_is_layout_rtl (container) &&
+ container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE;
+
+ 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 (caja_icon_canvas_item_get_max_text_width (item));
+
+ /* if the icon is highlighted, do some set-up */
+ if (needs_highlight && !details->is_renaming)
+ {
+ draw_frame (item,
+ drawable,
+ gtk_widget_has_focus (GTK_WIDGET (container)) ? container->details->highlight_color_rgba : container->details->active_color_rgba,
+ create_mask,
+ is_rtl_label_beside ? text_rect.x0 + item->details->text_dx : text_rect.x0,
+ text_rect.y0,
+ is_rtl_label_beside ? text_rect.x1 - text_rect.x0 - item->details->text_dx : text_rect.x1 - text_rect.x0,
+ text_rect.y1 - text_rect.y0);
+ }
+ else if (!needs_highlight && !details->is_renaming &&
+ (details->is_prelit ||
+ details->is_highlighted_as_keyboard_focus))
+ {
+ /* clear the underlying icons, where the text or overlaps them. */
+ gdk_window_clear_area (gtk_layout_get_bin_window (&EEL_CANVAS (container)->layout),
+ text_rect.x0,
+ text_rect.y0,
+ text_rect.x1 - text_rect.x0,
+ text_rect.y1 - text_rect.y0);
+ }
+
+ if (container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
+ {
+ x = text_rect.x0 + 2;
+ }
+ else
+ {
+ x = text_rect.x0 + ((text_rect.x1 - text_rect.x0) - max_text_width) / 2;
+ }
+
+ 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);
+
+ gtk_widget_style_get (GTK_WIDGET (container),
+ "frame_text", &needs_frame,
+ "activate_prelight_icon_label", &prelight_label,
+ NULL);
+ if (needs_frame && !needs_highlight && details->text_width > 0 && details->text_height > 0)
+ {
+ if (!(prelight_label && item->details->is_prelit))
+ {
+ draw_frame (item,
+ drawable,
+ container->details->normal_color_rgba,
+ create_mask,
+ text_rect.x0,
+ text_rect.y0,
+ text_rect.x1 - text_rect.x0,
+ text_rect.y1 - text_rect.y0);
+ }
+ else
+ {
+ draw_frame (item,
+ drawable,
+ container->details->prelight_color_rgba,
+ create_mask,
+ text_rect.x0,
+ text_rect.y0,
+ text_rect.x1 - text_rect.x0,
+ text_rect.y1 - text_rect.y0);
+ }
+ }
+
+ gc = caja_icon_container_get_label_color_and_gc
+ (CAJA_ICON_CONTAINER (canvas_item->canvas),
+ &label_color, TRUE, needs_highlight,
+ prelight_label & item->details->is_prelit);
+
+ draw_label_layout (item, drawable,
+ editable_layout, needs_highlight,
+ label_color,
+ x,
+ text_rect.y0 + TEXT_BACK_PADDING_Y, gc);
+ }
+
+ 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);
+
+ gc = caja_icon_container_get_label_color_and_gc
+ (CAJA_ICON_CONTAINER (canvas_item->canvas),
+ &label_color, FALSE, needs_highlight,
+ FALSE);
+
+ draw_label_layout (item, drawable,
+ additional_layout, needs_highlight,
+ label_color,
+ x,
+ text_rect.y0 + details->editable_text_height + LABEL_LINE_SPACING + TEXT_BACK_PADDING_Y, gc);
+ }
+
+ if (!create_mask && item->details->is_highlighted_as_keyboard_focus)
+ {
+ gtk_paint_focus (gtk_widget_get_style (GTK_WIDGET (EEL_CANVAS_ITEM (item)->canvas)),
+ drawable,
+ needs_highlight ? GTK_STATE_SELECTED : GTK_STATE_NORMAL,
+ NULL,
+ GTK_WIDGET (EEL_CANVAS_ITEM (item)->canvas),
+ "icon-container",
+ text_rect.x0,
+ text_rect.y0,
+ text_rect.x1 - text_rect.x0,
+ text_rect.y1 - text_rect.y0);
+ }
+
+ if (editable_layout != NULL)
+ {
+ g_object_unref (editable_layout);
+ }
+
+ if (additional_layout != NULL)
+ {
+ g_object_unref (additional_layout);
+ }
+}
+
+void
+caja_icon_canvas_item_set_is_visible (CajaIconCanvasItem *item,
+ gboolean visible)
+{
+ if (item->details->is_visible == visible)
+ return;
+
+ item->details->is_visible = visible;
+
+ if (!visible)
+ {
+ caja_icon_canvas_item_invalidate_label (item);
+ }
+}
+
+void
+caja_icon_canvas_item_invalidate_label (CajaIconCanvasItem *item)
+{
+ caja_icon_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;
+ }
+
+ if (item->details->embedded_text_layout)
+ {
+ g_object_unref (item->details->embedded_text_layout);
+ item->details->embedded_text_layout = NULL;
+ }
+}
+
+
+static GdkPixbuf *
+get_knob_pixbuf (void)
+{
+ GdkPixbuf *knob_pixbuf;
+ char *knob_filename;
+
+ knob_pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
+ "stock-caja-knob",
+ 8, 0, NULL);
+ if (!knob_pixbuf)
+ {
+ knob_filename = caja_pixmap_file ("knob.png");
+ knob_pixbuf = gdk_pixbuf_new_from_file (knob_filename, NULL);
+ g_free (knob_filename);
+ }
+
+ return knob_pixbuf;
+}
+
+static void
+draw_stretch_handles (CajaIconCanvasItem *item, GdkDrawable *drawable,
+ const EelIRect *rect)
+{
+ GtkWidget *widget;
+ GdkGC *gc;
+ GdkPixbuf *knob_pixbuf;
+ GdkBitmap *stipple;
+ int knob_width, knob_height;
+ GtkStyle *style;
+
+ if (!item->details->show_stretch_handles)
+ {
+ return;
+ }
+
+ widget = GTK_WIDGET (EEL_CANVAS_ITEM (item)->canvas);
+ style = gtk_widget_get_style (widget);
+
+ gc = gdk_gc_new (drawable);
+ knob_pixbuf = get_knob_pixbuf ();
+ knob_width = gdk_pixbuf_get_width (knob_pixbuf);
+ knob_height = gdk_pixbuf_get_height (knob_pixbuf);
+
+ stipple = eel_stipple_bitmap_for_screen (
+ gdk_drawable_get_screen (GDK_DRAWABLE (drawable)));
+
+ /* first draw the box */
+ gdk_gc_set_rgb_fg_color (gc, &style->white);
+ gdk_draw_rectangle
+ (drawable, gc, FALSE,
+ rect->x0,
+ rect->y0,
+ rect->x1 - rect->x0 - 1,
+ rect->y1 - rect->y0 - 1);
+
+ gdk_gc_set_rgb_fg_color (gc, &style->black);
+ gdk_gc_set_stipple (gc, stipple);
+ gdk_gc_set_fill (gc, GDK_STIPPLED);
+ gdk_draw_rectangle
+ (drawable, gc, FALSE,
+ rect->x0,
+ rect->y0,
+ rect->x1 - rect->x0 - 1,
+ rect->y1 - rect->y0 - 1);
+
+ /* draw the stretch handles themselves */
+
+ draw_pixbuf (knob_pixbuf, drawable, rect->x0, rect->y0);
+ draw_pixbuf (knob_pixbuf, drawable, rect->x0, rect->y1 - knob_height);
+ draw_pixbuf (knob_pixbuf, drawable, rect->x1 - knob_width, rect->y0);
+ draw_pixbuf (knob_pixbuf, drawable, rect->x1 - knob_width, rect->y1 - knob_height);
+ g_object_unref (knob_pixbuf);
+
+ g_object_unref (gc);
+}
+
+static void
+emblem_layout_reset (EmblemLayout *layout, CajaIconCanvasItem *icon_item, EelIRect icon_rect, gboolean is_rtl)
+{
+ layout->icon_item = icon_item;
+ layout->icon_rect = icon_rect;
+ layout->side = is_rtl ? LEFT_SIDE : RIGHT_SIDE;
+ layout->position = 0;
+ layout->index = 0;
+ layout->emblem = icon_item->details->emblem_pixbufs;
+}
+
+static gboolean
+emblem_layout_next (EmblemLayout *layout,
+ GdkPixbuf **emblem_pixbuf,
+ EelIRect *emblem_rect,
+ gboolean is_rtl)
+{
+ GdkPixbuf *pixbuf;
+ int width, height, x, y;
+ GdkPoint *attach_points;
+
+ /* Check if we have layed out all of the pixbufs. */
+ if (layout->emblem == NULL)
+ {
+ return FALSE;
+ }
+
+ /* Get the pixbuf. */
+ pixbuf = layout->emblem->data;
+ width = gdk_pixbuf_get_width (pixbuf);
+ height = gdk_pixbuf_get_height (pixbuf);
+
+
+ /* Advance to the next emblem. */
+ layout->emblem = layout->emblem->next;
+
+ attach_points = layout->icon_item->details->attach_points;
+ if (attach_points != NULL)
+ {
+ if (layout->index >= layout->icon_item->details->n_attach_points)
+ {
+ return FALSE;
+ }
+
+ x = layout->icon_rect.x0 + attach_points[layout->index].x;
+ y = layout->icon_rect.y0 + attach_points[layout->index].y;
+
+ layout->index += 1;
+
+ /* Return the rectangle and pixbuf. */
+ *emblem_pixbuf = pixbuf;
+ emblem_rect->x0 = x - width / 2;
+ emblem_rect->y0 = y - height / 2;
+ emblem_rect->x1 = emblem_rect->x0 + width;
+ emblem_rect->y1 = emblem_rect->y0 + height;
+
+ return TRUE;
+
+ }
+
+ for (;;)
+ {
+
+ /* Find the side to lay out along. */
+ switch (layout->side)
+ {
+ case RIGHT_SIDE:
+ x = layout->icon_rect.x1;
+ y = is_rtl ? layout->icon_rect.y1 : layout->icon_rect.y0;
+ break;
+ case BOTTOM_SIDE:
+ x = is_rtl ? layout->icon_rect.x0 : layout->icon_rect.x1;
+ y = layout->icon_rect.y1;
+ break;
+ case LEFT_SIDE:
+ x = layout->icon_rect.x0;
+ y = is_rtl ? layout->icon_rect.y0 : layout->icon_rect.y1;
+ break;
+ case TOP_SIDE:
+ x = is_rtl ? layout->icon_rect.x1 : layout->icon_rect.x0;
+ y = layout->icon_rect.y0;
+ break;
+ default:
+ g_assert_not_reached ();
+ x = 0;
+ y = 0;
+ break;
+ }
+ if (layout->position != 0)
+ {
+ switch (layout->side)
+ {
+ case RIGHT_SIDE:
+ y += (is_rtl ? -1 : 1) * (layout->position + height / 2);
+ break;
+ case BOTTOM_SIDE:
+ x += (is_rtl ? 1 : -1 ) * (layout->position + width / 2);
+ break;
+ case LEFT_SIDE:
+ y += (is_rtl ? 1 : -1) * (layout->position + height / 2);
+ break;
+ case TOP_SIDE:
+ x += (is_rtl ? -1 : 1) * (layout->position + width / 2);
+ break;
+ }
+ }
+
+ /* Check to see if emblem fits in current side. */
+ if (x >= layout->icon_rect.x0 && x <= layout->icon_rect.x1
+ && y >= layout->icon_rect.y0 && y <= layout->icon_rect.y1)
+ {
+
+ /* It fits. */
+
+ /* Advance along the side. */
+ switch (layout->side)
+ {
+ case RIGHT_SIDE:
+ case LEFT_SIDE:
+ layout->position += height + EMBLEM_SPACING;
+ break;
+ case BOTTOM_SIDE:
+ case TOP_SIDE:
+ layout->position += width + EMBLEM_SPACING;
+ break;
+ }
+
+ /* Return the rectangle and pixbuf. */
+ *emblem_pixbuf = pixbuf;
+ emblem_rect->x0 = x - width / 2;
+ emblem_rect->y0 = y - height / 2;
+ emblem_rect->x1 = emblem_rect->x0 + width;
+ emblem_rect->y1 = emblem_rect->y0 + height;
+
+ return TRUE;
+ }
+
+ /* It doesn't fit, so move to the next side. */
+ switch (layout->side)
+ {
+ case RIGHT_SIDE:
+ layout->side = is_rtl ? TOP_SIDE : BOTTOM_SIDE;
+ break;
+ case BOTTOM_SIDE:
+ layout->side = is_rtl ? RIGHT_SIDE : LEFT_SIDE;
+ break;
+ case LEFT_SIDE:
+ layout->side = is_rtl ? BOTTOM_SIDE : TOP_SIDE;
+ break;
+ case TOP_SIDE:
+ default:
+ return FALSE;
+ }
+ layout->position = 0;
+ }
+}
+
+static void
+draw_pixbuf (GdkPixbuf *pixbuf, GdkDrawable *drawable, int x, int y)
+{
+ /* FIXME bugzilla.gnome.org 40703:
+ * Dither would be better if we passed dither values.
+ */
+ gdk_draw_pixbuf (drawable, NULL, pixbuf, 0, 0, x, y,
+ gdk_pixbuf_get_width (pixbuf),
+ gdk_pixbuf_get_height (pixbuf),
+ GDK_RGB_DITHER_NORMAL, 0, 0);
+}
+
+/* shared code to highlight or dim the passed-in pixbuf */
+static GdkPixbuf *
+real_map_pixbuf (CajaIconCanvasItem *icon_item)
+{
+ EelCanvas *canvas;
+ char *audio_filename;
+ CajaIconContainer *container;
+ GdkPixbuf *temp_pixbuf, *old_pixbuf, *audio_pixbuf;
+ int emblem_size;
+ guint render_mode, saturation, brightness, lighten;
+
+ temp_pixbuf = icon_item->details->pixbuf;
+ canvas = EEL_CANVAS_ITEM(icon_item)->canvas;
+ container = CAJA_ICON_CONTAINER (canvas);
+
+ g_object_ref (temp_pixbuf);
+
+ if (icon_item->details->is_prelit ||
+ icon_item->details->is_highlighted_for_clipboard)
+ {
+ old_pixbuf = temp_pixbuf;
+
+ gtk_widget_style_get (GTK_WIDGET (container),
+ "prelight_icon_render_mode", &render_mode,
+ "prelight_icon_saturation", &saturation,
+ "prelight_icon_brightness", &brightness,
+ "prelight_icon_lighten", &lighten,
+ NULL);
+
+ if (render_mode > 0 || saturation < 255 || brightness < 255)
+ {
+ temp_pixbuf = eel_gdk_pixbuf_render (temp_pixbuf,
+ render_mode,
+ saturation,
+ brightness,
+ lighten,
+ container->details->prelight_icon_color_rgba);
+ g_object_unref (old_pixbuf);
+ }
+
+
+
+ /* FIXME bugzilla.gnome.org 42471: This hard-wired image is inappropriate to
+ * this level of code, which shouldn't know that the
+ * preview is audio, nor should it have an icon
+ * hard-wired in.
+ */
+
+ /* if the icon is currently being previewed, superimpose an image to indicate that */
+ /* audio is the only kind of previewing right now, so this code isn't as general as it could be */
+ if (icon_item->details->is_active)
+ {
+ emblem_size = caja_icon_get_emblem_size_for_icon_size (gdk_pixbuf_get_width (temp_pixbuf));
+ /* Load the audio symbol. */
+ audio_filename = caja_pixmap_file ("audio.svg");
+ if (audio_filename != NULL)
+ {
+ audio_pixbuf = gdk_pixbuf_new_from_file_at_scale (audio_filename,
+ emblem_size, emblem_size,
+ TRUE,
+ NULL);
+ }
+ else
+ {
+ audio_pixbuf = NULL;
+ }
+
+ /* Composite it onto the icon. */
+ if (audio_pixbuf != NULL)
+ {
+ gdk_pixbuf_composite
+ (audio_pixbuf,
+ temp_pixbuf,
+ 0, 0,
+ gdk_pixbuf_get_width (audio_pixbuf),
+ gdk_pixbuf_get_height (audio_pixbuf),
+ 0, 0,
+ 1.0, 1.0,
+ GDK_INTERP_BILINEAR, 0xFF);
+
+ g_object_unref (audio_pixbuf);
+ }
+
+ g_free (audio_filename);
+ }
+ }
+
+ if (icon_item->details->is_highlighted_for_selection
+ || icon_item->details->is_highlighted_for_drop)
+ {
+ guint color;
+
+ old_pixbuf = temp_pixbuf;
+
+ color = gtk_widget_has_focus (GTK_WIDGET (canvas)) ? CAJA_ICON_CONTAINER (canvas)->details->highlight_color_rgba : CAJA_ICON_CONTAINER (canvas)->details->active_color_rgba;
+
+ temp_pixbuf = eel_create_colorized_pixbuf (temp_pixbuf,
+ EEL_RGBA_COLOR_GET_R (color),
+ EEL_RGBA_COLOR_GET_G (color),
+ EEL_RGBA_COLOR_GET_B (color));
+
+ g_object_unref (old_pixbuf);
+ }
+
+ if (!icon_item->details->is_active
+ && !icon_item->details->is_prelit
+ && !icon_item->details->is_highlighted_for_selection
+ && !icon_item->details->is_highlighted_for_drop)
+ {
+ old_pixbuf = temp_pixbuf;
+
+ gtk_widget_style_get (GTK_WIDGET (container),
+ "normal_icon_render_mode", &render_mode,
+ "normal_icon_saturation", &saturation,
+ "normal_icon_brightness", &brightness,
+ "normal_icon_lighten", &lighten,
+ NULL);
+ if (render_mode > 0 || saturation < 255 || brightness < 255)
+ {
+ /* if theme requests colorization */
+ temp_pixbuf = eel_gdk_pixbuf_render (temp_pixbuf,
+ render_mode,
+ saturation,
+ brightness,
+ lighten,
+ container->details->normal_icon_color_rgba);
+ g_object_unref (old_pixbuf);
+ }
+ }
+
+ return temp_pixbuf;
+}
+
+static GdkPixbuf *
+map_pixbuf (CajaIconCanvasItem *icon_item)
+{
+ if (!(icon_item->details->rendered_pixbuf != NULL
+ && icon_item->details->rendered_is_active == icon_item->details->is_active
+ && icon_item->details->rendered_is_prelit == icon_item->details->is_prelit
+ && icon_item->details->rendered_is_highlighted_for_selection == icon_item->details->is_highlighted_for_selection
+ && icon_item->details->rendered_is_highlighted_for_drop == icon_item->details->is_highlighted_for_drop
+ && icon_item->details->rendered_is_highlighted_for_clipboard == icon_item->details->is_highlighted_for_clipboard
+ && (icon_item->details->is_highlighted_for_selection && icon_item->details->rendered_is_focused == gtk_widget_has_focus (GTK_WIDGET (EEL_CANVAS_ITEM (icon_item)->canvas)))))
+ {
+ if (icon_item->details->rendered_pixbuf != NULL)
+ {
+ g_object_unref (icon_item->details->rendered_pixbuf);
+ }
+ icon_item->details->rendered_pixbuf = real_map_pixbuf (icon_item);
+ icon_item->details->rendered_is_active = icon_item->details->is_active;
+ icon_item->details->rendered_is_prelit = icon_item->details->is_prelit;
+ icon_item->details->rendered_is_highlighted_for_selection = icon_item->details->is_highlighted_for_selection;
+ icon_item->details->rendered_is_highlighted_for_drop = icon_item->details->is_highlighted_for_drop;
+ icon_item->details->rendered_is_highlighted_for_clipboard = icon_item->details->is_highlighted_for_clipboard;
+ icon_item->details->rendered_is_focused = gtk_widget_has_focus (GTK_WIDGET (EEL_CANVAS_ITEM (icon_item)->canvas));
+ }
+
+ g_object_ref (icon_item->details->rendered_pixbuf);
+
+ return icon_item->details->rendered_pixbuf;
+}
+
+static void
+draw_embedded_text (CajaIconCanvasItem *item,
+ GdkDrawable *drawable,
+ int x, int y)
+{
+ GdkGC *gc;
+ GdkRectangle clip_rect;
+ PangoLayout *layout;
+ PangoContext *context;
+ PangoFontDescription *desc;
+
+ if (item->details->embedded_text == NULL ||
+ item->details->embedded_text_rect.width == 0 ||
+ item->details->embedded_text_rect.height == 0)
+ {
+ return;
+ }
+
+ if (item->details->embedded_text_layout != NULL)
+ {
+ layout = g_object_ref (item->details->embedded_text_layout);
+ }
+ else
+ {
+ context = gtk_widget_get_pango_context (GTK_WIDGET (EEL_CANVAS_ITEM (item)->canvas));
+ layout = pango_layout_new (context);
+ pango_layout_set_text (layout, item->details->embedded_text, -1);
+
+ desc = pango_font_description_from_string ("monospace 6");
+ pango_layout_set_font_description (layout, desc);
+ pango_font_description_free (desc);
+
+ if (item->details->is_visible)
+ {
+ item->details->embedded_text_layout = g_object_ref (layout);
+ }
+ }
+
+ gc = gdk_gc_new (drawable);
+
+ clip_rect.x = x + item->details->embedded_text_rect.x;
+ clip_rect.y = y + item->details->embedded_text_rect.y;
+ clip_rect.width = item->details->embedded_text_rect.width;
+ clip_rect.height = item->details->embedded_text_rect.height;
+
+ gdk_gc_set_clip_rectangle (gc, &clip_rect);
+
+ gdk_draw_layout (drawable, gc,
+ x + item->details->embedded_text_rect.x,
+ y + item->details->embedded_text_rect.y,
+ layout);
+
+ g_object_unref (gc);
+ g_object_unref (layout);
+}
+
+/* Draw the icon item for non-anti-aliased mode. */
+static void
+caja_icon_canvas_item_draw (EelCanvasItem *item, GdkDrawable *drawable,
+ GdkEventExpose *expose)
+{
+ CajaIconCanvasItem *icon_item;
+ CajaIconCanvasItemDetails *details;
+ EelIRect icon_rect, emblem_rect;
+ EmblemLayout emblem_layout;
+ GdkPixbuf *emblem_pixbuf, *temp_pixbuf;
+ GdkRectangle draw_rect, pixbuf_rect;
+ gboolean is_rtl;
+
+ icon_item = CAJA_ICON_CANVAS_ITEM (item);
+ details = icon_item->details;
+
+ /* Draw the pixbuf. */
+ if (details->pixbuf == NULL)
+ {
+ return;
+ }
+
+ icon_rect = icon_item->details->canvas_rect;
+
+ /* if the pre-lit or selection flag is set, make a pre-lit or darkened pixbuf and draw that instead */
+ /* and colorize normal pixbuf if rc wants that */
+ temp_pixbuf = map_pixbuf (icon_item);
+ pixbuf_rect.x = icon_rect.x0;
+ pixbuf_rect.y = icon_rect.y0;
+ pixbuf_rect.width = gdk_pixbuf_get_width (temp_pixbuf);
+ pixbuf_rect.height = gdk_pixbuf_get_height (temp_pixbuf);
+ if (gdk_rectangle_intersect (&(expose->area), &pixbuf_rect, &draw_rect))
+ {
+ gdk_draw_pixbuf (drawable,
+ NULL,
+ temp_pixbuf,
+ draw_rect.x - pixbuf_rect.x,
+ draw_rect.y - pixbuf_rect.y,
+ draw_rect.x,
+ draw_rect.y,
+ draw_rect.width,
+ draw_rect.height,
+ GDK_RGB_DITHER_NORMAL,
+ 0,0);
+ }
+ g_object_unref (temp_pixbuf);
+
+ draw_embedded_text (icon_item, drawable, icon_rect.x0, icon_rect.y0);
+
+ is_rtl = caja_icon_container_is_layout_rtl (CAJA_ICON_CONTAINER (item->canvas));
+
+ /* Draw the emblem pixbufs. */
+ emblem_layout_reset (&emblem_layout, icon_item, icon_rect, is_rtl);
+ while (emblem_layout_next (&emblem_layout, &emblem_pixbuf, &emblem_rect, is_rtl))
+ {
+ draw_pixbuf (emblem_pixbuf, drawable, emblem_rect.x0, emblem_rect.y0);
+ }
+
+ /* Draw stretching handles (if necessary). */
+ draw_stretch_handles (icon_item, drawable, &icon_rect);
+
+ /* Draw the label text. */
+ draw_label_text (icon_item, drawable, FALSE, icon_rect);
+}
+
+#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 (CajaIconCanvasItem *item,
+ const char *text)
+{
+ PangoLayout *layout;
+ PangoContext *context;
+ PangoFontDescription *desc;
+ CajaIconContainer *container;
+ EelCanvasItem *canvas_item;
+ GString *str;
+ char *zeroified_text;
+ const char *p;
+
+ canvas_item = EEL_CANVAS_ITEM (item);
+
+ container = CAJA_ICON_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);
+
+ if (container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
+ {
+ if (!caja_icon_container_is_layout_rtl (container))
+ {
+ pango_layout_set_alignment (layout, PANGO_ALIGN_LEFT);
+ }
+ else
+ {
+ pango_layout_set_alignment (layout, PANGO_ALIGN_RIGHT);
+ }
+ }
+ else
+ {
+ 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_font_description_set_size (desc,
+ pango_font_description_get_size (desc) +
+ container->details->font_size_table [container->details->zoom_level]);
+ }
+ 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,
+ CajaIconCanvasItem *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;
+}
+
+static void
+draw_label_layout (CajaIconCanvasItem *item,
+ GdkDrawable *drawable,
+ PangoLayout *layout,
+ gboolean highlight,
+ GdkColor *label_color,
+ int x,
+ int y,
+ GdkGC *gc)
+{
+ if (drawable == NULL)
+ {
+ return;
+ }
+
+ if (item->details->is_renaming)
+ {
+ return;
+ }
+
+ if (!highlight && (CAJA_ICON_CONTAINER (EEL_CANVAS_ITEM (item)->canvas)->details->use_drop_shadows))
+ {
+ /* draw a drop shadow */
+ eel_gdk_draw_layout_with_drop_shadow (drawable, gc,
+ label_color,
+ &gtk_widget_get_style (GTK_WIDGET (EEL_CANVAS_ITEM (item)->canvas))->black,
+ x, y,
+ layout);
+ }
+ else
+ {
+ gdk_draw_layout (drawable, gc,
+ x, y,
+ layout);
+ }
+}
+
+/* handle events */
+
+static int
+caja_icon_canvas_item_event (EelCanvasItem *item, GdkEvent *event)
+{
+ CajaIconCanvasItem *icon_item;
+ GdkCursor *cursor;
+
+ icon_item = CAJA_ICON_CANVAS_ITEM (item);
+
+ switch (event->type)
+ {
+ case GDK_ENTER_NOTIFY:
+ if (!icon_item->details->is_prelit)
+ {
+ icon_item->details->is_prelit = TRUE;
+ caja_icon_canvas_item_invalidate_label_size (icon_item);
+ eel_canvas_item_request_update (item);
+ eel_canvas_item_send_behind (item,
+ CAJA_ICON_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 (((GdkEventAny *)event)->window, cursor);
+ gdk_cursor_unref (cursor);
+ }
+
+ /* FIXME bugzilla.gnome.org 42473:
+ * We should emit our own signal here,
+ * not one from the container; it could hook
+ * up to that signal and emit one of its
+ * own. Doing it this way hard-codes what
+ * "user_data" is. Also, the two signals
+ * should be separate. The "unpreview" signal
+ * does not have a return value.
+ */
+ icon_item->details->is_active = caja_icon_container_emit_preview_signal
+ (CAJA_ICON_CONTAINER (item->canvas),
+ CAJA_ICON_CANVAS_ITEM (item)->user_data,
+ TRUE);
+ }
+ return TRUE;
+
+ case GDK_LEAVE_NOTIFY:
+ if (icon_item->details->is_prelit
+ || icon_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.
+ */
+ /* FIXME bugzilla.gnome.org 42473:
+ * We should emit our own signal here,
+ * not one from the containe; it could hook up
+ * to that signal and emit one of its
+ * ownr. Doing it this way hard-codes what
+ * "user_data" is. Also, the two signals
+ * should be separate. The "unpreview" signal
+ * does not have a return value.
+ */
+ caja_icon_container_emit_preview_signal
+ (CAJA_ICON_CONTAINER (item->canvas),
+ CAJA_ICON_CANVAS_ITEM (item)->user_data,
+ FALSE);
+ icon_item->details->is_prelit = FALSE;
+ icon_item->details->is_active = 0;
+ icon_item->details->is_highlighted_for_drop = FALSE;
+ caja_icon_canvas_item_invalidate_label_size (icon_item);
+ eel_canvas_item_request_update (item);
+
+ /* show default cursor */
+ gdk_window_set_cursor (((GdkEventAny *)event)->window, NULL);
+ }
+ return TRUE;
+
+ default:
+ /* Don't eat up other events; icon container might use them. */
+ return FALSE;
+ }
+}
+
+static gboolean
+hit_test_pixbuf (GdkPixbuf *pixbuf, EelIRect pixbuf_location, EelIRect probe_rect)
+{
+ EelIRect relative_rect, pixbuf_rect;
+ int x, y;
+ guint8 *pixel;
+
+ /* You can get here without a pixbuf in some strange cases. */
+ if (pixbuf == NULL)
+ {
+ return FALSE;
+ }
+
+ /* Check to see if it's within the rectangle at all. */
+ relative_rect.x0 = probe_rect.x0 - pixbuf_location.x0;
+ relative_rect.y0 = probe_rect.y0 - pixbuf_location.y0;
+ relative_rect.x1 = probe_rect.x1 - pixbuf_location.x0;
+ relative_rect.y1 = probe_rect.y1 - pixbuf_location.y0;
+ pixbuf_rect.x0 = 0;
+ pixbuf_rect.y0 = 0;
+ pixbuf_rect.x1 = gdk_pixbuf_get_width (pixbuf);
+ pixbuf_rect.y1 = gdk_pixbuf_get_height (pixbuf);
+ eel_irect_intersect (&relative_rect, &relative_rect, &pixbuf_rect);
+ if (eel_irect_is_empty (&relative_rect))
+ {
+ return FALSE;
+ }
+
+ /* If there's no alpha channel, it's opaque and we have a hit. */
+ if (!gdk_pixbuf_get_has_alpha (pixbuf))
+ {
+ return TRUE;
+ }
+ g_assert (gdk_pixbuf_get_n_channels (pixbuf) == 4);
+
+ /* Check the alpha channel of the pixel to see if we have a hit. */
+ for (x = relative_rect.x0; x < relative_rect.x1; x++)
+ {
+ for (y = relative_rect.y0; y < relative_rect.y1; y++)
+ {
+ pixel = gdk_pixbuf_get_pixels (pixbuf)
+ + y * gdk_pixbuf_get_rowstride (pixbuf)
+ + x * 4;
+ if (pixel[3] > 1)
+ {
+ return TRUE;
+ }
+ }
+ }
+ return FALSE;
+}
+
+static gboolean
+hit_test (CajaIconCanvasItem *icon_item, EelIRect canvas_rect)
+{
+ CajaIconCanvasItemDetails *details;
+ EelIRect emblem_rect;
+ EmblemLayout emblem_layout;
+ GdkPixbuf *emblem_pixbuf;
+ gboolean is_rtl;
+
+ details = icon_item->details;
+
+ /* Quick check to see if the rect hits the icon, text or emblems at all. */
+ if (!eel_irect_hits_irect (icon_item->details->canvas_rect, canvas_rect)
+ && (!eel_irect_hits_irect (details->text_rect, canvas_rect))
+ && (!eel_irect_hits_irect (details->emblem_rect, canvas_rect)))
+ {
+ return FALSE;
+ }
+
+ /* Check for hits in the stretch handles. */
+ if (hit_test_stretch_handle (icon_item, canvas_rect, NULL))
+ {
+ return TRUE;
+ }
+
+ /* Check for hit in the icon. */
+ if (eel_irect_hits_irect (icon_item->details->canvas_rect, canvas_rect))
+ {
+ return TRUE;
+ }
+
+ /* Check for hit in the text. */
+ if (eel_irect_hits_irect (details->text_rect, canvas_rect)
+ && !icon_item->details->is_renaming)
+ {
+ return TRUE;
+ }
+
+ is_rtl = caja_icon_container_is_layout_rtl (CAJA_ICON_CONTAINER (EEL_CANVAS_ITEM (icon_item)->canvas));
+
+ /* Check for hit in the emblem pixbufs. */
+ emblem_layout_reset (&emblem_layout, icon_item, icon_item->details->canvas_rect, is_rtl);
+ while (emblem_layout_next (&emblem_layout, &emblem_pixbuf, &emblem_rect, is_rtl))
+ {
+ if (hit_test_pixbuf (emblem_pixbuf, emblem_rect, canvas_rect))
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/* Point handler for the icon canvas item. */
+static double
+caja_icon_canvas_item_point (EelCanvasItem *item, double x, double y, int cx, int cy,
+ EelCanvasItem **actual_item)
+{
+ EelIRect canvas_rect;
+
+ *actual_item = item;
+ canvas_rect.x0 = cx;
+ canvas_rect.y0 = cy;
+ canvas_rect.x1 = cx + 1;
+ canvas_rect.y1 = cy + 1;
+ if (hit_test (CAJA_ICON_CANVAS_ITEM (item), canvas_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
+caja_icon_canvas_item_translate (EelCanvasItem *item, double dx, double dy)
+{
+ CajaIconCanvasItem *icon_item;
+ CajaIconCanvasItemDetails *details;
+
+ icon_item = CAJA_ICON_CANVAS_ITEM (item);
+ details = icon_item->details;
+
+ details->x += dx;
+ details->y += dy;
+}
+
+void
+caja_icon_canvas_item_get_bounds_for_layout (CajaIconCanvasItem *icon_item,
+ double *x1, double *y1, double *x2, double *y2)
+{
+ CajaIconCanvasItemDetails *details;
+ EelIRect *total_rect;
+
+ details = icon_item->details;
+
+ caja_icon_canvas_item_ensure_bounds_up_to_date (icon_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
+caja_icon_canvas_item_get_bounds_for_entire_item (CajaIconCanvasItem *icon_item,
+ double *x1, double *y1, double *x2, double *y2)
+{
+ CajaIconCanvasItemDetails *details;
+ EelIRect *total_rect;
+
+ details = icon_item->details;
+
+ caja_icon_canvas_item_ensure_bounds_up_to_date (icon_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 icon canvas item. */
+static void
+caja_icon_canvas_item_bounds (EelCanvasItem *item,
+ double *x1, double *y1, double *x2, double *y2)
+{
+ CajaIconCanvasItem *icon_item;
+ CajaIconCanvasItemDetails *details;
+ EelIRect *total_rect;
+
+ icon_item = CAJA_ICON_CANVAS_ITEM (item);
+ details = icon_item->details;
+
+ g_assert (x1 != NULL);
+ g_assert (y1 != NULL);
+ g_assert (x2 != NULL);
+ g_assert (y2 != NULL);
+
+ caja_icon_canvas_item_ensure_bounds_up_to_date (icon_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
+caja_icon_canvas_item_ensure_bounds_up_to_date (CajaIconCanvasItem *icon_item)
+{
+ CajaIconCanvasItemDetails *details;
+ EelIRect icon_rect, emblem_rect, icon_rect_raw;
+ 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;
+ EmblemLayout emblem_layout;
+ GdkPixbuf *emblem_pixbuf;
+ gboolean is_rtl;
+
+ details = icon_item->details;
+ item = EEL_CANVAS_ITEM (icon_item);
+
+ if (!details->bounds_cached)
+ {
+ measure_label_text (icon_item);
+
+ pixels_per_unit = EEL_CANVAS_ITEM (item)->canvas->pixels_per_unit;
+
+ /* Compute raw and scaled icon rectangle. */
+ icon_rect.x0 = 0;
+ icon_rect.y0 = 0;
+ icon_rect_raw.x0 = 0;
+ icon_rect_raw.y0 = 0;
+ if (details->pixbuf == NULL)
+ {
+ icon_rect.x1 = icon_rect.x0;
+ icon_rect.y1 = icon_rect.y0;
+ icon_rect_raw.x1 = icon_rect_raw.x0;
+ icon_rect_raw.y1 = icon_rect_raw.y0;
+ }
+ else
+ {
+ icon_rect_raw.x1 = icon_rect_raw.x0 + gdk_pixbuf_get_width (details->pixbuf);
+ icon_rect_raw.y1 = icon_rect_raw.y0 + gdk_pixbuf_get_height (details->pixbuf);
+ icon_rect.x1 = icon_rect_raw.x1 / pixels_per_unit;
+ icon_rect.y1 = icon_rect_raw.y1 / pixels_per_unit;
+ }
+
+ /* Compute text rectangle. */
+ text_rect = compute_text_rectangle (icon_item, icon_rect, FALSE, BOUNDS_USAGE_FOR_DISPLAY);
+ text_rect_for_layout = compute_text_rectangle (icon_item, icon_rect, FALSE, BOUNDS_USAGE_FOR_LAYOUT);
+ text_rect_for_entire_text = compute_text_rectangle (icon_item, icon_rect, FALSE, BOUNDS_USAGE_FOR_ENTIRE_ITEM);
+
+ is_rtl = caja_icon_container_is_layout_rtl (CAJA_ICON_CONTAINER (item->canvas));
+
+ /* Compute total rectangle, adding in emblem rectangles. */
+ 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);
+ emblem_layout_reset (&emblem_layout, icon_item, icon_rect_raw, is_rtl);
+ while (emblem_layout_next (&emblem_layout, &emblem_pixbuf, &emblem_rect, is_rtl))
+ {
+ emblem_rect.x0 = floor (emblem_rect.x0 / pixels_per_unit);
+ emblem_rect.y0 = floor (emblem_rect.y0 / pixels_per_unit);
+ emblem_rect.x1 = ceil (emblem_rect.x1 / pixels_per_unit);
+ emblem_rect.y1 = ceil (emblem_rect.y1 / pixels_per_unit);
+
+ eel_irect_union (&total_rect, &total_rect, &emblem_rect);
+ eel_irect_union (&total_rect_for_layout, &total_rect_for_layout, &emblem_rect);
+ eel_irect_union (&total_rect_for_entire_text, &total_rect_for_entire_text, &emblem_rect);
+ }
+
+ 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 icon only, in world coordinates. */
+EelDRect
+caja_icon_canvas_item_get_icon_rectangle (const CajaIconCanvasItem *item)
+{
+ EelDRect rectangle;
+ double pixels_per_unit;
+ GdkPixbuf *pixbuf;
+
+ g_return_val_if_fail (CAJA_IS_ICON_CANVAS_ITEM (item), eel_drect_empty);
+
+ rectangle.x0 = item->details->x;
+ rectangle.y0 = item->details->y;
+
+ pixbuf = item->details->pixbuf;
+
+ pixels_per_unit = EEL_CANVAS_ITEM (item)->canvas->pixels_per_unit;
+ rectangle.x1 = rectangle.x0 + (pixbuf == NULL ? 0 : gdk_pixbuf_get_width (pixbuf)) / pixels_per_unit;
+ rectangle.y1 = rectangle.y0 + (pixbuf == NULL ? 0 : gdk_pixbuf_get_height (pixbuf)) / 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;
+}
+
+EelDRect
+caja_icon_canvas_item_get_text_rectangle (CajaIconCanvasItem *item,
+ gboolean for_layout)
+{
+ /* FIXME */
+ EelIRect icon_rectangle;
+ EelIRect text_rectangle;
+ EelDRect ret;
+ double pixels_per_unit;
+ GdkPixbuf *pixbuf;
+
+ g_return_val_if_fail (CAJA_IS_ICON_CANVAS_ITEM (item), eel_drect_empty);
+
+ icon_rectangle.x0 = item->details->x;
+ icon_rectangle.y0 = item->details->y;
+
+ pixbuf = item->details->pixbuf;
+
+ pixels_per_unit = EEL_CANVAS_ITEM (item)->canvas->pixels_per_unit;
+ icon_rectangle.x1 = icon_rectangle.x0 + (pixbuf == NULL ? 0 : gdk_pixbuf_get_width (pixbuf)) / pixels_per_unit;
+ icon_rectangle.y1 = icon_rectangle.y0 + (pixbuf == NULL ? 0 : gdk_pixbuf_get_height (pixbuf)) / pixels_per_unit;
+
+ measure_label_text (item);
+
+ text_rectangle = compute_text_rectangle (item, icon_rectangle, FALSE,
+ for_layout ? BOUNDS_USAGE_FOR_LAYOUT : BOUNDS_USAGE_FOR_DISPLAY);
+
+ ret.x0 = text_rectangle.x0;
+ ret.y0 = text_rectangle.y0;
+ ret.x1 = text_rectangle.x1;
+ ret.y1 = text_rectangle.y1;
+
+ eel_canvas_item_i2w (EEL_CANVAS_ITEM (item),
+ &ret.x0,
+ &ret.y0);
+ eel_canvas_item_i2w (EEL_CANVAS_ITEM (item),
+ &ret.x1,
+ &ret.y1);
+
+ return ret;
+}
+
+
+/* Get the rectangle of the icon only, in canvas coordinates. */
+static void
+get_icon_canvas_rectangle (CajaIconCanvasItem *item,
+ EelIRect *rect)
+{
+ GdkPixbuf *pixbuf;
+
+ g_assert (CAJA_IS_ICON_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);
+
+ pixbuf = item->details->pixbuf;
+
+ rect->x1 = rect->x0 + (pixbuf == NULL ? 0 : gdk_pixbuf_get_width (pixbuf));
+ rect->y1 = rect->y0 + (pixbuf == NULL ? 0 : gdk_pixbuf_get_height (pixbuf));
+}
+
+void
+caja_icon_canvas_item_set_show_stretch_handles (CajaIconCanvasItem *item,
+ gboolean show_stretch_handles)
+{
+ g_return_if_fail (CAJA_IS_ICON_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 (CajaIconCanvasItem *item,
+ EelIRect probe_canvas_rect,
+ GtkCornerType *corner)
+{
+ EelIRect icon_rect;
+ GdkPixbuf *knob_pixbuf;
+ int knob_width, knob_height;
+ int hit_corner;
+
+ g_assert (CAJA_IS_ICON_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 icon at all. */
+ icon_rect = item->details->canvas_rect;
+ if (!eel_irect_hits_irect (probe_canvas_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_canvas_rect.x0 < icon_rect.x0 + knob_width)
+ {
+ if (probe_canvas_rect.y0 < icon_rect.y0 + knob_height)
+ hit_corner = GTK_CORNER_TOP_LEFT;
+ else if (probe_canvas_rect.y1 >= icon_rect.y1 - knob_height)
+ hit_corner = GTK_CORNER_BOTTOM_LEFT;
+ }
+ else if (probe_canvas_rect.x1 >= icon_rect.x1 - knob_width)
+ {
+ if (probe_canvas_rect.y0 < icon_rect.y0 + knob_height)
+ hit_corner = GTK_CORNER_TOP_RIGHT;
+ else if (probe_canvas_rect.y1 >= icon_rect.y1 - knob_height)
+ hit_corner = GTK_CORNER_BOTTOM_RIGHT;
+ }
+ if (corner)
+ *corner = hit_corner;
+
+ return hit_corner != -1;
+}
+
+gboolean
+caja_icon_canvas_item_hit_test_stretch_handles (CajaIconCanvasItem *item,
+ EelDPoint world_point,
+ GtkCornerType *corner)
+{
+ EelIRect canvas_rect;
+
+ g_return_val_if_fail (CAJA_IS_ICON_CANVAS_ITEM (item), FALSE);
+
+ eel_canvas_w2c (EEL_CANVAS_ITEM (item)->canvas,
+ world_point.x,
+ world_point.y,
+ &canvas_rect.x0,
+ &canvas_rect.y0);
+ canvas_rect.x1 = canvas_rect.x0 + 1;
+ canvas_rect.y1 = canvas_rect.y0 + 1;
+ return hit_test_stretch_handle (item, canvas_rect, corner);
+}
+
+/* caja_icon_canvas_item_hit_test_rectangle
+ *
+ * Check and see if there is an intersection between the item and the
+ * canvas rect.
+ */
+gboolean
+caja_icon_canvas_item_hit_test_rectangle (CajaIconCanvasItem *item, EelIRect canvas_rect)
+{
+ g_return_val_if_fail (CAJA_IS_ICON_CANVAS_ITEM (item), FALSE);
+
+ return hit_test (item, canvas_rect);
+}
+
+const char *
+caja_icon_canvas_item_get_editable_text (CajaIconCanvasItem *icon_item)
+{
+ g_return_val_if_fail (CAJA_IS_ICON_CANVAS_ITEM (icon_item), NULL);
+
+ return icon_item->details->editable_text;
+}
+
+void
+caja_icon_canvas_item_set_renaming (CajaIconCanvasItem *item, gboolean state)
+{
+ g_return_if_fail (CAJA_IS_ICON_CANVAS_ITEM (item));
+ g_return_if_fail (state == FALSE || state == TRUE);
+
+ if (!item->details->is_renaming == !state)
+ {
+ return;
+ }
+
+ item->details->is_renaming = state;
+ eel_canvas_item_request_update (EEL_CANVAS_ITEM (item));
+}
+
+double
+caja_icon_canvas_item_get_max_text_width (CajaIconCanvasItem *item)
+{
+ EelCanvasItem *canvas_item;
+ CajaIconContainer *container;
+
+ canvas_item = EEL_CANVAS_ITEM (item);
+ container = CAJA_ICON_CONTAINER (canvas_item->canvas);
+
+ if (caja_icon_container_is_tighter_layout (container))
+ {
+ return MAX_TEXT_WIDTH_TIGHTER * canvas_item->canvas->pixels_per_unit;
+ }
+ else
+ {
+
+ if (container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
+ {
+ if (container->details->layout_mode == CAJA_ICON_LAYOUT_T_B_L_R ||
+ container->details->layout_mode == CAJA_ICON_LAYOUT_T_B_R_L)
+ {
+ if (container->details->all_columns_same_width)
+ {
+ return MAX_TEXT_WIDTH_BESIDE_TOP_TO_BOTTOM * canvas_item->canvas->pixels_per_unit;
+ }
+ else
+ {
+ return -1;
+ }
+ }
+ else
+ {
+ return MAX_TEXT_WIDTH_BESIDE * canvas_item->canvas->pixels_per_unit;
+ }
+ }
+ else
+ {
+ return MAX_TEXT_WIDTH_STANDARD * canvas_item->canvas->pixels_per_unit;
+ }
+
+
+ }
+
+}
+
+/* CajaIconCanvasItemAccessible */
+
+static CajaIconCanvasItemAccessiblePrivate *
+accessible_get_priv (AtkObject *accessible)
+{
+ CajaIconCanvasItemAccessiblePrivate *priv;
+
+ priv = g_object_get_qdata (G_OBJECT (accessible),
+ accessible_private_data_quark);
+
+ return priv;
+}
+
+/* AtkAction interface */
+
+static gboolean
+caja_icon_canvas_item_accessible_idle_do_action (gpointer data)
+{
+ CajaIconCanvasItem *item;
+ CajaIconCanvasItemAccessibleActionContext *ctx;
+ CajaIcon *icon;
+ CajaIconContainer *container;
+ GList* selection;
+ GList file_list;
+ GdkEventButton button_event = { 0 };
+ gint action_number;
+
+ container = CAJA_ICON_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 = caja_icon_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
+caja_icon_canvas_item_accessible_do_action (AtkAction *accessible, int i)
+{
+ CajaIconCanvasItem *item;
+ CajaIconCanvasItemAccessibleActionContext *ctx;
+ CajaIcon *icon;
+ CajaIconContainer *container;
+
+ g_assert (i < LAST_ACTION);
+
+ item = eel_accessibility_get_gobject (ATK_OBJECT (accessible));
+ if (!item)
+ {
+ return FALSE;
+ }
+ icon = item->user_data;
+ container = CAJA_ICON_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 (CajaIconCanvasItemAccessibleActionContext, 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 (caja_icon_canvas_item_accessible_idle_do_action, container);
+ }
+ break;
+ default :
+ g_warning ("Invalid action passed to CajaIconCanvasItemAccessible::do_action");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static int
+caja_icon_canvas_item_accessible_get_n_actions (AtkAction *accessible)
+{
+ return LAST_ACTION;
+}
+
+static const char *
+caja_icon_canvas_item_accessible_action_get_description (AtkAction *accessible,
+ int i)
+{
+ CajaIconCanvasItemAccessiblePrivate *priv;
+
+ g_assert (i < LAST_ACTION);
+
+ priv = accessible_get_priv (ATK_OBJECT (accessible));
+ if (priv->action_descriptions[i])
+ {
+ return priv->action_descriptions[i];
+ }
+ else
+ {
+ return caja_icon_canvas_item_accessible_action_descriptions[i];
+ }
+}
+
+static const char *
+caja_icon_canvas_item_accessible_action_get_name (AtkAction *accessible, int i)
+{
+ g_assert (i < LAST_ACTION);
+
+ return caja_icon_canvas_item_accessible_action_names[i];
+}
+
+static const char *
+caja_icon_canvas_item_accessible_action_get_keybinding (AtkAction *accessible,
+ int i)
+{
+ g_assert (i < LAST_ACTION);
+
+ return NULL;
+}
+
+static gboolean
+caja_icon_canvas_item_accessible_action_set_description (AtkAction *accessible,
+ int i,
+ const char *description)
+{
+ CajaIconCanvasItemAccessiblePrivate *priv;
+
+ g_assert (i < LAST_ACTION);
+
+ priv = accessible_get_priv (ATK_OBJECT (accessible));
+
+ if (priv->action_descriptions[i])
+ {
+ g_free (priv->action_descriptions[i]);
+ }
+ priv->action_descriptions[i] = g_strdup (description);
+
+ return TRUE;
+}
+
+static void
+caja_icon_canvas_item_accessible_action_interface_init (AtkActionIface *iface)
+{
+ iface->do_action = caja_icon_canvas_item_accessible_do_action;
+ iface->get_n_actions = caja_icon_canvas_item_accessible_get_n_actions;
+ iface->get_description = caja_icon_canvas_item_accessible_action_get_description;
+ iface->get_keybinding = caja_icon_canvas_item_accessible_action_get_keybinding;
+ iface->get_name = caja_icon_canvas_item_accessible_action_get_name;
+ iface->set_description = caja_icon_canvas_item_accessible_action_set_description;
+}
+
+static const gchar* caja_icon_canvas_item_accessible_get_name(AtkObject* accessible)
+{
+ CajaIconCanvasItem* item;
+
+ if (accessible->name)
+ {
+ return accessible->name;
+ }
+
+ item = eel_accessibility_get_gobject(accessible);
+
+ if (!item)
+ {
+ return NULL;
+ }
+
+ return item->details->editable_text;
+}
+
+static const gchar* caja_icon_canvas_item_accessible_get_description(AtkObject* accessible)
+{
+ CajaIconCanvasItem* item;
+
+ item = eel_accessibility_get_gobject(accessible);
+
+ if (!item)
+ {
+ return NULL;
+ }
+
+ return item->details->additional_text;
+}
+
+static AtkObject *
+caja_icon_canvas_item_accessible_get_parent (AtkObject *accessible)
+{
+ CajaIconCanvasItem *item;
+
+ item = eel_accessibility_get_gobject (accessible);
+ if (!item)
+ {
+ return NULL;
+ }
+
+ return gtk_widget_get_accessible (GTK_WIDGET (EEL_CANVAS_ITEM (item)->canvas));
+}
+
+static int
+caja_icon_canvas_item_accessible_get_index_in_parent (AtkObject *accessible)
+{
+ CajaIconCanvasItem *item;
+ CajaIconContainer *container;
+ GList *l;
+ CajaIcon *icon;
+ int i;
+
+ item = eel_accessibility_get_gobject (accessible);
+ if (!item)
+ {
+ return -1;
+ }
+
+ container = CAJA_ICON_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 AtkStateSet*
+caja_icon_canvas_item_accessible_ref_state_set (AtkObject *accessible)
+{
+ AtkStateSet *state_set;
+ CajaIconCanvasItem *item;
+ CajaIconContainer *container;
+ CajaIcon *icon;
+ GList *l;
+ gboolean one_item_selected;
+
+ state_set = ATK_OBJECT_CLASS (accessible_parent_class)->ref_state_set (accessible);
+
+ item = eel_accessibility_get_gobject (accessible);
+ if (!item)
+ {
+ atk_state_set_add_state (state_set, ATK_STATE_DEFUNCT);
+ return state_set;
+ }
+ container = CAJA_ICON_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)
+ {
+
+ one_item_selected = FALSE;
+ l = container->details->icons;
+ while (l)
+ {
+ icon = l->data;
+
+ if (icon->item == item)
+ {
+ if (icon->is_selected)
+ {
+ one_item_selected = TRUE;
+ }
+ else
+ {
+ break;
+ }
+ }
+ else if (icon->is_selected)
+ {
+ one_item_selected = FALSE;
+ break;
+ }
+
+ l = l->next;
+ }
+
+ if (one_item_selected)
+ {
+ atk_state_set_add_state (state_set, ATK_STATE_FOCUSED);
+ }
+ }
+
+ return state_set;
+}
+
+static void
+caja_icon_canvas_item_accessible_initialize (AtkObject *accessible,
+ gpointer data)
+{
+ CajaIconCanvasItemAccessiblePrivate *priv;
+
+ if (ATK_OBJECT_CLASS (accessible_parent_class)->initialize)
+ {
+ ATK_OBJECT_CLASS (accessible_parent_class)->initialize (accessible, data);
+ }
+
+ priv = g_new0 (CajaIconCanvasItemAccessiblePrivate, 1);
+ g_object_set_qdata (G_OBJECT (accessible),
+ accessible_private_data_quark,
+ priv);
+}
+
+static void
+caja_icon_canvas_item_accessible_finalize (GObject *object)
+{
+ CajaIconCanvasItemAccessiblePrivate *priv;
+ int i;
+
+ priv = accessible_get_priv (ATK_OBJECT (object));
+
+ for (i = 0; i < LAST_ACTION; i++)
+ {
+ g_free (priv->action_descriptions[i]);
+ }
+ g_free (priv->image_description);
+ g_free (priv->description);
+
+ g_free (priv);
+
+ G_OBJECT_CLASS (accessible_parent_class)->finalize (object);
+}
+
+static void
+caja_icon_canvas_item_accessible_class_init (AtkObjectClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ accessible_parent_class = g_type_class_peek_parent (klass);
+
+ gobject_class->finalize = caja_icon_canvas_item_accessible_finalize;
+
+ klass->get_name = caja_icon_canvas_item_accessible_get_name;
+ klass->get_description = caja_icon_canvas_item_accessible_get_description;
+ klass->get_parent = caja_icon_canvas_item_accessible_get_parent;
+ klass->get_index_in_parent = caja_icon_canvas_item_accessible_get_index_in_parent;
+ klass->ref_state_set = caja_icon_canvas_item_accessible_ref_state_set;
+ klass->initialize = caja_icon_canvas_item_accessible_initialize;
+ accessible_private_data_quark = g_quark_from_static_string ("icon-canvas-item-accessible-private-data");
+}
+
+
+static const gchar* caja_icon_canvas_item_accessible_get_image_description(AtkImage* image)
+{
+ CajaIconCanvasItemAccessiblePrivate* priv;
+ CajaIconCanvasItem* item;
+ CajaIcon* icon;
+ CajaIconContainer* container;
+ char* description;
+
+ priv = accessible_get_priv(ATK_OBJECT(image));
+
+ if (priv->image_description)
+ {
+ return priv->image_description;
+ }
+ else
+ {
+ item = eel_accessibility_get_gobject(ATK_OBJECT (image));
+
+ if (item == NULL)
+ {
+ return NULL;
+ }
+
+ icon = item->user_data;
+ container = CAJA_ICON_CONTAINER(EEL_CANVAS_ITEM(item)->canvas);
+ description = caja_icon_container_get_icon_description(container, icon->data);
+ g_free(priv->description);
+ priv->description = description;
+
+ return priv->description;
+ }
+}
+
+static void
+caja_icon_canvas_item_accessible_get_image_size
+(AtkImage *image,
+ gint *width,
+ gint *height)
+{
+ CajaIconCanvasItem *item;
+
+ item = eel_accessibility_get_gobject (ATK_OBJECT (image));
+
+ if (!item || !item->details->pixbuf)
+ {
+ *width = *height = 0;
+ }
+ else
+ {
+ *width = gdk_pixbuf_get_width (item->details->pixbuf);
+ *height = gdk_pixbuf_get_height (item->details->pixbuf);
+ }
+}
+
+static void
+caja_icon_canvas_item_accessible_get_image_position
+(AtkImage *image,
+ gint *x,
+ gint *y,
+ AtkCoordType coord_type)
+{
+ CajaIconCanvasItem *item;
+ gint x_offset, y_offset, itmp;
+
+ item = eel_accessibility_get_gobject (ATK_OBJECT (image));
+ if (!item)
+ {
+ return;
+ }
+ if (!item->details->canvas_rect.x0 && !item->details->canvas_rect.x1)
+ {
+ return;
+ }
+ else
+ {
+ x_offset = 0;
+ y_offset = 0;
+ if (item->details->text_width)
+ {
+ itmp = item->details->canvas_rect.x0 -
+ item->details->text_rect.x0;
+ if (itmp > x_offset)
+ {
+ x_offset = itmp;
+ }
+ itmp = item->details->canvas_rect.y0 -
+ item->details->text_rect.y0;
+ if (itmp > y_offset)
+ {
+ y_offset = itmp;
+ }
+ }
+ if (item->details->emblem_pixbufs)
+ {
+ itmp = item->details->canvas_rect.x0 -
+ item->details->emblem_rect.x0;
+ if (itmp > x_offset)
+ {
+ x_offset = itmp;
+ }
+ itmp = item->details->canvas_rect.y0 -
+ item->details->emblem_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
+caja_icon_canvas_item_accessible_set_image_description
+(AtkImage *image,
+ const gchar *description)
+{
+ CajaIconCanvasItemAccessiblePrivate *priv;
+
+ priv = accessible_get_priv (ATK_OBJECT (image));
+
+ g_free (priv->image_description);
+ priv->image_description = g_strdup (description);
+
+ return TRUE;
+}
+
+static void
+caja_icon_canvas_item_accessible_image_interface_init (AtkImageIface *iface)
+{
+ iface->get_image_description = caja_icon_canvas_item_accessible_get_image_description;
+ iface->set_image_description = caja_icon_canvas_item_accessible_set_image_description;
+ iface->get_image_size = caja_icon_canvas_item_accessible_get_image_size;
+ iface->get_image_position = caja_icon_canvas_item_accessible_get_image_position;
+}
+
+static gint
+caja_icon_canvas_item_accessible_get_offset_at_point (AtkText *text,
+ gint x,
+ gint y,
+ AtkCoordType coords)
+{
+ gint real_x, real_y, real_width, real_height;
+ CajaIconCanvasItem *item;
+ gint editable_height;
+ gint offset = 0;
+ gint index;
+ PangoLayout *layout, *editable_layout, *additional_layout;
+ PangoRectangle rect0;
+ char *icon_text;
+ gboolean have_editable;
+ gboolean have_additional;
+ gint text_offset;
+
+ atk_component_get_extents (ATK_COMPONENT (text), &real_x, &real_y,
+ &real_width, &real_height, coords);
+
+ x -= real_x;
+ y -= real_y;
+
+ item = eel_accessibility_get_gobject (ATK_OBJECT (text));
+
+ if (item->details->pixbuf)
+ {
+ y -= gdk_pixbuf_get_height (item->details->pixbuf);
+ }
+ 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;
+ icon_text = item->details->additional_text;
+ y -= editable_height + LABEL_LINE_SPACING;
+ }
+ else
+ {
+ layout = editable_layout;
+ icon_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;
+ icon_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 (icon_text, -1);
+ }
+ else
+ {
+ offset = g_utf8_pointer_to_offset (icon_text, icon_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
+caja_icon_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 *icon_text;
+ CajaIconCanvasItem *item;
+ PangoLayout *layout, *editable_layout, *additional_layout;
+ PangoRectangle rect;
+ PangoRectangle rect0;
+ gboolean have_editable;
+ gint text_offset;
+
+ atk_component_get_position (ATK_COMPONENT (text), &pos_x, &pos_y, coords);
+ item = eel_accessibility_get_gobject (ATK_OBJECT (text));
+
+ if (item->details->pixbuf)
+ {
+ pos_y += gdk_pixbuf_get_height (item->details->pixbuf);
+ }
+
+ 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)
+ {
+ icon_text = item->details->editable_text;
+ layout = editable_layout;
+ }
+ else
+ {
+ offset -= len;
+ icon_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 (icon_text, offset) - icon_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
+caja_icon_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 = caja_icon_canvas_item_accessible_get_character_extents;
+ iface->get_offset_at_point = caja_icon_canvas_item_accessible_get_offset_at_point;
+}
+
+static GType
+caja_icon_canvas_item_accessible_get_type (void)
+{
+ static GType type = 0;
+
+ if (!type)
+ {
+ const GInterfaceInfo atk_image_info =
+ {
+ (GInterfaceInitFunc)
+ caja_icon_canvas_item_accessible_image_interface_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL
+ };
+
+ const GInterfaceInfo atk_text_info =
+ {
+ (GInterfaceInitFunc)
+ caja_icon_canvas_item_accessible_text_interface_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL
+ };
+
+ const GInterfaceInfo atk_action_info =
+ {
+ (GInterfaceInitFunc)
+ caja_icon_canvas_item_accessible_action_interface_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL
+ };
+
+ type = eel_accessibility_create_derived_type (
+ "CajaIconCanvasItemAccessibility",
+ EEL_TYPE_CANVAS_ITEM,
+ caja_icon_canvas_item_accessible_class_init);
+
+ if (type != G_TYPE_INVALID)
+ {
+ g_type_add_interface_static (
+ type, ATK_TYPE_IMAGE, &atk_image_info);
+
+ g_type_add_interface_static (
+ type, ATK_TYPE_TEXT, &atk_text_info);
+
+ g_type_add_interface_static (
+ type, ATK_TYPE_ACTION, &atk_action_info);
+
+ }
+ }
+
+ return type;
+}
+
+static AtkObject *
+caja_icon_canvas_item_accessible_create (GObject *for_object)
+{
+ GType type;
+ AtkObject *accessible;
+ CajaIconCanvasItem *item;
+ GString *item_text;
+
+ item = CAJA_ICON_CANVAS_ITEM (for_object);
+ g_assert (item != NULL);
+
+ type = caja_icon_canvas_item_accessible_get_type ();
+
+ if (type == G_TYPE_INVALID)
+ {
+ return atk_no_op_object_new (for_object);
+ }
+
+ 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 (type, NULL);
+ accessible = eel_accessibility_set_atk_object_return
+ (for_object, accessible);
+ atk_object_set_role (accessible, ATK_ROLE_ICON);
+ return accessible;
+}
+
+EEL_ACCESSIBLE_FACTORY (caja_icon_canvas_item_accessible_get_type (),
+ "CajaIconCanvasItemAccessibilityFactory",
+ caja_icon_canvas_item_accessible,
+ caja_icon_canvas_item_accessible_create)
+
+
+static GailTextUtil *
+caja_icon_canvas_item_get_text (GObject *text)
+{
+ return CAJA_ICON_CANVAS_ITEM (text)->details->text_util;
+}
+
+static void
+caja_icon_canvas_item_text_interface_init (EelAccessibleTextIface *iface)
+{
+ iface->get_text = caja_icon_canvas_item_get_text;
+}
+
+void
+caja_icon_canvas_item_set_entire_text (CajaIconCanvasItem *item,
+ gboolean entire_text)
+{
+ if (item->details->entire_text != entire_text)
+ {
+ item->details->entire_text = entire_text;
+
+ caja_icon_canvas_item_invalidate_label_size (item);
+ eel_canvas_item_request_update (EEL_CANVAS_ITEM (item));
+ }
+}
+
+
+/* Class initialization function for the icon canvas item. */
+static void
+caja_icon_canvas_item_class_init (CajaIconCanvasItemClass *class)
+{
+ GObjectClass *object_class;
+ EelCanvasItemClass *item_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ item_class = EEL_CANVAS_ITEM_CLASS (class);
+
+ object_class->finalize = caja_icon_canvas_item_finalize;
+ object_class->set_property = caja_icon_canvas_item_set_property;
+ object_class->get_property = caja_icon_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 = caja_icon_canvas_item_update;
+ item_class->draw = caja_icon_canvas_item_draw;
+ item_class->point = caja_icon_canvas_item_point;
+ item_class->translate = caja_icon_canvas_item_translate;
+ item_class->bounds = caja_icon_canvas_item_bounds;
+ item_class->event = caja_icon_canvas_item_event;
+
+ EEL_OBJECT_SET_FACTORY (CAJA_TYPE_ICON_CANVAS_ITEM,
+ caja_icon_canvas_item_accessible);
+
+ g_type_class_add_private (class, sizeof (CajaIconCanvasItemDetails));
+}
+
+
diff --git a/libcaja-private/caja-icon-canvas-item.h b/libcaja-private/caja-icon-canvas-item.h
new file mode 100644
index 00000000..e284d221
--- /dev/null
+++ b/libcaja-private/caja-icon-canvas-item.h
@@ -0,0 +1,123 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* Caja - Icon canvas item class for icon container.
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ *
+ * Author: Andy Hertzfeld <[email protected]>
+ *
+ * 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef CAJA_ICON_CANVAS_ITEM_H
+#define CAJA_ICON_CANVAS_ITEM_H
+
+#include <eel/eel-canvas.h>
+#include <eel/eel-art-extensions.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define CAJA_TYPE_ICON_CANVAS_ITEM caja_icon_canvas_item_get_type()
+#define CAJA_ICON_CANVAS_ITEM(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_ICON_CANVAS_ITEM, CajaIconCanvasItem))
+#define CAJA_ICON_CANVAS_ITEM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_ICON_CANVAS_ITEM, CajaIconCanvasItemClass))
+#define CAJA_IS_ICON_CANVAS_ITEM(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_ICON_CANVAS_ITEM))
+#define CAJA_IS_ICON_CANVAS_ITEM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_ICON_CANVAS_ITEM))
+#define CAJA_ICON_CANVAS_ITEM_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_ICON_CANVAS_ITEM, CajaIconCanvasItemClass))
+
+ typedef struct CajaIconCanvasItem CajaIconCanvasItem;
+ typedef struct CajaIconCanvasItemClass CajaIconCanvasItemClass;
+ typedef struct CajaIconCanvasItemDetails CajaIconCanvasItemDetails;
+
+ struct CajaIconCanvasItem
+ {
+ EelCanvasItem item;
+ CajaIconCanvasItemDetails *details;
+ gpointer user_data;
+ };
+
+ struct CajaIconCanvasItemClass
+ {
+ EelCanvasItemClass parent_class;
+ };
+
+ /* not namespaced due to their length */
+ typedef enum
+ {
+ BOUNDS_USAGE_FOR_LAYOUT,
+ BOUNDS_USAGE_FOR_ENTIRE_ITEM,
+ BOUNDS_USAGE_FOR_DISPLAY
+ } CajaIconCanvasItemBoundsUsage;
+
+ /* GObject */
+ GType caja_icon_canvas_item_get_type (void);
+
+ /* attributes */
+ void caja_icon_canvas_item_set_image (CajaIconCanvasItem *item,
+ GdkPixbuf *image);
+ GdkPixmap * caja_icon_canvas_item_get_image (CajaIconCanvasItem *item,
+ GdkBitmap **mask,
+ GdkColormap *colormap);
+ void caja_icon_canvas_item_set_emblems (CajaIconCanvasItem *item,
+ GList *emblem_pixbufs);
+ void caja_icon_canvas_item_set_show_stretch_handles (CajaIconCanvasItem *item,
+ gboolean show_stretch_handles);
+ void caja_icon_canvas_item_set_attach_points (CajaIconCanvasItem *item,
+ GdkPoint *attach_points,
+ int n_attach_points);
+ void caja_icon_canvas_item_set_embedded_text_rect (CajaIconCanvasItem *item,
+ const GdkRectangle *text_rect);
+ void caja_icon_canvas_item_set_embedded_text (CajaIconCanvasItem *item,
+ const char *text);
+ double caja_icon_canvas_item_get_max_text_width (CajaIconCanvasItem *item);
+ const char *caja_icon_canvas_item_get_editable_text (CajaIconCanvasItem *icon_item);
+ void caja_icon_canvas_item_set_renaming (CajaIconCanvasItem *icon_item,
+ gboolean state);
+
+ /* geometry and hit testing */
+ gboolean caja_icon_canvas_item_hit_test_rectangle (CajaIconCanvasItem *item,
+ EelIRect canvas_rect);
+ gboolean caja_icon_canvas_item_hit_test_stretch_handles (CajaIconCanvasItem *item,
+ EelDPoint world_point,
+ GtkCornerType *corner);
+ void caja_icon_canvas_item_invalidate_label (CajaIconCanvasItem *item);
+ void caja_icon_canvas_item_invalidate_label_size (CajaIconCanvasItem *item);
+ EelDRect caja_icon_canvas_item_get_icon_rectangle (const CajaIconCanvasItem *item);
+ EelDRect caja_icon_canvas_item_get_text_rectangle (CajaIconCanvasItem *item,
+ gboolean for_layout);
+ void caja_icon_canvas_item_get_bounds_for_layout (CajaIconCanvasItem *item,
+ double *x1, double *y1, double *x2, double *y2);
+ void caja_icon_canvas_item_get_bounds_for_entire_item (CajaIconCanvasItem *item,
+ double *x1, double *y1, double *x2, double *y2);
+ void caja_icon_canvas_item_update_bounds (CajaIconCanvasItem *item,
+ double i2w_dx, double i2w_dy);
+ void caja_icon_canvas_item_set_is_visible (CajaIconCanvasItem *item,
+ gboolean visible);
+ /* whether the entire label text must be visible at all times */
+ void caja_icon_canvas_item_set_entire_text (CajaIconCanvasItem *icon_item,
+ gboolean entire_text);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CAJA_ICON_CANVAS_ITEM_H */
diff --git a/libcaja-private/caja-icon-container.c b/libcaja-private/caja-icon-container.c
new file mode 100644
index 00000000..2be07d1e
--- /dev/null
+++ b/libcaja-private/caja-icon-container.c
@@ -0,0 +1,10612 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-icon-container.c - Icon container widget.
+
+ Copyright (C) 1999, 2000 Free Software Foundation
+ Copyright (C) 2000, 2001 Eazel, Inc.
+ Copyright (C) 2002, 2003 Red Hat, Inc.
+
+ The Mate 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 Mate 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 Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Ettore Perazzoli <[email protected]>,
+ Darin Adler <[email protected]>
+*/
+
+#include <config.h>
+#include <math.h>
+#include "caja-icon-container.h"
+
+#include "caja-debug-log.h"
+#include "caja-global-preferences.h"
+#include "caja-icon-private.h"
+#include "caja-lib-self-check-functions.h"
+#include "caja-marshal.h"
+#include <atk/atkaction.h>
+#include <eel/eel-accessibility.h>
+#include <eel/eel-background.h>
+#include <eel/eel-vfs-extensions.h>
+#include <eel/eel-gdk-pixbuf-extensions.h>
+#include <eel/eel-mate-extensions.h>
+#include <eel/eel-gtk-extensions.h>
+#include <eel/eel-art-extensions.h>
+#include <eel/eel-editable-label.h>
+#include <eel/eel-marshal.h>
+#include <eel/eel-string.h>
+#include <eel/eel-preferences.h>
+#include <eel/eel-enumeration.h>
+#include <eel/eel-canvas-rect-ellipse.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkx.h>
+#include <glib/gi18n.h>
+#include <stdio.h>
+#include <string.h>
+
+#define TAB_NAVIGATION_DISABLED
+
+/* Interval for updating the rubberband selection, in milliseconds. */
+#define RUBBERBAND_TIMEOUT_INTERVAL 10
+
+/* 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_BASE_WIDTH 96
+
+#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
+
+#define STANDARD_ICON_GRID_WIDTH 155
+
+#define TEXT_BESIDE_ICON_GRID_WIDTH 205
+
+/* 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 DEFAULT_SELECTION_BOX_ALPHA 0x40
+#define DEFAULT_HIGHLIGHT_ALPHA 0xff
+#define DEFAULT_NORMAL_ALPHA 0xff
+#define DEFAULT_PRELIGHT_ALPHA 0xff
+#define DEFAULT_LIGHT_INFO_COLOR 0xAAAAFD
+#define DEFAULT_DARK_INFO_COLOR 0x33337F
+
+#define DEFAULT_NORMAL_ICON_RENDER_MODE 0
+#define DEFAULT_PRELIGHT_ICON_RENDER_MODE 1
+#define DEFAULT_NORMAL_ICON_SATURATION 255
+#define DEFAULT_PRELIGHT_ICON_SATURATION 255
+#define DEFAULT_NORMAL_ICON_BRIGHTNESS 255
+#define DEFAULT_PRELIGHT_ICON_BRIGHTNESS 255
+#define DEFAULT_NORMAL_ICON_LIGHTEN 0
+#define DEFAULT_PRELIGHT_ICON_LIGHTEN 0
+
+#define MINIMUM_EMBEDDED_TEXT_RECT_WIDTH 20
+#define MINIMUM_EMBEDDED_TEXT_RECT_HEIGHT 20
+
+/* If icon size is bigger than this, request large embedded text.
+ * Its selected so that the non-large text should fit in "normal" icon sizes
+ */
+#define ICON_SIZE_FOR_LARGE_EMBEDDED_TEXT 55
+
+/* From caja-icon-canvas-item.c */
+#define MAX_TEXT_WIDTH_BESIDE 90
+
+#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 (eel_round, x)
+#define SNAP_NEAREST_VERTICAL(y) SNAP_VERTICAL (eel_round, y)
+
+#define SNAP_CEIL_HORIZONTAL(x) SNAP_HORIZONTAL (ceil, x)
+#define SNAP_CEIL_VERTICAL(y) SNAP_VERTICAL (ceil, y)
+
+/* Copied from CajaIconContainer */
+#define CAJA_ICON_CONTAINER_SEARCH_DIALOG_TIMEOUT 5
+
+/* Copied from CajaFile */
+#define UNDEFINED_TIME ((time_t) (-1))
+
+enum
+{
+ ACTION_ACTIVATE,
+ ACTION_MENU,
+ LAST_ACTION
+};
+
+typedef struct
+{
+ GList *selection;
+ char *action_descriptions[LAST_ACTION];
+} CajaIconContainerAccessiblePrivate;
+
+static GType caja_icon_container_accessible_get_type (void);
+
+static void activate_selected_items (CajaIconContainer *container);
+static void activate_selected_items_alternate (CajaIconContainer *container,
+ CajaIcon *icon);
+static void caja_icon_container_theme_changed (gpointer user_data);
+static void compute_stretch (StretchState *start,
+ StretchState *current);
+static CajaIcon *get_first_selected_icon (CajaIconContainer *container);
+static CajaIcon *get_nth_selected_icon (CajaIconContainer *container,
+ int index);
+static gboolean has_multiple_selection (CajaIconContainer *container);
+static gboolean all_selected (CajaIconContainer *container);
+static gboolean has_selection (CajaIconContainer *container);
+static void icon_destroy (CajaIconContainer *container,
+ CajaIcon *icon);
+static void end_renaming_mode (CajaIconContainer *container,
+ gboolean commit);
+static CajaIcon *get_icon_being_renamed (CajaIconContainer *container);
+static void finish_adding_new_icons (CajaIconContainer *container);
+static void update_label_color (EelBackground *background,
+ CajaIconContainer *icon_container);
+static inline void icon_get_bounding_box (CajaIcon *icon,
+ int *x1_return,
+ int *y1_return,
+ int *x2_return,
+ int *y2_return,
+ CajaIconCanvasItemBoundsUsage usage);
+static gboolean is_renaming (CajaIconContainer *container);
+static gboolean is_renaming_pending (CajaIconContainer *container);
+static void process_pending_icon_to_rename (CajaIconContainer *container);
+static void setup_label_gcs (CajaIconContainer *container);
+static void caja_icon_container_stop_monitor_top_left (CajaIconContainer *container,
+ CajaIconData *data,
+ gconstpointer client);
+static void caja_icon_container_start_monitor_top_left (CajaIconContainer *container,
+ CajaIconData *data,
+ gconstpointer client,
+ gboolean large_text);
+static void handle_hadjustment_changed (GtkAdjustment *adjustment,
+ CajaIconContainer *container);
+static void handle_vadjustment_changed (GtkAdjustment *adjustment,
+ CajaIconContainer *container);
+static GList * caja_icon_container_get_selected_icons (CajaIconContainer *container);
+static void caja_icon_container_update_visible_icons (CajaIconContainer *container);
+static void reveal_icon (CajaIconContainer *container,
+ CajaIcon *icon);
+
+static void caja_icon_container_set_rtl_positions (CajaIconContainer *container);
+static double get_mirror_x_position (CajaIconContainer *container,
+ CajaIcon *icon,
+ double x);
+
+static int compare_icons_horizontal (CajaIconContainer *container,
+ CajaIcon *icon_a,
+ CajaIcon *icon_b);
+
+static int compare_icons_vertical (CajaIconContainer *container,
+ CajaIcon *icon_a,
+ CajaIcon *icon_b);
+
+static void store_layout_timestamps_now (CajaIconContainer *container);
+
+static gpointer accessible_parent_class;
+
+static GQuark accessible_private_data_quark = 0;
+
+static const char *caja_icon_container_accessible_action_names[] =
+{
+ "activate",
+ "menu",
+ NULL
+};
+
+static const char *caja_icon_container_accessible_action_descriptions[] =
+{
+ "Activate selected items",
+ "Popup context menu",
+ NULL
+};
+
+G_DEFINE_TYPE (CajaIconContainer, caja_icon_container, EEL_TYPE_CANVAS);
+
+/* The CajaIconContainer signals. */
+enum
+{
+ ACTIVATE,
+ ACTIVATE_ALTERNATE,
+ 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_DROP_TARGET_URI,
+ GET_STORED_ICON_POSITION,
+ ICON_POSITION_CHANGED,
+ GET_STORED_LAYOUT_TIMESTAMP,
+ STORE_LAYOUT_TIMESTAMP,
+ ICON_TEXT_CHANGED,
+ ICON_STRETCH_STARTED,
+ ICON_STRETCH_ENDED,
+ RENAMING_ICON,
+ LAYOUT_CHANGED,
+ MOVE_COPY_ITEMS,
+ HANDLE_NETSCAPE_URL,
+ HANDLE_URI_LIST,
+ HANDLE_TEXT,
+ HANDLE_RAW,
+ PREVIEW,
+ SELECTION_CHANGED,
+ ICON_ADDED,
+ ICON_REMOVED,
+ CLEARED,
+ START_INTERACTIVE_SEARCH,
+ 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 CajaIcons. */
+
+static void
+icon_free (CajaIcon *icon)
+{
+ /* Destroy this canvas item; the parent will unref it. */
+ gtk_object_destroy (GTK_OBJECT (icon->item));
+ g_free (icon);
+}
+
+static gboolean
+icon_is_positioned (const CajaIcon *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 (CajaIcon *icon,
+ double x, double y)
+{
+ CajaIconContainer *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, height_below, width_left, width_right;
+ int min_x, max_x, min_y, max_y;
+
+ if (icon->x == x && icon->y == y)
+ {
+ return;
+ }
+
+ container = CAJA_ICON_CONTAINER (EEL_CANVAS_ITEM (icon->item)->canvas);
+
+ if (icon == get_icon_being_renamed (container))
+ {
+ end_renaming_mode (container, TRUE);
+ }
+
+ if (caja_icon_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 = caja_icon_canvas_item_get_icon_rectangle (icon->item);
+
+ /* determine icon rectangle relative to item rectangle */
+ height_above = icon_bounds.y0 - y1;
+ height_below = y2 - icon_bounds.y1;
+ width_left = icon_bounds.x0 - x1;
+ width_right = x2 - icon_bounds.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 void
+icon_get_size (CajaIconContainer *container,
+ CajaIcon *icon,
+ guint *size)
+{
+ if (size != NULL)
+ {
+ *size = MAX (caja_get_icon_size_for_zoom_level (container->details->zoom_level)
+ * icon->scale, CAJA_ICON_SIZE_SMALLEST);
+ }
+}
+
+/* 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 (CajaIconContainer *container,
+ CajaIcon *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 /
+ caja_get_icon_size_for_zoom_level
+ (container->details->zoom_level);
+ caja_icon_container_move_icon (container, icon,
+ icon->x, icon->y,
+ scale, FALSE,
+ snap, update_position);
+}
+
+static void
+icon_raise (CajaIcon *icon)
+{
+ EelCanvasItem *item, *band;
+
+ item = EEL_CANVAS_ITEM (icon->item);
+ band = CAJA_ICON_CONTAINER (item->canvas)->details->rubberband_info.selection_rectangle;
+
+ eel_canvas_item_send_behind (item, band);
+}
+
+static void
+emit_stretch_started (CajaIconContainer *container, CajaIcon *icon)
+{
+ g_signal_emit (container,
+ signals[ICON_STRETCH_STARTED], 0,
+ icon->data);
+}
+
+static void
+emit_stretch_ended (CajaIconContainer *container, CajaIcon *icon)
+{
+ g_signal_emit (container,
+ signals[ICON_STRETCH_ENDED], 0,
+ icon->data);
+}
+
+static void
+icon_toggle_selected (CajaIconContainer *container,
+ CajaIcon *icon)
+{
+ end_renaming_mode (container, TRUE);
+
+ icon->is_selected = !icon->is_selected;
+ 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;
+ caja_icon_canvas_item_set_show_stretch_handles (icon->item, FALSE);
+ /* snap the icon if necessary */
+ if (container->details->keep_aligned)
+ {
+ caja_icon_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 (CajaIconContainer *container,
+ CajaIcon *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 (CajaIcon *icon,
+ int *x1_return, int *y1_return,
+ int *x2_return, int *y2_return,
+ CajaIconCanvasItemBoundsUsage 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)
+ {
+ caja_icon_canvas_item_get_bounds_for_layout (icon->item,
+ &x1, &y1, &x2, &y2);
+ }
+ else if (usage == BOUNDS_USAGE_FOR_ENTIRE_ITEM)
+ {
+ caja_icon_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 CajaIconContainer. */
+
+gboolean
+caja_icon_container_scroll (CajaIconContainer *container,
+ int delta_x, int delta_y)
+{
+ GtkAdjustment *hadj, *vadj;
+ int old_h_value, old_v_value;
+
+ hadj = gtk_layout_get_hadjustment (GTK_LAYOUT (container));
+ vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (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);
+
+ eel_gtk_adjustment_set_value (hadj, gtk_adjustment_get_value (hadj) + delta_x);
+ eel_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 (CajaIconCanvasItem *item,
+ CajaIconContainer *container)
+{
+ g_assert (CAJA_IS_ICON_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 CajaIcon*
+get_pending_icon_to_reveal (CajaIconContainer *container)
+{
+ return container->details->pending_icon_to_reveal;
+}
+
+static void
+set_pending_icon_to_reveal (CajaIconContainer *container, CajaIcon *icon)
+{
+ CajaIcon *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,
+ gboolean safety_pad)
+{
+ 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);
+ if (safety_pad)
+ {
+ 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 (CajaIconContainer *container,
+ CajaIcon *icon,
+ EelIRect *bounds,
+ gboolean safety_pad)
+{
+ GList *p;
+ CajaIcon *one_icon;
+ EelIRect one_bounds;
+
+ item_get_canvas_bounds (EEL_CANVAS_ITEM (icon->item), bounds, safety_pad);
+
+ 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, safety_pad);
+ 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, safety_pad);
+ bounds->y0 = MIN (bounds->y0, one_bounds.y0);
+ bounds->y1 = MAX (bounds->y1, one_bounds.y1);
+ }
+ }
+
+
+}
+
+static void
+reveal_icon (CajaIconContainer *container,
+ CajaIcon *icon)
+{
+ CajaIconContainerDetails *details;
+ 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);
+
+ details = container->details;
+ gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
+
+ hadj = gtk_layout_get_hadjustment (GTK_LAYOUT (container));
+ vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (container));
+
+ if (caja_icon_container_is_auto_layout (container))
+ {
+ /* ensure that we reveal the entire row/column */
+ icon_get_row_and_column_bounds (container, icon, &bounds, TRUE);
+ }
+ else
+ {
+ item_get_canvas_bounds (EEL_CANVAS_ITEM (icon->item), &bounds, TRUE);
+ }
+ if (bounds.y0 < gtk_adjustment_get_value (vadj))
+ {
+ eel_gtk_adjustment_set_value (vadj, bounds.y0);
+ }
+ else if (bounds.y1 > gtk_adjustment_get_value (vadj) + allocation.height)
+ {
+ eel_gtk_adjustment_set_value
+ (vadj, bounds.y1 - allocation.height);
+ }
+
+ if (bounds.x0 < gtk_adjustment_get_value (hadj))
+ {
+ eel_gtk_adjustment_set_value (hadj, bounds.x0);
+ }
+ else if (bounds.x1 > gtk_adjustment_get_value (hadj) + allocation.width)
+ {
+ eel_gtk_adjustment_set_value
+ (hadj, bounds.x1 - allocation.width);
+ }
+}
+
+static void
+process_pending_icon_to_reveal (CajaIconContainer *container)
+{
+ CajaIcon *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)
+{
+ CajaIconContainer *container;
+ CajaIcon *icon;
+
+ container = CAJA_ICON_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 (CajaIconContainer *container)
+{
+ CajaIconContainerDetails *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 (CajaIconContainer *container,
+ CajaIcon *icon)
+{
+ CajaIconContainerDetails *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 (CajaIconContainer *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 (CajaIcon *icon)
+{
+ AtkObject *atk_object = eel_accessibility_for_object (icon->item);
+ atk_focus_tracker_notify (atk_object);
+}
+
+/* Set @icon as the icon currently selected for keyboard operations. */
+static void
+set_keyboard_focus (CajaIconContainer *container,
+ CajaIcon *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 (CajaIconContainer *container,
+ CajaIcon *icon)
+{
+ container->details->keyboard_rubberband_start = icon;
+}
+
+static void
+clear_keyboard_rubberband_start (CajaIconContainer *container)
+{
+ container->details->keyboard_rubberband_start = NULL;
+}
+
+/* carbon-copy of eel_canvas_group_bounds(), but
+ * for CajaIconContainerItems 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,
+ CajaIconCanvasItemBoundsUsage 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 (child->flags & EEL_CANVAS_ITEM_VISIBLE)
+ {
+ set = TRUE;
+ if (!CAJA_IS_ICON_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)
+ {
+ caja_icon_canvas_item_get_bounds_for_layout (CAJA_ICON_CANVAS_ITEM (child),
+ &minx, &miny, &maxx, &maxy);
+ }
+ else if (usage == BOUNDS_USAGE_FOR_ENTIRE_ITEM)
+ {
+ caja_icon_canvas_item_get_bounds_for_entire_item (CAJA_ICON_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 (!(child->flags & EEL_CANVAS_ITEM_VISIBLE))
+ continue;
+
+ if (!CAJA_IS_ICON_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)
+ {
+ caja_icon_canvas_item_get_bounds_for_layout (CAJA_ICON_CANVAS_ITEM (child),
+ &tx1, &ty1, &tx2, &ty2);
+ }
+ else if (usage == BOUNDS_USAGE_FOR_ENTIRE_ITEM)
+ {
+ caja_icon_canvas_item_get_bounds_for_entire_item (CAJA_ICON_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 (CajaIconContainer *container,
+ double *x1, double *y1,
+ double *x2, double *y2,
+ CajaIconCanvasItemBoundsUsage 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
+caja_icon_container_reset_scroll_region (CajaIconContainer *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_ADJUSTMENT (gtk_layout_get_hadjustment (GTK_LAYOUT (canvas))));
+ old_scroll_y = gtk_adjustment_get_value (GTK_ADJUSTMENT (gtk_layout_get_vadjustment (GTK_LAYOUT (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
+caja_icon_container_update_scroll_region (CajaIconContainer *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 (caja_icon_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
+ || caja_icon_container_is_empty (container)
+ || caja_icon_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 (!caja_icon_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 (caja_icon_container_is_layout_vertical (container))
+ {
+ if (caja_icon_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 (caja_icon_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 (caja_icon_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_layout_get_hadjustment (GTK_LAYOUT (container));
+ vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (container));
+
+ /* Scroll by 1/4 icon each time you click. */
+ step_increment = caja_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);
+ gtk_adjustment_changed (hadj);
+ }
+ if (gtk_adjustment_get_step_increment (vadj) != step_increment)
+ {
+ gtk_adjustment_set_step_increment (vadj, step_increment);
+ gtk_adjustment_changed (vadj);
+ }
+
+ /* Now that we have a new scroll region, clamp the
+ * adjustments so we are within the valid scroll area.
+ */
+ eel_gtk_adjustment_clamp_value (hadj);
+ eel_gtk_adjustment_clamp_value (vadj);
+}
+
+static int
+compare_icons (gconstpointer a, gconstpointer b, gpointer icon_container)
+{
+ CajaIconContainerClass *klass;
+ const CajaIcon *icon_a, *icon_b;
+
+ icon_a = a;
+ icon_b = b;
+ klass = CAJA_ICON_CONTAINER_GET_CLASS (icon_container);
+
+ return klass->compare_icons (icon_container, icon_a->data, icon_b->data);
+}
+
+static void
+sort_icons (CajaIconContainer *container,
+ GList **icons)
+{
+ CajaIconContainerClass *klass;
+
+ klass = CAJA_ICON_CONTAINER_GET_CLASS (container);
+ g_assert (klass->compare_icons != NULL);
+
+ *icons = g_list_sort_with_data (*icons, compare_icons, container);
+}
+
+static void
+resort (CajaIconContainer *container)
+{
+ sort_icons (container, &container->details->icons);
+}
+
+#if 0
+static double
+get_grid_width (CajaIconContainer *container)
+{
+ if (container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
+ {
+ return TEXT_BESIDE_ICON_GRID_WIDTH;
+ }
+ else
+ {
+ return STANDARD_ICON_GRID_WIDTH;
+ }
+}
+#endif
+typedef struct
+{
+ double width;
+ double height;
+ double x_offset;
+ double y_offset;
+} IconPositions;
+
+static void
+lay_down_one_line (CajaIconContainer *container,
+ GList *line_start,
+ GList *line_end,
+ double y,
+ double max_height,
+ GArray *positions,
+ gboolean whole_text)
+{
+ GList *p;
+ CajaIcon *icon;
+ double x, y_offset;
+ IconPositions *position;
+ int i;
+ gboolean is_rtl;
+
+ is_rtl = caja_icon_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++);
+
+ if (container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
+ {
+ y_offset = (max_height - position->height) / 2;
+ }
+ else
+ {
+ 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);
+ caja_icon_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_one_column (CajaIconContainer *container,
+ GList *line_start,
+ GList *line_end,
+ double x,
+ double y_start,
+ double y_iter,
+ GArray *positions)
+{
+ GList *p;
+ CajaIcon *icon;
+ double y;
+ IconPositions *position;
+ int i;
+ gboolean is_rtl;
+
+ is_rtl = caja_icon_container_is_layout_rtl (container);
+
+ /* Lay out the icons along the baseline. */
+ y = y_start;
+ i = 0;
+ for (p = line_start; p != line_end; p = p->next)
+ {
+ icon = p->data;
+
+ position = &g_array_index (positions, IconPositions, i++);
+
+ icon_set_position
+ (icon,
+ is_rtl ? get_mirror_x_position (container, icon, x + position->x_offset) : x + position->x_offset,
+ y + position->y_offset);
+
+ icon->saved_ltr_x = is_rtl ? get_mirror_x_position (container, icon, icon->x) : icon->x;
+
+ y += y_iter;
+ }
+}
+
+static void
+lay_down_icons_horizontal (CajaIconContainer *container,
+ GList *icons,
+ double start_y)
+{
+ GList *p, *line_start;
+ CajaIcon *icon;
+ double canvas_width, y, canvas_height;
+ GArray *positions;
+ IconPositions *position;
+ EelDRect bounds;
+ EelDRect icon_bounds;
+ EelDRect text_bounds;
+ double max_height_above, max_height_below;
+ double height_above, height_below;
+ double line_width;
+ gboolean gridded_layout;
+ double grid_width;
+ double max_text_width, max_icon_width;
+ int icon_width;
+ int i;
+ GtkAllocation allocation;
+
+ g_assert (CAJA_IS_ICON_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);
+ canvas_height = CANVAS_HEIGHT(container, allocation);
+
+ max_icon_width = max_text_width = 0.0;
+
+ if (container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
+ {
+ /* Would it be worth caching these bounds for the next loop? */
+ for (p = icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+
+ icon_bounds = caja_icon_canvas_item_get_icon_rectangle (icon->item);
+ max_icon_width = MAX (max_icon_width, ceil (icon_bounds.x1 - icon_bounds.x0));
+
+ text_bounds = caja_icon_canvas_item_get_text_rectangle (icon->item, TRUE);
+ max_text_width = MAX (max_text_width, ceil (text_bounds.x1 - text_bounds.x0));
+ }
+
+ grid_width = max_icon_width + max_text_width + ICON_PAD_LEFT + ICON_PAD_RIGHT;
+ }
+ else
+ {
+ grid_width = STANDARD_ICON_GRID_WIDTH;
+ }
+
+ gridded_layout = !caja_icon_container_is_tighter_layout (container);
+
+ line_width = container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE ? ICON_PAD_LEFT : 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 */
+ caja_icon_canvas_item_get_bounds_for_layout (icon->item,
+ &bounds.x0, &bounds.y0,
+ &bounds.x1, &bounds.y1);
+
+ icon_bounds = caja_icon_canvas_item_get_icon_rectangle (icon->item);
+ text_bounds = caja_icon_canvas_item_get_text_rectangle (icon->item, TRUE);
+
+ if (gridded_layout)
+ {
+ icon_width = ceil ((bounds.x1 - bounds.x0)/grid_width) * grid_width;
+
+
+ }
+ else
+ {
+ icon_width = (bounds.x1 - bounds.x0) + ICON_PAD_RIGHT + 8; /* 8 pixels extra for fancy selection box */
+ }
+
+ /* Calculate size above/below baseline */
+ 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 )
+ {
+ if (container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
+ {
+ y += ICON_PAD_TOP;
+ }
+ else
+ {
+ /* 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);
+
+ if (container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
+ {
+ y += max_height_above + max_height_below + ICON_PAD_BOTTOM;
+ }
+ else
+ {
+ /* Advance to next line. */
+ y += max_height_below + ICON_PAD_BOTTOM;
+ }
+
+ line_width = container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE ? ICON_PAD_LEFT : 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;
+
+ if (container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
+ {
+ if (gridded_layout)
+ {
+ position->x_offset = max_icon_width + ICON_PAD_LEFT + ICON_PAD_RIGHT - (icon_bounds.x1 - icon_bounds.x0);
+ }
+ else
+ {
+ position->x_offset = icon_width - ((icon_bounds.x1 - icon_bounds.x0) + (text_bounds.x1 - text_bounds.x0));
+ }
+ position->y_offset = 0;
+ }
+ else
+ {
+ 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)
+ {
+ if (container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
+ {
+ y += ICON_PAD_TOP;
+ }
+ else
+ {
+ /* 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);
+
+ /* Advance to next line. */
+ y += max_height_below + ICON_PAD_BOTTOM;
+ }
+
+ g_array_free (positions, TRUE);
+}
+
+static void
+get_max_icon_dimensions (GList *icon_start,
+ GList *icon_end,
+ double *max_icon_width,
+ double *max_icon_height,
+ double *max_text_width,
+ double *max_text_height,
+ double *max_bounds_height)
+{
+ CajaIcon *icon;
+ EelDRect icon_bounds;
+ EelDRect text_bounds;
+ GList *p;
+ double y1, y2;
+
+ *max_icon_width = *max_text_width = 0.0;
+ *max_icon_height = *max_text_height = 0.0;
+ *max_bounds_height = 0.0;
+
+ /* Would it be worth caching these bounds for the next loop? */
+ for (p = icon_start; p != icon_end; p = p->next)
+ {
+ icon = p->data;
+
+ icon_bounds = caja_icon_canvas_item_get_icon_rectangle (icon->item);
+ *max_icon_width = MAX (*max_icon_width, ceil (icon_bounds.x1 - icon_bounds.x0));
+ *max_icon_height = MAX (*max_icon_height, ceil (icon_bounds.y1 - icon_bounds.y0));
+
+ text_bounds = caja_icon_canvas_item_get_text_rectangle (icon->item, TRUE);
+ *max_text_width = MAX (*max_text_width, ceil (text_bounds.x1 - text_bounds.x0));
+ *max_text_height = MAX (*max_text_height, ceil (text_bounds.y1 - text_bounds.y0));
+
+ caja_icon_canvas_item_get_bounds_for_layout (icon->item,
+ NULL, &y1,
+ NULL, &y2);
+ *max_bounds_height = MAX (*max_bounds_height, y2 - y1);
+ }
+}
+
+/* column-wise layout. At the moment, this only works with label-beside-icon (used by "Compact View"). */
+static void
+lay_down_icons_vertical (CajaIconContainer *container,
+ GList *icons,
+ double start_y)
+{
+ GList *p, *line_start;
+ CajaIcon *icon;
+ double canvas_width, x, canvas_height;
+ GArray *positions;
+ IconPositions *position;
+ EelDRect icon_bounds;
+ EelDRect text_bounds;
+ EelCanvasItem *item;
+ GtkAllocation allocation;
+
+ double line_height;
+
+ double max_height;
+ double max_height_with_borders;
+ double max_width;
+ double max_width_in_column;
+
+ double max_bounds_height;
+ double max_bounds_height_with_borders;
+
+ double max_text_width, max_icon_width;
+ double max_text_height, max_icon_height;
+ int height;
+ int i;
+
+ g_assert (CAJA_IS_ICON_CONTAINER (container));
+ g_assert (container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE);
+
+ if (icons == NULL)
+ {
+ return;
+ }
+
+ positions = g_array_new (FALSE, FALSE, sizeof (IconPositions));
+ gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
+
+ /* Lay out icons a column at a time. */
+ canvas_width = CANVAS_WIDTH(container, allocation);
+ canvas_height = CANVAS_HEIGHT(container, allocation);
+
+ max_icon_width = max_text_width = 0.0;
+ max_icon_height = max_text_height = 0.0;
+ max_bounds_height = 0.0;
+
+ get_max_icon_dimensions (icons, NULL,
+ &max_icon_width, &max_icon_height,
+ &max_text_width, &max_text_height,
+ &max_bounds_height);
+
+ max_width = max_icon_width + max_text_width;
+ max_height = MAX (max_icon_height, max_text_height);
+ max_height_with_borders = ICON_PAD_TOP + max_height;
+
+ max_bounds_height_with_borders = ICON_PAD_TOP + max_bounds_height;
+
+ line_height = ICON_PAD_TOP;
+ line_start = icons;
+ x = 0;
+ i = 0;
+
+ max_width_in_column = 0.0;
+
+ for (p = icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+ item = EEL_CANVAS_ITEM (icon->item);
+
+ /* If this icon doesn't fit, it's time to lay out the column that's queued up. */
+
+ /* We use the bounds height here, since for wrapping we also want to consider
+ * overlapping emblems at the bottom. We may wrap a little bit too early since
+ * the icon with the max. bounds height may actually not be in the last row, but
+ * it is better than visual glitches
+ */
+ if (line_start != p && line_height + (max_bounds_height_with_borders-1) >= canvas_height )
+ {
+ x += ICON_PAD_LEFT;
+
+ /* correctly set (per-column) width */
+ if (!container->details->all_columns_same_width)
+ {
+ for (i = 0; i < (int) positions->len; i++)
+ {
+ position = &g_array_index (positions, IconPositions, i);
+ position->width = max_width_in_column;
+ }
+ }
+
+ lay_down_one_column (container, line_start, p, x, CONTAINER_PAD_TOP, max_height_with_borders, positions);
+
+ /* Advance to next column. */
+ if (container->details->all_columns_same_width)
+ {
+ x += max_width + ICON_PAD_RIGHT;
+ }
+ else
+ {
+ x += max_width_in_column + ICON_PAD_RIGHT;
+ }
+
+ line_height = ICON_PAD_TOP;
+ line_start = p;
+ i = 0;
+
+ max_width_in_column = 0;
+ }
+
+ icon_bounds = caja_icon_canvas_item_get_icon_rectangle (icon->item);
+ text_bounds = caja_icon_canvas_item_get_text_rectangle (icon->item, TRUE);
+
+ max_width_in_column = MAX (max_width_in_column,
+ ceil (icon_bounds.x1 - icon_bounds.x0) +
+ ceil (text_bounds.x1 - text_bounds.x0));
+
+ g_array_set_size (positions, i + 1);
+ position = &g_array_index (positions, IconPositions, i++);
+ if (container->details->all_columns_same_width)
+ {
+ position->width = max_width;
+ }
+ position->height = max_height;
+ position->y_offset = ICON_PAD_TOP;
+ position->x_offset = ICON_PAD_LEFT;
+
+ position->x_offset += max_icon_width - ceil (icon_bounds.x1 - icon_bounds.x0);
+
+ height = MAX (ceil (icon_bounds.y1 - icon_bounds.y0), ceil(text_bounds.y1 - text_bounds.y0));
+ position->y_offset += (max_height - height) / 2;
+
+ /* Add this icon. */
+ line_height += max_height_with_borders;
+ }
+
+ /* Lay down that last column of icons. */
+ if (line_start != NULL)
+ {
+ x += ICON_PAD_LEFT;
+ lay_down_one_column (container, line_start, NULL, x, CONTAINER_PAD_TOP, max_height_with_borders, positions);
+ }
+
+ g_array_free (positions, TRUE);
+}
+
+static void
+snap_position (CajaIconContainer *container,
+ CajaIcon *icon,
+ int *x, int *y)
+{
+ int center_x;
+ int baseline_y;
+ int icon_width;
+ int icon_height;
+ int total_width;
+ int total_height;
+ EelDRect icon_position;
+ GtkAllocation allocation;
+
+ icon_position = caja_icon_canvas_item_get_icon_rectangle (icon->item);
+ icon_width = icon_position.x1 - icon_position.x0;
+ icon_height = icon_position.y1 - icon_position.y0;
+
+ gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
+ total_width = CANVAS_WIDTH (container, allocation);
+ total_height = CANVAS_HEIGHT (container, allocation);
+
+ if (caja_icon_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 (caja_icon_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)
+{
+ CajaIcon *icon_a, *icon_b;
+ int x1, y1, x2, y2;
+ int center_a;
+ int center_b;
+
+ icon_a = (CajaIcon*)a;
+ icon_b = (CajaIcon*)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 (CajaIconContainer *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, CajaIcon *icon)
+{
+ EelIRect icon_pos;
+ EelIRect grid_pos;
+
+ icon_get_bounding_box (icon,
+ &icon_pos.x0, &icon_pos.y0,
+ &icon_pos.x1, &icon_pos.y1,
+ BOUNDS_USAGE_FOR_LAYOUT);
+ canvas_position_to_grid_position (grid,
+ icon_pos,
+ &grid_pos);
+ placement_grid_mark (grid, grid_pos);
+}
+
+static void
+find_empty_location (CajaIconContainer *container,
+ PlacementGrid *grid,
+ CajaIcon *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 = caja_icon_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 (CajaIconContainer *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 (caja_icon_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)
+ {
+ CajaIcon *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 (caja_icon_container_is_layout_rtl (container))
+ {
+ caja_icon_container_set_rtl_positions (container);
+ }
+}
+
+static double
+get_mirror_x_position (CajaIconContainer *container, CajaIcon *icon, double x)
+{
+ EelDRect icon_bounds;
+ GtkAllocation allocation;
+
+ gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
+ icon_bounds = caja_icon_canvas_item_get_icon_rectangle (icon->item);
+
+ return CANVAS_WIDTH(container, allocation) - x - (icon_bounds.x1 - icon_bounds.x0);
+}
+
+static void
+caja_icon_container_set_rtl_positions (CajaIconContainer *container)
+{
+ GList *l;
+ CajaIcon *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 (CajaIconContainer *container, GList *icons)
+{
+ GList *p, *placed_icons, *unplaced_icons;
+ int total, new_length, placed;
+ CajaIcon *icon;
+ int width, 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);
+ width = CANVAS_WIDTH(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, (CajaIcon*)p->data);
+ }
+
+ /* Place unplaced icons in the best locations */
+ for (p = unplaced_icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+
+ icon_rect = caja_icon_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->tighter_layout && !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 = caja_icon_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 = caja_icon_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.
+ */
+ caja_icon_container_freeze_icon_positions (container);
+}
+
+
+static void
+lay_down_icons (CajaIconContainer *container, GList *icons, double start_y)
+{
+ switch (container->details->layout_mode)
+ {
+ case CAJA_ICON_LAYOUT_L_R_T_B:
+ case CAJA_ICON_LAYOUT_R_L_T_B:
+ lay_down_icons_horizontal (container, icons, start_y);
+ break;
+
+ case CAJA_ICON_LAYOUT_T_B_L_R:
+ case CAJA_ICON_LAYOUT_T_B_R_L:
+ if (caja_icon_container_get_is_desktop (container))
+ {
+ lay_down_icons_vertical_desktop (container, icons);
+ }
+ else
+ {
+ lay_down_icons_vertical (container, icons, start_y);
+ }
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+redo_layout_internal (CajaIconContainer *container)
+{
+ finish_adding_new_icons (container);
+
+ /* 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)
+ {
+ resort (container);
+ lay_down_icons (container, container->details->icons, 0);
+ }
+
+ if (caja_icon_container_is_layout_rtl (container))
+ {
+ caja_icon_container_set_rtl_positions (container);
+ }
+
+ caja_icon_container_update_scroll_region (container);
+
+ process_pending_icon_to_reveal (container);
+ process_pending_icon_to_rename (container);
+ caja_icon_container_update_visible_icons (container);
+}
+
+static gboolean
+redo_layout_callback (gpointer callback_data)
+{
+ CajaIconContainer *container;
+
+ container = CAJA_ICON_CONTAINER (callback_data);
+ redo_layout_internal (container);
+ container->details->idle_id = 0;
+
+ return FALSE;
+}
+
+static void
+unschedule_redo_layout (CajaIconContainer *container)
+{
+ if (container->details->idle_id != 0)
+ {
+ g_source_remove (container->details->idle_id);
+ container->details->idle_id = 0;
+ }
+}
+
+static void
+schedule_redo_layout (CajaIconContainer *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 (CajaIconContainer *container)
+{
+ unschedule_redo_layout (container);
+ redo_layout_internal (container);
+}
+
+static void
+reload_icon_positions (CajaIconContainer *container)
+{
+ GList *p, *no_position_icons;
+ CajaIcon *icon;
+ gboolean have_stored_position;
+ CajaIconPosition 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);
+ caja_icon_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 (CajaIconContainer *container)
+{
+ GList *p;
+ CajaIcon *icon;
+
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+
+ caja_icon_canvas_item_invalidate_label_size (icon->item);
+ }
+}
+
+/* invalidate the entire labels (i.e. their attributes) for all the icons */
+static void
+invalidate_labels (CajaIconContainer *container)
+{
+ GList *p;
+ CajaIcon *icon;
+
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+
+ caja_icon_canvas_item_invalidate_label (icon->item);
+ }
+}
+
+static gboolean
+select_range (CajaIconContainer *container,
+ CajaIcon *icon1,
+ CajaIcon *icon2,
+ gboolean unselect_outside_range)
+{
+ gboolean selection_changed;
+ GList *p;
+ CajaIcon *icon;
+ CajaIcon *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 (CajaIconContainer *container,
+ CajaIcon *icon_to_select)
+{
+ gboolean selection_changed;
+ GList *p;
+ CajaIcon *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 (CajaIconContainer *container)
+{
+ return select_one_unselect_others (container, NULL);
+}
+
+void
+caja_icon_container_move_icon (CajaIconContainer *container,
+ CajaIcon *icon,
+ int x, int y,
+ double scale,
+ gboolean raise,
+ gboolean snap,
+ gboolean update_position)
+{
+ CajaIconContainerDetails *details;
+ gboolean emit_signal;
+ CajaIconPosition position;
+
+ details = container->details;
+
+ emit_signal = FALSE;
+
+ if (icon == get_icon_being_renamed (container))
+ {
+ end_renaming_mode (container, TRUE);
+ }
+
+ if (scale != icon->scale)
+ {
+ icon->scale = scale;
+ caja_icon_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 = caja_icon_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 (CajaIconContainer *container,
+ const EelDRect *previous_rect,
+ const EelDRect *current_rect)
+{
+ GList *p;
+ gboolean selection_changed, is_in, canvas_rect_calculated;
+ CajaIcon *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 = caja_icon_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)
+{
+ CajaIconContainer *container;
+ GtkWidget *widget;
+ CajaIconRubberbandInfo *band_info;
+ int x, y;
+ double x1, y1, x2, y2;
+ double world_x, world_y;
+ int x_scroll, y_scroll;
+ int adj_x, adj_y;
+ gboolean adj_changed;
+ GtkAllocation allocation;
+
+ EelDRect selection_rect;
+
+ widget = GTK_WIDGET (data);
+ container = CAJA_ICON_CONTAINER (data);
+ band_info = &container->details->rubberband_info;
+
+ g_assert (band_info->timer_id != 0);
+ g_assert (EEL_IS_CANVAS_RECT (band_info->selection_rectangle) ||
+ EEL_IS_CANVAS_RECT (band_info->selection_rectangle));
+
+ adj_changed = FALSE;
+ gtk_widget_get_allocation (widget, &allocation);
+
+ adj_x = gtk_adjustment_get_value (gtk_layout_get_hadjustment (GTK_LAYOUT (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_layout_get_vadjustment (GTK_LAYOUT (container)));
+ if (adj_y != band_info->last_adj_y)
+ {
+ band_info->last_adj_y = adj_y;
+ adj_changed = TRUE;
+ }
+
+ gtk_widget_get_pointer (widget, &x, &y);
+
+ if (x < 0)
+ {
+ x_scroll = x;
+ x = 0;
+ }
+ else if (x >= allocation.width)
+ {
+ x_scroll = x - allocation.width + 1;
+ x = allocation.width - 1;
+ }
+ else
+ {
+ x_scroll = 0;
+ }
+
+ if (y < 0)
+ {
+ y_scroll = y;
+ y = 0;
+ }
+ else if (y >= allocation.height)
+ {
+ y_scroll = y - allocation.height + 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;
+ }
+
+ caja_icon_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_layout_get_hadjustment (GTK_LAYOUT (container))),
+ y + gtk_adjustment_get_value (gtk_layout_get_vadjustment (GTK_LAYOUT (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,
+ &band_info->prev_rect,
+ &selection_rect);
+
+ band_info->prev_x = x;
+ band_info->prev_y = y;
+
+ band_info->prev_rect = selection_rect;
+
+ return TRUE;
+}
+
+static void
+start_rubberbanding (CajaIconContainer *container,
+ GdkEventButton *event)
+{
+ AtkObject *accessible;
+ CajaIconContainerDetails *details;
+ CajaIconRubberbandInfo *band_info;
+ guint fill_color, outline_color;
+ GdkColor *fill_color_gdk;
+ guchar fill_color_alpha;
+ GList *p;
+ CajaIcon *icon;
+ GtkStyle *style;
+
+ 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);
+
+ gtk_widget_style_get (GTK_WIDGET (container),
+ "selection_box_color", &fill_color_gdk,
+ "selection_box_alpha", &fill_color_alpha,
+ NULL);
+
+ if (!fill_color_gdk)
+ {
+ style = gtk_widget_get_style (GTK_WIDGET (container));
+ fill_color_gdk = gdk_color_copy (&style->base[GTK_STATE_SELECTED]);
+ }
+
+ fill_color = eel_gdk_color_to_rgb (fill_color_gdk) << 8 | fill_color_alpha;
+
+ gdk_color_free (fill_color_gdk);
+
+ outline_color = fill_color | 255;
+
+ band_info->selection_rectangle = eel_canvas_item_new
+ (eel_canvas_root
+ (EEL_CANVAS (container)),
+ EEL_TYPE_CANVAS_RECT,
+ "x1", band_info->start_x,
+ "y1", band_info->start_y,
+ "x2", band_info->start_x,
+ "y2", band_info->start_y,
+ "fill_color_rgba", fill_color,
+ "outline_color_rgba", outline_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_layout_get_hadjustment (GTK_LAYOUT (container)));
+ band_info->prev_y = event->y - gtk_adjustment_get_value (gtk_layout_get_vadjustment (GTK_LAYOUT (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 (CajaIconContainer *container,
+ guint32 time)
+{
+ CajaIconRubberbandInfo *band_info;
+ GList *icons;
+
+ 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;
+
+ /* Destroy this canvas item; the parent will unref it. */
+ eel_canvas_item_ungrab (band_info->selection_rectangle, time);
+ gtk_object_destroy (GTK_OBJECT (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 = caja_icon_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 (* IsBetterIconFunction) (CajaIconContainer *container,
+ CajaIcon *start_icon,
+ CajaIcon *best_so_far,
+ CajaIcon *candidate,
+ void *data);
+
+static CajaIcon *
+find_best_icon (CajaIconContainer *container,
+ CajaIcon *start_icon,
+ IsBetterIconFunction function,
+ void *data)
+{
+ GList *p;
+ CajaIcon *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 CajaIcon *
+find_best_selected_icon (CajaIconContainer *container,
+ CajaIcon *start_icon,
+ IsBetterIconFunction function,
+ void *data)
+{
+ GList *p;
+ CajaIcon *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 (CajaIconContainer *container,
+ CajaIcon *icon_a,
+ CajaIcon *icon_b)
+{
+ char *uri_a, *uri_b;
+ int result;
+
+ g_assert (CAJA_IS_ICON_CONTAINER (container));
+ g_assert (icon_a != NULL);
+ g_assert (icon_b != NULL);
+ g_assert (icon_a != icon_b);
+
+ uri_a = caja_icon_container_get_icon_uri (container, icon_a);
+ uri_b = caja_icon_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 (CajaIconContainer *container,
+ EelDRect icon_rect)
+{
+ if (container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
+ {
+ if (gtk_widget_get_direction (GTK_WIDGET (container)) == GTK_TEXT_DIR_RTL)
+ {
+ return icon_rect.x0;
+ }
+ else
+ {
+ return icon_rect.x1;
+ }
+ }
+ else
+ {
+ return (icon_rect.x0 + icon_rect.x1) / 2;
+ }
+}
+
+static int
+get_cmp_point_y (CajaIconContainer *container,
+ EelDRect icon_rect)
+{
+ if (container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
+ {
+ return (icon_rect.y0 + icon_rect.y1)/2;
+ }
+ else
+ {
+ return icon_rect.y1;
+ }
+}
+
+
+static int
+compare_icons_horizontal (CajaIconContainer *container,
+ CajaIcon *icon_a,
+ CajaIcon *icon_b)
+{
+ EelDRect world_rect;
+ int ax, bx;
+
+ world_rect = caja_icon_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 = caja_icon_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 (CajaIconContainer *container,
+ CajaIcon *icon_a,
+ CajaIcon *icon_b)
+{
+ EelDRect world_rect;
+ int ay, by;
+
+ world_rect = caja_icon_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 = caja_icon_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 (CajaIconContainer *container,
+ CajaIcon *icon_a,
+ CajaIcon *icon_b)
+{
+ EelDRect world_rect;
+ int ax, ay, bx, by;
+
+ world_rect = caja_icon_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 = caja_icon_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 (CajaIconContainer *container,
+ CajaIcon *icon_a,
+ CajaIcon *icon_b)
+{
+ EelDRect world_rect;
+ int ax, ay, bx, by;
+
+ world_rect = caja_icon_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 = caja_icon_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 (CajaIconContainer *container,
+ CajaIcon *start_icon,
+ CajaIcon *best_so_far,
+ CajaIcon *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 (CajaIconContainer *container,
+ CajaIcon *start_icon,
+ CajaIcon *best_so_far,
+ CajaIcon *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 (CajaIconContainer *container,
+ CajaIcon *start_icon,
+ CajaIcon *best_so_far,
+ CajaIcon *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 (CajaIconContainer *container,
+ CajaIcon *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 (CajaIconContainer *container,
+ CajaIcon *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 (CajaIconContainer *container,
+ CajaIcon *start_icon,
+ CajaIcon *best_so_far,
+ CajaIcon *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 (CajaIconContainer *container,
+ CajaIcon *start_icon,
+ CajaIcon *best_so_far,
+ CajaIcon *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 (CajaIconContainer *container,
+ CajaIcon *start_icon,
+ CajaIcon *best_so_far,
+ CajaIcon *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 (CajaIconContainer *container,
+ CajaIcon *start_icon,
+ CajaIcon *best_so_far,
+ CajaIcon *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 (CajaIconContainer *container,
+ CajaIcon *start_icon,
+ CajaIcon *best_so_far,
+ CajaIcon *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 (CajaIconContainer *container,
+ CajaIcon *start_icon,
+ CajaIcon *best_so_far,
+ CajaIcon *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 (CajaIconContainer *container,
+ CajaIcon *start_icon,
+ CajaIcon *best_so_far,
+ CajaIcon *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 (CajaIconContainer *container,
+ CajaIcon *start_icon,
+ CajaIcon *best_so_far,
+ CajaIcon *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 (CajaIconContainer *container,
+ CajaIcon *start_icon,
+ CajaIcon *best_so_far,
+ CajaIcon *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 (CajaIconContainer *container,
+ CajaIcon *start_icon,
+ CajaIcon *best_so_far,
+ CajaIcon *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 (CajaIconContainer *container,
+ CajaIcon *start_icon,
+ CajaIcon *best_so_far,
+ CajaIcon *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 (CajaIconContainer *container,
+ CajaIcon *start_icon,
+ CajaIcon *best_so_far,
+ CajaIcon *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 (CajaIconContainer *container,
+ CajaIcon *start_icon,
+ CajaIcon *best_so_far,
+ CajaIcon *candidate,
+ void *data)
+{
+ EelDRect world_rect;
+ int x, y;
+ int dx, dy;
+ int dist;
+ int *best_dist;
+
+
+ world_rect = caja_icon_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 (CajaIcon *icon1,
+ CajaIcon *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 (CajaIconContainer *container,
+ CajaIcon *icon,
+ CajaIcon *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, NULL, &rect);
+ }
+ }
+ else if (event != NULL &&
+ (event->state & GDK_CONTROL_MASK) == 0 &&
+ (event->state & GDK_SHIFT_MASK) != 0)
+ {
+ /* Select range */
+ CajaIcon *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 (CajaIconContainer *container,
+ GdkEventKey *event)
+{
+ CajaIcon *from;
+ CajaIcon *to;
+
+ /* Home selects the first icon.
+ * Control-Home sets the keyboard focus to the first icon.
+ */
+
+ 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 (CajaIconContainer *container,
+ GdkEventKey *event)
+{
+ CajaIcon *to;
+ CajaIcon *from;
+
+ /* End selects the last icon.
+ * Control-End sets the keyboard focus to the last icon.
+ */
+ from = find_best_selected_icon (container, NULL,
+ leftmost_in_top_row,
+ NULL);
+ to = find_best_icon (container, NULL,
+ caja_icon_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 (CajaIconContainer *container,
+ CajaIcon *icon,
+ GtkDirectionType direction)
+{
+ EelDRect world_rect;
+
+ world_rect = caja_icon_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 (CajaIconContainer *container,
+ GdkEventKey *event,
+ GtkDirectionType direction,
+ IsBetterIconFunction better_start,
+ IsBetterIconFunction empty_start,
+ IsBetterIconFunction better_destination,
+ IsBetterIconFunction better_destination_fallback_if_no_a11y,
+ IsBetterIconFunction better_destination_fallback_fallback,
+ IsBetterIconFunction better_destination_manual)
+{
+ CajaIcon *from;
+ CajaIcon *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);
+
+ /* only wrap around to next/previous row/column if no a11y is used.
+ * Visually impaired people may be easily confused by this.
+ */
+ if (to == NULL &&
+ better_destination_fallback_if_no_a11y != NULL &&
+ ATK_IS_NO_OP_OBJECT (gtk_widget_get_accessible (GTK_WIDGET (container))))
+ {
+ to = find_best_icon
+ (container, from,
+ better_destination_fallback_if_no_a11y,
+ &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 (CajaIconContainer *container,
+ GdkEventKey *event)
+{
+ IsBetterIconFunction no_a11y;
+ IsBetterIconFunction next_column_fallback;
+
+ no_a11y = NULL;
+ if (container->details->auto_layout &&
+ !caja_icon_container_is_layout_vertical (container) &&
+ !is_rectangle_selection_event (event))
+ {
+ no_a11y = next_row_leftmost;
+ }
+
+ next_column_fallback = NULL;
+ if (caja_icon_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,
+ caja_icon_container_is_layout_rtl (container) ?
+ rightmost_in_top_row : leftmost_in_top_row,
+ same_row_right_side_leftmost,
+ no_a11y,
+ next_column_fallback,
+ closest_in_90_degrees);
+}
+
+static void
+keyboard_left (CajaIconContainer *container,
+ GdkEventKey *event)
+{
+ IsBetterIconFunction no_a11y;
+ IsBetterIconFunction previous_column_fallback;
+
+ no_a11y = NULL;
+ if (container->details->auto_layout &&
+ !caja_icon_container_is_layout_vertical (container) &&
+ !is_rectangle_selection_event (event))
+ {
+ no_a11y = previous_row_rightmost;
+ }
+
+ previous_column_fallback = NULL;
+ if (caja_icon_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,
+ caja_icon_container_is_layout_rtl (container) ?
+ rightmost_in_top_row : leftmost_in_top_row,
+ same_row_left_side_rightmost,
+ no_a11y,
+ previous_column_fallback,
+ closest_in_90_degrees);
+}
+
+static void
+keyboard_down (CajaIconContainer *container,
+ GdkEventKey *event)
+{
+ IsBetterIconFunction no_a11y;
+ IsBetterIconFunction next_row_fallback;
+
+ no_a11y = NULL;
+ if (container->details->auto_layout &&
+ caja_icon_container_is_layout_vertical (container) &&
+ !is_rectangle_selection_event (event))
+ {
+ if (gtk_widget_get_direction (GTK_WIDGET (container)) == GTK_TEXT_DIR_RTL)
+ {
+ no_a11y = previous_column_highest;
+ }
+ else
+ {
+ no_a11y = next_column_highest;
+ }
+ }
+
+ next_row_fallback = NULL;
+ if (!caja_icon_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,
+ caja_icon_container_is_layout_rtl (container) ?
+ rightmost_in_top_row : leftmost_in_top_row,
+ same_column_below_highest,
+ no_a11y,
+ next_row_fallback,
+ closest_in_90_degrees);
+}
+
+static void
+keyboard_up (CajaIconContainer *container,
+ GdkEventKey *event)
+{
+ IsBetterIconFunction no_a11y;
+
+ no_a11y = NULL;
+ if (container->details->auto_layout &&
+ caja_icon_container_is_layout_vertical (container) &&
+ !is_rectangle_selection_event (event))
+ {
+ if (gtk_widget_get_direction (GTK_WIDGET (container)) == GTK_TEXT_DIR_RTL)
+ {
+ no_a11y = next_column_bottommost;
+ }
+ else
+ {
+ no_a11y = 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,
+ caja_icon_container_is_layout_rtl (container) ?
+ rightmost_in_top_row : leftmost_in_top_row,
+ same_column_above_lowest,
+ no_a11y,
+ NULL,
+ closest_in_90_degrees);
+}
+
+static void
+keyboard_space (CajaIconContainer *container,
+ GdkEventKey *event)
+{
+ CajaIcon *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
+ {
+ activate_selected_items (container);
+ }
+}
+
+/* look for the first icon that matches the longest part of a given
+ * search pattern
+ */
+typedef struct
+{
+ gunichar *name;
+ int last_match_length;
+} BestNameMatch;
+
+#ifndef TAB_NAVIGATION_DISABLED
+static void
+select_previous_or_next_icon (CajaIconContainer *container,
+ gboolean next,
+ GdkEventKey *event)
+{
+ CajaIcon *icon;
+ const GList *item;
+
+ item = NULL;
+ /* Chose the icon to start with.
+ * If we have a keyboard focus, start with it.
+ * Otherwise, use the single selected icon.
+ */
+ icon = container->details->keyboard_focus;
+ if (icon == NULL)
+ {
+ icon = get_first_selected_icon (container);
+ }
+
+ if (icon != NULL)
+ {
+ /* must have at least @icon in the list */
+ g_assert (container->details->icons != NULL);
+ item = g_list_find (container->details->icons, icon);
+ g_assert (item != NULL);
+
+ item = next ? item->next : item->prev;
+ if (item == NULL)
+ {
+ item = next ? g_list_first (container->details->icons) : g_list_last (container->details->icons);
+ }
+
+ }
+ else if (container->details->icons != NULL)
+ {
+ /* no selection yet, pick the first or last item to select */
+ item = next ? g_list_first (container->details->icons) : g_list_last (container->details->icons);
+ }
+
+ icon = (item != NULL) ? item->data : NULL;
+
+ if (icon != NULL)
+ {
+ keyboard_move_to (container, icon, NULL, event);
+ }
+}
+#endif
+
+/* GtkObject methods. */
+
+static void
+destroy (GtkObject *object)
+{
+ CajaIconContainer *container;
+
+ container = CAJA_ICON_CONTAINER (object);
+
+ caja_icon_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;
+ }
+
+ /* destroy interactive search dialog */
+ if (container->details->search_window)
+ {
+ gtk_widget_destroy (container->details->search_window);
+ container->details->search_window = NULL;
+ container->details->search_entry = NULL;
+ if (container->details->typeselect_flush_timeout)
+ {
+ g_source_remove (container->details->typeselect_flush_timeout);
+ container->details->typeselect_flush_timeout = 0;
+ }
+ }
+
+
+ GTK_OBJECT_CLASS (caja_icon_container_parent_class)->destroy (object);
+}
+
+static void
+finalize (GObject *object)
+{
+ CajaIconContainerDetails *details;
+
+ details = CAJA_ICON_CONTAINER (object)->details;
+
+ eel_preferences_remove_callback (CAJA_PREFERENCES_THEME,
+ caja_icon_container_theme_changed,
+ 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 (caja_icon_container_parent_class)->finalize (object);
+}
+
+/* GtkWidget methods. */
+
+static void
+size_request (GtkWidget *widget,
+ GtkRequisition *requisition)
+{
+ GTK_WIDGET_CLASS (caja_icon_container_parent_class)->size_request (widget, requisition);
+ requisition->width = 1;
+ requisition->height = 1;
+}
+
+static gboolean
+clear_size_allocation_count (gpointer data)
+{
+ CajaIconContainer *container;
+
+ container = CAJA_ICON_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)
+{
+ CajaIconContainer *container;
+ gboolean need_layout_redone;
+ GtkAllocation wid_allocation;
+
+ container = CAJA_ICON_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 (caja_icon_container_parent_class)->size_allocate (widget, allocation);
+
+ container->details->has_been_allocated = TRUE;
+
+ if (need_layout_redone)
+ {
+ redo_layout (container);
+ }
+}
+
+static void
+realize (GtkWidget *widget)
+{
+ GdkBitmap *stipple;
+ GtkAdjustment *vadj, *hadj;
+ CajaIconContainer *container;
+
+ GTK_WIDGET_CLASS (caja_icon_container_parent_class)->realize (widget);
+
+ container = CAJA_ICON_CONTAINER (widget);
+
+ /* Ensure that the desktop window is native so the background
+ set on it is drawn by X. */
+ if (container->details->is_desktop)
+ {
+ gdk_x11_drawable_get_xid (gtk_layout_get_bin_window (GTK_LAYOUT (widget)));
+ }
+
+ /* Set up DnD. */
+ caja_icon_dnd_init (container, NULL);
+
+ setup_label_gcs (container);
+
+ stipple = eel_stipple_bitmap_for_screen
+ (gdk_drawable_get_screen (GDK_DRAWABLE (gtk_widget_get_window (widget))));
+
+ caja_icon_dnd_set_stipple (container, stipple);
+
+ hadj = gtk_layout_get_hadjustment (GTK_LAYOUT (widget));
+ g_signal_connect (hadj, "value_changed",
+ G_CALLBACK (handle_hadjustment_changed), widget);
+
+ vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (widget));
+ g_signal_connect (vadj, "value_changed",
+ G_CALLBACK (handle_vadjustment_changed), widget);
+
+}
+
+static void
+unrealize (GtkWidget *widget)
+{
+ int i;
+ CajaIconContainer *container;
+
+ container = CAJA_ICON_CONTAINER (widget);
+
+ for (i = 0; i < LAST_LABEL_COLOR; i++)
+ {
+ if (container->details->label_gcs [i])
+ {
+ g_object_unref (container->details->label_gcs [i]);
+ container->details->label_gcs [i] = NULL;
+ }
+ }
+
+ caja_icon_dnd_fini (container);
+
+ if (container->details->typeselect_flush_timeout)
+ {
+ g_source_remove (container->details->typeselect_flush_timeout);
+ container->details->typeselect_flush_timeout = 0;
+ }
+
+ GTK_WIDGET_CLASS (caja_icon_container_parent_class)->unrealize (widget);
+}
+
+static void
+style_set (GtkWidget *widget,
+ GtkStyle *previous_style)
+{
+ CajaIconContainer *container;
+ gboolean frame_text;
+
+ container = CAJA_ICON_CONTAINER (widget);
+
+ gtk_widget_style_get (GTK_WIDGET (container),
+ "frame_text", &frame_text,
+ NULL);
+
+ container->details->use_drop_shadows = container->details->drop_shadows_requested && !frame_text;
+
+ caja_icon_container_theme_changed (CAJA_ICON_CONTAINER (widget));
+
+ if (gtk_widget_get_realized (widget))
+ {
+ invalidate_label_sizes (container);
+ caja_icon_container_request_update_all (container);
+ }
+
+ /* Don't chain up to parent, because that sets the background of the window and we're doing
+ that ourself with some delay, so this would cause flickering */
+}
+
+static gboolean
+button_press_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ CajaIconContainer *container;
+ gboolean selection_changed;
+ gboolean return_value;
+ gboolean clicked_on_icon;
+
+ container = CAJA_ICON_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 (caja_icon_container_parent_class)->button_press_event (widget, event);
+
+ /* Move focus to icon container, unless we're still renaming (to avoid exiting
+ * renaming mode)
+ */
+ if (!gtk_widget_has_focus (widget) && !(is_renaming (container) || is_renaming_pending (container)))
+ {
+ 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)
+ {
+ end_renaming_mode (container, TRUE);
+ 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
+caja_icon_container_did_not_drag (CajaIconContainer *container,
+ GdkEventButton *event)
+{
+ CajaIconContainerDetails *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 = eel_get_system_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
+ * CajaList 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 (CajaIconContainer *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 = eel_get_system_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 */
+ return (click_count == 1);
+}
+
+static void
+clear_drag_state (CajaIconContainer *container)
+{
+ container->details->drag_icon = NULL;
+ container->details->drag_state = DRAG_STATE_INITIAL;
+}
+
+static gboolean
+start_stretching (CajaIconContainer *container)
+{
+ CajaIconContainerDetails *details;
+ CajaIcon *icon;
+ EelDPoint world_point;
+ GtkWidget *toplevel;
+ GtkCornerType corner;
+ GdkCursor *cursor;
+
+ details = container->details;
+ icon = details->stretch_icon;
+
+ /* Check if we hit the stretch handles. */
+ world_point.x = details->drag_x;
+ world_point.y = details->drag_y;
+ if (!caja_icon_canvas_item_hit_test_stretch_handles (icon->item, world_point, &corner))
+ {
+ return FALSE;
+ }
+
+ switch (corner)
+ {
+ case GTK_CORNER_TOP_LEFT:
+ cursor = gdk_cursor_new (GDK_TOP_LEFT_CORNER);
+ break;
+ case GTK_CORNER_BOTTOM_LEFT:
+ cursor = gdk_cursor_new (GDK_BOTTOM_LEFT_CORNER);
+ break;
+ case GTK_CORNER_TOP_RIGHT:
+ cursor = gdk_cursor_new (GDK_TOP_RIGHT_CORNER);
+ break;
+ case GTK_CORNER_BOTTOM_RIGHT:
+ cursor = gdk_cursor_new (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)
+ gdk_cursor_unref (cursor);
+
+ /* Ensure the window itself is focused.. */
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (container));
+ if (toplevel != NULL && gtk_widget_get_realized (toplevel))
+ {
+ eel_gdk_window_focus (gtk_widget_get_window (toplevel), GDK_CURRENT_TIME);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+update_stretch_at_idle (CajaIconContainer *container)
+{
+ CajaIconContainerDetails *details;
+ CajaIcon *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 (CajaIconContainer *container,
+ double world_x, double world_y)
+{
+
+ g_return_if_fail (CAJA_IS_ICON_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 ((GtkFunction) update_stretch_at_idle, container);
+ }
+}
+
+static gboolean
+keyboard_stretching (CajaIconContainer *container,
+ GdkEventKey *event)
+{
+ CajaIcon *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_equal:
+ case GDK_plus:
+ case GDK_KP_Add:
+ icon_set_size (container, icon, size + 5, FALSE, FALSE);
+ break;
+ case GDK_minus:
+ case GDK_KP_Subtract:
+ icon_set_size (container, icon, size - 5, FALSE, FALSE);
+ break;
+ case GDK_0:
+ case GDK_KP_0:
+ caja_icon_container_move_icon (container, icon,
+ icon->x, icon->y,
+ 1.0,
+ FALSE, TRUE, TRUE);
+ break;
+ }
+
+ return TRUE;
+}
+
+static void
+ungrab_stretch_icon (CajaIconContainer *container)
+{
+ eel_canvas_item_ungrab (EEL_CANVAS_ITEM (container->details->stretch_icon->item),
+ GDK_CURRENT_TIME);
+}
+
+static void
+end_stretching (CajaIconContainer *container,
+ double world_x, double world_y)
+{
+ CajaIconPosition position;
+ CajaIcon *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 (caja_icon_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 (CajaIconContainer *container)
+{
+ CajaIcon *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);
+ }
+ caja_icon_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)
+{
+ CajaIconContainer *container;
+ CajaIconContainerDetails *details;
+ double world_x, world_y;
+
+ container = CAJA_ICON_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)
+ {
+ caja_icon_container_did_not_drag (container, event);
+ }
+ else
+ {
+ caja_icon_dnd_end_drag (container);
+ caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
+ "end drag from icon 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 (caja_icon_container_parent_class)->button_release_event (widget, event);
+}
+
+static int
+motion_notify_event (GtkWidget *widget,
+ GdkEventMotion *event)
+{
+ CajaIconContainer *container;
+ CajaIconContainerDetails *details;
+ double world_x, world_y;
+ int canvas_x, canvas_y;
+ GdkDragAction actions;
+
+ container = CAJA_ICON_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;
+
+ end_renaming_mode (container, TRUE);
+
+ eel_canvas_w2c (EEL_CANVAS (container),
+ details->drag_x,
+ details->drag_y,
+ &canvas_x,
+ &canvas_y);
+
+ actions = GDK_ACTION_COPY
+ | GDK_ACTION_LINK
+ | GDK_ACTION_ASK;
+
+ if (container->details->drag_allow_moves)
+ {
+ actions |= GDK_ACTION_MOVE;
+ }
+
+ caja_icon_dnd_begin_drag (container,
+ actions,
+ details->drag_button,
+ event,
+ canvas_x,
+ canvas_y);
+ caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
+ "begin drag from icon 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 (caja_icon_container_parent_class)->motion_notify_event (widget, event);
+}
+
+static void
+caja_icon_container_search_position_func (CajaIconContainer *container,
+ GtkWidget *search_dialog)
+{
+ gint x, y;
+ gint cont_x, cont_y;
+ gint cont_width, cont_height;
+ GdkWindow *cont_window;
+ GdkScreen *screen;
+ GtkRequisition requisition;
+ gint monitor_num;
+ GdkRectangle monitor;
+
+
+ cont_window = gtk_widget_get_window (GTK_WIDGET (container));
+ screen = gdk_drawable_get_screen (cont_window);
+
+ monitor_num = gdk_screen_get_monitor_at_window (screen, cont_window);
+ gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
+
+ gtk_widget_realize (search_dialog);
+
+ gdk_window_get_origin (cont_window, &cont_x, &cont_y);
+
+#if GTK_CHECK_VERSION(3, 0, 0)
+ cont_width = gdk_window_get_width(GDK_WINDOW(cont_window));
+ cont_height = gdk_window_get_height(GDK_WINDOW(cont_window));
+#else
+ gdk_drawable_get_size(cont_window, &cont_width, &cont_height);
+#endif
+
+ gtk_widget_size_request (search_dialog, &requisition);
+
+ if (cont_x + cont_width - requisition.width > gdk_screen_get_width (screen))
+ {
+ x = gdk_screen_get_width (screen) - requisition.width;
+ }
+ else if (cont_x + cont_width - requisition.width < 0)
+ {
+ x = 0;
+ }
+ else
+ {
+ x = cont_x + cont_width - requisition.width;
+ }
+
+ if (cont_y + cont_height > gdk_screen_get_height (screen))
+ {
+ y = gdk_screen_get_height (screen) - requisition.height;
+ }
+ else if (cont_y + cont_height < 0) /* isn't really possible ... */
+ {
+ y = 0;
+ }
+ else
+ {
+ y = cont_y + cont_height;
+ }
+
+ gtk_window_move (GTK_WINDOW (search_dialog), x, y);
+}
+
+static gboolean
+caja_icon_container_real_search_enable_popdown (gpointer data)
+{
+ CajaIconContainer *container = (CajaIconContainer *)data;
+
+ container->details->disable_popdown = FALSE;
+
+ g_object_unref (container);
+
+ return FALSE;
+}
+
+static void
+caja_icon_container_search_enable_popdown (GtkWidget *widget,
+ gpointer data)
+{
+ CajaIconContainer *container = (CajaIconContainer *) data;
+
+ g_object_ref (container);
+ g_timeout_add (200, caja_icon_container_real_search_enable_popdown, data);
+}
+
+static void
+caja_icon_container_search_disable_popdown (GtkEntry *entry,
+ GtkMenu *menu,
+ gpointer data)
+{
+ CajaIconContainer *container = (CajaIconContainer *) data;
+
+ container->details->disable_popdown = TRUE;
+ g_signal_connect (menu, "hide",
+ G_CALLBACK (caja_icon_container_search_enable_popdown),
+ data);
+}
+
+/* Cut and paste from gtkwindow.c */
+static void
+send_focus_change (GtkWidget *widget, gboolean in)
+{
+ GdkEvent *fevent;
+
+ fevent = gdk_event_new (GDK_FOCUS_CHANGE);
+
+ g_object_ref (widget);
+ ((GdkEventFocus *) fevent)->in = in;
+
+ gtk_widget_send_focus_change (widget, fevent);
+
+ fevent->focus_change.type = GDK_FOCUS_CHANGE;
+ fevent->focus_change.window = g_object_ref (gtk_widget_get_window (widget));
+ fevent->focus_change.in = in;
+
+ gtk_widget_event (widget, fevent);
+
+ g_object_notify (G_OBJECT (widget), "has-focus");
+
+ g_object_unref (widget);
+ gdk_event_free (fevent);
+}
+
+static void
+caja_icon_container_search_dialog_hide (GtkWidget *search_dialog,
+ CajaIconContainer *container)
+{
+ if (container->details->disable_popdown)
+ {
+ return;
+ }
+
+ if (container->details->search_entry_changed_id)
+ {
+ g_signal_handler_disconnect (container->details->search_entry,
+ container->details->search_entry_changed_id);
+ container->details->search_entry_changed_id = 0;
+ }
+ if (container->details->typeselect_flush_timeout)
+ {
+ g_source_remove (container->details->typeselect_flush_timeout);
+ container->details->typeselect_flush_timeout = 0;
+ }
+
+ /* send focus-in event */
+ send_focus_change (GTK_WIDGET (container->details->search_entry), FALSE);
+ gtk_widget_hide (search_dialog);
+ gtk_entry_set_text (GTK_ENTRY (container->details->search_entry), "");
+}
+
+static gboolean
+caja_icon_container_search_entry_flush_timeout (CajaIconContainer *container)
+{
+ caja_icon_container_search_dialog_hide (container->details->search_window, container);
+
+ return TRUE;
+}
+
+/* Because we're visible but offscreen, we just set a flag in the preedit
+ * callback.
+ */
+static void
+caja_icon_container_search_preedit_changed (GtkEntry *entry,
+ CajaIconContainer *container)
+{
+ container->details->imcontext_changed = 1;
+ if (container->details->typeselect_flush_timeout)
+ {
+ g_source_remove (container->details->typeselect_flush_timeout);
+ container->details->typeselect_flush_timeout =
+ g_timeout_add_seconds (CAJA_ICON_CONTAINER_SEARCH_DIALOG_TIMEOUT,
+ (GSourceFunc) caja_icon_container_search_entry_flush_timeout,
+ container);
+ }
+}
+
+static void
+caja_icon_container_search_activate (GtkEntry *entry,
+ CajaIconContainer *container)
+{
+ caja_icon_container_search_dialog_hide (container->details->search_window,
+ container);
+
+ activate_selected_items (container);
+}
+
+static gboolean
+caja_icon_container_search_delete_event (GtkWidget *widget,
+ GdkEventAny *event,
+ CajaIconContainer *container)
+{
+ g_assert (GTK_IS_WIDGET (widget));
+
+ caja_icon_container_search_dialog_hide (widget, container);
+
+ return TRUE;
+}
+
+static gboolean
+caja_icon_container_search_button_press_event (GtkWidget *widget,
+ GdkEventButton *event,
+ CajaIconContainer *container)
+{
+ g_assert (GTK_IS_WIDGET (widget));
+
+ caja_icon_container_search_dialog_hide (widget, container);
+
+ if (event->window == gtk_layout_get_bin_window (GTK_LAYOUT (container)))
+ {
+ button_press_event (GTK_WIDGET (container), event);
+ }
+
+ return TRUE;
+}
+
+static void
+caja_icon_container_get_icon_text (CajaIconContainer *container,
+ CajaIconData *data,
+ char **editable_text,
+ char **additional_text,
+ gboolean include_invisible)
+{
+ CajaIconContainerClass *klass;
+
+ klass = CAJA_ICON_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
+caja_icon_container_search_iter (CajaIconContainer *container,
+ const char *key, gint n)
+{
+ GList *p;
+ CajaIcon *icon;
+ char *name;
+ int count;
+ char *normalized_key, *case_normalized_key;
+ char *normalized_name, *case_normalized_name;
+
+ g_assert (key != NULL);
+ g_assert (n >= 1);
+
+ normalized_key = g_utf8_normalize (key, -1, G_NORMALIZE_ALL);
+ if (!normalized_key)
+ {
+ return FALSE;
+ }
+ case_normalized_key = g_utf8_casefold (normalized_key, -1);
+ g_free (normalized_key);
+ if (!case_normalized_key)
+ {
+ return FALSE;
+ }
+
+ icon = NULL;
+ name = NULL;
+ count = 0;
+ for (p = container->details->icons; p != NULL && count != n; p = p->next)
+ {
+ icon = p->data;
+ caja_icon_container_get_icon_text (container, icon->data, &name,
+ NULL, TRUE);
+
+ /* This can happen if a key event is handled really early while
+ * loading the icon container, before the items have all been
+ * updated once.
+ */
+ if (!name)
+ {
+ continue;
+ }
+
+ normalized_name = g_utf8_normalize (name, -1, G_NORMALIZE_ALL);
+ if (!normalized_name)
+ {
+ continue;
+ }
+ case_normalized_name = g_utf8_casefold (normalized_name, -1);
+ g_free (normalized_name);
+ if (!case_normalized_name)
+ {
+ continue;
+ }
+
+ if (strncmp (case_normalized_key, case_normalized_name,
+ strlen (case_normalized_key)) == 0)
+ {
+ count++;
+ }
+
+ g_free (case_normalized_name);
+ g_free (name);
+ name = NULL;
+ }
+
+ g_free (case_normalized_key);
+
+ if (count == n)
+ {
+ if (select_one_unselect_others (container, icon))
+ {
+ g_signal_emit (container, signals[SELECTION_CHANGED], 0);
+ }
+ schedule_keyboard_icon_reveal (container, icon);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+caja_icon_container_search_move (GtkWidget *window,
+ CajaIconContainer *container,
+ gboolean up)
+{
+ gboolean ret;
+ gint len;
+ gint count = 0;
+ const gchar *text;
+
+ text = gtk_entry_get_text (GTK_ENTRY (container->details->search_entry));
+
+ g_assert (text != NULL);
+
+ if (container->details->selected_iter == 0)
+ {
+ return;
+ }
+
+ if (up && container->details->selected_iter == 1)
+ {
+ return;
+ }
+
+ len = strlen (text);
+
+ if (len < 1)
+ {
+ return;
+ }
+
+ /* search */
+ unselect_all (container);
+
+ ret = caja_icon_container_search_iter (container, text,
+ up?((container->details->selected_iter) - 1):((container->details->selected_iter + 1)));
+
+ if (ret)
+ {
+ /* found */
+ container->details->selected_iter += up?(-1):(1);
+ }
+ else
+ {
+ /* return to old iter */
+ count = 0;
+ caja_icon_container_search_iter (container, text,
+ container->details->selected_iter);
+ }
+}
+
+static gboolean
+caja_icon_container_search_scroll_event (GtkWidget *widget,
+ GdkEventScroll *event,
+ CajaIconContainer *container)
+{
+ gboolean retval = FALSE;
+
+ if (event->direction == GDK_SCROLL_UP)
+ {
+ caja_icon_container_search_move (widget, container, TRUE);
+ retval = TRUE;
+ }
+ else if (event->direction == GDK_SCROLL_DOWN)
+ {
+ caja_icon_container_search_move (widget, container, FALSE);
+ retval = TRUE;
+ }
+
+ /* renew the flush timeout */
+ if (retval && container->details->typeselect_flush_timeout)
+ {
+ g_source_remove (container->details->typeselect_flush_timeout);
+ container->details->typeselect_flush_timeout =
+ g_timeout_add_seconds (CAJA_ICON_CONTAINER_SEARCH_DIALOG_TIMEOUT,
+ (GSourceFunc) caja_icon_container_search_entry_flush_timeout,
+ container);
+ }
+
+ return retval;
+}
+
+static gboolean
+caja_icon_container_search_key_press_event (GtkWidget *widget,
+ GdkEventKey *event,
+ CajaIconContainer *container)
+{
+ gboolean retval = FALSE;
+
+ g_assert (GTK_IS_WIDGET (widget));
+ g_assert (CAJA_IS_ICON_CONTAINER (container));
+
+ /* close window and cancel the search */
+ if (event->keyval == GDK_Escape || event->keyval == GDK_Tab)
+ {
+ caja_icon_container_search_dialog_hide (widget, container);
+ return TRUE;
+ }
+
+ /* close window and activate alternate */
+ if (event->keyval == GDK_Return && event->state & GDK_SHIFT_MASK)
+ {
+ caja_icon_container_search_dialog_hide (widget,
+ container);
+
+ activate_selected_items_alternate (container, NULL);
+ return TRUE;
+ }
+
+ /* select previous matching iter */
+ if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up)
+ {
+ caja_icon_container_search_move (widget, container, TRUE);
+ retval = TRUE;
+ }
+
+ if (((event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) == (GDK_CONTROL_MASK | GDK_SHIFT_MASK))
+ && (event->keyval == GDK_g || event->keyval == GDK_G))
+ {
+ caja_icon_container_search_move (widget, container, TRUE);
+ retval = TRUE;
+ }
+
+ /* select next matching iter */
+ if (event->keyval == GDK_Down || event->keyval == GDK_KP_Down)
+ {
+ caja_icon_container_search_move (widget, container, FALSE);
+ retval = TRUE;
+ }
+
+ if (((event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) == GDK_CONTROL_MASK)
+ && (event->keyval == GDK_g || event->keyval == GDK_G))
+ {
+ caja_icon_container_search_move (widget, container, FALSE);
+ retval = TRUE;
+ }
+
+ /* renew the flush timeout */
+ if (retval && container->details->typeselect_flush_timeout)
+ {
+ g_source_remove (container->details->typeselect_flush_timeout);
+ container->details->typeselect_flush_timeout =
+ g_timeout_add_seconds (CAJA_ICON_CONTAINER_SEARCH_DIALOG_TIMEOUT,
+ (GSourceFunc) caja_icon_container_search_entry_flush_timeout,
+ container);
+ }
+
+ return retval;
+}
+
+static void
+caja_icon_container_search_init (GtkWidget *entry,
+ CajaIconContainer *container)
+{
+ gint ret;
+ gint len;
+ const gchar *text;
+
+ g_assert (GTK_IS_ENTRY (entry));
+ g_assert (CAJA_IS_ICON_CONTAINER (container));
+
+ text = gtk_entry_get_text (GTK_ENTRY (entry));
+ len = strlen (text);
+
+ /* search */
+ unselect_all (container);
+ if (container->details->typeselect_flush_timeout)
+ {
+ g_source_remove (container->details->typeselect_flush_timeout);
+ container->details->typeselect_flush_timeout =
+ g_timeout_add_seconds (CAJA_ICON_CONTAINER_SEARCH_DIALOG_TIMEOUT,
+ (GSourceFunc) caja_icon_container_search_entry_flush_timeout,
+ container);
+ }
+
+ if (len < 1)
+ {
+ return;
+ }
+
+ ret = caja_icon_container_search_iter (container, text, 1);
+
+ if (ret)
+ {
+ container->details->selected_iter = 1;
+ }
+}
+
+static void
+caja_icon_container_ensure_interactive_directory (CajaIconContainer *container)
+{
+ GtkWidget *frame, *vbox, *toplevel;
+
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (container));
+
+ if (container->details->search_window != NULL)
+ {
+ return;
+ }
+
+ container->details->search_window = gtk_window_new (GTK_WINDOW_POPUP);
+
+ gtk_window_set_modal (GTK_WINDOW (container->details->search_window), TRUE);
+ gtk_window_set_type_hint (GTK_WINDOW (container->details->search_window),
+ GDK_WINDOW_TYPE_HINT_COMBO);
+
+ g_signal_connect (container->details->search_window, "delete_event",
+ G_CALLBACK (caja_icon_container_search_delete_event),
+ container);
+ g_signal_connect (container->details->search_window, "key_press_event",
+ G_CALLBACK (caja_icon_container_search_key_press_event),
+ container);
+ g_signal_connect (container->details->search_window, "button_press_event",
+ G_CALLBACK (caja_icon_container_search_button_press_event),
+ container);
+ g_signal_connect (container->details->search_window, "scroll_event",
+ G_CALLBACK (caja_icon_container_search_scroll_event),
+ container);
+
+ frame = gtk_frame_new (NULL);
+ gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
+ gtk_widget_show (frame);
+ gtk_container_add (GTK_CONTAINER (container->details->search_window), frame);
+
+ vbox = gtk_vbox_new (FALSE, 0);
+ gtk_widget_show (vbox);
+ gtk_container_add (GTK_CONTAINER (frame), vbox);
+ gtk_container_set_border_width (GTK_CONTAINER (vbox), 3);
+
+ /* add entry */
+ container->details->search_entry = gtk_entry_new ();
+ gtk_widget_show (container->details->search_entry);
+ g_signal_connect (container->details->search_entry, "populate_popup",
+ G_CALLBACK (caja_icon_container_search_disable_popdown),
+ container);
+ g_signal_connect (container->details->search_entry, "activate",
+ G_CALLBACK (caja_icon_container_search_activate),
+ container);
+ g_signal_connect (container->details->search_entry,
+ "preedit-changed",
+ G_CALLBACK (caja_icon_container_search_preedit_changed),
+ container);
+ gtk_container_add (GTK_CONTAINER (vbox), container->details->search_entry);
+
+ gtk_widget_realize (container->details->search_entry);
+}
+
+/* Pops up the interactive search entry. If keybinding is TRUE then the user
+ * started this by typing the start_interactive_search keybinding. Otherwise, it came from
+ */
+static gboolean
+caja_icon_container_real_start_interactive_search (CajaIconContainer *container,
+ gboolean keybinding)
+{
+ /* We only start interactive search if we have focus. If one of our
+ * children have focus, we don't want to start the search.
+ */
+ GtkWidgetClass *entry_parent_class;
+
+ if (container->details->search_window != NULL &&
+ gtk_widget_get_visible (container->details->search_window))
+ {
+ return TRUE;
+ }
+
+ if (!gtk_widget_has_focus (GTK_WIDGET (container)))
+ {
+ return FALSE;
+ }
+
+ caja_icon_container_ensure_interactive_directory (container);
+
+ if (keybinding)
+ {
+ gtk_entry_set_text (GTK_ENTRY (container->details->search_entry), "");
+ }
+
+ /* done, show it */
+ caja_icon_container_search_position_func (container, container->details->search_window);
+ gtk_widget_show (container->details->search_window);
+ if (container->details->search_entry_changed_id == 0)
+ {
+ container->details->search_entry_changed_id =
+ g_signal_connect (container->details->search_entry, "changed",
+ G_CALLBACK (caja_icon_container_search_init),
+ container);
+ }
+
+ container->details->typeselect_flush_timeout =
+ g_timeout_add_seconds (CAJA_ICON_CONTAINER_SEARCH_DIALOG_TIMEOUT,
+ (GSourceFunc) caja_icon_container_search_entry_flush_timeout,
+ container);
+
+ /* Grab focus will select all the text. We don't want that to happen, so we
+ * call the parent instance and bypass the selection change. This is probably
+ * really non-kosher. */
+ entry_parent_class = g_type_class_peek_parent (GTK_ENTRY_GET_CLASS (container->details->search_entry));
+ (entry_parent_class->grab_focus) (container->details->search_entry);
+
+ /* send focus-in event */
+ send_focus_change (container->details->search_entry, TRUE);
+
+ /* search first matching iter */
+ caja_icon_container_search_init (container->details->search_entry, container);
+
+ return TRUE;
+}
+
+static gboolean
+caja_icon_container_start_interactive_search (CajaIconContainer *container)
+{
+ return caja_icon_container_real_start_interactive_search (container, TRUE);
+}
+
+static gboolean
+handle_popups (CajaIconContainer *container,
+ GdkEventKey *event,
+ const char *signal)
+{
+ GdkEventButton button_event = { 0 };
+
+ g_signal_emit_by_name (container, signal, &button_event);
+
+ return TRUE;
+}
+
+static int
+key_press_event (GtkWidget *widget,
+ GdkEventKey *event)
+{
+ CajaIconContainer *container;
+ gboolean handled;
+
+ container = CAJA_ICON_CONTAINER (widget);
+ handled = FALSE;
+
+ if (is_renaming (container) || is_renaming_pending (container))
+ {
+ switch (event->keyval)
+ {
+ case GDK_Return:
+ case GDK_KP_Enter:
+ end_renaming_mode (container, TRUE);
+ handled = TRUE;
+ break;
+ case GDK_Escape:
+ end_renaming_mode (container, FALSE);
+ handled = TRUE;
+ break;
+ default:
+ break;
+ }
+ }
+ else
+ {
+ switch (event->keyval)
+ {
+ case GDK_Home:
+ case GDK_KP_Home:
+ keyboard_home (container, event);
+ handled = TRUE;
+ break;
+ case GDK_End:
+ case GDK_KP_End:
+ keyboard_end (container, event);
+ handled = TRUE;
+ break;
+ case GDK_Left:
+ case GDK_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_Up:
+ case GDK_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_Right:
+ case GDK_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_Down:
+ case GDK_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_space:
+ keyboard_space (container, event);
+ handled = TRUE;
+ break;
+#ifndef TAB_NAVIGATION_DISABLED
+ case GDK_Tab:
+ case GDK_ISO_Left_Tab:
+ select_previous_or_next_icon (container,
+ (event->state & GDK_SHIFT_MASK) == 0, event);
+ handled = TRUE;
+ break;
+#endif
+ case GDK_Return:
+ case GDK_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_Escape:
+ handled = undo_stretching (container);
+ break;
+ case GDK_plus:
+ case GDK_minus:
+ case GDK_equal:
+ case GDK_KP_Add:
+ case GDK_KP_Subtract:
+ case GDK_0:
+ case GDK_KP_0:
+ if (event->state & GDK_CONTROL_MASK)
+ {
+ handled = keyboard_stretching (container, event);
+ }
+ break;
+ case GDK_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_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 (caja_icon_container_parent_class)->key_press_event (widget, event);
+ }
+
+ /* We pass the event to the search_entry. If its text changes, then we
+ * start the typeahead find capabilities.
+ * Copied from CajaIconContainer */
+ if (!handled &&
+ event->keyval != GDK_slash /* don't steal slash key event, used for "go to" */ &&
+ event->keyval != GDK_BackSpace &&
+ event->keyval != GDK_Delete)
+ {
+ GdkEvent *new_event;
+ GdkWindow *window;
+ char *old_text;
+ const char *new_text;
+ gboolean retval;
+ GdkScreen *screen;
+ gboolean text_modified;
+ gulong popup_menu_id;
+
+ caja_icon_container_ensure_interactive_directory (container);
+
+ /* Make a copy of the current text */
+ old_text = g_strdup (gtk_entry_get_text (GTK_ENTRY (container->details->search_entry)));
+ new_event = gdk_event_copy ((GdkEvent *) event);
+ window = ((GdkEventKey *) new_event)->window;
+ ((GdkEventKey *) new_event)->window = gtk_widget_get_window (container->details->search_entry);
+ gtk_widget_realize (container->details->search_window);
+
+ popup_menu_id = g_signal_connect (container->details->search_entry,
+ "popup_menu", G_CALLBACK (gtk_true), NULL);
+
+ /* Move the entry off screen */
+ screen = gtk_widget_get_screen (GTK_WIDGET (container));
+ gtk_window_move (GTK_WINDOW (container->details->search_window),
+ gdk_screen_get_width (screen) + 1,
+ gdk_screen_get_height (screen) + 1);
+ gtk_widget_show (container->details->search_window);
+
+ /* Send the event to the window. If the preedit_changed signal is emitted
+ * during this event, we will set priv->imcontext_changed */
+ container->details->imcontext_changed = FALSE;
+ retval = gtk_widget_event (container->details->search_entry, new_event);
+ gtk_widget_hide (container->details->search_window);
+
+ g_signal_handler_disconnect (container->details->search_entry,
+ popup_menu_id);
+
+ /* We check to make sure that the entry tried to handle the text, and that
+ * the text has changed. */
+ new_text = gtk_entry_get_text (GTK_ENTRY (container->details->search_entry));
+ text_modified = strcmp (old_text, new_text) != 0;
+ g_free (old_text);
+ if (container->details->imcontext_changed || /* we're in a preedit */
+ (retval && text_modified)) /* ...or the text was modified */
+ {
+ if (caja_icon_container_real_start_interactive_search (container, FALSE))
+ {
+ gtk_widget_grab_focus (GTK_WIDGET (container));
+ return TRUE;
+ }
+ else
+ {
+ gtk_entry_set_text (GTK_ENTRY (container->details->search_entry), "");
+ return FALSE;
+ }
+ }
+
+ ((GdkEventKey *) new_event)->window = window;
+ gdk_event_free (new_event);
+ }
+
+ return handled;
+}
+
+static gboolean
+popup_menu (GtkWidget *widget)
+{
+ CajaIconContainer *container;
+
+ container = CAJA_ICON_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
+draw_canvas_background (EelCanvas *canvas,
+ int x, int y, int width, int height)
+{
+ /* Don't chain up to the parent to avoid clearing and redrawing */
+}
+
+
+static gboolean
+expose_event (GtkWidget *widget,
+ GdkEventExpose *event)
+{
+ /* g_warning ("Expose Icon Container %p '%d,%d: %d,%d'",
+ widget,
+ event->area.x, event->area.y,
+ event->area.width, event->area.height); */
+
+ return GTK_WIDGET_CLASS (caja_icon_container_parent_class)->expose_event (widget, event);
+}
+
+static AtkObject *
+get_accessible (GtkWidget *widget)
+{
+ AtkObject *accessible;
+
+ if ((accessible = eel_accessibility_get_atk_object (widget)))
+ {
+ return accessible;
+ }
+
+ accessible = g_object_new
+ (caja_icon_container_accessible_get_type (), NULL);
+
+ return eel_accessibility_set_atk_object_return (widget, accessible);
+}
+
+static void
+grab_notify_cb (GtkWidget *widget,
+ gboolean was_grabbed)
+{
+ CajaIconContainer *container;
+
+ container = CAJA_ICON_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)
+{
+ CajaIconContainer *container;
+
+ container = CAJA_ICON_CONTAINER (callback_data);
+ invalidate_label_sizes (container);
+ schedule_redo_layout (container);
+}
+
+static GObject*
+caja_icon_container_constructor (GType type,
+ guint n_construct_params,
+ GObjectConstructParam *construct_params)
+{
+ CajaIconContainer *container;
+ GObject *object;
+
+ object = G_OBJECT_CLASS (caja_icon_container_parent_class)->constructor
+ (type,
+ n_construct_params,
+ construct_params);
+
+ container = CAJA_ICON_CONTAINER (object);
+ if (caja_icon_container_get_is_desktop (container))
+ {
+ eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_DESKTOP_TEXT_ELLIPSIS_LIMIT,
+ text_ellipsis_limit_changed_container_callback,
+ container, G_OBJECT (container));
+ }
+ else
+ {
+ eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_ICON_VIEW_TEXT_ELLIPSIS_LIMIT,
+ text_ellipsis_limit_changed_container_callback,
+ container, G_OBJECT (container));
+ }
+
+ return object;
+}
+
+/* Initialization. */
+
+static void
+caja_icon_container_class_init (CajaIconContainerClass *class)
+{
+ GtkWidgetClass *widget_class;
+ EelCanvasClass *canvas_class;
+ GtkBindingSet *binding_set;
+
+ G_OBJECT_CLASS (class)->constructor = caja_icon_container_constructor;
+ G_OBJECT_CLASS (class)->finalize = finalize;
+ GTK_OBJECT_CLASS (class)->destroy = destroy;
+
+ /* Signals. */
+
+ signals[SELECTION_CHANGED]
+ = g_signal_new ("selection_changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaIconContainerClass,
+ 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 (CajaIconContainerClass,
+ button_press),
+ NULL, NULL,
+ caja_marshal_BOOLEAN__POINTER,
+ 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 (CajaIconContainerClass,
+ 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 (CajaIconContainerClass,
+ activate_alternate),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1,
+ 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 (CajaIconContainerClass,
+ 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 (CajaIconContainerClass,
+ 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 (CajaIconContainerClass,
+ 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 (CajaIconContainerClass,
+ icon_position_changed),
+ NULL, NULL,
+ caja_marshal_VOID__POINTER_POINTER,
+ G_TYPE_NONE, 2,
+ G_TYPE_POINTER,
+ G_TYPE_POINTER);
+ signals[ICON_TEXT_CHANGED]
+ = g_signal_new ("icon_text_changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaIconContainerClass,
+ icon_text_changed),
+ NULL, NULL,
+ caja_marshal_VOID__POINTER_STRING,
+ G_TYPE_NONE, 2,
+ G_TYPE_POINTER,
+ G_TYPE_STRING);
+ signals[ICON_STRETCH_STARTED]
+ = g_signal_new ("icon_stretch_started",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaIconContainerClass,
+ 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 (CajaIconContainerClass,
+ icon_stretch_ended),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
+ signals[RENAMING_ICON]
+ = g_signal_new ("renaming_icon",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaIconContainerClass,
+ renaming_icon),
+ 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 (CajaIconContainerClass,
+ get_icon_uri),
+ NULL, NULL,
+ eel_marshal_STRING__POINTER,
+ 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 (CajaIconContainerClass,
+ get_icon_drop_target_uri),
+ NULL, NULL,
+ eel_marshal_STRING__POINTER,
+ 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 (CajaIconContainerClass,
+ move_copy_items),
+ NULL, NULL,
+ caja_marshal_VOID__POINTER_POINTER_POINTER_ENUM_INT_INT,
+ 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 (CajaIconContainerClass,
+ handle_netscape_url),
+ NULL, NULL,
+ caja_marshal_VOID__STRING_STRING_ENUM_INT_INT,
+ 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 (CajaIconContainerClass,
+ handle_uri_list),
+ NULL, NULL,
+ caja_marshal_VOID__STRING_STRING_ENUM_INT_INT,
+ 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 (CajaIconContainerClass,
+ handle_text),
+ NULL, NULL,
+ caja_marshal_VOID__STRING_STRING_ENUM_INT_INT,
+ 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 (CajaIconContainerClass,
+ handle_raw),
+ NULL, NULL,
+ caja_marshal_VOID__POINTER_INT_STRING_STRING_ENUM_INT_INT,
+ 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[GET_CONTAINER_URI]
+ = g_signal_new ("get_container_uri",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaIconContainerClass,
+ get_container_uri),
+ NULL, NULL,
+ eel_marshal_STRING__VOID,
+ 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 (CajaIconContainerClass,
+ can_accept_item),
+ NULL, NULL,
+ eel_marshal_INT__POINTER_STRING,
+ 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 (CajaIconContainerClass,
+ get_stored_icon_position),
+ NULL, NULL,
+ eel_marshal_BOOLEAN__POINTER_POINTER,
+ 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 (CajaIconContainerClass,
+ get_stored_layout_timestamp),
+ NULL, NULL,
+ eel_marshal_BOOLEAN__POINTER_POINTER,
+ 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 (CajaIconContainerClass,
+ store_layout_timestamp),
+ NULL, NULL,
+ eel_marshal_BOOLEAN__POINTER_POINTER,
+ 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 (CajaIconContainerClass,
+ layout_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ signals[PREVIEW]
+ = g_signal_new ("preview",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaIconContainerClass,
+ preview),
+ NULL, NULL,
+ caja_marshal_INT__POINTER_BOOLEAN,
+ G_TYPE_INT, 2,
+ G_TYPE_POINTER,
+ G_TYPE_BOOLEAN);
+ signals[BAND_SELECT_STARTED]
+ = g_signal_new ("band_select_started",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaIconContainerClass,
+ 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 (CajaIconContainerClass,
+ 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 (CajaIconContainerClass,
+ 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 (CajaIconContainerClass,
+ 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 (CajaIconContainerClass,
+ cleared),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[START_INTERACTIVE_SEARCH]
+ = g_signal_new ("start_interactive_search",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (CajaIconContainerClass,
+ start_interactive_search),
+ NULL, NULL,
+ caja_marshal_BOOLEAN__VOID,
+ G_TYPE_BOOLEAN, 0);
+
+ /* GtkWidget class. */
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->size_request = size_request;
+ widget_class->size_allocate = size_allocate;
+ 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->get_accessible = get_accessible;
+ widget_class->style_set = style_set;
+ widget_class->expose_event = expose_event;
+ widget_class->grab_notify = grab_notify_cb;
+
+ canvas_class = EEL_CANVAS_CLASS (class);
+ canvas_class->draw_background = draw_canvas_background;
+
+ class->start_interactive_search = caja_icon_container_start_interactive_search;
+
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_boolean ("frame_text",
+ "Frame Text",
+ "Draw a frame around unselected text",
+ FALSE,
+ G_PARAM_READABLE));
+
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_boxed ("selection_box_color",
+ "Selection Box Color",
+ "Color of the selection box",
+ GDK_TYPE_COLOR,
+ G_PARAM_READABLE));
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_uchar ("selection_box_alpha",
+ "Selection Box Alpha",
+ "Opacity of the selection box",
+ 0, 0xff,
+ DEFAULT_SELECTION_BOX_ALPHA,
+ G_PARAM_READABLE));
+
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_uchar ("highlight_alpha",
+ "Highlight Alpha",
+ "Opacity of the highlight for selected icons",
+ 0, 0xff,
+ DEFAULT_HIGHLIGHT_ALPHA,
+ G_PARAM_READABLE));
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_uchar ("normal_alpha",
+ "Normal Alpha",
+ "Opacity of the normal icons if frame_text is set",
+ 0, 0xff,
+ DEFAULT_NORMAL_ALPHA,
+ G_PARAM_READABLE));
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_uchar ("prelight_alpha",
+ "Prelight Alpha",
+ "Opacity of the prelight icons if frame_text is set",
+ 0, 0xff,
+ DEFAULT_PRELIGHT_ALPHA,
+ G_PARAM_READABLE));
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_boxed ("light_info_color",
+ "Light Info Color",
+ "Color used for information text against a dark background",
+ GDK_TYPE_COLOR,
+ G_PARAM_READABLE));
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_boxed ("dark_info_color",
+ "Dark Info Color",
+ "Color used for information text against a light background",
+ GDK_TYPE_COLOR,
+ G_PARAM_READABLE));
+
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_uint ("normal_icon_render_mode",
+ "Normal Icon Render Mode",
+ "Mode of normal icons being rendered (0=normal, 1=spotlight, 2=colorize, 3=colorize-monochromely)",
+ 0, 3,
+ DEFAULT_NORMAL_ICON_RENDER_MODE,
+ G_PARAM_READABLE));
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_uint ("prelight_icon_render_mode",
+ "Prelight Icon Render Mode",
+ "Mode of prelight icons being rendered (0=normal, 1=spotlight, 2=colorize, 3=colorize-monochromely)",
+ 0, 3,
+ DEFAULT_PRELIGHT_ICON_RENDER_MODE,
+ G_PARAM_READABLE));
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_boxed ("normal_icon_color",
+ "Icon Normal Color",
+ "Color used for colorizing icons in normal state (default base[NORMAL])",
+ GDK_TYPE_COLOR,
+ G_PARAM_READABLE));
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_boxed ("prelight_icon_color",
+ "Icon Prelight Color",
+ "Color used for colorizing prelighted icons (default base[PRELIGHT])",
+ GDK_TYPE_COLOR,
+ G_PARAM_READABLE));
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_uint ("normal_icon_saturation",
+ "Normal Icon Saturation",
+ "Saturation of icons in normal state",
+ 0, 255,
+ DEFAULT_NORMAL_ICON_SATURATION,
+ G_PARAM_READABLE));
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_uint ("prelight_icon_saturation",
+ "Prelight Icon Saturation",
+ "Saturation of icons in prelight state",
+ 0, 255,
+ DEFAULT_PRELIGHT_ICON_SATURATION,
+ G_PARAM_READABLE));
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_uint ("normal_icon_brightness",
+ "Normal Icon Brightness",
+ "Brightness of icons in normal state",
+ 0, 255,
+ DEFAULT_NORMAL_ICON_BRIGHTNESS,
+ G_PARAM_READABLE));
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_uint ("prelight_icon_brightness",
+ "Prelight Icon Brightness",
+ "Brightness of icons in prelight state",
+ 0, 255,
+ DEFAULT_PRELIGHT_ICON_BRIGHTNESS,
+ G_PARAM_READABLE));
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_uint ("normal_icon_lighten",
+ "Normal Icon Lighten",
+ "Lighten icons in normal state",
+ 0, 255,
+ DEFAULT_NORMAL_ICON_LIGHTEN,
+ G_PARAM_READABLE));
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_uint ("prelight_icon_lighten",
+ "Prelight Icon Lighten",
+ "Lighten icons in prelight state",
+ 0, 255,
+ DEFAULT_PRELIGHT_ICON_LIGHTEN,
+ G_PARAM_READABLE));
+ 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));
+
+
+ binding_set = gtk_binding_set_by_class (class);
+
+ gtk_binding_entry_add_signal (binding_set, GDK_f, GDK_CONTROL_MASK, "start_interactive_search", 0);
+ gtk_binding_entry_add_signal (binding_set, GDK_F, GDK_CONTROL_MASK, "start_interactive_search", 0);
+}
+
+static void
+update_selected (CajaIconContainer *container)
+{
+ GList *node;
+ CajaIcon *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 (CAJA_ICON_CONTAINER (widget));
+
+ return FALSE;
+}
+
+static gboolean
+handle_focus_out_event (GtkWidget *widget, GdkEventFocus *event, gpointer user_data)
+{
+ /* End renaming and commit change. */
+ end_renaming_mode (CAJA_ICON_CONTAINER (widget), TRUE);
+ update_selected (CAJA_ICON_CONTAINER (widget));
+
+ return FALSE;
+}
+
+
+static int text_ellipsis_limits[CAJA_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 void
+text_ellipsis_limit_changed_callback (gpointer callback_data)
+{
+ char **pref;
+ unsigned int i;
+ int one_limit;
+ const EelEnumeration *eenum;
+ const EelEnumerationEntry *entry;
+
+ pref = eel_preferences_get_string_array (CAJA_PREFERENCES_ICON_VIEW_TEXT_ELLIPSIS_LIMIT);
+
+ /* set default */
+ get_text_ellipsis_limit_for_zoom (pref, NULL, &one_limit);
+ for (i = 0; i < CAJA_ZOOM_LEVEL_N_ENTRIES; i++)
+ {
+ text_ellipsis_limits[i] = one_limit;
+ }
+
+ /* override for each zoom level */
+ eenum = eel_enumeration_lookup ("default_zoom_level");
+ g_assert (eenum != NULL);
+ for (i = 0; i < eel_enumeration_get_length (eenum); i++)
+ {
+ entry = eel_enumeration_get_nth_entry (eenum, i);
+ if (get_text_ellipsis_limit_for_zoom (pref, entry->name, &one_limit))
+ {
+ text_ellipsis_limits[entry->value] = one_limit;
+ }
+ }
+
+ g_strfreev (pref);
+}
+
+static void
+desktop_text_ellipsis_limit_changed_callback (gpointer callback_data)
+{
+ int pref;
+
+ pref = eel_preferences_get_integer (CAJA_PREFERENCES_DESKTOP_TEXT_ELLIPSIS_LIMIT);
+ desktop_text_ellipsis_limit = pref;
+}
+
+static void
+caja_icon_container_init (CajaIconContainer *container)
+{
+ CajaIconContainerDetails *details;
+ EelBackground *background;
+ static gboolean setup_prefs = FALSE;
+
+ details = g_new0 (CajaIconContainerDetails, 1);
+
+ details->icon_set = g_hash_table_new (g_direct_hash, g_direct_equal);
+ details->layout_timestamp = UNDEFINED_TIME;
+
+ details->zoom_level = CAJA_ZOOM_LEVEL_STANDARD;
+
+ details->font_size_table[CAJA_ZOOM_LEVEL_SMALLEST] = -2 * PANGO_SCALE;
+ details->font_size_table[CAJA_ZOOM_LEVEL_SMALLER] = -2 * PANGO_SCALE;
+ details->font_size_table[CAJA_ZOOM_LEVEL_SMALL] = -0 * PANGO_SCALE;
+ details->font_size_table[CAJA_ZOOM_LEVEL_STANDARD] = 0 * PANGO_SCALE;
+ details->font_size_table[CAJA_ZOOM_LEVEL_LARGE] = 0 * PANGO_SCALE;
+ details->font_size_table[CAJA_ZOOM_LEVEL_LARGER] = 0 * PANGO_SCALE;
+ details->font_size_table[CAJA_ZOOM_LEVEL_LARGEST] = 0 * PANGO_SCALE;
+
+ container->details = details;
+
+ /* when the background changes, we must set up the label text color */
+ background = eel_get_widget_background (GTK_WIDGET (container));
+
+ g_signal_connect_object (background, "appearance_changed",
+ G_CALLBACK (update_label_color), container, 0);
+
+ 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);
+
+ eel_background_set_use_base (background, TRUE);
+
+ /* read in theme-dependent data */
+ caja_icon_container_theme_changed (container);
+ eel_preferences_add_callback (CAJA_PREFERENCES_THEME,
+ caja_icon_container_theme_changed,
+ container);
+
+ if (!setup_prefs)
+ {
+ eel_preferences_add_callback (CAJA_PREFERENCES_ICON_VIEW_TEXT_ELLIPSIS_LIMIT,
+ text_ellipsis_limit_changed_callback,
+ NULL);
+ text_ellipsis_limit_changed_callback (NULL);
+
+ eel_preferences_add_callback (CAJA_PREFERENCES_DESKTOP_TEXT_ELLIPSIS_LIMIT,
+ desktop_text_ellipsis_limit_changed_callback,
+ NULL);
+ desktop_text_ellipsis_limit_changed_callback (NULL);
+
+ setup_prefs = TRUE;
+ }
+}
+
+typedef struct
+{
+ CajaIconContainer *container;
+ GdkEventButton *event;
+} ContextMenuParameters;
+
+static gboolean
+handle_icon_double_click (CajaIconContainer *container,
+ CajaIcon *icon,
+ GdkEventButton *event)
+{
+ CajaIconContainerDetails *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])
+ {
+ if (!button_event_modifies_selection (event))
+ {
+ activate_selected_items (container);
+ return TRUE;
+ }
+ else if ((event->state & GDK_CONTROL_MASK) == 0 &&
+ (event->state & GDK_SHIFT_MASK) != 0)
+ {
+ activate_selected_items_alternate (container, icon);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/* CajaIcon 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_icon_button_press (CajaIconContainer *container,
+ CajaIcon *icon,
+ GdkEventButton *event)
+{
+ CajaIconContainerDetails *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_icon_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)
+ {
+ CajaIcon *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)
+ {
+ g_signal_emit (container,
+ signals[CONTEXT_CLICK_SELECTION], 0,
+ event);
+ }
+
+
+ return TRUE;
+}
+
+static int
+item_event_callback (EelCanvasItem *item,
+ GdkEvent *event,
+ gpointer data)
+{
+ CajaIconContainer *container;
+ CajaIconContainerDetails *details;
+ CajaIcon *icon;
+
+ container = CAJA_ICON_CONTAINER (data);
+ details = container->details;
+
+ icon = CAJA_ICON_CANVAS_ITEM (item)->user_data;
+ g_assert (icon != NULL);
+
+ switch (event->type)
+ {
+ case GDK_BUTTON_PRESS:
+ if (handle_icon_button_press (container, icon, &event->button))
+ {
+ /* Stop the event from being passed along further. Returning
+ * TRUE ain't enough.
+ */
+ return TRUE;
+ }
+ return FALSE;
+ default:
+ return FALSE;
+ }
+}
+
+GtkWidget *
+caja_icon_container_new (void)
+{
+ return gtk_widget_new (CAJA_TYPE_ICON_CONTAINER, NULL);
+}
+
+/* Clear all of the icons in the container. */
+void
+caja_icon_container_clear (CajaIconContainer *container)
+{
+ CajaIconContainerDetails *details;
+ CajaIcon *icon;
+ GList *p;
+
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+
+ details = container->details;
+ details->layout_timestamp = UNDEFINED_TIME;
+ details->store_layout_timestamps_when_finishing_new_icons = FALSE;
+
+ if (details->icons == NULL)
+ {
+ return;
+ }
+
+ end_renaming_mode (container, TRUE);
+
+ 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 = p->data;
+ if (icon->is_monitored)
+ {
+ caja_icon_container_stop_monitor_top_left (container,
+ icon->data,
+ icon);
+ }
+ icon_free (p->data);
+ }
+ g_list_free (details->icons);
+ details->icons = NULL;
+ g_list_free (details->new_icons);
+ details->new_icons = NULL;
+
+ g_hash_table_destroy (details->icon_set);
+ details->icon_set = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ caja_icon_container_update_scroll_region (container);
+}
+
+gboolean
+caja_icon_container_is_empty (CajaIconContainer *container)
+{
+ return container->details->icons == NULL;
+}
+
+CajaIconData *
+caja_icon_container_get_first_visible_icon (CajaIconContainer *container)
+{
+ GList *l;
+ CajaIcon *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_layout_get_hadjustment (GTK_LAYOUT (container)));
+ vadj_v = gtk_adjustment_get_value (gtk_layout_get_vadjustment (GTK_LAYOUT (container)));
+ h_page_size = gtk_adjustment_get_page_size (gtk_layout_get_hadjustment (GTK_LAYOUT (container)));
+
+ if (caja_icon_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 (caja_icon_container_is_layout_vertical (container))
+ {
+ pos = &x1;
+ if (caja_icon_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
+caja_icon_container_scroll_to_icon (CajaIconContainer *container,
+ CajaIconData *data)
+{
+ GList *l;
+ CajaIcon *icon;
+ GtkAdjustment *hadj, *vadj;
+ EelCanvasItem *item;
+ EelIRect bounds;
+ GtkAllocation allocation;
+
+ hadj = gtk_layout_get_hadjustment (GTK_LAYOUT (container));
+ vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (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 */
+ caja_icon_container_layout_now (container);
+
+ l = container->details->icons;
+ while (l != NULL)
+ {
+ icon = l->data;
+
+ if (icon->data == data &&
+ icon_is_positioned (icon))
+ {
+
+ item = EEL_CANVAS_ITEM (icon->item);
+
+ if (caja_icon_container_is_auto_layout (container))
+ {
+ /* ensure that we reveal the entire row/column */
+ icon_get_row_and_column_bounds (container, icon, &bounds, TRUE);
+ }
+ else
+ {
+ item_get_canvas_bounds (EEL_CANVAS_ITEM (icon->item), &bounds, TRUE);
+ }
+
+ if (caja_icon_container_is_layout_vertical (container))
+ {
+ if (caja_icon_container_is_layout_rtl (container))
+ {
+ eel_gtk_adjustment_set_value (hadj, bounds.x1 - allocation.width);
+ }
+ else
+ {
+ eel_gtk_adjustment_set_value (hadj, bounds.x0);
+ }
+ }
+ else
+ {
+ eel_gtk_adjustment_set_value (vadj, bounds.y0);
+ }
+ }
+
+ l = l->next;
+ }
+}
+
+/* Call a function for all the icons. */
+typedef struct
+{
+ CajaIconCallback callback;
+ gpointer callback_data;
+} CallbackAndData;
+
+static void
+call_icon_callback (gpointer data, gpointer callback_data)
+{
+ CajaIcon *icon;
+ CallbackAndData *callback_and_data;
+
+ icon = data;
+ callback_and_data = callback_data;
+ (* callback_and_data->callback) (icon->data, callback_and_data->callback_data);
+}
+
+void
+caja_icon_container_for_each (CajaIconContainer *container,
+ CajaIconCallback callback,
+ gpointer callback_data)
+{
+ CallbackAndData callback_and_data;
+
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+
+ callback_and_data.callback = callback;
+ callback_and_data.callback_data = callback_data;
+
+ g_list_foreach (container->details->icons,
+ call_icon_callback, &callback_and_data);
+}
+
+static int
+selection_changed_at_idle_callback (gpointer data)
+{
+ CajaIconContainer *container;
+
+ container = CAJA_ICON_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 (CajaIconContainer *container,
+ CajaIcon *icon)
+{
+ CajaIconContainerDetails *details;
+ gboolean was_selected;
+ CajaIcon *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);
+ 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;
+ }
+
+ if (icon->is_monitored)
+ {
+ caja_icon_container_stop_monitor_top_left (container,
+ icon->data,
+ icon);
+ }
+ 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 (CajaIconContainer *container)
+{
+ GList *selection;
+
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+
+ selection = caja_icon_container_get_selection (container);
+ if (selection != NULL)
+ {
+ g_signal_emit (container,
+ signals[ACTIVATE], 0,
+ selection);
+ }
+ g_list_free (selection);
+}
+
+static void
+activate_selected_items_alternate (CajaIconContainer *container,
+ CajaIcon *icon)
+{
+ GList *selection;
+
+ g_assert (CAJA_IS_ICON_CONTAINER (container));
+
+ if (icon != NULL)
+ {
+ selection = g_list_prepend (NULL, icon->data);
+ }
+ else
+ {
+ selection = caja_icon_container_get_selection (container);
+ }
+ if (selection != NULL)
+ {
+ g_signal_emit (container,
+ signals[ACTIVATE_ALTERNATE], 0,
+ selection);
+ }
+ g_list_free (selection);
+}
+
+static CajaIcon *
+get_icon_being_renamed (CajaIconContainer *container)
+{
+ CajaIcon *rename_icon;
+
+ if (!is_renaming (container))
+ {
+ return NULL;
+ }
+
+ g_assert (!has_multiple_selection (container));
+
+ rename_icon = get_first_selected_icon (container);
+ g_assert (rename_icon != NULL);
+
+ return rename_icon;
+}
+
+static CajaIconInfo *
+caja_icon_container_get_icon_images (CajaIconContainer *container,
+ CajaIconData *data,
+ int size,
+ GList **emblem_pixbufs,
+ char **embedded_text,
+ gboolean for_drag_accept,
+ gboolean need_large_embeddded_text,
+ gboolean *embedded_text_needs_loading,
+ gboolean *has_open_window)
+{
+ CajaIconContainerClass *klass;
+
+ klass = CAJA_ICON_CONTAINER_GET_CLASS (container);
+ g_assert (klass->get_icon_images != NULL);
+
+ return klass->get_icon_images (container, data, size, emblem_pixbufs, embedded_text, for_drag_accept, need_large_embeddded_text, embedded_text_needs_loading, has_open_window);
+}
+
+static void
+caja_icon_container_freeze_updates (CajaIconContainer *container)
+{
+ CajaIconContainerClass *klass;
+
+ klass = CAJA_ICON_CONTAINER_GET_CLASS (container);
+ g_assert (klass->freeze_updates != NULL);
+
+ klass->freeze_updates (container);
+}
+
+static void
+caja_icon_container_unfreeze_updates (CajaIconContainer *container)
+{
+ CajaIconContainerClass *klass;
+
+ klass = CAJA_ICON_CONTAINER_GET_CLASS (container);
+ g_assert (klass->unfreeze_updates != NULL);
+
+ klass->unfreeze_updates (container);
+}
+
+static void
+caja_icon_container_start_monitor_top_left (CajaIconContainer *container,
+ CajaIconData *data,
+ gconstpointer client,
+ gboolean large_text)
+{
+ CajaIconContainerClass *klass;
+
+ klass = CAJA_ICON_CONTAINER_GET_CLASS (container);
+ g_assert (klass->start_monitor_top_left != NULL);
+
+ klass->start_monitor_top_left (container, data, client, large_text);
+}
+
+static void
+caja_icon_container_stop_monitor_top_left (CajaIconContainer *container,
+ CajaIconData *data,
+ gconstpointer client)
+{
+ CajaIconContainerClass *klass;
+
+ klass = CAJA_ICON_CONTAINER_GET_CLASS (container);
+ g_return_if_fail (klass->stop_monitor_top_left != NULL);
+
+ klass->stop_monitor_top_left (container, data, client);
+}
+
+
+static void
+caja_icon_container_prioritize_thumbnailing (CajaIconContainer *container,
+ CajaIcon *icon)
+{
+ CajaIconContainerClass *klass;
+
+ klass = CAJA_ICON_CONTAINER_GET_CLASS (container);
+ g_assert (klass->prioritize_thumbnailing != NULL);
+
+ klass->prioritize_thumbnailing (container, icon->data);
+}
+
+static void
+caja_icon_container_update_visible_icons (CajaIconContainer *container)
+{
+ GtkAdjustment *vadj, *hadj;
+ double min_y, max_y;
+ double min_x, max_x;
+ double x0, y0, x1, y1;
+ GList *node;
+ CajaIcon *icon;
+ gboolean visible;
+ GtkAllocation allocation;
+
+ hadj = gtk_layout_get_hadjustment (GTK_LAYOUT (container));
+ vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (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 (caja_icon_container_is_layout_vertical (container))
+ {
+ visible = x1 >= min_x && x0 <= max_x;
+ }
+ else
+ {
+ visible = y1 >= min_y && y0 <= max_y;
+ }
+
+ if (visible)
+ {
+ caja_icon_canvas_item_set_is_visible (icon->item, TRUE);
+ caja_icon_container_prioritize_thumbnailing (container,
+ icon);
+ }
+ else
+ {
+ caja_icon_canvas_item_set_is_visible (icon->item, FALSE);
+ }
+ }
+ }
+}
+
+static void
+handle_vadjustment_changed (GtkAdjustment *adjustment,
+ CajaIconContainer *container)
+{
+ if (!caja_icon_container_is_layout_vertical (container))
+ {
+ caja_icon_container_update_visible_icons (container);
+ }
+}
+
+static void
+handle_hadjustment_changed (GtkAdjustment *adjustment,
+ CajaIconContainer *container)
+{
+ if (caja_icon_container_is_layout_vertical (container))
+ {
+ caja_icon_container_update_visible_icons (container);
+ }
+}
+
+
+void
+caja_icon_container_update_icon (CajaIconContainer *container,
+ CajaIcon *icon)
+{
+ CajaIconContainerDetails *details;
+ guint icon_size;
+ guint min_image_size, max_image_size;
+ CajaIconInfo *icon_info;
+ GdkPoint *attach_points;
+ int n_attach_points;
+ gboolean has_embedded_text_rect;
+ GdkPixbuf *pixbuf;
+ GList *emblem_pixbufs;
+ char *editable_text, *additional_text;
+ char *embedded_text;
+ GdkRectangle embedded_text_rect;
+ gboolean large_embedded_text;
+ gboolean embedded_text_needs_loading;
+ gboolean has_open_window;
+
+ 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, CAJA_ICON_MAXIMUM_SIZE);
+
+ /* Get the appropriate images for the file. */
+ if (container->details->forced_icon_size > 0)
+ {
+ icon_size = container->details->forced_icon_size;
+ }
+ else
+ {
+ icon_get_size (container, icon, &icon_size);
+ }
+
+
+ icon_size = MAX (icon_size, min_image_size);
+ icon_size = MIN (icon_size, max_image_size);
+
+ /* Get the icons. */
+ emblem_pixbufs = NULL;
+ embedded_text = NULL;
+ large_embedded_text = icon_size > ICON_SIZE_FOR_LARGE_EMBEDDED_TEXT;
+ icon_info = caja_icon_container_get_icon_images (container, icon->data, icon_size,
+ &emblem_pixbufs,
+ &embedded_text,
+ icon == details->drop_target,
+ large_embedded_text, &embedded_text_needs_loading,
+ &has_open_window);
+
+
+ if (container->details->forced_icon_size > 0)
+ pixbuf = caja_icon_info_get_pixbuf_at_size (icon_info, icon_size);
+ else
+ pixbuf = caja_icon_info_get_pixbuf (icon_info);
+ caja_icon_info_get_attach_points (icon_info, &attach_points, &n_attach_points);
+ has_embedded_text_rect = caja_icon_info_get_embedded_rect (icon_info,
+ &embedded_text_rect);
+
+ if (has_embedded_text_rect && embedded_text_needs_loading)
+ {
+ icon->is_monitored = TRUE;
+ caja_icon_container_start_monitor_top_left (container, icon->data, icon, large_embedded_text);
+ }
+
+ caja_icon_container_get_icon_text (container,
+ icon->data,
+ &editable_text,
+ &additional_text,
+ FALSE);
+
+ /* If name of icon being renamed was changed from elsewhere, end renaming mode.
+ * Alternatively, we could replace the characters in the editable text widget
+ * with the new name, but that could cause timing problems if the user just
+ * happened to be typing at that moment.
+ */
+ if (icon == get_icon_being_renamed (container) &&
+ eel_strcmp (editable_text,
+ caja_icon_canvas_item_get_editable_text (icon->item)) != 0)
+ {
+ end_renaming_mode (container, 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);
+
+ caja_icon_canvas_item_set_image (icon->item, pixbuf);
+ caja_icon_canvas_item_set_attach_points (icon->item, attach_points, n_attach_points);
+ caja_icon_canvas_item_set_emblems (icon->item, emblem_pixbufs);
+ caja_icon_canvas_item_set_embedded_text_rect (icon->item, &embedded_text_rect);
+ caja_icon_canvas_item_set_embedded_text (icon->item, embedded_text);
+
+ /* Let the pixbufs go. */
+ g_object_unref (pixbuf);
+ eel_gdk_pixbuf_list_free (emblem_pixbufs);
+
+ g_free (editable_text);
+ g_free (additional_text);
+
+ g_object_unref (icon_info);
+}
+
+static gboolean
+assign_icon_position (CajaIconContainer *container,
+ CajaIcon *icon)
+{
+ gboolean have_stored_position;
+ CajaIconPosition 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 (CajaIconContainer *container,
+ CajaIcon *icon)
+{
+ caja_icon_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 void
+finish_adding_new_icons (CajaIconContainer *container)
+{
+ GList *p, *new_icons, *no_position_icons, *semi_position_icons;
+ CajaIcon *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);
+
+ 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)
+ {
+ CajaIcon *icon;
+ CajaIconPosition 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 (caja_icon_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;
+ }
+}
+
+static gboolean
+is_old_or_unknown_icon_data (CajaIconContainer *container,
+ CajaIconData *data)
+{
+ time_t timestamp;
+ gboolean success;
+
+ if (container->details->layout_timestamp == UNDEFINED_TIME)
+ {
+ /* don't know */
+ return FALSE;
+ }
+
+ g_signal_emit (container,
+ signals[GET_STORED_LAYOUT_TIMESTAMP], 0,
+ data, &timestamp, &success);
+ return (!success || timestamp < container->details->layout_timestamp);
+}
+
+/**
+ * caja_icon_container_add:
+ * @container: A CajaIconContainer
+ * @data: Icon data.
+ *
+ * Add icon to represent @data to container.
+ * Returns FALSE if there was already such an icon.
+ **/
+gboolean
+caja_icon_container_add (CajaIconContainer *container,
+ CajaIconData *data)
+{
+ CajaIconContainerDetails *details;
+ CajaIcon *icon;
+ EelCanvasItem *band, *item;
+
+ g_return_val_if_fail (CAJA_IS_ICON_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 (CajaIcon, 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 = CAJA_ICON_CANVAS_ITEM
+ (eel_canvas_item_new (EEL_CANVAS_GROUP (EEL_CANVAS (container)->root),
+ caja_icon_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 = CAJA_ICON_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);
+
+ /* Run an idle function to add the icons. */
+ schedule_redo_layout (container);
+
+ return TRUE;
+}
+
+void
+caja_icon_container_layout_now (CajaIconContainer *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))));
+}
+
+/**
+ * caja_icon_container_remove:
+ * @container: A CajaIconContainer.
+ * @data: Icon data.
+ *
+ * Remove the icon with this data.
+ **/
+gboolean
+caja_icon_container_remove (CajaIconContainer *container,
+ CajaIconData *data)
+{
+ CajaIcon *icon;
+
+ g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (container), FALSE);
+ g_return_val_if_fail (data != NULL, FALSE);
+
+ end_renaming_mode (container, 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;
+}
+
+/**
+ * caja_icon_container_request_update:
+ * @container: A CajaIconContainer.
+ * @data: Icon data.
+ *
+ * Update the icon with this data.
+ **/
+void
+caja_icon_container_request_update (CajaIconContainer *container,
+ CajaIconData *data)
+{
+ CajaIcon *icon;
+
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+ g_return_if_fail (data != NULL);
+
+ icon = g_hash_table_lookup (container->details->icon_set, data);
+
+ if (icon != NULL)
+ {
+ caja_icon_container_update_icon (container, icon);
+ schedule_redo_layout (container);
+ }
+}
+
+/* zooming */
+
+CajaZoomLevel
+caja_icon_container_get_zoom_level (CajaIconContainer *container)
+{
+ return container->details->zoom_level;
+}
+
+void
+caja_icon_container_set_zoom_level (CajaIconContainer *container, int new_level)
+{
+ CajaIconContainerDetails *details;
+ int pinned_level;
+ double pixels_per_unit;
+
+ details = container->details;
+
+ end_renaming_mode (container, TRUE);
+
+ pinned_level = new_level;
+ if (pinned_level < CAJA_ZOOM_LEVEL_SMALLEST)
+ {
+ pinned_level = CAJA_ZOOM_LEVEL_SMALLEST;
+ }
+ else if (pinned_level > CAJA_ZOOM_LEVEL_LARGEST)
+ {
+ pinned_level = CAJA_ZOOM_LEVEL_LARGEST;
+ }
+
+ if (pinned_level == details->zoom_level)
+ {
+ return;
+ }
+
+ details->zoom_level = pinned_level;
+
+ pixels_per_unit = (double) caja_get_icon_size_for_zoom_level (pinned_level)
+ / CAJA_ICON_SIZE_STANDARD;
+ eel_canvas_set_pixels_per_unit (EEL_CANVAS (container), pixels_per_unit);
+
+ invalidate_labels (container);
+ caja_icon_container_request_update_all (container);
+}
+
+/**
+ * caja_icon_container_request_update_all:
+ * For each icon, synchronizes the displayed information (image, text) with the
+ * information from the model.
+ *
+ * @container: An icon container.
+ **/
+void
+caja_icon_container_request_update_all (CajaIconContainer *container)
+{
+ GList *node;
+ CajaIcon *icon;
+
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+
+ for (node = container->details->icons; node != NULL; node = node->next)
+ {
+ icon = node->data;
+ caja_icon_container_update_icon (container, icon);
+ }
+
+ redo_layout (container);
+}
+
+/**
+ * caja_icon_container_reveal:
+ * Change scroll position as necessary to reveal the specified item.
+ */
+void
+caja_icon_container_reveal (CajaIconContainer *container, CajaIconData *data)
+{
+ CajaIcon *icon;
+
+ g_return_if_fail (CAJA_IS_ICON_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);
+ }
+}
+
+/**
+ * caja_icon_container_get_selection:
+ * @container: An icon 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 icon is selected. The caller is expected to
+ * free the list when it is not needed anymore.
+ **/
+GList *
+caja_icon_container_get_selection (CajaIconContainer *container)
+{
+ GList *list, *p;
+
+ g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (container), NULL);
+
+ list = NULL;
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ CajaIcon *icon;
+
+ icon = p->data;
+ if (icon->is_selected)
+ {
+ list = g_list_prepend (list, icon->data);
+ }
+ }
+
+ return g_list_reverse (list);
+}
+
+static GList *
+caja_icon_container_get_selected_icons (CajaIconContainer *container)
+{
+ GList *list, *p;
+
+ g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (container), NULL);
+
+ list = NULL;
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ CajaIcon *icon;
+
+ icon = p->data;
+ if (icon->is_selected)
+ {
+ list = g_list_prepend (list, icon);
+ }
+ }
+
+ return g_list_reverse (list);
+}
+
+/**
+ * caja_icon_container_invert_selection:
+ * @container: An icon container.
+ *
+ * Inverts the selection in @container.
+ *
+ **/
+void
+caja_icon_container_invert_selection (CajaIconContainer *container)
+{
+ GList *p;
+
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ CajaIcon *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 *
+caja_icon_container_get_icon_locations (CajaIconContainer *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 =
+ ((CajaIcon *)node->data)->x;
+ g_array_index (result, GdkPoint, index).y =
+ ((CajaIcon *)node->data)->y;
+ }
+
+ return result;
+}
+
+/**
+ * caja_icon_container_get_selected_icon_locations:
+ * @container: An icon container widget.
+ *
+ * Returns an array of GdkPoints of locations of the selected icons.
+ **/
+GArray *
+caja_icon_container_get_selected_icon_locations (CajaIconContainer *container)
+{
+ GArray *result;
+ GList *icons;
+
+ g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (container), NULL);
+
+ icons = caja_icon_container_get_selected_icons (container);
+ result = caja_icon_container_get_icon_locations (container, icons);
+ g_list_free (icons);
+
+ return result;
+}
+
+/**
+ * caja_icon_container_select_all:
+ * @container: An icon container widget.
+ *
+ * Select all the icons in @container at once.
+ **/
+void
+caja_icon_container_select_all (CajaIconContainer *container)
+{
+ gboolean selection_changed;
+ GList *p;
+ CajaIcon *icon;
+
+ g_return_if_fail (CAJA_IS_ICON_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);
+ }
+}
+
+/**
+ * caja_icon_container_set_selection:
+ * @container: An icon container widget.
+ * @selection: A list of CajaIconData *.
+ *
+ * Set the selection to exactly the icons in @container which have
+ * programmer data matching one of the items in @selection.
+ **/
+void
+caja_icon_container_set_selection (CajaIconContainer *container,
+ GList *selection)
+{
+ gboolean selection_changed;
+ GHashTable *hash;
+ GList *p;
+ CajaIcon *icon;
+
+ g_return_if_fail (CAJA_IS_ICON_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->data) != NULL);
+ }
+ g_hash_table_destroy (hash);
+
+ if (selection_changed)
+ {
+ g_signal_emit (container,
+ signals[SELECTION_CHANGED], 0);
+ }
+}
+
+/**
+ * caja_icon_container_select_list_unselect_others.
+ * @container: An icon container widget.
+ * @selection: A list of CajaIcon *.
+ *
+ * Set the selection to exactly the icons in @selection.
+ **/
+void
+caja_icon_container_select_list_unselect_others (CajaIconContainer *container,
+ GList *selection)
+{
+ gboolean selection_changed;
+ GHashTable *hash;
+ GList *p;
+ CajaIcon *icon;
+
+ g_return_if_fail (CAJA_IS_ICON_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);
+ }
+}
+
+/**
+ * caja_icon_container_unselect_all:
+ * @container: An icon container widget.
+ *
+ * Deselect all the icons in @container.
+ **/
+void
+caja_icon_container_unselect_all (CajaIconContainer *container)
+{
+ if (unselect_all (container))
+ {
+ g_signal_emit (container,
+ signals[SELECTION_CHANGED], 0);
+ }
+}
+
+/**
+ * caja_icon_container_get_icon_by_uri:
+ * @container: An icon container widget.
+ * @uri: The uri of an icon 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.
+ **/
+CajaIcon *
+caja_icon_container_get_icon_by_uri (CajaIconContainer *container,
+ const char *uri)
+{
+ CajaIconContainerDetails *details;
+ GList *p;
+
+ /* Eventually, we must avoid searching the entire icon list,
+ but it's OK for now.
+ A hash table mapping uri to icon is one possibility.
+ */
+
+ details = container->details;
+
+ for (p = details->icons; p != NULL; p = p->next)
+ {
+ CajaIcon *icon;
+ char *icon_uri;
+ gboolean is_match;
+
+ icon = p->data;
+
+ icon_uri = caja_icon_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 CajaIcon *
+get_nth_selected_icon (CajaIconContainer *container, int index)
+{
+ GList *p;
+ CajaIcon *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 CajaIcon *
+get_first_selected_icon (CajaIconContainer *container)
+{
+ return get_nth_selected_icon (container, 1);
+}
+
+static gboolean
+has_multiple_selection (CajaIconContainer *container)
+{
+ return get_nth_selected_icon (container, 2) != NULL;
+}
+
+static gboolean
+all_selected (CajaIconContainer *container)
+{
+ GList *p;
+ CajaIcon *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 (CajaIconContainer *container)
+{
+ return get_nth_selected_icon (container, 1) != NULL;
+}
+
+/**
+ * caja_icon_container_show_stretch_handles:
+ * @container: An icon container widget.
+ *
+ * Makes stretch handles visible on the first selected icon.
+ **/
+void
+caja_icon_container_show_stretch_handles (CajaIconContainer *container)
+{
+ CajaIconContainerDetails *details;
+ CajaIcon *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 icon. */
+ if (details->stretch_icon != NULL)
+ {
+ caja_icon_canvas_item_set_show_stretch_handles
+ (details->stretch_icon->item, FALSE);
+ ungrab_stretch_icon (container);
+ emit_stretch_ended (container, details->stretch_icon);
+ }
+ caja_icon_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);
+}
+
+/**
+ * caja_icon_container_has_stretch_handles
+ * @container: An icon container widget.
+ *
+ * Returns true if the first selected item has stretch handles.
+ **/
+gboolean
+caja_icon_container_has_stretch_handles (CajaIconContainer *container)
+{
+ CajaIcon *icon;
+
+ icon = get_first_selected_icon (container);
+ if (icon == NULL)
+ {
+ return FALSE;
+ }
+
+ return icon == container->details->stretch_icon;
+}
+
+/**
+ * caja_icon_container_is_stretched
+ * @container: An icon container widget.
+ *
+ * Returns true if the any selected item is stretched to a size other than 1.0.
+ **/
+gboolean
+caja_icon_container_is_stretched (CajaIconContainer *container)
+{
+ GList *p;
+ CajaIcon *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;
+}
+
+/**
+ * caja_icon_container_unstretch
+ * @container: An icon container widget.
+ *
+ * Gets rid of any icon stretching.
+ **/
+void
+caja_icon_container_unstretch (CajaIconContainer *container)
+{
+ GList *p;
+ CajaIcon *icon;
+
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+ if (icon->is_selected)
+ {
+ caja_icon_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) CAJA_ICON_SIZE_SMALLEST);
+
+ /* 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 *
+caja_icon_container_get_icon_uri (CajaIconContainer *container,
+ CajaIcon *icon)
+{
+ char *uri;
+
+ uri = NULL;
+ g_signal_emit (container,
+ signals[GET_ICON_URI], 0,
+ icon->data,
+ &uri);
+ return uri;
+}
+
+char *
+caja_icon_container_get_icon_drop_target_uri (CajaIconContainer *container,
+ CajaIcon *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 (CajaIconContainer *container)
+{
+ if (!caja_icon_container_is_empty (container))
+ {
+ caja_icon_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
+caja_icon_container_set_auto_layout (CajaIconContainer *container,
+ gboolean auto_layout)
+{
+ g_return_if_fail (CAJA_IS_ICON_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);
+ caja_icon_container_freeze_icon_positions (container);
+ }
+
+ redo_layout (container);
+
+ g_signal_emit (container, signals[LAYOUT_CHANGED], 0);
+}
+
+
+/* Toggle the tighter layout boolean. */
+void
+caja_icon_container_set_tighter_layout (CajaIconContainer *container,
+ gboolean tighter_layout)
+{
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+ g_return_if_fail (tighter_layout == FALSE || tighter_layout == TRUE);
+
+ if (container->details->tighter_layout == tighter_layout)
+ {
+ return;
+ }
+
+ container->details->tighter_layout = tighter_layout;
+
+ if (container->details->auto_layout)
+ {
+ invalidate_label_sizes (container);
+ redo_layout (container);
+
+ g_signal_emit (container, signals[LAYOUT_CHANGED], 0);
+ }
+ else
+ {
+ /* in manual layout, label sizes still change, even though
+ * the icons don't move.
+ */
+ invalidate_label_sizes (container);
+ caja_icon_container_request_update_all (container);
+ }
+}
+
+gboolean
+caja_icon_container_is_keep_aligned (CajaIconContainer *container)
+{
+ return container->details->keep_aligned;
+}
+
+static gboolean
+align_icons_callback (gpointer callback_data)
+{
+ CajaIconContainer *container;
+
+ container = CAJA_ICON_CONTAINER (callback_data);
+ align_icons (container);
+ container->details->align_idle_id = 0;
+
+ return FALSE;
+}
+
+static void
+unschedule_align_icons (CajaIconContainer *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 (CajaIconContainer *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
+caja_icon_container_set_keep_aligned (CajaIconContainer *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);
+ }
+ }
+}
+
+void
+caja_icon_container_set_layout_mode (CajaIconContainer *container,
+ CajaIconLayoutMode mode)
+{
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+
+ container->details->layout_mode = mode;
+ invalidate_labels (container);
+
+ redo_layout (container);
+
+ g_signal_emit (container, signals[LAYOUT_CHANGED], 0);
+}
+
+void
+caja_icon_container_set_label_position (CajaIconContainer *container,
+ CajaIconLabelPosition position)
+{
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+
+ if (container->details->label_position != position)
+ {
+ container->details->label_position = position;
+
+ invalidate_labels (container);
+ caja_icon_container_request_update_all (container);
+
+ schedule_redo_layout (container);
+ }
+}
+
+/* Switch from automatic to manual layout, freezing all the icons in their
+ * current positions instead of restoring icon positions from the last manual
+ * layout as set_auto_layout does.
+ */
+void
+caja_icon_container_freeze_icon_positions (CajaIconContainer *container)
+{
+ gboolean changed;
+ GList *p;
+ CajaIcon *icon;
+ CajaIconPosition 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
+caja_icon_container_sort (CajaIconContainer *container)
+{
+ gboolean changed;
+
+ changed = !container->details->auto_layout;
+ container->details->auto_layout = TRUE;
+
+ reset_scroll_region_if_not_empty (container);
+ redo_layout (container);
+
+ if (changed)
+ {
+ g_signal_emit (container, signals[LAYOUT_CHANGED], 0);
+ }
+}
+
+gboolean
+caja_icon_container_is_auto_layout (CajaIconContainer *container)
+{
+ g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (container), FALSE);
+
+ return container->details->auto_layout;
+}
+
+gboolean
+caja_icon_container_is_tighter_layout (CajaIconContainer *container)
+{
+ g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (container), FALSE);
+
+ return container->details->tighter_layout;
+}
+
+static void
+pending_icon_to_rename_destroy_callback (CajaIconCanvasItem *item, CajaIconContainer *container)
+{
+ g_assert (container->details->pending_icon_to_rename != NULL);
+ g_assert (container->details->pending_icon_to_rename->item == item);
+ container->details->pending_icon_to_rename = NULL;
+}
+
+static CajaIcon*
+get_pending_icon_to_rename (CajaIconContainer *container)
+{
+ return container->details->pending_icon_to_rename;
+}
+
+static void
+set_pending_icon_to_rename (CajaIconContainer *container, CajaIcon *icon)
+{
+ CajaIcon *old_icon;
+
+ old_icon = container->details->pending_icon_to_rename;
+
+ if (icon == old_icon)
+ {
+ return;
+ }
+
+ if (old_icon != NULL)
+ {
+ g_signal_handlers_disconnect_by_func
+ (old_icon->item,
+ G_CALLBACK (pending_icon_to_rename_destroy_callback),
+ container);
+ }
+
+ if (icon != NULL)
+ {
+ g_signal_connect (icon->item, "destroy",
+ G_CALLBACK (pending_icon_to_rename_destroy_callback), container);
+ }
+
+ container->details->pending_icon_to_rename = icon;
+}
+
+static void
+process_pending_icon_to_rename (CajaIconContainer *container)
+{
+ CajaIcon *pending_icon_to_rename;
+
+ pending_icon_to_rename = get_pending_icon_to_rename (container);
+
+ if (pending_icon_to_rename != NULL)
+ {
+ if (pending_icon_to_rename->is_selected && !has_multiple_selection (container))
+ {
+ caja_icon_container_start_renaming_selected_item (container, FALSE);
+ }
+ else
+ {
+ set_pending_icon_to_rename (container, NULL);
+ }
+ }
+}
+
+static gboolean
+is_renaming_pending (CajaIconContainer *container)
+{
+ return get_pending_icon_to_rename (container) != NULL;
+}
+
+static gboolean
+is_renaming (CajaIconContainer *container)
+{
+ return container->details->renaming;
+}
+
+/**
+ * caja_icon_container_start_renaming_selected_item
+ * @container: An icon container widget.
+ * @select_all: Whether the whole file should initially be selected, or
+ * only its basename (i.e. everything except its extension).
+ *
+ * Displays the edit name widget on the first selected icon
+ **/
+void
+caja_icon_container_start_renaming_selected_item (CajaIconContainer *container,
+ gboolean select_all)
+{
+ CajaIconContainerDetails *details;
+ CajaIcon *icon;
+ EelDRect icon_rect;
+ EelDRect text_rect;
+ PangoContext *context;
+ PangoFontDescription *desc;
+ const char *editable_text;
+ int x, y, width;
+ int start_offset, end_offset;
+
+ /* Check if it already in renaming mode, if so - select all */
+ details = container->details;
+ if (details->renaming)
+ {
+ eel_editable_label_select_region (EEL_EDITABLE_LABEL (details->rename_widget),
+ 0,
+ -1);
+ return;
+ }
+
+ /* Find selected icon */
+ icon = get_first_selected_icon (container);
+ if (icon == NULL)
+ {
+ return;
+ }
+
+ g_assert (!has_multiple_selection (container));
+
+
+ if (!icon_is_positioned (icon))
+ {
+ set_pending_icon_to_rename (container, icon);
+ return;
+ }
+
+ set_pending_icon_to_rename (container, NULL);
+
+ /* Make a copy of the original editable text for a later compare */
+ editable_text = caja_icon_canvas_item_get_editable_text (icon->item);
+
+ /* This could conceivably be NULL if a rename was triggered really early. */
+ if (editable_text == NULL)
+ {
+ return;
+ }
+
+ details->original_text = g_strdup (editable_text);
+
+ /* Freeze updates so files added while renaming don't cause rename to loose focus, bug #318373 */
+ caja_icon_container_freeze_updates (container);
+
+ /* Create text renaming widget, if it hasn't been created already.
+ * We deal with the broken icon text item widget by keeping it around
+ * so its contents can still be cut and pasted as part of the clipboard
+ */
+ if (details->rename_widget == NULL)
+ {
+ details->rename_widget = eel_editable_label_new ("Test text");
+ eel_editable_label_set_line_wrap (EEL_EDITABLE_LABEL (details->rename_widget), TRUE);
+ eel_editable_label_set_line_wrap_mode (EEL_EDITABLE_LABEL (details->rename_widget), PANGO_WRAP_WORD_CHAR);
+ eel_editable_label_set_draw_outline (EEL_EDITABLE_LABEL (details->rename_widget), TRUE);
+
+ if (details->label_position != CAJA_ICON_LABEL_POSITION_BESIDE)
+ {
+ eel_editable_label_set_justify (EEL_EDITABLE_LABEL (details->rename_widget), GTK_JUSTIFY_CENTER);
+ }
+
+ gtk_misc_set_padding (GTK_MISC (details->rename_widget), 1, 1);
+ gtk_layout_put (GTK_LAYOUT (container),
+ details->rename_widget, 0, 0);
+ }
+
+ /* Set the right font */
+ if (details->font)
+ {
+ desc = pango_font_description_from_string (details->font);
+ }
+ else
+ {
+ context = gtk_widget_get_pango_context (GTK_WIDGET (container));
+ desc = pango_font_description_copy (pango_context_get_font_description (context));
+ pango_font_description_set_size (desc,
+ pango_font_description_get_size (desc) +
+ container->details->font_size_table [container->details->zoom_level]);
+ }
+ eel_editable_label_set_font_description (EEL_EDITABLE_LABEL (details->rename_widget),
+ desc);
+ pango_font_description_free (desc);
+
+ icon_rect = caja_icon_canvas_item_get_icon_rectangle (icon->item);
+ text_rect = caja_icon_canvas_item_get_text_rectangle (icon->item, TRUE);
+
+ if (caja_icon_container_is_layout_vertical (container) &&
+ container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
+ {
+ /* for one-line editables, the width changes dynamically */
+ width = -1;
+ }
+ else
+ {
+ width = caja_icon_canvas_item_get_max_text_width (icon->item);
+ }
+
+ if (details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
+ {
+ eel_canvas_w2c (EEL_CANVAS_ITEM (icon->item)->canvas,
+ text_rect.x0,
+ text_rect.y0,
+ &x, &y);
+ }
+ else
+ {
+ eel_canvas_w2c (EEL_CANVAS_ITEM (icon->item)->canvas,
+ (icon_rect.x0 + icon_rect.x1) / 2,
+ icon_rect.y1,
+ &x, &y);
+ x = x - width / 2 - 1;
+ }
+
+ gtk_layout_move (GTK_LAYOUT (container),
+ details->rename_widget,
+ x, y);
+
+ gtk_widget_set_size_request (details->rename_widget,
+ width, -1);
+ eel_editable_label_set_text (EEL_EDITABLE_LABEL (details->rename_widget),
+ editable_text);
+ if (select_all)
+ {
+ start_offset = 0;
+ end_offset = -1;
+ }
+ else
+ {
+ eel_filename_get_rename_region (editable_text, &start_offset, &end_offset);
+ }
+ eel_editable_label_select_region (EEL_EDITABLE_LABEL (details->rename_widget),
+ start_offset,
+ end_offset);
+ gtk_widget_show (details->rename_widget);
+
+ gtk_widget_grab_focus (details->rename_widget);
+
+ g_signal_emit (container,
+ signals[RENAMING_ICON], 0,
+ GTK_EDITABLE (details->rename_widget));
+
+ caja_icon_container_update_icon (container, icon);
+
+ /* We are in renaming mode */
+ details->renaming = TRUE;
+ caja_icon_canvas_item_set_renaming (icon->item, TRUE);
+}
+
+static void
+end_renaming_mode (CajaIconContainer *container, gboolean commit)
+{
+ CajaIcon *icon;
+ const char *changed_text;
+
+ set_pending_icon_to_rename (container, NULL);
+
+ icon = get_icon_being_renamed (container);
+ if (icon == NULL)
+ {
+ return;
+ }
+
+ /* We are not in renaming mode */
+ container->details->renaming = FALSE;
+ caja_icon_canvas_item_set_renaming (icon->item, FALSE);
+
+ caja_icon_container_unfreeze_updates (container);
+
+ if (commit)
+ {
+ set_pending_icon_to_reveal (container, icon);
+ }
+
+ gtk_widget_grab_focus (GTK_WIDGET (container));
+
+ if (commit)
+ {
+ /* Verify that text has been modified before signalling change. */
+ changed_text = eel_editable_label_get_text (EEL_EDITABLE_LABEL (container->details->rename_widget));
+ if (strcmp (container->details->original_text, changed_text) != 0)
+ {
+ g_signal_emit (container,
+ signals[ICON_TEXT_CHANGED], 0,
+ icon->data,
+ changed_text);
+ }
+ }
+
+ gtk_widget_hide (container->details->rename_widget);
+
+ g_free (container->details->original_text);
+
+}
+
+/* emit preview signal, called by the canvas item */
+gboolean
+caja_icon_container_emit_preview_signal (CajaIconContainer *icon_container,
+ CajaIcon *icon,
+ gboolean start_flag)
+{
+ gboolean result;
+
+ g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (icon_container), FALSE);
+ g_return_val_if_fail (icon != NULL, FALSE);
+ g_return_val_if_fail (start_flag == FALSE || start_flag == TRUE, FALSE);
+
+ result = FALSE;
+ g_signal_emit (icon_container,
+ signals[PREVIEW], 0,
+ icon->data,
+ start_flag,
+ &result);
+
+ return result;
+}
+
+gboolean
+caja_icon_container_has_stored_icon_positions (CajaIconContainer *container)
+{
+ GList *p;
+ CajaIcon *icon;
+ gboolean have_stored_position;
+ CajaIconPosition position;
+
+ 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)
+ {
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+void
+caja_icon_container_set_single_click_mode (CajaIconContainer *container,
+ gboolean single_click_mode)
+{
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+
+ container->details->single_click_mode = single_click_mode;
+}
+
+
+/* update the label color when the background changes */
+
+GdkGC *
+caja_icon_container_get_label_color_and_gc (CajaIconContainer *container,
+ GdkColor **color,
+ gboolean is_name,
+ gboolean is_highlight,
+ gboolean is_prelit)
+{
+ int idx;
+
+ if (is_name)
+ {
+ if (is_highlight)
+ {
+ if (gtk_widget_has_focus (GTK_WIDGET (container)))
+ {
+ idx = LABEL_COLOR_HIGHLIGHT;
+ }
+ else
+ {
+ idx = LABEL_COLOR_ACTIVE;
+ }
+ }
+ else
+ {
+ if (is_prelit)
+ {
+ idx = LABEL_COLOR_PRELIGHT;
+ }
+ else
+ {
+ idx = LABEL_COLOR;
+ }
+ }
+ }
+ else
+ {
+ if (is_highlight)
+ {
+ if (gtk_widget_has_focus (GTK_WIDGET (container)))
+ {
+ idx = LABEL_INFO_COLOR_HIGHLIGHT;
+ }
+ else
+ {
+ idx = LABEL_INFO_COLOR_ACTIVE;
+ }
+ }
+ else
+ {
+ idx = LABEL_INFO_COLOR;
+ }
+ }
+
+ if (color)
+ {
+ *color = &container->details->label_colors [idx];
+ }
+
+ return container->details->label_gcs [idx];
+}
+
+static void
+setup_gc_with_fg (CajaIconContainer *container, int idx, guint32 color)
+{
+ GdkGC *gc;
+ GdkColor gcolor;
+
+ gcolor = eel_gdk_rgb_to_color (color);
+ container->details->label_colors [idx] = gcolor;
+
+ gc = gdk_gc_new (gtk_layout_get_bin_window (GTK_LAYOUT (container)));
+ gdk_gc_set_rgb_fg_color (gc, &gcolor);
+
+ if (container->details->label_gcs [idx])
+ {
+ g_object_unref (container->details->label_gcs [idx]);
+ }
+
+ container->details->label_gcs [idx] = gc;
+}
+
+static void
+setup_label_gcs (CajaIconContainer *container)
+{
+ EelBackground *background;
+ GtkWidget *widget;
+ GdkColor *light_info_color, *dark_info_color;
+ guint light_info_value, dark_info_value;
+ gboolean frame_text;
+ GtkStyle *style;
+
+ if (!gtk_widget_get_realized (GTK_WIDGET (container)))
+ return;
+
+ widget = GTK_WIDGET (container);
+
+ g_assert (CAJA_IS_ICON_CONTAINER (container));
+
+ background = eel_get_widget_background (GTK_WIDGET (container));
+
+ /* read the info colors from the current theme; use a reasonable default if undefined */
+ gtk_widget_style_get (GTK_WIDGET (container),
+ "light_info_color", &light_info_color,
+ "dark_info_color", &dark_info_color,
+ NULL);
+ style = gtk_widget_get_style (widget);
+
+ if (light_info_color)
+ {
+ light_info_value = eel_gdk_color_to_rgb (light_info_color);
+ gdk_color_free (light_info_color);
+ }
+ else
+ {
+ light_info_value = DEFAULT_LIGHT_INFO_COLOR;
+ }
+
+ if (dark_info_color)
+ {
+ dark_info_value = eel_gdk_color_to_rgb (dark_info_color);
+ gdk_color_free (dark_info_color);
+ }
+ else
+ {
+ dark_info_value = DEFAULT_DARK_INFO_COLOR;
+ }
+
+ setup_gc_with_fg (container, LABEL_COLOR_HIGHLIGHT, eel_gdk_color_to_rgb (&style->text[GTK_STATE_SELECTED]));
+ setup_gc_with_fg (container, LABEL_COLOR_ACTIVE, eel_gdk_color_to_rgb (&style->text[GTK_STATE_ACTIVE]));
+ setup_gc_with_fg (container, LABEL_COLOR_PRELIGHT, eel_gdk_color_to_rgb (&style->text[GTK_STATE_PRELIGHT]));
+ setup_gc_with_fg (container,
+ LABEL_INFO_COLOR_HIGHLIGHT,
+ eel_gdk_color_is_dark (&style->base[GTK_STATE_SELECTED]) ? light_info_value : dark_info_value);
+ setup_gc_with_fg (container,
+ LABEL_INFO_COLOR_ACTIVE,
+ eel_gdk_color_is_dark (&style->base[GTK_STATE_ACTIVE]) ? light_info_value : dark_info_value);
+
+ /* If CajaIconContainer::frame_text is set, we can safely
+ * use the foreground color from the theme, because it will
+ * always be displayed against the gtk background */
+ gtk_widget_style_get (widget,
+ "frame_text", &frame_text,
+ NULL);
+
+ if (frame_text || !eel_background_is_set(background))
+ {
+ setup_gc_with_fg (container, LABEL_COLOR,
+ eel_gdk_color_to_rgb (&style->text[GTK_STATE_NORMAL]));
+ setup_gc_with_fg (container,
+ LABEL_INFO_COLOR,
+ eel_gdk_color_is_dark (&style->base[GTK_STATE_NORMAL]) ? light_info_value : dark_info_value);
+ }
+ else
+ {
+ if (container->details->use_drop_shadows || eel_background_is_dark (background))
+ {
+ setup_gc_with_fg (container, LABEL_COLOR, 0xEFEFEF);
+ setup_gc_with_fg (container,
+ LABEL_INFO_COLOR,
+ light_info_value);
+ }
+ else /* converse */
+ {
+ setup_gc_with_fg (container, LABEL_COLOR, 0x000000);
+ setup_gc_with_fg (container,
+ LABEL_INFO_COLOR,
+ dark_info_value);
+ }
+ }
+}
+
+static void
+update_label_color (EelBackground *background,
+ CajaIconContainer *container)
+{
+ g_assert (EEL_IS_BACKGROUND (background));
+
+ setup_label_gcs (container);
+}
+
+
+/* Return if the icon container is a fixed size */
+gboolean
+caja_icon_container_get_is_fixed_size (CajaIconContainer *container)
+{
+ g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (container), FALSE);
+
+ return container->details->is_fixed_size;
+}
+
+/* Set the icon container to be a fixed size */
+void
+caja_icon_container_set_is_fixed_size (CajaIconContainer *container,
+ gboolean is_fixed_size)
+{
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+
+ container->details->is_fixed_size = is_fixed_size;
+}
+
+gboolean
+caja_icon_container_get_is_desktop (CajaIconContainer *container)
+{
+ g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (container), FALSE);
+
+ return container->details->is_desktop;
+}
+
+void
+caja_icon_container_set_is_desktop (CajaIconContainer *container,
+ gboolean is_desktop)
+{
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+
+ container->details->is_desktop = is_desktop;
+}
+
+void
+caja_icon_container_set_margins (CajaIconContainer *container,
+ int left_margin,
+ int right_margin,
+ int top_margin,
+ int bottom_margin)
+{
+ g_return_if_fail (CAJA_IS_ICON_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);
+}
+
+void
+caja_icon_container_set_use_drop_shadows (CajaIconContainer *container,
+ gboolean use_drop_shadows)
+{
+ gboolean frame_text;
+
+ gtk_widget_style_get (GTK_WIDGET (container),
+ "frame_text", &frame_text,
+ NULL);
+
+ if (container->details->drop_shadows_requested == use_drop_shadows)
+ {
+ return;
+ }
+
+ container->details->drop_shadows_requested = use_drop_shadows;
+ container->details->use_drop_shadows = use_drop_shadows && !frame_text;
+ gtk_widget_queue_draw (GTK_WIDGET (container));
+}
+
+/* handle theme changes */
+
+static void
+caja_icon_container_theme_changed (gpointer user_data)
+{
+ CajaIconContainer *container;
+ GtkStyle *style;
+ GdkColor *prelight_icon_color, *normal_icon_color;
+ guchar highlight_alpha, normal_alpha, prelight_alpha;
+
+ container = CAJA_ICON_CONTAINER (user_data);
+
+ /* load the highlight color */
+ gtk_widget_style_get (GTK_WIDGET (container),
+ "highlight_alpha", &highlight_alpha,
+ NULL);
+
+ style = gtk_widget_get_style (GTK_WIDGET (container));
+
+ container->details->highlight_color_rgba =
+ EEL_RGBA_COLOR_PACK (style->base[GTK_STATE_SELECTED].red >> 8,
+ style->base[GTK_STATE_SELECTED].green >> 8,
+ style->base[GTK_STATE_SELECTED].blue >> 8,
+ highlight_alpha);
+ container->details->active_color_rgba =
+ EEL_RGBA_COLOR_PACK (style->base[GTK_STATE_ACTIVE].red >> 8,
+ style->base[GTK_STATE_ACTIVE].green >> 8,
+ style->base[GTK_STATE_ACTIVE].blue >> 8,
+ highlight_alpha);
+
+ /* load the prelight icon color */
+ gtk_widget_style_get (GTK_WIDGET (container),
+ "prelight_icon_color", &prelight_icon_color,
+ NULL);
+
+ if (prelight_icon_color)
+ {
+ container->details->prelight_icon_color_rgba =
+ EEL_RGBA_COLOR_PACK (prelight_icon_color->red >> 8,
+ prelight_icon_color->green >> 8,
+ prelight_icon_color->blue >> 8,
+ 255);
+ }
+ else /* if not defined by rc, set to default value */
+ {
+ container->details->prelight_icon_color_rgba =
+ EEL_RGBA_COLOR_PACK (style->base[GTK_STATE_PRELIGHT].red >> 8,
+ style->base[GTK_STATE_PRELIGHT].green >> 8,
+ style->base[GTK_STATE_PRELIGHT].blue >> 8,
+ 255);
+ }
+
+
+ /* load the normal icon color */
+ gtk_widget_style_get (GTK_WIDGET (container),
+ "normal_icon_color", &normal_icon_color,
+ NULL);
+
+ if (normal_icon_color)
+ {
+ container->details->normal_icon_color_rgba =
+ EEL_RGBA_COLOR_PACK (normal_icon_color->red >> 8,
+ normal_icon_color->green >> 8,
+ normal_icon_color->blue >> 8,
+ 255);
+ }
+ else /* if not defined by rc, set to default value */
+ {
+ container->details->normal_icon_color_rgba =
+ EEL_RGBA_COLOR_PACK (style->base[GTK_STATE_NORMAL].red >> 8,
+ style->base[GTK_STATE_NORMAL].green >> 8,
+ style->base[GTK_STATE_NORMAL].blue >> 8,
+ 255);
+ }
+
+
+ /* load the normal color */
+ gtk_widget_style_get (GTK_WIDGET (container),
+ "normal_alpha", &normal_alpha,
+ NULL);
+
+ container->details->normal_color_rgba =
+ EEL_RGBA_COLOR_PACK (style->base[GTK_STATE_NORMAL].red >> 8,
+ style->base[GTK_STATE_NORMAL].green >> 8,
+ style->base[GTK_STATE_NORMAL].blue >> 8,
+ normal_alpha);
+
+
+ /* load the prelight color */
+ gtk_widget_style_get (GTK_WIDGET (container),
+ "prelight_alpha", &prelight_alpha,
+ NULL);
+
+ container->details->prelight_color_rgba =
+ EEL_RGBA_COLOR_PACK (style->base[GTK_STATE_PRELIGHT].red >> 8,
+ style->base[GTK_STATE_PRELIGHT].green >> 8,
+ style->base[GTK_STATE_PRELIGHT].blue >> 8,
+ prelight_alpha);
+
+
+ setup_label_gcs (container);
+}
+
+void
+caja_icon_container_set_font (CajaIconContainer *container,
+ const char *font)
+{
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+
+ if (eel_strcmp (container->details->font, font) == 0)
+ {
+ return;
+ }
+
+ g_free (container->details->font);
+ container->details->font = g_strdup (font);
+
+ invalidate_labels (container);
+ caja_icon_container_request_update_all (container);
+ gtk_widget_queue_draw (GTK_WIDGET (container));
+}
+
+void
+caja_icon_container_set_font_size_table (CajaIconContainer *container,
+ const int font_size_table[CAJA_ZOOM_LEVEL_LARGEST + 1])
+{
+ int old_font_size;
+ int i;
+
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+ g_return_if_fail (font_size_table != NULL);
+
+ old_font_size = container->details->font_size_table[container->details->zoom_level];
+
+ for (i = 0; i <= CAJA_ZOOM_LEVEL_LARGEST; i++)
+ {
+ if (container->details->font_size_table[i] != font_size_table[i])
+ {
+ container->details->font_size_table[i] = font_size_table[i];
+ }
+ }
+
+ if (old_font_size != container->details->font_size_table[container->details->zoom_level])
+ {
+ invalidate_labels (container);
+ caja_icon_container_request_update_all (container);
+ }
+}
+
+/**
+ * caja_icon_container_get_icon_description
+ * @container: An icon container widget.
+ * @data: Icon data
+ *
+ * Gets the description for the icon. This function may return NULL.
+ **/
+char*
+caja_icon_container_get_icon_description (CajaIconContainer *container,
+ CajaIconData *data)
+{
+ CajaIconContainerClass *klass;
+
+ klass = CAJA_ICON_CONTAINER_GET_CLASS (container);
+
+ if (klass->get_icon_description)
+ {
+ return klass->get_icon_description (container, data);
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+gboolean
+caja_icon_container_get_allow_moves (CajaIconContainer *container)
+{
+ g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (container), FALSE);
+
+ return container->details->drag_allow_moves;
+}
+
+void
+caja_icon_container_set_allow_moves (CajaIconContainer *container,
+ gboolean allow_moves)
+{
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+
+ container->details->drag_allow_moves = allow_moves;
+}
+
+void
+caja_icon_container_set_forced_icon_size (CajaIconContainer *container,
+ int forced_icon_size)
+{
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+
+ if (forced_icon_size != container->details->forced_icon_size)
+ {
+ container->details->forced_icon_size = forced_icon_size;
+
+ invalidate_label_sizes (container);
+ caja_icon_container_request_update_all (container);
+ }
+}
+
+void
+caja_icon_container_set_all_columns_same_width (CajaIconContainer *container,
+ gboolean all_columns_same_width)
+{
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+
+ if (all_columns_same_width != container->details->all_columns_same_width)
+ {
+ container->details->all_columns_same_width = all_columns_same_width;
+
+ invalidate_labels (container);
+ caja_icon_container_request_update_all (container);
+ }
+}
+
+/**
+ * caja_icon_container_set_highlighted_for_clipboard
+ * @container: An icon container widget.
+ * @data: Icon Data associated with all icons that should be highlighted.
+ * Others will be unhighlighted.
+ **/
+void
+caja_icon_container_set_highlighted_for_clipboard (CajaIconContainer *container,
+ GList *clipboard_icon_data)
+{
+ GList *l;
+ CajaIcon *icon;
+ gboolean highlighted_for_clipboard;
+
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+
+ for (l = container->details->icons; l != NULL; l = l->next)
+ {
+ icon = l->data;
+ highlighted_for_clipboard = (g_list_find (clipboard_icon_data, icon->data) != NULL);
+
+ eel_canvas_item_set (EEL_CANVAS_ITEM (icon->item),
+ "highlighted-for-clipboard", highlighted_for_clipboard,
+ NULL);
+ }
+
+}
+
+/* CajaIconContainerAccessible */
+
+static CajaIconContainerAccessiblePrivate *
+accessible_get_priv (AtkObject *accessible)
+{
+ CajaIconContainerAccessiblePrivate *priv;
+
+ priv = g_object_get_qdata (G_OBJECT (accessible),
+ accessible_private_data_quark);
+
+ return priv;
+}
+
+/* AtkAction interface */
+
+static gboolean
+caja_icon_container_accessible_do_action (AtkAction *accessible, int i)
+{
+ GtkWidget *widget;
+ CajaIconContainer *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 = CAJA_ICON_CONTAINER (widget);
+ switch (i)
+ {
+ case ACTION_ACTIVATE :
+ selection = caja_icon_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 CajaIconContainerAccessible::do_action");
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static int
+caja_icon_container_accessible_get_n_actions (AtkAction *accessible)
+{
+ return LAST_ACTION;
+}
+
+static const char *
+caja_icon_container_accessible_action_get_description (AtkAction *accessible,
+ int i)
+{
+ CajaIconContainerAccessiblePrivate *priv;
+
+ g_assert (i < LAST_ACTION);
+
+ priv = accessible_get_priv (ATK_OBJECT (accessible));
+
+ if (priv->action_descriptions[i])
+ {
+ return priv->action_descriptions[i];
+ }
+ else
+ {
+ return caja_icon_container_accessible_action_descriptions[i];
+ }
+}
+
+static const char *
+caja_icon_container_accessible_action_get_name (AtkAction *accessible, int i)
+{
+ g_assert (i < LAST_ACTION);
+
+ return caja_icon_container_accessible_action_names[i];
+}
+
+static const char *
+caja_icon_container_accessible_action_get_keybinding (AtkAction *accessible,
+ int i)
+{
+ g_assert (i < LAST_ACTION);
+
+ return NULL;
+}
+
+static gboolean
+caja_icon_container_accessible_action_set_description (AtkAction *accessible,
+ int i,
+ const char *description)
+{
+ CajaIconContainerAccessiblePrivate *priv;
+
+ g_assert (i < LAST_ACTION);
+
+ priv = accessible_get_priv (ATK_OBJECT (accessible));
+
+ if (priv->action_descriptions[i])
+ {
+ g_free (priv->action_descriptions[i]);
+ }
+ priv->action_descriptions[i] = g_strdup (description);
+
+ return FALSE;
+}
+
+static void
+caja_icon_container_accessible_action_interface_init (AtkActionIface *iface)
+{
+ iface->do_action = caja_icon_container_accessible_do_action;
+ iface->get_n_actions = caja_icon_container_accessible_get_n_actions;
+ iface->get_description = caja_icon_container_accessible_action_get_description;
+ iface->get_name = caja_icon_container_accessible_action_get_name;
+ iface->get_keybinding = caja_icon_container_accessible_action_get_keybinding;
+ iface->set_description = caja_icon_container_accessible_action_set_description;
+}
+
+/* AtkSelection interface */
+
+static void
+caja_icon_container_accessible_update_selection (AtkObject *accessible)
+{
+ CajaIconContainer *container;
+ CajaIconContainerAccessiblePrivate *priv;
+ GList *l;
+ CajaIcon *icon;
+
+ container = CAJA_ICON_CONTAINER (gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible)));
+
+ priv = accessible_get_priv (accessible);
+
+ if (priv->selection)
+ {
+ g_list_free (priv->selection);
+ priv->selection = NULL;
+ }
+
+ for (l = container->details->icons; l != NULL; l = l->next)
+ {
+ icon = l->data;
+ if (icon->is_selected)
+ {
+ priv->selection = g_list_prepend (priv->selection,
+ icon);
+ }
+ }
+
+ priv->selection = g_list_reverse (priv->selection);
+}
+
+static void
+caja_icon_container_accessible_selection_changed_cb (CajaIconContainer *container,
+ gpointer data)
+{
+ g_signal_emit_by_name (data, "selection_changed");
+}
+
+static void
+caja_icon_container_accessible_icon_added_cb (CajaIconContainer *container,
+ CajaIconData *icon_data,
+ gpointer data)
+{
+ CajaIcon *icon;
+ AtkObject *atk_parent;
+ AtkObject *atk_child;
+ int index;
+
+ 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));
+ index = g_list_index (container->details->icons, icon);
+
+ g_signal_emit_by_name (atk_parent, "children_changed::add",
+ index, atk_child, NULL);
+ }
+}
+
+static void
+caja_icon_container_accessible_icon_removed_cb (CajaIconContainer *container,
+ CajaIconData *icon_data,
+ gpointer data)
+{
+ CajaIcon *icon;
+ AtkObject *atk_parent;
+ AtkObject *atk_child;
+ int index;
+
+ 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));
+ index = g_list_index (container->details->icons, icon);
+
+ g_signal_emit_by_name (atk_parent, "children_changed::remove",
+ index, atk_child, NULL);
+ }
+}
+
+static void
+caja_icon_container_accessible_cleared_cb (CajaIconContainer *container,
+ gpointer data)
+{
+ g_signal_emit_by_name (data, "children_changed", 0, NULL, NULL);
+}
+
+
+static gboolean
+caja_icon_container_accessible_add_selection (AtkSelection *accessible,
+ int i)
+{
+ GtkWidget *widget;
+ CajaIconContainer *container;
+ GList *l;
+ GList *selection;
+ CajaIcon *icon;
+
+ widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
+ if (!widget)
+ {
+ return FALSE;
+ }
+
+ container = CAJA_ICON_CONTAINER (widget);
+
+ l = g_list_nth (container->details->icons, i);
+ if (l)
+ {
+ icon = l->data;
+
+ selection = caja_icon_container_get_selection (container);
+ selection = g_list_prepend (selection,
+ icon->data);
+ caja_icon_container_set_selection (container, selection);
+
+ g_list_free (selection);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+caja_icon_container_accessible_clear_selection (AtkSelection *accessible)
+{
+ GtkWidget *widget;
+ CajaIconContainer *container;
+
+ widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
+ if (!widget)
+ {
+ return FALSE;
+ }
+
+ container = CAJA_ICON_CONTAINER (widget);
+
+ caja_icon_container_unselect_all (container);
+
+ return TRUE;
+}
+
+static AtkObject *
+caja_icon_container_accessible_ref_selection (AtkSelection *accessible,
+ int i)
+{
+ AtkObject *atk_object;
+ CajaIconContainerAccessiblePrivate *priv;
+ GList *item;
+ CajaIcon *icon;
+
+ caja_icon_container_accessible_update_selection (ATK_OBJECT (accessible));
+ priv = accessible_get_priv (ATK_OBJECT (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
+caja_icon_container_accessible_get_selection_count (AtkSelection *accessible)
+{
+ int count;
+ CajaIconContainerAccessiblePrivate *priv;
+
+ caja_icon_container_accessible_update_selection (ATK_OBJECT (accessible));
+ priv = accessible_get_priv (ATK_OBJECT (accessible));
+
+ count = g_list_length (priv->selection);
+
+ return count;
+}
+
+static gboolean
+caja_icon_container_accessible_is_child_selected (AtkSelection *accessible,
+ int i)
+{
+ CajaIconContainer *container;
+ GList *l;
+ CajaIcon *icon;
+ GtkWidget *widget;
+
+ widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
+ if (!widget)
+ {
+ return FALSE;
+ }
+
+ container = CAJA_ICON_CONTAINER (widget);
+
+ l = g_list_nth (container->details->icons, i);
+ if (l)
+ {
+ icon = l->data;
+ return icon->is_selected;
+ }
+ return FALSE;
+}
+
+static gboolean
+caja_icon_container_accessible_remove_selection (AtkSelection *accessible,
+ int i)
+{
+ CajaIconContainer *container;
+ CajaIconContainerAccessiblePrivate *priv;
+ GList *l;
+ GList *selection;
+ CajaIcon *icon;
+ GtkWidget *widget;
+
+ widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
+ if (!widget)
+ {
+ return FALSE;
+ }
+
+ caja_icon_container_accessible_update_selection (ATK_OBJECT (accessible));
+ priv = accessible_get_priv (ATK_OBJECT (accessible));
+
+ container = CAJA_ICON_CONTAINER (widget);
+
+ l = g_list_nth (priv->selection, i);
+ if (l)
+ {
+ icon = l->data;
+
+ selection = caja_icon_container_get_selection (container);
+ selection = g_list_remove (selection, icon->data);
+ caja_icon_container_set_selection (container, selection);
+
+ g_list_free (selection);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+caja_icon_container_accessible_select_all_selection (AtkSelection *accessible)
+{
+ CajaIconContainer *container;
+ GtkWidget *widget;
+
+ widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
+ if (!widget)
+ {
+ return FALSE;
+ }
+
+ container = CAJA_ICON_CONTAINER (widget);
+
+ caja_icon_container_select_all (container);
+
+ return TRUE;
+}
+
+void
+caja_icon_container_widget_to_file_operation_position (CajaIconContainer *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 -= caja_get_icon_size_for_zoom_level (container->details->zoom_level) / 2;
+ position->y -= caja_get_icon_size_for_zoom_level (container->details->zoom_level) / 2;
+}
+
+static void
+caja_icon_container_accessible_selection_interface_init (AtkSelectionIface *iface)
+{
+ iface->add_selection = caja_icon_container_accessible_add_selection;
+ iface->clear_selection = caja_icon_container_accessible_clear_selection;
+ iface->ref_selection = caja_icon_container_accessible_ref_selection;
+ iface->get_selection_count = caja_icon_container_accessible_get_selection_count;
+ iface->is_child_selected = caja_icon_container_accessible_is_child_selected;
+ iface->remove_selection = caja_icon_container_accessible_remove_selection;
+ iface->select_all_selection = caja_icon_container_accessible_select_all_selection;
+}
+
+
+static gint
+caja_icon_container_accessible_get_n_children (AtkObject *accessible)
+{
+ CajaIconContainer *container;
+ GtkWidget *widget;
+ gint i;
+
+ widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
+ if (!widget)
+ {
+ return FALSE;
+ }
+
+ container = CAJA_ICON_CONTAINER (widget);
+
+ i = g_hash_table_size (container->details->icon_set);
+ if (container->details->rename_widget)
+ {
+ i++;
+ }
+ return i;
+}
+
+static AtkObject*
+caja_icon_container_accessible_ref_child (AtkObject *accessible, int i)
+{
+ AtkObject *atk_object;
+ CajaIconContainer *container;
+ GList *item;
+ CajaIcon *icon;
+ GtkWidget *widget;
+
+ widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
+ if (!widget)
+ {
+ return NULL;
+ }
+
+ container = CAJA_ICON_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;
+ }
+ else
+ {
+ if (i == g_list_length (container->details->icons))
+ {
+ if (container->details->rename_widget)
+ {
+ atk_object = gtk_widget_get_accessible (container->details->rename_widget);
+ g_object_ref (atk_object);
+
+ return atk_object;
+ }
+ }
+ return NULL;
+ }
+}
+
+static void
+caja_icon_container_accessible_initialize (AtkObject *accessible,
+ gpointer data)
+{
+ CajaIconContainer *container;
+ CajaIconContainerAccessiblePrivate *priv;
+
+ if (ATK_OBJECT_CLASS (accessible_parent_class)->initialize)
+ {
+ ATK_OBJECT_CLASS (accessible_parent_class)->initialize (accessible, data);
+ }
+
+ priv = g_new0 (CajaIconContainerAccessiblePrivate, 1);
+ g_object_set_qdata (G_OBJECT (accessible),
+ accessible_private_data_quark,
+ priv);
+
+ if (GTK_IS_ACCESSIBLE (accessible))
+ {
+ caja_icon_container_accessible_update_selection
+ (ATK_OBJECT (accessible));
+
+ container = CAJA_ICON_CONTAINER (gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible)));
+ g_signal_connect (G_OBJECT (container), "selection_changed",
+ G_CALLBACK (caja_icon_container_accessible_selection_changed_cb),
+ accessible);
+ g_signal_connect (G_OBJECT (container), "icon_added",
+ G_CALLBACK (caja_icon_container_accessible_icon_added_cb),
+ accessible);
+ g_signal_connect (G_OBJECT (container), "icon_removed",
+ G_CALLBACK (caja_icon_container_accessible_icon_removed_cb),
+ accessible);
+ g_signal_connect (G_OBJECT (container), "cleared",
+ G_CALLBACK (caja_icon_container_accessible_cleared_cb),
+ accessible);
+ }
+}
+
+static void
+caja_icon_container_accessible_finalize (GObject *object)
+{
+ CajaIconContainerAccessiblePrivate *priv;
+ int i;
+
+ priv = accessible_get_priv (ATK_OBJECT (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_free (priv);
+
+ G_OBJECT_CLASS (accessible_parent_class)->finalize (object);
+}
+
+static void
+caja_icon_container_accessible_class_init (AtkObjectClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ accessible_parent_class = g_type_class_peek_parent (klass);
+
+ gobject_class->finalize = caja_icon_container_accessible_finalize;
+
+ klass->get_n_children = caja_icon_container_accessible_get_n_children;
+ klass->ref_child = caja_icon_container_accessible_ref_child;
+ klass->initialize = caja_icon_container_accessible_initialize;
+
+ accessible_private_data_quark = g_quark_from_static_string ("icon-container-accessible-private-data");
+}
+
+static GType
+caja_icon_container_accessible_get_type (void)
+{
+ static GType type = 0;
+
+ if (!type)
+ {
+ static GInterfaceInfo atk_action_info =
+ {
+ (GInterfaceInitFunc) caja_icon_container_accessible_action_interface_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL
+ };
+
+ static GInterfaceInfo atk_selection_info =
+ {
+ (GInterfaceInitFunc) caja_icon_container_accessible_selection_interface_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL
+ };
+
+ type = eel_accessibility_create_derived_type
+ ("CajaIconContainerAccessible",
+ EEL_TYPE_CANVAS,
+ caja_icon_container_accessible_class_init);
+
+ g_type_add_interface_static (type, ATK_TYPE_ACTION,
+ &atk_action_info);
+ g_type_add_interface_static (type, ATK_TYPE_SELECTION,
+ &atk_selection_info);
+ }
+
+ return type;
+}
+
+#if ! defined (CAJA_OMIT_SELF_CHECK)
+
+static char *
+check_compute_stretch (int icon_x, int icon_y, int icon_size,
+ int start_pointer_x, int start_pointer_y,
+ int end_pointer_x, int end_pointer_y)
+{
+ StretchState start, current;
+
+ start.icon_x = icon_x;
+ start.icon_y = icon_y;
+ start.icon_size = icon_size;
+ start.pointer_x = start_pointer_x;
+ start.pointer_y = start_pointer_y;
+ current.pointer_x = end_pointer_x;
+ current.pointer_y = end_pointer_y;
+
+ compute_stretch (&start, &current);
+
+ return g_strdup_printf ("%d,%d:%d",
+ current.icon_x,
+ current.icon_y,
+ current.icon_size);
+}
+
+void
+caja_self_check_icon_container (void)
+{
+ EEL_CHECK_STRING_RESULT (check_compute_stretch (0, 0, 16, 0, 0, 0, 0), "0,0:16");
+ EEL_CHECK_STRING_RESULT (check_compute_stretch (0, 0, 16, 16, 16, 17, 17), "0,0:17");
+ EEL_CHECK_STRING_RESULT (check_compute_stretch (0, 0, 16, 16, 16, 17, 16), "0,0:16");
+ EEL_CHECK_STRING_RESULT (check_compute_stretch (100, 100, 64, 105, 105, 40, 40), "35,35:129");
+}
+
+gboolean
+caja_icon_container_is_layout_rtl (CajaIconContainer *container)
+{
+ g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (container), 0);
+
+ return container->details->layout_mode == CAJA_ICON_LAYOUT_T_B_R_L ||
+ container->details->layout_mode == CAJA_ICON_LAYOUT_R_L_T_B;
+}
+
+gboolean
+caja_icon_container_is_layout_vertical (CajaIconContainer *container)
+{
+ g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (container), FALSE);
+
+ return (container->details->layout_mode == CAJA_ICON_LAYOUT_T_B_L_R ||
+ container->details->layout_mode == CAJA_ICON_LAYOUT_T_B_R_L);
+}
+
+int
+caja_icon_container_get_max_layout_lines_for_pango (CajaIconContainer *container)
+{
+ int limit;
+
+ if (caja_icon_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
+caja_icon_container_get_max_layout_lines (CajaIconContainer *container)
+{
+ int limit;
+
+ if (caja_icon_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
+caja_icon_container_begin_loading (CajaIconContainer *container)
+{
+ gboolean dummy;
+
+ if (caja_icon_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 (CajaIconContainer *container)
+{
+ CajaIcon *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
+caja_icon_container_end_loading (CajaIconContainer *container,
+ gboolean all_icons_added)
+{
+ if (all_icons_added &&
+ caja_icon_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
+caja_icon_container_get_store_layout_timestamps (CajaIconContainer *container)
+{
+ return container->details->store_layout_timestamps;
+}
+
+
+void
+caja_icon_container_set_store_layout_timestamps (CajaIconContainer *container,
+ gboolean store_layout_timestamps)
+{
+ container->details->store_layout_timestamps = store_layout_timestamps;
+}
+
+
+#endif /* ! CAJA_OMIT_SELF_CHECK */
diff --git a/libcaja-private/caja-icon-container.h b/libcaja-private/caja-icon-container.h
new file mode 100644
index 00000000..10472878
--- /dev/null
+++ b/libcaja-private/caja-icon-container.h
@@ -0,0 +1,371 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* mate-icon-container.h - Icon container widget.
+
+ Copyright (C) 1999, 2000 Free Software Foundation
+ Copyright (C) 2000 Eazel, Inc.
+
+ The Mate 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 Mate 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 Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Ettore Perazzoli <[email protected]>, Darin Adler <[email protected]>
+*/
+
+#ifndef CAJA_ICON_CONTAINER_H
+#define CAJA_ICON_CONTAINER_H
+
+#include <eel/eel-canvas.h>
+#include <libcaja-private/caja-icon-info.h>
+
+#define CAJA_TYPE_ICON_CONTAINER caja_icon_container_get_type()
+#define CAJA_ICON_CONTAINER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_ICON_CONTAINER, CajaIconContainer))
+#define CAJA_ICON_CONTAINER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_ICON_CONTAINER, CajaIconContainerClass))
+#define CAJA_IS_ICON_CONTAINER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_ICON_CONTAINER))
+#define CAJA_IS_ICON_CONTAINER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_ICON_CONTAINER))
+#define CAJA_ICON_CONTAINER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_ICON_CONTAINER, CajaIconContainerClass))
+
+
+#define CAJA_ICON_CONTAINER_ICON_DATA(pointer) \
+ ((CajaIconData *) (pointer))
+
+typedef struct CajaIconData CajaIconData;
+
+typedef void (* CajaIconCallback) (CajaIconData *icon_data,
+ gpointer callback_data);
+
+typedef struct
+{
+ int x;
+ int y;
+ double scale;
+} CajaIconPosition;
+
+typedef enum
+{
+ CAJA_ICON_LAYOUT_L_R_T_B,
+ CAJA_ICON_LAYOUT_R_L_T_B,
+ CAJA_ICON_LAYOUT_T_B_L_R,
+ CAJA_ICON_LAYOUT_T_B_R_L
+} CajaIconLayoutMode;
+
+typedef enum
+{
+ CAJA_ICON_LABEL_POSITION_UNDER,
+ CAJA_ICON_LABEL_POSITION_BESIDE
+} CajaIconLabelPosition;
+
+#define CAJA_ICON_CONTAINER_TYPESELECT_FLUSH_DELAY 1000000
+
+typedef struct CajaIconContainerDetails CajaIconContainerDetails;
+
+typedef struct
+{
+ EelCanvas canvas;
+ CajaIconContainerDetails *details;
+} CajaIconContainer;
+
+typedef struct
+{
+ EelCanvasClass parent_slot;
+
+ /* Operations on the container. */
+ int (* button_press) (CajaIconContainer *container,
+ GdkEventButton *event);
+ void (* context_click_background) (CajaIconContainer *container,
+ GdkEventButton *event);
+ void (* middle_click) (CajaIconContainer *container,
+ GdkEventButton *event);
+
+ /* Operations on icons. */
+ void (* activate) (CajaIconContainer *container,
+ CajaIconData *data);
+ void (* activate_alternate) (CajaIconContainer *container,
+ CajaIconData *data);
+ void (* context_click_selection) (CajaIconContainer *container,
+ GdkEventButton *event);
+ void (* move_copy_items) (CajaIconContainer *container,
+ const GList *item_uris,
+ GdkPoint *relative_item_points,
+ const char *target_uri,
+ GdkDragAction action,
+ int x,
+ int y);
+ void (* handle_netscape_url) (CajaIconContainer *container,
+ const char *url,
+ const char *target_uri,
+ GdkDragAction action,
+ int x,
+ int y);
+ void (* handle_uri_list) (CajaIconContainer *container,
+ const char *uri_list,
+ const char *target_uri,
+ GdkDragAction action,
+ int x,
+ int y);
+ void (* handle_text) (CajaIconContainer *container,
+ const char *text,
+ const char *target_uri,
+ GdkDragAction action,
+ int x,
+ int y);
+ void (* handle_raw) (CajaIconContainer *container,
+ char *raw_data,
+ int length,
+ const char *target_uri,
+ const char *direct_save_uri,
+ GdkDragAction action,
+ int x,
+ int y);
+
+ /* Queries on the container for subclass/client.
+ * These must be implemented. The default "do nothing" is not good enough.
+ */
+ char * (* get_container_uri) (CajaIconContainer *container);
+
+ /* Queries on icons for subclass/client.
+ * These must be implemented. The default "do nothing" is not
+ * good enough, these are _not_ signals.
+ */
+ CajaIconInfo *(* get_icon_images) (CajaIconContainer *container,
+ CajaIconData *data,
+ int icon_size,
+ GList **emblem_pixbufs,
+ char **embedded_text,
+ gboolean for_drag_accept,
+ gboolean need_large_embeddded_text,
+ gboolean *embedded_text_needs_loading,
+ gboolean *has_window_open);
+ void (* get_icon_text) (CajaIconContainer *container,
+ CajaIconData *data,
+ char **editable_text,
+ char **additional_text,
+ gboolean include_invisible);
+ char * (* get_icon_description) (CajaIconContainer *container,
+ CajaIconData *data);
+ int (* compare_icons) (CajaIconContainer *container,
+ CajaIconData *icon_a,
+ CajaIconData *icon_b);
+ int (* compare_icons_by_name) (CajaIconContainer *container,
+ CajaIconData *icon_a,
+ CajaIconData *icon_b);
+ void (* freeze_updates) (CajaIconContainer *container);
+ void (* unfreeze_updates) (CajaIconContainer *container);
+ void (* start_monitor_top_left) (CajaIconContainer *container,
+ CajaIconData *data,
+ gconstpointer client,
+ gboolean large_text);
+ void (* stop_monitor_top_left) (CajaIconContainer *container,
+ CajaIconData *data,
+ gconstpointer client);
+ void (* prioritize_thumbnailing) (CajaIconContainer *container,
+ CajaIconData *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) (CajaIconContainer *container,
+ CajaIconData *target,
+ const char *item_uri);
+ gboolean (* get_stored_icon_position) (CajaIconContainer *container,
+ CajaIconData *data,
+ CajaIconPosition *position);
+ char * (* get_icon_uri) (CajaIconContainer *container,
+ CajaIconData *data);
+ char * (* get_icon_drop_target_uri) (CajaIconContainer *container,
+ CajaIconData *data);
+
+ /* If icon 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 icon positions assigned.
+ *
+ * If icon data is not NULL, the position timestamp of the icon should be retrieved.
+ * That is the time when the file (i.e. icon data payload) was last displayed in a
+ * fully loaded directory with all icon positions assigned.
+ */
+ gboolean (* get_stored_layout_timestamp) (CajaIconContainer *container,
+ CajaIconData *data,
+ time_t *time);
+ /* If icon data is NULL, the layout timestamp of the container should be stored.
+ * If icon data is not NULL, the position timestamp of the container should be stored.
+ */
+ gboolean (* store_layout_timestamp) (CajaIconContainer *container,
+ CajaIconData *data,
+ const time_t *time);
+
+ /* Notifications for the whole container. */
+ void (* band_select_started) (CajaIconContainer *container);
+ void (* band_select_ended) (CajaIconContainer *container);
+ void (* selection_changed) (CajaIconContainer *container);
+ void (* layout_changed) (CajaIconContainer *container);
+
+ /* Notifications for icons. */
+ void (* icon_position_changed) (CajaIconContainer *container,
+ CajaIconData *data,
+ const CajaIconPosition *position);
+ void (* icon_text_changed) (CajaIconContainer *container,
+ CajaIconData *data,
+ const char *text);
+ void (* renaming_icon) (CajaIconContainer *container,
+ GtkWidget *renaming_widget);
+ void (* icon_stretch_started) (CajaIconContainer *container,
+ CajaIconData *data);
+ void (* icon_stretch_ended) (CajaIconContainer *container,
+ CajaIconData *data);
+ int (* preview) (CajaIconContainer *container,
+ CajaIconData *data,
+ gboolean start_flag);
+ void (* icon_added) (CajaIconContainer *container,
+ CajaIconData *data);
+ void (* icon_removed) (CajaIconContainer *container,
+ CajaIconData *data);
+ void (* cleared) (CajaIconContainer *container);
+ gboolean (* start_interactive_search) (CajaIconContainer *container);
+} CajaIconContainerClass;
+
+/* GtkObject */
+GType caja_icon_container_get_type (void);
+GtkWidget * caja_icon_container_new (void);
+
+
+/* adding, removing, and managing icons */
+void caja_icon_container_clear (CajaIconContainer *view);
+gboolean caja_icon_container_add (CajaIconContainer *view,
+ CajaIconData *data);
+void caja_icon_container_layout_now (CajaIconContainer *container);
+gboolean caja_icon_container_remove (CajaIconContainer *view,
+ CajaIconData *data);
+void caja_icon_container_for_each (CajaIconContainer *view,
+ CajaIconCallback callback,
+ gpointer callback_data);
+void caja_icon_container_request_update (CajaIconContainer *view,
+ CajaIconData *data);
+void caja_icon_container_request_update_all (CajaIconContainer *container);
+void caja_icon_container_reveal (CajaIconContainer *container,
+ CajaIconData *data);
+gboolean caja_icon_container_is_empty (CajaIconContainer *container);
+CajaIconData *caja_icon_container_get_first_visible_icon (CajaIconContainer *container);
+void caja_icon_container_scroll_to_icon (CajaIconContainer *container,
+ CajaIconData *data);
+
+void caja_icon_container_begin_loading (CajaIconContainer *container);
+void caja_icon_container_end_loading (CajaIconContainer *container,
+ gboolean all_icons_added);
+
+/* control the layout */
+gboolean caja_icon_container_is_auto_layout (CajaIconContainer *container);
+void caja_icon_container_set_auto_layout (CajaIconContainer *container,
+ gboolean auto_layout);
+gboolean caja_icon_container_is_tighter_layout (CajaIconContainer *container);
+void caja_icon_container_set_tighter_layout (CajaIconContainer *container,
+ gboolean tighter_layout);
+
+gboolean caja_icon_container_is_keep_aligned (CajaIconContainer *container);
+void caja_icon_container_set_keep_aligned (CajaIconContainer *container,
+ gboolean keep_aligned);
+void caja_icon_container_set_layout_mode (CajaIconContainer *container,
+ CajaIconLayoutMode mode);
+void caja_icon_container_set_label_position (CajaIconContainer *container,
+ CajaIconLabelPosition pos);
+void caja_icon_container_sort (CajaIconContainer *container);
+void caja_icon_container_freeze_icon_positions (CajaIconContainer *container);
+
+int caja_icon_container_get_max_layout_lines (CajaIconContainer *container);
+int caja_icon_container_get_max_layout_lines_for_pango (CajaIconContainer *container);
+
+void caja_icon_container_set_highlighted_for_clipboard (CajaIconContainer *container,
+ GList *clipboard_icon_data);
+
+/* operations on all icons */
+void caja_icon_container_unselect_all (CajaIconContainer *view);
+void caja_icon_container_select_all (CajaIconContainer *view);
+
+
+/* operations on the selection */
+GList * caja_icon_container_get_selection (CajaIconContainer *view);
+void caja_icon_container_invert_selection (CajaIconContainer *view);
+void caja_icon_container_set_selection (CajaIconContainer *view,
+ GList *selection);
+GArray * caja_icon_container_get_selected_icon_locations (CajaIconContainer *view);
+gboolean caja_icon_container_has_stretch_handles (CajaIconContainer *container);
+gboolean caja_icon_container_is_stretched (CajaIconContainer *container);
+void caja_icon_container_show_stretch_handles (CajaIconContainer *container);
+void caja_icon_container_unstretch (CajaIconContainer *container);
+void caja_icon_container_start_renaming_selected_item (CajaIconContainer *container,
+ gboolean select_all);
+
+/* options */
+CajaZoomLevel caja_icon_container_get_zoom_level (CajaIconContainer *view);
+void caja_icon_container_set_zoom_level (CajaIconContainer *view,
+ int new_zoom_level);
+void caja_icon_container_set_single_click_mode (CajaIconContainer *container,
+ gboolean single_click_mode);
+void caja_icon_container_enable_linger_selection (CajaIconContainer *view,
+ gboolean enable);
+gboolean caja_icon_container_get_is_fixed_size (CajaIconContainer *container);
+void caja_icon_container_set_is_fixed_size (CajaIconContainer *container,
+ gboolean is_fixed_size);
+gboolean caja_icon_container_get_is_desktop (CajaIconContainer *container);
+void caja_icon_container_set_is_desktop (CajaIconContainer *container,
+ gboolean is_desktop);
+void caja_icon_container_reset_scroll_region (CajaIconContainer *container);
+void caja_icon_container_set_font (CajaIconContainer *container,
+ const char *font);
+void caja_icon_container_set_font_size_table (CajaIconContainer *container,
+ const int font_size_table[CAJA_ZOOM_LEVEL_LARGEST + 1]);
+void caja_icon_container_set_margins (CajaIconContainer *container,
+ int left_margin,
+ int right_margin,
+ int top_margin,
+ int bottom_margin);
+void caja_icon_container_set_use_drop_shadows (CajaIconContainer *container,
+ gboolean use_drop_shadows);
+char* caja_icon_container_get_icon_description (CajaIconContainer *container,
+ CajaIconData *data);
+gboolean caja_icon_container_get_allow_moves (CajaIconContainer *container);
+void caja_icon_container_set_allow_moves (CajaIconContainer *container,
+ gboolean allow_moves);
+void caja_icon_container_set_forced_icon_size (CajaIconContainer *container,
+ int forced_icon_size);
+void caja_icon_container_set_all_columns_same_width (CajaIconContainer *container,
+ gboolean all_columns_same_width);
+
+gboolean caja_icon_container_is_layout_rtl (CajaIconContainer *container);
+gboolean caja_icon_container_is_layout_vertical (CajaIconContainer *container);
+
+gboolean caja_icon_container_get_store_layout_timestamps (CajaIconContainer *container);
+void caja_icon_container_set_store_layout_timestamps (CajaIconContainer *container,
+ gboolean store_layout);
+
+void caja_icon_container_widget_to_file_operation_position (CajaIconContainer *container,
+ GdkPoint *position);
+
+
+#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 /* CAJA_ICON_CONTAINER_H */
diff --git a/libcaja-private/caja-icon-dnd.c b/libcaja-private/caja-icon-dnd.c
new file mode 100644
index 00000000..b0e89942
--- /dev/null
+++ b/libcaja-private/caja-icon-dnd.c
@@ -0,0 +1,2123 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-icon-dnd.c - Drag & drop handling for the icon container widget.
+
+ Copyright (C) 1999, 2000 Free Software Foundation
+ Copyright (C) 2000 Eazel, Inc.
+
+ The Mate 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 Mate 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 Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Ettore Perazzoli <[email protected]>,
+ Darin Adler <[email protected]>,
+ Andy Hertzfeld <[email protected]>
+ Pavel Cisler <[email protected]>
+
+
+ XDS support: Benedikt Meurer <[email protected]> (adapted by Amos Brocco <[email protected]>)
+
+*/
+
+
+#include <config.h>
+#include <math.h>
+#include "caja-icon-dnd.h"
+
+#include "caja-debug-log.h"
+#include "caja-file-dnd.h"
+#include "caja-icon-private.h"
+#include "caja-link.h"
+#include "caja-metadata.h"
+#include <eel/eel-background.h>
+#include <eel/eel-gdk-pixbuf-extensions.h>
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-mate-extensions.h>
+#include <eel/eel-graphic-effects.h>
+#include <eel/eel-gtk-extensions.h>
+#include <eel/eel-gtk-macros.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 <eel/eel-canvas-rect-ellipse.h>
+#include <libcaja-private/caja-file-utilities.h>
+#include <libcaja-private/caja-file-changes-queue.h>
+#include <stdio.h>
+#include <string.h>
+
+static const GtkTargetEntry drag_types [] =
+{
+ { CAJA_ICON_DND_MATE_ICON_LIST_TYPE, 0, CAJA_ICON_DND_MATE_ICON_LIST },
+ { CAJA_ICON_DND_URI_LIST_TYPE, 0, CAJA_ICON_DND_URI_LIST },
+};
+
+static const GtkTargetEntry drop_types [] =
+{
+ { CAJA_ICON_DND_MATE_ICON_LIST_TYPE, 0, CAJA_ICON_DND_MATE_ICON_LIST },
+ /* prefer "_NETSCAPE_URL" over "text/uri-list" to satisfy web browsers. */
+ { CAJA_ICON_DND_NETSCAPE_URL_TYPE, 0, CAJA_ICON_DND_NETSCAPE_URL },
+ { CAJA_ICON_DND_URI_LIST_TYPE, 0, CAJA_ICON_DND_URI_LIST },
+ { CAJA_ICON_DND_COLOR_TYPE, 0, CAJA_ICON_DND_COLOR },
+ { CAJA_ICON_DND_BGIMAGE_TYPE, 0, CAJA_ICON_DND_BGIMAGE },
+ { CAJA_ICON_DND_KEYWORD_TYPE, 0, CAJA_ICON_DND_KEYWORD },
+ { CAJA_ICON_DND_RESET_BACKGROUND_TYPE, 0, CAJA_ICON_DND_RESET_BACKGROUND },
+ { CAJA_ICON_DND_XDNDDIRECTSAVE_TYPE, 0, CAJA_ICON_DND_XDNDDIRECTSAVE }, /* XDS Protocol Type */
+ { CAJA_ICON_DND_RAW_TYPE, 0, CAJA_ICON_DND_RAW },
+ /* Must be last: */
+ { CAJA_ICON_DND_ROOTWINDOW_DROP_TYPE, 0, CAJA_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 * caja_icon_container_find_drop_target (CajaIconContainer *container,
+ GdkDragContext *context,
+ int x, int y, gboolean *icon_hit,
+ gboolean rewrite_desktop);
+
+static EelCanvasItem *
+create_selection_shadow (CajaIconContainer *container,
+ GList *list)
+{
+ EelCanvasGroup *group;
+ EelCanvas *canvas;
+ GdkBitmap *stipple;
+ 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;
+ }
+
+ stipple = container->details->dnd_info->stipple;
+ g_return_val_if_fail (stipple != NULL, 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)
+ {
+ CajaDragSelectionItem *item;
+ int x1, y1, x2, y2;
+
+ item = p->data;
+
+ if (!item->got_icon_position)
+ {
+ continue;
+ }
+
+ x1 = item->icon_x;
+ y1 = item->icon_y;
+ x2 = x1 + item->icon_width;
+ y2 = y1 + item->icon_height;
+
+ if (x2 >= min_x && x1 <= max_x && y2 >= min_y && y1 <= max_y)
+ eel_canvas_item_new
+ (group,
+ eel_canvas_rect_get_type (),
+ "x1", (double) x1,
+ "y1", (double) y1,
+ "x2", (double) x2,
+ "y2", (double) y2,
+ "outline_color", "black",
+ "outline_stipple", stipple,
+ "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;
+ CajaDragEachSelectedItemDataGet iteratee;
+ gpointer iteratee_data;
+} IconGetDataBinderContext;
+
+static void
+canvas_rect_world_to_widget (EelCanvas *canvas,
+ EelDRect *world_rect,
+ EelIRect *widget_rect)
+{
+ EelDRect window_rect;
+
+ 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 (gtk_layout_get_hadjustment (GTK_LAYOUT (canvas)));
+ widget_rect->y0 = (int) window_rect.y0 - gtk_adjustment_get_value (gtk_layout_get_vadjustment (GTK_LAYOUT (canvas)));
+ widget_rect->x1 = (int) window_rect.x1 - gtk_adjustment_get_value (gtk_layout_get_hadjustment (GTK_LAYOUT (canvas)));
+ widget_rect->y1 = (int) window_rect.y1 - gtk_adjustment_get_value (gtk_layout_get_vadjustment (GTK_LAYOUT (canvas)));
+}
+
+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_layout_get_hadjustment (GTK_LAYOUT (canvas))),
+ widget_y + gtk_adjustment_get_value (gtk_layout_get_vadjustment (GTK_LAYOUT (canvas))),
+ world_x, world_y);
+}
+
+static gboolean
+icon_get_data_binder (CajaIcon *icon, gpointer data)
+{
+ IconGetDataBinderContext *context;
+ EelDRect world_rect;
+ EelIRect widget_rect;
+ char *uri;
+ CajaIconContainer *container;
+
+ context = (IconGetDataBinderContext *)data;
+
+ g_assert (CAJA_IS_ICON_CONTAINER (context->iterator_context));
+
+ container = CAJA_ICON_CONTAINER (context->iterator_context);
+
+ world_rect = caja_icon_canvas_item_get_icon_rectangle (icon->item);
+
+ canvas_rect_world_to_widget (EEL_CANVAS (container), &world_rect, &widget_rect);
+
+ uri = caja_icon_container_get_icon_uri (container, icon);
+ if (uri == NULL)
+ {
+ g_warning ("no URI for one of the iterated icons");
+ 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);
+
+ return TRUE;
+}
+
+/* Iterate over each selected icon in a CajaIconContainer,
+ * calling each_function on each.
+ */
+static void
+caja_icon_container_each_selected_icon (CajaIconContainer *container,
+ gboolean (*each_function) (CajaIcon *, gpointer), gpointer data)
+{
+ GList *p;
+ CajaIcon *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 caja_icon_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 (CajaDragEachSelectedItemDataGet iteratee,
+ gpointer iterator_context, gpointer data)
+{
+ IconGetDataBinderContext context;
+ CajaIconContainer *container;
+
+ g_assert (CAJA_IS_ICON_CONTAINER (iterator_context));
+ container = CAJA_ICON_CONTAINER (iterator_context);
+
+ context.iterator_context = iterator_context;
+ context.iteratee = iteratee;
+ context.iteratee_data = data;
+ caja_icon_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)
+{
+ g_assert (widget != NULL);
+ g_assert (CAJA_IS_ICON_CONTAINER (widget));
+ g_return_if_fail (context != NULL);
+
+ /* Call common function from caja-drag that set's up
+ * the selection data in the right format. Pass it means to
+ * iterate all the selected icons.
+ */
+ caja_drag_drag_data_get (widget, context, selection_data,
+ info, time, widget, each_icon_get_data_binder);
+}
+
+
+/* Target-side handling of the drag. */
+
+static void
+caja_icon_container_position_shadow (CajaIconContainer *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
+caja_icon_container_dropped_icon_feedback (GtkWidget *widget,
+ GtkSelectionData *data,
+ int x, int y)
+{
+ CajaIconContainer *container;
+ CajaIconDndInfo *dnd_info;
+
+ container = CAJA_ICON_CONTAINER (widget);
+ dnd_info = container->details->dnd_info;
+
+ /* Delete old selection list. */
+ caja_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? */
+ gtk_object_destroy (GTK_OBJECT (dnd_info->shadow));
+ }
+
+ /* Build the selection list and the shadow. */
+ dnd_info->drag_info.selection_list = caja_drag_build_selection_list (data);
+ dnd_info->shadow = create_selection_shadow (container, dnd_info->drag_info.selection_list);
+ caja_icon_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 (CAJA_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)
+ {
+ caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
+ "Invalid filename provided by XDS drag site");
+ g_free (prop_text);
+ return NULL;
+ }
+
+ return prop_text;
+}
+
+static void
+set_direct_save_uri (GtkWidget *widget, GdkDragContext *context, CajaDragInfo *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 = CAJA_ICON_DND_XDNDDIRECTSAVE;
+
+ uri = NULL;
+
+ filename = get_direct_save_filename (context);
+ drop_target = caja_icon_container_find_drop_target (CAJA_ICON_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_DRAWABLE (gdk_drag_context_get_source_window (context)),
+ gdk_atom_intern (CAJA_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, CAJA_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, CAJA_ICON_DND_TEXT);
+ }
+
+ if (caja_icon_container_get_is_desktop (CAJA_ICON_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;
+ CajaDragInfo *drag_info;
+ gboolean found;
+
+ drag_info = &(CAJA_ICON_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 == CAJA_ICON_DND_ROOTWINDOW_DROP ||
+ info == CAJA_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 == CAJA_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
+caja_icon_container_ensure_drag_data (CajaIconContainer *container,
+ GdkDragContext *context,
+ guint32 time)
+{
+ CajaIconDndInfo *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)
+{
+ CajaIconContainer *container;
+ CajaIconDndInfo *dnd_info;
+
+ container = CAJA_ICON_CONTAINER (widget);
+ dnd_info = container->details->dnd_info;
+
+ caja_drag_destroy_selection_list (dnd_info->drag_info.selection_list);
+ dnd_info->drag_info.selection_list = NULL;
+}
+
+static CajaIcon *
+caja_icon_container_item_at (CajaIconContainer *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)
+ {
+ CajaIcon *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 (caja_icon_canvas_item_hit_test_rectangle (icon->item, canvas_point))
+ {
+ return icon;
+ }
+ }
+
+ return NULL;
+}
+
+static char *
+get_container_uri (CajaIconContainer *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
+caja_icon_container_selection_items_local (CajaIconContainer *container,
+ GList *items)
+{
+ char *container_uri_string;
+ gboolean result;
+
+ /* must have at least one item */
+ g_assert (items);
+
+ result = FALSE;
+
+ /* get the URI associated with the container */
+ container_uri_string = get_container_uri (container);
+
+ if (eel_uri_is_desktop (container_uri_string))
+ {
+ result = caja_drag_items_on_desktop (items);
+ }
+ else
+ {
+ result = caja_drag_items_local (container_uri_string, items);
+ }
+ g_free (container_uri_string);
+
+ return result;
+}
+
+static GdkDragAction
+get_background_drag_action (CajaIconContainer *container,
+ GdkDragAction action)
+{
+ /* FIXME: This function is very FMDirectoryView specific, and
+ * should be moved out of caja-icon-dnd.c */
+ GdkDragAction valid_actions;
+
+ if (action == GDK_ACTION_ASK)
+ {
+ valid_actions = CAJA_DND_ACTION_SET_AS_FOLDER_BACKGROUND;
+ if (!eel_background_is_desktop (eel_get_widget_background (GTK_WIDGET (container))))
+ {
+ valid_actions |= CAJA_DND_ACTION_SET_AS_GLOBAL_BACKGROUND;
+ }
+
+ action = caja_drag_drop_background_ask
+ (GTK_WIDGET (container), valid_actions);
+ }
+
+ return action;
+}
+
+static void
+receive_dropped_color (CajaIconContainer *container,
+ int x, int y,
+ GdkDragAction action,
+ GtkSelectionData *data)
+{
+ action = get_background_drag_action (container, action);
+
+ if (action > 0)
+ {
+ char *uri;
+
+ uri = get_container_uri (container);
+ caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
+ "dropped color on icon container displaying %s", uri);
+ g_free (uri);
+
+ eel_background_receive_dropped_color
+ (eel_get_widget_background (GTK_WIDGET (container)),
+ GTK_WIDGET (container),
+ action, x, y, data);
+ }
+}
+
+/* handle dropped tile images */
+static void
+receive_dropped_tile_image (CajaIconContainer *container, GdkDragAction action, GtkSelectionData *data)
+{
+ g_assert (data != NULL);
+
+ action = get_background_drag_action (container, action);
+
+ if (action > 0)
+ {
+ char *uri;
+
+ uri = get_container_uri (container);
+ caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
+ "dropped tile image on icon container displaying %s", uri);
+ g_free (uri);
+
+ eel_background_receive_dropped_background_image
+ (eel_get_widget_background (GTK_WIDGET (container)),
+ action,
+ gtk_selection_data_get_data (data));
+ }
+}
+
+/* handle dropped keywords */
+static void
+receive_dropped_keyword (CajaIconContainer *container, const char *keyword, int x, int y)
+{
+ char *uri;
+ double world_x, world_y;
+
+ CajaIcon *drop_target_icon;
+ CajaFile *file;
+
+ g_assert (keyword != NULL);
+
+ /* find the item we hit with our drop, if any */
+ canvas_widget_to_world (EEL_CANVAS (container), x, y, &world_x, &world_y);
+ drop_target_icon = caja_icon_container_item_at (container, world_x, world_y);
+ if (drop_target_icon == NULL)
+ {
+ return;
+ }
+
+ /* FIXME bugzilla.gnome.org 42485:
+ * This does not belong in the icon code.
+ * It has to be in the file manager.
+ * The icon code has no right to deal with the file directly.
+ * But luckily there's no issue of not getting a file object,
+ * so we don't have to worry about async. issues here.
+ */
+ uri = caja_icon_container_get_icon_uri (container, drop_target_icon);
+
+ caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
+ "dropped emblem '%s' on icon container URI: %s",
+ keyword, uri);
+
+ file = caja_file_get_by_uri (uri);
+ g_free (uri);
+
+ caja_drag_file_receive_dropped_keyword (file, keyword);
+
+ caja_file_unref (file);
+ caja_icon_container_update_icon (container, drop_target_icon);
+}
+
+/* handle dropped url */
+static void
+receive_dropped_netscape_url (CajaIconContainer *container, const char *encoded_url, GdkDragContext *context, int x, int y)
+{
+ char *drop_target;
+
+ if (encoded_url == NULL)
+ {
+ return;
+ }
+
+ drop_target = caja_icon_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 (CajaIconContainer *container, const char *uri_list, GdkDragContext *context, int x, int y)
+{
+ char *drop_target;
+
+ if (uri_list == NULL)
+ {
+ return;
+ }
+
+ drop_target = caja_icon_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 (CajaIconContainer *container, const char *text, GdkDragContext *context, int x, int y)
+{
+ char *drop_target;
+
+ if (text == NULL)
+ {
+ return;
+ }
+
+ drop_target = caja_icon_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 (CajaIconContainer *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 = caja_icon_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)
+{
+ CajaIconContainer *container;
+ GtkWidget *widget;
+ float x_scroll_delta, y_scroll_delta;
+ GdkRectangle exposed_area;
+ GtkAllocation allocation;
+
+ g_assert (CAJA_IS_ICON_CONTAINER (data));
+ widget = GTK_WIDGET (data);
+ container = CAJA_ICON_CONTAINER (widget);
+
+ if (container->details->dnd_info->drag_info.waiting_to_autoscroll
+ && container->details->dnd_info->drag_info.start_auto_scroll_in > eel_get_system_time())
+ {
+ /* not yet */
+ return TRUE;
+ }
+
+ container->details->dnd_info->drag_info.waiting_to_autoscroll = FALSE;
+
+ caja_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 (!caja_icon_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 (CajaIconContainer *container)
+{
+ caja_drag_autoscroll_start (&container->details->dnd_info->drag_info,
+ GTK_WIDGET (container),
+ auto_scroll_timeout_callback,
+ container);
+}
+
+static void
+stop_auto_scroll (CajaIconContainer *container)
+{
+ caja_drag_autoscroll_stop (&container->details->dnd_info->drag_info);
+}
+
+static gboolean
+confirm_switch_to_manual_layout (CajaIconContainer *container)
+{
+#if 0
+ const char *message;
+ const char *detail;
+ GtkDialog *dialog;
+ int response;
+
+ /* FIXME bugzilla.gnome.org 40915: Use of the word "directory"
+ * makes this FMIconView specific. Move these messages into
+ * FMIconView so CajaIconContainer can be used for things
+ * that are not directories?
+ */
+ if (caja_icon_container_has_stored_icon_positions (container))
+ {
+ if (eel_g_list_exactly_one_item (container->details->dnd_info->drag_info.selection_list))
+ {
+ message = no_translate("Do you want to switch to manual layout and leave this item where you dropped it? "
+ "This will clobber the stored manual layout.");
+ detail = no_translate("This folder uses automatic layout.");
+ }
+ else
+ {
+ message = no_translate("Do you want to switch to manual layout and leave these items where you dropped them? "
+ "This will clobber the stored manual layout.");
+ detail = no_translate("This folder uses automatic layout.");
+ }
+ }
+ else
+ {
+ if (eel_g_list_exactly_one_item (container->details->dnd_info->drag_info.selection_list))
+ {
+ message = no_translate("Do you want to switch to manual layout and leave this item where you dropped it?");
+ detail = no_translate("This folder uses automatic layout.");
+ }
+ else
+ {
+ message = no_translate("Do you want to switch to manual layout and leave these items where you dropped them?");
+ detail = no_translate("This folder uses automatic layout.");
+
+ }
+ }
+
+ dialog = eel_show_yes_no_dialog (message, detail, _("Switch to Manual Layout?"),
+ GTK_STOCK_CANCEL,
+ GTK_WINDOW (gtk_widget_get_toplevel(GTK_WIDGET(container))));
+
+ response = gtk_dialog_run (dialog);
+ gtk_object_destroy (GTK_OBJECT (dialog));
+
+ return response == GTK_RESPONSE_YES;
+#else
+ return FALSE;
+#endif
+}
+
+static void
+handle_local_move (CajaIconContainer *container,
+ double world_x, double world_y)
+{
+ GList *moved_icons, *p;
+ CajaDragSelectionItem *item;
+ CajaIcon *icon;
+ CajaFile *file;
+ char screen_string[32];
+ GdkScreen *screen;
+ time_t now;
+
+ if (container->details->auto_layout)
+ {
+ if (!confirm_switch_to_manual_layout (container))
+ {
+ return;
+ }
+ caja_icon_container_freeze_icon_positions (container);
+ }
+
+ 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 = caja_icon_container_get_icon_by_uri
+ (container, item->uri);
+
+ if (icon == NULL)
+ {
+ /* probably dragged from another screen. Add it to
+ * this screen
+ */
+
+ file = caja_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));
+ caja_file_set_metadata (file,
+ CAJA_METADATA_KEY_SCREEN,
+ NULL, screen_string);
+ caja_file_set_time_metadata (file,
+ CAJA_METADATA_KEY_ICON_POSITION_TIMESTAMP, now);
+
+ caja_icon_container_add (container, CAJA_ICON_CONTAINER_ICON_DATA (file));
+
+ icon = caja_icon_container_get_icon_by_uri
+ (container, item->uri);
+ }
+
+ if (item->got_icon_position)
+ {
+ caja_icon_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);
+ }
+ caja_icon_container_select_list_unselect_others
+ (container, moved_icons);
+ /* Might have been moved in a way that requires adjusting scroll region. */
+ caja_icon_container_update_scroll_region (container);
+ g_list_free (moved_icons);
+}
+
+static void
+handle_nonlocal_move (CajaIconContainer *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, ((CajaDragSelectionItem *)p->data)->uri);
+ }
+ source_uris = g_list_reverse (source_uris);
+
+ is_rtl = caja_icon_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 = ((CajaDragSelectionItem *)p->data)->icon_x;
+ if (is_rtl)
+ item_x = -item_x - ((CajaDragSelectionItem *)p->data)->icon_width;
+ g_array_index (source_item_locations, GdkPoint, index).x = item_x;
+ g_array_index (source_item_locations, GdkPoint, index).y =
+ ((CajaDragSelectionItem *)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 = caja_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 *
+caja_icon_container_find_drop_target (CajaIconContainer *container,
+ GdkDragContext *context,
+ int x, int y,
+ gboolean *icon_hit,
+ gboolean rewrite_desktop)
+{
+ CajaIcon *drop_target_icon;
+ double world_x, world_y;
+ CajaFile *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 icon 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 = caja_icon_container_item_at (container, world_x, world_y);
+ if (drop_target_icon != NULL)
+ {
+ icon_uri = caja_icon_container_get_icon_uri (container, drop_target_icon);
+ if (icon_uri != NULL)
+ {
+ file = caja_file_get_by_uri (icon_uri);
+
+ if (!caja_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);
+ caja_file_unref (file);
+ }
+ }
+
+ if (drop_target_icon == NULL)
+ {
+ if (icon_hit)
+ {
+ *icon_hit = FALSE;
+ }
+
+ container_uri = get_container_uri (container);
+
+ if (rewrite_desktop &&
+ container_uri != NULL &&
+ eel_uri_is_desktop (container_uri))
+ {
+ g_free (container_uri);
+ container_uri = caja_get_desktop_directory_uri ();
+ }
+
+ return container_uri;
+ }
+
+ if (icon_hit)
+ {
+ *icon_hit = TRUE;
+ }
+ return caja_icon_container_get_icon_drop_target_uri (container, drop_target_icon);
+}
+
+static gboolean
+selection_is_image_file (GList *selection_list)
+{
+ const char *mime_type;
+ CajaDragSelectionItem *selected_item;
+ gboolean result;
+ GFile *location;
+ GFileInfo *info;
+
+ /* Make sure only one item is selected */
+ if (selection_list == NULL ||
+ selection_list->next != NULL)
+ {
+ return FALSE;
+ }
+
+ selected_item = selection_list->data;
+
+ mime_type = NULL;
+
+ location = g_file_new_for_uri (selected_item->uri);
+ info = g_file_query_info (location,
+ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
+ 0, NULL, NULL);
+ if (info)
+ {
+ mime_type = g_file_info_get_content_type (info);
+ }
+
+ result = eel_istr_has_prefix (mime_type, "image/");
+
+ if (info)
+ {
+ g_object_unref (info);
+ }
+ g_object_unref (location);
+
+ return result;
+}
+
+
+static void
+caja_icon_container_receive_dropped_icons (CajaIconContainer *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;
+ CajaDragSelectionItem *selected_item;
+
+ 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 (caja_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;
+
+ if (selection_is_image_file (container->details->dnd_info->drag_info.selection_list))
+ {
+ action |= CAJA_DND_ACTION_SET_AS_BACKGROUND;
+ }
+ }
+ real_action = caja_drag_drop_action_ask
+ (GTK_WIDGET (container), action);
+ }
+
+ if (real_action == (GdkDragAction) CAJA_DND_ACTION_SET_AS_BACKGROUND)
+ {
+ selected_item = container->details->dnd_info->drag_info.selection_list->data;
+ eel_background_receive_dropped_background_image
+ (eel_get_widget_background (GTK_WIDGET (container)),
+ real_action,
+ selected_item->uri);
+ return;
+ }
+
+ if (real_action > 0)
+ {
+ eel_canvas_window_to_world (EEL_CANVAS (container),
+ x + gtk_adjustment_get_value (gtk_layout_get_hadjustment (GTK_LAYOUT (container))),
+ y + gtk_adjustment_get_value (gtk_layout_get_vadjustment (GTK_LAYOUT (container))),
+ &world_x, &world_y);
+
+ drop_target = caja_icon_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 icon positions if the move ended up in
+ * the item's parent container
+ */
+ local_move_only = caja_icon_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);
+ caja_drag_destroy_selection_list (container->details->dnd_info->drag_info.selection_list);
+ container->details->dnd_info->drag_info.selection_list = NULL;
+}
+
+static void
+caja_icon_container_get_drop_action (CajaIconContainer *container,
+ GdkDragContext *context,
+ int x, int y,
+ int *action)
+{
+ char *drop_target;
+ gboolean icon_hit;
+ CajaIcon *icon;
+ 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 icon */
+ canvas_widget_to_world (EEL_CANVAS (container), x, y, &world_x, &world_y);
+
+ icon = caja_icon_container_item_at (container, world_x, world_y);
+
+ *action = 0;
+
+ /* case out on the type of object being dragged */
+ switch (container->details->dnd_info->drag_info.data_type)
+ {
+ case CAJA_ICON_DND_MATE_ICON_LIST:
+ if (container->details->dnd_info->drag_info.selection_list == NULL)
+ {
+ return;
+ }
+ drop_target = caja_icon_container_find_drop_target (container,
+ context, x, y, &icon_hit, FALSE);
+ if (!drop_target)
+ {
+ return;
+ }
+ caja_drag_default_drop_action_for_icons (context, drop_target,
+ container->details->dnd_info->drag_info.selection_list,
+ action);
+ g_free (drop_target);
+ break;
+ case CAJA_ICON_DND_URI_LIST:
+ drop_target = caja_icon_container_find_drop_target (container,
+ context, x, y, &icon_hit, FALSE);
+ *action = caja_drag_default_drop_action_for_uri_list (context, drop_target);
+
+ g_free (drop_target);
+ break;
+
+ /* handle emblems by setting the action if we're over an object */
+ case CAJA_ICON_DND_KEYWORD:
+ if (icon != NULL)
+ {
+ *action = gdk_drag_context_get_suggested_action (context);
+ }
+ break;
+
+ case CAJA_ICON_DND_NETSCAPE_URL:
+ *action = caja_drag_default_drop_action_for_netscape_url (context);
+ break;
+
+ case CAJA_ICON_DND_COLOR:
+ case CAJA_ICON_DND_BGIMAGE:
+ case CAJA_ICON_DND_RESET_BACKGROUND:
+ case CAJA_ICON_DND_ROOTWINDOW_DROP:
+ *action = gdk_drag_context_get_suggested_action (context);
+ break;
+
+ case CAJA_ICON_DND_TEXT:
+ case CAJA_ICON_DND_XDNDDIRECTSAVE:
+ case CAJA_ICON_DND_RAW:
+ *action = GDK_ACTION_COPY;
+ break;
+ }
+}
+
+static void
+set_drop_target (CajaIconContainer *container,
+ CajaIcon *icon)
+{
+ CajaIcon *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;
+ caja_icon_container_update_icon (container, old_icon);
+ caja_icon_container_update_icon (container, icon);
+}
+
+static void
+caja_icon_dnd_update_drop_target (CajaIconContainer *container,
+ GdkDragContext *context,
+ int x, int y)
+{
+ CajaIcon *icon;
+ CajaFile *file;
+ double world_x, world_y;
+ char *uri;
+
+ g_assert (CAJA_IS_ICON_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 = caja_icon_container_item_at (container, world_x, world_y);
+
+ /* FIXME bugzilla.gnome.org 42485:
+ * These "can_accept_items" tests need to be done by
+ * the icon view, not here. This file is not supposed to know
+ * that the target is a file.
+ */
+
+ /* Find if target icon accepts our drop. */
+ if (icon != NULL && (container->details->dnd_info->drag_info.data_type != CAJA_ICON_DND_KEYWORD))
+ {
+ uri = caja_icon_container_get_icon_uri (container, icon);
+ file = caja_file_get_by_uri (uri);
+ g_free (uri);
+
+ if (!caja_drag_can_accept_info (file,
+ container->details->dnd_info->drag_info.data_type,
+ container->details->dnd_info->drag_info.selection_list))
+ {
+ icon = NULL;
+ }
+
+ caja_file_unref (file);
+ }
+
+ set_drop_target (container, icon);
+}
+
+static void
+caja_icon_container_free_drag_data (CajaIconContainer *container)
+{
+ CajaIconDndInfo *dnd_info;
+
+ dnd_info = container->details->dnd_info;
+
+ dnd_info->drag_info.got_drop_data_type = FALSE;
+
+ if (dnd_info->shadow != NULL)
+ {
+ gtk_object_destroy (GTK_OBJECT (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;
+ }
+}
+
+static void
+drag_leave_callback (GtkWidget *widget,
+ GdkDragContext *context,
+ guint32 time,
+ gpointer data)
+{
+ CajaIconDndInfo *dnd_info;
+
+ dnd_info = CAJA_ICON_CONTAINER (widget)->details->dnd_info;
+
+ if (dnd_info->shadow != NULL)
+ eel_canvas_item_hide (dnd_info->shadow);
+
+ stop_dnd_highlight (widget);
+
+ set_drop_target (CAJA_ICON_CONTAINER (widget), NULL);
+ stop_auto_scroll (CAJA_ICON_CONTAINER (widget));
+ caja_icon_container_free_drag_data(CAJA_ICON_CONTAINER (widget));
+}
+
+static void
+drag_begin_callback (GtkWidget *widget,
+ GdkDragContext *context,
+ gpointer data)
+{
+ CajaIconContainer *container;
+ GdkScreen *screen;
+ GdkColormap *colormap;
+ GdkPixmap *pixmap;
+ GdkBitmap *mask;
+ double x1, y1, x2, y2, winx, winy;
+ int x_offset, y_offset;
+ int start_x, start_y;
+ gboolean use_mask;
+
+ container = CAJA_ICON_CONTAINER (widget);
+
+ screen = gtk_widget_get_screen (widget);
+ colormap = NULL;
+ if (gdk_screen_is_composited (screen))
+ {
+ colormap = gdk_screen_get_rgba_colormap (screen);
+ if (colormap != NULL)
+ {
+ use_mask = FALSE;
+ }
+ }
+
+ /* Fall back on using the same colormap as the widget */
+ if (colormap == NULL)
+ {
+ colormap = gtk_widget_get_colormap (widget);
+ use_mask = TRUE;
+ }
+
+ start_x = container->details->dnd_info->drag_info.start_x + gtk_adjustment_get_value (gtk_layout_get_hadjustment (GTK_LAYOUT (container)));
+ start_y = container->details->dnd_info->drag_info.start_y + gtk_adjustment_get_value (gtk_layout_get_vadjustment (GTK_LAYOUT (container)));
+
+ /* create a pixmap and mask to drag with */
+ pixmap = caja_icon_canvas_item_get_image (container->details->drag_icon->item, &mask, colormap);
+
+ /* we want to drag semi-transparent pixbufs, but X is too slow dealing with
+ stippled masks, so we had to remove the code; this comment is left as a memorial
+ to it, with the hope that we get it back someday as X Windows improves */
+
+ /* 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;
+
+ if (!use_mask && pixmap != NULL)
+ {
+ cairo_t *cr;
+
+ /* If composite works, make the icons partially transparent */
+ cr = gdk_cairo_create (pixmap);
+ cairo_set_operator (cr, CAIRO_OPERATOR_DEST_OUT);
+ cairo_set_source_rgba(cr, 1,0,0,0.35);
+ cairo_paint (cr);
+ cairo_destroy (cr);
+ }
+
+ gtk_drag_set_icon_pixmap (context,
+ colormap,
+ pixmap, (use_mask ? mask : NULL),
+ x_offset, y_offset);
+}
+
+void
+caja_icon_dnd_begin_drag (CajaIconContainer *container,
+ GdkDragAction actions,
+ int button,
+ GdkEventMotion *event,
+ int start_x,
+ int start_y)
+{
+ CajaIconDndInfo *dnd_info;
+ GdkDragContext *context;
+
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+ g_return_if_fail (event != NULL);
+
+ dnd_info = container->details->dnd_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_layout_get_hadjustment (GTK_LAYOUT (container)));
+ dnd_info->drag_info.start_y = start_y - gtk_adjustment_get_value (gtk_layout_get_vadjustment (GTK_LAYOUT (container)));
+
+ /* start the drag */
+ context = gtk_drag_begin (GTK_WIDGET (container),
+ dnd_info->drag_info.target_list,
+ actions,
+ button,
+ (GdkEvent *) event);
+}
+
+static gboolean
+drag_highlight_expose (GtkWidget *widget,
+ GdkEventExpose *event,
+ gpointer data)
+{
+ gint x, y, width, height;
+ GdkWindow *window;
+
+ x = gtk_adjustment_get_value (gtk_layout_get_hadjustment (GTK_LAYOUT (widget)));
+ y = gtk_adjustment_get_value (gtk_layout_get_vadjustment (GTK_LAYOUT (widget)));
+
+#if GTK_CHECK_VERSION(3, 0, 0)
+ width = gdk_window_get_width(GDK_WINDOW(gtk_widget_get_window(widget)));
+ height = gdk_window_get_height(GDK_WINDOW(gtk_widget_get_window(widget)));
+#else
+ gdk_drawable_get_size(gtk_widget_get_window(widget), &width, &height);
+#endif
+
+ window = gtk_layout_get_bin_window (GTK_LAYOUT (widget));
+
+ gtk_paint_shadow (gtk_widget_get_style (widget), window,
+ GTK_STATE_NORMAL, GTK_SHADOW_OUT,
+ NULL, widget, "dnd",
+ x, y, width, height);
+
+ gdk_draw_rectangle (window,
+ (gtk_widget_get_style(widget))->black_gc,
+ FALSE,
+ x, y, width - 1, height - 1);
+
+ return FALSE;
+}
+
+/* Queue a redraw of the dnd highlight rect */
+static void
+dnd_highlight_queue_redraw (GtkWidget *widget)
+{
+ CajaIconDndInfo *dnd_info;
+ int width, height;
+ GtkAllocation allocation;
+
+ dnd_info = CAJA_ICON_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)
+{
+ CajaIconDndInfo *dnd_info;
+ GtkWidget *toplevel;
+
+ dnd_info = CAJA_ICON_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, "expose_event",
+ G_CALLBACK (drag_highlight_expose),
+ NULL);
+ dnd_highlight_queue_redraw (widget);
+ }
+}
+
+static void
+stop_dnd_highlight (GtkWidget *widget)
+{
+ CajaIconDndInfo *dnd_info;
+
+ dnd_info = CAJA_ICON_CONTAINER (widget)->details->dnd_info;
+
+ if (dnd_info->highlighted)
+ {
+ g_signal_handlers_disconnect_by_func (widget,
+ drag_highlight_expose,
+ NULL);
+ dnd_highlight_queue_redraw (widget);
+ dnd_info->highlighted = FALSE;
+ }
+}
+
+static gboolean
+drag_motion_callback (GtkWidget *widget,
+ GdkDragContext *context,
+ int x, int y,
+ guint32 time)
+{
+ int action;
+
+ caja_icon_container_ensure_drag_data (CAJA_ICON_CONTAINER (widget), context, time);
+ caja_icon_container_position_shadow (CAJA_ICON_CONTAINER (widget), x, y);
+ caja_icon_dnd_update_drop_target (CAJA_ICON_CONTAINER (widget), context, x, y);
+ set_up_auto_scroll_if_needed (CAJA_ICON_CONTAINER (widget));
+ /* Find out what the drop actions are based on our drag selection and
+ * the drop target.
+ */
+ action = 0;
+ caja_icon_container_get_drop_action (CAJA_ICON_CONTAINER (widget), context, x, y,
+ &action);
+ if (action != 0)
+ {
+ start_dnd_highlight (widget);
+ }
+
+ 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)
+{
+ CajaIconDndInfo *dnd_info;
+
+ dnd_info = CAJA_ICON_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
+caja_icon_dnd_end_drag (CajaIconContainer *container)
+{
+ CajaIconDndInfo *dnd_info;
+
+ g_return_if_fail (CAJA_IS_ICON_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)
+{
+ CajaDragInfo *drag_info;
+ EelBackground *background;
+ char *tmp;
+ const char *tmp_raw;
+ int length;
+ gboolean success;
+
+ drag_info = &(CAJA_ICON_CONTAINER (widget)->details->dnd_info->drag_info);
+
+ drag_info->got_drop_data_type = TRUE;
+ drag_info->data_type = info;
+
+ switch (info)
+ {
+ case CAJA_ICON_DND_MATE_ICON_LIST:
+ caja_icon_container_dropped_icon_feedback (widget, data, x, y);
+ break;
+ case CAJA_ICON_DND_COLOR:
+ case CAJA_ICON_DND_BGIMAGE:
+ case CAJA_ICON_DND_KEYWORD:
+ case CAJA_ICON_DND_URI_LIST:
+ case CAJA_ICON_DND_TEXT:
+ case CAJA_ICON_DND_RESET_BACKGROUND:
+ case CAJA_ICON_DND_XDNDDIRECTSAVE:
+ case CAJA_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 CAJA_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 CAJA_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 CAJA_ICON_DND_MATE_ICON_LIST:
+ caja_icon_container_receive_dropped_icons
+ (CAJA_ICON_CONTAINER (widget),
+ context, x, y);
+ break;
+ case CAJA_ICON_DND_COLOR:
+ receive_dropped_color (CAJA_ICON_CONTAINER (widget),
+ x, y,
+ gdk_drag_context_get_selected_action (context),
+ data);
+ success = TRUE;
+ break;
+ case CAJA_ICON_DND_BGIMAGE:
+ receive_dropped_tile_image
+ (CAJA_ICON_CONTAINER (widget),
+ gdk_drag_context_get_selected_action (context),
+ data);
+ break;
+ case CAJA_ICON_DND_KEYWORD:
+ receive_dropped_keyword
+ (CAJA_ICON_CONTAINER (widget),
+ (char *) gtk_selection_data_get_data (data), x, y);
+ break;
+ case CAJA_ICON_DND_NETSCAPE_URL:
+ receive_dropped_netscape_url
+ (CAJA_ICON_CONTAINER (widget),
+ (char *) gtk_selection_data_get_data (data), context, x, y);
+ success = TRUE;
+ break;
+ case CAJA_ICON_DND_URI_LIST:
+ receive_dropped_uri_list
+ (CAJA_ICON_CONTAINER (widget),
+ (char *) gtk_selection_data_get_data (data), context, x, y);
+ success = TRUE;
+ break;
+ case CAJA_ICON_DND_TEXT:
+ tmp = gtk_selection_data_get_text (data);
+ receive_dropped_text
+ (CAJA_ICON_CONTAINER (widget),
+ (char *) tmp, context, x, y);
+ success = TRUE;
+ g_free (tmp);
+ break;
+ case CAJA_ICON_DND_RAW:
+ length = gtk_selection_data_get_length (data);
+ tmp_raw = gtk_selection_data_get_data (data);
+ receive_dropped_raw
+ (CAJA_ICON_CONTAINER (widget),
+ tmp_raw, length, drag_info->direct_save_uri,
+ context, x, y);
+ success = TRUE;
+ break;
+ case CAJA_ICON_DND_RESET_BACKGROUND:
+ background = eel_get_widget_background (widget);
+ if (background != NULL)
+ {
+ eel_background_reset (background);
+ }
+ gtk_drag_finish (context, FALSE, FALSE, time);
+ break;
+ case CAJA_ICON_DND_ROOTWINDOW_DROP:
+ /* Do nothing, everything is done by the sender */
+ break;
+ case CAJA_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 (CAJA_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);
+
+ caja_file_changes_queue_file_added (location);
+ p.x = x;
+ p.y = y;
+ caja_file_changes_queue_schedule_position_set (
+ location,
+ p,
+ gdk_screen_get_number (
+ gtk_widget_get_screen (widget)));
+ g_object_unref (location);
+ caja_file_changes_consume_changes (TRUE);
+ success = TRUE;
+ }
+ break;
+ } /* CAJA_ICON_DND_XDNDDIRECTSAVE */
+ }
+ gtk_drag_finish (context, success, FALSE, time);
+
+ caja_icon_container_free_drag_data (CAJA_ICON_CONTAINER (widget));
+
+ set_drop_target (CAJA_ICON_CONTAINER (widget), NULL);
+
+ /* reinitialise it for the next dnd */
+ drag_info->drop_occured = FALSE;
+ }
+
+}
+
+void
+caja_icon_dnd_set_stipple (CajaIconContainer *container,
+ GdkBitmap *stipple)
+{
+ if (stipple != NULL)
+ {
+ g_object_ref (stipple);
+ }
+
+ if (container->details->dnd_info->stipple != NULL)
+ {
+ g_object_unref (container->details->dnd_info->stipple);
+ }
+
+ container->details->dnd_info->stipple = stipple;
+}
+
+void
+caja_icon_dnd_init (CajaIconContainer *container,
+ GdkBitmap *stipple)
+{
+ GtkTargetList *targets;
+ int n_elements;
+
+ g_return_if_fail (container != NULL);
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+
+
+ container->details->dnd_info = g_new0 (CajaIconDndInfo, 1);
+ caja_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 (!caja_icon_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, CAJA_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);
+
+ if (stipple != NULL)
+ {
+ container->details->dnd_info->stipple = g_object_ref (stipple);
+ }
+}
+
+void
+caja_icon_dnd_fini (CajaIconContainer *container)
+{
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+
+ if (container->details->dnd_info != NULL)
+ {
+ stop_auto_scroll (container);
+
+ if (container->details->dnd_info->stipple != NULL)
+ {
+ g_object_unref (container->details->dnd_info->stipple);
+ }
+
+ caja_drag_finalize (&container->details->dnd_info->drag_info);
+ container->details->dnd_info = NULL;
+ }
+}
diff --git a/libcaja-private/caja-icon-dnd.h b/libcaja-private/caja-icon-dnd.h
new file mode 100644
index 00000000..e7fcc615
--- /dev/null
+++ b/libcaja-private/caja-icon-dnd.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-icon-dnd.h - Drag & drop handling for the icon container widget.
+
+ Copyright (C) 1999, 2000 Free Software Foundation
+ Copyright (C) 2000 Eazel, Inc.
+
+ The Mate 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 Mate 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 Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Ettore Perazzoli <[email protected]>,
+ Darin Adler <[email protected]>,
+ Andy Hertzfeld <[email protected]>
+*/
+
+#ifndef CAJA_ICON_DND_H
+#define CAJA_ICON_DND_H
+
+#include <libcaja-private/caja-icon-container.h>
+#include <libcaja-private/caja-dnd.h>
+
+/* DnD-related information. */
+typedef struct
+{
+ /* inherited drag info context */
+ CajaDragInfo drag_info;
+
+ gboolean highlighted;
+
+ /* Stipple for drawing icon shadows during DnD. */
+ GdkBitmap *stipple;
+
+ /* Shadow for the icons being dragged. */
+ EelCanvasItem *shadow;
+} CajaIconDndInfo;
+
+
+void caja_icon_dnd_init (CajaIconContainer *container,
+ GdkBitmap *stipple);
+void caja_icon_dnd_fini (CajaIconContainer *container);
+void caja_icon_dnd_set_stipple (CajaIconContainer *container,
+ GdkBitmap *stipple);
+void caja_icon_dnd_begin_drag (CajaIconContainer *container,
+ GdkDragAction actions,
+ gint button,
+ GdkEventMotion *event,
+ int start_x,
+ int start_y);
+void caja_icon_dnd_end_drag (CajaIconContainer *container);
+
+#endif /* CAJA_ICON_DND_H */
diff --git a/libcaja-private/caja-icon-info.c b/libcaja-private/caja-icon-info.c
new file mode 100644
index 00000000..63ed8792
--- /dev/null
+++ b/libcaja-private/caja-icon-info.c
@@ -0,0 +1,751 @@
+/* caja-icon-info.c
+ * Copyright (C) 2007 Red Hat, Inc., Alexander Larsson <[email protected]>
+ *
+ * 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include "caja-icon-info.h"
+#include "caja-default-file-icon.h"
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+#include <eel/eel-gdk-pixbuf-extensions.h>
+
+struct _CajaIconInfo
+{
+ GObject parent;
+
+ gboolean sole_owner;
+ guint64 last_use_time;
+ GdkPixbuf *pixbuf;
+
+ gboolean got_embedded_rect;
+ GdkRectangle embedded_rect;
+ gint n_attach_points;
+ GdkPoint *attach_points;
+ char *display_name;
+ char *icon_name;
+};
+
+struct _CajaIconInfoClass
+{
+ GObjectClass parent_class;
+};
+
+static void schedule_reap_cache (void);
+
+G_DEFINE_TYPE (CajaIconInfo,
+ caja_icon_info,
+ G_TYPE_OBJECT);
+
+static void
+caja_icon_info_init (CajaIconInfo *icon)
+{
+ icon->last_use_time = g_thread_gettime ();
+ icon->sole_owner = TRUE;
+}
+
+gboolean
+caja_icon_info_is_fallback (CajaIconInfo *icon)
+{
+ return icon->pixbuf == NULL;
+}
+
+static void
+pixbuf_toggle_notify (gpointer info,
+ GObject *object,
+ gboolean is_last_ref)
+{
+ CajaIconInfo *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_thread_gettime ();
+ schedule_reap_cache ();
+ }
+}
+
+static void
+caja_icon_info_finalize (GObject *object)
+{
+ CajaIconInfo *icon;
+
+ icon = CAJA_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->attach_points);
+ g_free (icon->display_name);
+ g_free (icon->icon_name);
+
+ G_OBJECT_CLASS (caja_icon_info_parent_class)->finalize (object);
+}
+
+static void
+caja_icon_info_class_init (CajaIconInfoClass *icon_info_class)
+{
+ GObjectClass *gobject_class;
+
+ gobject_class = (GObjectClass *) icon_info_class;
+
+ gobject_class->finalize = caja_icon_info_finalize;
+
+}
+
+CajaIconInfo *
+caja_icon_info_new_for_pixbuf (GdkPixbuf *pixbuf)
+{
+ CajaIconInfo *icon;
+
+ icon = g_object_new (CAJA_TYPE_ICON_INFO, NULL);
+
+ if (pixbuf)
+ {
+ icon->pixbuf = g_object_ref (pixbuf);
+ }
+
+ return icon;
+}
+
+static CajaIconInfo *
+caja_icon_info_new_for_icon_info (GtkIconInfo *icon_info)
+{
+ CajaIconInfo *icon;
+ GdkPoint *points;
+ gint n_points;
+ const char *filename;
+ char *basename, *p;
+
+ icon = g_object_new (CAJA_TYPE_ICON_INFO, NULL);
+
+ icon->pixbuf = gtk_icon_info_load_icon (icon_info, NULL);
+
+ icon->got_embedded_rect = gtk_icon_info_get_embedded_rect (icon_info,
+ &icon->embedded_rect);
+
+ if (gtk_icon_info_get_attach_points (icon_info, &points, &n_points))
+ {
+ icon->n_attach_points = n_points;
+ icon->attach_points = points;
+ }
+
+ icon->display_name = g_strdup (gtk_icon_info_get_display_name (icon_info));
+
+ 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;
+ }
+
+ 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 NSEC_PER_SEC ((guint64)1000000000L)
+
+static guint time_now;
+
+static gboolean
+reap_old_icon (gpointer key,
+ gpointer value,
+ gpointer user_info)
+{
+ CajaIconInfo *icon = value;
+ gboolean *reapable_icons_left = user_info;
+
+ if (icon->sole_owner)
+ {
+ if (time_now - icon->last_use_time > 30 * NSEC_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_thread_gettime ();
+
+ 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
+caja_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);
+}
+
+CajaIconInfo *
+caja_icon_info_lookup (GIcon *icon,
+ int size)
+{
+ CajaIconInfo *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,
+ NULL, NULL, NULL);
+ if (stream)
+ {
+ pixbuf = eel_gdk_pixbuf_load_from_stream_at_size (stream, size);
+ g_object_unref (stream);
+ }
+
+ icon_info = caja_icon_info_new_for_pixbuf (pixbuf);
+
+ 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 (icon_theme, (const char **)names, size, 0);
+
+ if (gtkicon_info == NULL)
+ {
+ return caja_icon_info_new_for_pixbuf (NULL);
+ }
+
+ filename = gtk_icon_info_get_filename (gtkicon_info);
+
+ lookup_key.filename = (char *)filename;
+ lookup_key.size = size;
+
+ icon_info = g_hash_table_lookup (themed_icon_cache, &lookup_key);
+ if (icon_info)
+ {
+ gtk_icon_info_free (gtkicon_info);
+ return g_object_ref (icon_info);
+ }
+
+ icon_info = caja_icon_info_new_for_icon_info (gtkicon_info);
+
+ key = themed_icon_key_new (filename, size);
+ g_hash_table_insert (themed_icon_cache, key, icon_info);
+
+ gtk_icon_info_free (gtkicon_info);
+
+ return g_object_ref (icon_info);
+ }
+ else
+ {
+ GdkPixbuf *pixbuf;
+ GtkIconInfo *gtk_icon_info;
+
+ gtk_icon_info = gtk_icon_theme_lookup_by_gicon (gtk_icon_theme_get_default (),
+ icon,
+ size,
+ GTK_ICON_LOOKUP_GENERIC_FALLBACK);
+ if (gtk_icon_info != NULL)
+ {
+ pixbuf = gtk_icon_info_load_icon (gtk_icon_info, NULL);
+ gtk_icon_info_free (gtk_icon_info);
+ }
+ else
+ {
+ pixbuf = NULL;
+ }
+
+ return caja_icon_info_new_for_pixbuf (pixbuf);
+ }
+}
+
+CajaIconInfo *
+caja_icon_info_lookup_from_name (const char *name,
+ int size)
+{
+ GIcon *icon;
+ CajaIconInfo *info;
+
+ icon = g_themed_icon_new (name);
+ info = caja_icon_info_lookup (icon, size);
+ g_object_unref (icon);
+ return info;
+}
+
+CajaIconInfo *
+caja_icon_info_lookup_from_path (const char *path,
+ int size)
+{
+ GFile *icon_file;
+ GIcon *icon;
+ CajaIconInfo *info;
+
+ icon_file = g_file_new_for_path (path);
+ icon = g_file_icon_new (icon_file);
+ info = caja_icon_info_lookup (icon, size);
+ g_object_unref (icon);
+ g_object_unref (icon_file);
+ return info;
+}
+
+GdkPixbuf *
+caja_icon_info_get_pixbuf_nodefault (CajaIconInfo *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 *
+caja_icon_info_get_pixbuf (CajaIconInfo *icon)
+{
+ GdkPixbuf *res;
+
+ res = caja_icon_info_get_pixbuf_nodefault (icon);
+ if (res == NULL)
+ {
+ res = gdk_pixbuf_new_from_data (caja_default_file_icon,
+ GDK_COLORSPACE_RGB,
+ TRUE,
+ 8,
+ caja_default_file_icon_width,
+ caja_default_file_icon_height,
+ caja_default_file_icon_width * 4, /* stride */
+ NULL, /* don't destroy info */
+ NULL);
+ }
+
+ return res;
+}
+
+GdkPixbuf *
+caja_icon_info_get_pixbuf_nodefault_at_size (CajaIconInfo *icon,
+ gsize forced_size)
+{
+ GdkPixbuf *pixbuf, *scaled_pixbuf;
+ int w, h, s;
+ double scale;
+
+ pixbuf = caja_icon_info_get_pixbuf_nodefault (icon);
+
+ if (pixbuf == NULL)
+ return NULL;
+
+ w = gdk_pixbuf_get_width (pixbuf);
+ h = gdk_pixbuf_get_height (pixbuf);
+ 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 *
+caja_icon_info_get_pixbuf_at_size (CajaIconInfo *icon,
+ gsize forced_size)
+{
+ GdkPixbuf *pixbuf, *scaled_pixbuf;
+ int w, h, s;
+ double scale;
+
+ pixbuf = caja_icon_info_get_pixbuf (icon);
+
+ w = gdk_pixbuf_get_width (pixbuf);
+ h = gdk_pixbuf_get_height (pixbuf);
+ 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;
+}
+
+gboolean
+caja_icon_info_get_embedded_rect (CajaIconInfo *icon,
+ GdkRectangle *rectangle)
+{
+ *rectangle = icon->embedded_rect;
+ return icon->got_embedded_rect;
+}
+
+gboolean
+caja_icon_info_get_attach_points (CajaIconInfo *icon,
+ GdkPoint **points,
+ gint *n_points)
+{
+ *n_points = icon->n_attach_points;
+ *points = icon->attach_points;
+ return icon->n_attach_points != 0;
+}
+
+const char* caja_icon_info_get_display_name(CajaIconInfo* icon)
+{
+ return icon->display_name;
+}
+
+const char* caja_icon_info_get_used_name(CajaIconInfo* icon)
+{
+ return icon->icon_name;
+}
+
+/* Return nominal icon size for given zoom level.
+ * @zoom_level: zoom level for which to find matching icon size.
+ *
+ * Return value: icon size between CAJA_ICON_SIZE_SMALLEST and
+ * CAJA_ICON_SIZE_LARGEST, inclusive.
+ */
+guint
+caja_get_icon_size_for_zoom_level (CajaZoomLevel zoom_level)
+{
+ switch (zoom_level)
+ {
+ case CAJA_ZOOM_LEVEL_SMALLEST:
+ return CAJA_ICON_SIZE_SMALLEST;
+ case CAJA_ZOOM_LEVEL_SMALLER:
+ return CAJA_ICON_SIZE_SMALLER;
+ case CAJA_ZOOM_LEVEL_SMALL:
+ return CAJA_ICON_SIZE_SMALL;
+ case CAJA_ZOOM_LEVEL_STANDARD:
+ return CAJA_ICON_SIZE_STANDARD;
+ case CAJA_ZOOM_LEVEL_LARGE:
+ return CAJA_ICON_SIZE_LARGE;
+ case CAJA_ZOOM_LEVEL_LARGER:
+ return CAJA_ICON_SIZE_LARGER;
+ case CAJA_ZOOM_LEVEL_LARGEST:
+ return CAJA_ICON_SIZE_LARGEST;
+ }
+ g_return_val_if_reached (CAJA_ICON_SIZE_STANDARD);
+}
+
+float
+caja_get_relative_icon_size_for_zoom_level (CajaZoomLevel zoom_level)
+{
+ return (float)caja_get_icon_size_for_zoom_level (zoom_level) / CAJA_ICON_SIZE_STANDARD;
+}
+
+guint
+caja_icon_get_larger_icon_size (guint size)
+{
+ if (size < CAJA_ICON_SIZE_SMALLEST)
+ {
+ return CAJA_ICON_SIZE_SMALLEST;
+ }
+ if (size < CAJA_ICON_SIZE_SMALLER)
+ {
+ return CAJA_ICON_SIZE_SMALLER;
+ }
+ if (size < CAJA_ICON_SIZE_SMALL)
+ {
+ return CAJA_ICON_SIZE_SMALL;
+ }
+ if (size < CAJA_ICON_SIZE_STANDARD)
+ {
+ return CAJA_ICON_SIZE_STANDARD;
+ }
+ if (size < CAJA_ICON_SIZE_LARGE)
+ {
+ return CAJA_ICON_SIZE_LARGE;
+ }
+ if (size < CAJA_ICON_SIZE_LARGER)
+ {
+ return CAJA_ICON_SIZE_LARGER;
+ }
+ return CAJA_ICON_SIZE_LARGEST;
+}
+
+guint
+caja_icon_get_smaller_icon_size (guint size)
+{
+ if (size > CAJA_ICON_SIZE_LARGEST)
+ {
+ return CAJA_ICON_SIZE_LARGEST;
+ }
+ if (size > CAJA_ICON_SIZE_LARGER)
+ {
+ return CAJA_ICON_SIZE_LARGER;
+ }
+ if (size > CAJA_ICON_SIZE_LARGE)
+ {
+ return CAJA_ICON_SIZE_LARGE;
+ }
+ if (size > CAJA_ICON_SIZE_STANDARD)
+ {
+ return CAJA_ICON_SIZE_STANDARD;
+ }
+ if (size > CAJA_ICON_SIZE_SMALL)
+ {
+ return CAJA_ICON_SIZE_SMALL;
+ }
+ if (size > CAJA_ICON_SIZE_SMALLER)
+ {
+ return CAJA_ICON_SIZE_SMALLER;
+ }
+ return CAJA_ICON_SIZE_SMALLEST;
+}
+
+gint
+caja_get_icon_size_for_stock_size (GtkIconSize size)
+{
+ gint w, h;
+
+ if (gtk_icon_size_lookup (size, &w, &h))
+ {
+ return MAX (w, h);
+ }
+ return CAJA_ZOOM_LEVEL_STANDARD;
+}
+
+
+guint
+caja_icon_get_emblem_size_for_icon_size (guint size)
+{
+ if (size >= 96)
+ return 48;
+ if (size >= 64)
+ return 32;
+ if (size >= 48)
+ return 24;
+ if (size >= 24)
+ return 16;
+ if (size >= 16)
+ return 12;
+
+ return 0; /* no emblems for smaller sizes */
+}
diff --git a/libcaja-private/caja-icon-info.h b/libcaja-private/caja-icon-info.h
new file mode 100644
index 00000000..7c2a75d2
--- /dev/null
+++ b/libcaja-private/caja-icon-info.h
@@ -0,0 +1,99 @@
+#ifndef CAJA_ICON_INFO_H
+#define CAJA_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>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+ /* Names for Caja's different zoom levels, from tiniest items to largest items */
+ typedef enum {
+ CAJA_ZOOM_LEVEL_SMALLEST,
+ CAJA_ZOOM_LEVEL_SMALLER,
+ CAJA_ZOOM_LEVEL_SMALL,
+ CAJA_ZOOM_LEVEL_STANDARD,
+ CAJA_ZOOM_LEVEL_LARGE,
+ CAJA_ZOOM_LEVEL_LARGER,
+ CAJA_ZOOM_LEVEL_LARGEST
+ }
+ CajaZoomLevel;
+
+#define CAJA_ZOOM_LEVEL_N_ENTRIES (CAJA_ZOOM_LEVEL_LARGEST + 1)
+
+ /* Nominal icon sizes for each Caja 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 CAJA_ICON_SIZE_SMALLEST 16
+#define CAJA_ICON_SIZE_SMALLER 24
+#define CAJA_ICON_SIZE_SMALL 32
+#define CAJA_ICON_SIZE_STANDARD 48
+#define CAJA_ICON_SIZE_LARGE 72
+#define CAJA_ICON_SIZE_LARGER 96
+#define CAJA_ICON_SIZE_LARGEST 192
+
+ /* Maximum size of an icon that the icon factory will ever produce */
+#define CAJA_ICON_MAXIMUM_SIZE 320
+
+ typedef struct _CajaIconInfo CajaIconInfo;
+ typedef struct _CajaIconInfoClass CajaIconInfoClass;
+
+
+#define CAJA_TYPE_ICON_INFO (caja_icon_info_get_type ())
+#define CAJA_ICON_INFO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_ICON_INFO, CajaIconInfo))
+#define CAJA_ICON_INFO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_ICON_INFO, CajaIconInfoClass))
+#define CAJA_IS_ICON_INFO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_ICON_INFO))
+#define CAJA_IS_ICON_INFO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_ICON_INFO))
+#define CAJA_ICON_INFO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_ICON_INFO, CajaIconInfoClass))
+
+
+ GType caja_icon_info_get_type (void) G_GNUC_CONST;
+
+ CajaIconInfo * caja_icon_info_new_for_pixbuf (GdkPixbuf *pixbuf);
+ CajaIconInfo * caja_icon_info_lookup (GIcon *icon,
+ int size);
+ CajaIconInfo * caja_icon_info_lookup_from_name (const char *name,
+ int size);
+ CajaIconInfo * caja_icon_info_lookup_from_path (const char *path,
+ int size);
+ gboolean caja_icon_info_is_fallback (CajaIconInfo *icon);
+ GdkPixbuf * caja_icon_info_get_pixbuf (CajaIconInfo *icon);
+ GdkPixbuf * caja_icon_info_get_pixbuf_nodefault (CajaIconInfo *icon);
+ GdkPixbuf * caja_icon_info_get_pixbuf_nodefault_at_size (CajaIconInfo *icon,
+ gsize forced_size);
+ GdkPixbuf * caja_icon_info_get_pixbuf_at_size (CajaIconInfo *icon,
+ gsize forced_size);
+ gboolean caja_icon_info_get_embedded_rect (CajaIconInfo *icon,
+ GdkRectangle *rectangle);
+ gboolean caja_icon_info_get_attach_points (CajaIconInfo *icon,
+ GdkPoint **points,
+ gint *n_points);
+ const char* caja_icon_info_get_display_name(CajaIconInfo* icon);
+ const char* caja_icon_info_get_used_name(CajaIconInfo* icon);
+
+ void caja_icon_info_clear_caches (void);
+
+ /* Relationship between zoom levels and icons sizes. */
+ guint caja_get_icon_size_for_zoom_level (CajaZoomLevel zoom_level);
+ float caja_get_relative_icon_size_for_zoom_level (CajaZoomLevel zoom_level);
+
+ guint caja_icon_get_larger_icon_size (guint size);
+ guint caja_icon_get_smaller_icon_size (guint size);
+
+ gint caja_get_icon_size_for_stock_size (GtkIconSize size);
+ guint caja_icon_get_emblem_size_for_icon_size (guint size);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CAJA_ICON_INFO_H */
+
diff --git a/libcaja-private/caja-icon-names.h b/libcaja-private/caja-icon-names.h
new file mode 100644
index 00000000..d0598749
--- /dev/null
+++ b/libcaja-private/caja-icon-names.h
@@ -0,0 +1,30 @@
+#ifndef CAJA_ICON_NAMES_H
+#define CAJA_ICON_NAMES_H
+
+/* Icons for places */
+#define CAJA_ICON_COMPUTER "computer"
+#define CAJA_ICON_DESKTOP "user-desktop"
+#define CAJA_ICON_FILESYSTEM "drive-harddisk"
+#define CAJA_ICON_FOLDER "folder"
+#define CAJA_ICON_FOLDER_REMOTE "folder-remote"
+#define CAJA_ICON_HOME "user-home"
+#define CAJA_ICON_NETWORK "network-workgroup"
+#define CAJA_ICON_NETWORK_SERVER "network-server"
+#define CAJA_ICON_SEARCH "system-search"
+#define CAJA_ICON_TRASH "user-trash"
+#define CAJA_ICON_TRASH_FULL "user-trash-full"
+#define CAJA_ICON_DELETE "edit-delete"
+
+/* Icons for emblems */
+#define CAJA_ICON_EMBLEM_READONLY "emblem-readonly"
+#define CAJA_ICON_EMBLEM_UNREADABLE "emblem-unreadable"
+#define CAJA_ICON_EMBLEM_SYMLINK "emblem-symbolic-link"
+
+/* Other icons */
+#define CAJA_ICON_TEMPLATE "text-x-generic-template"
+
+/* Icons not provided by fd.o naming spec or caja itself */
+#define CAJA_ICON_BURN "caja-cd-burner"
+
+#endif /* CAJA_ICON_NAMES_H */
+
diff --git a/libcaja-private/caja-icon-private.h b/libcaja-private/caja-icon-private.h
new file mode 100644
index 00000000..3214586d
--- /dev/null
+++ b/libcaja-private/caja-icon-private.h
@@ -0,0 +1,336 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* mate-icon-container-private.h
+
+ Copyright (C) 1999, 2000 Free Software Foundation
+ Copyright (C) 2000 Eazel, Inc.
+
+ The Mate 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 Mate 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 Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Ettore Perazzoli <[email protected]>
+*/
+
+#ifndef CAJA_ICON_CONTAINER_PRIVATE_H
+#define CAJA_ICON_CONTAINER_PRIVATE_H
+
+#include <eel/eel-glib-extensions.h>
+#include <libcaja-private/caja-icon-canvas-item.h>
+#include <libcaja-private/caja-icon-container.h>
+#include <libcaja-private/caja-icon-dnd.h>
+
+/* An Icon. */
+
+typedef struct
+{
+ /* Object represented by this icon. */
+ CajaIconData *data;
+
+ /* Canvas item for the icon. */
+ CajaIconCanvasItem *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 caja metafile.
+ */
+ double saved_ltr_x;
+
+ /* Scale factor (stretches icon). */
+ double scale;
+
+ /* 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;
+
+ /* Whether a monitor was set on this icon. */
+ eel_boolean_bit is_monitored : 1;
+
+ eel_boolean_bit has_lazy_position : 1;
+} CajaIcon;
+
+
+/* Private CajaIconContainer members. */
+
+typedef struct
+{
+ gboolean active;
+
+ double start_x, start_y;
+
+ EelCanvasItem *selection_rectangle;
+
+ guint timer_id;
+
+ guint prev_x, prev_y;
+ EelDRect prev_rect;
+ int last_adj_x;
+ int last_adj_y;
+} CajaIconRubberbandInfo;
+
+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 CajaIconContainerDetails
+{
+ /* List of icons. */
+ GList *icons;
+ GList *new_icons;
+ GHashTable *icon_set;
+
+ /* Current icon for keyboard navigation. */
+ CajaIcon *keyboard_focus;
+ CajaIcon *keyboard_rubberband_start;
+
+ /* Current icon with stretch handles, so we have only one. */
+ CajaIcon *stretch_icon;
+ double stretch_initial_x, stretch_initial_y;
+ guint stretch_initial_size;
+
+ /* Last highlighted drop target. */
+ CajaIcon *drop_target;
+
+ /* Rubberbanding status. */
+ CajaIconRubberbandInfo 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;
+ CajaIcon *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).
+ */
+ CajaIcon *pending_icon_to_reveal;
+
+ /* If a request is made to rename an unpositioned icon we remember
+ * it and start renaming it once it gets positioned (in relayout).
+ */
+ CajaIcon *pending_icon_to_rename;
+
+ /* 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;
+ CajaIcon *drag_icon;
+ int drag_x, drag_y;
+ DragState drag_state;
+ gboolean drag_started;
+ StretchState stretch_start;
+ gboolean drag_allow_moves;
+
+ gboolean icon_selected_on_button_down;
+ CajaIcon *double_click_icon[2]; /* Both clicks in a double click need to be on the same icon */
+ guint double_click_button[2];
+
+ CajaIcon *range_selection_base_icon;
+
+ /* Renaming Details */
+ gboolean renaming;
+ GtkWidget *rename_widget; /* Editable text item */
+ char *original_text; /* Copy of editable text for later compare */
+
+ /* Idle ID. */
+ guint idle_id;
+
+ /* Idle handler for stretch code */
+ guint stretch_idle_id;
+
+ /* Align idle id */
+ guint align_idle_id;
+
+ /* DnD info. */
+ CajaIconDndInfo *dnd_info;
+
+ /* zoom level */
+ int zoom_level;
+
+ /* specific fonts used to draw labels */
+ char *font;
+
+ /* font sizes used to draw labels */
+ int font_size_table[CAJA_ZOOM_LEVEL_LARGEST + 1];
+
+ /* pixbuf and color for label highlighting */
+ guint32 highlight_color_rgba;
+ guint32 active_color_rgba;
+ guint32 normal_color_rgba;
+ guint32 prelight_color_rgba;
+ guint32 prelight_icon_color_rgba;
+ guint32 normal_icon_color_rgba;
+
+ /* colors for text labels */
+ GdkGC *label_gcs [LAST_LABEL_COLOR];
+ GdkColor label_colors [LAST_LABEL_COLOR];
+
+ /* 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;
+ gboolean tighter_layout;
+
+ /* Whether for the vertical layout, all columns are supposed to
+ * have the same width. */
+ gboolean all_columns_same_width;
+
+ /* Layout mode */
+ CajaIconLayoutMode layout_mode;
+
+ /* Label position */
+ CajaIconLabelPosition label_position;
+
+ /* Forced icon size, iff greater than 0 */
+ int forced_icon_size;
+
+ /* 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;
+
+ /* Whether we should use drop shadows for the icon labels or not */
+ gboolean use_drop_shadows;
+ gboolean drop_shadows_requested;
+
+ /* 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 store_layout_timestamps : 1;
+ eel_boolean_bit store_layout_timestamps_when_finishing_new_icons : 1;
+ time_t layout_timestamp;
+
+ /* interactive search */
+ gboolean disable_popdown;
+ gboolean imcontext_changed;
+ int selected_iter;
+ GtkWidget *search_window;
+ GtkWidget *search_entry;
+ guint search_entry_changed_id;
+ guint typeselect_flush_timeout;
+};
+
+/* Private functions shared by mutiple files. */
+CajaIcon *caja_icon_container_get_icon_by_uri (CajaIconContainer *container,
+ const char *uri);
+void caja_icon_container_move_icon (CajaIconContainer *container,
+ CajaIcon *icon,
+ int x,
+ int y,
+ double scale,
+ gboolean raise,
+ gboolean snap,
+ gboolean update_position);
+void caja_icon_container_select_list_unselect_others (CajaIconContainer *container,
+ GList *icons);
+char * caja_icon_container_get_icon_uri (CajaIconContainer *container,
+ CajaIcon *icon);
+char * caja_icon_container_get_icon_drop_target_uri (CajaIconContainer *container,
+ CajaIcon *icon);
+void caja_icon_container_update_icon (CajaIconContainer *container,
+ CajaIcon *icon);
+gboolean caja_icon_container_has_stored_icon_positions (CajaIconContainer *container);
+gboolean caja_icon_container_emit_preview_signal (CajaIconContainer *view,
+ CajaIcon *icon,
+ gboolean start_flag);
+gboolean caja_icon_container_scroll (CajaIconContainer *container,
+ int delta_x,
+ int delta_y);
+void caja_icon_container_update_scroll_region (CajaIconContainer *container);
+
+/* label color for items */
+GdkGC *caja_icon_container_get_label_color_and_gc (CajaIconContainer *container,
+ GdkColor **color,
+ gboolean first_line,
+ gboolean needs_highlight,
+ gboolean is_prelit);
+
+#endif /* CAJA_ICON_CONTAINER_PRIVATE_H */
diff --git a/libcaja-private/caja-idle-queue.c b/libcaja-private/caja-idle-queue.c
new file mode 100644
index 00000000..c198f5e0
--- /dev/null
+++ b/libcaja-private/caja-idle-queue.c
@@ -0,0 +1,156 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: 8; c-basic-offset: 8 -*- */
+
+/*
+ * libcaja: A library for caja view implementations.
+ *
+ * Copyright (C) 2001 Eazel, 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, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Author: Darin Adler <[email protected]>
+ *
+ */
+
+#include <config.h>
+#include "caja-idle-queue.h"
+
+#include <gtk/gtk.h>
+
+struct CajaIdleQueue
+{
+ GList *functions;
+ guint idle_id;
+ gboolean in_idle;
+ gboolean destroy;
+};
+
+typedef struct
+{
+ GFunc callback;
+ gpointer data;
+ gpointer callback_data;
+ GFreeFunc free_callback_data;
+} QueuedFunction;
+
+static gboolean
+execute_queued_functions (gpointer callback_data)
+{
+ CajaIdleQueue *queue;
+ GList *functions, *node;
+ QueuedFunction *function;
+
+ queue = callback_data;
+
+ /* We could receive more incoming functions while dispatching
+ * these, so keep going until the queue is empty.
+ */
+ queue->in_idle = TRUE;
+ while (queue->functions != NULL)
+ {
+ functions = g_list_reverse (queue->functions);
+ queue->functions = NULL;
+
+ for (node = functions; node != NULL; node = node->next)
+ {
+ function = node->data;
+
+ if (!queue->destroy)
+ {
+ (* function->callback) (function->data, function->callback_data);
+ }
+ if (function->free_callback_data != NULL)
+ {
+ (* function->free_callback_data) (function->callback_data);
+ }
+
+ g_free (function);
+ }
+
+ g_list_free (functions);
+ }
+ queue->in_idle = FALSE;
+
+ queue->idle_id = 0;
+
+ if (queue->destroy)
+ {
+ caja_idle_queue_destroy (queue);
+ }
+
+ return FALSE;
+}
+
+CajaIdleQueue *
+caja_idle_queue_new (void)
+{
+ return g_new0 (CajaIdleQueue, 1);
+}
+
+void
+caja_idle_queue_add (CajaIdleQueue *queue,
+ GFunc callback,
+ gpointer data,
+ gpointer callback_data,
+ GFreeFunc free_callback_data)
+{
+ QueuedFunction *function;
+
+ function = g_new (QueuedFunction, 1);
+ function->callback = callback;
+ function->data = data;
+ function->callback_data = callback_data;
+ function->free_callback_data = free_callback_data;
+
+ queue->functions = g_list_prepend (queue->functions, function);
+
+ if (queue->idle_id == 0)
+ {
+ queue->idle_id = g_idle_add (execute_queued_functions, queue);
+ }
+}
+
+void
+caja_idle_queue_destroy (CajaIdleQueue *queue)
+{
+ GList *node;
+ QueuedFunction *function;
+
+ if (queue->in_idle)
+ {
+ queue->destroy = TRUE;
+ return;
+ }
+
+ for (node = queue->functions; node != NULL; node = node->next)
+ {
+ function = node->data;
+
+ if (function->free_callback_data != NULL)
+ {
+ (* function->free_callback_data) (function->callback_data);
+ }
+
+ g_free (function);
+ }
+
+ g_list_free (queue->functions);
+
+ if (queue->idle_id != 0)
+ {
+ g_source_remove (queue->idle_id);
+ }
+
+ g_free (queue);
+}
diff --git a/libcaja-private/caja-idle-queue.h b/libcaja-private/caja-idle-queue.h
new file mode 100644
index 00000000..18785f00
--- /dev/null
+++ b/libcaja-private/caja-idle-queue.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: 8; c-basic-offset: 8 -*- */
+
+/*
+ * libcaja: A library for caja view implementations.
+ *
+ * Copyright (C) 2001 Eazel, 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, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Author: Darin Adler <[email protected]>
+ *
+ */
+
+#ifndef CAJA_IDLE_QUEUE_H
+#define CAJA_IDLE_QUEUE_H
+
+#include <glib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+ typedef struct CajaIdleQueue CajaIdleQueue;
+
+ CajaIdleQueue *caja_idle_queue_new (void);
+ void caja_idle_queue_add (CajaIdleQueue *queue,
+ GFunc callback,
+ gpointer data,
+ gpointer callback_data,
+ GFreeFunc free_callback_data);
+ void caja_idle_queue_destroy (CajaIdleQueue *queue);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CAJA_IDLE_QUEUE_H */
diff --git a/libcaja-private/caja-iso9660.h b/libcaja-private/caja-iso9660.h
new file mode 100644
index 00000000..46a729aa
--- /dev/null
+++ b/libcaja-private/caja-iso9660.h
@@ -0,0 +1,110 @@
+/*
+ * Header file iso9660.h - assorted structure definitions and typecasts.
+ * specific to iso9660 filesystem.
+
+ Written by Eric Youngdale (1993).
+
+ Copyright 1993 Yggdrasil Computing, Incorporated
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#ifndef _ISOFS_FS_H
+#define _ISOFS_FS_H
+
+/*
+ * The isofs filesystem constants/structures
+ */
+
+#define ISODCL(from, to) (to - from + 1)
+
+struct iso_volume_descriptor
+{
+ char type[ISODCL(1,1)]; /* 711 */
+ char id[ISODCL(2,6)];
+ char version[ISODCL(7,7)];
+ char data[ISODCL(8,2048)];
+};
+
+/* volume descriptor types */
+#define ISO_VD_PRIMARY 1
+#define ISO_VD_END 255
+
+#define ISO_STANDARD_ID "CD001"
+
+struct iso_primary_descriptor
+{
+ char type [ISODCL ( 1, 1)]; /* 711 */
+ char id [ISODCL ( 2, 6)];
+ char version [ISODCL ( 7, 7)]; /* 711 */
+ char unused1 [ISODCL ( 8, 8)];
+ char system_id [ISODCL ( 9, 40)]; /* achars */
+ char volume_id [ISODCL ( 41, 72)]; /* dchars */
+ char unused2 [ISODCL ( 73, 80)];
+ char volume_space_size [ISODCL ( 81, 88)]; /* 733 */
+ char unused3 [ISODCL ( 89, 120)];
+ char volume_set_size [ISODCL (121, 124)]; /* 723 */
+ char volume_sequence_number [ISODCL (125, 128)]; /* 723 */
+ char logical_block_size [ISODCL (129, 132)]; /* 723 */
+ char path_table_size [ISODCL (133, 140)]; /* 733 */
+ char type_l_path_table [ISODCL (141, 144)]; /* 731 */
+ char opt_type_l_path_table [ISODCL (145, 148)]; /* 731 */
+ char type_m_path_table [ISODCL (149, 152)]; /* 732 */
+ char opt_type_m_path_table [ISODCL (153, 156)]; /* 732 */
+ char root_directory_record [ISODCL (157, 190)]; /* 9.1 */
+ char volume_set_id [ISODCL (191, 318)]; /* dchars */
+ char publisher_id [ISODCL (319, 446)]; /* achars */
+ char preparer_id [ISODCL (447, 574)]; /* achars */
+ char application_id [ISODCL (575, 702)]; /* achars */
+ char copyright_file_id [ISODCL (703, 739)]; /* 7.5 dchars */
+ char abstract_file_id [ISODCL (740, 776)]; /* 7.5 dchars */
+ char bibliographic_file_id [ISODCL (777, 813)]; /* 7.5 dchars */
+ char creation_date [ISODCL (814, 830)]; /* 8.4.26.1 */
+ char modification_date [ISODCL (831, 847)]; /* 8.4.26.1 */
+ char expiration_date [ISODCL (848, 864)]; /* 8.4.26.1 */
+ char effective_date [ISODCL (865, 881)]; /* 8.4.26.1 */
+ char file_structure_version [ISODCL (882, 882)]; /* 711 */
+ char unused4 [ISODCL (883, 883)];
+ char application_data [ISODCL (884, 1395)];
+ char unused5 [ISODCL (1396, 2048)];
+};
+
+/* We use this to help us look up the parent inode numbers. */
+
+struct iso_path_table
+{
+ unsigned char name_len[2]; /* 721 */
+ char extent[4]; /* 731 */
+ char parent[2]; /* 721 */
+ char name[1];
+};
+
+struct iso_directory_record
+{
+ unsigned char length [ISODCL (1, 1)]; /* 711 */
+ char ext_attr_length [ISODCL (2, 2)]; /* 711 */
+ char extent [ISODCL (3, 10)]; /* 733 */
+ char size [ISODCL (11, 18)]; /* 733 */
+ char date [ISODCL (19, 25)]; /* 7 by 711 */
+ char flags [ISODCL (26, 26)];
+ char file_unit_size [ISODCL (27, 27)]; /* 711 */
+ char interleave [ISODCL (28, 28)]; /* 711 */
+ char volume_sequence_number [ISODCL (29, 32)]; /* 723 */
+ unsigned char name_len [ISODCL (33, 33)]; /* 711 */
+ char name [34]; /* Not really, but we need something here */
+};
+#endif
+
+
+
diff --git a/libcaja-private/caja-keep-last-vertical-box.c b/libcaja-private/caja-keep-last-vertical-box.c
new file mode 100644
index 00000000..77a74041
--- /dev/null
+++ b/libcaja-private/caja-keep-last-vertical-box.c
@@ -0,0 +1,166 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-keep-last-vertical-box.c: Subclass of GtkVBox that clips off
+ items that don't fit, except the last one.
+
+ Copyright (C) 2000 Eazel, Inc.
+
+ The Mate 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 Mate 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 Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: John Sullivan <[email protected]>,
+ */
+
+#include <config.h>
+#include "caja-keep-last-vertical-box.h"
+
+#include <eel/eel-gtk-macros.h>
+
+static void caja_keep_last_vertical_box_class_init (CajaKeepLastVerticalBoxClass *class);
+static void caja_keep_last_vertical_box_init (CajaKeepLastVerticalBox *box);
+static void caja_keep_last_vertical_box_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation);
+
+EEL_CLASS_BOILERPLATE (CajaKeepLastVerticalBox, caja_keep_last_vertical_box, GTK_TYPE_VBOX)
+
+/* Standard class initialization function */
+static void
+caja_keep_last_vertical_box_class_init (CajaKeepLastVerticalBoxClass *klass)
+{
+ GtkWidgetClass *widget_class;
+
+ widget_class = (GtkWidgetClass *) klass;
+
+ widget_class->size_allocate = caja_keep_last_vertical_box_size_allocate;
+}
+
+/* Standard object initialization function */
+static void
+caja_keep_last_vertical_box_init (CajaKeepLastVerticalBox *box)
+{
+}
+
+
+/* caja_keep_last_vertical_box_new:
+ *
+ * Create a new vertical box that clips off items from the end that don't
+ * fit, except the last item, which is always kept. When packing this widget
+ * into another vbox, use TRUE for expand and TRUE for fill or this class's
+ * special clipping magic won't work because this widget's allocation might
+ * be larger than the available space.
+ *
+ * @spacing: Vertical space between items.
+ *
+ * Return value: A new CajaKeepLastVerticalBox
+ */
+GtkWidget *
+caja_keep_last_vertical_box_new (gint spacing)
+{
+ CajaKeepLastVerticalBox *box;
+
+ box = CAJA_KEEP_LAST_VERTICAL_BOX (gtk_widget_new (caja_keep_last_vertical_box_get_type (), NULL));
+
+ gtk_box_set_spacing (GTK_BOX (box), spacing);
+
+ /* If homogeneous is TRUE and there are too many items to fit
+ * naturally, they will be squashed together to fit in the space.
+ * We want the ones that don't fit to be not shown at all, so
+ * we set homogeneous to FALSE.
+ */
+ gtk_box_set_homogeneous (GTK_BOX (box), FALSE);
+
+ return GTK_WIDGET (box);
+}
+
+static void
+caja_keep_last_vertical_box_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ GtkBox *box;
+ GtkWidget *last_child, *child;
+ GList *children, *l;
+ GtkAllocation last_child_allocation, child_allocation, tiny_allocation;
+
+ g_return_if_fail (CAJA_IS_KEEP_LAST_VERTICAL_BOX (widget));
+ g_return_if_fail (allocation != NULL);
+
+ EEL_CALL_PARENT (GTK_WIDGET_CLASS, size_allocate, (widget, allocation));
+
+ box = GTK_BOX (widget);
+ children = gtk_container_get_children (GTK_CONTAINER(widget));
+ l = g_list_last (children);
+
+ if (l != NULL)
+ {
+ last_child = l->data;
+ l = l->prev;
+
+ gtk_widget_get_allocation (last_child, &last_child_allocation);
+
+ /* If last child doesn't fit vertically, prune items from the end of the
+ * list one at a time until it does.
+ */
+ if (last_child_allocation.y + last_child_allocation.height >
+ allocation->y + allocation->height)
+ {
+
+ while (l != NULL)
+ {
+ child = l->data;
+ l = l->prev;
+
+ gtk_widget_get_allocation (child, &child_allocation);
+
+ /* Reallocate this child's position so that it does not appear.
+ * Setting the width & height to 0 is not enough, as
+ * one pixel is still drawn. Must also move it outside
+ * visible range. For the cases I've seen, -1, -1 works fine.
+ * This might not work in all future cases. Alternatively, the
+ * items that don't fit could be hidden, but that would interfere
+ * with having other hidden children.
+ *
+ * Note that these children are having their size allocated twice,
+ * once by gtk_vbox_size_allocate and then again here. I don't
+ * know of any problems with this, but holler if you do.
+ */
+ tiny_allocation.x = tiny_allocation.y = -1;
+ tiny_allocation.height = tiny_allocation.width = 0;
+ gtk_widget_size_allocate (child, &tiny_allocation);
+
+ /* We're done if the special last item fits now. */
+ if (child_allocation.y + last_child_allocation.height <=
+ allocation->y + allocation->height)
+ {
+ last_child_allocation.y = child_allocation.y;
+ gtk_widget_size_allocate (last_child, &last_child_allocation);
+ break;
+ }
+
+ /* If the special last item still doesn't fit, but we've
+ * run out of earlier items, then the special last item is
+ * just too darn tall. Let's squash it down to fit in the box's
+ * allocation.
+ */
+ if (l == NULL)
+ {
+ last_child_allocation.y = allocation->y;
+ last_child_allocation.height = allocation->height;
+ gtk_widget_size_allocate (last_child, &last_child_allocation);
+ }
+ }
+ }
+ }
+ g_list_free (children);
+}
diff --git a/libcaja-private/caja-keep-last-vertical-box.h b/libcaja-private/caja-keep-last-vertical-box.h
new file mode 100644
index 00000000..d5c9eabf
--- /dev/null
+++ b/libcaja-private/caja-keep-last-vertical-box.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-keep-last-vertical-box.h: Subclass of GtkVBox that clips off
+ items that don't fit, except the last one.
+
+ Copyright (C) 2000 Eazel, Inc.
+
+ The Mate 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 Mate 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 Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: John Sullivan <[email protected]>,
+ */
+
+#ifndef CAJA_KEEP_LAST_VERTICAL_BOX_H
+#define CAJA_KEEP_LAST_VERTICAL_BOX_H
+
+#include <gtk/gtk.h>
+
+#define CAJA_TYPE_KEEP_LAST_VERTICAL_BOX caja_keep_last_vertical_box_get_type()
+#define CAJA_KEEP_LAST_VERTICAL_BOX(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_KEEP_LAST_VERTICAL_BOX, CajaKeepLastVerticalBox))
+#define CAJA_KEEP_LAST_VERTICAL_BOX_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_KEEP_LAST_VERTICAL_BOX, CajaKeepLastVerticalBoxClass))
+#define CAJA_IS_KEEP_LAST_VERTICAL_BOX(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_KEEP_LAST_VERTICAL_BOX))
+#define CAJA_IS_KEEP_LAST_VERTICAL_BOX_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_KEEP_LAST_VERTICAL_BOX))
+#define CAJA_KEEP_LAST_VERTICAL_BOX_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_KEEP_LAST_VERTICAL_BOX, CajaKeepLastVerticalBoxClass))
+
+typedef struct CajaKeepLastVerticalBox CajaKeepLastVerticalBox;
+typedef struct CajaKeepLastVerticalBoxClass CajaKeepLastVerticalBoxClass;
+
+struct CajaKeepLastVerticalBox
+{
+ GtkVBox vbox;
+};
+
+struct CajaKeepLastVerticalBoxClass
+{
+ GtkVBoxClass parent_class;
+};
+
+GType caja_keep_last_vertical_box_get_type (void);
+GtkWidget *caja_keep_last_vertical_box_new (gint spacing);
+
+#endif /* CAJA_KEEP_LAST_VERTICAL_BOX_H */
diff --git a/libcaja-private/caja-lib-self-check-functions.c b/libcaja-private/caja-lib-self-check-functions.c
new file mode 100644
index 00000000..e529fc3f
--- /dev/null
+++ b/libcaja-private/caja-lib-self-check-functions.c
@@ -0,0 +1,38 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-lib-self-check-functions.c: Wrapper for all self check functions
+ in Caja 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Darin Adler <[email protected]>
+*/
+
+#include <config.h>
+
+#if ! defined (CAJA_OMIT_SELF_CHECK)
+
+#include "caja-lib-self-check-functions.h"
+
+void
+caja_run_lib_self_checks (void)
+{
+ CAJA_LIB_FOR_EACH_SELF_CHECK_FUNCTION (EEL_CALL_SELF_CHECK_FUNCTION)
+}
+
+#endif /* ! CAJA_OMIT_SELF_CHECK */
diff --git a/libcaja-private/caja-lib-self-check-functions.h b/libcaja-private/caja-lib-self-check-functions.h
new file mode 100644
index 00000000..f7b53766
--- /dev/null
+++ b/libcaja-private/caja-lib-self-check-functions.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-lib-self-check-functions.h: Wrapper and prototypes for all
+ self-check functions in libcaja.
+
+ 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Darin Adler <[email protected]>
+*/
+
+#include <eel/eel-self-checks.h>
+
+void caja_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 CAJA_LIB_FOR_EACH_SELF_CHECK_FUNCTION(macro) \
+ macro (caja_self_check_file_utilities) \
+ macro (caja_self_check_file_operations) \
+ macro (caja_self_check_directory) \
+ macro (caja_self_check_file) \
+ macro (caja_self_check_icon_container) \
+/* Add new self-check functions to the list above this line. */
+
+/* Generate prototypes for all the functions. */
+CAJA_LIB_FOR_EACH_SELF_CHECK_FUNCTION (EEL_SELF_CHECK_FUNCTION_PROTOTYPE)
diff --git a/libcaja-private/caja-link.c b/libcaja-private/caja-link.c
new file mode 100644
index 00000000..0954f3c5
--- /dev/null
+++ b/libcaja-private/caja-link.c
@@ -0,0 +1,642 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Jonathan Blandford <[email protected]>
+ Alexander Larsson <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-link.h"
+
+#include "caja-directory-notify.h"
+#include "caja-directory.h"
+#include "caja-file-utilities.h"
+#include "caja-file.h"
+#include "caja-program-choosing.h"
+#include "caja-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 CAJA_LINK_GENERIC_TAG "Link"
+#define CAJA_LINK_TRASH_TAG "X-caja-trash"
+#define CAJA_LINK_MOUNT_TAG "FSDevice"
+#define CAJA_LINK_HOME_TAG "X-caja-home"
+
+static gboolean
+is_link_mime_type (const char *mime_type)
+{
+ if (mime_type != NULL &&
+ (g_ascii_strcasecmp (mime_type, "application/x-mate-app-info") == 0 ||
+ 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
+caja_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;
+ CajaFileChangesQueuePosition 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 = caja_get_desktop_directory_uri ();
+ }
+ else
+ {
+ real_directory_uri = g_strdup (directory_uri);
+ }
+
+ if (unique_filename)
+ {
+ uri = caja_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;
+ caja_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;
+
+ caja_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
+caja_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
+caja_link_local_set_text (const char *uri,
+ const char *text)
+{
+ return caja_link_local_set_key (uri, "Name", text, TRUE);
+}
+
+
+gboolean
+caja_link_local_set_icon (const char *uri,
+ const char *icon)
+{
+ return caja_link_local_set_key (uri, "Icon", icon, FALSE);
+}
+
+char *
+caja_link_local_get_text (const char *path)
+{
+ return slurp_key_string (path, "Name", TRUE);
+}
+
+char *
+caja_link_local_get_additional_text (const char *path)
+{
+ /* The comment field of current .desktop files is often bad.
+ * It just contains a copy of the name. This is probably because the
+ * panel shows the comment field as a tooltip.
+ */
+ return NULL;
+#ifdef THIS_IS_NOT_USED_RIGHT_NOW
+ char *type;
+ char *retval;
+
+ if (!is_local_file_a_link (uri))
+ {
+ return NULL;
+ }
+
+ type = slurp_key_string (path, "Type", FALSE);
+ retval = NULL;
+ if (type == NULL)
+ {
+ return NULL;
+ }
+
+ if (strcmp (type, "Application") == 0)
+ {
+ retval = slurp_key_string (path, "Comment", TRUE);
+ }
+
+ g_free (type);
+
+ return retval;
+#endif
+}
+
+static char *
+caja_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, CAJA_LINK_GENERIC_TAG) == 0) ||
+ (strcmp (type, CAJA_LINK_MOUNT_TAG) == 0) ||
+ (strcmp (type, CAJA_LINK_TRASH_TAG) == 0) ||
+ (strcmp (type, CAJA_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);
+ }
+ }
+ }
+
+ return retval;
+}
+
+static char *
+caja_link_get_link_name_from_desktop (GKeyFile *key_file)
+{
+ return g_key_file_get_locale_string (key_file, MAIN_GROUP, "Name", NULL, NULL);
+}
+
+static char *
+caja_link_get_link_icon_from_desktop (GKeyFile *key_file)
+{
+ char *icon_uri, *icon, *p, *type;
+
+ icon_uri = g_key_file_get_string (key_file, MAIN_GROUP, "X-Caja-Icon", NULL);
+ if (icon_uri != NULL)
+ {
+ return icon_uri;
+ }
+
+ icon = g_key_file_get_string (key_file, MAIN_GROUP, "Icon", NULL);
+ if (icon != NULL)
+ {
+ if (!g_path_is_absolute (icon))
+ {
+ /* Strip out any extension on non-filename icons. Old desktop files may have this */
+ p = strchr (icon, '.');
+ /* 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;
+ }
+ }
+ return icon;
+ }
+
+ type = g_key_file_get_string (key_file, MAIN_GROUP, "Type", NULL);
+ if (g_strcmp0 (type, "Application") == 0)
+ {
+ icon = g_strdup ("mate-fs-executable");
+ }
+ else if (g_strcmp0 (type, "Link") == 0)
+ {
+ icon = g_strdup ("mate-dev-symlink");
+ }
+ else if (g_strcmp0 (type, "FSDevice") == 0)
+ {
+ icon = g_strdup ("mate-dev-harddisk");
+ }
+ else if (g_strcmp0 (type, "Directory") == 0)
+ {
+ icon = g_strdup (CAJA_ICON_FOLDER);
+ }
+ else if (g_strcmp0 (type, "Service") == 0 ||
+ g_strcmp0 (type, "ServiceType") == 0)
+ {
+ icon = g_strdup ("mate-fs-web");
+ }
+ else
+ {
+ icon = g_strdup ("mate-fs-regular");
+ }
+ g_free (type);
+
+ return icon;
+}
+
+char *
+caja_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 = caja_link_get_link_uri_from_desktop (key_file, uri);
+ g_key_file_free (key_file);
+
+ return retval;
+}
+
+static gboolean
+string_array_contains (char **array,
+ const char *str)
+{
+ char **p;
+
+ if (!array)
+ return FALSE;
+
+ for (p = array; *p; p++)
+ if (g_ascii_strcasecmp (*p, str) == 0)
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+void
+caja_link_get_link_info_given_file_contents (const char *file_contents,
+ int link_file_size,
+ const char *file_uri,
+ char **uri,
+ char **name,
+ char **icon,
+ gboolean *is_launcher,
+ gboolean *is_foreign)
+{
+ GKeyFile *key_file;
+ 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;
+ }
+
+ *uri = caja_link_get_link_uri_from_desktop (key_file, file_uri);
+ *name = caja_link_get_link_name_from_desktop (key_file);
+ *icon = caja_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, "MATE"))
+ {
+ *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, "MATE"))
+ {
+ *is_foreign = TRUE;
+ }
+ g_strfreev (not_show_in);
+
+ g_key_file_free (key_file);
+}
diff --git a/libcaja-private/caja-link.h b/libcaja-private/caja-link.h
new file mode 100644
index 00000000..352bbcb6
--- /dev/null
+++ b/libcaja-private/caja-link.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Jonathan Blandford <[email protected]>
+*/
+
+#ifndef CAJA_LINK_H
+#define CAJA_LINK_H
+
+#include <gdk/gdk.h>
+
+gboolean caja_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 caja_link_local_set_text (const char *uri,
+ const char *text);
+gboolean caja_link_local_set_icon (const char *uri,
+ const char *icon);
+char * caja_link_local_get_text (const char *uri);
+char * caja_link_local_get_additional_text (const char *uri);
+char * caja_link_local_get_link_uri (const char *uri);
+void caja_link_get_link_info_given_file_contents (const char *file_contents,
+ int link_file_size,
+ const char *file_uri,
+ char **uri,
+ char **name,
+ char **icon,
+ gboolean *is_launcher,
+ gboolean *is_foreign);
+
+#endif /* CAJA_LINK_H */
diff --git a/libcaja-private/caja-marshal.c b/libcaja-private/caja-marshal.c
new file mode 100644
index 00000000..59c9ddcc
--- /dev/null
+++ b/libcaja-private/caja-marshal.c
@@ -0,0 +1,2 @@
+#include "caja-marshal.h"
+#include "caja-marshal-guts.c"
diff --git a/libcaja-private/caja-marshal.list b/libcaja-private/caja-marshal.list
new file mode 100644
index 00000000..787dcc52
--- /dev/null
+++ b/libcaja-private/caja-marshal.list
@@ -0,0 +1,23 @@
+BOOLEAN:POINTER
+BOOLEAN:VOID
+INT:POINTER,BOOLEAN
+INT:POINTER,INT
+INT:POINTER,POINTER
+OBJECT:BOXED
+POINTER:VOID
+STRING:VOID
+VOID:DOUBLE
+VOID:INT,BOOLEAN,BOOLEAN,BOOLEAN,BOOLEAN
+VOID:INT,STRING
+VOID:OBJECT,BOOLEAN
+VOID:OBJECT,OBJECT
+VOID:POINTER,ENUM
+VOID:POINTER,POINTER
+VOID:POINTER,POINTER
+VOID:POINTER,POINTER,POINTER,ENUM,INT,INT
+VOID:POINTER,STRING
+VOID:POINTER,STRING,ENUM,INT,INT
+VOID:STRING,STRING,ENUM,INT,INT
+VOID:STRING,ENUM,INT,INT
+VOID:STRING,STRING
+VOID:POINTER,INT,STRING,STRING,ENUM,INT,INT
diff --git a/libcaja-private/caja-merged-directory.c b/libcaja-private/caja-merged-directory.c
new file mode 100644
index 00000000..328876cf
--- /dev/null
+++ b/libcaja-private/caja-merged-directory.c
@@ -0,0 +1,735 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-merged-directory.c: Subclass of CajaDirectory to implement the
+ virtual merged 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Darin Adler <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-merged-directory.h"
+
+#include "caja-directory-private.h"
+#include "caja-directory-notify.h"
+#include "caja-file.h"
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-gtk-macros.h>
+#include <gtk/gtk.h>
+
+struct CajaMergedDirectoryDetails
+{
+ GList *directories;
+ GList *directories_not_done_loading;
+ GHashTable *callbacks;
+ GHashTable *monitors;
+};
+
+typedef struct
+{
+ CajaMergedDirectory *merged;
+ CajaDirectoryCallback callback;
+ gpointer callback_data;
+
+ CajaFileAttributes wait_for_attributes;
+ gboolean wait_for_file_list;
+
+ GList *non_ready_directories;
+ GList *merged_file_list;
+} MergedCallback;
+
+typedef struct
+{
+ CajaMergedDirectory *merged;
+
+ gboolean monitor_hidden_files;
+ gboolean monitor_backup_files;
+ CajaFileAttributes monitor_attributes;
+} MergedMonitor;
+
+enum
+{
+ ADD_REAL_DIRECTORY,
+ REMOVE_REAL_DIRECTORY,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (CajaMergedDirectory, caja_merged_directory,
+ CAJA_TYPE_DIRECTORY);
+#define parent_class caja_merged_directory_parent_class
+
+static guint
+merged_callback_hash (gconstpointer merged_callback_as_pointer)
+{
+ const MergedCallback *merged_callback;
+
+ merged_callback = merged_callback_as_pointer;
+ return GPOINTER_TO_UINT (merged_callback->callback)
+ ^ GPOINTER_TO_UINT (merged_callback->callback_data);
+}
+
+static gboolean
+merged_callback_equal (gconstpointer merged_callback_as_pointer,
+ gconstpointer merged_callback_as_pointer_2)
+{
+ const MergedCallback *merged_callback, *merged_callback_2;
+
+ merged_callback = merged_callback_as_pointer;
+ merged_callback_2 = merged_callback_as_pointer_2;
+
+ return merged_callback->callback == merged_callback_2->callback
+ && merged_callback->callback_data == merged_callback_2->callback_data;
+}
+
+static void
+merged_callback_destroy (MergedCallback *merged_callback)
+{
+ g_assert (merged_callback != NULL);
+ g_assert (CAJA_IS_MERGED_DIRECTORY (merged_callback->merged));
+
+ g_list_free (merged_callback->non_ready_directories);
+ caja_file_list_free (merged_callback->merged_file_list);
+ g_free (merged_callback);
+}
+
+static void
+merged_callback_check_done (MergedCallback *merged_callback)
+{
+ /* Check if we are ready. */
+ if (merged_callback->non_ready_directories != NULL)
+ {
+ return;
+ }
+
+ /* Remove from the hash table before sending it. */
+ g_hash_table_remove (merged_callback->merged->details->callbacks, merged_callback);
+
+ /* We are ready, so do the real callback. */
+ (* merged_callback->callback) (CAJA_DIRECTORY (merged_callback->merged),
+ merged_callback->merged_file_list,
+ merged_callback->callback_data);
+
+ /* And we are done. */
+ merged_callback_destroy (merged_callback);
+}
+
+static void
+merged_callback_remove_directory (MergedCallback *merged_callback,
+ CajaDirectory *directory)
+{
+ merged_callback->non_ready_directories = g_list_remove
+ (merged_callback->non_ready_directories, directory);
+ merged_callback_check_done (merged_callback);
+}
+
+static void
+directory_ready_callback (CajaDirectory *directory,
+ GList *files,
+ gpointer callback_data)
+{
+ MergedCallback *merged_callback;
+
+ g_assert (CAJA_IS_DIRECTORY (directory));
+ g_assert (callback_data != NULL);
+
+ merged_callback = callback_data;
+ g_assert (g_list_find (merged_callback->non_ready_directories, directory) != NULL);
+
+ /* Update based on this call. */
+ merged_callback->merged_file_list = g_list_concat
+ (merged_callback->merged_file_list,
+ caja_file_list_copy (files));
+
+ /* Check if we are ready. */
+ merged_callback_remove_directory (merged_callback, directory);
+}
+
+static void
+merged_call_when_ready (CajaDirectory *directory,
+ CajaFileAttributes file_attributes,
+ gboolean wait_for_file_list,
+ CajaDirectoryCallback callback,
+ gpointer callback_data)
+{
+ CajaMergedDirectory *merged;
+ MergedCallback search_key, *merged_callback;
+ GList *node;
+
+ merged = CAJA_MERGED_DIRECTORY (directory);
+
+ /* Check to be sure we aren't overwriting. */
+ search_key.callback = callback;
+ search_key.callback_data = callback_data;
+ if (g_hash_table_lookup (merged->details->callbacks, &search_key) != NULL)
+ {
+ g_warning ("tried to add a new callback while an old one was pending");
+ return;
+ }
+
+ /* Create a merged_callback record. */
+ merged_callback = g_new0 (MergedCallback, 1);
+ merged_callback->merged = merged;
+ merged_callback->callback = callback;
+ merged_callback->callback_data = callback_data;
+ merged_callback->wait_for_attributes = file_attributes;
+ merged_callback->wait_for_file_list = wait_for_file_list;
+ for (node = merged->details->directories; node != NULL; node = node->next)
+ {
+ merged_callback->non_ready_directories = g_list_prepend
+ (merged_callback->non_ready_directories, node->data);
+ }
+
+ /* Put it in the hash table. */
+ g_hash_table_insert (merged->details->callbacks,
+ merged_callback, merged_callback);
+
+ /* Handle the pathological case where there are no directories. */
+ if (merged->details->directories == NULL)
+ {
+ merged_callback_check_done (merged_callback);
+ }
+
+ /* Now tell all the directories about it. */
+ for (node = merged->details->directories; node != NULL; node = node->next)
+ {
+ caja_directory_call_when_ready
+ (node->data,
+ merged_callback->wait_for_attributes,
+ merged_callback->wait_for_file_list,
+ directory_ready_callback, merged_callback);
+ }
+}
+
+static void
+merged_cancel_callback (CajaDirectory *directory,
+ CajaDirectoryCallback callback,
+ gpointer callback_data)
+{
+ CajaMergedDirectory *merged;
+ MergedCallback search_key, *merged_callback;
+ GList *node;
+
+ merged = CAJA_MERGED_DIRECTORY (directory);
+
+ /* Find the entry in the table. */
+ search_key.callback = callback;
+ search_key.callback_data = callback_data;
+ merged_callback = g_hash_table_lookup (merged->details->callbacks, &search_key);
+ if (merged_callback == NULL)
+ {
+ return;
+ }
+
+ /* Remove from the hash table before working with it. */
+ g_hash_table_remove (merged_callback->merged->details->callbacks, merged_callback);
+
+ /* Tell all the directories to cancel the call. */
+ for (node = merged_callback->non_ready_directories; node != NULL; node = node->next)
+ {
+ caja_directory_cancel_callback
+ (node->data,
+ directory_ready_callback, merged_callback);
+ }
+ merged_callback_destroy (merged_callback);
+}
+
+static void
+build_merged_callback_list (CajaDirectory *directory,
+ GList *file_list,
+ gpointer callback_data)
+{
+ GList **merged_list;
+
+ merged_list = callback_data;
+ *merged_list = g_list_concat (*merged_list,
+ caja_file_list_copy (file_list));
+}
+
+/* Create a monitor on each of the directories in the list. */
+static void
+merged_monitor_add (CajaDirectory *directory,
+ gconstpointer client,
+ gboolean monitor_hidden_files,
+ gboolean monitor_backup_files,
+ CajaFileAttributes file_attributes,
+ CajaDirectoryCallback callback,
+ gpointer callback_data)
+{
+ CajaMergedDirectory *merged;
+ MergedMonitor *monitor;
+ GList *node;
+ GList *merged_callback_list;
+
+ merged = CAJA_MERGED_DIRECTORY (directory);
+
+ /* Map the client to a unique value so this doesn't interfere
+ * with direct monitoring of the directory by the same client.
+ */
+ monitor = g_hash_table_lookup (merged->details->monitors, client);
+ if (monitor != NULL)
+ {
+ g_assert (monitor->merged == merged);
+ }
+ else
+ {
+ monitor = g_new0 (MergedMonitor, 1);
+ monitor->merged = merged;
+ g_hash_table_insert (merged->details->monitors,
+ (gpointer) client, monitor);
+ }
+ monitor->monitor_hidden_files = monitor_hidden_files;
+ monitor->monitor_backup_files = monitor_backup_files;
+ monitor->monitor_attributes = file_attributes;
+
+ /* Call through to the real directory add calls. */
+ merged_callback_list = NULL;
+ for (node = merged->details->directories; node != NULL; node = node->next)
+ {
+ caja_directory_file_monitor_add
+ (node->data, monitor,
+ monitor_hidden_files, monitor_backup_files,
+ file_attributes,
+ build_merged_callback_list, &merged_callback_list);
+ }
+ if (callback != NULL)
+ {
+ (* callback) (directory, merged_callback_list, callback_data);
+ }
+ caja_file_list_free (merged_callback_list);
+}
+
+static void
+merged_monitor_destroy (CajaMergedDirectory *merged, MergedMonitor *monitor)
+{
+ GList *node;
+
+ /* Call through to the real directory remove calls. */
+ for (node = merged->details->directories; node != NULL; node = node->next)
+ {
+ caja_directory_file_monitor_remove (node->data, monitor);
+ }
+
+ g_free (monitor);
+}
+
+/* Remove the monitor from each of the directories in the list. */
+static void
+merged_monitor_remove (CajaDirectory *directory,
+ gconstpointer client)
+{
+ CajaMergedDirectory *merged;
+ MergedMonitor *monitor;
+
+ merged = CAJA_MERGED_DIRECTORY (directory);
+
+ /* Map the client to the value used by the earlier add call. */
+ monitor = g_hash_table_lookup (merged->details->monitors, client);
+ if (monitor == NULL)
+ {
+ return;
+ }
+ g_hash_table_remove (merged->details->monitors, client);
+
+ merged_monitor_destroy (merged, monitor);
+}
+
+static void
+merged_force_reload (CajaDirectory *directory)
+{
+ CajaMergedDirectory *merged;
+ GList *node;
+
+ merged = CAJA_MERGED_DIRECTORY (directory);
+
+ /* Call through to the real force_reload calls. */
+ for (node = merged->details->directories; node != NULL; node = node->next)
+ {
+ caja_directory_force_reload (node->data);
+ }
+}
+
+/* Return true if any directory in the list does. */
+static gboolean
+merged_contains_file (CajaDirectory *directory,
+ CajaFile *file)
+{
+ CajaMergedDirectory *merged;
+ GList *node;
+
+ merged = CAJA_MERGED_DIRECTORY (directory);
+
+ for (node = merged->details->directories; node != NULL; node = node->next)
+ {
+ if (caja_directory_contains_file (node->data, file))
+ {
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+/* Return true only if all directories in the list do. */
+static gboolean
+merged_are_all_files_seen (CajaDirectory *directory)
+{
+ CajaMergedDirectory *merged;
+ GList *node;
+
+ merged = CAJA_MERGED_DIRECTORY (directory);
+
+ for (node = merged->details->directories; node != NULL; node = node->next)
+ {
+ if (!caja_directory_are_all_files_seen (node->data))
+ {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+/* Return true if any directory in the list does. */
+static gboolean
+merged_is_not_empty (CajaDirectory *directory)
+{
+ CajaMergedDirectory *merged;
+ GList *node;
+
+ merged = CAJA_MERGED_DIRECTORY (directory);
+
+ for (node = merged->details->directories; node != NULL; node = node->next)
+ {
+ if (caja_directory_is_not_empty (node->data))
+ {
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static GList *
+merged_get_file_list (CajaDirectory *directory)
+{
+ GList *dirs_file_list, *merged_dir_file_list = NULL;
+ GList *dir_list;
+ GList *cur_node;
+
+ dirs_file_list = NULL;
+ dir_list = CAJA_MERGED_DIRECTORY (directory)->details->directories;
+
+ for (cur_node = dir_list; cur_node != NULL; cur_node = cur_node->next)
+ {
+ CajaDirectory *cur_dir;
+
+ cur_dir = CAJA_DIRECTORY (cur_node->data);
+ dirs_file_list = g_list_concat (dirs_file_list,
+ caja_directory_get_file_list (cur_dir));
+ }
+
+ merged_dir_file_list = EEL_CALL_PARENT_WITH_RETURN_VALUE
+ (CAJA_DIRECTORY_CLASS, get_file_list, (directory));
+
+ return g_list_concat (dirs_file_list, merged_dir_file_list);
+}
+
+static void
+forward_files_added_cover (CajaDirectory *real_directory,
+ GList *files,
+ gpointer callback_data)
+{
+ caja_directory_emit_files_added (CAJA_DIRECTORY (callback_data), files);
+}
+
+static void
+forward_files_changed_cover (CajaDirectory *real_directory,
+ GList *files,
+ gpointer callback_data)
+{
+ caja_directory_emit_files_changed (CAJA_DIRECTORY (callback_data), files);
+}
+
+static void
+done_loading_callback (CajaDirectory *real_directory,
+ CajaMergedDirectory *merged)
+{
+ merged->details->directories_not_done_loading = g_list_remove
+ (merged->details->directories_not_done_loading, real_directory);
+ if (merged->details->directories_not_done_loading == NULL)
+ {
+ caja_directory_emit_done_loading (CAJA_DIRECTORY (merged));
+ }
+}
+
+static void
+monitor_add_directory (gpointer key,
+ gpointer value,
+ gpointer callback_data)
+{
+ MergedMonitor *monitor;
+
+ monitor = value;
+ caja_directory_file_monitor_add
+ (CAJA_DIRECTORY (callback_data), monitor,
+ monitor->monitor_hidden_files,
+ monitor->monitor_backup_files,
+ monitor->monitor_attributes,
+ forward_files_added_cover, monitor->merged);
+}
+
+static void
+merged_add_real_directory (CajaMergedDirectory *merged,
+ CajaDirectory *real_directory)
+{
+ g_return_if_fail (CAJA_IS_MERGED_DIRECTORY (merged));
+ g_return_if_fail (CAJA_IS_DIRECTORY (real_directory));
+ g_return_if_fail (!CAJA_IS_MERGED_DIRECTORY (real_directory));
+ g_return_if_fail (g_list_find (merged->details->directories, real_directory) == NULL);
+
+ /* Add to our list of directories. */
+ caja_directory_ref (real_directory);
+ merged->details->directories = g_list_prepend
+ (merged->details->directories, real_directory);
+ merged->details->directories_not_done_loading = g_list_prepend
+ (merged->details->directories_not_done_loading, real_directory);
+
+ g_signal_connect_object (real_directory, "done_loading",
+ G_CALLBACK (done_loading_callback), merged, 0);
+
+ /* FIXME bugzilla.gnome.org 45084: The done_loading part won't work for the case where
+ * we have no directories in our list.
+ */
+
+ /* Add the directory to any extant monitors. */
+ g_hash_table_foreach (merged->details->monitors,
+ monitor_add_directory,
+ real_directory);
+ /* FIXME bugzilla.gnome.org 42541: Do we need to add the directory to callbacks too? */
+
+ g_signal_connect_object (real_directory, "files_added",
+ G_CALLBACK (forward_files_added_cover), merged, 0);
+ g_signal_connect_object (real_directory, "files_changed",
+ G_CALLBACK (forward_files_changed_cover), merged, 0);
+}
+
+void
+caja_merged_directory_add_real_directory (CajaMergedDirectory *merged,
+ CajaDirectory *real_directory)
+{
+ g_return_if_fail (CAJA_IS_MERGED_DIRECTORY (merged));
+ g_return_if_fail (CAJA_IS_DIRECTORY (real_directory));
+ g_return_if_fail (!CAJA_IS_MERGED_DIRECTORY (real_directory));
+
+ /* Quietly do nothing if asked to add something that's already there. */
+ if (g_list_find (merged->details->directories, real_directory) != NULL)
+ {
+ return;
+ }
+
+ g_signal_emit (merged, signals[ADD_REAL_DIRECTORY], 0, real_directory);
+}
+
+GList *
+caja_merged_directory_get_real_directories (CajaMergedDirectory *merged)
+{
+ return g_list_copy (merged->details->directories);
+}
+
+static void
+merged_callback_remove_directory_cover (gpointer key,
+ gpointer value,
+ gpointer callback_data)
+{
+ merged_callback_remove_directory
+ (value, CAJA_DIRECTORY (callback_data));
+}
+
+static void
+monitor_remove_directory (gpointer key,
+ gpointer value,
+ gpointer callback_data)
+{
+ caja_directory_file_monitor_remove
+ (CAJA_DIRECTORY (callback_data), value);
+}
+
+static void
+real_directory_notify_files_removed (CajaDirectory *real_directory)
+{
+ GList *files, *l;
+
+ files = caja_directory_get_file_list (real_directory);
+
+ for (l = files; l; l = l->next)
+ {
+ CajaFile *file;
+ char *uri;
+
+ file = CAJA_FILE (l->data);
+ uri = caja_file_get_uri (file);
+ caja_file_unref (file);
+
+ l->data = uri;
+ }
+
+ if (files)
+ {
+ caja_directory_notify_files_removed_by_uri (files);
+ }
+
+ eel_g_list_free_deep (files);
+}
+
+static void
+merged_remove_real_directory (CajaMergedDirectory *merged,
+ CajaDirectory *real_directory)
+{
+ g_return_if_fail (CAJA_IS_MERGED_DIRECTORY (merged));
+ g_return_if_fail (CAJA_IS_DIRECTORY (real_directory));
+ g_return_if_fail (g_list_find (merged->details->directories, real_directory) != NULL);
+
+ /* Since the real directory will be going away, act as if files were removed */
+ real_directory_notify_files_removed (real_directory);
+
+ /* Remove this directory from callbacks and monitors. */
+ eel_g_hash_table_safe_for_each (merged->details->callbacks,
+ merged_callback_remove_directory_cover,
+ real_directory);
+ g_hash_table_foreach (merged->details->monitors,
+ monitor_remove_directory,
+ real_directory);
+
+ /* Disconnect all the signals. */
+ g_signal_handlers_disconnect_matched
+ (real_directory, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, merged);
+
+ /* Remove from our list of directories. */
+ merged->details->directories = g_list_remove
+ (merged->details->directories, real_directory);
+ merged->details->directories_not_done_loading = g_list_remove
+ (merged->details->directories_not_done_loading, real_directory);
+ caja_directory_unref (real_directory);
+}
+
+void
+caja_merged_directory_remove_real_directory (CajaMergedDirectory *merged,
+ CajaDirectory *real_directory)
+{
+ g_return_if_fail (CAJA_IS_MERGED_DIRECTORY (merged));
+
+ /* Quietly do nothing if asked to remove something that's not there. */
+ if (g_list_find (merged->details->directories, real_directory) == NULL)
+ {
+ return;
+ }
+
+ g_signal_emit (merged, signals[REMOVE_REAL_DIRECTORY], 0, real_directory);
+}
+
+static void
+merged_monitor_destroy_cover (gpointer key,
+ gpointer value,
+ gpointer callback_data)
+{
+ merged_monitor_destroy (callback_data, value);
+}
+
+static void
+merged_callback_destroy_cover (gpointer key,
+ gpointer value,
+ gpointer callback_data)
+{
+ merged_callback_destroy (value);
+}
+
+static void
+merged_finalize (GObject *object)
+{
+ CajaMergedDirectory *merged;
+
+ merged = CAJA_MERGED_DIRECTORY (object);
+
+ g_hash_table_foreach (merged->details->monitors,
+ merged_monitor_destroy_cover, merged);
+ g_hash_table_foreach (merged->details->callbacks,
+ merged_callback_destroy_cover, NULL);
+
+ g_hash_table_destroy (merged->details->callbacks);
+ g_hash_table_destroy (merged->details->monitors);
+ caja_directory_list_free (merged->details->directories);
+ g_list_free (merged->details->directories_not_done_loading);
+ g_free (merged->details);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+caja_merged_directory_init (CajaMergedDirectory *merged)
+{
+ merged->details = g_new0 (CajaMergedDirectoryDetails, 1);
+ merged->details->callbacks = g_hash_table_new
+ (merged_callback_hash, merged_callback_equal);
+ merged->details->monitors = g_hash_table_new (NULL, NULL);
+}
+
+static void
+caja_merged_directory_class_init (CajaMergedDirectoryClass *class)
+{
+ CajaDirectoryClass *directory_class;
+
+ directory_class = CAJA_DIRECTORY_CLASS (class);
+
+ G_OBJECT_CLASS (class)->finalize = merged_finalize;
+
+ directory_class->contains_file = merged_contains_file;
+ directory_class->call_when_ready = merged_call_when_ready;
+ directory_class->cancel_callback = merged_cancel_callback;
+ directory_class->file_monitor_add = merged_monitor_add;
+ directory_class->file_monitor_remove = merged_monitor_remove;
+ directory_class->force_reload = merged_force_reload;
+ directory_class->are_all_files_seen = merged_are_all_files_seen;
+ directory_class->is_not_empty = merged_is_not_empty;
+ /* Override get_file_list so that we can return a list that includes
+ * the files from each of the directories in CajaMergedDirectory->details->directories.
+ */
+ directory_class->get_file_list = merged_get_file_list;
+
+ class->add_real_directory = merged_add_real_directory;
+ class->remove_real_directory = merged_remove_real_directory;
+
+ signals[ADD_REAL_DIRECTORY]
+ = g_signal_new ("add_real_directory",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaMergedDirectoryClass,
+ add_real_directory),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1, G_TYPE_POINTER);
+ signals[REMOVE_REAL_DIRECTORY]
+ = g_signal_new ("remove_real_directory",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaMergedDirectoryClass,
+ remove_real_directory),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1, G_TYPE_POINTER);
+}
diff --git a/libcaja-private/caja-merged-directory.h b/libcaja-private/caja-merged-directory.h
new file mode 100644
index 00000000..592e60ad
--- /dev/null
+++ b/libcaja-private/caja-merged-directory.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-merged-directory.h: Subclass of CajaDirectory to implement
+ a virtual directory consisting of the merged contents of some real
+ directories.
+
+ 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Darin Adler <[email protected]>
+*/
+
+#ifndef CAJA_MERGED_DIRECTORY_H
+#define CAJA_MERGED_DIRECTORY_H
+
+#include <libcaja-private/caja-directory.h>
+
+#define CAJA_TYPE_MERGED_DIRECTORY caja_merged_directory_get_type()
+#define CAJA_MERGED_DIRECTORY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_MERGED_DIRECTORY, CajaMergedDirectory))
+#define CAJA_MERGED_DIRECTORY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_MERGED_DIRECTORY, CajaMergedDirectoryClass))
+#define CAJA_IS_MERGED_DIRECTORY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_MERGED_DIRECTORY))
+#define CAJA_IS_MERGED_DIRECTORY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_MERGED_DIRECTORY))
+#define CAJA_MERGED_DIRECTORY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_MERGED_DIRECTORY, CajaMergedDirectoryClass))
+
+typedef struct CajaMergedDirectoryDetails CajaMergedDirectoryDetails;
+
+typedef struct
+{
+ CajaDirectory parent_slot;
+ CajaMergedDirectoryDetails *details;
+} CajaMergedDirectory;
+
+typedef struct
+{
+ CajaDirectoryClass parent_slot;
+
+ void (* add_real_directory) (CajaMergedDirectory *merged_directory,
+ CajaDirectory *real_directory);
+ void (* remove_real_directory) (CajaMergedDirectory *merged_directory,
+ CajaDirectory *real_directory);
+} CajaMergedDirectoryClass;
+
+GType caja_merged_directory_get_type (void);
+void caja_merged_directory_add_real_directory (CajaMergedDirectory *merged_directory,
+ CajaDirectory *real_directory);
+void caja_merged_directory_remove_real_directory (CajaMergedDirectory *merged_directory,
+ CajaDirectory *real_directory);
+GList * caja_merged_directory_get_real_directories (CajaMergedDirectory *merged_directory);
+
+#endif /* CAJA_MERGED_DIRECTORY_H */
diff --git a/libcaja-private/caja-metadata.c b/libcaja-private/caja-metadata.c
new file mode 100644
index 00000000..6acae98d
--- /dev/null
+++ b/libcaja-private/caja-metadata.c
@@ -0,0 +1,80 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-*/
+
+/* caja-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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "caja-metadata.h"
+#include <glib.h>
+
+static char *used_metadata_names[] =
+{
+ CAJA_METADATA_KEY_DEFAULT_VIEW,
+ CAJA_METADATA_KEY_LOCATION_BACKGROUND_COLOR,
+ CAJA_METADATA_KEY_LOCATION_BACKGROUND_IMAGE,
+ CAJA_METADATA_KEY_ICON_VIEW_ZOOM_LEVEL,
+ CAJA_METADATA_KEY_ICON_VIEW_AUTO_LAYOUT,
+ CAJA_METADATA_KEY_ICON_VIEW_TIGHTER_LAYOUT,
+ CAJA_METADATA_KEY_ICON_VIEW_SORT_BY,
+ CAJA_METADATA_KEY_ICON_VIEW_SORT_REVERSED,
+ CAJA_METADATA_KEY_ICON_VIEW_KEEP_ALIGNED,
+ CAJA_METADATA_KEY_ICON_VIEW_LAYOUT_TIMESTAMP,
+ CAJA_METADATA_KEY_LIST_VIEW_ZOOM_LEVEL,
+ CAJA_METADATA_KEY_LIST_VIEW_SORT_COLUMN,
+ CAJA_METADATA_KEY_LIST_VIEW_SORT_REVERSED,
+ CAJA_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS,
+ CAJA_METADATA_KEY_LIST_VIEW_COLUMN_ORDER,
+ CAJA_METADATA_KEY_COMPACT_VIEW_ZOOM_LEVEL,
+ CAJA_METADATA_KEY_WINDOW_GEOMETRY,
+ CAJA_METADATA_KEY_WINDOW_SCROLL_POSITION,
+ CAJA_METADATA_KEY_WINDOW_SHOW_HIDDEN_FILES,
+ CAJA_METADATA_KEY_WINDOW_MAXIMIZED,
+ CAJA_METADATA_KEY_WINDOW_STICKY,
+ CAJA_METADATA_KEY_WINDOW_KEEP_ABOVE,
+ CAJA_METADATA_KEY_SIDEBAR_BACKGROUND_COLOR,
+ CAJA_METADATA_KEY_SIDEBAR_BACKGROUND_IMAGE,
+ CAJA_METADATA_KEY_SIDEBAR_BUTTONS,
+ CAJA_METADATA_KEY_ANNOTATION,
+ CAJA_METADATA_KEY_ICON_POSITION,
+ CAJA_METADATA_KEY_ICON_POSITION_TIMESTAMP,
+ CAJA_METADATA_KEY_ICON_SCALE,
+ CAJA_METADATA_KEY_CUSTOM_ICON,
+ CAJA_METADATA_KEY_SCREEN,
+ CAJA_METADATA_KEY_EMBLEMS,
+ NULL
+};
+
+guint
+caja_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/libcaja-private/caja-metadata.h b/libcaja-private/caja-metadata.h
new file mode 100644
index 00000000..59eb1193
--- /dev/null
+++ b/libcaja-private/caja-metadata.h
@@ -0,0 +1,82 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: John Sullivan <[email protected]>
+*/
+
+#ifndef CAJA_METADATA_H
+#define CAJA_METADATA_H
+
+/* Keys for getting/setting Caja metadata. All metadata used in Caja
+ * should define its key here, so we can keep track of the whole set easily.
+ * Any updates here needs to be added in caja-metadata.c too.
+ */
+
+#include <glib.h>
+
+/* Per-file */
+
+#define CAJA_METADATA_KEY_DEFAULT_VIEW "caja-default-view"
+
+#define CAJA_METADATA_KEY_LOCATION_BACKGROUND_COLOR "folder-background-color"
+#define CAJA_METADATA_KEY_LOCATION_BACKGROUND_IMAGE "folder-background-image"
+
+#define CAJA_METADATA_KEY_ICON_VIEW_ZOOM_LEVEL "caja-icon-view-zoom-level"
+#define CAJA_METADATA_KEY_ICON_VIEW_AUTO_LAYOUT "caja-icon-view-auto-layout"
+#define CAJA_METADATA_KEY_ICON_VIEW_TIGHTER_LAYOUT "caja-icon-view-tighter-layout"
+#define CAJA_METADATA_KEY_ICON_VIEW_SORT_BY "caja-icon-view-sort-by"
+#define CAJA_METADATA_KEY_ICON_VIEW_SORT_REVERSED "caja-icon-view-sort-reversed"
+#define CAJA_METADATA_KEY_ICON_VIEW_KEEP_ALIGNED "caja-icon-view-keep-aligned"
+#define CAJA_METADATA_KEY_ICON_VIEW_LAYOUT_TIMESTAMP "caja-icon-view-layout-timestamp"
+
+#define CAJA_METADATA_KEY_LIST_VIEW_ZOOM_LEVEL "caja-list-view-zoom-level"
+#define CAJA_METADATA_KEY_LIST_VIEW_SORT_COLUMN "caja-list-view-sort-column"
+#define CAJA_METADATA_KEY_LIST_VIEW_SORT_REVERSED "caja-list-view-sort-reversed"
+#define CAJA_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS "caja-list-view-visible-columns"
+#define CAJA_METADATA_KEY_LIST_VIEW_COLUMN_ORDER "caja-list-view-column-order"
+
+#define CAJA_METADATA_KEY_COMPACT_VIEW_ZOOM_LEVEL "caja-compact-view-zoom-level"
+
+#define CAJA_METADATA_KEY_WINDOW_GEOMETRY "caja-window-geometry"
+#define CAJA_METADATA_KEY_WINDOW_SCROLL_POSITION "caja-window-scroll-position"
+#define CAJA_METADATA_KEY_WINDOW_SHOW_HIDDEN_FILES "caja-window-show-hidden-files"
+#define CAJA_METADATA_KEY_WINDOW_MAXIMIZED "caja-window-maximized"
+#define CAJA_METADATA_KEY_WINDOW_STICKY "caja-window-sticky"
+#define CAJA_METADATA_KEY_WINDOW_KEEP_ABOVE "caja-window-keep-above"
+
+#define CAJA_METADATA_KEY_SIDEBAR_BACKGROUND_COLOR "caja-sidebar-background-color"
+#define CAJA_METADATA_KEY_SIDEBAR_BACKGROUND_IMAGE "caja-sidebar-background-image"
+#define CAJA_METADATA_KEY_SIDEBAR_BUTTONS "caja-sidebar-buttons"
+
+#define CAJA_METADATA_KEY_ICON_POSITION "caja-icon-position"
+#define CAJA_METADATA_KEY_ICON_POSITION_TIMESTAMP "caja-icon-position-timestamp"
+#define CAJA_METADATA_KEY_ANNOTATION "annotation"
+#define CAJA_METADATA_KEY_ICON_SCALE "icon-scale"
+#define CAJA_METADATA_KEY_CUSTOM_ICON "custom-icon"
+#define CAJA_METADATA_KEY_SCREEN "screen"
+#define CAJA_METADATA_KEY_EMBLEMS "emblems"
+
+/* This is where desktop item metadata are stored in mateconf */
+#define CAJA_DESKTOP_METADATA_MATECONF_PATH "/apps/caja/desktop-metadata"
+
+guint caja_metadata_get_id (const char *metadata);
+
+#endif /* CAJA_METADATA_H */
diff --git a/libcaja-private/caja-mime-actions.c b/libcaja-private/caja-mime-actions.c
new file mode 100644
index 00000000..c946e938
--- /dev/null
+++ b/libcaja-private/caja-mime-actions.c
@@ -0,0 +1,2597 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-mime-actions.c - uri-specific versions of mime action functions
+
+ Copyright (C) 2000, 2001 Eazel, Inc.
+
+ The Mate 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 Mate 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 Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Maciej Stachowiak <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-mime-actions.h"
+
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-stock-dialogs.h>
+#include <eel/eel-alert-dialog.h>
+#include <eel/eel-string.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <string.h>
+#include <gdk/gdkx.h>
+
+#include "caja-file-attributes.h"
+#include "caja-file.h"
+#include "caja-autorun.h"
+#include "caja-file-operations.h"
+#include "caja-metadata.h"
+#include "caja-program-choosing.h"
+#include "caja-desktop-icon-file.h"
+#include "caja-global-preferences.h"
+#include "caja-debug-log.h"
+#include "caja-open-with-dialog.h"
+
+typedef enum
+{
+ ACTIVATION_ACTION_LAUNCH_DESKTOP_FILE,
+ ACTIVATION_ACTION_ASK,
+ ACTIVATION_ACTION_LAUNCH,
+ ACTIVATION_ACTION_LAUNCH_IN_TERMINAL,
+ ACTIVATION_ACTION_OPEN_IN_VIEW,
+ ACTIVATION_ACTION_OPEN_IN_APPLICATION,
+ ACTIVATION_ACTION_DO_NOTHING,
+} ActivationAction;
+
+typedef struct
+{
+ CajaFile *file;
+ char *uri;
+} LaunchLocation;
+
+typedef struct
+{
+ GAppInfo *application;
+ GList *uris;
+} ApplicationLaunchParameters;
+
+typedef struct
+{
+ CajaWindowSlotInfo *slot_info;
+ gpointer window_info;
+ GtkWindow *parent_window;
+ GCancellable *cancellable;
+ GList *locations;
+ GList *mountables;
+ GList *start_mountables;
+ GList *not_mounted;
+ CajaWindowOpenMode mode;
+ CajaWindowOpenFlags flags;
+ char *timed_wait_prompt;
+ gboolean timed_wait_active;
+ CajaFileListHandle *files_handle;
+ gboolean tried_mounting;
+ char *activation_directory;
+ gboolean user_confirmation;
+} ActivateParameters;
+
+/* Number of seconds until cancel dialog shows up */
+#define DELAY_UNTIL_CANCEL_MSECS 5000
+
+#define RESPONSE_RUN 1000
+#define RESPONSE_DISPLAY 1001
+#define RESPONSE_RUN_IN_TERMINAL 1002
+#define RESPONSE_MARK_TRUSTED 1003
+
+#define SILENT_WINDOW_OPEN_LIMIT 5
+
+/* This number controls a maximum character count for a URL that is
+ * displayed as part of a dialog. It's fairly arbitrary -- big enough
+ * to allow most "normal" URIs to display in full, but small enough to
+ * prevent the dialog from getting insanely wide.
+ */
+#define MAX_URI_IN_DIALOG_LENGTH 60
+
+static void cancel_activate_callback (gpointer callback_data);
+static void activate_activation_uris_ready_callback (GList *files,
+ gpointer callback_data);
+static void activation_mount_mountables (ActivateParameters *parameters);
+static void activation_start_mountables (ActivateParameters *parameters);
+static void activate_callback (GList *files,
+ gpointer callback_data);
+static void activation_mount_not_mounted (ActivateParameters *parameters);
+
+
+static void
+launch_location_free (LaunchLocation *location)
+{
+ caja_file_unref (location->file);
+ g_free (location->uri);
+ g_free (location);
+}
+
+static void
+launch_location_list_free (GList *list)
+{
+ g_list_foreach (list, (GFunc)launch_location_free, NULL);
+ g_list_free (list);
+}
+
+static GList *
+get_file_list_for_launch_locations (GList *locations)
+{
+ GList *files, *l;
+ LaunchLocation *location;
+
+ files = NULL;
+ for (l = locations; l != NULL; l = l->next)
+ {
+ location = l->data;
+
+ files = g_list_prepend (files,
+ caja_file_ref (location->file));
+ }
+ return g_list_reverse (files);
+}
+
+
+static LaunchLocation *
+launch_location_from_file (CajaFile *file)
+{
+ LaunchLocation *location;
+ location = g_new (LaunchLocation, 1);
+ location->file = caja_file_ref (file);
+ location->uri = caja_file_get_uri (file);
+
+ return location;
+}
+
+static void
+launch_location_update_from_file (LaunchLocation *location,
+ CajaFile *file)
+{
+ caja_file_unref (location->file);
+ g_free (location->uri);
+ location->file = caja_file_ref (file);
+ location->uri = caja_file_get_uri (file);
+}
+
+static void
+launch_location_update_from_uri (LaunchLocation *location,
+ const char *uri)
+{
+ caja_file_unref (location->file);
+ g_free (location->uri);
+ location->file = caja_file_get_by_uri (uri);
+ location->uri = g_strdup (uri);
+}
+
+static LaunchLocation *
+find_launch_location_for_file (GList *list,
+ CajaFile *file)
+{
+ LaunchLocation *location;
+ GList *l;
+
+ for (l = list; l != NULL; l = l->next)
+ {
+ location = l->data;
+
+ if (location->file == file)
+ {
+ return location;
+ }
+ }
+ return NULL;
+}
+
+static GList *
+launch_locations_from_file_list (GList *list)
+{
+ GList *new;
+
+ new = NULL;
+ while (list)
+ {
+ new = g_list_prepend (new,
+ launch_location_from_file (list->data));
+ list = list->next;
+ }
+ new = g_list_reverse (new);
+ return new;
+}
+
+static ApplicationLaunchParameters *
+application_launch_parameters_new (GAppInfo *application,
+ GList *uris)
+{
+ ApplicationLaunchParameters *result;
+
+ result = g_new0 (ApplicationLaunchParameters, 1);
+ result->application = g_object_ref (application);
+ result->uris = eel_g_str_list_copy (uris);
+
+ return result;
+}
+
+static void
+application_launch_parameters_free (ApplicationLaunchParameters *parameters)
+{
+ g_object_unref (parameters->application);
+ eel_g_list_free_deep (parameters->uris);
+
+ g_free (parameters);
+}
+
+static GList*
+filter_caja_handler (GList *apps)
+{
+ GList *l, *next;
+ GAppInfo *application;
+ const char *id;
+
+ l = apps;
+ while (l != NULL)
+ {
+ application = (GAppInfo *) l->data;
+ next = l->next;
+
+ id = g_app_info_get_id (application);
+ if (id != NULL &&
+ strcmp (id,
+ "caja-folder-handler.desktop") == 0)
+ {
+ g_object_unref (application);
+ apps = g_list_delete_link (apps, l);
+ }
+
+ l = next;
+ }
+
+ return apps;
+}
+
+static GList*
+filter_non_uri_apps (GList *apps)
+{
+ GList *l, *next;
+ GAppInfo *app;
+
+ for (l = apps; l != NULL; l = next)
+ {
+ app = l->data;
+ next = l->next;
+
+ if (!g_app_info_supports_uris (app))
+ {
+ apps = g_list_delete_link (apps, l);
+ g_object_unref (app);
+ }
+ }
+ return apps;
+}
+
+
+static gboolean
+caja_mime_actions_check_if_required_attributes_ready (CajaFile *file)
+{
+ CajaFileAttributes attributes;
+ gboolean ready;
+
+ attributes = caja_mime_actions_get_required_file_attributes ();
+ ready = caja_file_check_if_ready (file, attributes);
+
+ return ready;
+}
+
+CajaFileAttributes
+caja_mime_actions_get_required_file_attributes (void)
+{
+ return CAJA_FILE_ATTRIBUTE_INFO |
+ CAJA_FILE_ATTRIBUTE_LINK_INFO;
+}
+
+static gboolean
+file_has_local_path (CajaFile *file)
+{
+ GFile *location;
+ char *path;
+ gboolean res;
+
+
+ /* Don't only check _is_native, because we want to support
+ using the fuse path */
+ location = caja_file_get_location (file);
+ if (g_file_is_native (location))
+ {
+ res = TRUE;
+ }
+ else
+ {
+ path = g_file_get_path (location);
+
+ res = path != NULL;
+
+ g_free (path);
+ }
+ g_object_unref (location);
+
+ return res;
+}
+
+GAppInfo *
+caja_mime_get_default_application_for_file (CajaFile *file)
+{
+ GAppInfo *app;
+ char *mime_type;
+ char *uri_scheme;
+
+ if (!caja_mime_actions_check_if_required_attributes_ready (file))
+ {
+ return NULL;
+ }
+
+ mime_type = caja_file_get_mime_type (file);
+ app = g_app_info_get_default_for_type (mime_type, !file_has_local_path (file));
+ g_free (mime_type);
+
+ if (app == NULL)
+ {
+ uri_scheme = caja_file_get_uri_scheme (file);
+ if (uri_scheme != NULL)
+ {
+ app = g_app_info_get_default_for_uri_scheme (uri_scheme);
+ g_free (uri_scheme);
+ }
+ }
+
+ return app;
+}
+
+static int
+file_compare_by_mime_type (CajaFile *file_a,
+ CajaFile *file_b)
+{
+ char *mime_type_a, *mime_type_b;
+ int ret;
+
+ mime_type_a = caja_file_get_mime_type (file_a);
+ mime_type_b = caja_file_get_mime_type (file_b);
+
+ ret = strcmp (mime_type_a, mime_type_b);
+
+ g_free (mime_type_a);
+ g_free (mime_type_b);
+
+ return ret;
+}
+
+static int
+file_compare_by_parent_uri (CajaFile *file_a,
+ CajaFile *file_b)
+{
+ char *parent_uri_a, *parent_uri_b;
+ int ret;
+
+ parent_uri_a = caja_file_get_parent_uri (file_a);
+ parent_uri_b = caja_file_get_parent_uri (file_b);
+
+ ret = strcmp (parent_uri_a, parent_uri_b);
+
+ g_free (parent_uri_a);
+ g_free (parent_uri_b);
+
+ return ret;
+}
+
+static int
+application_compare_by_name (const GAppInfo *app_a,
+ const GAppInfo *app_b)
+{
+ return g_utf8_collate (g_app_info_get_display_name ((GAppInfo *)app_a),
+ g_app_info_get_display_name ((GAppInfo *)app_b));
+}
+
+static int
+application_compare_by_id (const GAppInfo *app_a,
+ const GAppInfo *app_b)
+{
+ const char *id_a, *id_b;
+
+ id_a = g_app_info_get_id ((GAppInfo *)app_a);
+ id_b = g_app_info_get_id ((GAppInfo *)app_b);
+
+ if (id_a == NULL && id_b == NULL)
+ {
+ if (g_app_info_equal ((GAppInfo *)app_a, (GAppInfo *)app_b))
+ {
+ return 0;
+ }
+ if ((gsize)app_a < (gsize) app_b)
+ {
+ return -1;
+ }
+ return 1;
+ }
+
+ if (id_a == NULL)
+ {
+ return -1;
+ }
+
+ if (id_b == NULL)
+ {
+ return 1;
+ }
+
+
+ return strcmp (id_a, id_b);
+}
+
+GList *
+caja_mime_get_applications_for_file (CajaFile *file)
+{
+ char *mime_type;
+ char *uri_scheme;
+ GList *result;
+ GAppInfo *uri_handler;
+
+ if (!caja_mime_actions_check_if_required_attributes_ready (file))
+ {
+ return NULL;
+ }
+ mime_type = caja_file_get_mime_type (file);
+ result = g_app_info_get_all_for_type (mime_type);
+
+ uri_scheme = caja_file_get_uri_scheme (file);
+ if (uri_scheme != NULL)
+ {
+ uri_handler = g_app_info_get_default_for_uri_scheme (uri_scheme);
+ if (uri_handler)
+ {
+ result = g_list_prepend (result, uri_handler);
+ }
+ g_free (uri_scheme);
+ }
+
+ if (!file_has_local_path (file))
+ {
+ /* Filter out non-uri supporting apps */
+ result = filter_non_uri_apps (result);
+ }
+
+ result = g_list_sort (result, (GCompareFunc) application_compare_by_name);
+ g_free (mime_type);
+
+ return filter_caja_handler (result);
+}
+
+gboolean
+caja_mime_has_any_applications_for_file (CajaFile *file)
+{
+ GList *apps;
+ char *mime_type;
+ gboolean result;
+ char *uri_scheme;
+ GAppInfo *uri_handler;
+
+ mime_type = caja_file_get_mime_type (file);
+
+ apps = g_app_info_get_all_for_type (mime_type);
+
+ uri_scheme = caja_file_get_uri_scheme (file);
+ if (uri_scheme != NULL)
+ {
+ uri_handler = g_app_info_get_default_for_uri_scheme (uri_scheme);
+ if (uri_handler)
+ {
+ apps = g_list_prepend (apps, uri_handler);
+ }
+ g_free (uri_scheme);
+ }
+
+ if (!file_has_local_path (file))
+ {
+ /* Filter out non-uri supporting apps */
+ apps = filter_non_uri_apps (apps);
+ }
+ apps = filter_caja_handler (apps);
+
+ if (apps)
+ {
+ result = TRUE;
+ eel_g_object_list_free (apps);
+ }
+ else
+ {
+ result = FALSE;
+ }
+
+ g_free (mime_type);
+
+ return result;
+}
+
+GAppInfo *
+caja_mime_get_default_application_for_files (GList *files)
+{
+ GList *l, *sorted_files;
+ CajaFile *file;
+ GAppInfo *app, *one_app;
+
+ g_assert (files != NULL);
+
+ sorted_files = g_list_sort (g_list_copy (files), (GCompareFunc) file_compare_by_mime_type);
+
+ app = NULL;
+ for (l = sorted_files; l != NULL; l = l->next)
+ {
+ file = l->data;
+
+ if (l->prev &&
+ file_compare_by_mime_type (file, l->prev->data) == 0 &&
+ file_compare_by_parent_uri (file, l->prev->data) == 0)
+ {
+ continue;
+ }
+
+ one_app = caja_mime_get_default_application_for_file (file);
+ if (one_app == NULL || (app != NULL && !g_app_info_equal (app, one_app)))
+ {
+ if (app)
+ {
+ g_object_unref (app);
+ }
+ if (one_app)
+ {
+ g_object_unref (one_app);
+ }
+ app = NULL;
+ break;
+ }
+
+ if (app == NULL)
+ {
+ app = one_app;
+ }
+ else
+ {
+ g_object_unref (one_app);
+ }
+ }
+
+ g_list_free (sorted_files);
+
+ return app;
+}
+
+/* returns an intersection of two mime application lists,
+ * and returns a new list, freeing a, b and all applications
+ * that are not in the intersection set.
+ * The lists are assumed to be pre-sorted by their IDs */
+static GList *
+intersect_application_lists (GList *a,
+ GList *b)
+{
+ GList *l, *m;
+ GList *ret;
+ GAppInfo *a_app, *b_app;
+ int cmp;
+
+ ret = NULL;
+
+ l = a;
+ m = b;
+
+ while (l != NULL && m != NULL)
+ {
+ a_app = (GAppInfo *) l->data;
+ b_app = (GAppInfo *) m->data;
+
+ cmp = application_compare_by_id (a_app, b_app);
+ if (cmp > 0)
+ {
+ g_object_unref (b_app);
+ m = m->next;
+ }
+ else if (cmp < 0)
+ {
+ g_object_unref (a_app);
+ l = l->next;
+ }
+ else
+ {
+ g_object_unref (b_app);
+ ret = g_list_prepend (ret, a_app);
+ l = l->next;
+ m = m->next;
+ }
+ }
+
+ g_list_foreach (l, (GFunc) g_object_unref, NULL);
+ g_list_foreach (m, (GFunc) g_object_unref, NULL);
+
+ g_list_free (a);
+ g_list_free (b);
+
+ return g_list_reverse (ret);
+}
+
+GList *
+caja_mime_get_applications_for_files (GList *files)
+{
+ GList *l, *sorted_files;
+ CajaFile *file;
+ GList *one_ret, *ret;
+
+ g_assert (files != NULL);
+
+ sorted_files = g_list_sort (g_list_copy (files), (GCompareFunc) file_compare_by_mime_type);
+
+ ret = NULL;
+ for (l = sorted_files; l != NULL; l = l->next)
+ {
+ file = l->data;
+
+ if (l->prev &&
+ file_compare_by_mime_type (file, l->prev->data) == 0 &&
+ file_compare_by_parent_uri (file, l->prev->data) == 0)
+ {
+ continue;
+ }
+
+ one_ret = caja_mime_get_applications_for_file (file);
+ one_ret = g_list_sort (one_ret, (GCompareFunc) application_compare_by_id);
+ if (ret != NULL)
+ {
+ ret = intersect_application_lists (ret, one_ret);
+ }
+ else
+ {
+ ret = one_ret;
+ }
+
+ if (ret == NULL)
+ {
+ break;
+ }
+ }
+
+ g_list_free (sorted_files);
+
+ ret = g_list_sort (ret, (GCompareFunc) application_compare_by_name);
+
+ return ret;
+}
+
+gboolean
+caja_mime_has_any_applications_for_files (GList *files)
+{
+ GList *l, *sorted_files;
+ CajaFile *file;
+ gboolean ret;
+
+ g_assert (files != NULL);
+
+ sorted_files = g_list_sort (g_list_copy (files), (GCompareFunc) file_compare_by_mime_type);
+
+ ret = TRUE;
+ for (l = sorted_files; l != NULL; l = l->next)
+ {
+ file = CAJA_FILE (l->data);
+
+ if (l->prev &&
+ file_compare_by_mime_type (file, l->prev->data) == 0 &&
+ file_compare_by_parent_uri (file, l->prev->data) == 0)
+ {
+ continue;
+ }
+
+ if (!caja_mime_has_any_applications_for_file (file))
+ {
+ ret = FALSE;
+ break;
+ }
+ }
+
+ g_list_free (sorted_files);
+
+ return ret;
+}
+
+
+
+static void
+trash_or_delete_files (GtkWindow *parent_window,
+ const GList *files,
+ gboolean delete_if_all_already_in_trash)
+{
+ GList *locations;
+ const GList *node;
+
+ locations = NULL;
+ for (node = files; node != NULL; node = node->next)
+ {
+ locations = g_list_prepend (locations,
+ caja_file_get_location ((CajaFile *) node->data));
+ }
+
+ locations = g_list_reverse (locations);
+
+ caja_file_operations_trash_or_delete (locations,
+ parent_window,
+ NULL, NULL);
+ eel_g_object_list_free (locations);
+}
+
+static void
+report_broken_symbolic_link (GtkWindow *parent_window, CajaFile *file)
+{
+ char *target_path;
+ char *display_name;
+ char *prompt;
+ char *detail;
+ GtkDialog *dialog;
+ GList file_as_list;
+ int response;
+
+ g_assert (caja_file_is_broken_symbolic_link (file));
+
+ display_name = caja_file_get_display_name (file);
+ if (caja_file_is_in_trash (file))
+ {
+ prompt = g_strdup_printf (_("The Link \"%s\" is Broken."), display_name);
+ }
+ else
+ {
+ prompt = g_strdup_printf (_("The Link \"%s\" is Broken. Move it to Trash?"), display_name);
+ }
+ g_free (display_name);
+
+ target_path = caja_file_get_symbolic_link_target_path (file);
+ if (target_path == NULL)
+ {
+ detail = g_strdup (_("This link cannot be used, because it has no target."));
+ }
+ else
+ {
+ detail = g_strdup_printf (_("This link cannot be used, because its target "
+ "\"%s\" doesn't exist."), target_path);
+ }
+
+ if (caja_file_is_in_trash (file))
+ {
+ eel_run_simple_dialog (GTK_WIDGET (parent_window), FALSE, GTK_MESSAGE_WARNING,
+ prompt, detail, GTK_STOCK_CANCEL, NULL);
+ goto out;
+ }
+
+ dialog = eel_show_yes_no_dialog (prompt, detail, _("Mo_ve to Trash"), GTK_STOCK_CANCEL,
+ parent_window);
+
+ gtk_dialog_set_default_response (dialog, GTK_RESPONSE_CANCEL);
+
+ /* Make this modal to avoid problems with reffing the view & file
+ * to keep them around in case the view changes, which would then
+ * cause the old view not to be destroyed, which would cause its
+ * merged MateComponent items not to be un-merged. Maybe we need to unmerge
+ * explicitly when disconnecting views instead of relying on the
+ * unmerge in Destroy. But since MateComponentUIHandler is probably going
+ * to change wildly, I don't want to mess with this now.
+ */
+
+ response = gtk_dialog_run (dialog);
+ gtk_object_destroy (GTK_OBJECT (dialog));
+
+ if (response == GTK_RESPONSE_YES)
+ {
+ file_as_list.data = file;
+ file_as_list.next = NULL;
+ file_as_list.prev = NULL;
+ trash_or_delete_files (parent_window, &file_as_list, TRUE);
+ }
+
+out:
+ g_free (prompt);
+ g_free (target_path);
+ g_free (detail);
+}
+
+static ActivationAction
+get_executable_text_file_action (GtkWindow *parent_window, CajaFile *file)
+{
+ GtkDialog *dialog;
+ char *file_name;
+ char *prompt;
+ char *detail;
+ int preferences_value;
+ int response;
+
+ g_assert (caja_file_contains_text (file));
+
+ preferences_value = eel_preferences_get_enum
+ (CAJA_PREFERENCES_EXECUTABLE_TEXT_ACTIVATION);
+ switch (preferences_value)
+ {
+ case CAJA_EXECUTABLE_TEXT_LAUNCH:
+ return ACTIVATION_ACTION_LAUNCH;
+ case CAJA_EXECUTABLE_TEXT_DISPLAY:
+ return ACTIVATION_ACTION_OPEN_IN_APPLICATION;
+ case CAJA_EXECUTABLE_TEXT_ASK:
+ break;
+ default:
+ /* Complain non-fatally, since preference data can't be trusted */
+ g_warning ("Unknown value %d for CAJA_PREFERENCES_EXECUTABLE_TEXT_ACTIVATION",
+ preferences_value);
+
+ }
+
+
+ file_name = caja_file_get_display_name (file);
+ prompt = g_strdup_printf (_("Do you want to run \"%s\", or display its contents?"),
+ file_name);
+ detail = g_strdup_printf (_("\"%s\" is an executable text file."),
+ file_name);
+ g_free (file_name);
+
+ dialog = eel_create_question_dialog (prompt,
+ detail,
+ _("Run in _Terminal"), RESPONSE_RUN_IN_TERMINAL,
+ _("_Display"), RESPONSE_DISPLAY,
+ parent_window);
+ gtk_dialog_add_button (dialog, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
+ gtk_dialog_add_button (dialog, _("_Run"), RESPONSE_RUN);
+ gtk_dialog_set_default_response (dialog, GTK_RESPONSE_CANCEL);
+ gtk_widget_show (GTK_WIDGET (dialog));
+
+ g_free (prompt);
+ g_free (detail);
+
+ response = gtk_dialog_run (dialog);
+ gtk_object_destroy (GTK_OBJECT (dialog));
+
+ switch (response)
+ {
+ case RESPONSE_RUN:
+ return ACTIVATION_ACTION_LAUNCH;
+ case RESPONSE_RUN_IN_TERMINAL:
+ return ACTIVATION_ACTION_LAUNCH_IN_TERMINAL;
+ case RESPONSE_DISPLAY:
+ return ACTIVATION_ACTION_OPEN_IN_APPLICATION;
+ default:
+ return ACTIVATION_ACTION_DO_NOTHING;
+ }
+}
+
+static ActivationAction
+get_default_executable_text_file_action (void)
+{
+ int preferences_value;
+
+ preferences_value = eel_preferences_get_enum
+ (CAJA_PREFERENCES_EXECUTABLE_TEXT_ACTIVATION);
+ switch (preferences_value)
+ {
+ case CAJA_EXECUTABLE_TEXT_LAUNCH:
+ return ACTIVATION_ACTION_LAUNCH;
+ case CAJA_EXECUTABLE_TEXT_DISPLAY:
+ return ACTIVATION_ACTION_OPEN_IN_APPLICATION;
+ case CAJA_EXECUTABLE_TEXT_ASK:
+ default:
+ return ACTIVATION_ACTION_ASK;
+ }
+}
+
+gboolean
+caja_mime_file_opens_in_view (CajaFile *file)
+{
+ return (caja_file_is_directory (file) ||
+ CAJA_IS_DESKTOP_ICON_FILE (file) ||
+ caja_file_is_caja_link (file));
+}
+
+static ActivationAction
+get_activation_action (CajaFile *file)
+{
+ ActivationAction action;
+ char *activation_uri;
+
+ if (caja_file_is_launcher (file))
+ {
+ return ACTIVATION_ACTION_LAUNCH_DESKTOP_FILE;
+ }
+
+ activation_uri = caja_file_get_activation_uri (file);
+ if (activation_uri == NULL)
+ {
+ activation_uri = caja_file_get_uri (file);
+ }
+
+ action = ACTIVATION_ACTION_DO_NOTHING;
+ if (caja_file_is_launchable (file))
+ {
+ char *executable_path;
+
+ action = ACTIVATION_ACTION_LAUNCH;
+
+ executable_path = g_filename_from_uri (activation_uri, NULL, NULL);
+ if (!executable_path)
+ {
+ action = ACTIVATION_ACTION_DO_NOTHING;
+ }
+ else if (caja_file_contains_text (file))
+ {
+ action = get_default_executable_text_file_action ();
+ }
+ g_free (executable_path);
+ }
+
+ if (action == ACTIVATION_ACTION_DO_NOTHING)
+ {
+ if (caja_mime_file_opens_in_view (file))
+ {
+ action = ACTIVATION_ACTION_OPEN_IN_VIEW;
+ }
+ else
+ {
+ action = ACTIVATION_ACTION_OPEN_IN_APPLICATION;
+ }
+ }
+ g_free (activation_uri);
+
+ return action;
+}
+
+gboolean
+caja_mime_file_opens_in_external_app (CajaFile *file)
+{
+ ActivationAction activation_action;
+
+ activation_action = get_activation_action (file);
+
+ return (activation_action == ACTIVATION_ACTION_OPEN_IN_APPLICATION);
+}
+
+
+static unsigned int
+mime_application_hash (GAppInfo *app)
+{
+ const char *id;
+
+ id = g_app_info_get_id (app);
+
+ if (id == NULL)
+ {
+ return GPOINTER_TO_UINT(app);
+ }
+
+ return g_str_hash (id);
+}
+
+static void
+list_to_parameters_foreach (GAppInfo *application,
+ GList *uris,
+ GList **ret)
+{
+ ApplicationLaunchParameters *parameters;
+
+ uris = g_list_reverse (uris);
+
+ parameters = application_launch_parameters_new
+ (application, uris);
+ *ret = g_list_prepend (*ret, parameters);
+}
+
+
+/**
+ * make_activation_parameters
+ *
+ * Construct a list of ApplicationLaunchParameters from a list of CajaFiles,
+ * where files that have the same default application are put into the same
+ * launch parameter, and others are put into the unhandled_files list.
+ *
+ * @files: Files to use for construction.
+ * @unhandled_files: Files without any default application will be put here.
+ *
+ * Return value: Newly allocated list of ApplicationLaunchParameters.
+ **/
+static GList *
+make_activation_parameters (GList *uris,
+ GList **unhandled_uris)
+{
+ GList *ret, *l, *app_uris;
+ CajaFile *file;
+ GAppInfo *app, *old_app;
+ GHashTable *app_table;
+ char *uri;
+
+ ret = NULL;
+ *unhandled_uris = NULL;
+
+ app_table = g_hash_table_new_full
+ ((GHashFunc) mime_application_hash,
+ (GEqualFunc) g_app_info_equal,
+ (GDestroyNotify) g_object_unref,
+ (GDestroyNotify) g_list_free);
+
+ for (l = uris; l != NULL; l = l->next)
+ {
+ uri = l->data;
+ file = caja_file_get_by_uri (uri);
+
+ app = caja_mime_get_default_application_for_file (file);
+ if (app != NULL)
+ {
+ app_uris = NULL;
+
+ if (g_hash_table_lookup_extended (app_table, app,
+ (gpointer *) &old_app,
+ (gpointer *) &app_uris))
+ {
+ g_hash_table_steal (app_table, old_app);
+
+ app_uris = g_list_prepend (app_uris, uri);
+
+ g_object_unref (app);
+ app = old_app;
+ }
+ else
+ {
+ app_uris = g_list_prepend (NULL, uri);
+ }
+
+ g_hash_table_insert (app_table, app, app_uris);
+ }
+ else
+ {
+ *unhandled_uris = g_list_prepend (*unhandled_uris, uri);
+ }
+ caja_file_unref (file);
+ }
+
+ g_hash_table_foreach (app_table,
+ (GHFunc) list_to_parameters_foreach,
+ &ret);
+
+ g_hash_table_destroy (app_table);
+
+ *unhandled_uris = g_list_reverse (*unhandled_uris);
+
+ return g_list_reverse (ret);
+}
+
+static gboolean
+file_was_cancelled (CajaFile *file)
+{
+ GError *error;
+
+ error = caja_file_get_file_info_error (file);
+ return
+ error != NULL &&
+ error->domain == G_IO_ERROR &&
+ error->code == G_IO_ERROR_CANCELLED;
+}
+
+static gboolean
+file_was_not_mounted (CajaFile *file)
+{
+ GError *error;
+
+ error = caja_file_get_file_info_error (file);
+ return
+ error != NULL &&
+ error->domain == G_IO_ERROR &&
+ error->code == G_IO_ERROR_NOT_MOUNTED;
+}
+
+static void
+activation_parameters_free (ActivateParameters *parameters)
+{
+ if (parameters->timed_wait_active)
+ {
+ eel_timed_wait_stop (cancel_activate_callback, parameters);
+ }
+
+ if (parameters->slot_info)
+ {
+ g_object_remove_weak_pointer (G_OBJECT (parameters->slot_info), (gpointer *)&parameters->slot_info);
+ }
+ if (parameters->parent_window)
+ {
+ g_object_remove_weak_pointer (G_OBJECT (parameters->parent_window), (gpointer *)&parameters->parent_window);
+ }
+ g_object_unref (parameters->cancellable);
+ launch_location_list_free (parameters->locations);
+ caja_file_list_free (parameters->mountables);
+ caja_file_list_free (parameters->start_mountables);
+ caja_file_list_free (parameters->not_mounted);
+ g_free (parameters->activation_directory);
+ g_free (parameters->timed_wait_prompt);
+ g_assert (parameters->files_handle == NULL);
+ g_free (parameters);
+}
+
+static void
+cancel_activate_callback (gpointer callback_data)
+{
+ ActivateParameters *parameters = callback_data;
+
+ parameters->timed_wait_active = FALSE;
+
+ g_cancellable_cancel (parameters->cancellable);
+
+ if (parameters->files_handle)
+ {
+ caja_file_list_cancel_call_when_ready (parameters->files_handle);
+ parameters->files_handle = NULL;
+ activation_parameters_free (parameters);
+ }
+}
+
+static void
+activation_start_timed_cancel (ActivateParameters *parameters)
+{
+ parameters->timed_wait_active = TRUE;
+ eel_timed_wait_start_with_duration
+ (DELAY_UNTIL_CANCEL_MSECS,
+ cancel_activate_callback,
+ parameters,
+ parameters->timed_wait_prompt,
+ parameters->parent_window);
+}
+
+static void
+pause_activation_timed_cancel (ActivateParameters *parameters)
+{
+ if (parameters->timed_wait_active)
+ {
+ eel_timed_wait_stop (cancel_activate_callback, parameters);
+ parameters->timed_wait_active = FALSE;
+ }
+}
+
+static void
+unpause_activation_timed_cancel (ActivateParameters *parameters)
+{
+ if (!parameters->timed_wait_active)
+ {
+ activation_start_timed_cancel (parameters);
+ }
+}
+
+
+static void
+activate_mount_op_active (GtkMountOperation *operation,
+ GParamSpec *pspec,
+ ActivateParameters *parameters)
+{
+ gboolean is_active;
+
+ g_object_get (operation, "is-showing", &is_active, NULL);
+
+ if (is_active)
+ {
+ pause_activation_timed_cancel (parameters);
+ }
+ else
+ {
+ unpause_activation_timed_cancel (parameters);
+ }
+}
+
+static gboolean
+confirm_multiple_windows (GtkWindow *parent_window,
+ int count,
+ gboolean use_tabs)
+{
+ GtkDialog *dialog;
+ char *prompt;
+ char *detail;
+ int response;
+
+ if (count <= SILENT_WINDOW_OPEN_LIMIT)
+ {
+ return TRUE;
+ }
+
+ prompt = _("Are you sure you want to open all files?");
+ if (use_tabs)
+ {
+ detail = g_strdup_printf (ngettext("This will open %d separate tab.",
+ "This will open %d separate tabs.", count), count);
+ }
+ else
+ {
+ detail = g_strdup_printf (ngettext("This will open %d separate window.",
+ "This will open %d separate windows.", count), count);
+ }
+ dialog = eel_show_yes_no_dialog (prompt, detail,
+ GTK_STOCK_OK, GTK_STOCK_CANCEL,
+ parent_window);
+ g_free (detail);
+
+ response = gtk_dialog_run (dialog);
+ gtk_object_destroy (GTK_OBJECT (dialog));
+
+ return response == GTK_RESPONSE_YES;
+}
+
+typedef struct
+{
+ CajaWindowSlotInfo *slot_info;
+ GtkWindow *parent_window;
+ CajaFile *file;
+ GList *files;
+ CajaWindowOpenMode mode;
+ CajaWindowOpenFlags flags;
+ char *activation_directory;
+ gboolean user_confirmation;
+ char *uri;
+ GDBusProxy *proxy;
+ GtkWidget *dialog;
+} ActivateParametersInstall;
+
+static void
+activate_parameters_install_free (ActivateParametersInstall *parameters_install)
+{
+ if (parameters_install->slot_info)
+ {
+ g_object_remove_weak_pointer (G_OBJECT (parameters_install->slot_info), (gpointer *)&parameters_install->slot_info);
+ }
+ if (parameters_install->parent_window)
+ {
+ g_object_remove_weak_pointer (G_OBJECT (parameters_install->parent_window), (gpointer *)&parameters_install->parent_window);
+ }
+
+ if (parameters_install->proxy != NULL)
+ {
+ g_object_unref (parameters_install->proxy);
+ }
+
+ caja_file_unref (parameters_install->file);
+ caja_file_list_free (parameters_install->files);
+ g_free (parameters_install->activation_directory);
+ g_free (parameters_install->uri);
+ g_free (parameters_install);
+}
+
+static char *
+get_application_no_mime_type_handler_message (CajaFile *file, char *uri)
+{
+ char *uri_for_display;
+ char *nice_uri;
+ char *error_message;
+ GFile *location;
+
+ /* For local files, we want to use filename if possible */
+ if (caja_file_is_local (file))
+ {
+ location = caja_file_get_location (file);
+ nice_uri = g_file_get_parse_name (location);
+ g_object_unref (location);
+ }
+ else
+ {
+ nice_uri = g_strdup (uri);
+ }
+
+ /* Truncate the URI so it doesn't get insanely wide. Note that even
+ * though the dialog uses wrapped text, if the URI doesn't contain
+ * white space then the text-wrapping code is too stupid to wrap it.
+ */
+ uri_for_display = eel_str_middle_truncate (nice_uri, MAX_URI_IN_DIALOG_LENGTH);
+ error_message = g_strdup_printf (_("Could not display \"%s\"."), uri_for_display);
+ g_free (nice_uri);
+ g_free (uri_for_display);
+ return error_message;
+}
+
+static void
+application_selected_cb (CajaOpenWithDialog *dialog,
+ GAppInfo *app,
+ gpointer user_data)
+{
+ GtkWindow *parent_window;
+ CajaFile *file;
+ GList files;
+
+ parent_window = GTK_WINDOW (user_data);
+
+ file = g_object_get_data (G_OBJECT (dialog), "mime-action:file");
+
+ files.next = NULL;
+ files.prev = NULL;
+ files.data = file;
+ caja_launch_application (app, &files, parent_window);
+}
+
+static void
+choose_program (GtkDialog *message_dialog, int response, gpointer callback_data)
+{
+ GtkWidget *dialog;
+ char *uri;
+ char *mime_type;
+ CajaFile *file;
+
+ if (response != GTK_RESPONSE_ACCEPT)
+ {
+ gtk_widget_destroy (GTK_WIDGET (message_dialog));
+ return;
+ }
+
+ file = g_object_get_data (G_OBJECT (message_dialog), "mime-action:file");
+
+ g_assert (CAJA_IS_FILE (file));
+
+ caja_file_ref (file);
+ uri = caja_file_get_uri (file);
+ mime_type = caja_file_get_mime_type (file);
+
+ dialog = caja_open_with_dialog_new (uri, mime_type, NULL);
+ g_object_set_data_full (G_OBJECT (dialog),
+ "mime-action:file",
+ caja_file_ref (file),
+ (GDestroyNotify)caja_file_unref);
+
+ gtk_window_set_screen (GTK_WINDOW (dialog),
+ gtk_widget_get_screen (GTK_WIDGET (callback_data)));
+
+ /* Destroy the message dialog after ref:ing the file */
+ gtk_widget_destroy (GTK_WIDGET (message_dialog));
+
+ gtk_widget_show (dialog);
+
+ g_signal_connect_object (dialog,
+ "application_selected",
+ G_CALLBACK (application_selected_cb),
+ callback_data,
+ 0);
+
+ g_free (uri);
+ g_free (mime_type);
+ caja_file_unref (file);
+}
+
+static void
+show_unhandled_type_error (ActivateParametersInstall *parameters)
+{
+ GtkWidget *dialog;
+
+ char *mime_type = caja_file_get_mime_type (parameters->file);
+ char *error_message = get_application_no_mime_type_handler_message (parameters->file, parameters->uri);
+ if (g_content_type_is_unknown (mime_type))
+ {
+ dialog = eel_alert_dialog_new (parameters->parent_window,
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ 0,
+ error_message,
+ _("The file is of an unknown type"));
+ }
+ else
+ {
+ char *text;
+ text = g_strdup_printf (_("There is no application installed for %s files"), g_content_type_get_description (mime_type));
+
+ dialog = eel_alert_dialog_new (parameters->parent_window,
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ 0,
+ error_message,
+ text);
+
+ g_free (text);
+ }
+
+ gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Select Application"), GTK_RESPONSE_ACCEPT);
+
+ gtk_dialog_add_button (GTK_DIALOG (dialog), GTK_STOCK_OK, GTK_RESPONSE_OK);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+
+ g_object_set_data_full (G_OBJECT (dialog),
+ "mime-action:file",
+ caja_file_ref (parameters->file),
+ (GDestroyNotify)caja_file_unref);
+
+ gtk_widget_show (GTK_WIDGET (dialog));
+
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (choose_program), parameters->parent_window);
+
+ g_free (error_message);
+ g_free (mime_type);
+}
+
+static void
+search_for_application_dbus_call_notify_cb (GDBusProxy *proxy,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ ActivateParametersInstall *parameters_install = user_data;
+ GVariant *variant;
+ GError *error = NULL;
+
+ variant = g_dbus_proxy_call_finish (proxy, result, &error);
+ if (variant == NULL)
+ {
+ if (!g_dbus_error_is_remote_error (error) ||
+ g_strcmp0 (g_dbus_error_get_remote_error (error), "org.freedesktop.PackageKit.Modify.Failed") == 0)
+ {
+ char *message;
+
+ message = g_strdup_printf ("%s\n%s",
+ _("There was an internal error trying to search for applications:"),
+ error->message);
+ eel_show_error_dialog (_("Unable to search for application"), message,
+ parameters_install->parent_window);
+ g_free (message);
+ }
+
+ g_error_free (error);
+ activate_parameters_install_free (parameters_install);
+ return;
+ }
+
+ g_variant_unref (variant);
+
+ /* activate the file again */
+ caja_mime_activate_files (parameters_install->parent_window,
+ parameters_install->slot_info,
+ parameters_install->files,
+ parameters_install->activation_directory,
+ parameters_install->mode,
+ parameters_install->flags,
+ parameters_install->user_confirmation);
+
+ activate_parameters_install_free (parameters_install);
+}
+
+static void
+search_for_application_mime_type (ActivateParametersInstall *parameters_install, const gchar *mime_type)
+{
+ GdkWindow *window;
+ guint xid = 0;
+ const char *mime_types[2];
+
+ g_assert (parameters_install->proxy != NULL);
+
+ /* get XID from parent window */
+ window = gtk_widget_get_window (GTK_WIDGET (parameters_install->parent_window));
+ if (window != NULL)
+ {
+ xid = GDK_WINDOW_XID (window);
+ }
+
+ mime_types[0] = mime_type;
+ mime_types[1] = NULL;
+
+ g_dbus_proxy_call (parameters_install->proxy,
+ "InstallMimeTypes",
+ g_variant_new ("(u^ass)",
+ xid,
+ mime_types,
+ "hide-confirm-search"),
+ G_DBUS_CALL_FLAGS_NONE,
+ G_MAXINT /* no timeout */,
+ NULL /* cancellable */,
+ (GAsyncReadyCallback) search_for_application_dbus_call_notify_cb,
+ parameters_install);
+
+ caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
+ "InstallMimeType method invoked for %s", mime_type);
+}
+
+static void
+application_unhandled_file_install (GtkDialog *dialog,
+ gint response_id,
+ ActivateParametersInstall *parameters_install)
+{
+ char *mime_type;
+
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+ parameters_install->dialog = NULL;
+
+ if (response_id == GTK_RESPONSE_YES)
+ {
+ mime_type = caja_file_get_mime_type (parameters_install->file);
+ search_for_application_mime_type (parameters_install, mime_type);
+ g_free (mime_type);
+ }
+ else
+ {
+ /* free as we're not going to get the async dbus callback */
+ activate_parameters_install_free (parameters_install);
+ }
+}
+
+static gboolean
+delete_cb (GtkDialog *dialog)
+{
+ gtk_dialog_response (dialog, GTK_RESPONSE_DELETE_EVENT);
+ return TRUE;
+}
+
+static void
+pk_proxy_appeared_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ ActivateParametersInstall *parameters_install = user_data;
+ char *mime_type;
+ char *error_message;
+ GtkWidget *dialog;
+ GDBusProxy *proxy;
+ GError *error = NULL;
+
+ proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
+
+ if (error != NULL)
+ {
+ g_warning ("Couldn't call Modify on the PackageKit interface: %s",
+ error->message);
+ g_error_free (error);
+
+ /* show an unhelpful dialog */
+ show_unhandled_type_error (parameters_install);
+ /* The callback wasn't started, so we have to free the parameters */
+ activate_parameters_install_free (parameters_install);
+
+ return;
+ }
+
+ mime_type = caja_file_get_mime_type (parameters_install->file);
+ error_message = get_application_no_mime_type_handler_message (parameters_install->file,
+ parameters_install->uri);
+ /* use a custom dialog to prompt the user to install new software */
+ dialog = gtk_message_dialog_new (parameters_install->parent_window, 0,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_YES_NO,
+ "%s", error_message);
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ _("There is no application installed for %s files.\n"
+ "Do you want to search for an application to open this file?"),
+ g_content_type_get_description (mime_type));
+ gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+
+ parameters_install->dialog = dialog;
+ parameters_install->proxy = proxy;
+
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (application_unhandled_file_install),
+ parameters_install);
+ g_signal_connect (dialog, "delete-event",
+ G_CALLBACK (delete_cb), NULL);
+ gtk_widget_show_all (dialog);
+ g_free (mime_type);
+}
+
+static void
+application_unhandled_uri (ActivateParameters *parameters, char *uri)
+{
+ gboolean show_install_mime;
+ char *mime_type;
+ CajaFile *file;
+ ActivateParametersInstall *parameters_install;
+
+ file = caja_file_get_by_uri (uri);
+
+ mime_type = caja_file_get_mime_type (file);
+
+ /* copy the parts of parameters we are interested in as the orignal will be unref'd */
+ parameters_install = g_new0 (ActivateParametersInstall, 1);
+ parameters_install->slot_info = parameters->slot_info;
+ g_object_add_weak_pointer (G_OBJECT (parameters_install->slot_info), (gpointer *)&parameters_install->slot_info);
+ if (parameters->parent_window)
+ {
+ parameters_install->parent_window = parameters->parent_window;
+ g_object_add_weak_pointer (G_OBJECT (parameters_install->parent_window), (gpointer *)&parameters_install->parent_window);
+ }
+ parameters_install->activation_directory = g_strdup (parameters->activation_directory);
+ parameters_install->file = file;
+ parameters_install->files = get_file_list_for_launch_locations (parameters->locations);
+ parameters_install->mode = parameters->mode;
+ parameters_install->flags = parameters->flags;
+ parameters_install->user_confirmation = parameters->user_confirmation;
+ parameters_install->uri = g_strdup(uri);
+
+#ifdef ENABLE_PACKAGEKIT
+ /* allow an admin to disable the PackageKit search functionality */
+ show_install_mime = eel_preferences_get_boolean (CAJA_PREFERENCES_INSTALL_MIME_ACTIVATION);
+#else
+ /* we have no install functionality */
+ show_install_mime = FALSE;
+#endif
+ /* There is no use trying to look for handlers of application/octet-stream */
+ if (g_content_type_is_unknown (mime_type))
+ {
+ show_install_mime = FALSE;
+ goto out;
+ }
+
+ if (!show_install_mime)
+ {
+ goto out;
+ }
+
+ g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ "org.freedesktop.PackageKit",
+ "/org/freedesktop/PackageKit",
+ "org.freedesktop.PackageKit.Modify",
+ NULL,
+ pk_proxy_appeared_cb,
+ parameters_install);
+
+ return;
+
+out:
+ /* show an unhelpful dialog */
+ show_unhandled_type_error (parameters_install);
+ /* The callback wasn't started, so we have to free the parameters */
+ activate_parameters_install_free (parameters_install);
+
+ g_free (mime_type);
+}
+
+typedef struct
+{
+ GtkWindow *parent_window;
+ CajaFile *file;
+} ActivateParametersDesktop;
+
+static void
+activate_parameters_desktop_free (ActivateParametersDesktop *parameters_desktop)
+{
+ if (parameters_desktop->parent_window)
+ {
+ g_object_remove_weak_pointer (G_OBJECT (parameters_desktop->parent_window), (gpointer *)&parameters_desktop->parent_window);
+ }
+ caja_file_unref (parameters_desktop->file);
+ g_free (parameters_desktop);
+}
+
+static void
+untrusted_launcher_response_callback (GtkDialog *dialog,
+ int response_id,
+ ActivateParametersDesktop *parameters)
+{
+ GdkScreen *screen;
+ char *uri;
+ GFile *file;
+
+ switch (response_id)
+ {
+ case RESPONSE_RUN:
+ screen = gtk_widget_get_screen (GTK_WIDGET (parameters->parent_window));
+ uri = caja_file_get_uri (parameters->file);
+ caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
+ "directory view activate_callback launch_desktop_file window=%p: %s",
+ parameters->parent_window, uri);
+ caja_launch_desktop_file (screen, uri, NULL,
+ parameters->parent_window);
+ g_free (uri);
+ break;
+ case RESPONSE_MARK_TRUSTED:
+ file = caja_file_get_location (parameters->file);
+ caja_file_mark_desktop_file_trusted (file,
+ parameters->parent_window,
+ TRUE,
+ NULL, NULL);
+ g_object_unref (file);
+ break;
+ default:
+ /* Just destroy dialog */
+ break;
+ }
+
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+ activate_parameters_desktop_free (parameters);
+}
+
+static void
+activate_desktop_file (ActivateParameters *parameters,
+ CajaFile *file)
+{
+ ActivateParametersDesktop *parameters_desktop;
+ char *primary, *secondary, *display_name;
+ GtkWidget *dialog;
+ GdkScreen *screen;
+ char *uri;
+
+ screen = gtk_widget_get_screen (GTK_WIDGET (parameters->parent_window));
+
+ if (!caja_file_is_trusted_link (file))
+ {
+ /* copy the parts of parameters we are interested in as the orignal will be freed */
+ parameters_desktop = g_new0 (ActivateParametersDesktop, 1);
+ if (parameters->parent_window)
+ {
+ parameters_desktop->parent_window = parameters->parent_window;
+ g_object_add_weak_pointer (G_OBJECT (parameters_desktop->parent_window), (gpointer *)&parameters_desktop->parent_window);
+ }
+ parameters_desktop->file = caja_file_ref (file);
+
+ primary = _("Untrusted application launcher");
+ display_name = caja_file_get_display_name (file);
+ secondary =
+ g_strdup_printf (_("The application launcher \"%s\" has not been marked as trusted. "
+ "If you do not know the source of this file, launching it may be unsafe."
+ ),
+ display_name);
+
+ dialog = eel_alert_dialog_new (parameters->parent_window,
+ 0,
+ GTK_MESSAGE_WARNING,
+ GTK_BUTTONS_NONE,
+ primary,
+ secondary);
+ gtk_dialog_add_button (GTK_DIALOG (dialog),
+ _("_Launch Anyway"), RESPONSE_RUN);
+ if (caja_file_can_set_permissions (file))
+ {
+ gtk_dialog_add_button (GTK_DIALOG (dialog),
+ _("Mark as _Trusted"), RESPONSE_MARK_TRUSTED);
+ }
+ gtk_dialog_add_button (GTK_DIALOG (dialog),
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_CANCEL);
+
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (untrusted_launcher_response_callback),
+ parameters_desktop);
+ gtk_widget_show (dialog);
+
+ g_free (display_name);
+ g_free (secondary);
+ return;
+ }
+
+ uri = caja_file_get_uri (file);
+ caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
+ "directory view activate_callback launch_desktop_file window=%p: %s",
+ parameters->parent_window, uri);
+ caja_launch_desktop_file (screen, uri, NULL,
+ parameters->parent_window);
+ g_free (uri);
+}
+
+static void
+activate_files (ActivateParameters *parameters)
+{
+ CajaWindowInfo *window_info;
+ CajaWindowOpenFlags flags;
+ CajaFile *file;
+ GList *launch_desktop_files;
+ GList *launch_files;
+ GList *launch_in_terminal_files;
+ GList *open_in_app_uris;
+ GList *open_in_app_parameters;
+ GList *unhandled_open_in_app_uris;
+ ApplicationLaunchParameters *one_parameters;
+ GList *open_in_view_files;
+ GList *l;
+ int count;
+ char *uri;
+ char *executable_path, *quoted_path, *name;
+ char *old_working_dir;
+ ActivationAction action;
+ GdkScreen *screen;
+ LaunchLocation *location;
+
+ screen = gtk_widget_get_screen (GTK_WIDGET (parameters->parent_window));
+
+ launch_desktop_files = NULL;
+ launch_files = NULL;
+ launch_in_terminal_files = NULL;
+ open_in_app_uris = NULL;
+ open_in_view_files = NULL;
+
+ for (l = parameters->locations; l != NULL; l = l->next)
+ {
+ location = l->data;
+ file = location->file;
+
+ if (file_was_cancelled (file))
+ {
+ continue;
+ }
+
+ action = get_activation_action (file);
+ if (action == ACTIVATION_ACTION_ASK)
+ {
+ /* Special case for executable text files, since it might be
+ * dangerous & unexpected to launch these.
+ */
+ pause_activation_timed_cancel (parameters);
+ action = get_executable_text_file_action (parameters->parent_window, file);
+ unpause_activation_timed_cancel (parameters);
+ }
+
+ switch (action)
+ {
+ case ACTIVATION_ACTION_LAUNCH_DESKTOP_FILE :
+ launch_desktop_files = g_list_prepend (launch_desktop_files, file);
+ break;
+ case ACTIVATION_ACTION_LAUNCH :
+ launch_files = g_list_prepend (launch_files, file);
+ break;
+ case ACTIVATION_ACTION_LAUNCH_IN_TERMINAL :
+ launch_in_terminal_files = g_list_prepend (launch_in_terminal_files, file);
+ break;
+ case ACTIVATION_ACTION_OPEN_IN_VIEW :
+ open_in_view_files = g_list_prepend (open_in_view_files, file);
+ break;
+ case ACTIVATION_ACTION_OPEN_IN_APPLICATION :
+ open_in_app_uris = g_list_prepend (open_in_app_uris, location->uri);
+ break;
+ case ACTIVATION_ACTION_DO_NOTHING :
+ break;
+ case ACTIVATION_ACTION_ASK :
+ g_assert_not_reached ();
+ break;
+ }
+ }
+
+ launch_desktop_files = g_list_reverse (launch_desktop_files);
+ for (l = launch_desktop_files; l != NULL; l = l->next)
+ {
+ file = CAJA_FILE (l->data);
+
+ activate_desktop_file (parameters, file);
+ }
+
+ old_working_dir = NULL;
+ if (parameters->activation_directory &&
+ (launch_files != NULL || launch_in_terminal_files != NULL))
+ {
+ old_working_dir = g_get_current_dir ();
+ g_chdir (parameters->activation_directory);
+
+ }
+
+ launch_files = g_list_reverse (launch_files);
+ for (l = launch_files; l != NULL; l = l->next)
+ {
+ file = CAJA_FILE (l->data);
+
+ uri = caja_file_get_activation_uri (file);
+ executable_path = g_filename_from_uri (uri, NULL, NULL);
+ quoted_path = g_shell_quote (executable_path);
+ name = caja_file_get_name (file);
+
+ caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
+ "directory view activate_callback launch_file window=%p: %s",
+ parameters->parent_window, quoted_path);
+
+ caja_launch_application_from_command (screen, name, quoted_path, FALSE, NULL);
+ g_free (name);
+ g_free (quoted_path);
+ g_free (executable_path);
+ g_free (uri);
+
+ }
+
+ launch_in_terminal_files = g_list_reverse (launch_in_terminal_files);
+ for (l = launch_in_terminal_files; l != NULL; l = l->next)
+ {
+ file = CAJA_FILE (l->data);
+
+ uri = caja_file_get_activation_uri (file);
+ executable_path = g_filename_from_uri (uri, NULL, NULL);
+ quoted_path = g_shell_quote (executable_path);
+ name = caja_file_get_name (file);
+
+ caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
+ "directory view activate_callback launch_in_terminal window=%p: %s",
+ parameters->parent_window, quoted_path);
+
+ caja_launch_application_from_command (screen, name, quoted_path, TRUE, NULL);
+ g_free (name);
+ g_free (quoted_path);
+ g_free (executable_path);
+ g_free (uri);
+ }
+
+ if (old_working_dir != NULL)
+ {
+ g_chdir (old_working_dir);
+ g_free (old_working_dir);
+ }
+
+ open_in_view_files = g_list_reverse (open_in_view_files);
+ count = g_list_length (open_in_view_files);
+
+ flags = parameters->flags;
+ if (count > 1)
+ {
+ if ((parameters->flags & CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW) == 0)
+ {
+ flags |= CAJA_WINDOW_OPEN_FLAG_NEW_TAB;
+ }
+ else
+ {
+ flags |= CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW;
+ }
+ }
+
+ if (parameters->slot_info != NULL &&
+ (!parameters->user_confirmation ||
+ confirm_multiple_windows (parameters->parent_window, count,
+ (flags & CAJA_WINDOW_OPEN_FLAG_NEW_TAB) != 0)))
+ {
+
+ if ((flags & CAJA_WINDOW_OPEN_FLAG_NEW_TAB) != 0 &&
+ eel_preferences_get_enum (CAJA_PREFERENCES_NEW_TAB_POSITION) ==
+ CAJA_NEW_TAB_POSITION_AFTER_CURRENT_TAB)
+ {
+ /* When inserting N tabs after the current one,
+ * we first open tab N, then tab N-1, ..., then tab 0.
+ * Each of them is appended to the current tab, i.e.
+ * prepended to the list of tabs to open.
+ */
+ open_in_view_files = g_list_reverse (open_in_view_files);
+ }
+
+
+ for (l = open_in_view_files; l != NULL; l = l->next)
+ {
+ GFile *f;
+ /* The ui should ask for navigation or object windows
+ * depending on what the current one is */
+ file = CAJA_FILE (l->data);
+
+ uri = caja_file_get_activation_uri (file);
+ f = g_file_new_for_uri (uri);
+ caja_window_slot_info_open_location (parameters->slot_info,
+ f, parameters->mode, flags, NULL);
+ g_object_unref (f);
+ g_free (uri);
+ }
+ }
+
+ open_in_app_parameters = NULL;
+ unhandled_open_in_app_uris = NULL;
+
+ if (open_in_app_uris != NULL)
+ {
+ open_in_app_uris = g_list_reverse (open_in_app_uris);
+
+ open_in_app_parameters = make_activation_parameters
+ (open_in_app_uris, &unhandled_open_in_app_uris);
+ }
+
+ for (l = open_in_app_parameters; l != NULL; l = l->next)
+ {
+ one_parameters = l->data;
+
+ caja_launch_application_by_uri (one_parameters->application,
+ one_parameters->uris,
+ parameters->parent_window);
+ application_launch_parameters_free (one_parameters);
+ }
+
+ for (l = unhandled_open_in_app_uris; l != NULL; l = l->next)
+ {
+ uri = l->data;
+
+ /* this does not block */
+ application_unhandled_uri (parameters, uri);
+ }
+
+ window_info = NULL;
+ if (parameters->slot_info != NULL)
+ {
+ window_info = caja_window_slot_info_get_window (parameters->slot_info);
+ }
+
+ if (open_in_app_parameters != NULL ||
+ unhandled_open_in_app_uris != NULL)
+ {
+ if ((parameters->flags & CAJA_WINDOW_OPEN_FLAG_CLOSE_BEHIND) != 0 &&
+ window_info != NULL &&
+ caja_window_info_get_window_type (window_info) == CAJA_WINDOW_SPATIAL)
+ {
+ caja_window_info_close (window_info);
+ }
+ }
+
+ g_list_free (launch_desktop_files);
+ g_list_free (launch_files);
+ g_list_free (launch_in_terminal_files);
+ g_list_free (open_in_view_files);
+ g_list_free (open_in_app_uris);
+ g_list_free (open_in_app_parameters);
+ g_list_free (unhandled_open_in_app_uris);
+
+ activation_parameters_free (parameters);
+}
+
+static void
+activation_mount_not_mounted_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ ActivateParameters *parameters = user_data;
+ GError *error;
+ CajaFile *file;
+ LaunchLocation *loc;
+
+ file = parameters->not_mounted->data;
+
+ error = NULL;
+ if (!g_file_mount_enclosing_volume_finish (G_FILE (source_object), res, &error))
+ {
+ if (error->domain != G_IO_ERROR ||
+ (error->code != G_IO_ERROR_CANCELLED &&
+ error->code != G_IO_ERROR_FAILED_HANDLED &&
+ error->code != G_IO_ERROR_ALREADY_MOUNTED))
+ {
+ eel_show_error_dialog (_("Unable to mount location"),
+ error->message, parameters->parent_window);
+ }
+
+ if (error->domain != G_IO_ERROR ||
+ error->code != G_IO_ERROR_ALREADY_MOUNTED)
+ {
+ loc = find_launch_location_for_file (parameters->locations,
+ file);
+ if (loc)
+ {
+ parameters->locations =
+ g_list_remove (parameters->locations, loc);
+ launch_location_free (loc);
+ }
+ }
+
+ g_error_free (error);
+ }
+
+ parameters->not_mounted = g_list_delete_link (parameters->not_mounted,
+ parameters->not_mounted);
+ caja_file_unref (file);
+
+ activation_mount_not_mounted (parameters);
+}
+
+static void
+activation_mount_not_mounted (ActivateParameters *parameters)
+{
+ CajaFile *file;
+ GFile *location;
+ LaunchLocation *loc;
+ GMountOperation *mount_op;
+ GList *l, *next, *files;
+
+ if (parameters->not_mounted != NULL)
+ {
+ file = parameters->not_mounted->data;
+ mount_op = gtk_mount_operation_new (parameters->parent_window);
+ g_mount_operation_set_password_save (mount_op, G_PASSWORD_SAVE_FOR_SESSION);
+ g_signal_connect (mount_op, "notify::is-showing",
+ G_CALLBACK (activate_mount_op_active), parameters);
+ location = caja_file_get_location (file);
+ g_file_mount_enclosing_volume (location, 0, mount_op, parameters->cancellable,
+ activation_mount_not_mounted_callback, parameters);
+ g_object_unref (location);
+ /* unref mount_op here - g_file_mount_enclosing_volume() does ref for itself */
+ g_object_unref (mount_op);
+ return;
+ }
+
+ parameters->tried_mounting = TRUE;
+
+ if (parameters->locations == NULL)
+ {
+ activation_parameters_free (parameters);
+ return;
+ }
+
+ /* once the mount is finished, refresh all attributes */
+ /* - fixes new windows not appearing after successful mount */
+ for (l = parameters->locations; l != NULL; l = next)
+ {
+ loc = l->data;
+ next = l->next;
+ caja_file_invalidate_all_attributes (loc->file);
+ }
+
+ files = get_file_list_for_launch_locations (parameters->locations);
+ caja_file_list_call_when_ready
+ (files,
+ caja_mime_actions_get_required_file_attributes () | CAJA_FILE_ATTRIBUTE_LINK_INFO,
+ &parameters->files_handle,
+ activate_callback, parameters);
+ caja_file_list_free (files);
+}
+
+
+static void
+activate_callback (GList *files, gpointer callback_data)
+{
+ ActivateParameters *parameters = callback_data;
+ GList *l, *next;
+ CajaFile *file;
+ LaunchLocation *location;
+
+ parameters->files_handle = NULL;
+
+ for (l = parameters->locations; l != NULL; l = next)
+ {
+ location = l->data;
+ file = location->file;
+ next = l->next;
+
+ if (file_was_cancelled (file))
+ {
+ launch_location_free (location);
+ parameters->locations = g_list_delete_link (parameters->locations, l);
+ continue;
+ }
+
+ if (file_was_not_mounted (file))
+ {
+ if (parameters->tried_mounting)
+ {
+ launch_location_free (location);
+ parameters->locations = g_list_delete_link (parameters->locations, l);
+ }
+ else
+ {
+ parameters->not_mounted = g_list_prepend (parameters->not_mounted,
+ caja_file_ref (file));
+ }
+ continue;
+ }
+ }
+
+
+ if (parameters->not_mounted != NULL)
+ {
+ activation_mount_not_mounted (parameters);
+ }
+ else
+ {
+ activate_files (parameters);
+ }
+}
+
+static void
+activate_activation_uris_ready_callback (GList *files_ignore,
+ gpointer callback_data)
+{
+ ActivateParameters *parameters = callback_data;
+ GList *l, *next, *files;
+ CajaFile *file;
+ LaunchLocation *location;
+
+ parameters->files_handle = NULL;
+
+ for (l = parameters->locations; l != NULL; l = next)
+ {
+ location = l->data;
+ file = location->file;
+ next = l->next;
+
+ if (file_was_cancelled (file))
+ {
+ launch_location_free (location);
+ parameters->locations = g_list_delete_link (parameters->locations, l);
+ continue;
+ }
+
+ if (caja_file_is_broken_symbolic_link (file))
+ {
+ launch_location_free (location);
+ parameters->locations = g_list_delete_link (parameters->locations, l);
+ pause_activation_timed_cancel (parameters);
+ report_broken_symbolic_link (parameters->parent_window, file);
+ unpause_activation_timed_cancel (parameters);
+ continue;
+ }
+
+ if (caja_file_get_file_type (file) == G_FILE_TYPE_MOUNTABLE &&
+ !caja_file_has_activation_uri (file))
+ {
+ /* Don't launch these... There is nothing we
+ can do */
+ launch_location_free (location);
+ parameters->locations = g_list_delete_link (parameters->locations, l);
+ continue;
+ }
+
+ }
+
+ if (parameters->locations == NULL)
+ {
+ activation_parameters_free (parameters);
+ return;
+ }
+
+ /* Convert the files to the actual activation uri files */
+ for (l = parameters->locations; l != NULL; l = l->next)
+ {
+ char *uri;
+ location = l->data;
+
+ /* We want the file for the activation URI since we care
+ * about the attributes for that, not for the original file.
+ */
+ uri = caja_file_get_activation_uri (location->file);
+ if (uri != NULL)
+ {
+ launch_location_update_from_uri (location, uri);
+ }
+ g_free (uri);
+ }
+
+
+ /* get the parameters for the actual files */
+ files = get_file_list_for_launch_locations (parameters->locations);
+ caja_file_list_call_when_ready
+ (files,
+ caja_mime_actions_get_required_file_attributes () | CAJA_FILE_ATTRIBUTE_LINK_INFO,
+ &parameters->files_handle,
+ activate_callback, parameters);
+ caja_file_list_free (files);
+}
+
+static void
+activation_get_activation_uris (ActivateParameters *parameters)
+{
+ GList *l, *files;
+ CajaFile *file;
+ LaunchLocation *location;
+
+ /* link target info might be stale, re-read it */
+ for (l = parameters->locations; l != NULL; l = l->next)
+ {
+ location = l->data;
+ file = location->file;
+
+ if (file_was_cancelled (file))
+ {
+ launch_location_free (location);
+ parameters->locations = g_list_delete_link (parameters->locations, l);
+ continue;
+ }
+
+ if (caja_file_is_symbolic_link (file))
+ {
+ caja_file_invalidate_attributes
+ (file,
+ CAJA_FILE_ATTRIBUTE_INFO |
+ CAJA_FILE_ATTRIBUTE_LINK_INFO);
+ }
+ }
+
+ if (parameters->locations == NULL)
+ {
+ activation_parameters_free (parameters);
+ return;
+ }
+
+ files = get_file_list_for_launch_locations (parameters->locations);
+ caja_file_list_call_when_ready
+ (files,
+ CAJA_FILE_ATTRIBUTE_INFO |
+ CAJA_FILE_ATTRIBUTE_LINK_INFO,
+ &parameters->files_handle,
+ activate_activation_uris_ready_callback, parameters);
+ caja_file_list_free (files);
+}
+
+static void
+activation_mountable_mounted (CajaFile *file,
+ GFile *result_location,
+ GError *error,
+ gpointer callback_data)
+{
+ ActivateParameters *parameters = callback_data;
+ CajaFile *target_file;
+ LaunchLocation *location;
+
+ /* Remove from list of files that have to be mounted */
+ parameters->mountables = g_list_remove (parameters->mountables, file);
+ caja_file_unref (file);
+
+
+ if (error == NULL)
+ {
+ /* Replace file with the result of the mount */
+ target_file = caja_file_get (result_location);
+
+ location = find_launch_location_for_file (parameters->locations,
+ file);
+ if (location)
+ {
+ launch_location_update_from_file (location, target_file);
+ }
+ caja_file_unref (target_file);
+ }
+ else
+ {
+ /* Remove failed file */
+
+ if (error->domain != G_IO_ERROR ||
+ (error->code != G_IO_ERROR_FAILED_HANDLED &&
+ error->code != G_IO_ERROR_ALREADY_MOUNTED))
+ {
+ location = find_launch_location_for_file (parameters->locations,
+ file);
+ if (location)
+ {
+ parameters->locations =
+ g_list_remove (parameters->locations,
+ location);
+ launch_location_free (location);
+ }
+ }
+
+ if (error->domain != G_IO_ERROR ||
+ (error->code != G_IO_ERROR_CANCELLED &&
+ error->code != G_IO_ERROR_FAILED_HANDLED &&
+ error->code != G_IO_ERROR_ALREADY_MOUNTED))
+ {
+ eel_show_error_dialog (_("Unable to mount location"),
+ error->message, parameters->parent_window);
+ }
+
+ if (error->code == G_IO_ERROR_CANCELLED)
+ {
+ activation_parameters_free (parameters);
+ return;
+ }
+ }
+
+ /* Mount more mountables */
+ activation_mount_mountables (parameters);
+}
+
+
+static void
+activation_mount_mountables (ActivateParameters *parameters)
+{
+ CajaFile *file;
+ GMountOperation *mount_op;
+
+ if (parameters->mountables != NULL)
+ {
+ file = parameters->mountables->data;
+ mount_op = gtk_mount_operation_new (parameters->parent_window);
+ g_mount_operation_set_password_save (mount_op, G_PASSWORD_SAVE_FOR_SESSION);
+ g_signal_connect (mount_op, "notify::is-showing",
+ G_CALLBACK (activate_mount_op_active), parameters);
+ caja_file_mount (file,
+ mount_op,
+ parameters->cancellable,
+ activation_mountable_mounted,
+ parameters);
+ g_object_unref (mount_op);
+ return;
+ }
+
+ if (parameters->mountables == NULL && parameters->start_mountables == NULL)
+ activation_get_activation_uris (parameters);
+}
+
+
+static void
+activation_mountable_started (CajaFile *file,
+ GFile *gfile_of_file,
+ GError *error,
+ gpointer callback_data)
+{
+ ActivateParameters *parameters = callback_data;
+ LaunchLocation *location;
+
+ /* Remove from list of files that have to be mounted */
+ parameters->start_mountables = g_list_remove (parameters->start_mountables, file);
+ caja_file_unref (file);
+
+ if (error == NULL)
+ {
+ /* Remove file */
+ location = find_launch_location_for_file (parameters->locations, file);
+ if (location != NULL)
+ {
+ parameters->locations = g_list_remove (parameters->locations, location);
+ launch_location_free (location);
+ }
+
+ }
+ else
+ {
+ /* Remove failed file */
+ if (error->domain != G_IO_ERROR ||
+ (error->code != G_IO_ERROR_FAILED_HANDLED))
+ {
+ location = find_launch_location_for_file (parameters->locations,
+ file);
+ if (location)
+ {
+ parameters->locations =
+ g_list_remove (parameters->locations,
+ location);
+ launch_location_free (location);
+ }
+ }
+
+ if (error->domain != G_IO_ERROR ||
+ (error->code != G_IO_ERROR_CANCELLED &&
+ error->code != G_IO_ERROR_FAILED_HANDLED))
+ {
+ eel_show_error_dialog (_("Unable to start location"),
+ error->message, NULL);
+ }
+
+ if (error->code == G_IO_ERROR_CANCELLED)
+ {
+ activation_parameters_free (parameters);
+ return;
+ }
+ }
+
+ /* Start more mountables */
+ activation_start_mountables (parameters);
+}
+
+static void
+activation_start_mountables (ActivateParameters *parameters)
+{
+ CajaFile *file;
+ GMountOperation *start_op;
+
+ if (parameters->start_mountables != NULL)
+ {
+ file = parameters->start_mountables->data;
+ start_op = gtk_mount_operation_new (parameters->parent_window);
+ g_signal_connect (start_op, "notify::is-showing",
+ G_CALLBACK (activate_mount_op_active), parameters);
+ caja_file_start (file,
+ start_op,
+ parameters->cancellable,
+ activation_mountable_started,
+ parameters);
+ g_object_unref (start_op);
+ return;
+ }
+
+ if (parameters->mountables == NULL && parameters->start_mountables == NULL)
+ activation_get_activation_uris (parameters);
+}
+
+/**
+ * caja_mime_activate_files:
+ *
+ * Activate a list of files. Each one might launch with an application or
+ * with a component. This is normally called only by subclasses.
+ * @view: FMDirectoryView in question.
+ * @files: A GList of CajaFiles to activate.
+ *
+ **/
+void
+caja_mime_activate_files (GtkWindow *parent_window,
+ CajaWindowSlotInfo *slot_info,
+ GList *files,
+ const char *launch_directory,
+ CajaWindowOpenMode mode,
+ CajaWindowOpenFlags flags,
+ gboolean user_confirmation)
+{
+ ActivateParameters *parameters;
+ char *file_name;
+ int file_count;
+ GList *l, *next;
+ CajaFile *file;
+ LaunchLocation *location;
+
+ if (files == NULL)
+ {
+ return;
+ }
+
+ caja_debug_log_with_file_list (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, files,
+ "caja_mime_activate_files window=%p",
+ parent_window);
+
+ parameters = g_new0 (ActivateParameters, 1);
+ parameters->slot_info = slot_info;
+ g_object_add_weak_pointer (G_OBJECT (parameters->slot_info), (gpointer *)&parameters->slot_info);
+ if (parent_window)
+ {
+ parameters->parent_window = parent_window;
+ g_object_add_weak_pointer (G_OBJECT (parameters->parent_window), (gpointer *)&parameters->parent_window);
+ }
+ parameters->cancellable = g_cancellable_new ();
+ parameters->activation_directory = g_strdup (launch_directory);
+ parameters->locations = launch_locations_from_file_list (files);
+ parameters->mode = mode;
+ parameters->flags = flags;
+ parameters->user_confirmation = user_confirmation;
+
+ file_count = g_list_length (files);
+ if (file_count == 1)
+ {
+ file_name = caja_file_get_display_name (files->data);
+ parameters->timed_wait_prompt = g_strdup_printf (_("Opening \"%s\"."), file_name);
+ g_free (file_name);
+ }
+ else
+ {
+ parameters->timed_wait_prompt = g_strdup_printf (ngettext ("Opening %d item.",
+ "Opening %d items.",
+ file_count),
+ file_count);
+ }
+
+
+ for (l = parameters->locations; l != NULL; l = next)
+ {
+ location = l->data;
+ file = location->file;
+ next = l->next;
+
+ if (caja_file_can_mount (file))
+ {
+ parameters->mountables = g_list_prepend (parameters->mountables,
+ caja_file_ref (file));
+ }
+
+ if (caja_file_can_start (file))
+ {
+ parameters->start_mountables = g_list_prepend (parameters->start_mountables,
+ caja_file_ref (file));
+ }
+ }
+
+ activation_start_timed_cancel (parameters);
+ if (parameters->mountables != NULL)
+ activation_mount_mountables (parameters);
+ if (parameters->start_mountables != NULL)
+ activation_start_mountables (parameters);
+ if (parameters->mountables == NULL && parameters->start_mountables == NULL)
+ activation_get_activation_uris (parameters);
+}
+
+/**
+ * caja_mime_activate_file:
+ *
+ * Activate a file in this view. This might involve switching the displayed
+ * location for the current window, or launching an application.
+ * @view: FMDirectoryView in question.
+ * @file: A CajaFile representing the file in this view to activate.
+ * @use_new_window: Should this item be opened in a new window?
+ *
+ **/
+
+void
+caja_mime_activate_file (GtkWindow *parent_window,
+ CajaWindowSlotInfo *slot_info,
+ CajaFile *file,
+ const char *launch_directory,
+ CajaWindowOpenMode mode,
+ CajaWindowOpenFlags flags)
+{
+ GList *files;
+
+ g_return_if_fail (CAJA_IS_FILE (file));
+
+ files = g_list_prepend (NULL, file);
+ caja_mime_activate_files (parent_window, slot_info, files, launch_directory, mode, flags, FALSE);
+ g_list_free (files);
+}
diff --git a/libcaja-private/caja-mime-actions.h b/libcaja-private/caja-mime-actions.h
new file mode 100644
index 00000000..6551c560
--- /dev/null
+++ b/libcaja-private/caja-mime-actions.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-mime-actions.h - uri-specific versions of mime action functions
+
+ Copyright (C) 2000 Eazel, Inc.
+
+ The Mate 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 Mate 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 Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Maciej Stachowiak <[email protected]>
+*/
+
+#ifndef CAJA_MIME_ACTIONS_H
+#define CAJA_MIME_ACTIONS_H
+
+#include <gio/gio.h>
+
+#include <libcaja-private/caja-file.h>
+#include <libcaja-private/caja-window-info.h>
+#include <libcaja-private/caja-window-slot-info.h>
+
+CajaFileAttributes caja_mime_actions_get_required_file_attributes (void);
+
+GAppInfo * caja_mime_get_default_application_for_file (CajaFile *file);
+GList * caja_mime_get_applications_for_file (CajaFile *file);
+
+GAppInfo * caja_mime_get_default_application_for_files (GList *files);
+GList * caja_mime_get_applications_for_files (GList *file);
+
+gboolean caja_mime_has_any_applications_for_file (CajaFile *file);
+gboolean caja_mime_has_any_applications_for_files (GList *files);
+
+gboolean caja_mime_file_opens_in_view (CajaFile *file);
+gboolean caja_mime_file_opens_in_external_app (CajaFile *file);
+void caja_mime_activate_files (GtkWindow *parent_window,
+ CajaWindowSlotInfo *slot_info,
+ GList *files,
+ const char *launch_directory,
+ CajaWindowOpenMode mode,
+ CajaWindowOpenFlags flags,
+ gboolean user_confirmation);
+void caja_mime_activate_file (GtkWindow *parent_window,
+ CajaWindowSlotInfo *slot_info,
+ CajaFile *file,
+ const char *launch_directory,
+ CajaWindowOpenMode mode,
+ CajaWindowOpenFlags flags);
+
+
+#endif /* CAJA_MIME_ACTIONS_H */
diff --git a/libcaja-private/caja-mime-application-chooser.c b/libcaja-private/caja-mime-application-chooser.c
new file mode 100644
index 00000000..a183f0c5
--- /dev/null
+++ b/libcaja-private/caja-mime-application-chooser.c
@@ -0,0 +1,746 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ caja-mime-application-chooser.c: an mime-application chooser
+
+ Copyright (C) 2004 Novell, Inc.
+ Copyright (C) 2007 Red Hat, Inc.
+
+ The Mate 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 Mate 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 Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Dave Camp <[email protected]>
+ Alexander Larsson <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-mime-application-chooser.h"
+
+#include "caja-open-with-dialog.h"
+#include "caja-signaller.h"
+#include "caja-file.h"
+#include <eel/eel-stock-dialogs.h>
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-string.h>
+
+#include <string.h>
+#include <glib/gi18n-lib.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+
+struct _CajaMimeApplicationChooserDetails
+{
+ char *uri;
+
+ char *content_type;
+ char *extension;
+ char *type_description;
+ char *orig_mime_type;
+
+ guint refresh_timeout;
+
+ GtkWidget *label;
+ GtkWidget *entry;
+ GtkWidget *treeview;
+ GtkWidget *remove_button;
+
+ gboolean for_multiple_files;
+
+ GtkListStore *model;
+ GtkCellRenderer *toggle_renderer;
+};
+
+enum
+{
+ COLUMN_APPINFO,
+ COLUMN_DEFAULT,
+ COLUMN_ICON,
+ COLUMN_NAME,
+ NUM_COLUMNS
+};
+
+G_DEFINE_TYPE (CajaMimeApplicationChooser, caja_mime_application_chooser, GTK_TYPE_VBOX);
+
+static void refresh_model (CajaMimeApplicationChooser *chooser);
+static void refresh_model_soon (CajaMimeApplicationChooser *chooser);
+static void mime_type_data_changed_cb (GObject *signaller,
+ gpointer user_data);
+
+static void
+caja_mime_application_chooser_finalize (GObject *object)
+{
+ CajaMimeApplicationChooser *chooser;
+
+ chooser = CAJA_MIME_APPLICATION_CHOOSER (object);
+
+ if (chooser->details->refresh_timeout)
+ {
+ g_source_remove (chooser->details->refresh_timeout);
+ }
+
+ g_signal_handlers_disconnect_by_func (caja_signaller_get_current (),
+ G_CALLBACK (mime_type_data_changed_cb),
+ chooser);
+
+
+ g_free (chooser->details->uri);
+ g_free (chooser->details->content_type);
+ g_free (chooser->details->extension);
+ g_free (chooser->details->type_description);
+ g_free (chooser->details->orig_mime_type);
+
+ g_free (chooser->details);
+
+ G_OBJECT_CLASS (caja_mime_application_chooser_parent_class)->finalize (object);
+}
+
+static void
+caja_mime_application_chooser_destroy (GtkObject *object)
+{
+ GTK_OBJECT_CLASS (caja_mime_application_chooser_parent_class)->destroy (object);
+}
+
+static void
+caja_mime_application_chooser_class_init (CajaMimeApplicationChooserClass *class)
+{
+ GObjectClass *gobject_class;
+ GtkObjectClass *object_class;
+
+ gobject_class = G_OBJECT_CLASS (class);
+ gobject_class->finalize = caja_mime_application_chooser_finalize;
+
+ object_class = GTK_OBJECT_CLASS (class);
+ object_class->destroy = caja_mime_application_chooser_destroy;
+}
+
+static void
+default_toggled_cb (GtkCellRendererToggle *renderer,
+ const char *path_str,
+ gpointer user_data)
+{
+ CajaMimeApplicationChooser *chooser;
+ GtkTreeIter iter;
+ GtkTreePath *path;
+ GError *error;
+
+ chooser = CAJA_MIME_APPLICATION_CHOOSER (user_data);
+
+ path = gtk_tree_path_new_from_string (path_str);
+ if (gtk_tree_model_get_iter (GTK_TREE_MODEL (chooser->details->model),
+ &iter, path))
+ {
+ gboolean is_default;
+ gboolean success;
+ GAppInfo *info;
+ char *message;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (chooser->details->model),
+ &iter,
+ COLUMN_DEFAULT, &is_default,
+ COLUMN_APPINFO, &info,
+ -1);
+
+ if (!is_default && info != NULL)
+ {
+ error = NULL;
+ if (chooser->details->extension)
+ {
+ success = g_app_info_set_as_default_for_extension (info,
+ chooser->details->extension,
+ &error);
+ }
+ else
+ {
+ success = g_app_info_set_as_default_for_type (info,
+ chooser->details->content_type,
+ &error);
+ }
+
+ if (!success)
+ {
+ message = g_strdup_printf (_("Could not set application as the default: %s"), error->message);
+ eel_show_error_dialog (_("Could not set as default application"),
+ message,
+ GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (chooser))));
+ g_free (message);
+ g_error_free (error);
+ }
+
+ g_signal_emit_by_name (caja_signaller_get_current (),
+ "mime_data_changed");
+ }
+ g_object_unref (info);
+ }
+ gtk_tree_path_free (path);
+}
+
+static GAppInfo *
+get_selected_application (CajaMimeApplicationChooser *chooser)
+{
+ GtkTreeIter iter;
+ GtkTreeSelection *selection;
+ GAppInfo *info;
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (chooser->details->treeview));
+
+ info = NULL;
+ if (gtk_tree_selection_get_selected (selection,
+ NULL,
+ &iter))
+ {
+ gtk_tree_model_get (GTK_TREE_MODEL (chooser->details->model),
+ &iter,
+ COLUMN_APPINFO, &info,
+ -1);
+ }
+
+ return info;
+}
+
+static void
+selection_changed_cb (GtkTreeSelection *selection,
+ gpointer user_data)
+{
+ CajaMimeApplicationChooser *chooser;
+ GAppInfo *info;
+
+ chooser = CAJA_MIME_APPLICATION_CHOOSER (user_data);
+
+ info = get_selected_application (chooser);
+ if (info)
+ {
+ gtk_widget_set_sensitive (chooser->details->remove_button,
+ g_app_info_can_remove_supports_type (info));
+
+ g_object_unref (info);
+ }
+ else
+ {
+ gtk_widget_set_sensitive (chooser->details->remove_button,
+ FALSE);
+ }
+}
+
+static GtkWidget *
+create_tree_view (CajaMimeApplicationChooser *chooser)
+{
+ GtkWidget *treeview;
+ GtkListStore *store;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *renderer;
+ GtkTreeSelection *selection;
+
+ treeview = gtk_tree_view_new ();
+ gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE);
+
+ store = gtk_list_store_new (NUM_COLUMNS,
+ G_TYPE_APP_INFO,
+ G_TYPE_BOOLEAN,
+ G_TYPE_ICON,
+ G_TYPE_STRING);
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
+ COLUMN_NAME,
+ GTK_SORT_ASCENDING);
+ gtk_tree_view_set_model (GTK_TREE_VIEW (treeview),
+ GTK_TREE_MODEL (store));
+ chooser->details->model = store;
+
+ renderer = gtk_cell_renderer_toggle_new ();
+ g_signal_connect (renderer, "toggled",
+ G_CALLBACK (default_toggled_cb),
+ chooser);
+ gtk_cell_renderer_toggle_set_radio (GTK_CELL_RENDERER_TOGGLE (renderer),
+ TRUE);
+
+ column = gtk_tree_view_column_new_with_attributes (_("Default"),
+ renderer,
+ "active",
+ COLUMN_DEFAULT,
+ NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
+ chooser->details->toggle_renderer = renderer;
+
+ renderer = gtk_cell_renderer_pixbuf_new ();
+ g_object_set (renderer, "stock-size", GTK_ICON_SIZE_LARGE_TOOLBAR, NULL);
+ column = gtk_tree_view_column_new_with_attributes (_("Icon"),
+ renderer,
+ "gicon",
+ COLUMN_ICON,
+ NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
+
+ renderer = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes (_("Name"),
+ renderer,
+ "markup",
+ COLUMN_NAME,
+ NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
+ g_signal_connect (selection, "changed",
+ G_CALLBACK (selection_changed_cb),
+ chooser);
+
+ return treeview;
+}
+
+static void
+add_clicked_cb (GtkButton *button,
+ gpointer user_data)
+{
+ CajaMimeApplicationChooser *chooser;
+ GtkWidget *dialog;
+
+ chooser = CAJA_MIME_APPLICATION_CHOOSER (user_data);
+
+ if (chooser->details->for_multiple_files)
+ {
+ dialog = caja_add_application_dialog_new_for_multiple_files (chooser->details->extension,
+ chooser->details->orig_mime_type);
+ }
+ else
+ {
+ dialog = caja_add_application_dialog_new (chooser->details->uri,
+ chooser->details->orig_mime_type);
+ }
+ gtk_window_set_screen (GTK_WINDOW (dialog),
+ gtk_widget_get_screen (GTK_WIDGET (chooser)));
+ gtk_widget_show (dialog);
+}
+
+static void
+remove_clicked_cb (GtkButton *button,
+ gpointer user_data)
+{
+ CajaMimeApplicationChooser *chooser;
+ GError *error;
+ GAppInfo *info;
+
+ chooser = CAJA_MIME_APPLICATION_CHOOSER (user_data);
+
+ info = get_selected_application (chooser);
+
+ if (info)
+ {
+ error = NULL;
+ if (!g_app_info_remove_supports_type (info,
+ chooser->details->content_type,
+ &error))
+ {
+ eel_show_error_dialog (_("Could not remove application"),
+ error->message,
+ GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (chooser))));
+ g_error_free (error);
+
+ }
+ g_signal_emit_by_name (caja_signaller_get_current (),
+ "mime_data_changed");
+ g_object_unref (info);
+ }
+}
+
+static void
+reset_clicked_cb (GtkButton *button,
+ gpointer user_data)
+{
+ CajaMimeApplicationChooser *chooser;
+
+ chooser = CAJA_MIME_APPLICATION_CHOOSER (user_data);
+
+ g_app_info_reset_type_associations (chooser->details->content_type);
+
+ g_signal_emit_by_name (caja_signaller_get_current (),
+ "mime_data_changed");
+}
+
+static void
+mime_type_data_changed_cb (GObject *signaller,
+ gpointer user_data)
+{
+ CajaMimeApplicationChooser *chooser;
+
+ chooser = CAJA_MIME_APPLICATION_CHOOSER (user_data);
+
+ refresh_model_soon (chooser);
+}
+
+static void
+caja_mime_application_chooser_init (CajaMimeApplicationChooser *chooser)
+{
+ GtkWidget *box;
+ GtkWidget *scrolled;
+ GtkWidget *button;
+
+ chooser->details = g_new0 (CajaMimeApplicationChooserDetails, 1);
+
+ chooser->details->for_multiple_files = FALSE;
+ 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_misc_set_alignment (GTK_MISC (chooser->details->label), 0.0, 0.5);
+ 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_box_pack_start (GTK_BOX (chooser), chooser->details->label,
+ FALSE, FALSE, 0);
+
+ gtk_widget_show (chooser->details->label);
+
+ scrolled = gtk_scrolled_window_new (NULL, NULL);
+
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled),
+ GTK_SHADOW_IN);
+
+ gtk_widget_show (scrolled);
+ gtk_box_pack_start (GTK_BOX (chooser), scrolled, TRUE, TRUE, 6);
+
+ chooser->details->treeview = create_tree_view (chooser);
+ gtk_widget_show (chooser->details->treeview);
+
+ gtk_container_add (GTK_CONTAINER (scrolled),
+ chooser->details->treeview);
+
+ box = gtk_hbutton_box_new ();
+ 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_from_stock (GTK_STOCK_ADD);
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (add_clicked_cb),
+ chooser);
+
+ gtk_widget_show (button);
+ gtk_container_add (GTK_CONTAINER (box), button);
+
+ button = gtk_button_new_from_stock (GTK_STOCK_REMOVE);
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (remove_clicked_cb),
+ chooser);
+
+ gtk_widget_show (button);
+ gtk_container_add (GTK_CONTAINER (box), button);
+
+ chooser->details->remove_button = button;
+
+ button = gtk_button_new_with_label (_("Reset"));
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (reset_clicked_cb),
+ chooser);
+
+ gtk_widget_show (button);
+ gtk_container_add (GTK_CONTAINER (box), button);
+
+ g_signal_connect (caja_signaller_get_current (),
+ "mime_data_changed",
+ G_CALLBACK (mime_type_data_changed_cb),
+ chooser);
+}
+
+static char *
+get_extension (const char *basename)
+{
+ char *p;
+
+ p = strrchr (basename, '.');
+
+ if (p && *(p + 1) != '\0')
+ {
+ return g_strdup (p + 1);
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+static gboolean
+refresh_model_timeout (gpointer data)
+{
+ CajaMimeApplicationChooser *chooser = data;
+
+ chooser->details->refresh_timeout = 0;
+
+ refresh_model (chooser);
+
+ return FALSE;
+}
+
+/* This adds a slight delay so that we're sure the mime data is
+ done writing */
+static void
+refresh_model_soon (CajaMimeApplicationChooser *chooser)
+{
+ if (chooser->details->refresh_timeout != 0)
+ return;
+
+ chooser->details->refresh_timeout =
+ g_timeout_add (300,
+ refresh_model_timeout,
+ chooser);
+}
+
+static void
+refresh_model (CajaMimeApplicationChooser *chooser)
+{
+ GList *applications;
+ GAppInfo *default_app;
+ GList *l;
+ GtkTreeSelection *selection;
+ GtkTreeViewColumn *column;
+
+ column = gtk_tree_view_get_column (GTK_TREE_VIEW (chooser->details->treeview), 0);
+ gtk_tree_view_column_set_visible (column, TRUE);
+
+ gtk_list_store_clear (chooser->details->model);
+
+ applications = g_app_info_get_all_for_type (chooser->details->content_type);
+ default_app = g_app_info_get_default_for_type (chooser->details->content_type, FALSE);
+
+ for (l = applications; l != NULL; l = l->next)
+ {
+ GtkTreeIter iter;
+ gboolean is_default;
+ GAppInfo *application;
+ char *escaped;
+ GIcon *icon;
+
+ application = l->data;
+
+ is_default = default_app && g_app_info_equal (default_app, application);
+
+ escaped = g_markup_escape_text (g_app_info_get_display_name (application), -1);
+
+ icon = g_app_info_get_icon (application);
+
+ gtk_list_store_append (chooser->details->model, &iter);
+ gtk_list_store_set (chooser->details->model, &iter,
+ COLUMN_APPINFO, application,
+ COLUMN_DEFAULT, is_default,
+ COLUMN_ICON, icon,
+ COLUMN_NAME, escaped,
+ -1);
+
+ g_free (escaped);
+ }
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (chooser->details->treeview));
+
+ if (applications)
+ {
+ g_object_set (chooser->details->toggle_renderer,
+ "visible", TRUE,
+ NULL);
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
+ }
+ else
+ {
+ GtkTreeIter iter;
+ char *name;
+
+ gtk_tree_view_column_set_visible (column, FALSE);
+ gtk_list_store_append (chooser->details->model, &iter);
+ name = g_strdup_printf ("<i>%s</i>", _("No applications selected"));
+ gtk_list_store_set (chooser->details->model, &iter,
+ COLUMN_NAME, name,
+ COLUMN_APPINFO, NULL,
+ -1);
+ g_free (name);
+
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_NONE);
+ }
+
+ if (default_app)
+ {
+ g_object_unref (default_app);
+ }
+
+ eel_g_object_list_free (applications);
+}
+
+static void
+set_extension_and_description (CajaMimeApplicationChooser *chooser,
+ const char *extension,
+ const char *mime_type)
+{
+ if (extension != NULL &&
+ g_content_type_is_unknown (mime_type))
+ {
+ chooser->details->extension = g_strdup (extension);
+ chooser->details->content_type = g_strdup_printf ("application/x-extension-%s", extension);
+ /* the %s here is a file extension */
+ chooser->details->type_description =
+ g_strdup_printf (_("%s document"), extension);
+ }
+ else
+ {
+ char *description;
+
+ chooser->details->content_type = g_strdup (mime_type);
+ description = g_content_type_get_description (mime_type);
+ if (description == NULL)
+ {
+ description = g_strdup (_("Unknown"));
+ }
+
+ chooser->details->type_description = description;
+ }
+}
+
+static gboolean
+set_uri_and_type (CajaMimeApplicationChooser *chooser,
+ const char *uri,
+ const char *mime_type)
+{
+ char *label;
+ char *name;
+ char *emname;
+ char *extension;
+ GFile *file;
+
+ chooser->details->uri = g_strdup (uri);
+
+ file = g_file_new_for_uri (uri);
+ name = g_file_get_basename (file);
+ g_object_unref (file);
+
+ chooser->details->orig_mime_type = g_strdup (mime_type);
+
+ extension = get_extension (name);
+ set_extension_and_description (CAJA_MIME_APPLICATION_CHOOSER (chooser),
+ extension, mime_type);
+ g_free (extension);
+
+ /* first %s is filename, second %s is mime-type description */
+ emname = g_strdup_printf ("<i>%s</i>", name);
+ label = g_strdup_printf (_("Select an application to open %s and other files of type \"%s\""),
+ emname, chooser->details->type_description);
+ g_free (emname);
+
+ gtk_label_set_markup (GTK_LABEL (chooser->details->label), label);
+
+ g_free (label);
+ g_free (name);
+
+ refresh_model (chooser);
+
+ return TRUE;
+}
+
+static char *
+get_extension_from_file (CajaFile *nfile)
+{
+ char *name;
+ char *extension;
+
+ name = caja_file_get_name (nfile);
+ extension = get_extension (name);
+
+ g_free (name);
+
+ return extension;
+}
+
+static gboolean
+set_uri_and_type_for_multiple_files (CajaMimeApplicationChooser *chooser,
+ GList *uris,
+ const char *mime_type)
+{
+ char *label;
+ char *first_extension;
+ gboolean same_extension;
+ GList *iter;
+
+ chooser->details->for_multiple_files = TRUE;
+ chooser->details->uri = NULL;
+ chooser->details->orig_mime_type = g_strdup (mime_type);
+ same_extension = TRUE;
+ first_extension = get_extension_from_file (CAJA_FILE (uris->data));
+ iter = uris->next;
+
+ while (iter != NULL)
+ {
+ char *extension_current;
+
+ extension_current = get_extension_from_file (CAJA_FILE (iter->data));
+ if (eel_strcmp (first_extension, extension_current))
+ {
+ same_extension = FALSE;
+ g_free (extension_current);
+ break;
+ }
+ iter = iter->next;
+
+ g_free (extension_current);
+ }
+ if (!same_extension)
+ {
+ set_extension_and_description (CAJA_MIME_APPLICATION_CHOOSER (chooser),
+ NULL, mime_type);
+ }
+ else
+ {
+ set_extension_and_description (CAJA_MIME_APPLICATION_CHOOSER (chooser),
+ first_extension, mime_type);
+ }
+
+ g_free (first_extension);
+
+ label = g_strdup_printf (_("Open all files of type \"%s\" with:"),
+ chooser->details->type_description);
+ gtk_label_set_markup (GTK_LABEL (chooser->details->label), label);
+
+ g_free (label);
+
+ refresh_model (chooser);
+
+ return TRUE;
+}
+
+GtkWidget *
+caja_mime_application_chooser_new (const char *uri,
+ const char *mime_type)
+{
+ GtkWidget *chooser;
+
+ chooser = gtk_widget_new (CAJA_TYPE_MIME_APPLICATION_CHOOSER, NULL);
+
+ set_uri_and_type (CAJA_MIME_APPLICATION_CHOOSER (chooser), uri, mime_type);
+
+ return chooser;
+}
+
+GtkWidget *
+caja_mime_application_chooser_new_for_multiple_files (GList *uris,
+ const char *mime_type)
+{
+ GtkWidget *chooser;
+
+ chooser = gtk_widget_new (CAJA_TYPE_MIME_APPLICATION_CHOOSER, NULL);
+
+ set_uri_and_type_for_multiple_files (CAJA_MIME_APPLICATION_CHOOSER (chooser),
+ uris, mime_type);
+
+ return chooser;
+}
+
diff --git a/libcaja-private/caja-mime-application-chooser.h b/libcaja-private/caja-mime-application-chooser.h
new file mode 100644
index 00000000..ed1ec683
--- /dev/null
+++ b/libcaja-private/caja-mime-application-chooser.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ caja-mime-application-chooser.c: Manages applications for mime types
+
+ Copyright (C) 2004 Novell, Inc.
+
+ The Mate 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 Mate 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 Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Dave Camp <[email protected]>
+*/
+
+#ifndef CAJA_MIME_APPLICATION_CHOOSER_H
+#define CAJA_MIME_APPLICATION_CHOOSER_H
+
+#include <gtk/gtk.h>
+
+#define CAJA_TYPE_MIME_APPLICATION_CHOOSER (caja_mime_application_chooser_get_type ())
+#define CAJA_MIME_APPLICATION_CHOOSER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_MIME_APPLICATION_CHOOSER, CajaMimeApplicationChooser))
+#define CAJA_MIME_APPLICATION_CHOOSER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_MIME_APPLICATION_CHOOSER, CajaMimeApplicationChooserClass))
+#define CAJA_IS_MIME_APPLICATION_CHOOSER(obj) (G_TYPE_INSTANCE_CHECK_TYPE ((obj), CAJA_TYPE_MIME_APPLICATION_CHOOSER)
+
+typedef struct _CajaMimeApplicationChooser CajaMimeApplicationChooser;
+typedef struct _CajaMimeApplicationChooserClass CajaMimeApplicationChooserClass;
+typedef struct _CajaMimeApplicationChooserDetails CajaMimeApplicationChooserDetails;
+
+struct _CajaMimeApplicationChooser
+{
+ GtkVBox parent;
+ CajaMimeApplicationChooserDetails *details;
+};
+
+struct _CajaMimeApplicationChooserClass
+{
+ GtkVBoxClass parent_class;
+};
+
+GType caja_mime_application_chooser_get_type (void);
+GtkWidget* caja_mime_application_chooser_new (const char *uri,
+ const char *mime_type);
+GtkWidget* caja_mime_application_chooser_new_for_multiple_files (GList *uris,
+ const char *mime_type);
+
+#endif /* CAJA_MIME_APPLICATION_CHOOSER_H */
diff --git a/libcaja-private/caja-module.c b/libcaja-private/caja-module.c
new file mode 100644
index 00000000..3cadd59e
--- /dev/null
+++ b/libcaja-private/caja-module.c
@@ -0,0 +1,293 @@
+/*
+ * caja-module.h - Interface to caja 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, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Author: Dave Camp <[email protected]>
+ *
+ */
+
+#include <config.h>
+#include "caja-module.h"
+
+#include <eel/eel-gtk-macros.h>
+#include <eel/eel-debug.h>
+#include <gmodule.h>
+
+#define CAJA_TYPE_MODULE (caja_module_get_type ())
+#define CAJA_MODULE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_MODULE, CajaModule))
+#define CAJA_MODULE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_MODULE, CajaModule))
+#define CAJA_IS_MODULE(obj) (G_TYPE_INSTANCE_CHECK_TYPE ((obj), CAJA_TYPE_MODULE))
+#define CAJA_IS_MODULE_CLASS(klass) (G_TYPE_CLASS_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_MODULE))
+
+typedef struct _CajaModule CajaModule;
+typedef struct _CajaModuleClass CajaModuleClass;
+
+struct _CajaModule
+{
+ GTypeModule parent;
+
+ GModule *library;
+
+ char *path;
+
+ void (*initialize) (GTypeModule *module);
+ void (*shutdown) (void);
+
+ void (*list_types) (const GType **types,
+ int *num_types);
+
+};
+
+struct _CajaModuleClass
+{
+ GTypeModuleClass parent;
+};
+
+static GList *module_objects = NULL;
+
+static GType caja_module_get_type (void);
+
+G_DEFINE_TYPE (CajaModule, caja_module, G_TYPE_TYPE_MODULE);
+#define parent_class caja_module_parent_class
+
+static gboolean
+caja_module_load (GTypeModule *gmodule)
+{
+ CajaModule *module;
+
+ module = CAJA_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;
+ }
+
+ if (!g_module_symbol (module->library,
+ "caja_module_initialize",
+ (gpointer *)&module->initialize) ||
+ !g_module_symbol (module->library,
+ "caja_module_shutdown",
+ (gpointer *)&module->shutdown) ||
+ !g_module_symbol (module->library,
+ "caja_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
+caja_module_unload (GTypeModule *gmodule)
+{
+ CajaModule *module;
+
+ module = CAJA_MODULE (gmodule);
+
+ module->shutdown ();
+
+ g_module_close (module->library);
+
+ module->initialize = NULL;
+ module->shutdown = NULL;
+ module->list_types = NULL;
+}
+
+static void
+caja_module_finalize (GObject *object)
+{
+ CajaModule *module;
+
+ module = CAJA_MODULE (object);
+
+ g_free (module->path);
+
+ EEL_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
+}
+
+static void
+caja_module_init (CajaModule *module)
+{
+}
+
+static void
+caja_module_class_init (CajaModuleClass *class)
+{
+ G_OBJECT_CLASS (class)->finalize = caja_module_finalize;
+ G_TYPE_MODULE_CLASS (class)->load = caja_module_load;
+ G_TYPE_MODULE_CLASS (class)->unload = caja_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 (CajaModule *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;
+ }
+ caja_module_add_type (types[i]);
+ }
+}
+
+static CajaModule *
+caja_module_load_file (const char *filename)
+{
+ CajaModule *module;
+
+ module = g_object_new (CAJA_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);
+ caja_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
+caja_module_setup (void)
+{
+ static gboolean initialized = FALSE;
+
+ if (!initialized)
+ {
+ initialized = TRUE;
+
+ load_module_dir (CAJA_EXTENSIONDIR);
+
+ eel_debug_call_at_shutdown (free_module_objects);
+ }
+}
+
+GList *
+caja_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
+caja_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
+caja_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/libcaja-private/caja-module.h b/libcaja-private/caja-module.h
new file mode 100644
index 00000000..c920e3f8
--- /dev/null
+++ b/libcaja-private/caja-module.h
@@ -0,0 +1,46 @@
+/*
+ * caja-module.h - Interface to caja 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, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Author: Dave Camp <[email protected]>
+ *
+ */
+
+#ifndef CAJA_MODULE_H
+#define CAJA_MODULE_H
+
+#include <glib-object.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+ void caja_module_setup (void);
+ GList *caja_module_get_extensions_for_type (GType type);
+ void caja_module_extension_list_free (GList *list);
+
+
+ /* Add a type to the module interface - allows caja to add its own modules
+ * without putting them in separate shared libraries */
+ void caja_module_add_type (GType type);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/libcaja-private/caja-monitor.c b/libcaja-private/caja-monitor.c
new file mode 100644
index 00000000..0a8a2e34
--- /dev/null
+++ b/libcaja-private/caja-monitor.c
@@ -0,0 +1,157 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-monitor.c: file and directory change monitoring for caja
+
+ 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Seth Nickell <[email protected]>
+ Darin Adler <[email protected]>
+ Alex Graveley <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-monitor.h"
+#include "caja-file-changes-queue.h"
+#include "caja-file-utilities.h"
+
+#include <gio/gio.h>
+
+struct CajaMonitor
+{
+ GFileMonitor *monitor;
+};
+
+gboolean
+caja_monitor_active (void)
+{
+ static gboolean tried_monitor = FALSE;
+ static gboolean monitor_success;
+ GFileMonitor *dir_monitor;
+ GFile *file;
+
+ if (tried_monitor == FALSE)
+ {
+ file = g_file_new_for_path (g_get_home_dir ());
+ dir_monitor = g_file_monitor_directory (file, G_FILE_MONITOR_NONE, NULL, NULL);
+ g_object_unref (file);
+
+ monitor_success = (dir_monitor != NULL);
+ if (dir_monitor)
+ {
+ g_object_unref (dir_monitor);
+ }
+
+ tried_monitor = TRUE;
+ }
+
+ return monitor_success;
+}
+
+static gboolean call_consume_changes_idle_id = 0;
+
+static gboolean
+call_consume_changes_idle_cb (gpointer not_used)
+{
+ caja_file_changes_consume_changes (TRUE);
+ call_consume_changes_idle_id = 0;
+ return FALSE;
+}
+
+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:
+ caja_file_changes_queue_file_changed (child);
+ break;
+ case G_FILE_MONITOR_EVENT_DELETED:
+ caja_file_changes_queue_file_removed (child);
+ break;
+ case G_FILE_MONITOR_EVENT_CREATED:
+ caja_file_changes_queue_file_added (child);
+ break;
+
+ case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
+ /* TODO: Do something */
+ break;
+ case G_FILE_MONITOR_EVENT_UNMOUNTED:
+ /* TODO: Do something */
+ break;
+ }
+
+ g_free (uri);
+ g_free (to_uri);
+
+ if (call_consume_changes_idle_id == 0)
+ {
+ call_consume_changes_idle_id =
+ g_idle_add (call_consume_changes_idle_cb, NULL);
+ }
+}
+
+CajaMonitor *
+caja_monitor_directory (GFile *location)
+{
+ GFileMonitor *dir_monitor;
+ CajaMonitor *ret;
+
+ dir_monitor = g_file_monitor_directory (location, G_FILE_MONITOR_WATCH_MOUNTS, NULL, NULL);
+
+ ret = g_new0 (CajaMonitor, 1);
+ ret->monitor = dir_monitor;
+
+ if (ret->monitor)
+ {
+ g_signal_connect (ret->monitor, "changed", (GCallback)dir_changed, ret);
+ }
+
+ /* We return a monitor even on failure, so we can avoid later trying again */
+ return ret;
+}
+
+void
+caja_monitor_cancel (CajaMonitor *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);
+ }
+
+ g_free (monitor);
+}
diff --git a/libcaja-private/caja-monitor.h b/libcaja-private/caja-monitor.h
new file mode 100644
index 00000000..85271c95
--- /dev/null
+++ b/libcaja-private/caja-monitor.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-monitor.h: file and directory change monitoring for caja
+
+ 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Seth Nickell <[email protected]>
+ Darin Adler <[email protected]>
+*/
+
+#ifndef CAJA_MONITOR_H
+#define CAJA_MONITOR_H
+
+#include <glib.h>
+#include <gio/gio.h>
+
+typedef struct CajaMonitor CajaMonitor;
+
+gboolean caja_monitor_active (void);
+CajaMonitor *caja_monitor_directory (GFile *location);
+void caja_monitor_cancel (CajaMonitor *monitor);
+
+#endif /* CAJA_MONITOR_H */
diff --git a/libcaja-private/caja-open-with-dialog.c b/libcaja-private/caja-open-with-dialog.c
new file mode 100644
index 00000000..5471367a
--- /dev/null
+++ b/libcaja-private/caja-open-with-dialog.c
@@ -0,0 +1,1183 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ eel-open-with-dialog.c: an open-with dialog
+
+ Copyright (C) 2004 Novell, Inc.
+ Copyright (C) 2007 Red Hat, Inc.
+
+ The Mate 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 Mate 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 Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Dave Camp <[email protected]>
+ Alexander Larsson <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-open-with-dialog.h"
+#include "caja-signaller.h"
+
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-stock-dialogs.h>
+
+#include <string.h>
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+
+#define sure_string(s) ((const char *)((s)!=NULL?(s):""))
+#define DESKTOP_ENTRY_GROUP "Desktop Entry"
+
+struct _CajaOpenWithDialogDetails
+{
+ GAppInfo *selected_app_info;
+
+ char *content_type;
+ char *extension;
+
+ GtkWidget *label;
+ GtkWidget *entry;
+ GtkWidget *button;
+ GtkWidget *checkbox;
+
+ GtkWidget *desc_label;
+
+ GtkWidget *open_label;
+
+ GtkWidget *program_list;
+ GtkListStore *program_list_store;
+ GSList *add_icon_paths;
+ gint add_items_idle_id;
+ gint add_icons_idle_id;
+
+ gboolean add_mode;
+};
+
+enum
+{
+ COLUMN_APP_INFO,
+ COLUMN_ICON,
+ COLUMN_GICON,
+ COLUMN_NAME,
+ COLUMN_COMMENT,
+ COLUMN_EXEC,
+ NUM_COLUMNS
+};
+
+enum
+{
+ RESPONSE_OPEN,
+ RESPONSE_REMOVE
+};
+
+enum
+{
+ APPLICATION_SELECTED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+G_DEFINE_TYPE (CajaOpenWithDialog, caja_open_with_dialog, GTK_TYPE_DIALOG);
+
+static void
+caja_open_with_dialog_finalize (GObject *object)
+{
+ CajaOpenWithDialog *dialog;
+
+ dialog = CAJA_OPEN_WITH_DIALOG (object);
+
+ if (dialog->details->add_icons_idle_id)
+ {
+ g_source_remove (dialog->details->add_icons_idle_id);
+ }
+
+ if (dialog->details->add_items_idle_id)
+ {
+ g_source_remove (dialog->details->add_items_idle_id);
+ }
+
+ if (dialog->details->selected_app_info)
+ {
+ g_object_unref (dialog->details->selected_app_info);
+ }
+ g_free (dialog->details->content_type);
+ g_free (dialog->details->extension);
+
+ g_free (dialog->details);
+
+ G_OBJECT_CLASS (caja_open_with_dialog_parent_class)->finalize (object);
+}
+
+static void
+caja_open_with_dialog_destroy (GtkObject *object)
+{
+ GTK_OBJECT_CLASS (caja_open_with_dialog_parent_class)->destroy (object);
+}
+
+/* An application is valid if:
+ *
+ * 1) The file exists
+ * 2) The user has permissions to run the file
+ */
+static gboolean
+check_application (CajaOpenWithDialog *dialog)
+{
+ char *command;
+ char *path = NULL;
+ char **argv = NULL;
+ int argc;
+ GError *error = NULL;
+ gint retval = TRUE;
+
+ command = NULL;
+ if (dialog->details->selected_app_info != NULL)
+ {
+ command = g_strdup (g_app_info_get_executable (dialog->details->selected_app_info));
+ }
+
+ if (command == NULL)
+ {
+ command = g_strdup (gtk_entry_get_text (GTK_ENTRY (dialog->details->entry)));
+ }
+
+ g_shell_parse_argv (command, &argc, &argv, &error);
+ if (error)
+ {
+ eel_show_error_dialog (_("Could not run application"),
+ error->message,
+ GTK_WINDOW (dialog));
+ g_error_free (error);
+ retval = FALSE;
+ goto cleanup;
+ }
+
+ path = g_find_program_in_path (argv[0]);
+ if (!path)
+ {
+ char *error_message;
+
+ error_message = g_strdup_printf (_("Could not find '%s'"),
+ argv[0]);
+
+ eel_show_error_dialog (_("Could not find application"),
+ error_message,
+ GTK_WINDOW (dialog));
+ g_free (error_message);
+ retval = FALSE;
+ goto cleanup;
+ }
+
+cleanup:
+ g_strfreev (argv);
+ g_free (path);
+ g_free (command);
+
+ return retval;
+}
+
+/* Only called for non-desktop files */
+static char *
+get_app_name (const char *commandline, GError **error)
+{
+ char *basename;
+ char *unquoted;
+ char **argv;
+ int argc;
+
+ if (!g_shell_parse_argv (commandline,
+ &argc, &argv, error))
+ {
+ return NULL;
+ }
+
+ unquoted = g_shell_unquote (argv[0], NULL);
+ if (unquoted)
+ {
+ basename = g_path_get_basename (unquoted);
+ }
+ else
+ {
+ basename = g_strdup (argv[0]);
+ }
+
+ g_free (unquoted);
+ g_strfreev (argv);
+
+ return basename;
+}
+
+/* This will check if the application the user wanted exists will return that
+ * application. If it doesn't exist, it will create one and return that.
+ * It also sets the app info as the default for this type.
+ */
+static GAppInfo *
+add_or_find_application (CajaOpenWithDialog *dialog)
+{
+ GAppInfo *app;
+ char *app_name;
+ const char *commandline;
+ GError *error;
+ gboolean success, should_set_default;
+ char *message;
+ GList *applications;
+
+ error = NULL;
+ app = NULL;
+ if (dialog->details->selected_app_info)
+ {
+ app = g_object_ref (dialog->details->selected_app_info);
+ }
+ else
+ {
+ commandline = gtk_entry_get_text (GTK_ENTRY (dialog->details->entry));
+ app_name = get_app_name (commandline, &error);
+ if (app_name != NULL)
+ {
+ app = g_app_info_create_from_commandline (commandline,
+ app_name,
+ G_APP_INFO_CREATE_NONE,
+ &error);
+ g_free (app_name);
+ }
+ }
+
+ if (app == NULL)
+ {
+ message = g_strdup_printf (_("Could not add application to the application database: %s"), error->message);
+ eel_show_error_dialog (_("Could not add application"),
+ message,
+ GTK_WINDOW (dialog));
+ g_free (message);
+ g_error_free (error);
+ return NULL;
+ }
+
+ should_set_default = (dialog->details->add_mode) ||
+ (!dialog->details->add_mode &&
+ gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->details->checkbox)));
+ success = TRUE;
+
+ if (should_set_default)
+ {
+ if (dialog->details->content_type)
+ {
+ success = g_app_info_set_as_default_for_type (app,
+ dialog->details->content_type,
+ &error);
+ }
+ else
+ {
+ success = g_app_info_set_as_default_for_extension (app,
+ dialog->details->extension,
+ &error);
+ }
+ }
+ else
+ {
+ applications = g_app_info_get_all_for_type (dialog->details->content_type);
+ if (dialog->details->content_type && applications != NULL)
+ {
+ /* we don't care about reporting errors here */
+ g_app_info_add_supports_type (app,
+ dialog->details->content_type,
+ NULL);
+ }
+
+ if (applications != NULL)
+ {
+ eel_g_object_list_free (applications);
+ }
+ }
+
+ if (!success && should_set_default)
+ {
+ message = g_strdup_printf (_("Could not set application as the default: %s"), error->message);
+ eel_show_error_dialog (_("Could not set as default application"),
+ message,
+ GTK_WINDOW (dialog));
+ g_free (message);
+ g_error_free (error);
+ }
+
+ g_signal_emit_by_name (caja_signaller_get_current (),
+ "mime_data_changed");
+ return app;
+}
+
+static void
+emit_application_selected (CajaOpenWithDialog *dialog,
+ GAppInfo *application)
+{
+ g_signal_emit (G_OBJECT (dialog), signals[APPLICATION_SELECTED], 0,
+ application);
+}
+
+static void
+response_cb (CajaOpenWithDialog *dialog,
+ int response_id,
+ gpointer data)
+{
+ GAppInfo *application;
+
+ switch (response_id)
+ {
+ case RESPONSE_OPEN:
+ if (check_application (dialog))
+ {
+ application = add_or_find_application (dialog);
+
+ if (application)
+ {
+ emit_application_selected (dialog, application);
+ g_object_unref (application);
+
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+ }
+ }
+
+ break;
+ case RESPONSE_REMOVE:
+ if (dialog->details->selected_app_info != NULL)
+ {
+ if (g_app_info_delete (dialog->details->selected_app_info))
+ {
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ GAppInfo *info, *selected;
+
+ selected = dialog->details->selected_app_info;
+ dialog->details->selected_app_info = NULL;
+
+ model = GTK_TREE_MODEL (dialog->details->program_list_store);
+ if (gtk_tree_model_get_iter_first (model, &iter))
+ {
+ do
+ {
+ gtk_tree_model_get (model, &iter,
+ COLUMN_APP_INFO, &info,
+ -1);
+ if (g_app_info_equal (selected, info))
+ {
+ gtk_list_store_remove (dialog->details->program_list_store, &iter);
+ break;
+ }
+ }
+ while (gtk_tree_model_iter_next (model, &iter));
+ }
+
+ g_object_unref (selected);
+ }
+ }
+ break;
+ case GTK_RESPONSE_NONE:
+ case GTK_RESPONSE_DELETE_EVENT:
+ case GTK_RESPONSE_CANCEL:
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+ break;
+ default :
+ g_assert_not_reached ();
+ }
+
+}
+
+
+static void
+caja_open_with_dialog_class_init (CajaOpenWithDialogClass *class)
+{
+ GObjectClass *gobject_class;
+ GtkObjectClass *object_class;
+
+ gobject_class = G_OBJECT_CLASS (class);
+ gobject_class->finalize = caja_open_with_dialog_finalize;
+
+ object_class = GTK_OBJECT_CLASS (class);
+ object_class->destroy = caja_open_with_dialog_destroy;
+
+ signals[APPLICATION_SELECTED] =
+ g_signal_new ("application_selected",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaOpenWithDialogClass,
+ application_selected),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE,
+ 1, G_TYPE_POINTER);
+}
+
+static void
+chooser_response_cb (GtkFileChooser *chooser,
+ int response,
+ gpointer user_data)
+{
+ CajaOpenWithDialog *dialog;
+
+ dialog = CAJA_OPEN_WITH_DIALOG (user_data);
+
+ if (response == GTK_RESPONSE_OK)
+ {
+ char *filename;
+
+ filename = gtk_file_chooser_get_filename (chooser);
+
+ if (filename)
+ {
+ char *quoted_text;
+
+ quoted_text = g_shell_quote (filename);
+
+ gtk_entry_set_text (GTK_ENTRY (dialog->details->entry),
+ quoted_text);
+ gtk_editable_set_position (GTK_EDITABLE (dialog->details->entry), -1);
+ g_free (quoted_text);
+ g_free (filename);
+ }
+ }
+
+ gtk_widget_destroy (GTK_WIDGET (chooser));
+}
+
+static void
+browse_clicked_cb (GtkWidget *button,
+ gpointer user_data)
+{
+ CajaOpenWithDialog *dialog;
+ GtkWidget *chooser;
+
+ dialog = CAJA_OPEN_WITH_DIALOG (user_data);
+
+ chooser = gtk_file_chooser_dialog_new (_("Select an Application"),
+ GTK_WINDOW (dialog),
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ GTK_STOCK_CANCEL,
+ GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OPEN,
+ GTK_RESPONSE_OK,
+ NULL);
+ gtk_window_set_destroy_with_parent (GTK_WINDOW (chooser), TRUE);
+ g_signal_connect (chooser, "response",
+ G_CALLBACK (chooser_response_cb), dialog);
+ gtk_dialog_set_default_response (GTK_DIALOG (chooser),
+ GTK_RESPONSE_OK);
+ gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (chooser), TRUE);
+ gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (chooser),
+ FALSE);
+ gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (chooser),
+ "/usr/bin");
+
+ gtk_widget_show (chooser);
+}
+
+static void
+entry_changed_cb (GtkWidget *entry,
+ CajaOpenWithDialog *dialog)
+{
+ /* We are writing in the entry, so we are not using a known appinfo anymore */
+ if (dialog->details->selected_app_info != NULL)
+ {
+ g_object_unref (dialog->details->selected_app_info);
+ dialog->details->selected_app_info = NULL;
+ }
+
+ if (gtk_entry_get_text (GTK_ENTRY (dialog->details->entry))[0] == '\000')
+ {
+ gtk_widget_set_sensitive (dialog->details->button, FALSE);
+ }
+ else
+ {
+ gtk_widget_set_sensitive (dialog->details->button, TRUE);
+ }
+}
+
+static GdkPixbuf *
+get_pixbuf_for_icon (GIcon *icon)
+{
+ GdkPixbuf *pixbuf;
+ char *filename;
+
+ pixbuf = NULL;
+ if (G_IS_FILE_ICON (icon))
+ {
+ filename = g_file_get_path (g_file_icon_get_file (G_FILE_ICON (icon)));
+ if (filename)
+ {
+ pixbuf = gdk_pixbuf_new_from_file_at_size (filename, 24, 24, NULL);
+ }
+ g_free (filename);
+ }
+ else if (G_IS_THEMED_ICON (icon))
+ {
+ const char * const *names;
+ char *icon_no_extension;
+ char *p;
+
+ names = g_themed_icon_get_names (G_THEMED_ICON (icon));
+
+ if (names != NULL && names[0] != NULL)
+ {
+ icon_no_extension = g_strdup (names[0]);
+ p = strrchr (icon_no_extension, '.');
+ if (p &&
+ (strcmp (p, ".png") == 0 ||
+ strcmp (p, ".xpm") == 0 ||
+ strcmp (p, ".svg") == 0))
+ {
+ *p = 0;
+ }
+ pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
+ icon_no_extension, 24, 0, NULL);
+ g_free (icon_no_extension);
+ }
+ }
+ return pixbuf;
+}
+
+static gboolean
+caja_open_with_dialog_add_icon_idle (CajaOpenWithDialog *dialog)
+{
+ GtkTreeIter iter;
+ GtkTreePath *path;
+ GdkPixbuf *pixbuf;
+ GIcon *icon;
+ gboolean long_operation;
+
+ long_operation = FALSE;
+ do
+ {
+ if (!dialog->details->add_icon_paths)
+ {
+ dialog->details->add_icons_idle_id = 0;
+ return FALSE;
+ }
+
+ path = dialog->details->add_icon_paths->data;
+ dialog->details->add_icon_paths->data = NULL;
+ dialog->details->add_icon_paths = g_slist_delete_link (dialog->details->add_icon_paths,
+ dialog->details->add_icon_paths);
+
+ if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (dialog->details->program_list_store),
+ &iter, path))
+ {
+ gtk_tree_path_free (path);
+ continue;
+ }
+
+ gtk_tree_path_free (path);
+
+ gtk_tree_model_get (GTK_TREE_MODEL (dialog->details->program_list_store), &iter,
+ COLUMN_GICON, &icon, -1);
+
+ if (icon == NULL)
+ {
+ continue;
+ }
+
+ pixbuf = get_pixbuf_for_icon (icon);
+ if (pixbuf)
+ {
+ long_operation = TRUE;
+ gtk_list_store_set (dialog->details->program_list_store, &iter, COLUMN_ICON, pixbuf, -1);
+ g_object_unref (pixbuf);
+ }
+
+ /* don't go back into the main loop if this wasn't very hard to do */
+ }
+ while (!long_operation);
+
+ return TRUE;
+}
+
+
+static gboolean
+caja_open_with_search_equal_func (GtkTreeModel *model,
+ int column,
+ const char *key,
+ GtkTreeIter *iter,
+ gpointer user_data)
+{
+ char *normalized_key;
+ char *name, *normalized_name;
+ char *path, *normalized_path;
+ char *basename, *normalized_basename;
+ gboolean ret;
+
+ if (key != NULL)
+ {
+ normalized_key = g_utf8_casefold (key, -1);
+ g_assert (normalized_key != NULL);
+
+ ret = TRUE;
+
+ gtk_tree_model_get (model, iter,
+ COLUMN_NAME, &name,
+ COLUMN_EXEC, &path,
+ -1);
+
+ if (name != NULL)
+ {
+ normalized_name = g_utf8_casefold (name, -1);
+ g_assert (normalized_name != NULL);
+
+ if (strncmp (normalized_name, normalized_key, strlen (normalized_key)) == 0)
+ {
+ ret = FALSE;
+ }
+
+ g_free (normalized_name);
+ }
+
+ if (ret && path != NULL)
+ {
+ normalized_path = g_utf8_casefold (path, -1);
+ g_assert (normalized_path != NULL);
+
+ basename = g_path_get_basename (path);
+ g_assert (basename != NULL);
+
+ normalized_basename = g_utf8_casefold (basename, -1);
+ g_assert (normalized_basename != NULL);
+
+ if (strncmp (normalized_path, normalized_key, strlen (normalized_key)) == 0 ||
+ strncmp (normalized_basename, normalized_key, strlen (normalized_key)) == 0)
+ {
+ ret = FALSE;
+ }
+
+ g_free (basename);
+ g_free (normalized_basename);
+ g_free (normalized_path);
+ }
+
+ g_free (name);
+ g_free (path);
+ g_free (normalized_key);
+
+ return ret;
+ }
+ else
+ {
+ return TRUE;
+ }
+}
+
+
+
+static gboolean
+caja_open_with_dialog_add_items_idle (CajaOpenWithDialog *dialog)
+{
+ GtkCellRenderer *renderer;
+ GtkTreeViewColumn *column;
+ GtkTreeModel *sort;
+ GList *all_applications;
+ GList *l;
+
+ /* create list store */
+ dialog->details->program_list_store = gtk_list_store_new (NUM_COLUMNS,
+ G_TYPE_APP_INFO,
+ GDK_TYPE_PIXBUF,
+ G_TYPE_ICON,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_STRING);
+ sort = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (dialog->details->program_list_store));
+ all_applications = g_app_info_get_all ();
+
+ for (l = all_applications; l; l = l->next)
+ {
+ GAppInfo *app = l->data;
+ GtkTreeIter iter;
+ GtkTreePath *path;
+
+ if (!g_app_info_supports_uris (app) &&
+ !g_app_info_supports_files (app))
+ continue;
+
+ gtk_list_store_append (dialog->details->program_list_store, &iter);
+ gtk_list_store_set (dialog->details->program_list_store, &iter,
+ COLUMN_APP_INFO, app,
+ COLUMN_ICON, NULL,
+ COLUMN_GICON, g_app_info_get_icon (app),
+ COLUMN_NAME, g_app_info_get_display_name (app),
+ COLUMN_COMMENT, g_app_info_get_description (app),
+ COLUMN_EXEC, g_app_info_get_executable,
+ -1);
+
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (dialog->details->program_list_store), &iter);
+ if (path != NULL)
+ {
+ dialog->details->add_icon_paths = g_slist_prepend (dialog->details->add_icon_paths, path);
+ }
+ }
+ g_list_free (all_applications);
+
+ gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->details->program_list),
+ GTK_TREE_MODEL (sort));
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sort),
+ COLUMN_NAME, GTK_SORT_ASCENDING);
+ gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (dialog->details->program_list),
+ caja_open_with_search_equal_func,
+ NULL, NULL);
+
+ renderer = gtk_cell_renderer_pixbuf_new ();
+ column = gtk_tree_view_column_new ();
+ gtk_tree_view_column_pack_start (column, renderer, FALSE);
+ gtk_tree_view_column_set_attributes (column, renderer,
+ "pixbuf", COLUMN_ICON,
+ NULL);
+
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_tree_view_column_pack_start (column, renderer, TRUE);
+ gtk_tree_view_column_set_attributes (column, renderer,
+ "text", COLUMN_NAME,
+ NULL);
+ gtk_tree_view_column_set_sort_column_id (column, COLUMN_NAME);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->details->program_list), column);
+
+ dialog->details->add_icon_paths = g_slist_reverse (dialog->details->add_icon_paths);
+
+ if (!dialog->details->add_icons_idle_id)
+ {
+ dialog->details->add_icons_idle_id =
+ g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, (GSourceFunc) caja_open_with_dialog_add_icon_idle,
+ dialog, NULL);
+ }
+
+ dialog->details->add_items_idle_id = 0;
+ return FALSE;
+}
+
+static void
+program_list_selection_changed (GtkTreeSelection *selection,
+ CajaOpenWithDialog *dialog)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ GAppInfo *info;
+
+ if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+ {
+ gtk_widget_set_sensitive (dialog->details->button, FALSE);
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog),
+ RESPONSE_REMOVE,
+ FALSE);
+ return;
+ }
+
+ info = NULL;
+ gtk_tree_model_get (model, &iter,
+ COLUMN_APP_INFO, &info,
+ -1);
+
+ if (info == NULL)
+ {
+ return;
+ }
+
+ gtk_entry_set_text (GTK_ENTRY (dialog->details->entry),
+ sure_string (g_app_info_get_executable (info)));
+ gtk_label_set_text (GTK_LABEL (dialog->details->desc_label),
+ sure_string (g_app_info_get_description (info)));
+ gtk_widget_set_sensitive (dialog->details->button, TRUE);
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog),
+ RESPONSE_REMOVE,
+ g_app_info_can_delete (info));
+
+ if (dialog->details->selected_app_info)
+ {
+ g_object_unref (dialog->details->selected_app_info);
+ }
+
+ dialog->details->selected_app_info = info;
+}
+
+static void
+program_list_selection_activated (GtkTreeView *view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ CajaOpenWithDialog *dialog)
+{
+ GtkTreeSelection *selection;
+
+ /* update the entry with the info from the selection */
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->details->program_list));
+ program_list_selection_changed (selection, dialog);
+
+ gtk_dialog_response (GTK_DIALOG (&dialog->parent), RESPONSE_OPEN);
+}
+
+static void
+expander_toggled (GtkWidget *expander, CajaOpenWithDialog *dialog)
+{
+ if (gtk_expander_get_expanded (GTK_EXPANDER (expander)) == TRUE)
+ {
+ gtk_widget_grab_focus (dialog->details->entry);
+ gtk_window_resize (GTK_WINDOW (dialog), 400, 1);
+ }
+ else
+ {
+ GtkTreeSelection *selection;
+
+ gtk_widget_grab_focus (dialog->details->program_list);
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->details->program_list));
+ program_list_selection_changed (selection, dialog);
+ }
+}
+
+static void
+caja_open_with_dialog_init (CajaOpenWithDialog *dialog)
+{
+ GtkWidget *hbox;
+ GtkWidget *vbox;
+ GtkWidget *vbox2;
+ GtkWidget *label;
+ GtkWidget *align;
+ GtkWidget *scrolled_window;
+ GtkWidget *expander;
+ GtkTreeSelection *selection;
+
+ dialog->details = g_new0 (CajaOpenWithDialogDetails, 1);
+
+ gtk_window_set_title (GTK_WINDOW (dialog), _("Open With"));
+ gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE);
+ gtk_container_set_border_width (GTK_CONTAINER (dialog), 5);
+ gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+ gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE);
+
+ gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), 2);
+
+ vbox = gtk_vbox_new (FALSE, 12);
+ gtk_container_set_border_width (GTK_CONTAINER (vbox), 5);
+
+ vbox2 = gtk_vbox_new (FALSE, 6);
+ gtk_box_pack_start (GTK_BOX (vbox), vbox2, TRUE, TRUE, 0);
+
+ dialog->details->label = gtk_label_new ("");
+ gtk_misc_set_alignment (GTK_MISC (dialog->details->label), 0.0, 0.5);
+ gtk_label_set_line_wrap (GTK_LABEL (dialog->details->label), TRUE);
+ gtk_box_pack_start (GTK_BOX (vbox2), dialog->details->label,
+ FALSE, FALSE, 0);
+ gtk_widget_show (dialog->details->label);
+
+
+ scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+ gtk_widget_set_size_request (scrolled_window, 400, 300);
+
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window),
+ GTK_SHADOW_IN);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ dialog->details->program_list = gtk_tree_view_new ();
+ gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (dialog->details->program_list),
+ FALSE);
+ gtk_container_add (GTK_CONTAINER (scrolled_window), dialog->details->program_list);
+
+ gtk_box_pack_start (GTK_BOX (vbox2), scrolled_window, TRUE, TRUE, 0);
+
+ dialog->details->desc_label = gtk_label_new (_("Select an application to view its description."));
+ gtk_misc_set_alignment (GTK_MISC (dialog->details->desc_label), 0.0, 0.5);
+ gtk_label_set_justify (GTK_LABEL (dialog->details->desc_label), GTK_JUSTIFY_LEFT);
+ gtk_label_set_line_wrap (GTK_LABEL (dialog->details->desc_label), TRUE);
+ gtk_label_set_single_line_mode (GTK_LABEL (dialog->details->desc_label), FALSE);
+ gtk_box_pack_start (GTK_BOX (vbox2), dialog->details->desc_label, FALSE, FALSE, 0);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->details->program_list));
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
+ g_signal_connect (selection, "changed",
+ G_CALLBACK (program_list_selection_changed),
+ dialog);
+ g_signal_connect (dialog->details->program_list, "row-activated",
+ G_CALLBACK (program_list_selection_activated),
+ dialog);
+
+ dialog->details->add_items_idle_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
+ (GSourceFunc) caja_open_with_dialog_add_items_idle,
+ dialog, NULL);
+
+
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), vbox, TRUE, TRUE, 0);
+ gtk_widget_show_all (vbox);
+
+
+ expander = gtk_expander_new_with_mnemonic (_("_Use a custom command"));
+ gtk_box_pack_start (GTK_BOX (vbox), expander, FALSE, FALSE, 0);
+ g_signal_connect_after (expander, "activate", G_CALLBACK (expander_toggled), dialog);
+
+ gtk_widget_show (expander);
+
+ hbox = gtk_hbox_new (FALSE, 6);
+ gtk_container_add (GTK_CONTAINER (expander), hbox);
+ gtk_widget_show (hbox);
+
+ dialog->details->entry = gtk_entry_new ();
+ gtk_entry_set_activates_default (GTK_ENTRY (dialog->details->entry), TRUE);
+
+ gtk_box_pack_start (GTK_BOX (hbox), dialog->details->entry,
+ TRUE, TRUE, 0);
+ gtk_widget_show (dialog->details->entry);
+
+ dialog->details->button = gtk_button_new_with_mnemonic (_("_Browse..."));
+ g_signal_connect (dialog->details->button, "clicked",
+ G_CALLBACK (browse_clicked_cb), dialog);
+ gtk_box_pack_start (GTK_BOX (hbox), dialog->details->button, FALSE, FALSE, 0);
+ gtk_widget_show (dialog->details->button);
+
+ /* Add remember this application checkbox - only visible in open mode */
+ dialog->details->checkbox = gtk_check_button_new ();
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->details->checkbox), TRUE);
+ gtk_button_set_use_underline (GTK_BUTTON (dialog->details->checkbox), TRUE);
+ gtk_widget_show (GTK_WIDGET (dialog->details->checkbox));
+ gtk_box_pack_start (GTK_BOX (vbox), dialog->details->checkbox, FALSE, FALSE, 0);
+
+ gtk_dialog_add_button (GTK_DIALOG (dialog),
+ GTK_STOCK_REMOVE,
+ RESPONSE_REMOVE);
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog),
+ RESPONSE_REMOVE,
+ FALSE);
+
+ gtk_dialog_add_button (GTK_DIALOG (dialog),
+ GTK_STOCK_CANCEL,
+ GTK_RESPONSE_CANCEL);
+
+
+ /* Create a custom stock icon */
+ dialog->details->button = gtk_button_new ();
+
+ /* Hook up the entry to the button */
+ gtk_widget_set_sensitive (dialog->details->button, FALSE);
+ g_signal_connect (G_OBJECT (dialog->details->entry), "changed",
+ G_CALLBACK (entry_changed_cb), dialog);
+
+ hbox = gtk_hbox_new (FALSE, 2);
+ gtk_widget_show (hbox);
+
+ label = gtk_label_new_with_mnemonic (_("_Open"));
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), GTK_WIDGET (dialog->details->button));
+ gtk_widget_show (label);
+ dialog->details->open_label = label;
+
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+
+ align = gtk_alignment_new (0.5, 0.5, 0.0, 0.0);
+ gtk_widget_show (align);
+
+ gtk_widget_show (dialog->details->button);
+ gtk_widget_set_can_default (dialog->details->button, TRUE);
+
+
+ gtk_container_add (GTK_CONTAINER (align), hbox);
+ gtk_container_add (GTK_CONTAINER (dialog->details->button), align);
+
+ gtk_dialog_add_action_widget (GTK_DIALOG (dialog),
+ dialog->details->button, RESPONSE_OPEN);
+
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog),
+ RESPONSE_OPEN);
+
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (response_cb),
+ dialog);
+}
+
+static char *
+get_extension (const char *basename)
+{
+ char *p;
+
+ p = strrchr (basename, '.');
+
+ if (p && *(p + 1) != '\0')
+ {
+ return g_strdup (p + 1);
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+static void
+set_uri_and_type (CajaOpenWithDialog *dialog,
+ const char *uri,
+ const char *mime_type,
+ const char *passed_extension,
+ gboolean add_mode)
+{
+ char *label;
+ char *emname;
+ char *name, *extension;
+ char *description;
+ char *checkbox_text;
+
+ name = NULL;
+ extension = NULL;
+
+ if (uri != NULL)
+ {
+ GFile *file;
+
+ file = g_file_new_for_uri (uri);
+ name = g_file_get_basename (file);
+ g_object_unref (file);
+ }
+ if (passed_extension == NULL && name != NULL)
+ {
+ extension = get_extension (name);
+ }
+ else
+ {
+ extension = g_strdup (passed_extension);
+ }
+
+ if (extension != NULL &&
+ g_content_type_is_unknown (mime_type))
+ {
+ dialog->details->extension = g_strdup (extension);
+
+ if (name != NULL)
+ {
+ emname = g_strdup_printf ("<i>%s</i>", name);
+ if (add_mode)
+ {
+ /* first %s is a filename and second %s is a file extension */
+ label = g_strdup_printf (_("Open %s and other %s document with:"),
+ emname, dialog->details->extension);
+ }
+ else
+ {
+ /* the %s here is a file name */
+ label = g_strdup_printf (_("Open %s with:"), emname);
+ checkbox_text = g_strdup_printf (_("_Remember this application for %s documents"),
+ dialog->details->extension);
+
+ gtk_button_set_label (GTK_BUTTON (dialog->details->checkbox), checkbox_text);
+ g_free (checkbox_text);
+ }
+ g_free (emname);
+ }
+ else
+ {
+ /* Only in add mode - the %s here is a file extension */
+ label = g_strdup_printf (_("Open all %s documents with:"),
+ dialog->details->extension);
+ }
+ g_free (extension);
+ }
+ else
+ {
+ dialog->details->content_type = g_strdup (mime_type);
+ description = g_content_type_get_description (mime_type);
+
+ if (description == NULL)
+ {
+ description = g_strdup (_("Unknown"));
+ }
+
+ if (name != NULL)
+ {
+ emname = g_strdup_printf ("<i>%s</i>", name);
+ if (add_mode)
+ {
+ /* First %s is a filename, second is a description
+ * of the type, eg "plain text document" */
+ label = g_strdup_printf (_("Open %s and other \"%s\" files with:"),
+ emname, description);
+ }
+ else
+ {
+ /* %s is a filename */
+ label = g_strdup_printf (_("Open %s with:"), emname);
+ /* %s is a file type description */
+ checkbox_text = g_strdup_printf (_("_Remember this application for \"%s\" files"),
+ description);
+
+ gtk_button_set_label (GTK_BUTTON (dialog->details->checkbox), checkbox_text);
+ g_free (checkbox_text);
+ }
+ g_free (emname);
+ }
+ else
+ {
+ /* Only in add mode */
+ label = g_strdup_printf (_("Open all \"%s\" files with:"), description);
+ }
+
+ g_free (description);
+ }
+
+ dialog->details->add_mode = add_mode;
+ if (add_mode)
+ {
+ gtk_widget_hide (dialog->details->checkbox);
+
+ gtk_label_set_text_with_mnemonic (GTK_LABEL (dialog->details->open_label),
+ _("_Add"));
+ gtk_window_set_title (GTK_WINDOW (dialog), _("Add Application"));
+ }
+
+ gtk_label_set_markup (GTK_LABEL (dialog->details->label), label);
+
+ g_free (label);
+ g_free (name);
+}
+
+
+static GtkWidget *
+real_caja_open_with_dialog_new (const char *uri,
+ const char *mime_type,
+ const char *extension,
+ gboolean add_mode)
+{
+ GtkWidget *dialog;
+
+ dialog = gtk_widget_new (CAJA_TYPE_OPEN_WITH_DIALOG, NULL);
+
+ set_uri_and_type (CAJA_OPEN_WITH_DIALOG (dialog), uri, mime_type, extension, add_mode);
+
+ return dialog;
+}
+
+GtkWidget *
+caja_open_with_dialog_new (const char *uri,
+ const char *mime_type,
+ const char *extension)
+{
+ return real_caja_open_with_dialog_new (uri, mime_type, extension, FALSE);
+}
+
+GtkWidget *
+caja_add_application_dialog_new (const char *uri,
+ const char *mime_type)
+{
+ CajaOpenWithDialog *dialog;
+
+ dialog = CAJA_OPEN_WITH_DIALOG (real_caja_open_with_dialog_new (uri, mime_type, NULL, TRUE));
+
+ return GTK_WIDGET (dialog);
+}
+
+GtkWidget *
+caja_add_application_dialog_new_for_multiple_files (const char *extension,
+ const char *mime_type)
+{
+ CajaOpenWithDialog *dialog;
+
+ dialog = CAJA_OPEN_WITH_DIALOG (real_caja_open_with_dialog_new (NULL, mime_type, extension, TRUE));
+
+ return GTK_WIDGET (dialog);
+}
+
diff --git a/libcaja-private/caja-open-with-dialog.h b/libcaja-private/caja-open-with-dialog.h
new file mode 100644
index 00000000..213e7d78
--- /dev/null
+++ b/libcaja-private/caja-open-with-dialog.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ caja-open-with-dialog.c: an open-with dialog
+
+ Copyright (C) 2004 Novell, Inc.
+
+ The Mate 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 Mate 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 Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Dave Camp <[email protected]>
+*/
+
+#ifndef CAJA_OPEN_WITH_DIALOG_H
+#define CAJA_OPEN_WITH_DIALOG_H
+
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+
+#define CAJA_TYPE_OPEN_WITH_DIALOG (caja_open_with_dialog_get_type ())
+#define CAJA_OPEN_WITH_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_OPEN_WITH_DIALOG, CajaOpenWithDialog))
+#define CAJA_OPEN_WITH_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_OPEN_WITH_DIALOG, CajaOpenWithDialogClass))
+#define CAJA_IS_OPEN_WITH_DIALOG(obj) (G_TYPE_INSTANCE_CHECK_TYPE ((obj), CAJA_TYPE_OPEN_WITH_DIALOG)
+
+typedef struct _CajaOpenWithDialog CajaOpenWithDialog;
+typedef struct _CajaOpenWithDialogClass CajaOpenWithDialogClass;
+typedef struct _CajaOpenWithDialogDetails CajaOpenWithDialogDetails;
+
+struct _CajaOpenWithDialog
+{
+ GtkDialog parent;
+ CajaOpenWithDialogDetails *details;
+};
+
+struct _CajaOpenWithDialogClass
+{
+ GtkDialogClass parent_class;
+
+ void (*application_selected) (CajaOpenWithDialog *dialog,
+ GAppInfo *application);
+};
+
+GType caja_open_with_dialog_get_type (void);
+GtkWidget* caja_open_with_dialog_new (const char *uri,
+ const char *mime_type,
+ const char *extension);
+GtkWidget* caja_add_application_dialog_new (const char *uri,
+ const char *mime_type);
+GtkWidget* caja_add_application_dialog_new_for_multiple_files (const char *extension,
+ const char *mime_type);
+
+
+
+#endif /* CAJA_OPEN_WITH_DIALOG_H */
diff --git a/libcaja-private/caja-program-choosing.c b/libcaja-private/caja-program-choosing.c
new file mode 100644
index 00000000..0a4b7f24
--- /dev/null
+++ b/libcaja-private/caja-program-choosing.c
@@ -0,0 +1,509 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-program-choosing.c - functions for selecting and activating
+ programs for opening/viewing particular files.
+
+ Copyright (C) 2000 Eazel, Inc.
+
+ The Mate 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 Mate 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 Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: John Sullivan <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-program-choosing.h"
+
+#include "caja-mime-actions.h"
+#include "caja-global-preferences.h"
+#include "caja-icon-info.h"
+#include "caja-recent.h"
+#include "caja-desktop-icon-file.h"
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-mate-extensions.h>
+#include <eel/eel-stock-dialogs.h>
+#include <eel/eel-preferences.h>
+#include <eel/eel-string.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>
+
+/**
+ * application_cannot_open_location
+ *
+ * Handle the case where an application has been selected to be launched,
+ * and it cannot handle the current uri scheme. This can happen
+ * because the default application for a file type may not be able
+ * to handle some kinds of locations. We want to tell users that their
+ * default application doesn't work here, rather than switching off to
+ * a different one without them noticing.
+ *
+ * @application: The application that was to be launched.
+ * @file: The file whose location was passed as a parameter to the application
+ * @parent_window: A window to use as the parent for any error dialogs.
+ * */
+static void
+application_cannot_open_location (GAppInfo *application,
+ CajaFile *file,
+ const char *uri_scheme,
+ GtkWindow *parent_window)
+{
+#ifdef NEW_MIME_COMPLETE
+ GtkDialog *message_dialog;
+ LaunchParameters *launch_parameters;
+ char *prompt;
+ char *message;
+ char *file_name;
+ int response;
+
+ file_name = caja_file_get_display_name (file);
+
+ if (caja_mime_has_any_applications_for_file (file))
+ {
+ if (application != NULL)
+ {
+ prompt = _("Open Failed, would you like to choose another application?");
+ message = g_strdup_printf (_("\"%s\" cannot open \"%s\" because \"%s\" cannot access files at \"%s\" "
+ "locations."),
+ g_app_info_get_display_name (application), file_name,
+ g_app_info_get_display_name (application), uri_scheme);
+ }
+ else
+ {
+ prompt = _("Open Failed, would you like to choose another action?");
+ message = g_strdup_printf (_("The default action cannot open \"%s\" because it cannot access files at \"%s\" "
+ "locations."),
+ file_name, uri_scheme);
+ }
+
+ message_dialog = eel_show_yes_no_dialog (prompt,
+ message,
+ GTK_STOCK_OK,
+ GTK_STOCK_CANCEL,
+ parent_window);
+ response = gtk_dialog_run (message_dialog);
+ gtk_object_destroy (GTK_OBJECT (message_dialog));
+
+ if (response == GTK_RESPONSE_YES)
+ {
+ launch_parameters = launch_parameters_new (file, parent_window);
+ caja_choose_application_for_file
+ (file,
+ parent_window,
+ launch_application_callback,
+ launch_parameters);
+
+ }
+ g_free (message);
+ }
+ else
+ {
+ if (application != NULL)
+ {
+ prompt = g_strdup_printf (_("\"%s\" cannot open \"%s\" because \"%s\" cannot access files at \"%s\" "
+ "locations."), g_app_info_get_display_name (application), file_name,
+ g_app_info_get_display_name (application), uri_scheme);
+ message = _("No other applications are available to view this file. "
+ "If you copy this file onto your computer, you may be able to open "
+ "it.");
+ }
+ else
+ {
+ prompt = g_strdup_printf (_("The default action cannot open \"%s\" because it cannot access files at \"%s\" "
+ "locations."), file_name, uri_scheme);
+ message = _("No other actions are available to view this file. "
+ "If you copy this file onto your computer, you may be able to open "
+ "it.");
+ }
+
+ eel_show_info_dialog (prompt, message, parent_window);
+ g_free (prompt);
+ }
+
+ g_free (file_name);
+#endif
+}
+
+/**
+ * caja_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
+caja_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, caja_file_get_activation_uri (l->data));
+ }
+ uris = g_list_reverse (uris);
+ caja_launch_application_by_uri (application, uris,
+ parent_window);
+ eel_g_list_free_deep (uris);
+}
+
+void
+caja_launch_application_by_uri (GAppInfo *application,
+ GList *uris,
+ GtkWindow *parent_window)
+{
+ char *uri, *uri_scheme;
+ GList *locations, *l;
+ GFile *location;
+ CajaFile *file;
+ gboolean result;
+ GError *error;
+ GdkAppLaunchContext *launch_context;
+ CajaIconInfo *icon;
+ int count, total;
+
+ g_assert (uris != NULL);
+
+ /* count the number of uris with local paths */
+ count = 0;
+ total = g_list_length (uris);
+ locations = NULL;
+ for (l = uris; l != NULL; l = l->next)
+ {
+ uri = l->data;
+
+ location = g_file_new_for_uri (uri);
+ if (g_file_is_native (location))
+ {
+ count++;
+ }
+ locations = g_list_prepend (locations, location);
+ }
+ locations = g_list_reverse (locations);
+
+ launch_context = gdk_app_launch_context_new ();
+ if (parent_window)
+ gdk_app_launch_context_set_screen (launch_context,
+ gtk_window_get_screen (parent_window));
+
+ file = caja_file_get_by_uri (uris->data);
+ icon = caja_file_get_icon (file, 48, 0);
+ caja_file_unref (file);
+ if (icon)
+ {
+ gdk_app_launch_context_set_icon_name (launch_context,
+ caja_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)
+ {
+ if (error->domain == G_IO_ERROR &&
+ error->code == G_IO_ERROR_NOT_SUPPORTED)
+ {
+ uri_scheme = g_uri_parse_scheme (uris->data);
+ application_cannot_open_location (application,
+ file,
+ uri_scheme,
+ parent_window);
+ g_free (uri_scheme);
+ }
+ else
+ {
+#ifdef NEW_MIME_COMPLETE
+ caja_program_chooser_show_invalid_message
+ (MATE_VFS_MIME_ACTION_TYPE_APPLICATION, file, parent_window);
+#else
+ g_warning ("Cannot open app: %s\n", error->message);
+#endif
+ }
+ g_error_free (error);
+ }
+ else
+ {
+ for (l = uris; l != NULL; l = l->next)
+ {
+ file = caja_file_get_by_uri (l->data);
+ caja_recent_add_file (file, application);
+ caja_file_unref (file);
+ }
+ }
+
+ eel_g_object_list_free (locations);
+}
+
+/**
+ * caja_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
+caja_launch_application_from_command (GdkScreen *screen,
+ const char *name,
+ 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);
+
+ if (use_terminal)
+ {
+ eel_mate_open_terminal_on_screen (full_command, screen);
+ }
+ else
+ {
+ gdk_spawn_command_line_on_screen (screen, full_command, NULL);
+ }
+
+ g_free (full_command);
+}
+
+/**
+ * caja_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
+caja_launch_application_from_command_array (GdkScreen *screen,
+ const char *name,
+ 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;
+ }
+ }
+
+ if (use_terminal)
+ {
+ eel_mate_open_terminal_on_screen (full_command, screen);
+ }
+ else
+ {
+ gdk_spawn_command_line_on_screen (screen, full_command, NULL);
+ }
+
+ g_free (full_command);
+}
+
+void
+caja_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);
+
+ eel_g_object_list_free (files);
+ 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_app_launch_context_new ();
+ /* 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);
+ }
+
+ eel_g_object_list_free (files);
+ g_object_unref (context);
+ g_object_unref (app_info);
+}
diff --git a/libcaja-private/caja-program-choosing.h b/libcaja-private/caja-program-choosing.h
new file mode 100644
index 00000000..09201a29
--- /dev/null
+++ b/libcaja-private/caja-program-choosing.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-program-choosing.h - functions for selecting and activating
+ programs for opening/viewing particular files.
+
+ Copyright (C) 2000 Eazel, Inc.
+
+ The Mate 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 Mate 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 Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: John Sullivan <[email protected]>
+*/
+
+#ifndef CAJA_PROGRAM_CHOOSING_H
+#define CAJA_PROGRAM_CHOOSING_H
+
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+#include <libcaja-private/caja-file.h>
+
+typedef void (*CajaApplicationChoiceCallback) (GAppInfo *application,
+ gpointer callback_data);
+
+void caja_launch_application (GAppInfo *application,
+ GList *files,
+ GtkWindow *parent_window);
+void caja_launch_application_by_uri (GAppInfo *application,
+ GList *uris,
+ GtkWindow *parent_window);
+void caja_launch_application_from_command (GdkScreen *screen,
+ const char *name,
+ const char *command_string,
+ gboolean use_terminal,
+ ...) G_GNUC_NULL_TERMINATED;
+void caja_launch_application_from_command_array (GdkScreen *screen,
+ const char *name,
+ const char *command_string,
+ gboolean use_terminal,
+ const char * const * parameters);
+void caja_launch_desktop_file (GdkScreen *screen,
+ const char *desktop_file_uri,
+ const GList *parameter_uris,
+ GtkWindow *parent_window);
+
+#endif /* CAJA_PROGRAM_CHOOSING_H */
diff --git a/libcaja-private/caja-progress-info.c b/libcaja-private/caja-progress-info.c
new file mode 100644
index 00000000..4945e22b
--- /dev/null
+++ b/libcaja-private/caja-progress-info.c
@@ -0,0 +1,930 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Alexander Larsson <[email protected]>
+*/
+
+#include <config.h>
+#include <math.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <eel/eel-string.h>
+#include <eel/eel-glib-extensions.h>
+#include "caja-progress-info.h"
+
+enum
+{
+ CHANGED,
+ PROGRESS_CHANGED,
+ STARTED,
+ FINISHED,
+ LAST_SIGNAL
+};
+
+/* TODO:
+ * Want an icon for the operation.
+ * Add and implement cancel button
+ */
+
+#define SIGNAL_DELAY_MSEC 100
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+struct _CajaProgressInfo
+{
+ GObject parent_instance;
+
+ GCancellable *cancellable;
+
+ char *status;
+ char *details;
+ double progress;
+ 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 changed_at_idle;
+ gboolean progress_at_idle;
+};
+
+struct _CajaProgressInfoClass
+{
+ GObjectClass parent_class;
+};
+
+static GList *active_progress_infos = NULL;
+
+static GtkStatusIcon *status_icon = NULL;
+static int n_progress_ops = 0;
+
+
+G_LOCK_DEFINE_STATIC(progress_info);
+
+G_DEFINE_TYPE (CajaProgressInfo, caja_progress_info, G_TYPE_OBJECT)
+
+GList *
+caja_get_all_progress_info (void)
+{
+ GList *l;
+
+ G_LOCK (progress_info);
+
+ l = eel_g_object_list_copy (active_progress_infos);
+
+ G_UNLOCK (progress_info);
+
+ return l;
+}
+
+static void
+caja_progress_info_finalize (GObject *object)
+{
+ CajaProgressInfo *info;
+
+ info = CAJA_PROGRESS_INFO (object);
+
+ g_free (info->status);
+ g_free (info->details);
+ g_object_unref (info->cancellable);
+
+ if (G_OBJECT_CLASS (caja_progress_info_parent_class)->finalize)
+ {
+ (*G_OBJECT_CLASS (caja_progress_info_parent_class)->finalize) (object);
+ }
+}
+
+static void
+caja_progress_info_dispose (GObject *object)
+{
+ CajaProgressInfo *info;
+
+ info = CAJA_PROGRESS_INFO (object);
+
+ G_LOCK (progress_info);
+
+ /* Remove from active list in dispose, since a get_all_progress_info()
+ call later could revive the object */
+ active_progress_infos = g_list_remove (active_progress_infos, object);
+
+ /* 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
+caja_progress_info_class_init (CajaProgressInfoClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->finalize = caja_progress_info_finalize;
+ gobject_class->dispose = caja_progress_info_dispose;
+
+ signals[CHANGED] =
+ g_signal_new ("changed",
+ CAJA_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",
+ CAJA_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",
+ CAJA_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",
+ CAJA_TYPE_PROGRESS_INFO,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+}
+
+static gboolean
+delete_event (GtkWidget *widget,
+ GdkEventAny *event)
+{
+ gtk_widget_hide (widget);
+ return TRUE;
+}
+
+static void
+status_icon_activate_cb (GtkStatusIcon *icon,
+ GtkWidget *progress_window)
+{
+ if (gtk_widget_get_visible (progress_window))
+ {
+ gtk_widget_hide (progress_window);
+ }
+ else
+ {
+ gtk_window_present (GTK_WINDOW (progress_window));
+ }
+}
+
+static GtkWidget *
+get_progress_window (void)
+{
+ static GtkWidget *progress_window = NULL;
+ GtkWidget *vbox;
+
+ if (progress_window != NULL)
+ {
+ return progress_window;
+ }
+
+ progress_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_resizable (GTK_WINDOW (progress_window),
+ FALSE);
+ gtk_container_set_border_width (GTK_CONTAINER (progress_window), 10);
+
+ gtk_window_set_title (GTK_WINDOW (progress_window),
+ _("File Operations"));
+ gtk_window_set_wmclass (GTK_WINDOW (progress_window),
+ "file_progress", "Caja");
+ gtk_window_set_position (GTK_WINDOW (progress_window),
+ GTK_WIN_POS_CENTER);
+ gtk_window_set_icon_name (GTK_WINDOW (progress_window),
+ "system-file-manager");
+
+ vbox = gtk_vbox_new (FALSE, 0);
+ gtk_box_set_spacing (GTK_BOX (vbox), 5);
+
+ gtk_container_add (GTK_CONTAINER (progress_window),
+ vbox);
+
+ gtk_widget_show_all (progress_window);
+
+ g_signal_connect (progress_window,
+ "delete_event",
+ (GCallback)delete_event, NULL);
+
+ status_icon = gtk_status_icon_new_from_icon_name ("system-file-manager");
+ g_signal_connect (status_icon, "activate",
+ (GCallback)status_icon_activate_cb,
+ progress_window);
+
+ gtk_status_icon_set_visible (status_icon, FALSE);
+
+ return progress_window;
+}
+
+
+typedef struct
+{
+ GtkWidget *widget;
+ CajaProgressInfo *info;
+ GtkLabel *status;
+ GtkLabel *details;
+ GtkProgressBar *progress_bar;
+} ProgressWidgetData;
+
+static void
+progress_widget_data_free (ProgressWidgetData *data)
+{
+ g_object_unref (data->info);
+ g_free (data);
+}
+
+static void
+update_data (ProgressWidgetData *data)
+{
+ char *status, *details;
+ char *markup;
+
+ status = caja_progress_info_get_status (data->info);
+ gtk_label_set_text (data->status, status);
+ g_free (status);
+
+ details = caja_progress_info_get_details (data->info);
+ markup = g_markup_printf_escaped ("<span size='small'>%s</span>", details);
+ gtk_label_set_markup (data->details, markup);
+ g_free (details);
+ g_free (markup);
+}
+
+static void
+update_progress (ProgressWidgetData *data)
+{
+ double progress;
+
+ progress = caja_progress_info_get_progress (data->info);
+ if (progress < 0)
+ {
+ gtk_progress_bar_pulse (data->progress_bar);
+ }
+ else
+ {
+ gtk_progress_bar_set_fraction (data->progress_bar, progress);
+ }
+}
+
+static void
+update_status_icon_and_window (void)
+{
+ char *tooltip;
+
+ tooltip = g_strdup_printf (ngettext ("%'d file operation active",
+ "%'d file operations active",
+ n_progress_ops),
+ n_progress_ops);
+ gtk_status_icon_set_tooltip_text (status_icon, tooltip);
+ g_free (tooltip);
+
+ if (n_progress_ops == 0)
+ {
+ gtk_status_icon_set_visible (status_icon, FALSE);
+ gtk_widget_hide (get_progress_window ());
+ }
+ else
+ {
+ gtk_status_icon_set_visible (status_icon, TRUE);
+ }
+}
+
+static void
+op_finished (ProgressWidgetData *data)
+{
+ gtk_widget_destroy (data->widget);
+
+ n_progress_ops--;
+ update_status_icon_and_window ();
+}
+
+static void
+cancel_clicked (GtkWidget *button,
+ ProgressWidgetData *data)
+{
+ caja_progress_info_cancel (data->info);
+ gtk_widget_set_sensitive (button, FALSE);
+}
+
+
+static GtkWidget *
+progress_widget_new (CajaProgressInfo *info)
+{
+ ProgressWidgetData *data;
+ GtkWidget *label, *progress_bar, *hbox, *vbox, *box, *button, *image;
+
+ data = g_new0 (ProgressWidgetData, 1);
+ data->info = g_object_ref (info);
+
+ vbox = gtk_vbox_new (FALSE, 0);
+ gtk_box_set_spacing (GTK_BOX (vbox), 5);
+
+
+ data->widget = vbox;
+ g_object_set_data_full (G_OBJECT (data->widget),
+ "data", data,
+ (GDestroyNotify)progress_widget_data_free);
+
+ label = gtk_label_new ("status");
+ gtk_widget_set_size_request (label, 500, -1);
+ gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+ gtk_label_set_line_wrap_mode (GTK_LABEL (label), PANGO_WRAP_WORD_CHAR);
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_box_pack_start (GTK_BOX (vbox),
+ label,
+ TRUE, FALSE,
+ 0);
+ data->status = GTK_LABEL (label);
+
+ hbox = gtk_hbox_new (FALSE,10);
+
+ progress_bar = gtk_progress_bar_new ();
+ data->progress_bar = GTK_PROGRESS_BAR (progress_bar);
+ gtk_progress_bar_set_pulse_step (data->progress_bar, 0.05);
+ box = gtk_vbox_new (FALSE,0);
+ gtk_box_pack_start(GTK_BOX (box),
+ progress_bar,
+ TRUE,FALSE,
+ 0);
+ gtk_box_pack_start(GTK_BOX (hbox),
+ box,
+ TRUE,TRUE,
+ 0);
+
+ image = gtk_image_new_from_stock (GTK_STOCK_CANCEL,
+ GTK_ICON_SIZE_BUTTON);
+ button = gtk_button_new ();
+ gtk_container_add (GTK_CONTAINER (button), image);
+ gtk_box_pack_start (GTK_BOX (hbox),
+ button,
+ FALSE,FALSE,
+ 0);
+ g_signal_connect (button, "clicked", (GCallback)cancel_clicked, data);
+
+ gtk_box_pack_start (GTK_BOX (vbox),
+ hbox,
+ FALSE,FALSE,
+ 0);
+
+ label = gtk_label_new ("details");
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+ gtk_box_pack_start (GTK_BOX (vbox),
+ label,
+ TRUE, FALSE,
+ 0);
+ data->details = GTK_LABEL (label);
+
+ gtk_widget_show_all (data->widget);
+
+ update_data (data);
+ update_progress (data);
+
+ g_signal_connect_swapped (data->info,
+ "changed",
+ (GCallback)update_data, data);
+ g_signal_connect_swapped (data->info,
+ "progress_changed",
+ (GCallback)update_progress, data);
+ g_signal_connect_swapped (data->info,
+ "finished",
+ (GCallback)op_finished, data);
+
+ return data->widget;
+}
+
+static void
+handle_new_progress_info (CajaProgressInfo *info)
+{
+ GtkWidget *window, *progress;
+
+ window = get_progress_window ();
+
+ progress = progress_widget_new (info);
+ gtk_box_pack_start (GTK_BOX (gtk_bin_get_child (GTK_BIN (window))),
+ progress,
+ FALSE, FALSE, 6);
+
+ gtk_window_present (GTK_WINDOW (window));
+
+ n_progress_ops++;
+ update_status_icon_and_window ();
+}
+
+static gboolean
+new_op_started_timeout (CajaProgressInfo *info)
+{
+ if (caja_progress_info_get_is_paused (info))
+ {
+ return TRUE;
+ }
+ if (!caja_progress_info_get_is_finished (info))
+ {
+ handle_new_progress_info (info);
+ }
+ g_object_unref (info);
+ return FALSE;
+}
+
+static void
+new_op_started (CajaProgressInfo *info)
+{
+ g_signal_handlers_disconnect_by_func (info, (GCallback)new_op_started, NULL);
+ g_timeout_add_seconds (2,
+ (GSourceFunc)new_op_started_timeout,
+ g_object_ref (info));
+}
+
+static void
+caja_progress_info_init (CajaProgressInfo *info)
+{
+ info->cancellable = g_cancellable_new ();
+
+ G_LOCK (progress_info);
+ active_progress_infos = g_list_append (active_progress_infos, info);
+ G_UNLOCK (progress_info);
+
+ g_signal_connect (info, "started", (GCallback)new_op_started, NULL);
+}
+
+CajaProgressInfo *
+caja_progress_info_new (void)
+{
+ CajaProgressInfo *info;
+
+ info = g_object_new (CAJA_TYPE_PROGRESS_INFO, NULL);
+
+ return info;
+}
+
+char *
+caja_progress_info_get_status (CajaProgressInfo *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 *
+caja_progress_info_get_details (CajaProgressInfo *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
+caja_progress_info_get_progress (CajaProgressInfo *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
+caja_progress_info_cancel (CajaProgressInfo *info)
+{
+ G_LOCK (progress_info);
+
+ g_cancellable_cancel (info->cancellable);
+
+ G_UNLOCK (progress_info);
+}
+
+GCancellable *
+caja_progress_info_get_cancellable (CajaProgressInfo *info)
+{
+ GCancellable *c;
+
+ G_LOCK (progress_info);
+
+ c = g_object_ref (info->cancellable);
+
+ G_UNLOCK (progress_info);
+
+ return c;
+}
+
+gboolean
+caja_progress_info_get_is_started (CajaProgressInfo *info)
+{
+ gboolean res;
+
+ G_LOCK (progress_info);
+
+ res = info->started;
+
+ G_UNLOCK (progress_info);
+
+ return res;
+}
+
+gboolean
+caja_progress_info_get_is_finished (CajaProgressInfo *info)
+{
+ gboolean res;
+
+ G_LOCK (progress_info);
+
+ res = info->finished;
+
+ G_UNLOCK (progress_info);
+
+ return res;
+}
+
+gboolean
+caja_progress_info_get_is_paused (CajaProgressInfo *info)
+{
+ gboolean res;
+
+ G_LOCK (progress_info);
+
+ res = info->paused;
+
+ G_UNLOCK (progress_info);
+
+ return res;
+}
+
+static gboolean
+idle_callback (gpointer data)
+{
+ CajaProgressInfo *info = data;
+ gboolean start_at_idle;
+ gboolean finish_at_idle;
+ gboolean changed_at_idle;
+ gboolean progress_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;
+
+ info->start_at_idle = FALSE;
+ info->finish_at_idle = FALSE;
+ info->changed_at_idle = FALSE;
+ info->progress_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);
+ }
+
+ g_object_unref (info);
+
+ return FALSE;
+}
+
+/* Called with lock held */
+static void
+queue_idle (CajaProgressInfo *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);
+ }
+}
+
+void
+caja_progress_info_pause (CajaProgressInfo *info)
+{
+ G_LOCK (progress_info);
+
+ if (!info->paused)
+ {
+ info->paused = TRUE;
+ }
+
+ G_UNLOCK (progress_info);
+}
+
+void
+caja_progress_info_resume (CajaProgressInfo *info)
+{
+ G_LOCK (progress_info);
+
+ if (info->paused)
+ {
+ info->paused = FALSE;
+ }
+
+ G_UNLOCK (progress_info);
+}
+
+void
+caja_progress_info_start (CajaProgressInfo *info)
+{
+ G_LOCK (progress_info);
+
+ if (!info->started)
+ {
+ info->started = TRUE;
+
+ info->start_at_idle = TRUE;
+ queue_idle (info, TRUE);
+ }
+
+ G_UNLOCK (progress_info);
+}
+
+void
+caja_progress_info_finish (CajaProgressInfo *info)
+{
+ G_LOCK (progress_info);
+
+ if (!info->finished)
+ {
+ info->finished = TRUE;
+
+ info->finish_at_idle = TRUE;
+ queue_idle (info, TRUE);
+ }
+
+ G_UNLOCK (progress_info);
+}
+
+void
+caja_progress_info_take_status (CajaProgressInfo *info,
+ char *status)
+{
+ G_LOCK (progress_info);
+
+ if (eel_strcmp (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
+caja_progress_info_set_status (CajaProgressInfo *info,
+ const char *status)
+{
+ G_LOCK (progress_info);
+
+ if (eel_strcmp (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
+caja_progress_info_take_details (CajaProgressInfo *info,
+ char *details)
+{
+ G_LOCK (progress_info);
+
+ if (eel_strcmp (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
+caja_progress_info_set_details (CajaProgressInfo *info,
+ const char *details)
+{
+ G_LOCK (progress_info);
+
+ if (eel_strcmp (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
+caja_progress_info_pulse_progress (CajaProgressInfo *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
+caja_progress_info_set_progress (CajaProgressInfo *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);
+}
diff --git a/libcaja-private/caja-progress-info.h b/libcaja-private/caja-progress-info.h
new file mode 100644
index 00000000..4c0d21da
--- /dev/null
+++ b/libcaja-private/caja-progress-info.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Alexander Larsson <[email protected]>
+*/
+
+#ifndef CAJA_PROGRESS_INFO_H
+#define CAJA_PROGRESS_INFO_H
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#define CAJA_TYPE_PROGRESS_INFO (caja_progress_info_get_type ())
+#define CAJA_PROGRESS_INFO(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), CAJA_TYPE_PROGRESS_INFO, CajaProgressInfo))
+#define CAJA_PROGRESS_INFO_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), CAJA_TYPE_PROGRESS_INFO, CajaProgressInfoClass))
+#define CAJA_IS_PROGRESS_INFO(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), CAJA_TYPE_PROGRESS_INFO))
+#define CAJA_IS_PROGRESS_INFO_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), CAJA_TYPE_PROGRESS_INFO))
+#define CAJA_PROGRESS_INFO_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), CAJA_TYPE_PROGRESS_INFO, CajaProgressInfoClass))
+
+typedef struct _CajaProgressInfo CajaProgressInfo;
+typedef struct _CajaProgressInfoClass CajaProgressInfoClass;
+
+GType caja_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.
+ */
+
+CajaProgressInfo *caja_progress_info_new (void);
+
+GList * caja_get_all_progress_info (void);
+
+char * caja_progress_info_get_status (CajaProgressInfo *info);
+char * caja_progress_info_get_details (CajaProgressInfo *info);
+double caja_progress_info_get_progress (CajaProgressInfo *info);
+GCancellable *caja_progress_info_get_cancellable (CajaProgressInfo *info);
+void caja_progress_info_cancel (CajaProgressInfo *info);
+gboolean caja_progress_info_get_is_started (CajaProgressInfo *info);
+gboolean caja_progress_info_get_is_finished (CajaProgressInfo *info);
+gboolean caja_progress_info_get_is_paused (CajaProgressInfo *info);
+
+void caja_progress_info_start (CajaProgressInfo *info);
+void caja_progress_info_finish (CajaProgressInfo *info);
+void caja_progress_info_pause (CajaProgressInfo *info);
+void caja_progress_info_resume (CajaProgressInfo *info);
+void caja_progress_info_set_status (CajaProgressInfo *info,
+ const char *status);
+void caja_progress_info_take_status (CajaProgressInfo *info,
+ char *status);
+void caja_progress_info_set_details (CajaProgressInfo *info,
+ const char *details);
+void caja_progress_info_take_details (CajaProgressInfo *info,
+ char *details);
+void caja_progress_info_set_progress (CajaProgressInfo *info,
+ double current,
+ double total);
+void caja_progress_info_pulse_progress (CajaProgressInfo *info);
+
+
+
+#endif /* CAJA_PROGRESS_INFO_H */
diff --git a/libcaja-private/caja-query.c b/libcaja-private/caja-query.c
new file mode 100644
index 00000000..53e51ff6
--- /dev/null
+++ b/libcaja-private/caja-query.c
@@ -0,0 +1,395 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * Copyright (C) 2005 Novell, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Anders Carlsson <[email protected]>
+ *
+ */
+
+#include <config.h>
+#include <string.h>
+
+#include "caja-query.h"
+#include <eel/eel-gtk-macros.h>
+#include <eel/eel-glib-extensions.h>
+#include <glib/gi18n.h>
+#include <libcaja-private/caja-file-utilities.h>
+
+struct CajaQueryDetails
+{
+ char *text;
+ char *location_uri;
+ GList *mime_types;
+};
+
+static void caja_query_class_init (CajaQueryClass *class);
+static void caja_query_init (CajaQuery *query);
+
+G_DEFINE_TYPE (CajaQuery,
+ caja_query,
+ G_TYPE_OBJECT);
+
+static GObjectClass *parent_class = NULL;
+
+static void
+finalize (GObject *object)
+{
+ CajaQuery *query;
+
+ query = CAJA_QUERY (object);
+
+ g_free (query->details->text);
+ g_free (query->details);
+
+ EEL_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
+}
+
+static void
+caja_query_class_init (CajaQueryClass *class)
+{
+ GObjectClass *gobject_class;
+
+ parent_class = g_type_class_peek_parent (class);
+
+ gobject_class = G_OBJECT_CLASS (class);
+ gobject_class->finalize = finalize;
+}
+
+static void
+caja_query_init (CajaQuery *query)
+{
+ query->details = g_new0 (CajaQueryDetails, 1);
+}
+
+CajaQuery *
+caja_query_new (void)
+{
+ return g_object_new (CAJA_TYPE_QUERY, NULL);
+}
+
+
+char *
+caja_query_get_text (CajaQuery *query)
+{
+ return g_strdup (query->details->text);
+}
+
+void
+caja_query_set_text (CajaQuery *query, const char *text)
+{
+ g_free (query->details->text);
+ query->details->text = g_strdup (text);
+}
+
+char *
+caja_query_get_location (CajaQuery *query)
+{
+ return g_strdup (query->details->location_uri);
+}
+
+void
+caja_query_set_location (CajaQuery *query, const char *uri)
+{
+ g_free (query->details->location_uri);
+ query->details->location_uri = g_strdup (uri);
+}
+
+GList *
+caja_query_get_mime_types (CajaQuery *query)
+{
+ return eel_g_str_list_copy (query->details->mime_types);
+}
+
+void
+caja_query_set_mime_types (CajaQuery *query, GList *mime_types)
+{
+ eel_g_list_free_deep (query->details->mime_types);
+ query->details->mime_types = eel_g_str_list_copy (mime_types);
+}
+
+void
+caja_query_add_mime_type (CajaQuery *query, const char *mime_type)
+{
+ query->details->mime_types = g_list_append (query->details->mime_types,
+ g_strdup (mime_type));
+}
+
+char *
+caja_query_to_readable_string (CajaQuery *query)
+{
+ if (!query || !query->details->text)
+ {
+ return g_strdup (_("Search"));
+ }
+
+ return g_strdup_printf (_("Search for \"%s\""), query->details->text);
+}
+
+static char *
+encode_home_uri (const char *uri)
+{
+ char *home_uri;
+ const char *encoded_uri;
+
+ home_uri = caja_get_home_directory_uri ();
+
+ if (g_str_has_prefix (uri, home_uri))
+ {
+ encoded_uri = uri + strlen (home_uri);
+ if (*encoded_uri == '/')
+ {
+ encoded_uri++;
+ }
+ }
+ else
+ {
+ encoded_uri = uri;
+ }
+
+ g_free (home_uri);
+
+ return g_markup_escape_text (encoded_uri, -1);
+}
+
+static char *
+decode_home_uri (const char *uri)
+{
+ char *home_uri;
+ char *decoded_uri;
+
+ if (g_str_has_prefix (uri, "file:"))
+ {
+ decoded_uri = g_strdup (uri);
+ }
+ else
+ {
+ home_uri = caja_get_home_directory_uri ();
+
+ decoded_uri = g_strconcat (home_uri, "/", uri, NULL);
+
+ g_free (home_uri);
+ }
+
+ return decoded_uri;
+}
+
+
+typedef struct
+{
+ CajaQuery *query;
+ gboolean in_text;
+ gboolean in_location;
+ gboolean in_mimetypes;
+ gboolean in_mimetype;
+} ParserInfo;
+
+static void
+start_element_cb (GMarkupParseContext *ctx,
+ const char *element_name,
+ const char **attribute_names,
+ const char **attribute_values,
+ gpointer user_data,
+ GError **err)
+{
+ ParserInfo *info;
+
+ info = (ParserInfo *) user_data;
+
+ if (strcmp (element_name, "text") == 0)
+ info->in_text = TRUE;
+ else if (strcmp (element_name, "location") == 0)
+ info->in_location = TRUE;
+ else if (strcmp (element_name, "mimetypes") == 0)
+ info->in_mimetypes = TRUE;
+ else if (strcmp (element_name, "mimetype") == 0)
+ info->in_mimetype = TRUE;
+}
+
+static void
+end_element_cb (GMarkupParseContext *ctx,
+ const char *element_name,
+ gpointer user_data,
+ GError **err)
+{
+ ParserInfo *info;
+
+ info = (ParserInfo *) user_data;
+
+ if (strcmp (element_name, "text") == 0)
+ info->in_text = FALSE;
+ else if (strcmp (element_name, "location") == 0)
+ info->in_location = FALSE;
+ else if (strcmp (element_name, "mimetypes") == 0)
+ info->in_mimetypes = FALSE;
+ else if (strcmp (element_name, "mimetype") == 0)
+ info->in_mimetype = FALSE;
+}
+
+static void
+text_cb (GMarkupParseContext *ctx,
+ const char *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **err)
+{
+ ParserInfo *info;
+ char *t, *uri;
+
+ info = (ParserInfo *) user_data;
+
+ t = g_strndup (text, text_len);
+
+ if (info->in_text)
+ {
+ caja_query_set_text (info->query, t);
+ }
+ else if (info->in_location)
+ {
+ uri = decode_home_uri (t);
+ caja_query_set_location (info->query, uri);
+ g_free (uri);
+ }
+ else if (info->in_mimetypes && info->in_mimetype)
+ {
+ caja_query_add_mime_type (info->query, t);
+ }
+
+ g_free (t);
+
+}
+
+static void
+error_cb (GMarkupParseContext *ctx,
+ GError *err,
+ gpointer user_data)
+{
+}
+
+static GMarkupParser parser =
+{
+ start_element_cb,
+ end_element_cb,
+ text_cb,
+ NULL,
+ error_cb
+};
+
+
+static CajaQuery *
+caja_query_parse_xml (char *xml, gsize xml_len)
+{
+ ParserInfo info = { NULL };
+ GMarkupParseContext *ctx;
+
+ if (xml_len == -1)
+ {
+ xml_len = strlen (xml);
+ }
+
+ info.query = caja_query_new ();
+ info.in_text = FALSE;
+
+ ctx = g_markup_parse_context_new (&parser, 0, &info, NULL);
+ g_markup_parse_context_parse (ctx, xml, xml_len, NULL);
+
+ return info.query;
+}
+
+
+CajaQuery *
+caja_query_load (char *file)
+{
+ CajaQuery *query;
+ char *xml;
+ gsize xml_len;
+
+ if (!g_file_test (file, G_FILE_TEST_EXISTS))
+ {
+ return NULL;
+ }
+
+
+ g_file_get_contents (file, &xml, &xml_len, NULL);
+ query = caja_query_parse_xml (xml, xml_len);
+ g_free (xml);
+
+ return query;
+}
+
+static char *
+caja_query_to_xml (CajaQuery *query)
+{
+ GString *xml;
+ char *text;
+ char *uri;
+ char *mimetype;
+ GList *l;
+
+ xml = g_string_new ("");
+ g_string_append (xml,
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<query version=\"1.0\">\n");
+
+ text = g_markup_escape_text (query->details->text, -1);
+ g_string_append_printf (xml, " <text>%s</text>\n", text);
+ g_free (text);
+
+ if (query->details->location_uri)
+ {
+ uri = encode_home_uri (query->details->location_uri);
+ g_string_append_printf (xml, " <location>%s</location>\n", uri);
+ g_free (uri);
+ }
+
+ if (query->details->mime_types)
+ {
+ g_string_append (xml, " <mimetypes>\n");
+ for (l = query->details->mime_types; l != NULL; l = l->next)
+ {
+ mimetype = g_markup_escape_text (l->data, -1);
+ g_string_append_printf (xml, " <mimetype>%s</mimetype>\n", mimetype);
+ g_free (mimetype);
+ }
+ g_string_append (xml, " </mimetypes>\n");
+ }
+
+ g_string_append (xml, "</query>\n");
+
+ return g_string_free (xml, FALSE);
+}
+
+gboolean
+caja_query_save (CajaQuery *query, char *file)
+{
+ char *xml;
+ GError *err = NULL;
+ gboolean res;
+
+
+ res = TRUE;
+ xml = caja_query_to_xml (query);
+ g_file_set_contents (file, xml, strlen (xml), &err);
+ g_free (xml);
+
+ if (err != NULL)
+ {
+ res = FALSE;
+ g_error_free (err);
+ }
+ return res;
+}
diff --git a/libcaja-private/caja-query.h b/libcaja-private/caja-query.h
new file mode 100644
index 00000000..1f387a88
--- /dev/null
+++ b/libcaja-private/caja-query.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * Copyright (C) 2005 Novell, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Anders Carlsson <[email protected]>
+ *
+ */
+
+#ifndef CAJA_QUERY_H
+#define CAJA_QUERY_H
+
+#include <glib-object.h>
+
+#define CAJA_TYPE_QUERY (caja_query_get_type ())
+#define CAJA_QUERY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_QUERY, CajaQuery))
+#define CAJA_QUERY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_QUERY, CajaQueryClass))
+#define CAJA_IS_QUERY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_QUERY))
+#define CAJA_IS_QUERY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_QUERY))
+#define CAJA_QUERY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_QUERY, CajaQueryClass))
+
+typedef struct CajaQueryDetails CajaQueryDetails;
+
+typedef struct CajaQuery
+{
+ GObject parent;
+ CajaQueryDetails *details;
+} CajaQuery;
+
+typedef struct
+{
+ GObjectClass parent_class;
+} CajaQueryClass;
+
+GType caja_query_get_type (void);
+gboolean caja_query_enabled (void);
+
+CajaQuery* caja_query_new (void);
+
+char * caja_query_get_text (CajaQuery *query);
+void caja_query_set_text (CajaQuery *query, const char *text);
+
+char * caja_query_get_location (CajaQuery *query);
+void caja_query_set_location (CajaQuery *query, const char *uri);
+
+GList * caja_query_get_mime_types (CajaQuery *query);
+void caja_query_set_mime_types (CajaQuery *query, GList *mime_types);
+void caja_query_add_mime_type (CajaQuery *query, const char *mime_type);
+
+char * caja_query_to_readable_string (CajaQuery *query);
+CajaQuery *caja_query_load (char *file);
+gboolean caja_query_save (CajaQuery *query, char *file);
+
+#endif /* CAJA_QUERY_H */
diff --git a/libcaja-private/caja-recent.c b/libcaja-private/caja-recent.c
new file mode 100644
index 00000000..7d496678
--- /dev/null
+++ b/libcaja-private/caja-recent.c
@@ -0,0 +1,80 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "caja-recent.h"
+
+#include <eel/eel-vfs-extensions.h>
+
+#define DEFAULT_APP_EXEC "mate-open %u"
+
+static GtkRecentManager *
+caja_recent_get_manager (void)
+{
+ static GtkRecentManager *manager = NULL;
+
+ if (manager == NULL)
+ {
+ manager = gtk_recent_manager_get_default ();
+ }
+
+ return manager;
+}
+
+void
+caja_recent_add_file (CajaFile *file,
+ GAppInfo *application)
+{
+ GtkRecentData recent_data;
+ char *uri;
+
+ uri = caja_file_get_uri (file);
+
+ /* do not add trash:// etc */
+ if (eel_uri_is_trash (uri) ||
+ eel_uri_is_search (uri) ||
+ eel_uri_is_desktop (uri))
+ {
+ g_free (uri);
+ return;
+ }
+
+ recent_data.display_name = NULL;
+ recent_data.description = NULL;
+
+ recent_data.mime_type = caja_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 (caja_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/libcaja-private/caja-recent.h b/libcaja-private/caja-recent.h
new file mode 100644
index 00000000..dac63637
--- /dev/null
+++ b/libcaja-private/caja-recent.h
@@ -0,0 +1,13 @@
+
+
+#ifndef __CAJA_RECENT_H__
+#define __CAJA_RECENT_H__
+
+#include <gtk/gtk.h>
+#include <libcaja-private/caja-file.h>
+#include <gio/gio.h>
+
+void caja_recent_add_file (CajaFile *file,
+ GAppInfo *application);
+
+#endif
diff --git a/libcaja-private/caja-saved-search-file.c b/libcaja-private/caja-saved-search-file.c
new file mode 100644
index 00000000..05b6109c
--- /dev/null
+++ b/libcaja-private/caja-saved-search-file.c
@@ -0,0 +1,49 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-saved-search-file.h: Subclass of CajaVFSFile to implement the
+ the case of a Saved Search file.
+
+ Copyright (C) 2005 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Alexander Larsson
+*/
+#include <config.h>
+#include "caja-saved-search-file.h"
+#include "caja-file-private.h"
+
+G_DEFINE_TYPE(CajaSavedSearchFile, caja_saved_search_file, CAJA_TYPE_VFS_FILE)
+
+
+static void
+caja_saved_search_file_init (CajaSavedSearchFile *search_file)
+{
+ CajaFile *file;
+
+ file = CAJA_FILE (search_file);
+}
+
+static void
+caja_saved_search_file_class_init (CajaSavedSearchFileClass * klass)
+{
+ CajaFileClass *file_class;
+
+ file_class = CAJA_FILE_CLASS (klass);
+
+ file_class->default_file_type = G_FILE_TYPE_DIRECTORY;
+}
+
diff --git a/libcaja-private/caja-saved-search-file.h b/libcaja-private/caja-saved-search-file.h
new file mode 100644
index 00000000..0cdedc57
--- /dev/null
+++ b/libcaja-private/caja-saved-search-file.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-saved-search-file.h: Subclass of CajaVFSFile to implement the
+ the case of a Saved Search file.
+
+ Copyright (C) 2005 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Alexander Larsson
+*/
+
+#ifndef CAJA_SAVED_SEARCH_FILE_H
+#define CAJA_SAVED_SEARCH_FILE_H
+
+#include <libcaja-private/caja-vfs-file.h>
+
+#define CAJA_TYPE_SAVED_SEARCH_FILE caja_saved_search_file_get_type()
+#define CAJA_SAVED_SEARCH_FILE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_SAVED_SEARCH_FILE, CajaSavedSearchFile))
+#define CAJA_SAVED_SEARCH_FILE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_SAVED_SEARCH_FILE, CajaSavedSearchFileClass))
+#define CAJA_IS_SAVED_SEARCH_FILE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_SAVED_SEARCH_FILE))
+#define CAJA_IS_SAVED_SEARCH_FILE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_SAVED_SEARCH_FILE))
+#define CAJA_SAVED_SEARCH_FILE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_SAVED_SEARCH_FILE, CajaSavedSearchFileClass))
+
+
+typedef struct CajaSavedSearchFileDetails CajaSavedSearchFileDetails;
+
+typedef struct
+{
+ CajaFile parent_slot;
+} CajaSavedSearchFile;
+
+typedef struct
+{
+ CajaFileClass parent_slot;
+} CajaSavedSearchFileClass;
+
+GType caja_saved_search_file_get_type (void);
+
+#endif /* CAJA_SAVED_SEARCH_FILE_H */
diff --git a/libcaja-private/caja-search-directory-file.c b/libcaja-private/caja-search-directory-file.c
new file mode 100644
index 00000000..d1b2ba03
--- /dev/null
+++ b/libcaja-private/caja-search-directory-file.c
@@ -0,0 +1,260 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-search-directory-file.c: Subclass of CajaFile 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Anders Carlsson <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-search-directory-file.h"
+
+#include "caja-directory-notify.h"
+#include "caja-directory-private.h"
+#include "caja-file-attributes.h"
+#include "caja-file-private.h"
+#include "caja-file-utilities.h"
+#include <eel/eel-glib-extensions.h>
+#include "caja-search-directory.h"
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <string.h>
+
+struct CajaSearchDirectoryFileDetails
+{
+ CajaSearchDirectory *search_directory;
+};
+
+G_DEFINE_TYPE(CajaSearchDirectoryFile, caja_search_directory_file, CAJA_TYPE_FILE);
+
+
+static void
+search_directory_file_monitor_add (CajaFile *file,
+ gconstpointer client,
+ CajaFileAttributes 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 */
+ caja_search_directory_file_update_display_name (CAJA_SEARCH_DIRECTORY_FILE (file));
+}
+
+static void
+search_directory_file_monitor_remove (CajaFile *file,
+ gconstpointer client)
+{
+ /* Do nothing here, we don't have any monitors */
+}
+
+static void
+search_directory_file_call_when_ready (CajaFile *file,
+ CajaFileAttributes file_attributes,
+ CajaFileCallback callback,
+ gpointer callback_data)
+
+{
+ /* Update display name, in case this didn't happen yet */
+ caja_search_directory_file_update_display_name (CAJA_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 (CajaFile *file,
+ CajaFileCallback callback,
+ gpointer callback_data)
+{
+ /* Do nothing here, we don't have any pending calls */
+}
+
+static gboolean
+search_directory_file_check_if_ready (CajaFile *file,
+ CajaFileAttributes attributes)
+{
+ return TRUE;
+}
+
+static gboolean
+search_directory_file_get_item_count (CajaFile *file,
+ guint *count,
+ gboolean *count_unreadable)
+{
+ CajaSearchDirectory *search_dir;
+ GList *file_list;
+
+ if (count)
+ {
+ search_dir = CAJA_SEARCH_DIRECTORY (file->details->directory);
+
+ file_list = caja_directory_get_file_list (file->details->directory);
+
+ *count = g_list_length (file_list);
+
+ caja_file_list_free (file_list);
+ }
+
+ return TRUE;
+}
+
+static CajaRequestStatus
+search_directory_file_get_deep_counts (CajaFile *file,
+ guint *directory_count,
+ guint *file_count,
+ guint *unreadable_directory_count,
+ goffset *total_size)
+{
+ CajaSearchDirectory *search_dir;
+ CajaFile *dir_file;
+ GList *file_list, *l;
+ guint dirs, files;
+ GFileType type;
+
+ search_dir = CAJA_SEARCH_DIRECTORY (file->details->directory);
+
+ file_list = caja_directory_get_file_list (file->details->directory);
+
+ dirs = files = 0;
+ for (l = file_list; l != NULL; l = l->next)
+ {
+ dir_file = CAJA_FILE (l->data);
+ type = caja_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;
+ }
+
+ caja_file_list_free (file_list);
+
+ return CAJA_REQUEST_DONE;
+}
+
+static char *
+search_directory_file_get_where_string (CajaFile *file)
+{
+ return g_strdup (_("Search"));
+}
+
+void
+caja_search_directory_file_update_display_name (CajaSearchDirectoryFile *search_file)
+{
+ CajaFile *file;
+ CajaSearchDirectory *search_dir;
+ CajaQuery *query;
+ char *display_name;
+ gboolean changed;
+
+
+ display_name = NULL;
+ file = CAJA_FILE (search_file);
+ if (file->details->directory)
+ {
+ search_dir = CAJA_SEARCH_DIRECTORY (file->details->directory);
+ query = caja_search_directory_get_query (search_dir);
+
+ if (query != NULL)
+ {
+ display_name = caja_query_to_readable_string (query);
+ g_object_unref (query);
+ }
+ }
+
+ if (display_name == NULL)
+ {
+ display_name = g_strdup (_("Search"));
+ }
+
+ changed = caja_file_set_display_name (file, display_name, NULL, TRUE);
+ if (changed)
+ {
+ caja_file_emit_changed (file);
+ }
+}
+
+static void
+caja_search_directory_file_init (CajaSearchDirectoryFile *search_file)
+{
+ CajaFile *file;
+
+ file = CAJA_FILE (search_file);
+
+ 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;
+
+ caja_file_set_display_name (file, _("Search"), NULL, TRUE);
+}
+
+static void
+caja_search_directory_file_class_init (CajaSearchDirectoryFileClass *klass)
+{
+ GObjectClass *object_class;
+ CajaFileClass *file_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ file_class = CAJA_FILE_CLASS (klass);
+
+ 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;
+}
diff --git a/libcaja-private/caja-search-directory-file.h b/libcaja-private/caja-search-directory-file.h
new file mode 100644
index 00000000..9ceef618
--- /dev/null
+++ b/libcaja-private/caja-search-directory-file.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-search-directory-file.h: Subclass of CajaFile 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Alexander Larsson <[email protected]>
+*/
+
+#ifndef CAJA_SEARCH_DIRECTORY_FILE_H
+#define CAJA_SEARCH_DIRECTORY_FILE_H
+
+#include <libcaja-private/caja-file.h>
+
+#define CAJA_TYPE_SEARCH_DIRECTORY_FILE caja_search_directory_file_get_type()
+#define CAJA_SEARCH_DIRECTORY_FILE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_SEARCH_DIRECTORY_FILE, CajaSearchDirectoryFile))
+#define CAJA_SEARCH_DIRECTORY_FILE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_SEARCH_DIRECTORY_FILE, CajaSearchDirectoryFileClass))
+#define CAJA_IS_SEARCH_DIRECTORY_FILE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_SEARCH_DIRECTORY_FILE))
+#define CAJA_IS_SEARCH_DIRECTORY_FILE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_SEARCH_DIRECTORY_FILE))
+#define CAJA_SEARCH_DIRECTORY_FILE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_SEARCH_DIRECTORY_FILE, CajaSearchDirectoryFileClass))
+
+typedef struct CajaSearchDirectoryFileDetails CajaSearchDirectoryFileDetails;
+
+typedef struct
+{
+ CajaFile parent_slot;
+ CajaSearchDirectoryFileDetails *details;
+} CajaSearchDirectoryFile;
+
+typedef struct
+{
+ CajaFileClass parent_slot;
+} CajaSearchDirectoryFileClass;
+
+GType caja_search_directory_file_get_type (void);
+void caja_search_directory_file_update_display_name (CajaSearchDirectoryFile *search_file);
+
+#endif /* CAJA_SEARCH_DIRECTORY_FILE_H */
diff --git a/libcaja-private/caja-search-directory.c b/libcaja-private/caja-search-directory.c
new file mode 100644
index 00000000..7c1d7660
--- /dev/null
+++ b/libcaja-private/caja-search-directory.c
@@ -0,0 +1,934 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Anders Carlsson <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-search-directory.h"
+#include "caja-search-directory-file.h"
+
+#include "caja-directory-private.h"
+#include "caja-file.h"
+#include "caja-file-private.h"
+#include "caja-file-utilities.h"
+#include "caja-search-engine.h"
+#include <eel/eel-glib-extensions.h>
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+#include <string.h>
+#include <sys/time.h>
+
+struct CajaSearchDirectoryDetails
+{
+ CajaQuery *query;
+ char *saved_search_uri;
+ gboolean modified;
+
+ CajaSearchEngine *engine;
+
+ gboolean search_running;
+ gboolean search_finished;
+
+ GList *files;
+ GHashTable *file_hash;
+
+ GList *monitor_list;
+ GList *callback_list;
+ GList *pending_callback_list;
+};
+
+typedef struct
+{
+ gboolean monitor_hidden_files;
+ gboolean monitor_backup_files;
+ CajaFileAttributes monitor_attributes;
+
+ gconstpointer client;
+} SearchMonitor;
+
+typedef struct
+{
+ CajaSearchDirectory *search_directory;
+
+ CajaDirectoryCallback callback;
+ gpointer callback_data;
+
+ CajaFileAttributes wait_for_attributes;
+ gboolean wait_for_file_list;
+ GList *file_list;
+ GHashTable *non_ready_hash;
+} SearchCallback;
+
+G_DEFINE_TYPE (CajaSearchDirectory, caja_search_directory,
+ CAJA_TYPE_DIRECTORY);
+
+static void search_engine_hits_added (CajaSearchEngine *engine, GList *hits, CajaSearchDirectory *search);
+static void search_engine_hits_subtracted (CajaSearchEngine *engine, GList *hits, CajaSearchDirectory *search);
+static void search_engine_finished (CajaSearchEngine *engine, CajaSearchDirectory *search);
+static void search_engine_error (CajaSearchEngine *engine, const char *error, CajaSearchDirectory *search);
+static void search_callback_file_ready_callback (CajaFile *file, gpointer data);
+static void file_changed (CajaFile *file, CajaSearchDirectory *search);
+
+static void
+ensure_search_engine (CajaSearchDirectory *search)
+{
+ if (!search->details->engine)
+ {
+ search->details->engine = caja_search_engine_new ();
+ g_signal_connect (search->details->engine, "hits-added",
+ G_CALLBACK (search_engine_hits_added),
+ search);
+ g_signal_connect (search->details->engine, "hits-subtracted",
+ G_CALLBACK (search_engine_hits_subtracted),
+ search);
+ g_signal_connect (search->details->engine, "finished",
+ G_CALLBACK (search_engine_finished),
+ search);
+ g_signal_connect (search->details->engine, "error",
+ G_CALLBACK (search_engine_error),
+ search);
+ }
+}
+
+static void
+reset_file_list (CajaSearchDirectory *search)
+{
+ GList *list, *monitor_list;
+ CajaFile *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;
+ caja_file_monitor_remove (file, monitor);
+ }
+ }
+
+ caja_file_list_free (search->details->files);
+ search->details->files = NULL;
+}
+
+static void
+start_or_stop_search_engine (CajaSearchDirectory *search, gboolean adding)
+{
+ if (adding && (search->details->monitor_list ||
+ search->details->pending_callback_list) &&
+ search->details->query &&
+ !search->details->search_running)
+ {
+ /* We need to start the search engine */
+ search->details->search_running = TRUE;
+ search->details->search_finished = FALSE;
+ ensure_search_engine (search);
+ caja_search_engine_set_query (search->details->engine, search->details->query);
+
+ reset_file_list (search);
+
+ caja_search_engine_start (search->details->engine);
+ }
+ else if (!adding && !search->details->monitor_list &&
+ !search->details->pending_callback_list &&
+ search->details->engine &&
+ search->details->search_running)
+ {
+ search->details->search_running = FALSE;
+ caja_search_engine_stop (search->details->engine);
+
+ reset_file_list (search);
+ }
+
+}
+
+static void
+file_changed (CajaFile *file, CajaSearchDirectory *search)
+{
+ GList list;
+
+ list.data = file;
+ list.next = NULL;
+
+ caja_directory_emit_files_changed (CAJA_DIRECTORY (search), &list);
+}
+
+static void
+search_monitor_add (CajaDirectory *directory,
+ gconstpointer client,
+ gboolean monitor_hidden_files,
+ gboolean monitor_backup_files,
+ CajaFileAttributes file_attributes,
+ CajaDirectoryCallback callback,
+ gpointer callback_data)
+{
+ GList *list;
+ SearchMonitor *monitor;
+ CajaSearchDirectory *search;
+ CajaFile *file;
+
+ search = CAJA_SEARCH_DIRECTORY (directory);
+
+ monitor = g_new0 (SearchMonitor, 1);
+ monitor->monitor_hidden_files = monitor_hidden_files;
+ monitor->monitor_backup_files = monitor_backup_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 */
+ caja_file_monitor_add (file, monitor, file_attributes);
+ }
+
+ start_or_stop_search_engine (search, TRUE);
+}
+
+static void
+search_monitor_remove_file_monitors (SearchMonitor *monitor, CajaSearchDirectory *search)
+{
+ GList *list;
+ CajaFile *file;
+
+ for (list = search->details->files; list != NULL; list = list->next)
+ {
+ file = list->data;
+
+ caja_file_monitor_remove (file, monitor);
+ }
+}
+
+static void
+search_monitor_destroy (SearchMonitor *monitor, CajaSearchDirectory *search)
+{
+ search_monitor_remove_file_monitors (monitor, search);
+
+ g_free (monitor);
+}
+
+static void
+search_monitor_remove (CajaDirectory *directory,
+ gconstpointer client)
+{
+ CajaSearchDirectory *search;
+ SearchMonitor *monitor;
+ GList *list;
+
+ search = CAJA_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;
+ }
+ }
+
+ start_or_stop_search_engine (search, FALSE);
+}
+
+static void
+cancel_call_when_ready (gpointer key, gpointer value, gpointer user_data)
+{
+ SearchCallback *search_callback;
+ CajaFile *file;
+
+ file = key;
+ search_callback = user_data;
+
+ caja_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);
+ }
+
+ caja_file_list_free (search_callback->file_list);
+
+ g_free (search_callback);
+}
+
+static void
+search_callback_invoke_and_destroy (SearchCallback *search_callback)
+{
+ search_callback->callback (CAJA_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 (CajaFile *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;
+ CajaFile *file;
+
+ file_list_copy = g_list_copy (callback->file_list);
+
+ for (list = file_list_copy; list != NULL; list = list->next)
+ {
+ file = list->data;
+
+ caja_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 (CajaSearchDirectory *search, CajaDirectoryCallback 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 (CajaSearchDirectory *search, CajaDirectoryCallback 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 (CajaDirectory *directory,
+ CajaFileAttributes file_attributes,
+ gboolean wait_for_file_list,
+ CajaDirectoryCallback callback,
+ gpointer callback_data)
+{
+ CajaSearchDirectory *search;
+ SearchCallback *search_callback;
+
+ search = CAJA_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_finished)
+ {
+ /* Add it to the pending callback list, which will be
+ * processed when the directory has finished loading
+ */
+ search->details->pending_callback_list =
+ g_list_prepend (search->details->pending_callback_list, search_callback);
+
+ /* We might need to start the search engine */
+ start_or_stop_search_engine (search, TRUE);
+ }
+ else
+ {
+ search_callback->file_list = caja_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 (CajaDirectory *directory,
+ CajaDirectoryCallback callback,
+ gpointer callback_data)
+{
+ CajaSearchDirectory *search;
+ SearchCallback *search_callback;
+
+ search = CAJA_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);
+
+ return;
+ }
+
+ /* 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);
+
+ /* We might need to stop the search engine now */
+ start_or_stop_search_engine (search, FALSE);
+ }
+}
+
+
+static void
+search_engine_hits_added (CajaSearchEngine *engine, GList *hits,
+ CajaSearchDirectory *search)
+{
+ GList *hit_list;
+ GList *file_list;
+ CajaFile *file;
+ char *uri;
+ SearchMonitor *monitor;
+ GList *monitor_list;
+
+ file_list = NULL;
+
+ for (hit_list = hits; hit_list != NULL; hit_list = hit_list->next)
+ {
+ uri = hit_list->data;
+
+ if (g_str_has_suffix (uri, CAJA_SAVED_SEARCH_EXTENSION))
+ {
+ /* Never return saved searches themselves as hits */
+ continue;
+ }
+
+ file = caja_file_get_by_uri (uri);
+
+ for (monitor_list = search->details->monitor_list; monitor_list; monitor_list = monitor_list->next)
+ {
+ monitor = monitor_list->data;
+
+ /* Add monitors */
+ caja_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);
+ }
+
+ search->details->files = g_list_concat (search->details->files, file_list);
+
+ caja_directory_emit_files_added (CAJA_DIRECTORY (search), file_list);
+
+ file = caja_directory_get_corresponding_file (CAJA_DIRECTORY (search));
+ caja_file_emit_changed (file);
+ caja_file_unref (file);
+}
+
+static void
+search_engine_hits_subtracted (CajaSearchEngine *engine, GList *hits,
+ CajaSearchDirectory *search)
+{
+ GList *hit_list;
+ GList *monitor_list;
+ SearchMonitor *monitor;
+ GList *file_list;
+ char *uri;
+ CajaFile *file;
+
+ file_list = NULL;
+
+ for (hit_list = hits; hit_list != NULL; hit_list = hit_list->next)
+ {
+ uri = hit_list->data;
+ file = caja_file_get_by_uri (uri);
+
+ for (monitor_list = search->details->monitor_list; monitor_list;
+ monitor_list = monitor_list->next)
+ {
+ monitor = monitor_list->data;
+ /* Remove monitors */
+ caja_file_monitor_remove (file, monitor);
+ }
+
+ g_signal_handlers_disconnect_by_func (file, file_changed, search);
+
+ search->details->files = g_list_remove (search->details->files, file);
+
+ file_list = g_list_prepend (file_list, file);
+ }
+
+ caja_directory_emit_files_changed (CAJA_DIRECTORY (search), file_list);
+
+ caja_file_list_free (file_list);
+
+ file = caja_directory_get_corresponding_file (CAJA_DIRECTORY (search));
+ caja_file_emit_changed (file);
+ caja_file_unref (file);
+}
+
+static void
+search_callback_add_pending_file_callbacks (SearchCallback *callback)
+{
+ callback->file_list = caja_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_engine_error (CajaSearchEngine *engine, const char *error_message, CajaSearchDirectory *search)
+{
+ GError *error;
+
+ error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED,
+ error_message);
+ caja_directory_emit_load_error (CAJA_DIRECTORY (search),
+ error);
+ g_error_free (error);
+}
+
+static void
+search_engine_finished (CajaSearchEngine *engine, CajaSearchDirectory *search)
+{
+ search->details->search_finished = TRUE;
+
+ caja_directory_emit_done_loading (CAJA_DIRECTORY (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
+search_force_reload (CajaDirectory *directory)
+{
+ CajaSearchDirectory *search;
+
+ search = CAJA_SEARCH_DIRECTORY (directory);
+
+ if (!search->details->query)
+ {
+ return;
+ }
+
+ search->details->search_finished = FALSE;
+
+ if (!search->details->engine)
+ {
+ return;
+ }
+
+ /* Remove file monitors */
+ reset_file_list (search);
+
+ if (search->details->search_running)
+ {
+ caja_search_engine_stop (search->details->engine);
+ caja_search_engine_set_query (search->details->engine, search->details->query);
+ caja_search_engine_start (search->details->engine);
+ }
+}
+
+static gboolean
+search_are_all_files_seen (CajaDirectory *directory)
+{
+ CajaSearchDirectory *search;
+
+ search = CAJA_SEARCH_DIRECTORY (directory);
+
+ return (!search->details->query ||
+ search->details->search_finished);
+}
+
+static gboolean
+search_contains_file (CajaDirectory *directory,
+ CajaFile *file)
+{
+ CajaSearchDirectory *search;
+
+ search = CAJA_SEARCH_DIRECTORY (directory);
+
+ /* FIXME: Maybe put the files in a hash */
+ return (g_list_find (search->details->files, file) != NULL);
+}
+
+static GList *
+search_get_file_list (CajaDirectory *directory)
+{
+ CajaSearchDirectory *search;
+
+ search = CAJA_SEARCH_DIRECTORY (directory);
+
+ return caja_file_list_copy (search->details->files);
+}
+
+
+static gboolean
+search_is_editable (CajaDirectory *directory)
+{
+ return FALSE;
+}
+
+static void
+search_dispose (GObject *object)
+{
+ CajaSearchDirectory *search;
+ GList *list;
+
+ search = CAJA_SEARCH_DIRECTORY (object);
+
+ /* 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;
+ }
+
+ if (search->details->query)
+ {
+ g_object_unref (search->details->query);
+ search->details->query = NULL;
+ }
+
+ if (search->details->engine)
+ {
+ if (search->details->search_running)
+ {
+ caja_search_engine_stop (search->details->engine);
+ }
+
+ g_object_unref (search->details->engine);
+ search->details->engine = NULL;
+ }
+
+ G_OBJECT_CLASS (caja_search_directory_parent_class)->dispose (object);
+}
+
+static void
+search_finalize (GObject *object)
+{
+ CajaSearchDirectory *search;
+
+ search = CAJA_SEARCH_DIRECTORY (object);
+
+ g_free (search->details->saved_search_uri);
+
+ g_free (search->details);
+
+ G_OBJECT_CLASS (caja_search_directory_parent_class)->finalize (object);
+}
+
+static void
+caja_search_directory_init (CajaSearchDirectory *search)
+{
+ search->details = g_new0 (CajaSearchDirectoryDetails, 1);
+}
+
+static void
+caja_search_directory_class_init (CajaSearchDirectoryClass *class)
+{
+ CajaDirectoryClass *directory_class;
+
+ G_OBJECT_CLASS (class)->dispose = search_dispose;
+ G_OBJECT_CLASS (class)->finalize = search_finalize;
+
+ directory_class = CAJA_DIRECTORY_CLASS (class);
+
+ 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;
+}
+
+char *
+caja_search_directory_generate_new_uri (void)
+{
+ static int counter = 0;
+ char *uri;
+
+ uri = g_strdup_printf (EEL_SEARCH_URI"//%d/", counter++);
+
+ return uri;
+}
+
+
+void
+caja_search_directory_set_query (CajaSearchDirectory *search,
+ CajaQuery *query)
+{
+ CajaDirectory *dir;
+ CajaFile *as_file;
+
+ if (search->details->query != query)
+ {
+ search->details->modified = TRUE;
+ }
+
+ if (query)
+ {
+ g_object_ref (query);
+ }
+
+ if (search->details->query)
+ {
+ g_object_unref (search->details->query);
+ }
+
+ search->details->query = query;
+
+ dir = CAJA_DIRECTORY (search);
+ as_file = dir->details->as_file;
+ if (as_file != NULL)
+ {
+ caja_search_directory_file_update_display_name (CAJA_SEARCH_DIRECTORY_FILE (as_file));
+ }
+}
+
+CajaQuery *
+caja_search_directory_get_query (CajaSearchDirectory *search)
+{
+ if (search->details->query != NULL)
+ {
+ return g_object_ref (search->details->query);
+ }
+
+ return NULL;
+}
+
+CajaSearchDirectory *
+caja_search_directory_new_from_saved_search (const char *uri)
+{
+ CajaSearchDirectory *search;
+ CajaQuery *query;
+ char *file;
+
+ search = CAJA_SEARCH_DIRECTORY (g_object_new (CAJA_TYPE_SEARCH_DIRECTORY, NULL));
+
+ search->details->saved_search_uri = g_strdup (uri);
+
+ file = g_filename_from_uri (uri, NULL, NULL);
+ if (file != NULL)
+ {
+ query = caja_query_load (file);
+ if (query != NULL)
+ {
+ caja_search_directory_set_query (search, query);
+ g_object_unref (query);
+ }
+ g_free (file);
+ }
+ else
+ {
+ g_warning ("Non-local saved searches not supported");
+ }
+
+ search->details->modified = FALSE;
+ return search;
+}
+
+gboolean
+caja_search_directory_is_saved_search (CajaSearchDirectory *search)
+{
+ return search->details->saved_search_uri != NULL;
+}
+
+gboolean
+caja_search_directory_is_modified (CajaSearchDirectory *search)
+{
+ return search->details->modified;
+}
+
+gboolean
+caja_search_directory_is_indexed (CajaSearchDirectory *search)
+{
+ ensure_search_engine (search);
+ return caja_search_engine_is_indexed (search->details->engine);
+}
+
+
+void
+caja_search_directory_save_to_file (CajaSearchDirectory *search,
+ const char *save_file_uri)
+{
+ char *file;
+
+ file = g_filename_from_uri (save_file_uri, NULL, NULL);
+ if (file == NULL)
+ {
+ return;
+ }
+
+ if (search->details->query != NULL)
+ {
+ caja_query_save (search->details->query, file);
+ }
+
+ g_free (file);
+}
+
+void
+caja_search_directory_save_search (CajaSearchDirectory *search)
+{
+ if (search->details->saved_search_uri == NULL)
+ {
+ return;
+ }
+
+ caja_search_directory_save_to_file (search,
+ search->details->saved_search_uri);
+ search->details->modified = FALSE;
+}
diff --git a/libcaja-private/caja-search-directory.h b/libcaja-private/caja-search-directory.h
new file mode 100644
index 00000000..47e85fa7
--- /dev/null
+++ b/libcaja-private/caja-search-directory.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-search-directory.h: Subclass of CajaDirectory 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+#ifndef CAJA_SEARCH_DIRECTORY_H
+#define CAJA_SEARCH_DIRECTORY_H
+
+#include <libcaja-private/caja-directory.h>
+#include <libcaja-private/caja-query.h>
+
+#define CAJA_TYPE_SEARCH_DIRECTORY caja_search_directory_get_type()
+#define CAJA_SEARCH_DIRECTORY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_SEARCH_DIRECTORY, CajaSearchDirectory))
+#define CAJA_SEARCH_DIRECTORY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_SEARCH_DIRECTORY, CajaSearchDirectoryClass))
+#define CAJA_IS_SEARCH_DIRECTORY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_SEARCH_DIRECTORY))
+#define CAJA_IS_SEARCH_DIRECTORY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_SEARCH_DIRECTORY))
+#define CAJA_SEARCH_DIRECTORY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_SEARCH_DIRECTORY, CajaSearchDirectoryClass))
+
+typedef struct CajaSearchDirectoryDetails CajaSearchDirectoryDetails;
+
+typedef struct
+{
+ CajaDirectory parent_slot;
+ CajaSearchDirectoryDetails *details;
+} CajaSearchDirectory;
+
+typedef struct
+{
+ CajaDirectoryClass parent_slot;
+} CajaSearchDirectoryClass;
+
+GType caja_search_directory_get_type (void);
+
+char *caja_search_directory_generate_new_uri (void);
+
+CajaSearchDirectory *caja_search_directory_new_from_saved_search (const char *uri);
+
+gboolean caja_search_directory_is_saved_search (CajaSearchDirectory *search);
+gboolean caja_search_directory_is_modified (CajaSearchDirectory *search);
+gboolean caja_search_directory_is_indexed (CajaSearchDirectory *search);
+void caja_search_directory_save_search (CajaSearchDirectory *search);
+void caja_search_directory_save_to_file (CajaSearchDirectory *search,
+ const char *save_file_uri);
+
+CajaQuery *caja_search_directory_get_query (CajaSearchDirectory *search);
+void caja_search_directory_set_query (CajaSearchDirectory *search,
+ CajaQuery *query);
+
+#endif /* CAJA_SEARCH_DIRECTORY_H */
diff --git a/libcaja-private/caja-search-engine-beagle.c b/libcaja-private/caja-search-engine-beagle.c
new file mode 100644
index 00000000..de591977
--- /dev/null
+++ b/libcaja-private/caja-search-engine-beagle.c
@@ -0,0 +1,443 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * Copyright (C) 2005 Novell, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Anders Carlsson <[email protected]>
+ *
+ */
+
+#include <config.h>
+#include "caja-search-engine-beagle.h"
+
+#include <eel/eel-gtk-macros.h>
+#include <eel/eel-glib-extensions.h>
+#include <gmodule.h>
+
+typedef struct _BeagleHit BeagleHit;
+typedef struct _BeagleQuery BeagleQuery;
+typedef struct _BeagleClient BeagleClient;
+typedef struct _BeagleRequest BeagleRequest;
+typedef struct _BeagleFinishedResponse BeagleFinishedResponse;
+typedef struct _BeagleHitsAddedResponse BeagleHitsAddedResponse;
+typedef struct _BeagleQueryPartProperty BeagleQueryPartProperty;
+typedef struct _BeagleQueryPart BeagleQueryPart;
+typedef struct _BeagleHitsSubtractedResponse BeagleHitsSubtractedResponse;
+
+struct CajaSearchEngineBeagleDetails
+{
+ BeagleClient *client;
+ CajaQuery *query;
+
+ BeagleQuery *current_query;
+ char *current_query_uri_prefix;
+ gboolean query_finished;
+};
+
+/* We dlopen() all the following from libbeagle at runtime */
+#define BEAGLE_HIT(x) ((BeagleHit *)(x))
+#define BEAGLE_REQUEST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), beagle_request_get_type(), BeagleRequest))
+#define BEAGLE_QUERY_PART(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), beagle_query_part_get_type(), BeagleQueryPart))
+
+typedef enum
+{
+ BEAGLE_QUERY_PART_LOGIC_REQUIRED = 1,
+ BEAGLE_QUERY_PART_LOGIC_PROHIBITED = 2
+} BeagleQueryPartLogic;
+
+typedef enum
+{
+ BEAGLE_PROPERTY_TYPE_UNKNOWN = 0,
+ BEAGLE_PROPERTY_TYPE_TEXT = 1,
+ BEAGLE_PROPERTY_TYPE_KEYWORD = 2,
+ BEAGLE_PROPERTY_TYPE_DATE = 3,
+ BEAGLE_PROPERTY_TYPE_LAST = 4
+} BeaglePropertyType;
+
+/* *static* wrapper function pointers */
+static gboolean (*beagle_client_send_request_async) (BeagleClient *client,
+ BeagleRequest *request,
+ GError **err) = NULL;
+static const char* (*beagle_hit_get_uri)(BeagleHit* hit) = NULL;
+static GSList *(*beagle_hits_added_response_get_hits) (BeagleHitsAddedResponse *response) = NULL;
+static BeagleQuery *(*beagle_query_new) (void) = NULL;
+static void (*beagle_query_add_text) (BeagleQuery *query,
+ const char *str) = NULL;
+static BeagleQueryPartProperty *(*beagle_query_part_property_new) (void) = NULL;
+static void (*beagle_query_part_set_logic) (BeagleQueryPart *part,
+ BeagleQueryPartLogic logic) = NULL;
+static void (*beagle_query_part_property_set_key) (BeagleQueryPartProperty *part,
+ const char *key) = NULL;
+static void (*beagle_query_part_property_set_value) (BeagleQueryPartProperty *part,
+ const char * value) = NULL;
+static void (*beagle_query_part_property_set_property_type) (BeagleQueryPartProperty *part,
+ BeaglePropertyType prop_type) = NULL;
+static void (*beagle_query_add_part) (BeagleQuery *query,
+ BeagleQueryPart *part) = NULL;
+static GType (*beagle_request_get_type) (void) = NULL;
+static GType (*beagle_query_part_get_type) (void) = NULL;
+static gboolean (*beagle_util_daemon_is_running) (void) = NULL;
+static BeagleClient *(*beagle_client_new_real) (const char *client_name) = NULL;
+static void (*beagle_query_set_max_hits) (BeagleQuery *query,
+ int max_hits) = NULL;
+static GSList *(*beagle_hits_subtracted_response_get_uris) (BeagleHitsSubtractedResponse *response) = NULL;
+
+static struct BeagleDlMapping
+{
+ const char *fn_name;
+ gpointer *fn_ptr_ref;
+} beagle_dl_mapping[] =
+{
+#define MAP(a) { #a, (gpointer *)&a }
+ MAP (beagle_client_send_request_async),
+ MAP (beagle_hit_get_uri),
+ MAP (beagle_hits_added_response_get_hits),
+ MAP (beagle_query_new),
+ MAP (beagle_query_add_text),
+ MAP (beagle_query_part_property_new),
+ MAP (beagle_query_part_set_logic),
+ MAP (beagle_query_part_property_set_key),
+ MAP (beagle_query_part_property_set_value),
+ MAP (beagle_query_part_property_set_property_type),
+ MAP (beagle_query_add_part),
+ MAP (beagle_request_get_type),
+ MAP (beagle_query_part_get_type),
+ MAP (beagle_util_daemon_is_running),
+ MAP (beagle_query_set_max_hits),
+ MAP (beagle_hits_subtracted_response_get_uris),
+#undef MAP
+ { "beagle_client_new", (gpointer *)&beagle_client_new_real },
+};
+
+static void
+open_libbeagle (void)
+{
+ static gboolean done = FALSE;
+
+ if (!done)
+ {
+ int i;
+ GModule *beagle;
+
+ done = TRUE;
+
+ beagle = g_module_open ("libbeagle.so.1", G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL);
+ if (!beagle)
+ return;
+
+ for (i = 0; i < G_N_ELEMENTS (beagle_dl_mapping); i++)
+ {
+ if (!g_module_symbol (beagle, beagle_dl_mapping[i].fn_name,
+ beagle_dl_mapping[i].fn_ptr_ref))
+ {
+ g_warning ("Missing symbol '%s' in libbeagle\n",
+ beagle_dl_mapping[i].fn_name);
+ g_module_close (beagle);
+
+ for (i = 0; i < G_N_ELEMENTS (beagle_dl_mapping); i++)
+ beagle_dl_mapping[i].fn_ptr_ref = NULL;
+
+ return;
+ }
+ }
+ }
+}
+
+static BeagleClient *
+beagle_client_new (const char *client_name)
+{
+ if (beagle_client_new_real)
+ return beagle_client_new_real (client_name);
+
+ return NULL;
+}
+
+static void caja_search_engine_beagle_class_init (CajaSearchEngineBeagleClass *class);
+static void caja_search_engine_beagle_init (CajaSearchEngineBeagle *engine);
+
+G_DEFINE_TYPE (CajaSearchEngineBeagle,
+ caja_search_engine_beagle,
+ CAJA_TYPE_SEARCH_ENGINE);
+
+static CajaSearchEngineClass *parent_class = NULL;
+
+static void
+finalize (GObject *object)
+{
+ CajaSearchEngineBeagle *beagle;
+
+ beagle = CAJA_SEARCH_ENGINE_BEAGLE (object);
+
+ if (beagle->details->current_query)
+ {
+ g_object_unref (beagle->details->current_query);
+ beagle->details->current_query = NULL;
+ g_free (beagle->details->current_query_uri_prefix);
+ beagle->details->current_query_uri_prefix = NULL;
+ }
+
+ if (beagle->details->query)
+ {
+ g_object_unref (beagle->details->query);
+ beagle->details->query = NULL;
+ }
+
+ if (beagle->details->client)
+ {
+ g_object_unref (beagle->details->client);
+ beagle->details->client = NULL;
+ }
+
+ g_free (beagle->details);
+
+ EEL_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
+}
+
+static void
+beagle_hits_added (BeagleQuery *query,
+ BeagleHitsAddedResponse *response,
+ CajaSearchEngineBeagle *engine)
+{
+ GSList *hits, *list;
+ GList *hit_uris;
+ const char *uri;
+
+ hit_uris = NULL;
+
+ hits = beagle_hits_added_response_get_hits (response);
+
+ for (list = hits; list != NULL; list = list->next)
+ {
+ BeagleHit *hit = BEAGLE_HIT (list->data);
+
+ uri = beagle_hit_get_uri (hit);
+
+ if (engine->details->current_query_uri_prefix &&
+ !g_str_has_prefix (uri, engine->details->current_query_uri_prefix))
+ {
+ continue;
+ }
+
+ hit_uris = g_list_prepend (hit_uris, (char *)uri);
+ }
+
+ caja_search_engine_hits_added (CAJA_SEARCH_ENGINE (engine), hit_uris);
+ g_list_free (hit_uris);
+}
+
+static void
+beagle_hits_subtracted (BeagleQuery *query,
+ BeagleHitsSubtractedResponse *response,
+ CajaSearchEngineBeagle *engine)
+{
+ GSList *uris, *list;
+ GList *hit_uris;
+
+ hit_uris = NULL;
+
+ uris = beagle_hits_subtracted_response_get_uris (response);
+
+ for (list = uris; list != NULL; list = list->next)
+ {
+ hit_uris = g_list_prepend (hit_uris, (char *)list->data);
+ }
+
+ caja_search_engine_hits_subtracted (CAJA_SEARCH_ENGINE (engine), hit_uris);
+ g_list_free (hit_uris);
+}
+
+static void
+beagle_finished (BeagleQuery *query,
+ BeagleFinishedResponse *response,
+ CajaSearchEngineBeagle *engine)
+{
+ /* For some reason we keep getting finished events,
+ * only emit finished once */
+ if (engine->details->query_finished)
+ {
+ return;
+ }
+
+ engine->details->query_finished = TRUE;
+ caja_search_engine_finished (CAJA_SEARCH_ENGINE (engine));
+}
+
+static void
+beagle_error (BeagleQuery *query,
+ GError *error,
+ CajaSearchEngineBeagle *engine)
+{
+ caja_search_engine_error (CAJA_SEARCH_ENGINE (engine), error->message);
+}
+
+static void
+caja_search_engine_beagle_start (CajaSearchEngine *engine)
+{
+ CajaSearchEngineBeagle *beagle;
+ GError *error;
+ GList *mimetypes, *l;
+ char *text, *mimetype;
+
+ error = NULL;
+ beagle = CAJA_SEARCH_ENGINE_BEAGLE (engine);
+
+ if (beagle->details->current_query)
+ {
+ return;
+ }
+
+ beagle->details->query_finished = FALSE;
+ beagle->details->current_query = beagle_query_new ();
+ g_signal_connect (beagle->details->current_query,
+ "hits-added", G_CALLBACK (beagle_hits_added), engine);
+ g_signal_connect (beagle->details->current_query,
+ "hits-subtracted", G_CALLBACK (beagle_hits_subtracted), engine);
+ g_signal_connect (beagle->details->current_query,
+ "finished", G_CALLBACK (beagle_finished), engine);
+ g_signal_connect (beagle->details->current_query,
+ "error", G_CALLBACK (beagle_error), engine);
+
+ /* We only want files */
+ beagle_query_add_text (beagle->details->current_query," type:File");
+
+ beagle_query_set_max_hits (beagle->details->current_query,
+ 1000);
+
+ text = caja_query_get_text (beagle->details->query);
+ beagle_query_add_text (beagle->details->current_query,
+ text);
+
+ mimetypes = caja_query_get_mime_types (beagle->details->query);
+ for (l = mimetypes; l != NULL; l = l->next)
+ {
+ char* temp;
+ mimetype = l->data;
+ temp = g_strconcat (" mimetype:", mimetype, NULL);
+ beagle_query_add_text (beagle->details->current_query,temp);
+ g_free (temp);
+ }
+
+ beagle->details->current_query_uri_prefix = caja_query_get_location (beagle->details->query);
+
+ if (!beagle_client_send_request_async (beagle->details->client,
+ BEAGLE_REQUEST (beagle->details->current_query), &error))
+ {
+ caja_search_engine_error (engine, error->message);
+ g_error_free (error);
+ }
+
+ /* These must live during the lifetime of the query */
+ g_free (text);
+ eel_g_list_free_deep (mimetypes);
+}
+
+static void
+caja_search_engine_beagle_stop (CajaSearchEngine *engine)
+{
+ CajaSearchEngineBeagle *beagle;
+
+ beagle = CAJA_SEARCH_ENGINE_BEAGLE (engine);
+
+ if (beagle->details->current_query)
+ {
+ g_object_unref (beagle->details->current_query);
+ beagle->details->current_query = NULL;
+ g_free (beagle->details->current_query_uri_prefix);
+ beagle->details->current_query_uri_prefix = NULL;
+ }
+}
+
+static gboolean
+caja_search_engine_beagle_is_indexed (CajaSearchEngine *engine)
+{
+ return TRUE;
+}
+
+static void
+caja_search_engine_beagle_set_query (CajaSearchEngine *engine, CajaQuery *query)
+{
+ CajaSearchEngineBeagle *beagle;
+
+ beagle = CAJA_SEARCH_ENGINE_BEAGLE (engine);
+
+ if (query)
+ {
+ g_object_ref (query);
+ }
+
+ if (beagle->details->query)
+ {
+ g_object_unref (beagle->details->query);
+ }
+
+ beagle->details->query = query;
+}
+
+static void
+caja_search_engine_beagle_class_init (CajaSearchEngineBeagleClass *class)
+{
+ GObjectClass *gobject_class;
+ CajaSearchEngineClass *engine_class;
+
+ parent_class = g_type_class_peek_parent (class);
+
+ gobject_class = G_OBJECT_CLASS (class);
+ gobject_class->finalize = finalize;
+
+ engine_class = CAJA_SEARCH_ENGINE_CLASS (class);
+ engine_class->set_query = caja_search_engine_beagle_set_query;
+ engine_class->start = caja_search_engine_beagle_start;
+ engine_class->stop = caja_search_engine_beagle_stop;
+ engine_class->is_indexed = caja_search_engine_beagle_is_indexed;
+}
+
+static void
+caja_search_engine_beagle_init (CajaSearchEngineBeagle *engine)
+{
+ engine->details = g_new0 (CajaSearchEngineBeagleDetails, 1);
+}
+
+
+CajaSearchEngine *
+caja_search_engine_beagle_new (void)
+{
+ CajaSearchEngineBeagle *engine;
+ BeagleClient *client;
+
+ open_libbeagle ();
+
+ if (beagle_util_daemon_is_running == NULL ||
+ !beagle_util_daemon_is_running ())
+ {
+ /* check whether daemon is running as beagle_client_new
+ * doesn't fail when a stale socket file exists */
+ return NULL;
+ }
+
+ client = beagle_client_new (NULL);
+
+ if (client == NULL)
+ {
+ return NULL;
+ }
+
+ engine = g_object_new (CAJA_TYPE_SEARCH_ENGINE_BEAGLE, NULL);
+
+ engine->details->client = client;
+
+ return CAJA_SEARCH_ENGINE (engine);
+}
diff --git a/libcaja-private/caja-search-engine-beagle.h b/libcaja-private/caja-search-engine-beagle.h
new file mode 100644
index 00000000..bab7adab
--- /dev/null
+++ b/libcaja-private/caja-search-engine-beagle.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * Copyright (C) 2005 Novell, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Anders Carlsson <[email protected]>
+ *
+ */
+
+#ifndef CAJA_SEARCH_ENGINE_BEAGLE_H
+#define CAJA_SEARCH_ENGINE_BEAGLE_H
+
+#include <libcaja-private/caja-search-engine.h>
+
+#define CAJA_TYPE_SEARCH_ENGINE_BEAGLE (caja_search_engine_beagle_get_type ())
+#define CAJA_SEARCH_ENGINE_BEAGLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_SEARCH_ENGINE_BEAGLE, CajaSearchEngineBeagle))
+#define CAJA_SEARCH_ENGINE_BEAGLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_SEARCH_ENGINE_BEAGLE, CajaSearchEngineBeagleClass))
+#define CAJA_IS_SEARCH_ENGINE_BEAGLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_SEARCH_ENGINE_BEAGLE))
+#define CAJA_IS_SEARCH_ENGINE_BEAGLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_SEARCH_ENGINE_BEAGLE))
+#define CAJA_SEARCH_ENGINE_BEAGLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_SEARCH_ENGINE_BEAGLE, CajaSearchEngineBeagleClass))
+
+typedef struct CajaSearchEngineBeagleDetails CajaSearchEngineBeagleDetails;
+
+typedef struct CajaSearchEngineBeagle
+{
+ CajaSearchEngine parent;
+ CajaSearchEngineBeagleDetails *details;
+} CajaSearchEngineBeagle;
+
+typedef struct
+{
+ CajaSearchEngineClass parent_class;
+} CajaSearchEngineBeagleClass;
+
+GType caja_search_engine_beagle_get_type (void);
+
+CajaSearchEngine* caja_search_engine_beagle_new (void);
+
+#endif /* CAJA_SEARCH_ENGINE_BEAGLE_H */
diff --git a/libcaja-private/caja-search-engine-simple.c b/libcaja-private/caja-search-engine-simple.c
new file mode 100644
index 00000000..62bffd5e
--- /dev/null
+++ b/libcaja-private/caja-search-engine-simple.c
@@ -0,0 +1,464 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * Copyright (C) 2005 Red Hat, Inc
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Alexander Larsson <[email protected]>
+ *
+ */
+
+#include <config.h>
+#include "caja-search-engine-simple.h"
+
+#include <string.h>
+#include <glib.h>
+
+#include <eel/eel-gtk-macros.h>
+#include <eel/eel-glib-extensions.h>
+#include <gio/gio.h>
+
+#define BATCH_SIZE 500
+
+typedef struct
+{
+ CajaSearchEngineSimple *engine;
+ GCancellable *cancellable;
+
+ GList *mime_types;
+ char **words;
+ GList *found_list;
+
+ GQueue *directories; /* GFiles */
+
+ GHashTable *visited;
+
+ gint n_processed_files;
+ GList *uri_hits;
+} SearchThreadData;
+
+
+struct CajaSearchEngineSimpleDetails
+{
+ CajaQuery *query;
+
+ SearchThreadData *active_search;
+
+ gboolean query_finished;
+};
+
+
+static void caja_search_engine_simple_class_init (CajaSearchEngineSimpleClass *class);
+static void caja_search_engine_simple_init (CajaSearchEngineSimple *engine);
+
+G_DEFINE_TYPE (CajaSearchEngineSimple,
+ caja_search_engine_simple,
+ CAJA_TYPE_SEARCH_ENGINE);
+
+static CajaSearchEngineClass *parent_class = NULL;
+
+static void
+finalize (GObject *object)
+{
+ CajaSearchEngineSimple *simple;
+
+ simple = CAJA_SEARCH_ENGINE_SIMPLE (object);
+
+ if (simple->details->query)
+ {
+ g_object_unref (simple->details->query);
+ simple->details->query = NULL;
+ }
+
+ g_free (simple->details);
+
+ EEL_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
+}
+
+static SearchThreadData *
+search_thread_data_new (CajaSearchEngineSimple *engine,
+ CajaQuery *query)
+{
+ SearchThreadData *data;
+ char *text, *lower, *normalized, *uri;
+ GFile *location;
+
+ data = g_new0 (SearchThreadData, 1);
+
+ data->engine = engine;
+ data->directories = g_queue_new ();
+ data->visited = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+ uri = caja_query_get_location (query);
+ location = NULL;
+ if (uri != NULL)
+ {
+ location = g_file_new_for_uri (uri);
+ g_free (uri);
+ }
+ if (location == NULL)
+ {
+ location = g_file_new_for_path ("/");
+ }
+ g_queue_push_tail (data->directories, location);
+
+ text = caja_query_get_text (query);
+ normalized = g_utf8_normalize (text, -1, G_NORMALIZE_NFD);
+ lower = g_utf8_strdown (normalized, -1);
+ data->words = g_strsplit (lower, " ", -1);
+ g_free (text);
+ g_free (lower);
+ g_free (normalized);
+
+ data->mime_types = caja_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_strfreev (data->words);
+ eel_g_list_free_deep (data->mime_types);
+ eel_g_list_free_deep (data->uri_hits);
+ g_free (data);
+}
+
+static gboolean
+search_thread_done_idle (gpointer user_data)
+{
+ SearchThreadData *data;
+
+ data = user_data;
+
+ if (!g_cancellable_is_cancelled (data->cancellable))
+ {
+ caja_search_engine_finished (CAJA_SEARCH_ENGINE (data->engine));
+ data->engine->details->active_search = NULL;
+ }
+
+ search_thread_data_free (data);
+
+ return FALSE;
+}
+
+typedef struct
+{
+ GList *uris;
+ SearchThreadData *thread_data;
+} SearchHits;
+
+
+static gboolean
+search_thread_add_hits_idle (gpointer user_data)
+{
+ SearchHits *hits;
+
+ hits = user_data;
+
+ if (!g_cancellable_is_cancelled (hits->thread_data->cancellable))
+ {
+ caja_search_engine_hits_added (CAJA_SEARCH_ENGINE (hits->thread_data->engine),
+ hits->uris);
+ }
+
+ eel_g_list_free_deep (hits->uris);
+ g_free (hits);
+
+ return FALSE;
+}
+
+static void
+send_batch (SearchThreadData *data)
+{
+ SearchHits *hits;
+
+ data->n_processed_files = 0;
+
+ if (data->uri_hits)
+ {
+ hits = g_new (SearchHits, 1);
+ hits->uris = data->uri_hits;
+ hits->thread_data = data;
+ g_idle_add (search_thread_add_hits_idle, hits);
+ }
+ data->uri_hits = NULL;
+}
+
+#define STD_ATTRIBUTES \
+ G_FILE_ATTRIBUTE_STANDARD_NAME "," \
+ G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME "," \
+ G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN "," \
+ G_FILE_ATTRIBUTE_STANDARD_TYPE "," \
+ 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;
+ char *lower_name, *normalized;
+ gboolean hit;
+ int i;
+ GList *l;
+ const char *id;
+ gboolean visited;
+
+ enumerator = g_file_enumerate_children (dir,
+ data->mime_types != NULL ?
+ STD_ATTRIBUTES ","
+ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE
+ :
+ STD_ATTRIBUTES
+ ,
+ 0, data->cancellable, NULL);
+
+ if (enumerator == NULL)
+ {
+ return;
+ }
+
+ while ((info = g_file_enumerator_next_file (enumerator, data->cancellable, NULL)) != NULL)
+ {
+ if (g_file_info_get_is_hidden (info))
+ {
+ goto next;
+ }
+
+ display_name = g_file_info_get_display_name (info);
+ if (display_name == NULL)
+ {
+ goto next;
+ }
+
+ normalized = g_utf8_normalize (display_name, -1, G_NORMALIZE_NFD);
+ lower_name = g_utf8_strdown (normalized, -1);
+ g_free (normalized);
+
+ hit = TRUE;
+ for (i = 0; data->words[i] != NULL; i++)
+ {
+ if (strstr (lower_name, data->words[i]) == NULL)
+ {
+ hit = FALSE;
+ break;
+ }
+ }
+ g_free (lower_name);
+
+ if (hit && data->mime_types)
+ {
+ mime_type = g_file_info_get_content_type (info);
+ hit = FALSE;
+
+ for (l = data->mime_types; mime_type != NULL && l != NULL; l = l->next)
+ {
+ if (g_content_type_equals (mime_type, l->data))
+ {
+ hit = TRUE;
+ break;
+ }
+ }
+ }
+
+ child = g_file_get_child (dir, g_file_info_get_name (info));
+
+ if (hit)
+ {
+ data->uri_hits = g_list_prepend (data->uri_hits, g_file_get_uri (child));
+ }
+
+ data->n_processed_files++;
+ if (data->n_processed_files > BATCH_SIZE)
+ {
+ send_batch (data);
+ }
+
+ if (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);
+ }
+ send_batch (data);
+
+ g_idle_add (search_thread_done_idle, data);
+
+ return NULL;
+}
+
+static void
+caja_search_engine_simple_start (CajaSearchEngine *engine)
+{
+ CajaSearchEngineSimple *simple;
+ SearchThreadData *data;
+
+ simple = CAJA_SEARCH_ENGINE_SIMPLE (engine);
+
+ if (simple->details->active_search != NULL)
+ {
+ return;
+ }
+
+ if (simple->details->query == NULL)
+ {
+ return;
+ }
+
+ data = search_thread_data_new (simple, simple->details->query);
+
+ g_thread_create (search_thread_func, data, FALSE, NULL);
+
+ simple->details->active_search = data;
+}
+
+static void
+caja_search_engine_simple_stop (CajaSearchEngine *engine)
+{
+ CajaSearchEngineSimple *simple;
+
+ simple = CAJA_SEARCH_ENGINE_SIMPLE (engine);
+
+ if (simple->details->active_search != NULL)
+ {
+ g_cancellable_cancel (simple->details->active_search->cancellable);
+ simple->details->active_search = NULL;
+ }
+}
+
+static gboolean
+caja_search_engine_simple_is_indexed (CajaSearchEngine *engine)
+{
+ return FALSE;
+}
+
+static void
+caja_search_engine_simple_set_query (CajaSearchEngine *engine, CajaQuery *query)
+{
+ CajaSearchEngineSimple *simple;
+
+ simple = CAJA_SEARCH_ENGINE_SIMPLE (engine);
+
+ if (query)
+ {
+ g_object_ref (query);
+ }
+
+ if (simple->details->query)
+ {
+ g_object_unref (simple->details->query);
+ }
+
+ simple->details->query = query;
+}
+
+static void
+caja_search_engine_simple_class_init (CajaSearchEngineSimpleClass *class)
+{
+ GObjectClass *gobject_class;
+ CajaSearchEngineClass *engine_class;
+
+ parent_class = g_type_class_peek_parent (class);
+
+ gobject_class = G_OBJECT_CLASS (class);
+ gobject_class->finalize = finalize;
+
+ engine_class = CAJA_SEARCH_ENGINE_CLASS (class);
+ engine_class->set_query = caja_search_engine_simple_set_query;
+ engine_class->start = caja_search_engine_simple_start;
+ engine_class->stop = caja_search_engine_simple_stop;
+ engine_class->is_indexed = caja_search_engine_simple_is_indexed;
+}
+
+static void
+caja_search_engine_simple_init (CajaSearchEngineSimple *engine)
+{
+ engine->details = g_new0 (CajaSearchEngineSimpleDetails, 1);
+}
+
+
+CajaSearchEngine *
+caja_search_engine_simple_new (void)
+{
+ CajaSearchEngine *engine;
+
+ engine = g_object_new (CAJA_TYPE_SEARCH_ENGINE_SIMPLE, NULL);
+
+ return engine;
+}
diff --git a/libcaja-private/caja-search-engine-simple.h b/libcaja-private/caja-search-engine-simple.h
new file mode 100644
index 00000000..42baeda3
--- /dev/null
+++ b/libcaja-private/caja-search-engine-simple.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * Copyright (C) 2005 Red Hat, Inc
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Alexander Larsson <[email protected]>
+ *
+ */
+
+#ifndef CAJA_SEARCH_ENGINE_SIMPLE_H
+#define CAJA_SEARCH_ENGINE_SIMPLE_H
+
+#include <libcaja-private/caja-search-engine.h>
+
+#define CAJA_TYPE_SEARCH_ENGINE_SIMPLE (caja_search_engine_simple_get_type ())
+#define CAJA_SEARCH_ENGINE_SIMPLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_SEARCH_ENGINE_SIMPLE, CajaSearchEngineSimple))
+#define CAJA_SEARCH_ENGINE_SIMPLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_SEARCH_ENGINE_SIMPLE, CajaSearchEngineSimpleClass))
+#define CAJA_IS_SEARCH_ENGINE_SIMPLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_SEARCH_ENGINE_SIMPLE))
+#define CAJA_IS_SEARCH_ENGINE_SIMPLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_SEARCH_ENGINE_SIMPLE))
+#define CAJA_SEARCH_ENGINE_SIMPLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_SEARCH_ENGINE_SIMPLE, CajaSearchEngineSimpleClass))
+
+typedef struct CajaSearchEngineSimpleDetails CajaSearchEngineSimpleDetails;
+
+typedef struct CajaSearchEngineSimple
+{
+ CajaSearchEngine parent;
+ CajaSearchEngineSimpleDetails *details;
+} CajaSearchEngineSimple;
+
+typedef struct
+{
+ CajaSearchEngineClass parent_class;
+} CajaSearchEngineSimpleClass;
+
+GType caja_search_engine_simple_get_type (void);
+
+CajaSearchEngine* caja_search_engine_simple_new (void);
+
+#endif /* CAJA_SEARCH_ENGINE_SIMPLE_H */
diff --git a/libcaja-private/caja-search-engine-tracker.c b/libcaja-private/caja-search-engine-tracker.c
new file mode 100644
index 00000000..90be40b3
--- /dev/null
+++ b/libcaja-private/caja-search-engine-tracker.c
@@ -0,0 +1,580 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * Copyright (C) 2005 Mr Jamie McCracken
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Jamie McCracken <[email protected]>
+ *
+ */
+
+#include <config.h>
+#include "caja-search-engine-tracker.h"
+#include <eel/eel-gtk-macros.h>
+#include <eel/eel-glib-extensions.h>
+#include <gmodule.h>
+#include <string.h>
+
+
+typedef struct _TrackerClient TrackerClient;
+
+typedef enum
+{
+ TRACKER_0_6 = 1 << 0,
+ TRACKER_0_7 = 1 << 1,
+ TRACKER_0_8 = 1 << 2
+} TrackerVersion;
+
+
+/* tracker 0.6 API */
+typedef void (*TrackerArrayReply) (char **result, GError *error, gpointer user_data);
+
+static TrackerClient * (*tracker_connect) (gboolean enable_warnings,
+ gint timeout) = NULL;
+static void (*tracker_disconnect) (TrackerClient *client) = NULL;
+static void (*tracker_cancel_last_call) (TrackerClient *client) = NULL;
+static int (*tracker_get_version) (TrackerClient *client, GError **error) = NULL;
+
+
+static void (*tracker_search_metadata_by_text_async) (TrackerClient *client,
+ const char *query,
+ TrackerArrayReply callback,
+ gpointer user_data) = NULL;
+static void (*tracker_search_metadata_by_text_and_mime_async) (TrackerClient *client,
+ const char *query,
+ const char **mimes,
+ TrackerArrayReply callback,
+ gpointer user_data) = NULL;
+static void (*tracker_search_metadata_by_text_and_location_async) (TrackerClient *client,
+ const char *query,
+ const char *location,
+ TrackerArrayReply callback,
+ gpointer user_data) = NULL;
+static void (*tracker_search_metadata_by_text_and_mime_and_location_async) (TrackerClient *client,
+ const char *query,
+ const char **mimes,
+ const char *location,
+ TrackerArrayReply callback,
+ gpointer user_data) = NULL;
+
+
+/* tracker 0.8 API */
+typedef enum
+{
+ TRACKER_CLIENT_ENABLE_WARNINGS = 1 << 0
+} TrackerClientFlags;
+
+typedef void (*TrackerReplyGPtrArray) (GPtrArray *result,
+ GError *error,
+ gpointer user_data);
+
+static TrackerClient * (*tracker_client_new) (TrackerClientFlags flags,
+ gint timeout) = NULL;
+static gchar * (*tracker_sparql_escape) (const gchar *str) = NULL;
+static guint (*tracker_resources_sparql_query_async) (TrackerClient *client,
+ const gchar *query,
+ TrackerReplyGPtrArray callback,
+ gpointer user_data) = NULL;
+
+
+static struct TrackerDlMapping
+{
+ const char *fn_name;
+ gpointer *fn_ptr_ref;
+ TrackerVersion versions;
+} tracker_dl_mapping[] =
+{
+#define MAP(a,v) { #a, (gpointer *)&a, v }
+ MAP (tracker_connect, TRACKER_0_6 | TRACKER_0_7),
+ MAP (tracker_disconnect, TRACKER_0_6 | TRACKER_0_7),
+ MAP (tracker_get_version, TRACKER_0_6),
+ MAP (tracker_cancel_last_call, TRACKER_0_6 | TRACKER_0_7 | TRACKER_0_8),
+ MAP (tracker_search_metadata_by_text_async, TRACKER_0_6 | TRACKER_0_7),
+ MAP (tracker_search_metadata_by_text_and_location_async, TRACKER_0_6 | TRACKER_0_7),
+ MAP (tracker_client_new, TRACKER_0_8),
+ MAP (tracker_sparql_escape, TRACKER_0_8),
+ MAP (tracker_resources_sparql_query_async, TRACKER_0_8)
+#undef MAP
+};
+
+
+static TrackerVersion
+open_libtracker (void)
+{
+ static gboolean done = FALSE;
+ static TrackerVersion version = 0;
+ gpointer x;
+
+ if (!done)
+ {
+ int i;
+ GModule *tracker;
+ GModuleFlags flags;
+
+ done = TRUE;
+ flags = G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL;
+
+ tracker = g_module_open ("libtracker-client-0.8.so.0", flags);
+ version = TRACKER_0_8;
+
+ if (!tracker)
+ {
+ tracker = g_module_open ("libtracker-client-0.7.so.0", flags);
+
+ if (tracker && !g_module_symbol (tracker, "tracker_resources_sparql_query_async", &x))
+ {
+ version = TRACKER_0_7;
+ }
+ }
+
+ if (!tracker)
+ {
+ tracker = g_module_open ("libtrackerclient.so.0", flags);
+ version = TRACKER_0_6;
+ }
+
+ if (!tracker)
+ {
+ tracker = g_module_open ("libtracker.so.0", flags);
+ version = TRACKER_0_6;
+ }
+
+ if (!tracker)
+ return 0;
+
+ for (i = 0; i < G_N_ELEMENTS (tracker_dl_mapping); i++)
+ {
+ if ((tracker_dl_mapping[i].versions & version) == 0)
+ continue;
+
+ if (!g_module_symbol (tracker, tracker_dl_mapping[i].fn_name,
+ tracker_dl_mapping[i].fn_ptr_ref))
+ {
+ g_warning ("Missing symbol '%s' in libtracker\n",
+ tracker_dl_mapping[i].fn_name);
+ g_module_close (tracker);
+
+ for (i = 0; i < G_N_ELEMENTS (tracker_dl_mapping); i++)
+ tracker_dl_mapping[i].fn_ptr_ref = NULL;
+
+ return 0;
+ }
+ }
+ }
+
+ return version;
+}
+
+
+struct CajaSearchEngineTrackerDetails
+{
+ CajaQuery *query;
+ TrackerClient *client;
+ gboolean query_pending;
+ TrackerVersion version;
+};
+
+
+static void caja_search_engine_tracker_class_init (CajaSearchEngineTrackerClass *class);
+static void caja_search_engine_tracker_init (CajaSearchEngineTracker *engine);
+
+G_DEFINE_TYPE (CajaSearchEngineTracker,
+ caja_search_engine_tracker,
+ CAJA_TYPE_SEARCH_ENGINE);
+
+static CajaSearchEngineClass *parent_class = NULL;
+
+static void
+finalize (GObject *object)
+{
+ CajaSearchEngineTracker *tracker;
+
+ tracker = CAJA_SEARCH_ENGINE_TRACKER (object);
+
+ if (tracker->details->query)
+ {
+ g_object_unref (tracker->details->query);
+ tracker->details->query = NULL;
+ }
+
+ if (tracker->details->version == TRACKER_0_8)
+ {
+ g_object_unref (tracker->details->client);
+ }
+ else
+ {
+ tracker_disconnect (tracker->details->client);
+ }
+
+ g_free (tracker->details);
+
+ EEL_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
+}
+
+
+/* stolen from tracker sources, tracker.c */
+static void
+sparql_append_string_literal (GString *sparql,
+ const gchar *str)
+{
+ char *s;
+
+ s = tracker_sparql_escape (str);
+
+ g_string_append_c (sparql, '"');
+ g_string_append (sparql, s);
+ g_string_append_c (sparql, '"');
+
+ g_free (s);
+}
+
+
+static void
+search_callback (gpointer results, GError *error, gpointer user_data)
+{
+ CajaSearchEngineTracker *tracker;
+ char **results_p;
+ GPtrArray *OUT_result;
+ GList *hit_uris;
+ gint i;
+ char *uri;
+
+ tracker = CAJA_SEARCH_ENGINE_TRACKER (user_data);
+ hit_uris = NULL;
+
+ tracker->details->query_pending = FALSE;
+
+ if (error)
+ {
+ caja_search_engine_error (CAJA_SEARCH_ENGINE (tracker), error->message);
+ g_error_free (error);
+ return;
+ }
+
+ if (! results)
+ {
+ return;
+ }
+
+ if (tracker->details->version == TRACKER_0_8)
+ {
+ /* new tracker 0.8 API */
+ OUT_result = (GPtrArray*) results;
+
+ for (i = 0; i < OUT_result->len; i++)
+ {
+ uri = g_strdup (((gchar **) OUT_result->pdata[i])[0]);
+ if (uri)
+ {
+ hit_uris = g_list_prepend (hit_uris, (char *)uri);
+ }
+ }
+
+ g_ptr_array_foreach (OUT_result, (GFunc) g_free, NULL);
+ g_ptr_array_free (OUT_result, TRUE);
+
+ }
+ else
+ {
+ /* old tracker 0.6 API */
+ for (results_p = results; *results_p; results_p++)
+ {
+ if (tracker->details->version == TRACKER_0_6)
+ uri = g_filename_to_uri (*results_p, NULL, NULL);
+ else
+ uri = g_strdup (*results_p);
+
+ if (uri)
+ {
+ hit_uris = g_list_prepend (hit_uris, (char *)uri);
+ }
+ }
+ g_strfreev ((gchar **)results);
+ }
+
+ caja_search_engine_hits_added (CAJA_SEARCH_ENGINE (tracker), hit_uris);
+ caja_search_engine_finished (CAJA_SEARCH_ENGINE (tracker));
+ g_list_foreach (hit_uris, (GFunc) g_free, NULL);
+ g_list_free (hit_uris);
+}
+
+
+static void
+caja_search_engine_tracker_start (CajaSearchEngine *engine)
+{
+ CajaSearchEngineTracker *tracker;
+ GList *mimetypes, *l;
+ char *search_text, *location, *location_uri;
+ char **mimes;
+ int i, mime_count;
+ GString *sparql;
+
+ tracker = CAJA_SEARCH_ENGINE_TRACKER (engine);
+
+
+ if (tracker->details->query_pending)
+ {
+ return;
+ }
+
+ if (tracker->details->query == NULL)
+ {
+ return;
+ }
+
+ search_text = caja_query_get_text (tracker->details->query);
+
+ mimetypes = caja_query_get_mime_types (tracker->details->query);
+
+ location_uri = caja_query_get_location (tracker->details->query);
+
+ if (location_uri)
+ {
+ location = (tracker->details->version == TRACKER_0_6) ?
+ g_filename_from_uri (location_uri, NULL, NULL) :
+ g_strdup (location_uri);
+ g_free (location_uri);
+ }
+ else
+ {
+ location = NULL;
+ }
+
+ mime_count = g_list_length (mimetypes);
+
+ i = 0;
+ sparql = NULL;
+
+ if (tracker->details->version == TRACKER_0_8)
+ {
+ /* new tracker 0.8 API */
+ sparql = g_string_new ("SELECT ?url WHERE { ?file a nfo:FileDataObject ; nie:url ?url; ");
+ if (mime_count > 0)
+ g_string_append (sparql, "nie:mimeType ?mime ; ");
+ g_string_append (sparql, "fts:match ");
+ sparql_append_string_literal (sparql, search_text);
+
+ if (location || mime_count > 0)
+ {
+ g_string_append (sparql, " . FILTER (");
+
+ if (location)
+ {
+ g_string_append (sparql, "fn:starts-with(?url, ");
+ sparql_append_string_literal (sparql, location);
+ g_string_append (sparql, ")");
+ }
+
+ if (mime_count > 0)
+ {
+ if (location)
+ g_string_append (sparql, " && ");
+ g_string_append (sparql, "(");
+ for (l = mimetypes; l != NULL; l = l->next)
+ {
+ if (l != mimetypes)
+ g_string_append (sparql, " || ");
+ g_string_append (sparql, "?mime = ");
+ sparql_append_string_literal (sparql, l->data);
+ }
+ g_string_append (sparql, ")");
+ }
+
+ g_string_append (sparql, ")");
+ }
+ g_string_append (sparql, " }");
+
+ tracker_resources_sparql_query_async (tracker->details->client,
+ sparql->str,
+ (TrackerReplyGPtrArray) search_callback,
+ tracker);
+ g_string_free (sparql, TRUE);
+
+ }
+ else
+ {
+ /* old tracker 0.6 API */
+ if (mime_count > 0)
+ {
+ /* convert list into array */
+ mimes = g_new (char *, (mime_count + 1));
+
+ for (l = mimetypes; l != NULL; l = l->next)
+ {
+ mimes[i] = g_strdup (l->data);
+ i++;
+ }
+
+ mimes[mime_count] = NULL;
+
+ if (location)
+ {
+ tracker_search_metadata_by_text_and_mime_and_location_async (tracker->details->client,
+ search_text, (const char **)mimes, location,
+ (TrackerArrayReply) search_callback,
+ tracker);
+ }
+ else
+ {
+ tracker_search_metadata_by_text_and_mime_async (tracker->details->client,
+ search_text, (const char**)mimes,
+ (TrackerArrayReply) search_callback,
+ tracker);
+ }
+
+ g_strfreev (mimes);
+
+ }
+ else
+ {
+ if (location)
+ {
+ tracker_search_metadata_by_text_and_location_async (tracker->details->client,
+ search_text,
+ location,
+ (TrackerArrayReply) search_callback,
+ tracker);
+ }
+ else
+ {
+ tracker_search_metadata_by_text_async (tracker->details->client,
+ search_text,
+ (TrackerArrayReply) search_callback,
+ tracker);
+ }
+ }
+ }
+
+ g_free (location);
+
+ tracker->details->query_pending = TRUE;
+ g_free (search_text);
+ eel_g_list_free_deep (mimetypes);
+}
+
+static void
+caja_search_engine_tracker_stop (CajaSearchEngine *engine)
+{
+ CajaSearchEngineTracker *tracker;
+
+ tracker = CAJA_SEARCH_ENGINE_TRACKER (engine);
+
+ if (tracker->details->query && tracker->details->query_pending)
+ {
+ tracker_cancel_last_call (tracker->details->client);
+ tracker->details->query_pending = FALSE;
+ }
+}
+
+static gboolean
+caja_search_engine_tracker_is_indexed (CajaSearchEngine *engine)
+{
+ return TRUE;
+}
+
+static void
+caja_search_engine_tracker_set_query (CajaSearchEngine *engine, CajaQuery *query)
+{
+ CajaSearchEngineTracker *tracker;
+
+ tracker = CAJA_SEARCH_ENGINE_TRACKER (engine);
+
+ if (query)
+ {
+ g_object_ref (query);
+ }
+
+ if (tracker->details->query)
+ {
+ g_object_unref (tracker->details->query);
+ }
+
+ tracker->details->query = query;
+}
+
+static void
+caja_search_engine_tracker_class_init (CajaSearchEngineTrackerClass *class)
+{
+ GObjectClass *gobject_class;
+ CajaSearchEngineClass *engine_class;
+
+ parent_class = g_type_class_peek_parent (class);
+
+ gobject_class = G_OBJECT_CLASS (class);
+ gobject_class->finalize = finalize;
+
+ engine_class = CAJA_SEARCH_ENGINE_CLASS (class);
+ engine_class->set_query = caja_search_engine_tracker_set_query;
+ engine_class->start = caja_search_engine_tracker_start;
+ engine_class->stop = caja_search_engine_tracker_stop;
+ engine_class->is_indexed = caja_search_engine_tracker_is_indexed;
+}
+
+static void
+caja_search_engine_tracker_init (CajaSearchEngineTracker *engine)
+{
+ engine->details = g_new0 (CajaSearchEngineTrackerDetails, 1);
+}
+
+
+CajaSearchEngine *
+caja_search_engine_tracker_new (void)
+{
+ CajaSearchEngineTracker *engine;
+ TrackerClient *tracker_client;
+ TrackerVersion version;
+
+ version = open_libtracker ();
+
+ if (version == TRACKER_0_8)
+ {
+ tracker_client = tracker_client_new (TRACKER_CLIENT_ENABLE_WARNINGS, G_MAXINT);
+ }
+ else
+ {
+ if (! tracker_connect)
+ return NULL;
+
+ tracker_client = tracker_connect (FALSE, -1);
+ }
+
+ if (!tracker_client)
+ {
+ return NULL;
+ }
+
+ if (version == TRACKER_0_6)
+ {
+ GError *err = NULL;
+
+ tracker_get_version (tracker_client, &err);
+
+ if (err != NULL)
+ {
+ g_error_free (err);
+ tracker_disconnect (tracker_client);
+ return NULL;
+ }
+ }
+
+ engine = g_object_new (CAJA_TYPE_SEARCH_ENGINE_TRACKER, NULL);
+
+ engine->details->client = tracker_client;
+ engine->details->query_pending = FALSE;
+ engine->details->version = version;
+
+ return CAJA_SEARCH_ENGINE (engine);
+}
diff --git a/libcaja-private/caja-search-engine-tracker.h b/libcaja-private/caja-search-engine-tracker.h
new file mode 100644
index 00000000..2de11a70
--- /dev/null
+++ b/libcaja-private/caja-search-engine-tracker.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * Copyright (C) 2005 Mr Jamie McCracken
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Jamie McCracken ([email protected])
+ *
+ */
+
+#ifndef CAJA_SEARCH_ENGINE_TRACKER_H
+#define CAJA_SEARCH_ENGINE_TRACKER_H
+
+#include <libcaja-private/caja-search-engine.h>
+
+#define CAJA_TYPE_SEARCH_ENGINE_TRACKER (caja_search_engine_tracker_get_type ())
+#define CAJA_SEARCH_ENGINE_TRACKER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_SEARCH_ENGINE_TRACKER, CajaSearchEngineTracker))
+#define CAJA_SEARCH_ENGINE_TRACKER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_SEARCH_ENGINE_TRACKER, CajaSearchEngineTrackerClass))
+#define CAJA_IS_SEARCH_ENGINE_TRACKER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_SEARCH_ENGINE_TRACKER))
+#define CAJA_IS_SEARCH_ENGINE_TRACKER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_SEARCH_ENGINE_TRACKER))
+#define CAJA_SEARCH_ENGINE_TRACKER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_SEARCH_ENGINE_TRACKER, CajaSearchEngineTrackerClass))
+
+typedef struct CajaSearchEngineTrackerDetails CajaSearchEngineTrackerDetails;
+
+typedef struct CajaSearchEngineTracker
+{
+ CajaSearchEngine parent;
+ CajaSearchEngineTrackerDetails *details;
+} CajaSearchEngineTracker;
+
+typedef struct
+{
+ CajaSearchEngineClass parent_class;
+} CajaSearchEngineTrackerClass;
+
+GType caja_search_engine_tracker_get_type (void);
+
+CajaSearchEngine* caja_search_engine_tracker_new (void);
+
+#endif /* CAJA_SEARCH_ENGINE_TRACKER_H */
diff --git a/libcaja-private/caja-search-engine.c b/libcaja-private/caja-search-engine.c
new file mode 100644
index 00000000..539d1442
--- /dev/null
+++ b/libcaja-private/caja-search-engine.c
@@ -0,0 +1,216 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * Copyright (C) 2005 Novell, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Anders Carlsson <[email protected]>
+ *
+ */
+
+#include <config.h>
+#include "caja-search-engine.h"
+#include "caja-search-engine-beagle.h"
+#include "caja-search-engine-simple.h"
+#include "caja-search-engine-tracker.h"
+
+#include <eel/eel-gtk-macros.h>
+
+struct CajaSearchEngineDetails
+{
+ int none;
+};
+
+enum
+{
+ HITS_ADDED,
+ HITS_SUBTRACTED,
+ FINISHED,
+ ERROR,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+static void caja_search_engine_class_init (CajaSearchEngineClass *class);
+static void caja_search_engine_init (CajaSearchEngine *engine);
+
+G_DEFINE_ABSTRACT_TYPE (CajaSearchEngine,
+ caja_search_engine,
+ G_TYPE_OBJECT);
+
+static GObjectClass *parent_class = NULL;
+
+static void
+finalize (GObject *object)
+{
+ CajaSearchEngine *engine;
+
+ engine = CAJA_SEARCH_ENGINE (object);
+
+ g_free (engine->details);
+
+ EEL_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
+}
+
+static void
+caja_search_engine_class_init (CajaSearchEngineClass *class)
+{
+ GObjectClass *gobject_class;
+
+ parent_class = g_type_class_peek_parent (class);
+
+ gobject_class = G_OBJECT_CLASS (class);
+ gobject_class->finalize = finalize;
+
+ signals[HITS_ADDED] =
+ g_signal_new ("hits-added",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaSearchEngineClass, hits_added),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
+
+ signals[HITS_SUBTRACTED] =
+ g_signal_new ("hits-subtracted",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaSearchEngineClass, hits_subtracted),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
+
+ signals[FINISHED] =
+ g_signal_new ("finished",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaSearchEngineClass, finished),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[ERROR] =
+ g_signal_new ("error",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaSearchEngineClass, error),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE, 1,
+ G_TYPE_STRING);
+
+}
+
+static void
+caja_search_engine_init (CajaSearchEngine *engine)
+{
+ engine->details = g_new0 (CajaSearchEngineDetails, 1);
+}
+
+CajaSearchEngine *
+caja_search_engine_new (void)
+{
+ CajaSearchEngine *engine;
+
+ engine = caja_search_engine_tracker_new ();
+ if (engine)
+ {
+ return engine;
+ }
+
+ engine = caja_search_engine_beagle_new ();
+ if (engine)
+ {
+ return engine;
+ }
+
+ engine = caja_search_engine_simple_new ();
+ return engine;
+}
+
+void
+caja_search_engine_set_query (CajaSearchEngine *engine, CajaQuery *query)
+{
+ g_return_if_fail (CAJA_IS_SEARCH_ENGINE (engine));
+ g_return_if_fail (CAJA_SEARCH_ENGINE_GET_CLASS (engine)->set_query != NULL);
+
+ CAJA_SEARCH_ENGINE_GET_CLASS (engine)->set_query (engine, query);
+}
+
+void
+caja_search_engine_start (CajaSearchEngine *engine)
+{
+ g_return_if_fail (CAJA_IS_SEARCH_ENGINE (engine));
+ g_return_if_fail (CAJA_SEARCH_ENGINE_GET_CLASS (engine)->start != NULL);
+
+ CAJA_SEARCH_ENGINE_GET_CLASS (engine)->start (engine);
+}
+
+
+void
+caja_search_engine_stop (CajaSearchEngine *engine)
+{
+ g_return_if_fail (CAJA_IS_SEARCH_ENGINE (engine));
+ g_return_if_fail (CAJA_SEARCH_ENGINE_GET_CLASS (engine)->stop != NULL);
+
+ CAJA_SEARCH_ENGINE_GET_CLASS (engine)->stop (engine);
+}
+
+gboolean
+caja_search_engine_is_indexed (CajaSearchEngine *engine)
+{
+ g_return_val_if_fail (CAJA_IS_SEARCH_ENGINE (engine), FALSE);
+ g_return_val_if_fail (CAJA_SEARCH_ENGINE_GET_CLASS (engine)->is_indexed != NULL, FALSE);
+
+ return CAJA_SEARCH_ENGINE_GET_CLASS (engine)->is_indexed (engine);
+}
+
+void
+caja_search_engine_hits_added (CajaSearchEngine *engine, GList *hits)
+{
+ g_return_if_fail (CAJA_IS_SEARCH_ENGINE (engine));
+
+ g_signal_emit (engine, signals[HITS_ADDED], 0, hits);
+}
+
+
+void
+caja_search_engine_hits_subtracted (CajaSearchEngine *engine, GList *hits)
+{
+ g_return_if_fail (CAJA_IS_SEARCH_ENGINE (engine));
+
+ g_signal_emit (engine, signals[HITS_SUBTRACTED], 0, hits);
+}
+
+
+void
+caja_search_engine_finished (CajaSearchEngine *engine)
+{
+ g_return_if_fail (CAJA_IS_SEARCH_ENGINE (engine));
+
+ g_signal_emit (engine, signals[FINISHED], 0);
+}
+
+void
+caja_search_engine_error (CajaSearchEngine *engine, const char *error_message)
+{
+ g_return_if_fail (CAJA_IS_SEARCH_ENGINE (engine));
+
+ g_signal_emit (engine, signals[ERROR], 0, error_message);
+}
diff --git a/libcaja-private/caja-search-engine.h b/libcaja-private/caja-search-engine.h
new file mode 100644
index 00000000..26fbee4e
--- /dev/null
+++ b/libcaja-private/caja-search-engine.h
@@ -0,0 +1,77 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * Copyright (C) 2005 Novell, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Anders Carlsson <[email protected]>
+ *
+ */
+
+#ifndef CAJA_SEARCH_ENGINE_H
+#define CAJA_SEARCH_ENGINE_H
+
+#include <glib-object.h>
+#include <libcaja-private/caja-query.h>
+
+#define CAJA_TYPE_SEARCH_ENGINE (caja_search_engine_get_type ())
+#define CAJA_SEARCH_ENGINE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_SEARCH_ENGINE, CajaSearchEngine))
+#define CAJA_SEARCH_ENGINE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_SEARCH_ENGINE, CajaSearchEngineClass))
+#define CAJA_IS_SEARCH_ENGINE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_SEARCH_ENGINE))
+#define CAJA_IS_SEARCH_ENGINE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_SEARCH_ENGINE))
+#define CAJA_SEARCH_ENGINE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_SEARCH_ENGINE, CajaSearchEngineClass))
+
+typedef struct CajaSearchEngineDetails CajaSearchEngineDetails;
+
+typedef struct CajaSearchEngine
+{
+ GObject parent;
+ CajaSearchEngineDetails *details;
+} CajaSearchEngine;
+
+typedef struct
+{
+ GObjectClass parent_class;
+
+ /* VTable */
+ void (*set_query) (CajaSearchEngine *engine, CajaQuery *query);
+ void (*start) (CajaSearchEngine *engine);
+ void (*stop) (CajaSearchEngine *engine);
+ gboolean (*is_indexed) (CajaSearchEngine *engine);
+
+ /* Signals */
+ void (*hits_added) (CajaSearchEngine *engine, GList *hits);
+ void (*hits_subtracted) (CajaSearchEngine *engine, GList *hits);
+ void (*finished) (CajaSearchEngine *engine);
+ void (*error) (CajaSearchEngine *engine, const char *error_message);
+} CajaSearchEngineClass;
+
+GType caja_search_engine_get_type (void);
+gboolean caja_search_engine_enabled (void);
+
+CajaSearchEngine* caja_search_engine_new (void);
+
+void caja_search_engine_set_query (CajaSearchEngine *engine, CajaQuery *query);
+void caja_search_engine_start (CajaSearchEngine *engine);
+void caja_search_engine_stop (CajaSearchEngine *engine);
+gboolean caja_search_engine_is_indexed (CajaSearchEngine *engine);
+
+void caja_search_engine_hits_added (CajaSearchEngine *engine, GList *hits);
+void caja_search_engine_hits_subtracted (CajaSearchEngine *engine, GList *hits);
+void caja_search_engine_finished (CajaSearchEngine *engine);
+void caja_search_engine_error (CajaSearchEngine *engine, const char *error_message);
+
+#endif /* CAJA_SEARCH_ENGINE_H */
diff --git a/libcaja-private/caja-sidebar-provider.c b/libcaja-private/caja-sidebar-provider.c
new file mode 100644
index 00000000..f5e9cf52
--- /dev/null
+++ b/libcaja-private/caja-sidebar-provider.c
@@ -0,0 +1,74 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-sidebar-provider.c: register and create CajaSidebars
+
+ 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Alexander Larsson <[email protected]>
+*/
+
+#include <string.h>
+#include "caja-sidebar-provider.h"
+
+static void
+caja_sidebar_provider_base_init (gpointer g_class)
+{
+}
+
+GType
+caja_sidebar_provider_get_type (void)
+{
+ static GType type = 0;
+
+ if (!type)
+ {
+ const GTypeInfo info =
+ {
+ sizeof (CajaSidebarProviderIface),
+ caja_sidebar_provider_base_init,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ 0,
+ 0,
+ NULL
+ };
+
+ type = g_type_register_static (G_TYPE_INTERFACE,
+ "CajaSidebarProvider",
+ &info, 0);
+ g_type_interface_add_prerequisite (type, G_TYPE_OBJECT);
+ }
+
+ return type;
+}
+
+CajaSidebar *
+caja_sidebar_provider_create (CajaSidebarProvider *provider,
+ CajaWindowInfo *window)
+{
+ return (* CAJA_SIDEBAR_PROVIDER_GET_IFACE (provider)->create) (provider, window);
+}
+
+
+GList *
+caja_list_sidebar_providers (void)
+{
+ return NULL;
+}
diff --git a/libcaja-private/caja-sidebar-provider.h b/libcaja-private/caja-sidebar-provider.h
new file mode 100644
index 00000000..63d90c56
--- /dev/null
+++ b/libcaja-private/caja-sidebar-provider.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-sidebar-provider.h: register and create CajaSidebars
+
+ 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Alexander Larsson <[email protected]>
+*/
+
+#ifndef CAJA_SIDEBAR_PROVIDER_H
+#define CAJA_SIDEBAR_PROVIDER_H
+
+#include <libcaja-private/caja-sidebar.h>
+#include <libcaja-private/caja-window-info.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define CAJA_TYPE_SIDEBAR_PROVIDER (caja_sidebar_provider_get_type ())
+#define CAJA_SIDEBAR_PROVIDER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_SIDEBAR_PROVIDER, CajaSidebarProvider))
+#define CAJA_IS_SIDEBAR_PROVIDER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_SIDEBAR_PROVIDER))
+#define CAJA_SIDEBAR_PROVIDER_GET_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), CAJA_TYPE_SIDEBAR_PROVIDER, CajaSidebarProviderIface))
+
+ typedef struct _CajaSidebarProvider CajaSidebarProvider;
+ typedef struct _CajaSidebarProviderIface CajaSidebarProviderIface;
+
+ struct _CajaSidebarProviderIface
+ {
+ GTypeInterface g_iface;
+
+ CajaSidebar * (*create) (CajaSidebarProvider *provider,
+ CajaWindowInfo *window);
+ };
+
+ /* Interface Functions */
+ GType caja_sidebar_provider_get_type (void);
+ CajaSidebar * caja_sidebar_provider_create (CajaSidebarProvider *provider,
+ CajaWindowInfo *window);
+ GList * caja_list_sidebar_providers (void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CAJA_SIDEBAR_PROVIDER_H */
diff --git a/libcaja-private/caja-sidebar.c b/libcaja-private/caja-sidebar.c
new file mode 100644
index 00000000..08ae7724
--- /dev/null
+++ b/libcaja-private/caja-sidebar.c
@@ -0,0 +1,128 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-sidebar.c: Interface for caja sidebar plugins
+
+ 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Alexander Larsson <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-sidebar.h"
+
+enum
+{
+ TAB_ICON_CHANGED,
+ ZOOM_PARAMETERS_CHANGED,
+ ZOOM_LEVEL_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint caja_sidebar_signals[LAST_SIGNAL] = { 0 };
+
+static void
+caja_sidebar_base_init (gpointer g_class)
+{
+ static gboolean initialized = FALSE;
+
+ if (! initialized)
+ {
+ caja_sidebar_signals[TAB_ICON_CHANGED] =
+ g_signal_new ("tab_icon_changed",
+ CAJA_TYPE_SIDEBAR,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaSidebarIface, tab_icon_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ initialized = TRUE;
+ }
+}
+
+GType
+caja_sidebar_get_type (void)
+{
+ static GType type = 0;
+
+ if (!type)
+ {
+ const GTypeInfo info =
+ {
+ sizeof (CajaSidebarIface),
+ caja_sidebar_base_init,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ 0,
+ 0,
+ NULL
+ };
+
+ type = g_type_register_static (G_TYPE_INTERFACE,
+ "CajaSidebar",
+ &info, 0);
+ g_type_interface_add_prerequisite (type, GTK_TYPE_WIDGET);
+ }
+
+ return type;
+}
+
+
+const char *
+caja_sidebar_get_sidebar_id (CajaSidebar *sidebar)
+{
+ g_return_val_if_fail (CAJA_IS_SIDEBAR (sidebar), NULL);
+
+ return (* CAJA_SIDEBAR_GET_IFACE (sidebar)->get_sidebar_id) (sidebar);
+}
+
+char *
+caja_sidebar_get_tab_label (CajaSidebar *sidebar)
+{
+ g_return_val_if_fail (CAJA_IS_SIDEBAR (sidebar), NULL);
+
+ return (* CAJA_SIDEBAR_GET_IFACE (sidebar)->get_tab_label) (sidebar);
+}
+
+char *
+caja_sidebar_get_tab_tooltip (CajaSidebar *sidebar)
+{
+ g_return_val_if_fail (CAJA_IS_SIDEBAR (sidebar), NULL);
+
+ return (* CAJA_SIDEBAR_GET_IFACE (sidebar)->get_tab_tooltip) (sidebar);
+}
+
+GdkPixbuf *
+caja_sidebar_get_tab_icon (CajaSidebar *sidebar)
+{
+ g_return_val_if_fail (CAJA_IS_SIDEBAR (sidebar), NULL);
+
+ return (* CAJA_SIDEBAR_GET_IFACE (sidebar)->get_tab_icon) (sidebar);
+}
+
+void
+caja_sidebar_is_visible_changed (CajaSidebar *sidebar,
+ gboolean is_visible)
+{
+ g_return_if_fail (CAJA_IS_SIDEBAR (sidebar));
+
+ (* CAJA_SIDEBAR_GET_IFACE (sidebar)->is_visible_changed) (sidebar,
+ is_visible);
+}
diff --git a/libcaja-private/caja-sidebar.h b/libcaja-private/caja-sidebar.h
new file mode 100644
index 00000000..6b8f694e
--- /dev/null
+++ b/libcaja-private/caja-sidebar.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-sidebar.h: Interface for caja sidebar plugins
+
+ 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Alexander Larsson <[email protected]>
+*/
+
+#ifndef CAJA_SIDEBAR_H
+#define CAJA_SIDEBAR_H
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define CAJA_TYPE_SIDEBAR (caja_sidebar_get_type ())
+#define CAJA_SIDEBAR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_SIDEBAR, CajaSidebar))
+#define CAJA_IS_SIDEBAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_SIDEBAR))
+#define CAJA_SIDEBAR_GET_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), CAJA_TYPE_SIDEBAR, CajaSidebarIface))
+
+ typedef struct _CajaSidebar CajaSidebar; /* dummy typedef */
+ typedef struct _CajaSidebarIface CajaSidebarIface;
+
+ /* Must also be a GtkWidget */
+ struct _CajaSidebarIface
+ {
+ GTypeInterface g_iface;
+
+ /* Signals: */
+ void (* tab_icon_changed) (CajaSidebar *sidebar);
+
+ /* VTable: */
+ const char * (* get_sidebar_id) (CajaSidebar *sidebar);
+ char * (* get_tab_label) (CajaSidebar *sidebar);
+ char * (* get_tab_tooltip) (CajaSidebar *sidebar);
+ GdkPixbuf * (* get_tab_icon) (CajaSidebar *sidebar);
+ void (* is_visible_changed) (CajaSidebar *sidebar,
+ gboolean is_visible);
+
+
+ /* Padding for future expansion */
+ void (*_reserved1) (void);
+ void (*_reserved2) (void);
+ void (*_reserved3) (void);
+ void (*_reserved4) (void);
+ void (*_reserved5) (void);
+ void (*_reserved6) (void);
+ void (*_reserved7) (void);
+ void (*_reserved8) (void);
+ };
+
+ GType caja_sidebar_get_type (void);
+
+ const char *caja_sidebar_get_sidebar_id (CajaSidebar *sidebar);
+ char * caja_sidebar_get_tab_label (CajaSidebar *sidebar);
+ char * caja_sidebar_get_tab_tooltip (CajaSidebar *sidebar);
+ GdkPixbuf * caja_sidebar_get_tab_icon (CajaSidebar *sidebar);
+ void caja_sidebar_is_visible_changed (CajaSidebar *sidebar,
+ gboolean is_visible);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CAJA_VIEW_H */
diff --git a/libcaja-private/caja-signaller.c b/libcaja-private/caja-signaller.c
new file mode 100644
index 00000000..217e13f4
--- /dev/null
+++ b/libcaja-private/caja-signaller.c
@@ -0,0 +1,115 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 1999, 2000 Eazel, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Author: John Sullivan <[email protected]>
+ */
+
+/* caja-signaller.h: Class to manage caja-wide signals that don't
+ * correspond to any particular object.
+ */
+
+#include <config.h>
+#include "caja-signaller.h"
+
+#include <eel/eel-debug.h>
+
+typedef GObject CajaSignaller;
+typedef GObjectClass CajaSignallerClass;
+
+enum
+{
+ HISTORY_LIST_CHANGED,
+ EMBLEMS_CHANGED,
+ POPUP_MENU_CHANGED,
+ USER_DIRS_CHANGED,
+ MIME_DATA_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+static GType caja_signaller_get_type (void);
+
+G_DEFINE_TYPE (CajaSignaller, caja_signaller, G_TYPE_OBJECT);
+
+GObject *
+caja_signaller_get_current (void)
+{
+ static GObject *global_signaller = NULL;
+
+ if (global_signaller == NULL)
+ {
+ global_signaller = g_object_new (caja_signaller_get_type (), NULL);
+ eel_debug_call_at_shutdown_with_data (g_object_unref, global_signaller);
+ }
+
+ return global_signaller;
+}
+
+static void
+caja_signaller_init (CajaSignaller *signaller)
+{
+}
+
+static void
+caja_signaller_class_init (CajaSignallerClass *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[EMBLEMS_CHANGED] =
+ g_signal_new ("emblems_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[USER_DIRS_CHANGED] =
+ g_signal_new ("user_dirs_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/libcaja-private/caja-signaller.h b/libcaja-private/caja-signaller.h
new file mode 100644
index 00000000..54d68cb0
--- /dev/null
+++ b/libcaja-private/caja-signaller.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 1999, 2000 Eazel, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Author: John Sullivan <[email protected]>
+ */
+
+/* caja-signaller.h: Class to manage caja-wide signals that don't
+ * correspond to any particular object.
+ */
+
+#ifndef CAJA_SIGNALLER_H
+#define CAJA_SIGNALLER_H
+
+#include <glib-object.h>
+
+/* CajaSignaller is a class that manages signals between
+ disconnected Caja code. Caja 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 CajaSignaller to connect with or emit signals for */
+GObject *caja_signaller_get_current (void);
+
+#endif /* CAJA_SIGNALLER_H */
diff --git a/libcaja-private/caja-thumbnails.c b/libcaja-private/caja-thumbnails.c
new file mode 100644
index 00000000..b656c550
--- /dev/null
+++ b/libcaja-private/caja-thumbnails.c
@@ -0,0 +1,1084 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Andy Hertzfeld <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-thumbnails.h"
+
+#define MATE_DESKTOP_USE_UNSTABLE_API
+
+#include "caja-directory-notify.h"
+#include "caja-global-preferences.h"
+#include "caja-file-utilities.h"
+#include <math.h>
+#include <eel/eel-gdk-pixbuf-extensions.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 <pthread.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <signal.h>
+#include <libmateui/mate-desktop-thumbnail.h>
+
+#include "caja-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 gpointer thumbnail_thread_start (gpointer data);
+
+/* 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;
+} CajaThumbnailInfo;
+
+struct CajaThumbnailAsyncLoadHandle
+{
+ GCancellable *cancellable;
+ char *file_path;
+ guint base_size;
+ guint nominal_size;
+ gboolean force_nominal;
+ CajaThumbnailAsyncLoadFunc load_func;
+ gpointer load_func_user_data;
+};
+
+
+/*
+ * 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 pthread_mutex_t thumbnails_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+/* 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;
+
+/* Added in glib 2.14 */
+#ifndef G_QUEUE_INIT
+#define G_QUEUE_INIT { NULL, NULL, 0 }
+#endif
+
+/* The list of CajaThumbnailInfo 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 CajaThumbnailInfo *currently_thumbnailing = NULL;
+
+static MateDesktopThumbnailFactory *thumbnail_factory = NULL;
+
+static int thumbnail_icon_size = 0;
+
+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 (CajaThumbnailInfo *info)
+{
+ g_free (info->image_uri);
+ g_free (info->mime_type);
+ g_free (info);
+}
+
+static MateDesktopThumbnailFactory *
+get_thumbnail_factory (void)
+{
+ static MateDesktopThumbnailFactory *thumbnail_factory = NULL;
+
+ if (thumbnail_factory == NULL)
+ {
+ thumbnail_factory = mate_desktop_thumbnail_factory_new (MATE_DESKTOP_THUMBNAIL_SIZE_NORMAL);
+ }
+
+ 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)
+{
+ pthread_attr_t thread_attributes;
+ pthread_t thumbnail_thread;
+
+ /* Don't do this in thread, since g_object_ref is not threadsafe */
+ if (thumbnail_factory == NULL)
+ {
+ thumbnail_factory = get_thumbnail_factory ();
+ }
+
+ /* We create the thread in the detached state, as we don't need/want
+ to join with it at any point. */
+ pthread_attr_init (&thread_attributes);
+ pthread_attr_setdetachstate (&thread_attributes,
+ PTHREAD_CREATE_DETACHED);
+#ifdef _POSIX_THREAD_ATTR_STACKSIZE
+ pthread_attr_setstacksize (&thread_attributes, 128*1024);
+#endif
+#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;
+ pthread_create (&thumbnail_thread, &thread_attributes,
+ thumbnail_thread_start, NULL);
+
+ thumbnail_thread_starter_id = 0;
+
+ return FALSE;
+}
+
+void
+caja_update_thumbnail_file_copied (const char *source_file_uri,
+ const char *destination_file_uri)
+{
+ char *old_thumbnail_path;
+ time_t mtime;
+ GdkPixbuf *pixbuf;
+ MateDesktopThumbnailFactory *factory;
+
+ old_thumbnail_path = mate_desktop_thumbnail_path_for_uri (source_file_uri, MATE_DESKTOP_THUMBNAIL_SIZE_NORMAL);
+ if (old_thumbnail_path != NULL &&
+ g_file_test (old_thumbnail_path, G_FILE_TEST_EXISTS))
+ {
+ if (get_file_mtime (destination_file_uri, &mtime))
+ {
+ pixbuf = gdk_pixbuf_new_from_file (old_thumbnail_path, NULL);
+
+ if (pixbuf && mate_desktop_thumbnail_has_uri (pixbuf, source_file_uri))
+ {
+ factory = get_thumbnail_factory ();
+ mate_desktop_thumbnail_factory_save_thumbnail (factory,
+ pixbuf,
+ destination_file_uri,
+ mtime);
+ }
+
+ if (pixbuf)
+ {
+ g_object_unref (pixbuf);
+ }
+ }
+ }
+
+ g_free (old_thumbnail_path);
+}
+
+void
+caja_update_thumbnail_file_renamed (const char *source_file_uri,
+ const char *destination_file_uri)
+{
+ caja_update_thumbnail_file_copied (source_file_uri, destination_file_uri);
+ caja_remove_thumbnail_for_file (source_file_uri);
+}
+
+void
+caja_remove_thumbnail_for_file (const char *file_uri)
+{
+ char *thumbnail_path;
+
+ thumbnail_path = mate_desktop_thumbnail_path_for_uri (file_uri, MATE_DESKTOP_THUMBNAIL_SIZE_NORMAL);
+ if (thumbnail_path != NULL)
+ {
+ unlink (thumbnail_path);
+ }
+ g_free (thumbnail_path);
+}
+
+static GdkPixbuf *
+caja_get_thumbnail_frame (void)
+{
+ char *image_path;
+ static GdkPixbuf *thumbnail_frame = NULL;
+
+ if (thumbnail_frame == NULL)
+ {
+ image_path = caja_pixmap_file ("thumbnail_frame.png");
+ if (image_path != NULL)
+ {
+ thumbnail_frame = gdk_pixbuf_new_from_file (image_path, NULL);
+ }
+ g_free (image_path);
+ }
+
+ return thumbnail_frame;
+}
+
+
+void
+caja_thumbnail_frame_image (GdkPixbuf **pixbuf)
+{
+ GdkPixbuf *pixbuf_with_frame, *frame;
+ int left_offset, top_offset, right_offset, bottom_offset;
+
+ /* The pixbuf isn't already framed (i.e., it was not made by
+ * an old Caja), so we must embed it in a frame.
+ */
+
+ frame = caja_get_thumbnail_frame ();
+ if (frame == NULL)
+ {
+ return;
+ }
+
+ left_offset = CAJA_THUMBNAIL_FRAME_LEFT;
+ top_offset = CAJA_THUMBNAIL_FRAME_TOP;
+ right_offset = CAJA_THUMBNAIL_FRAME_RIGHT;
+ bottom_offset = CAJA_THUMBNAIL_FRAME_BOTTOM;
+
+ pixbuf_with_frame = eel_embed_image_in_frame
+ (*pixbuf, frame,
+ left_offset, top_offset, right_offset, bottom_offset);
+ g_object_unref (*pixbuf);
+
+ *pixbuf = pixbuf_with_frame;
+}
+
+GdkPixbuf *
+caja_thumbnail_unframe_image (GdkPixbuf *pixbuf)
+{
+ GdkPixbuf *pixbuf_without_frame, *frame;
+ int left_offset, top_offset, right_offset, bottom_offset;
+ int w, h;
+
+ /* The pixbuf isn't already framed (i.e., it was not made by
+ * an old Caja), so we must embed it in a frame.
+ */
+
+ frame = caja_get_thumbnail_frame ();
+ if (frame == NULL)
+ {
+ return NULL;
+ }
+
+ left_offset = CAJA_THUMBNAIL_FRAME_LEFT;
+ top_offset = CAJA_THUMBNAIL_FRAME_TOP;
+ right_offset = CAJA_THUMBNAIL_FRAME_RIGHT;
+ bottom_offset = CAJA_THUMBNAIL_FRAME_BOTTOM;
+
+ w = gdk_pixbuf_get_width (pixbuf) - left_offset - right_offset;
+ h = gdk_pixbuf_get_height (pixbuf) - top_offset - bottom_offset;
+ pixbuf_without_frame =
+ gdk_pixbuf_new (gdk_pixbuf_get_colorspace (pixbuf),
+ gdk_pixbuf_get_has_alpha (pixbuf),
+ gdk_pixbuf_get_bits_per_sample (pixbuf),
+ w, h);
+
+ gdk_pixbuf_copy_area (pixbuf,
+ left_offset, top_offset,
+ w, h,
+ pixbuf_without_frame, 0, 0);
+
+ return pixbuf_without_frame;
+}
+
+typedef struct
+{
+ gboolean is_thumbnail;
+ guint base_size;
+ guint nominal_size;
+ gboolean force_nominal;
+ int original_height;
+ int original_width;
+ double *scale_x_out;
+ double *scale_y_out;
+} ThumbnailLoadArgs;
+
+static void
+thumbnail_loader_size_prepared (GdkPixbufLoader *loader,
+ int width,
+ int height,
+ ThumbnailLoadArgs *args)
+{
+ int size = MAX (width, height);
+
+ args->original_width = width;
+ args->original_height = height;
+
+ if (args->force_nominal)
+ {
+ args->base_size = size;
+ }
+ else if (args->base_size == 0)
+ {
+ if (args->is_thumbnail)
+ {
+ args->base_size = 128 * CAJA_ICON_SIZE_STANDARD / thumbnail_icon_size;
+ }
+ else
+ {
+ if (size > args->nominal_size * thumbnail_icon_size / CAJA_ICON_SIZE_STANDARD)
+ {
+ args->base_size = size * CAJA_ICON_SIZE_STANDARD / thumbnail_icon_size;
+ }
+ else if (size > CAJA_ICON_SIZE_STANDARD)
+ {
+ args->base_size = args->nominal_size;
+ }
+ else
+ {
+ /* Don't scale up small icons */
+ args->base_size = CAJA_ICON_SIZE_STANDARD;
+ }
+ }
+ }
+
+ if (args->base_size != args->nominal_size)
+ {
+ double scale;
+
+ scale = (double) args->nominal_size / args->base_size;
+
+ if ((int) (width * scale) > CAJA_ICON_MAXIMUM_SIZE ||
+ (int) (height * scale) > CAJA_ICON_MAXIMUM_SIZE)
+ {
+ scale = MIN ((double) CAJA_ICON_MAXIMUM_SIZE / width,
+ (double) CAJA_ICON_MAXIMUM_SIZE / height);
+ }
+
+ width = MAX (1, floor (width * scale + 0.5));
+ height = MAX (1, floor (height * scale + 0.5));
+
+ gdk_pixbuf_loader_set_size (loader, width, height);
+ }
+
+}
+
+static void
+thumbnail_loader_area_prepared (GdkPixbufLoader *loader,
+ ThumbnailLoadArgs *args)
+{
+ GdkPixbuf *pixbuf;
+
+ pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
+
+ *args->scale_x_out = (double) gdk_pixbuf_get_width (pixbuf) / args->original_width;
+ *args->scale_y_out = (double) gdk_pixbuf_get_height (pixbuf) / args->original_height;
+}
+
+static GdkPixbuf *
+get_pixbuf_from_data (const unsigned char *buffer,
+ gsize buflen,
+ const char *path,
+ guint base_size,
+ guint nominal_size,
+ gboolean force_nominal,
+ double *scale_x_out,
+ double *scale_y_out)
+{
+ GdkPixbufLoader *loader;
+ GdkPixbuf *pixbuf;
+ ThumbnailLoadArgs args;
+ GError *error;
+
+ if (thumbnail_icon_size == 0)
+ {
+ eel_preferences_add_auto_integer (CAJA_PREFERENCES_ICON_VIEW_THUMBNAIL_SIZE,
+ &thumbnail_icon_size);
+ }
+
+ loader = gdk_pixbuf_loader_new ();
+ g_signal_connect (loader, "size-prepared",
+ G_CALLBACK (thumbnail_loader_size_prepared),
+ &args);
+ g_signal_connect (loader, "area-prepared",
+ G_CALLBACK (thumbnail_loader_area_prepared),
+ &args);
+
+ args.is_thumbnail = strstr (path, "/.thumbnails/") != NULL;
+ args.base_size = base_size;
+ args.nominal_size = nominal_size;
+ args.force_nominal = force_nominal;
+ args.scale_x_out = scale_x_out;
+ args.scale_y_out = scale_y_out;
+
+ error = NULL;
+
+ if (!gdk_pixbuf_loader_write (loader, buffer, buflen, &error))
+ {
+ g_message ("Failed to write %s to thumbnail pixbuf loader: %s", path, error->message);
+
+ gdk_pixbuf_loader_close (loader, NULL);
+ g_object_unref (G_OBJECT (loader));
+ g_error_free (error);
+
+ return NULL;
+ }
+
+ error = NULL;
+
+ if (!gdk_pixbuf_loader_close (loader, &error) ||
+ /* Seems we have to check this even if it returned TRUE (#403255) */
+ error != NULL)
+ {
+ /* In some cases, we don't get an error even with FALSE returns (#538888) */
+ if (error != NULL)
+ {
+ g_message ("Failed to close thumbnail pixbuf loader for %s: %s", path, error->message);
+ }
+ else
+ {
+ g_message ("Failed to close thumbnail pixbuf loader for %s", path);
+ }
+
+ g_object_unref (G_OBJECT (loader));
+ g_error_free (error);
+
+ return NULL;
+ }
+
+ pixbuf = g_object_ref (gdk_pixbuf_loader_get_pixbuf (loader));
+
+ g_object_unref (G_OBJECT (loader));
+
+ return pixbuf;
+}
+
+
+/* routine to load an image from the passed-in path
+ */
+GdkPixbuf *
+caja_thumbnail_load_image (const char *path,
+ guint base_size,
+ guint nominal_size,
+ gboolean force_nominal,
+ double *scale_x_out,
+ double *scale_y_out)
+{
+ GdkPixbuf *pixbuf;
+ guchar *buffer;
+ gsize buflen;
+ GError *error;
+
+ error = NULL;
+
+ if (!g_file_get_contents (path, (gchar **) &buffer, &buflen, &error))
+ {
+ g_message ("Failed to load %s into memory: %s", path, error->message);
+
+ g_error_free (error);
+
+ return NULL;
+ }
+
+ pixbuf = get_pixbuf_from_data (buffer, buflen, path,
+ base_size, nominal_size, force_nominal,
+ scale_x_out, scale_y_out);
+
+ g_free (buffer);
+
+ return pixbuf;
+}
+
+static void
+async_thumbnail_read_image (GObject *source_object,
+ GAsyncResult *res,
+ gpointer callback_data)
+{
+ CajaThumbnailAsyncLoadHandle *handle = callback_data;
+ GdkPixbuf *pixbuf;
+ double scale_x, scale_y;
+ gsize file_size;
+ char *file_contents;
+
+ pixbuf = NULL;
+ scale_x = scale_y = 1.0;
+
+ if (g_file_load_contents_finish (G_FILE (source_object),
+ res,
+ &file_contents, &file_size,
+ NULL, NULL))
+ {
+ pixbuf = get_pixbuf_from_data (file_contents, file_size,
+ handle->file_path,
+ handle->base_size,
+ handle->nominal_size,
+ handle->force_nominal,
+ &scale_x, &scale_y);
+ g_free (file_contents);
+ }
+
+ handle->load_func (handle,
+ handle->file_path,
+ pixbuf, scale_x, scale_y,
+ handle->load_func_user_data);
+
+ g_object_unref (pixbuf);
+
+ g_object_unref (handle->cancellable);
+ g_free (handle->file_path);
+ g_free (handle);
+}
+
+CajaThumbnailAsyncLoadHandle *
+caja_thumbnail_load_image_async (const char *path,
+ guint base_size,
+ guint nominal_size,
+ gboolean force_nominal,
+ CajaThumbnailAsyncLoadFunc load_func,
+ gpointer load_func_user_data)
+{
+ CajaThumbnailAsyncLoadHandle *handle;
+ GFile *location;
+
+
+ handle = g_new (CajaThumbnailAsyncLoadHandle, 1);
+ handle->cancellable = g_cancellable_new ();
+ handle->file_path = g_strdup (path);
+ handle->base_size = base_size;
+ handle->nominal_size = nominal_size;
+ handle->force_nominal = force_nominal;
+ handle->load_func = load_func;
+ handle->load_func_user_data = load_func_user_data;
+
+
+ location = g_file_new_for_path (path);
+ g_file_load_contents_async (location, handle->cancellable,
+ async_thumbnail_read_image,
+ handle);
+ g_object_unref (location);
+
+ return handle;
+}
+
+void
+caja_thumbnail_load_image_cancel (CajaThumbnailAsyncLoadHandle *handle)
+{
+ g_assert (handle != NULL);
+
+ g_cancellable_cancel (handle->cancellable);
+}
+
+void
+caja_thumbnail_remove_from_queue (const char *file_uri)
+{
+ GList *node;
+
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Remove from queue) Locking mutex\n");
+#endif
+ pthread_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
+ pthread_mutex_unlock (&thumbnails_mutex);
+}
+
+void
+caja_thumbnail_remove_all_from_queue (void)
+{
+ CajaThumbnailInfo *info;
+ GList *l, *next;
+
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Remove all from queue) Locking mutex\n");
+#endif
+ pthread_mutex_lock (&thumbnails_mutex);
+
+ /*********************************
+ * MUTEX LOCKED
+ *********************************/
+
+ l = thumbnails_to_make.head;
+ while (l != NULL)
+ {
+ info = l->data;
+ next = l->next;
+ if (info != currently_thumbnailing)
+ {
+ g_hash_table_remove (thumbnails_to_make_hash,
+ info->image_uri);
+ free_thumbnail_info (info);
+ g_queue_delete_link ((GQueue *)&thumbnails_to_make, l);
+ }
+
+ l = next;
+ }
+
+ /*********************************
+ * MUTEX UNLOCKED
+ *********************************/
+
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Remove all from queue) Unlocking mutex\n");
+#endif
+ pthread_mutex_unlock (&thumbnails_mutex);
+}
+
+void
+caja_thumbnail_prioritize (const char *file_uri)
+{
+ GList *node;
+
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Prioritize) Locking mutex\n");
+#endif
+ pthread_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
+ pthread_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 caja_file_changed() is
+ thread-safe. */
+static gboolean
+thumbnail_thread_notify_file_changed (gpointer image_uri)
+{
+ CajaFile *file;
+
+ GDK_THREADS_ENTER ();
+
+ file = caja_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)
+ {
+ caja_file_set_is_thumbnailing (file, FALSE);
+ caja_file_invalidate_attributes (file,
+ CAJA_FILE_ATTRIBUTE_THUMBNAIL |
+ CAJA_FILE_ATTRIBUTE_INFO);
+ caja_file_unref (file);
+ }
+ g_free (image_uri);
+
+ GDK_THREADS_LEAVE ();
+
+ 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);
+ eel_debug_call_at_shutdown_with_data ((GFreeFunc)g_hash_table_destroy,
+ image_mime_types);
+
+ 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; i < G_N_ELEMENTS (types); 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
+caja_can_thumbnail_internally (CajaFile *file)
+{
+ char *mime_type;
+ gboolean res;
+
+ mime_type = caja_file_get_mime_type (file);
+ res = pixbuf_can_load_type (mime_type);
+ g_free (mime_type);
+ return res;
+}
+
+gboolean
+caja_thumbnail_is_mimetype_limited_by_size (const char *mime_type)
+{
+ return pixbuf_can_load_type (mime_type);
+}
+
+gboolean
+caja_can_thumbnail (CajaFile *file)
+{
+ MateDesktopThumbnailFactory *factory;
+ gboolean res;
+ char *uri;
+ time_t mtime;
+ char *mime_type;
+
+ uri = caja_file_get_uri (file);
+ mime_type = caja_file_get_mime_type (file);
+ mtime = caja_file_get_mtime (file);
+
+ factory = get_thumbnail_factory ();
+ res = mate_desktop_thumbnail_factory_can_thumbnail (factory,
+ uri,
+ mime_type,
+ mtime);
+ g_free (mime_type);
+ g_free (uri);
+
+ return res;
+}
+
+void
+caja_create_thumbnail (CajaFile *file)
+{
+ time_t file_mtime = 0;
+ CajaThumbnailInfo *info;
+ CajaThumbnailInfo *existing_info;
+ GList *existing, *node;
+
+ caja_file_set_is_thumbnailing (file, TRUE);
+
+ info = g_new0 (CajaThumbnailInfo, 1);
+ info->image_uri = caja_file_get_uri (file);
+ info->mime_type = caja_file_get_mime_type (file);
+
+ /* Hopefully the CajaFile 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
+ pthread_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
+ pthread_mutex_unlock (&thumbnails_mutex);
+}
+
+/* thumbnail_thread is invoked as a separate thread to to make thumbnails. */
+static gpointer
+thumbnail_thread_start (gpointer data)
+{
+ CajaThumbnailInfo *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
+ pthread_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;
+ pthread_mutex_unlock (&thumbnails_mutex);
+ pthread_exit (NULL);
+ }
+
+ /* 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
+ pthread_mutex_unlock (&thumbnails_mutex);
+
+ time (&current_time);
+
+ /* Don't try to create a thumbnail if the file was modified recently.
+ This prevents constant re-thumbnailing of changing files. */
+ if (current_time < current_orig_mtime + THUMBNAIL_CREATION_DELAY_SECS &&
+ current_time >= current_orig_mtime)
+ {
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Thumbnail Thread) Skipping: %s\n",
+ info->image_uri);
+#endif
+ /* Reschedule thumbnailing via a change notification */
+ g_timeout_add_seconds (1, thumbnail_thread_notify_file_changed,
+ g_strdup (info->image_uri));
+ continue;
+ }
+
+ /* Create the thumbnail. */
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Thumbnail Thread) Creating thumbnail: %s\n",
+ info->image_uri);
+#endif
+
+ pixbuf = mate_desktop_thumbnail_factory_generate_thumbnail (thumbnail_factory,
+ info->image_uri,
+ info->mime_type);
+
+ if (pixbuf)
+ {
+ mate_desktop_thumbnail_factory_save_thumbnail (thumbnail_factory,
+ pixbuf,
+ info->image_uri,
+ current_orig_mtime);
+ g_object_unref (pixbuf);
+ }
+ else
+ {
+ mate_desktop_thumbnail_factory_create_failed_thumbnail (thumbnail_factory,
+ info->image_uri,
+ current_orig_mtime);
+ }
+ /* We need to call caja_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/libcaja-private/caja-thumbnails.h b/libcaja-private/caja-thumbnails.h
new file mode 100644
index 00000000..b58669f1
--- /dev/null
+++ b/libcaja-private/caja-thumbnails.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Andy Hertzfeld <[email protected]>
+*/
+
+#ifndef CAJA_THUMBNAILS_H
+#define CAJA_THUMBNAILS_H
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <libcaja-private/caja-file.h>
+
+typedef struct CajaThumbnailAsyncLoadHandle CajaThumbnailAsyncLoadHandle;
+
+typedef void (* CajaThumbnailAsyncLoadFunc) (CajaThumbnailAsyncLoadHandle *handle,
+ const char *path,
+ GdkPixbuf *pixbuf,
+ double scale_x,
+ double scale_y,
+ gpointer user_data);
+
+
+#define CAJA_THUMBNAIL_FRAME_LEFT 3
+#define CAJA_THUMBNAIL_FRAME_TOP 3
+#define CAJA_THUMBNAIL_FRAME_RIGHT 3
+#define CAJA_THUMBNAIL_FRAME_BOTTOM 3
+
+/* Returns NULL if there's no thumbnail yet. */
+void caja_create_thumbnail (CajaFile *file);
+gboolean caja_can_thumbnail (CajaFile *file);
+gboolean caja_can_thumbnail_internally (CajaFile *file);
+gboolean caja_thumbnail_is_mimetype_limited_by_size
+(const char *mime_type);
+void caja_thumbnail_frame_image (GdkPixbuf **pixbuf);
+GdkPixbuf *caja_thumbnail_unframe_image (GdkPixbuf *pixbuf);
+GdkPixbuf *caja_thumbnail_load_image (const char *path,
+ guint base_size,
+ guint nominal_size,
+ gboolean force_nominal,
+ double *scale_x_out,
+ double *scale_y_out);
+CajaThumbnailAsyncLoadHandle *
+caja_thumbnail_load_image_async (const char *path,
+ guint base_size,
+ guint nominal_size,
+ gboolean force_nominal,
+ CajaThumbnailAsyncLoadFunc load_func,
+ gpointer load_func_user_data);
+void caja_thumbnail_load_image_cancel (CajaThumbnailAsyncLoadHandle *handle);
+void caja_update_thumbnail_file_copied (const char *source_file_uri,
+ const char *destination_file_uri);
+void caja_update_thumbnail_file_renamed (const char *source_file_uri,
+ const char *destination_file_uri);
+void caja_remove_thumbnail_for_file (const char *file_uri);
+
+/* Queue handling: */
+void caja_thumbnail_remove_from_queue (const char *file_uri);
+void caja_thumbnail_remove_all_from_queue (void);
+void caja_thumbnail_prioritize (const char *file_uri);
+
+
+#endif /* CAJA_THUMBNAILS_H */
diff --git a/libcaja-private/caja-trash-monitor.c b/libcaja-private/caja-trash-monitor.c
new file mode 100644
index 00000000..ce344a52
--- /dev/null
+++ b/libcaja-private/caja-trash-monitor.c
@@ -0,0 +1,250 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ caja-trash-monitor.c: Caja 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Pavel Cisler <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-trash-monitor.h"
+
+#include "caja-directory-notify.h"
+#include "caja-directory.h"
+#include "caja-file-attributes.h"
+#include "caja-icon-names.h"
+#include <eel/eel-debug.h>
+#include <gio/gio.h>
+#include <string.h>
+
+struct CajaTrashMonitorDetails
+{
+ gboolean empty;
+ GIcon *icon;
+ GFileMonitor *file_monitor;
+};
+
+enum
+{
+ TRASH_STATE_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+static CajaTrashMonitor *caja_trash_monitor = NULL;
+
+G_DEFINE_TYPE(CajaTrashMonitor, caja_trash_monitor, G_TYPE_OBJECT)
+
+static void
+caja_trash_monitor_finalize (GObject *object)
+{
+ CajaTrashMonitor *trash_monitor;
+
+ trash_monitor = CAJA_TRASH_MONITOR (object);
+
+ if (trash_monitor->details->icon)
+ {
+ g_object_unref (trash_monitor->details->icon);
+ }
+ if (trash_monitor->details->file_monitor)
+ {
+ g_object_unref (trash_monitor->details->file_monitor);
+ }
+
+ G_OBJECT_CLASS (caja_trash_monitor_parent_class)->finalize (object);
+}
+
+static void
+caja_trash_monitor_class_init (CajaTrashMonitorClass *klass)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = caja_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 (CajaTrashMonitorClass, 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(CajaTrashMonitorDetails));
+}
+
+static void
+update_info_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ CajaTrashMonitor *trash_monitor;
+ GFileInfo *info;
+ GIcon *icon;
+ const char * const *names;
+ gboolean empty;
+ int i;
+
+ trash_monitor = CAJA_TRASH_MONITOR (user_data);
+
+ info = g_file_query_info_finish (G_FILE (source_object),
+ res, NULL);
+
+ if (info != NULL)
+ {
+ icon = g_file_info_get_icon (info);
+
+ if (icon)
+ {
+ g_object_unref (trash_monitor->details->icon);
+ trash_monitor->details->icon = g_object_ref (icon);
+ empty = TRUE;
+ if (G_IS_THEMED_ICON (icon))
+ {
+ names = g_themed_icon_get_names (G_THEMED_ICON (icon));
+ for (i = 0; names[i] != NULL; i++)
+ {
+ if (strcmp (names[i], CAJA_ICON_TRASH_FULL) == 0)
+ {
+ empty = FALSE;
+ break;
+ }
+ }
+ }
+ if (trash_monitor->details->empty != empty)
+ {
+ trash_monitor->details->empty = empty;
+
+ /* trash got empty or full, notify everyone who cares */
+ g_signal_emit (trash_monitor,
+ signals[TRASH_STATE_CHANGED], 0,
+ trash_monitor->details->empty);
+ }
+ }
+ g_object_unref (info);
+ }
+
+ g_object_unref (trash_monitor);
+}
+
+static void
+schedule_update_info (CajaTrashMonitor *trash_monitor)
+{
+ GFile *location;
+
+ location = g_file_new_for_uri ("trash:///");
+
+ g_file_query_info_async (location,
+ G_FILE_ATTRIBUTE_STANDARD_ICON,
+ 0, 0, NULL,
+ update_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)
+{
+ CajaTrashMonitor *trash_monitor;
+
+ trash_monitor = CAJA_TRASH_MONITOR (user_data);
+
+ schedule_update_info (trash_monitor);
+}
+
+static void
+caja_trash_monitor_init (CajaTrashMonitor *trash_monitor)
+{
+ GFile *location;
+
+ trash_monitor->details = G_TYPE_INSTANCE_GET_PRIVATE (trash_monitor,
+ CAJA_TYPE_TRASH_MONITOR,
+ CajaTrashMonitorDetails);
+
+ trash_monitor->details->empty = TRUE;
+ trash_monitor->details->icon = g_themed_icon_new (CAJA_ICON_TRASH);
+
+ 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 (caja_trash_monitor);
+}
+
+CajaTrashMonitor *
+caja_trash_monitor_get (void)
+{
+ if (caja_trash_monitor == NULL)
+ {
+ /* not running yet, start it up */
+
+ caja_trash_monitor = CAJA_TRASH_MONITOR
+ (g_object_new (CAJA_TYPE_TRASH_MONITOR, NULL));
+ eel_debug_call_at_shutdown (unref_trash_monitor);
+ }
+
+ return caja_trash_monitor;
+}
+
+gboolean
+caja_trash_monitor_is_empty (void)
+{
+ CajaTrashMonitor *monitor;
+
+ monitor = caja_trash_monitor_get ();
+ return monitor->details->empty;
+}
+
+GIcon *
+caja_trash_monitor_get_icon (void)
+{
+ CajaTrashMonitor *monitor;
+
+ monitor = caja_trash_monitor_get ();
+ if (monitor->details->icon)
+ {
+ return g_object_ref (monitor->details->icon);
+ }
+ return NULL;
+}
+
+void
+caja_trash_monitor_add_new_trash_directories (void)
+{
+ /* We trashed something... */
+}
diff --git a/libcaja-private/caja-trash-monitor.h b/libcaja-private/caja-trash-monitor.h
new file mode 100644
index 00000000..ec947054
--- /dev/null
+++ b/libcaja-private/caja-trash-monitor.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ caja-trash-monitor.h: Caja 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Pavel Cisler <[email protected]>
+*/
+
+#ifndef CAJA_TRASH_MONITOR_H
+#define CAJA_TRASH_MONITOR_H
+
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+
+typedef struct CajaTrashMonitor CajaTrashMonitor;
+typedef struct CajaTrashMonitorClass CajaTrashMonitorClass;
+typedef struct CajaTrashMonitorDetails CajaTrashMonitorDetails;
+
+#define CAJA_TYPE_TRASH_MONITOR caja_trash_monitor_get_type()
+#define CAJA_TRASH_MONITOR(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_TRASH_MONITOR, CajaTrashMonitor))
+#define CAJA_TRASH_MONITOR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_TRASH_MONITOR, CajaTrashMonitorClass))
+#define CAJA_IS_TRASH_MONITOR(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_TRASH_MONITOR))
+#define CAJA_IS_TRASH_MONITOR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_TRASH_MONITOR))
+#define CAJA_TRASH_MONITOR_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_TRASH_MONITOR, CajaTrashMonitorClass))
+
+struct CajaTrashMonitor
+{
+ GObject object;
+ CajaTrashMonitorDetails *details;
+};
+
+struct CajaTrashMonitorClass
+{
+ GObjectClass parent_class;
+
+ void (* trash_state_changed) (CajaTrashMonitor *trash_monitor,
+ gboolean new_state);
+};
+
+GType caja_trash_monitor_get_type (void);
+
+CajaTrashMonitor *caja_trash_monitor_get (void);
+gboolean caja_trash_monitor_is_empty (void);
+GIcon *caja_trash_monitor_get_icon (void);
+
+void caja_trash_monitor_add_new_trash_directories (void);
+
+#endif
diff --git a/libcaja-private/caja-tree-view-drag-dest.c b/libcaja-private/caja-tree-view-drag-dest.c
new file mode 100644
index 00000000..528ff029
--- /dev/null
+++ b/libcaja-private/caja-tree-view-drag-dest.c
@@ -0,0 +1,1306 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2002 Sun Microsystems, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Dave Camp <[email protected]>
+ * XDS support: Benedikt Meurer <[email protected]> (adapted by Amos Brocco <[email protected]>)
+ */
+
+/* caja-tree-view-drag-dest.c: Handles drag and drop for treeviews which
+ * contain a hierarchy of files
+ */
+
+#include <config.h>
+#include "caja-tree-view-drag-dest.h"
+
+#include <eel/eel-gtk-macros.h>
+#include <gtk/gtk.h>
+#include "caja-file-dnd.h"
+#include "caja-file-changes-queue.h"
+#include "caja-icon-dnd.h"
+#include "caja-link.h"
+#include "caja-marshal.h"
+#include "caja-debug-log.h"
+#include <stdio.h>
+#include <string.h>
+
+#define AUTO_SCROLL_MARGIN 20
+
+#define HOVER_EXPAND_TIMEOUT 1
+
+struct _CajaTreeViewDragDestDetails
+{
+ GtkTreeView *tree_view;
+
+ gboolean drop_occurred;
+
+ gboolean have_drag_data;
+ guint drag_type;
+ GtkSelectionData *drag_data;
+ GList *drag_list;
+
+ guint highlight_id;
+ guint scroll_id;
+ guint expand_id;
+
+ char *direct_save_uri;
+};
+
+enum
+{
+ GET_ROOT_URI,
+ GET_FILE_FOR_PATH,
+ MOVE_COPY_ITEMS,
+ HANDLE_NETSCAPE_URL,
+ HANDLE_URI_LIST,
+ HANDLE_TEXT,
+ HANDLE_RAW,
+ LAST_SIGNAL
+};
+
+static void caja_tree_view_drag_dest_init (CajaTreeViewDragDest *dest);
+static void caja_tree_view_drag_dest_class_init (CajaTreeViewDragDestClass *class);
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (CajaTreeViewDragDest, caja_tree_view_drag_dest,
+ G_TYPE_OBJECT);
+#define parent_class caja_tree_view_drag_dest_parent_class
+
+static const GtkTargetEntry drag_types [] =
+{
+ { CAJA_ICON_DND_MATE_ICON_LIST_TYPE, 0, CAJA_ICON_DND_MATE_ICON_LIST },
+ /* prefer "_NETSCAPE_URL" over "text/uri-list" to satisfy web browsers. */
+ { CAJA_ICON_DND_NETSCAPE_URL_TYPE, 0, CAJA_ICON_DND_NETSCAPE_URL },
+ { CAJA_ICON_DND_URI_LIST_TYPE, 0, CAJA_ICON_DND_URI_LIST },
+ { CAJA_ICON_DND_KEYWORD_TYPE, 0, CAJA_ICON_DND_KEYWORD },
+ { CAJA_ICON_DND_XDNDDIRECTSAVE_TYPE, 0, CAJA_ICON_DND_XDNDDIRECTSAVE }, /* XDS Protocol Type */
+ { CAJA_ICON_DND_RAW_TYPE, 0, CAJA_ICON_DND_RAW }
+};
+
+
+static void
+gtk_tree_view_vertical_autoscroll (GtkTreeView *tree_view)
+{
+ GdkRectangle visible_rect;
+ GtkAdjustment *vadjustment;
+ GdkWindow *window;
+ int y;
+ int offset;
+ float value;
+
+ window = gtk_tree_view_get_bin_window (tree_view);
+
+#if GTK_CHECK_VERSION(3, 0, 0)
+ vadjustment = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(tree_view));
+#else
+ vadjustment = gtk_tree_view_get_vadjustment(tree_view);
+#endif
+
+ gdk_window_get_pointer (window, 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 (CajaTreeViewDragDest *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_timeout (CajaTreeViewDragDest *dest)
+{
+ if (dest->details->expand_id)
+ {
+ g_source_remove (dest->details->expand_id);
+ dest->details->expand_id = 0;
+ }
+}
+
+static gboolean
+highlight_expose (GtkWidget *widget,
+ GdkEventExpose *event,
+ gpointer data)
+{
+ GdkWindow *bin_window;
+ int width;
+ int height;
+
+ if (gtk_widget_is_drawable (widget))
+ {
+ bin_window =
+ gtk_tree_view_get_bin_window (GTK_TREE_VIEW (widget));
+
+#if GTK_CHECK_VERSION(3, 0, 0)
+ width = gdk_window_get_width(GDK_WINDOW(bin_window));
+ height = gdk_window_get_height(GDK_WINDOW(bin_window));
+#else
+ gdk_drawable_get_size(bin_window, &width, &height);
+#endif
+
+
+ gtk_paint_focus (gtk_widget_get_style (widget),
+ bin_window,
+ gtk_widget_get_state (widget),
+ NULL,
+ widget,
+ "treeview-drop-indicator",
+ 0, 0, width, height);
+ }
+
+ return FALSE;
+}
+
+static void
+set_widget_highlight (CajaTreeViewDragDest *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,
+ "expose_event",
+ G_CALLBACK (highlight_expose),
+ dest, 0);
+ gtk_widget_queue_draw (GTK_WIDGET (dest->details->tree_view));
+ }
+}
+
+static void
+set_drag_dest_row (CajaTreeViewDragDest *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 (CajaTreeViewDragDest *dest)
+{
+ gtk_tree_view_set_drag_dest_row (dest->details->tree_view, NULL, 0);
+ set_widget_highlight (dest, FALSE);
+}
+
+static gboolean
+get_drag_data (CajaTreeViewDragDest *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 (CAJA_ICON_DND_XDNDDIRECTSAVE_TYPE, FALSE) &&
+ !dest->details->drop_occurred)
+ {
+ dest->details->drag_type = CAJA_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
+free_drag_data (CajaTreeViewDragDest *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)
+ {
+ caja_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;
+}
+
+static char *
+get_root_uri (CajaTreeViewDragDest *dest)
+{
+ char *uri;
+
+ g_signal_emit (dest, signals[GET_ROOT_URI], 0, &uri);
+
+ return uri;
+}
+
+static CajaFile *
+file_for_path (CajaTreeViewDragDest *dest, GtkTreePath *path)
+{
+ CajaFile *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 = caja_file_get_by_uri (uri);
+ }
+
+ g_free (uri);
+ }
+
+ return file;
+}
+
+static GtkTreePath *
+get_drop_path (CajaTreeViewDragDest *dest,
+ GtkTreePath *path)
+{
+ CajaFile *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 */ ||
+ !caja_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);
+
+ caja_file_unref (file);
+ file = file_for_path (dest, ret);
+ }
+ }
+ caja_file_unref (file);
+
+ return ret;
+}
+
+static char *
+get_drop_target_uri_for_path (CajaTreeViewDragDest *dest,
+ GtkTreePath *path)
+{
+ CajaFile *file;
+ char *target;
+
+ file = file_for_path (dest, path);
+ if (file == NULL)
+ {
+ return NULL;
+ }
+
+ target = caja_file_get_drop_target_uri (file);
+ caja_file_unref (file);
+
+ return target;
+}
+
+static guint
+get_drop_action (CajaTreeViewDragDest *dest,
+ GdkDragContext *context,
+ GtkTreePath *path)
+{
+ char *drop_target;
+ int action;
+
+ if (!dest->details->have_drag_data ||
+ (dest->details->drag_type == CAJA_ICON_DND_MATE_ICON_LIST &&
+ dest->details->drag_list == NULL))
+ {
+ return 0;
+ }
+
+ switch (dest->details->drag_type)
+ {
+ case CAJA_ICON_DND_MATE_ICON_LIST :
+ drop_target = get_drop_target_uri_for_path (dest, path);
+
+ if (!drop_target)
+ {
+ return 0;
+ }
+
+ caja_drag_default_drop_action_for_icons
+ (context,
+ drop_target,
+ dest->details->drag_list,
+ &action);
+
+ g_free (drop_target);
+
+ return action;
+
+ case CAJA_ICON_DND_NETSCAPE_URL:
+ drop_target = get_drop_target_uri_for_path (dest, path);
+
+ if (drop_target == NULL)
+ {
+ return 0;
+ }
+
+ action = caja_drag_default_drop_action_for_netscape_url (context);
+
+ g_free (drop_target);
+
+ return action;
+
+ case CAJA_ICON_DND_URI_LIST :
+ drop_target = get_drop_target_uri_for_path (dest, path);
+
+ if (drop_target == NULL)
+ {
+ return 0;
+ }
+
+ g_free (drop_target);
+
+ return gdk_drag_context_get_suggested_action (context);
+
+ case CAJA_ICON_DND_TEXT:
+ case CAJA_ICON_DND_RAW:
+ case CAJA_ICON_DND_XDNDDIRECTSAVE:
+ return GDK_ACTION_COPY;
+
+ case CAJA_ICON_DND_KEYWORD:
+
+ if (!path)
+ {
+ return 0;
+ }
+
+ return GDK_ACTION_COPY;
+ }
+
+ return 0;
+}
+
+static gboolean
+drag_motion_callback (GtkWidget *widget,
+ GdkDragContext *context,
+ int x,
+ int y,
+ guint32 time,
+ gpointer data)
+{
+ CajaTreeViewDragDest *dest;
+ GtkTreePath *path;
+ GtkTreePath *drop_path, *old_drop_path;
+ GtkTreeModel *model;
+ GtkTreeIter drop_iter;
+ GtkTreeViewDropPosition pos;
+ GdkWindow *bin_window;
+ guint action;
+ gboolean res = TRUE;
+
+ dest = CAJA_TREE_VIEW_DRAG_DEST (data);
+
+ gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
+ x, y, &path, &pos);
+
+
+ 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);
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
+ if (drop_path == NULL || (old_drop_path != NULL &&
+ gtk_tree_path_compare (old_drop_path, drop_path) != 0))
+ {
+ remove_expand_timeout (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);
+ }
+ }
+ }
+ else
+ {
+ clear_drag_dest_row (dest);
+ remove_expand_timeout (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)
+{
+ CajaTreeViewDragDest *dest;
+
+ dest = CAJA_TREE_VIEW_DRAG_DEST (data);
+
+ clear_drag_dest_row (dest);
+
+ free_drag_data (dest);
+
+ remove_scroll_timeout (dest);
+ remove_expand_timeout (dest);
+}
+
+static char *
+get_drop_target_uri_at_pos (CajaTreeViewDragDest *dest, int x, int y)
+{
+ char *drop_target;
+ GtkTreePath *path;
+ GtkTreePath *drop_path;
+ GtkTreeViewDropPosition pos;
+
+ gtk_tree_view_get_dest_row_at_pos (dest->details->tree_view, x, y,
+ &path, &pos);
+
+ 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 (CajaTreeViewDragDest *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 (caja_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 = caja_drag_drop_action_ask
+ (GTK_WIDGET (dest->details->tree_view), action);
+ }
+
+ /* We only want to copy external uris */
+ if (dest->details->drag_type == CAJA_ICON_DND_URI_LIST)
+ {
+ action = GDK_ACTION_COPY;
+ }
+
+ if (real_action > 0)
+ {
+ if (!caja_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 (CajaTreeViewDragDest *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,
+ ((CajaDragSelectionItem *)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 (CajaTreeViewDragDest *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 (CajaTreeViewDragDest *dest,
+ GdkDragContext *context,
+ int x, int y)
+{
+ char *drop_target;
+ char *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 (CajaTreeViewDragDest *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 (CajaTreeViewDragDest *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 void
+receive_dropped_keyword (CajaTreeViewDragDest *dest,
+ GdkDragContext *context,
+ int x, int y)
+{
+ char *drop_target_uri;
+ CajaFile *drop_target_file;
+
+ if (!dest->details->drag_data)
+ {
+ return;
+ }
+
+ drop_target_uri = get_drop_target_uri_at_pos (dest, x, y);
+ g_assert (drop_target_uri != NULL);
+
+ drop_target_file = caja_file_get_by_uri (drop_target_uri);
+
+ if (drop_target_file != NULL)
+ {
+ caja_drag_file_receive_dropped_keyword (drop_target_file,
+ (char *) gtk_selection_data_get_data (dest->details->drag_data));
+ caja_file_unref (drop_target_file);
+ }
+
+ g_free (drop_target_uri);
+}
+
+static gboolean
+receive_xds (CajaTreeViewDragDest *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 (CAJA_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);
+
+ caja_file_changes_queue_file_added (location);
+ caja_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)
+{
+ CajaTreeViewDragDest *dest;
+ const char *tmp;
+ int length;
+ gboolean success, finished;
+
+ dest = CAJA_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 == CAJA_ICON_DND_MATE_ICON_LIST)
+ {
+ dest->details->drag_list =
+ caja_drag_build_selection_list (selection_data);
+ }
+ }
+
+ if (dest->details->drop_occurred)
+ {
+ success = FALSE;
+ finished = TRUE;
+ switch (info)
+ {
+ case CAJA_ICON_DND_MATE_ICON_LIST :
+ receive_dropped_icons (dest, context, x, y);
+ success = TRUE;
+ break;
+ case CAJA_ICON_DND_NETSCAPE_URL :
+ receive_dropped_netscape_url (dest, context, x, y);
+ success = TRUE;
+ break;
+ case CAJA_ICON_DND_URI_LIST :
+ receive_dropped_uri_list (dest, context, x, y);
+ success = TRUE;
+ break;
+ case CAJA_ICON_DND_TEXT:
+ receive_dropped_text (dest, context, x, y);
+ success = TRUE;
+ break;
+ case CAJA_ICON_DND_RAW:
+ length = gtk_selection_data_get_length (selection_data);
+ tmp = gtk_selection_data_get_data (selection_data);
+ receive_dropped_raw (dest, tmp, length, context, x, y);
+ success = TRUE;
+ break;
+ case CAJA_ICON_DND_KEYWORD:
+ receive_dropped_keyword (dest, context, x, y);
+ success = TRUE;
+ break;
+ case CAJA_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 (CAJA_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)
+ {
+ caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
+ "Invalid filename provided by XDS drag site");
+ g_free (prop_text);
+ return NULL;
+ }
+
+ return prop_text;
+}
+
+static gboolean
+set_direct_save_uri (CajaTreeViewDragDest *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_DRAWABLE (gdk_drag_context_get_source_window (context)),
+ gdk_atom_intern (CAJA_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
+ {
+ caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
+ "Invalid filename provided by XDS drag site");
+ }
+ }
+ else
+ {
+ caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
+ "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)
+{
+ CajaTreeViewDragDest *dest;
+ guint info;
+ GdkAtom target;
+
+ dest = CAJA_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 == CAJA_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);
+ remove_expand_timeout (dest);
+ clear_drag_dest_row (dest);
+
+ return TRUE;
+}
+
+static void
+tree_view_weak_notify (gpointer user_data,
+ GObject *object)
+{
+ CajaTreeViewDragDest *dest;
+
+ dest = CAJA_TREE_VIEW_DRAG_DEST (user_data);
+
+ remove_scroll_timeout (dest);
+ remove_expand_timeout (dest);
+
+ dest->details->tree_view = NULL;
+}
+
+static void
+caja_tree_view_drag_dest_dispose (GObject *object)
+{
+ CajaTreeViewDragDest *dest;
+
+ dest = CAJA_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);
+ remove_expand_timeout (dest);
+
+ EEL_CALL_PARENT (G_OBJECT_CLASS, dispose, (object));
+}
+
+static void
+caja_tree_view_drag_dest_finalize (GObject *object)
+{
+ CajaTreeViewDragDest *dest;
+
+ dest = CAJA_TREE_VIEW_DRAG_DEST (object);
+
+ free_drag_data (dest);
+
+ g_free (dest->details);
+
+ EEL_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
+}
+
+static void
+caja_tree_view_drag_dest_init (CajaTreeViewDragDest *dest)
+{
+ dest->details = g_new0 (CajaTreeViewDragDestDetails, 1);
+}
+
+static void
+caja_tree_view_drag_dest_class_init (CajaTreeViewDragDestClass *class)
+{
+ GObjectClass *gobject_class;
+
+ gobject_class = G_OBJECT_CLASS (class);
+
+ gobject_class->dispose = caja_tree_view_drag_dest_dispose;
+ gobject_class->finalize = caja_tree_view_drag_dest_finalize;
+
+ signals[GET_ROOT_URI] =
+ g_signal_new ("get_root_uri",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaTreeViewDragDestClass,
+ get_root_uri),
+ NULL, NULL,
+ caja_marshal_STRING__VOID,
+ 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 (CajaTreeViewDragDestClass,
+ get_file_for_path),
+ NULL, NULL,
+ caja_marshal_OBJECT__BOXED,
+ CAJA_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 (CajaTreeViewDragDestClass,
+ move_copy_items),
+ NULL, NULL,
+
+ caja_marshal_VOID__POINTER_STRING_ENUM_INT_INT,
+ 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 (CajaTreeViewDragDestClass,
+ handle_netscape_url),
+ NULL, NULL,
+ caja_marshal_VOID__STRING_STRING_ENUM_INT_INT,
+ 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 (CajaTreeViewDragDestClass,
+ handle_uri_list),
+ NULL, NULL,
+ caja_marshal_VOID__STRING_STRING_ENUM_INT_INT,
+ 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 (CajaTreeViewDragDestClass,
+ handle_text),
+ NULL, NULL,
+ caja_marshal_VOID__STRING_STRING_ENUM_INT_INT,
+ 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 (CajaTreeViewDragDestClass,
+ handle_raw),
+ NULL, NULL,
+ caja_marshal_VOID__POINTER_INT_STRING_STRING_ENUM_INT_INT,
+ 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);
+}
+
+
+
+CajaTreeViewDragDest *
+caja_tree_view_drag_dest_new (GtkTreeView *tree_view)
+{
+ CajaTreeViewDragDest *dest;
+ GtkTargetList *targets;
+
+ dest = g_object_new (CAJA_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, CAJA_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/libcaja-private/caja-tree-view-drag-dest.h b/libcaja-private/caja-tree-view-drag-dest.h
new file mode 100644
index 00000000..01e71b68
--- /dev/null
+++ b/libcaja-private/caja-tree-view-drag-dest.h
@@ -0,0 +1,106 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2002 Sun Microsystems, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Dave Camp <[email protected]>
+ */
+
+/* caja-tree-view-drag-dest.h: Handles drag and drop for treeviews which
+ * contain a hierarchy of files
+ */
+
+#ifndef CAJA_TREE_VIEW_DRAG_DEST_H
+#define CAJA_TREE_VIEW_DRAG_DEST_H
+
+#include <gtk/gtk.h>
+
+#include "caja-file.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define CAJA_TYPE_TREE_VIEW_DRAG_DEST (caja_tree_view_drag_dest_get_type ())
+#define CAJA_TREE_VIEW_DRAG_DEST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_TREE_VIEW_DRAG_DEST, CajaTreeViewDragDest))
+#define CAJA_TREE_VIEW_DRAG_DEST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_TREE_VIEW_DRAG_DEST, CajaTreeViewDragDestClass))
+#define CAJA_IS_TREE_VIEW_DRAG_DEST(obj) (G_TYPE_INSTANCE_CHECK_TYPE ((obj), CAJA_TYPE_TREE_VIEW_DRAG_DEST))
+#define CAJA_IS_TREE_VIEW_DRAG_DEST_CLASS(klass) (G_TYPE_CLASS_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_TREE_VIEW_DRAG_DEST))
+
+ typedef struct _CajaTreeViewDragDest CajaTreeViewDragDest;
+ typedef struct _CajaTreeViewDragDestClass CajaTreeViewDragDestClass;
+ typedef struct _CajaTreeViewDragDestDetails CajaTreeViewDragDestDetails;
+
+ struct _CajaTreeViewDragDest
+ {
+ GObject parent;
+
+ CajaTreeViewDragDestDetails *details;
+ };
+
+ struct _CajaTreeViewDragDestClass
+ {
+ GObjectClass parent;
+
+ char *(*get_root_uri) (CajaTreeViewDragDest *dest);
+ CajaFile *(*get_file_for_path) (CajaTreeViewDragDest *dest,
+ GtkTreePath *path);
+ void (*move_copy_items) (CajaTreeViewDragDest *dest,
+ const GList *item_uris,
+ const char *target_uri,
+ GdkDragAction action,
+ int x,
+ int y);
+ void (* handle_netscape_url) (CajaTreeViewDragDest *dest,
+ const char *url,
+ const char *target_uri,
+ GdkDragAction action,
+ int x,
+ int y);
+ void (* handle_uri_list) (CajaTreeViewDragDest *dest,
+ const char *uri_list,
+ const char *target_uri,
+ GdkDragAction action,
+ int x,
+ int y);
+ void (* handle_text) (CajaTreeViewDragDest *dest,
+ const char *text,
+ const char *target_uri,
+ GdkDragAction action,
+ int x,
+ int y);
+ void (* handle_raw) (CajaTreeViewDragDest *dest,
+ char *raw_data,
+ int length,
+ const char *target_uri,
+ const char *direct_save_uri,
+ GdkDragAction action,
+ int x,
+ int y);
+ };
+
+ GType caja_tree_view_drag_dest_get_type (void);
+ CajaTreeViewDragDest *caja_tree_view_drag_dest_new (GtkTreeView *tree_view);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/libcaja-private/caja-ui-utilities.c b/libcaja-private/caja-ui-utilities.c
new file mode 100644
index 00000000..f9f75819
--- /dev/null
+++ b/libcaja-private/caja-ui-utilities.c
@@ -0,0 +1,256 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-ui-utilities.c - helper functions for GtkUIManager stuff
+
+ Copyright (C) 2004 Red Hat, Inc.
+
+ The Mate 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 Mate 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 Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Alexander Larsson <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-ui-utilities.h"
+#include "caja-icon-info.h"
+#include <gio/gio.h>
+
+#include <gtk/gtk.h>
+#include <eel/eel-debug.h>
+
+void
+caja_ui_unmerge_ui (GtkUIManager *ui_manager,
+ guint *merge_id,
+ GtkActionGroup **action_group)
+{
+ if (*merge_id != 0)
+ {
+ gtk_ui_manager_remove_ui (ui_manager,
+ *merge_id);
+ *merge_id = 0;
+ }
+ if (*action_group != NULL)
+ {
+ gtk_ui_manager_remove_action_group (ui_manager,
+ *action_group);
+ *action_group = NULL;
+ }
+}
+
+void
+caja_ui_prepare_merge_ui (GtkUIManager *ui_manager,
+ const char *name,
+ guint *merge_id,
+ GtkActionGroup **action_group)
+{
+ *merge_id = gtk_ui_manager_new_merge_id (ui_manager);
+ *action_group = gtk_action_group_new (name);
+ gtk_action_group_set_translation_domain (*action_group, GETTEXT_PACKAGE);
+ gtk_ui_manager_insert_action_group (ui_manager, *action_group, 0);
+ g_object_unref (*action_group); /* owned by ui manager */
+}
+
+
+char *
+caja_get_ui_directory (void)
+{
+ return g_strdup (DATADIR "/caja/ui");
+}
+
+char *
+caja_ui_file (const char *partial_path)
+{
+ char *path;
+
+ path = g_build_filename (DATADIR "/caja/ui", partial_path, NULL);
+ if (g_file_test (path, G_FILE_TEST_EXISTS))
+ {
+ return path;
+ }
+ g_free (path);
+ return NULL;
+}
+
+const char *
+caja_ui_string_get (const char *filename)
+{
+ static GHashTable *ui_cache = NULL;
+ char *ui;
+ char *path;
+
+ if (ui_cache == NULL)
+ {
+ ui_cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+ eel_debug_call_at_shutdown_with_data ((GFreeFunc)g_hash_table_destroy, ui_cache);
+ }
+
+ ui = g_hash_table_lookup (ui_cache, filename);
+ if (ui == NULL)
+ {
+ path = caja_ui_file (filename);
+ if (path == NULL || !g_file_get_contents (path, &ui, NULL, NULL))
+ {
+ g_warning ("Unable to load ui file %s\n", filename);
+ }
+ g_free (path);
+ g_hash_table_insert (ui_cache,
+ g_strdup (filename),
+ ui);
+ }
+
+ return ui;
+}
+
+static void
+extension_action_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ caja_menu_item_activate (CAJA_MENU_ITEM (callback_data));
+}
+
+static void
+extension_action_sensitive_callback (CajaMenuItem *item,
+ GParamSpec *arg1,
+ gpointer user_data)
+{
+ gboolean value;
+
+ g_object_get (G_OBJECT (item),
+ "sensitive", &value,
+ NULL);
+
+ gtk_action_set_sensitive (GTK_ACTION (user_data), value);
+}
+
+static GdkPixbuf *
+get_action_icon (const char *icon_name, int size)
+{
+ CajaIconInfo *info;
+ GdkPixbuf *pixbuf;
+
+ if (g_path_is_absolute (icon_name))
+ {
+ info = caja_icon_info_lookup_from_path (icon_name, size);
+ }
+ else
+ {
+ info = caja_icon_info_lookup_from_name (icon_name, size);
+ }
+ pixbuf = caja_icon_info_get_pixbuf_nodefault_at_size (info, size);
+ g_object_unref (info);
+
+ return pixbuf;
+}
+
+GtkAction *
+caja_action_from_menu_item (CajaMenuItem *item)
+{
+ char *name, *label, *tip, *icon_name;
+ gboolean sensitive, priority;
+ GtkAction *action;
+ GdkPixbuf *pixbuf;
+
+ g_object_get (G_OBJECT (item),
+ "name", &name, "label", &label,
+ "tip", &tip, "icon", &icon_name,
+ "sensitive", &sensitive,
+ "priority", &priority,
+ NULL);
+
+ action = gtk_action_new (name,
+ label,
+ tip,
+ icon_name);
+
+ if (icon_name != NULL)
+ {
+ pixbuf = get_action_icon (icon_name,
+ caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU));
+ if (pixbuf != NULL)
+ {
+ g_object_set_data_full (G_OBJECT (action), "menu-icon",
+ pixbuf,
+ g_object_unref);
+ }
+ }
+
+ gtk_action_set_sensitive (action, sensitive);
+ g_object_set (action, "is-important", priority, NULL);
+
+ g_signal_connect_data (action, "activate",
+ G_CALLBACK (extension_action_callback),
+ g_object_ref (item),
+ (GClosureNotify)g_object_unref, 0);
+
+ g_free (name);
+ g_free (label);
+ g_free (tip);
+ g_free (icon_name);
+
+ return action;
+}
+
+GtkAction *
+caja_toolbar_action_from_menu_item (CajaMenuItem *item)
+{
+ char *name, *label, *tip, *icon_name;
+ gboolean sensitive, priority;
+ GtkAction *action;
+ GdkPixbuf *pixbuf;
+
+ g_object_get (G_OBJECT (item),
+ "name", &name, "label", &label,
+ "tip", &tip, "icon", &icon_name,
+ "sensitive", &sensitive,
+ "priority", &priority,
+ NULL);
+
+ action = gtk_action_new (name,
+ label,
+ tip,
+ icon_name);
+
+ if (icon_name != NULL)
+ {
+ pixbuf = get_action_icon (icon_name,
+ caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_LARGE_TOOLBAR));
+ if (pixbuf != NULL)
+ {
+ g_object_set_data_full (G_OBJECT (action), "toolbar-icon",
+ pixbuf,
+ g_object_unref);
+ }
+ }
+
+ gtk_action_set_sensitive (action, sensitive);
+ g_object_set (action, "is-important", priority, NULL);
+
+ g_signal_connect_data (action, "activate",
+ G_CALLBACK (extension_action_callback),
+ g_object_ref (item),
+ (GClosureNotify)g_object_unref, 0);
+
+ g_signal_connect_object (item, "notify::sensitive",
+ G_CALLBACK (extension_action_sensitive_callback),
+ action,
+ 0);
+
+ g_free (name);
+ g_free (label);
+ g_free (tip);
+ g_free (icon_name);
+
+ return action;
+}
diff --git a/libcaja-private/caja-ui-utilities.h b/libcaja-private/caja-ui-utilities.h
new file mode 100644
index 00000000..e333c6da
--- /dev/null
+++ b/libcaja-private/caja-ui-utilities.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-ui-utilities.h - helper functions for GtkUIManager stuff
+
+ Copyright (C) 2004 Red Hat, Inc.
+
+ The Mate 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 Mate 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 Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Alexander Larsson <[email protected]>
+*/
+#ifndef CAJA_UI_UTILITIES_H
+#define CAJA_UI_UTILITIES_H
+
+#include <gtk/gtk.h>
+#include <libcaja-extension/caja-menu-item.h>
+
+char * caja_get_ui_directory (void);
+char * caja_ui_file (const char *partial_path);
+void caja_ui_unmerge_ui (GtkUIManager *ui_manager,
+ guint *merge_id,
+ GtkActionGroup **action_group);
+void caja_ui_prepare_merge_ui (GtkUIManager *ui_manager,
+ const char *name,
+ guint *merge_id,
+ GtkActionGroup **action_group);
+GtkAction * caja_action_from_menu_item (CajaMenuItem *item);
+GtkAction * caja_toolbar_action_from_menu_item (CajaMenuItem *item);
+const char *caja_ui_string_get (const char *filename);
+
+#endif /* CAJA_UI_UTILITIES_H */
diff --git a/libcaja-private/caja-undo-manager.c b/libcaja-private/caja-undo-manager.c
new file mode 100644
index 00000000..35951898
--- /dev/null
+++ b/libcaja-private/caja-undo-manager.c
@@ -0,0 +1,320 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* CajaUndoManager - Undo/Redo transaction manager.
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ *
+ * Author: Gene Z. Ragan <[email protected]>
+ *
+ * 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <libcaja-private/caja-undo-manager.h>
+#include <libcaja-private/caja-undo-transaction.h>
+
+#include <eel/eel-gtk-macros.h>
+#include <eel/eel-gtk-extensions.h>
+#include <gtk/gtk.h>
+#include "caja-undo-private.h"
+
+struct CajaUndoManagerDetails
+{
+ CajaUndoTransaction *transaction;
+
+ /* These are used to tell undo from redo. */
+ gboolean current_transaction_is_redo;
+ gboolean new_transaction_is_redo;
+
+ /* These are used only so that we can complain if we get more
+ * than one transaction inside undo.
+ */
+ gboolean undo_in_progress;
+ int num_transactions_during_undo;
+};
+
+enum
+{
+ CHANGED,
+ LAST_SIGNAL
+};
+static guint signals[LAST_SIGNAL];
+
+typedef struct
+{
+#ifdef UIH
+ MateComponentUIHandler *handler;
+#endif /* UIH */
+ char *path;
+ char *no_undo_menu_item_label;
+ char *no_undo_menu_item_hint;
+} UndoMenuHandlerConnection;
+
+G_DEFINE_TYPE (CajaUndoManager,
+ caja_undo_manager,
+ G_TYPE_OBJECT)
+
+static void
+release_transaction (CajaUndoManager *manager)
+{
+ CajaUndoTransaction *transaction;
+
+ transaction = manager->details->transaction;
+ manager->details->transaction = NULL;
+ if (transaction != NULL)
+ {
+ g_object_unref (transaction);
+ }
+}
+
+void
+caja_undo_manager_append (CajaUndoManager *manager,
+ CajaUndoTransaction *transaction)
+{
+ CajaUndoTransaction *duplicate_transaction;
+
+ /* Check, complain, and ignore the passed-in transaction if we
+ * get more than one within a single undo operation. The single
+ * transaction we get during the undo operation is supposed to
+ * be the one for redoing the undo (or re-undoing the redo).
+ */
+ if (manager->details->undo_in_progress)
+ {
+ manager->details->num_transactions_during_undo += 1;
+ g_return_if_fail (manager->details->num_transactions_during_undo == 1);
+ }
+
+ g_return_if_fail (transaction != NULL);
+
+ /* Keep a copy of this transaction (dump the old one). */
+ duplicate_transaction = g_object_ref (transaction);
+ release_transaction (manager);
+ manager->details->transaction = duplicate_transaction;
+ manager->details->current_transaction_is_redo =
+ manager->details->new_transaction_is_redo;
+
+ /* Fire off signal indicating that the undo state has changed. */
+ g_signal_emit (manager, signals[CHANGED], 0);
+}
+
+void
+caja_undo_manager_forget (CajaUndoManager *manager,
+ CajaUndoTransaction *transaction)
+{
+ /* Nothing to forget unless the item we are passed is the
+ * transaction we are currently holding.
+ */
+ if (transaction != manager->details->transaction)
+ {
+ return;
+ }
+
+ /* Get rid of the transaction we are holding on to. */
+ release_transaction (manager);
+
+ /* Fire off signal indicating that the undo state has changed. */
+ g_signal_emit (manager, signals[CHANGED], 0);
+}
+
+CajaUndoManager *
+caja_undo_manager_new (void)
+{
+ return CAJA_UNDO_MANAGER (g_object_new (caja_undo_manager_get_type (), NULL));
+}
+
+static void
+caja_undo_manager_init (CajaUndoManager *manager)
+{
+ manager->details = g_new0 (CajaUndoManagerDetails, 1);
+}
+
+void
+caja_undo_manager_undo (CajaUndoManager *manager)
+{
+ CajaUndoTransaction *transaction;
+
+ g_return_if_fail (CAJA_IS_UNDO_MANAGER (manager));
+
+ transaction = manager->details->transaction;
+ manager->details->transaction = NULL;
+ if (transaction != NULL)
+ {
+ /* Perform the undo. New transactions that come in
+ * during an undo are redo transactions. New
+ * transactions that come in during a redo are undo
+ * transactions. Transactions that come in outside
+ * are always undo and never redo.
+ */
+ manager->details->new_transaction_is_redo =
+ !manager->details->current_transaction_is_redo;
+ manager->details->undo_in_progress = TRUE;
+ manager->details->num_transactions_during_undo = 0;
+ caja_undo_transaction_undo (transaction);
+ manager->details->undo_in_progress = FALSE;
+ manager->details->new_transaction_is_redo = FALSE;
+
+ /* Let go of the transaction. */
+ g_object_unref (transaction);
+
+ /* Fire off signal indicating the undo state has changed. */
+ g_signal_emit (manager, signals[CHANGED], 0);
+ }
+}
+
+static void
+finalize (GObject *object)
+{
+ CajaUndoManager *manager;
+
+ manager = CAJA_UNDO_MANAGER (object);
+
+ release_transaction (manager);
+
+ g_free (manager->details);
+
+ if (G_OBJECT_CLASS (caja_undo_manager_parent_class)->finalize)
+ {
+ (* G_OBJECT_CLASS (caja_undo_manager_parent_class)->finalize) (object);
+ }
+}
+
+void
+caja_undo_manager_attach (CajaUndoManager *manager, GObject *target)
+{
+ g_return_if_fail (CAJA_IS_UNDO_MANAGER (manager));
+ g_return_if_fail (G_IS_OBJECT (target));
+
+ caja_undo_attach_undo_manager (G_OBJECT (target), manager);
+}
+
+#ifdef UIH
+static void
+update_undo_menu_item (CajaUndoManager *manager,
+ UndoMenuHandlerConnection *connection)
+{
+ CORBA_Environment ev;
+ Caja_Undo_MenuItem *menu_item;
+
+ g_assert (CAJA_IS_UNDO_MANAGER (manager));
+ g_assert (connection != NULL);
+ g_assert (MATECOMPONENT_IS_UI_HANDLER (connection->handler));
+ g_assert (connection->path != NULL);
+ g_assert (connection->no_undo_menu_item_label != NULL);
+ g_assert (connection->no_undo_menu_item_hint != NULL);
+
+ CORBA_exception_init (&ev);
+
+ if (CORBA_Object_is_nil (manager->details->transaction, &ev))
+ {
+ menu_item = NULL;
+ }
+ else
+ {
+ if (manager->details->current_transaction_is_redo)
+ {
+ menu_item = Caja_Undo_Transaction__get_redo_menu_item
+ (manager->details->transaction, &ev);
+ }
+ else
+ {
+ menu_item = Caja_Undo_Transaction__get_undo_menu_item
+ (manager->details->transaction, &ev);
+ }
+ }
+
+ matecomponent_ui_handler_menu_set_sensitivity
+ (connection->handler, connection->path,
+ menu_item != NULL);
+ matecomponent_ui_handler_menu_set_label
+ (connection->handler, connection->path,
+ menu_item == NULL
+ ? connection->no_undo_menu_item_label
+ : menu_item->label);
+ matecomponent_ui_handler_menu_set_hint
+ (connection->handler, connection->path,
+ menu_item == NULL
+ ? connection->no_undo_menu_item_hint
+ : menu_item->hint);
+
+ CORBA_free (menu_item);
+
+ CORBA_exception_free (&ev);
+}
+
+static void
+undo_menu_handler_connection_free (UndoMenuHandlerConnection *connection)
+{
+ g_assert (connection != NULL);
+ g_assert (MATECOMPONENT_IS_UI_HANDLER (connection->handler));
+ g_assert (connection->path != NULL);
+ g_assert (connection->no_undo_menu_item_label != NULL);
+ g_assert (connection->no_undo_menu_item_hint != NULL);
+
+ g_free (connection->path);
+ g_free (connection->no_undo_menu_item_label);
+ g_free (connection->no_undo_menu_item_hint);
+ g_free (connection);
+}
+
+static void
+undo_menu_handler_connection_free_cover (gpointer data)
+{
+ undo_menu_handler_connection_free (data);
+}
+
+void
+caja_undo_manager_set_up_matecomponent_ui_handler_undo_item (CajaUndoManager *manager,
+ MateComponentUIHandler *handler,
+ const char *path,
+ const char *no_undo_menu_item_label,
+ const char *no_undo_menu_item_hint)
+{
+ UndoMenuHandlerConnection *connection;
+
+ connection = g_new (UndoMenuHandlerConnection, 1);
+ connection->handler = handler;
+ connection->path = g_strdup (path);
+ connection->no_undo_menu_item_label = g_strdup (no_undo_menu_item_label);
+ connection->no_undo_menu_item_hint = g_strdup (no_undo_menu_item_hint);
+
+ /* Set initial state of menu item. */
+ update_undo_menu_item (manager, connection);
+
+ /* Update it again whenever the changed signal is emitted. */
+ eel_gtk_signal_connect_full_while_alive
+ (GTK_OBJECT (manager), "changed",
+ G_CALLBACK (update_undo_menu_item), NULL,
+ connection, undo_menu_handler_connection_free_cover,
+ FALSE, FALSE,
+ GTK_OBJECT (handler));
+}
+#endif /* UIH */
+
+static void
+caja_undo_manager_class_init (CajaUndoManagerClass *class)
+{
+ G_OBJECT_CLASS (class)->finalize = finalize;
+
+ signals[CHANGED] = g_signal_new
+ ("changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaUndoManagerClass,
+ changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
diff --git a/libcaja-private/caja-undo-manager.h b/libcaja-private/caja-undo-manager.h
new file mode 100644
index 00000000..41665060
--- /dev/null
+++ b/libcaja-private/caja-undo-manager.h
@@ -0,0 +1,82 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* CajaUndoManager - Manages undo and redo transactions.
+ * This is the public interface used by the application.
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ *
+ * Author: Gene Z. Ragan <[email protected]>
+ *
+ * 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef CAJA_UNDO_MANAGER_H
+#define CAJA_UNDO_MANAGER_H
+
+#include <libcaja-private/caja-undo.h>
+
+#define CAJA_TYPE_UNDO_MANAGER caja_undo_manager_get_type()
+#define CAJA_UNDO_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_UNDO_MANAGER, CajaUndoManager))
+#define CAJA_UNDO_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_UNDO_MANAGER, CajaUndoManagerClass))
+#define CAJA_IS_UNDO_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_UNDO_MANAGER))
+#define CAJA_IS_UNDO_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_UNDO_MANAGER))
+#define CAJA_UNDO_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_UNDO_MANAGER, CajaUndoManagerClass))
+
+typedef struct CajaUndoManagerDetails CajaUndoManagerDetails;
+
+typedef struct
+{
+ GObject parent;
+ CajaUndoManagerDetails *details;
+} CajaUndoManager;
+
+typedef struct
+{
+ GObjectClass parent_slot;
+ void (* changed) (GObject *object, gpointer data);
+} CajaUndoManagerClass;
+
+GType caja_undo_manager_get_type (void);
+CajaUndoManager *caja_undo_manager_new (void);
+
+/* Undo operations. */
+void caja_undo_manager_undo (CajaUndoManager *undo_manager);
+
+#ifdef UIH
+/* Connect the manager to a particular menu item. */
+void caja_undo_manager_set_up_matecomponent_ui_handler_undo_item (CajaUndoManager *manager,
+ MateComponentUIHandler *handler,
+ const char *path,
+ const char *no_undo_menu_item_label,
+ const char *no_undo_menu_item_hint);
+
+#endif
+
+/* Attach the undo manager to a Gtk object so that object and the widgets inside it can participate in undo. */
+void caja_undo_manager_attach (CajaUndoManager *manager,
+ GObject *object);
+
+void caja_undo_manager_append (CajaUndoManager *manager,
+ CajaUndoTransaction *transaction);
+void caja_undo_manager_forget (CajaUndoManager *manager,
+ CajaUndoTransaction *transaction);
+
+#endif /* CAJA_UNDO_MANAGER_H */
diff --git a/libcaja-private/caja-undo-private.h b/libcaja-private/caja-undo-private.h
new file mode 100644
index 00000000..bd620388
--- /dev/null
+++ b/libcaja-private/caja-undo-private.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* xxx
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ *
+ * Author: Gene Z. Ragan <[email protected]>
+ *
+ * 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef CAJA_UNDO_PRIVATE_H
+#define CAJA_UNDO_PRIVATE_H
+
+#include <libcaja-private/caja-undo.h>
+#include <libcaja-private/caja-undo-manager.h>
+#include <glib-object.h>
+
+CajaUndoManager * caja_undo_get_undo_manager (GObject *attached_object);
+void caja_undo_attach_undo_manager (GObject *object,
+ CajaUndoManager *manager);
+
+#endif /* CAJA_UNDO_PRIVATE_H */
diff --git a/libcaja-private/caja-undo-signal-handlers.c b/libcaja-private/caja-undo-signal-handlers.c
new file mode 100644
index 00000000..04d5360e
--- /dev/null
+++ b/libcaja-private/caja-undo-signal-handlers.c
@@ -0,0 +1,345 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* Signal handlers to enable undo in Gtk Widgets.
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ *
+ * Author: Gene Z. Ragan <[email protected]>
+ *
+ * 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+#include <config.h>
+#include <gtk/gtk.h>
+
+#include <glib/gi18n.h>
+#include <libcaja-private/caja-undo.h>
+
+#include <eel/eel-gtk-macros.h>
+#include <string.h>
+
+#include "caja-undo-signal-handlers.h"
+
+
+typedef struct
+{
+ char *undo_text;
+ gint position;
+ guint selection_start;
+ guint selection_end;
+} EditableUndoData;
+
+typedef struct
+{
+ gboolean undo_registered;
+} EditableUndoObjectData;
+
+
+static void restore_editable_from_undo_snapshot_callback (GObject *target,
+ gpointer callback_data);
+static void editable_register_edit_undo (GtkEditable *editable);
+static void free_editable_object_data (gpointer data);
+
+/* caja_undo_set_up_caja_entry_for_undo
+ *
+ * Functions and callback methods to handle undo
+ * in a CajaEntry
+ */
+
+static void
+caja_entry_user_changed_callback (CajaEntry *entry)
+{
+ /* Register undo transaction */
+ editable_register_edit_undo (GTK_EDITABLE (entry));
+}
+
+void
+caja_undo_set_up_caja_entry_for_undo (CajaEntry *entry)
+{
+ EditableUndoObjectData *data;
+
+ if (!CAJA_IS_ENTRY (entry) )
+ {
+ return;
+ }
+
+ data = g_new(EditableUndoObjectData, 1);
+ data->undo_registered = FALSE;
+ g_object_set_data_full (G_OBJECT (entry), "undo_registered",
+ data, free_editable_object_data);
+
+ /* Connect to entry signals */
+ g_signal_connect (entry, "user_changed",
+ G_CALLBACK (caja_entry_user_changed_callback),
+ NULL);
+}
+
+void
+caja_undo_tear_down_caja_entry_for_undo (CajaEntry *entry)
+{
+ if (!CAJA_IS_ENTRY (entry) )
+ {
+ return;
+ }
+
+ /* Disconnect from entry signals */
+ g_signal_handlers_disconnect_by_func
+ (entry, G_CALLBACK (caja_entry_user_changed_callback), NULL);
+
+}
+
+/* caja_undo_set_up_caja_entry_for_undo
+ *
+ * Functions and callback methods to handle undo
+ * in a CajaEntry
+ */
+
+static void
+free_editable_undo_data (gpointer data)
+{
+ EditableUndoData *undo_data;
+
+ undo_data = (EditableUndoData *) data;
+
+ g_free (undo_data->undo_text);
+ g_free (undo_data);
+}
+
+static void
+free_editable_object_data (gpointer data)
+{
+ g_free (data);
+}
+
+
+static void
+editable_insert_text_callback (GtkEditable *editable)
+{
+ /* Register undo transaction */
+ editable_register_edit_undo (editable);
+}
+
+static void
+editable_delete_text_callback (GtkEditable *editable)
+{
+ /* Register undo transaction */
+ editable_register_edit_undo (editable);
+}
+
+static void
+editable_register_edit_undo (GtkEditable *editable)
+{
+ EditableUndoData *undo_data;
+ EditableUndoObjectData *undo_info;
+ gpointer data;
+
+ if (!GTK_IS_EDITABLE (editable) )
+ {
+ return;
+ }
+
+ /* Check our undo registered flag */
+ data = g_object_get_data (G_OBJECT (editable), "undo_registered");
+ if (data == NULL)
+ {
+ g_warning ("Undo data is NULL");
+ return;
+ }
+
+ undo_info = (EditableUndoObjectData *)data;
+ if (undo_info->undo_registered)
+ {
+ return;
+ }
+
+ undo_data = g_new0 (EditableUndoData, 1);
+ undo_data->undo_text = gtk_editable_get_chars (editable, 0, -1);
+ undo_data->position = gtk_editable_get_position (editable);
+ gtk_editable_get_selection_bounds (editable,
+ &undo_data->selection_start,
+ &undo_data->selection_end);
+
+ caja_undo_register
+ (G_OBJECT (editable),
+ restore_editable_from_undo_snapshot_callback,
+ undo_data,
+ (GDestroyNotify) free_editable_undo_data,
+ _("Edit"),
+ _("Undo Edit"),
+ _("Undo the edit"),
+ _("Redo Edit"),
+ _("Redo the edit"));
+
+ undo_info->undo_registered = TRUE;
+}
+
+void
+caja_undo_set_up_editable_for_undo (GtkEditable *editable)
+{
+ EditableUndoObjectData *data;
+
+ if (!GTK_IS_EDITABLE (editable) )
+ {
+ return;
+ }
+
+ /* Connect to editable signals */
+ g_signal_connect (editable, "insert_text",
+ G_CALLBACK (editable_insert_text_callback), NULL);
+ g_signal_connect (editable, "delete_text",
+ G_CALLBACK (editable_delete_text_callback), NULL);
+
+
+ data = g_new (EditableUndoObjectData, 1);
+ data->undo_registered = FALSE;
+ g_object_set_data_full (G_OBJECT (editable), "undo_registered",
+ data, free_editable_object_data);
+}
+
+void
+caja_undo_tear_down_editable_for_undo (GtkEditable *editable)
+{
+ if (!GTK_IS_EDITABLE (editable) )
+ {
+ return;
+ }
+
+ /* Disconnect from entry signals */
+ g_signal_handlers_disconnect_by_func
+ (editable, G_CALLBACK (editable_insert_text_callback), NULL);
+ g_signal_handlers_disconnect_by_func
+ (editable, G_CALLBACK (editable_delete_text_callback), NULL);
+}
+
+/* restore_editable_from_undo_snapshot_callback
+ *
+ * Restore edited text.
+ */
+static void
+restore_editable_from_undo_snapshot_callback (GObject *target, gpointer callback_data)
+{
+ GtkEditable *editable;
+ GtkWindow *window;
+ EditableUndoData *undo_data;
+ EditableUndoObjectData *data;
+ gint position;
+
+ editable = GTK_EDITABLE (target);
+ undo_data = (EditableUndoData *) callback_data;
+
+ /* Check our undo registered flag */
+ data = g_object_get_data (target, "undo_registered");
+ if (data == NULL)
+ {
+ g_warning ("Undo regisetred flag not found");
+ return;
+ }
+
+ /* Reset the registered flag so we get a new item for future editing. */
+ data->undo_registered = FALSE;
+
+ /* Register a new undo transaction for redo. */
+ editable_register_edit_undo (editable);
+
+ /* Restore the text. */
+ position = 0;
+ gtk_editable_delete_text (editable, 0, -1);
+ gtk_editable_insert_text (editable, undo_data->undo_text,
+ strlen (undo_data->undo_text), &position);
+
+ /* Set focus to widget */
+ window = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (target)));
+ gtk_window_set_focus (window, GTK_WIDGET (editable));
+
+ /* We have to do this call, because the previous call selects all text */
+ gtk_editable_select_region (editable, 0, 0);
+
+ /* Restore selection */
+ gtk_editable_select_region (editable, undo_data->selection_start,
+ undo_data->selection_end);
+
+ /* Set the i-beam to the saved position */
+ gtk_editable_set_position (editable, undo_data->position);
+
+ /* Reset the registered flag so we get a new item for future editing. */
+ data->undo_registered = FALSE;
+}
+
+
+/* editable_set_undo_key
+ *
+ * Allow the use of ctrl-z from within widget.
+ */
+
+/* Undo is disabled until we have a better implementation.
+ * Both here and in caja-shell-ui.xml.
+ */
+
+/* FIXME bugzilla.gnome.org 43515: Undo doesn't work */
+#ifdef UNDO_ENABLED
+
+static gboolean
+editable_key_press_event (GtkEditable *editable, GdkEventKey *event, gpointer user_data)
+{
+ switch (event->keyval)
+ {
+ /* Undo */
+ case 'z':
+ if ((event->state & GDK_CONTROL_MASK) != 0)
+ {
+ caja_undo (GTK_OBJECT (editable));
+ return TRUE;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return FALSE;
+}
+
+#endif
+
+/* editable_set_undo_key
+ *
+ * Allow the use of ctrl-z from within widget. This should only be
+ * set if there is no menu bar to use to undo the widget.
+ */
+
+void
+caja_undo_editable_set_undo_key (GtkEditable *editable, gboolean value)
+{
+ /* FIXME bugzilla.gnome.org 43515: Undo doesn't work */
+#ifdef UNDO_ENABLED
+ if (value)
+ {
+ /* Connect to entry signals */
+ g_signal_connect (editable, "key_press_event",
+ G_CALLBACK (editable_key_press_event), NULL);
+ }
+ else
+ {
+ /* FIXME bugzilla.gnome.org 45092: Warns if the handler
+ * is not already connected. We could use object data
+ * to prevent that little problem.
+ */
+ g_signal_disconnect_by_func (editable,
+ G_CALLBACK (editable_key_press_event), NULL);
+ }
+#endif
+}
diff --git a/libcaja-private/caja-undo-signal-handlers.h b/libcaja-private/caja-undo-signal-handlers.h
new file mode 100644
index 00000000..d80aaa9c
--- /dev/null
+++ b/libcaja-private/caja-undo-signal-handlers.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* Signal handlers to enable undo in Gtk Widgets.
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ *
+ * Author: Gene Z. Ragan <[email protected]>
+ *
+ * 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef CAJA_UNDO_SIGNAL_HANDLERS_H
+#define CAJA_UNDO_SIGNAL_HANDLERS_H
+
+#include <libcaja-private/caja-entry.h>
+
+void caja_undo_set_up_caja_entry_for_undo (CajaEntry *entry);
+void caja_undo_tear_down_caja_entry_for_undo (CajaEntry *entry);
+void caja_undo_set_up_editable_for_undo (GtkEditable *editable);
+void caja_undo_tear_down_editable_for_undo (GtkEditable *editable);
+void caja_undo_editable_set_undo_key (GtkEditable *editable,
+ gboolean value);
+
+#endif /* CAJA_UNDO_SIGNAL_HANDLERS_H */
diff --git a/libcaja-private/caja-undo-transaction.c b/libcaja-private/caja-undo-transaction.c
new file mode 100644
index 00000000..b5e1bb40
--- /dev/null
+++ b/libcaja-private/caja-undo-transaction.c
@@ -0,0 +1,343 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* CajaUndoTransaction - An object for an undoable transaction.
+ * Used internally by undo machinery.
+ * Not public.
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ *
+ * Author: Gene Z. Ragan <[email protected]>
+ *
+ * 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <libcaja-private/caja-undo.h>
+#include <libcaja-private/caja-undo-manager.h>
+#include <libcaja-private/caja-undo-transaction.h>
+
+#include "caja-undo-private.h"
+#include <gtk/gtk.h>
+
+#define CAJA_UNDO_TRANSACTION_LIST_DATA "Caja undo transaction list"
+
+/* undo atoms */
+static void undo_atom_list_free (GList *list);
+static void undo_atom_list_undo_and_free (GList *list);
+
+G_DEFINE_TYPE (CajaUndoTransaction, caja_undo_transaction,
+ G_TYPE_OBJECT);
+
+#ifdef UIH
+static Caja_Undo_MenuItem *
+impl_Caja_Undo_Transaction__get_undo_menu_item (PortableServer_Servant servant,
+ CORBA_Environment *ev)
+{
+ CajaUndoTransaction *transaction;
+ Caja_Undo_MenuItem *item;
+
+ transaction = CAJA_UNDO_TRANSACTION (matecomponent_object_from_servant (servant));
+
+ item = Caja_Undo_MenuItem__alloc ();
+ item->label = CORBA_string_dup (transaction->undo_menu_item_label);
+ item->hint = CORBA_string_dup (transaction->undo_menu_item_hint);
+
+ return item;
+}
+
+static Caja_Undo_MenuItem *
+impl_Caja_Undo_Transaction__get_redo_menu_item (PortableServer_Servant servant,
+ CORBA_Environment *ev)
+{
+ CajaUndoTransaction *transaction;
+ Caja_Undo_MenuItem *item;
+
+ transaction = CAJA_UNDO_TRANSACTION (matecomponent_object_from_servant (servant));
+
+ item = Caja_Undo_MenuItem__alloc ();
+ item->label = CORBA_string_dup (transaction->redo_menu_item_label);
+ item->hint = CORBA_string_dup (transaction->redo_menu_item_hint);
+
+ return item;
+}
+
+static CORBA_char *
+impl_Caja_Undo_Transaction__get_operation_name (PortableServer_Servant servant,
+ CORBA_Environment *ev)
+{
+ CajaUndoTransaction *transaction;
+
+ transaction = CAJA_UNDO_TRANSACTION (matecomponent_object_from_servant (servant));
+ return CORBA_string_dup (transaction->operation_name);
+}
+#endif
+
+
+CajaUndoTransaction *
+caja_undo_transaction_new (const char *operation_name,
+ const char *undo_menu_item_label,
+ const char *undo_menu_item_hint,
+ const char *redo_menu_item_label,
+ const char *redo_menu_item_hint)
+{
+ CajaUndoTransaction *transaction;
+
+ transaction = CAJA_UNDO_TRANSACTION (g_object_new (caja_undo_transaction_get_type (), NULL));
+
+ transaction->operation_name = g_strdup (operation_name);
+ transaction->undo_menu_item_label = g_strdup (undo_menu_item_label);
+ transaction->undo_menu_item_hint = g_strdup (undo_menu_item_hint);
+ transaction->redo_menu_item_label = g_strdup (redo_menu_item_label);
+ transaction->redo_menu_item_hint = g_strdup (redo_menu_item_hint);
+
+ return transaction;
+}
+
+static void
+caja_undo_transaction_init (CajaUndoTransaction *transaction)
+{
+}
+
+static void
+remove_transaction_from_object (gpointer list_data, gpointer callback_data)
+{
+ CajaUndoAtom *atom;
+ CajaUndoTransaction *transaction;
+ GList *list;
+
+ g_assert (list_data != NULL);
+ atom = list_data;
+ transaction = CAJA_UNDO_TRANSACTION (callback_data);
+
+ /* Remove the transaction from the list on the atom. */
+ list = g_object_get_data (atom->target, CAJA_UNDO_TRANSACTION_LIST_DATA);
+
+ if (list != NULL)
+ {
+ list = g_list_remove (list, transaction);
+ g_object_set_data (atom->target, CAJA_UNDO_TRANSACTION_LIST_DATA, list);
+ }
+}
+
+static void
+remove_transaction_from_atom_targets (CajaUndoTransaction *transaction)
+{
+
+ g_list_foreach (transaction->atom_list,
+ remove_transaction_from_object,
+ transaction);
+}
+
+static void
+caja_undo_transaction_finalize (GObject *object)
+{
+ CajaUndoTransaction *transaction;
+
+ transaction = CAJA_UNDO_TRANSACTION (object);
+
+ remove_transaction_from_atom_targets (transaction);
+ undo_atom_list_free (transaction->atom_list);
+
+ g_free (transaction->operation_name);
+ g_free (transaction->undo_menu_item_label);
+ g_free (transaction->undo_menu_item_hint);
+ g_free (transaction->redo_menu_item_label);
+ g_free (transaction->redo_menu_item_hint);
+
+ if (transaction->owner != NULL)
+ {
+ g_object_unref (transaction->owner);
+ }
+
+ G_OBJECT_CLASS (caja_undo_transaction_parent_class)->finalize (object);
+}
+
+void
+caja_undo_transaction_add_atom (CajaUndoTransaction *transaction,
+ const CajaUndoAtom *atom)
+{
+ GList *list;
+
+ g_return_if_fail (CAJA_IS_UNDO_TRANSACTION (transaction));
+ g_return_if_fail (atom != NULL);
+ g_return_if_fail (GTK_IS_OBJECT (atom->target));
+
+ /* Add the atom to the atom list in the transaction. */
+ transaction->atom_list = g_list_append
+ (transaction->atom_list, g_memdup (atom, sizeof (*atom)));
+
+ /* Add the transaction to the list on the atoms target object. */
+ list = g_object_get_data (atom->target, CAJA_UNDO_TRANSACTION_LIST_DATA);
+ if (g_list_find (list, transaction) != NULL)
+ {
+ return;
+ }
+
+ /* If it's not already on that atom, this object is new. */
+ list = g_list_prepend (list, transaction);
+ g_object_set_data (atom->target, CAJA_UNDO_TRANSACTION_LIST_DATA, list);
+
+ /* Connect a signal handler to the atom so it will unregister
+ * itself when it's destroyed.
+ */
+ g_signal_connect (atom->target, "destroy",
+ G_CALLBACK (caja_undo_transaction_unregister_object),
+ NULL);
+}
+
+void
+caja_undo_transaction_undo (CajaUndoTransaction *transaction)
+{
+ g_return_if_fail (CAJA_IS_UNDO_TRANSACTION (transaction));
+
+ remove_transaction_from_atom_targets (transaction);
+ undo_atom_list_undo_and_free (transaction->atom_list);
+
+ transaction->atom_list = NULL;
+}
+
+void
+caja_undo_transaction_add_to_undo_manager (CajaUndoTransaction *transaction,
+ CajaUndoManager *manager)
+{
+ g_return_if_fail (CAJA_IS_UNDO_TRANSACTION (transaction));
+ g_return_if_fail (transaction->owner == NULL);
+
+ if (manager != NULL)
+ {
+ caja_undo_manager_append (manager, transaction);
+ transaction->owner = g_object_ref (manager);
+ }
+}
+
+static void
+remove_atoms (CajaUndoTransaction *transaction,
+ GObject *object)
+{
+ GList *p, *next;
+ CajaUndoAtom *atom;
+
+ g_assert (CAJA_IS_UNDO_TRANSACTION (transaction));
+ g_assert (G_IS_OBJECT (object));
+
+ /* Destroy any atoms for this object. */
+ for (p = transaction->atom_list; p != NULL; p = next)
+ {
+ atom = p->data;
+ next = p->next;
+
+ if (atom->target == object)
+ {
+ transaction->atom_list = g_list_remove_link
+ (transaction->atom_list, p);
+ undo_atom_list_free (p);
+ }
+ }
+
+ /* If all the atoms are gone, forget this transaction.
+ * This may end up freeing the transaction.
+ */
+ if (transaction->atom_list == NULL)
+ {
+ caja_undo_manager_forget (
+ transaction->owner, transaction);
+ }
+}
+
+static void
+remove_atoms_cover (gpointer list_data, gpointer callback_data)
+{
+ if (CAJA_IS_UNDO_TRANSACTION (list_data))
+ {
+ remove_atoms (CAJA_UNDO_TRANSACTION (list_data), G_OBJECT (callback_data));
+ }
+}
+
+void
+caja_undo_transaction_unregister_object (GObject *object)
+{
+ GList *list;
+
+ g_return_if_fail (G_IS_OBJECT (object));
+
+ /* Remove atoms from each transaction on the list. */
+ list = g_object_get_data (object, CAJA_UNDO_TRANSACTION_LIST_DATA);
+ if (list != NULL)
+ {
+ g_list_foreach (list, remove_atoms_cover, object);
+ g_list_free (list);
+ g_object_set_data (object, CAJA_UNDO_TRANSACTION_LIST_DATA, NULL);
+ }
+}
+
+static void
+undo_atom_free (CajaUndoAtom *atom)
+{
+ /* Call the destroy-notify function if it's present. */
+ if (atom->callback_data_destroy_notify != NULL)
+ {
+ (* atom->callback_data_destroy_notify) (atom->callback_data);
+ }
+
+ /* Free the atom storage. */
+ g_free (atom);
+}
+
+static void
+undo_atom_undo_and_free (CajaUndoAtom *atom)
+{
+ /* Call the function that does the actual undo. */
+ (* atom->callback) (atom->target, atom->callback_data);
+
+ /* Get rid of the atom now that it's spent. */
+ undo_atom_free (atom);
+}
+
+static void
+undo_atom_free_cover (gpointer atom, gpointer callback_data)
+{
+ g_assert (atom != NULL);
+ g_assert (callback_data == NULL);
+ undo_atom_free (atom);
+}
+
+static void
+undo_atom_undo_and_free_cover (gpointer atom, gpointer callback_data)
+{
+ g_assert (atom != NULL);
+ g_assert (callback_data == NULL);
+ undo_atom_undo_and_free (atom);
+}
+
+static void
+undo_atom_list_free (GList *list)
+{
+ g_list_foreach (list, undo_atom_free_cover, NULL);
+ g_list_free (list);
+}
+
+static void
+undo_atom_list_undo_and_free (GList *list)
+{
+ g_list_foreach (list, undo_atom_undo_and_free_cover, NULL);
+ g_list_free (list);
+}
+
+static void
+caja_undo_transaction_class_init (CajaUndoTransactionClass *klass)
+{
+ G_OBJECT_CLASS (klass)->finalize = caja_undo_transaction_finalize;
+}
diff --git a/libcaja-private/caja-undo-transaction.h b/libcaja-private/caja-undo-transaction.h
new file mode 100644
index 00000000..d9c802af
--- /dev/null
+++ b/libcaja-private/caja-undo-transaction.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* CajaUndoTransaction - An object for an undoable transaction.
+ * Used internally by undo machinery.
+ * Not public.
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ *
+ * Author: Gene Z. Ragan <[email protected]>
+ *
+ * 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef CAJA_UNDO_TRANSACTION_H
+#define CAJA_UNDO_TRANSACTION_H
+
+#include <libcaja-private/caja-undo.h>
+
+#define CAJA_TYPE_UNDO_TRANSACTION caja_undo_transaction_get_type()
+#define CAJA_UNDO_TRANSACTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_UNDO_TRANSACTION, CajaUndoTransaction))
+#define CAJA_UNDO_TRANSACTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_UNDO_TRANSACTION, CajaUndoTransactionClass))
+#define CAJA_IS_UNDO_TRANSACTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_UNDO_TRANSACTION))
+#define CAJA_IS_UNDO_TRANSACTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_UNDO_TRANSACTION))
+#define CAJA_UNDO_TRANSACTION_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_UNDO_TRANSACTION, CajaUndoTransactionClass))
+
+/* The typedef for CajaUndoTransaction is in caja-undo.h
+ to avoid circular deps */
+typedef struct _CajaUndoTransactionClass CajaUndoTransactionClass;
+
+struct _CajaUndoTransaction
+{
+ GObject parent_slot;
+
+ char *operation_name;
+ char *undo_menu_item_label;
+ char *undo_menu_item_hint;
+ char *redo_menu_item_label;
+ char *redo_menu_item_hint;
+ GList *atom_list;
+
+ CajaUndoManager *owner;
+};
+
+struct _CajaUndoTransactionClass
+{
+ GObjectClass parent_slot;
+};
+
+GType caja_undo_transaction_get_type (void);
+CajaUndoTransaction *caja_undo_transaction_new (const char *operation_name,
+ const char *undo_menu_item_label,
+ const char *undo_menu_item_hint,
+ const char *redo_menu_item_label,
+ const char *redo_menu_item_hint);
+void caja_undo_transaction_add_atom (CajaUndoTransaction *transaction,
+ const CajaUndoAtom *atom);
+void caja_undo_transaction_add_to_undo_manager (CajaUndoTransaction *transaction,
+ CajaUndoManager *manager);
+void caja_undo_transaction_unregister_object (GObject *atom_target);
+void caja_undo_transaction_undo (CajaUndoTransaction *transaction);
+
+#endif /* CAJA_UNDO_TRANSACTION_H */
diff --git a/libcaja-private/caja-undo.c b/libcaja-private/caja-undo.c
new file mode 100644
index 00000000..6dc5a029
--- /dev/null
+++ b/libcaja-private/caja-undo.c
@@ -0,0 +1,218 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-undo.c - public interface for objects that implement
+ * undoable actions -- works across components
+ *
+ * Copyright (C) 2000 Eazel, 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Darin Adler <[email protected]>
+ */
+
+#include <config.h>
+#include "caja-undo.h"
+
+#include "caja-undo-private.h"
+#include "caja-undo-transaction.h"
+#include <gtk/gtk.h>
+
+#define CAJA_UNDO_MANAGER_DATA "Caja undo manager"
+
+/* Register a simple undo action by calling caja_undo_register_full. */
+void
+caja_undo_register (GObject *target,
+ CajaUndoCallback callback,
+ gpointer callback_data,
+ GDestroyNotify callback_data_destroy_notify,
+ const char *operation_name,
+ const char *undo_menu_item_label,
+ const char *undo_menu_item_hint,
+ const char *redo_menu_item_label,
+ const char *redo_menu_item_hint)
+{
+ CajaUndoAtom atom;
+ GList single_atom_list;
+
+ g_return_if_fail (G_IS_OBJECT (target));
+ g_return_if_fail (callback != NULL);
+
+ /* Make an atom. */
+ atom.target = target;
+ atom.callback = callback;
+ atom.callback_data = callback_data;
+ atom.callback_data_destroy_notify = callback_data_destroy_notify;
+
+ /* Make a single-atom list. */
+ single_atom_list.data = &atom;
+ single_atom_list.next = NULL;
+ single_atom_list.prev = NULL;
+
+ /* Call the full version of the registration function,
+ * using the undo target as the place to search for the
+ * undo manager.
+ */
+ caja_undo_register_full (&single_atom_list,
+ target,
+ operation_name,
+ undo_menu_item_label,
+ undo_menu_item_hint,
+ redo_menu_item_label,
+ redo_menu_item_hint);
+}
+
+/* Register an undo action. */
+void
+caja_undo_register_full (GList *atoms,
+ GObject *undo_manager_search_start_object,
+ const char *operation_name,
+ const char *undo_menu_item_label,
+ const char *undo_menu_item_hint,
+ const char *redo_menu_item_label,
+ const char *redo_menu_item_hint)
+{
+ CajaUndoTransaction *transaction;
+ GList *p;
+
+ g_return_if_fail (atoms != NULL);
+ g_return_if_fail (G_IS_OBJECT (undo_manager_search_start_object));
+
+ /* Create an undo transaction */
+ transaction = caja_undo_transaction_new (operation_name,
+ undo_menu_item_label,
+ undo_menu_item_hint,
+ redo_menu_item_label,
+ redo_menu_item_hint);
+ for (p = atoms; p != NULL; p = p->next)
+ {
+ caja_undo_transaction_add_atom (transaction, p->data);
+ }
+ caja_undo_transaction_add_to_undo_manager
+ (transaction,
+ caja_undo_get_undo_manager (undo_manager_search_start_object));
+
+ /* Now we are done with the transaction.
+ * If the undo manager is holding it, then this will not destroy it.
+ */
+ g_object_unref (transaction);
+}
+
+/* Cover for forgetting about all undo relating to a particular target. */
+void
+caja_undo_unregister (GObject *target)
+{
+ /* Perhaps this should also unregister all children if called on a
+ * GtkContainer? That might be handy.
+ */
+ caja_undo_transaction_unregister_object (target);
+}
+
+void
+caja_undo (GObject *undo_manager_search_start_object)
+{
+ CajaUndoManager *manager;
+
+ g_return_if_fail (G_IS_OBJECT (undo_manager_search_start_object));
+
+ manager = caja_undo_get_undo_manager (undo_manager_search_start_object);
+ if (manager != NULL)
+ {
+ caja_undo_manager_undo (manager);
+ }
+}
+
+CajaUndoManager *
+caja_undo_get_undo_manager (GObject *start_object)
+{
+ CajaUndoManager *manager;
+ GtkWidget *parent;
+ GtkWindow *transient_parent;
+
+ if (start_object == NULL)
+ {
+ return NULL;
+ }
+
+ g_return_val_if_fail (G_IS_OBJECT (start_object), NULL);
+
+ /* Check for an undo manager right here. */
+ manager = g_object_get_data (start_object, CAJA_UNDO_MANAGER_DATA);
+ if (manager != NULL)
+ {
+ return manager;
+ }
+
+ /* Check for undo manager up the parent chain. */
+ if (GTK_IS_WIDGET (start_object))
+ {
+ parent = gtk_widget_get_parent (GTK_WIDGET (start_object));
+ if (parent != NULL)
+ {
+ manager = caja_undo_get_undo_manager (G_OBJECT (parent));
+ if (manager != NULL)
+ {
+ return manager;
+ }
+ }
+
+ /* Check for undo manager in our window's parent. */
+ if (GTK_IS_WINDOW (start_object))
+ {
+ transient_parent = gtk_window_get_transient_for (GTK_WINDOW (start_object));
+ if (transient_parent != NULL)
+ {
+ manager = caja_undo_get_undo_manager (G_OBJECT (transient_parent));
+ if (manager != NULL)
+ {
+ return manager;
+ }
+ }
+ }
+ }
+
+ /* Found nothing. I can live with that. */
+ return NULL;
+}
+
+void
+caja_undo_attach_undo_manager (GObject *object,
+ CajaUndoManager *manager)
+{
+ g_return_if_fail (G_IS_OBJECT (object));
+
+ if (manager == NULL)
+ {
+ g_object_set_data (object, CAJA_UNDO_MANAGER_DATA, NULL);
+ }
+ else
+ {
+ g_object_ref (manager);
+ g_object_set_data_full
+ (object, CAJA_UNDO_MANAGER_DATA,
+ manager, g_object_unref);
+ }
+}
+
+/* Copy a reference to the undo manager fromone object to another. */
+void
+caja_undo_share_undo_manager (GObject *destination_object,
+ GObject *source_object)
+{
+ CajaUndoManager *manager;
+
+ manager = caja_undo_get_undo_manager (source_object);
+ caja_undo_attach_undo_manager (destination_object, manager);
+}
diff --git a/libcaja-private/caja-undo.h b/libcaja-private/caja-undo.h
new file mode 100644
index 00000000..09adefc0
--- /dev/null
+++ b/libcaja-private/caja-undo.h
@@ -0,0 +1,77 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-undo.h - public interface for objects that implement
+ * undoable actions -- works across components
+ *
+ * Copyright (C) 2000 Eazel, 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Darin Adler <[email protected]>
+ */
+
+#ifndef CAJA_UNDO_H
+#define CAJA_UNDO_H
+
+#include <glib-object.h>
+
+typedef struct _CajaUndoTransaction CajaUndoTransaction;
+
+
+/* The basic undoable operation. */
+typedef void (* CajaUndoCallback) (GObject *target, gpointer callback_data);
+
+/* Recipe for undo of a bit of work on an object.
+ * Create these atoms when you want to register more
+ * than one as a single undoable operation.
+ */
+typedef struct
+{
+ GObject *target;
+ CajaUndoCallback callback;
+ gpointer callback_data;
+ GDestroyNotify callback_data_destroy_notify;
+} CajaUndoAtom;
+
+/* Registering something that can be undone. */
+void caja_undo_register (GObject *target,
+ CajaUndoCallback callback,
+ gpointer callback_data,
+ GDestroyNotify callback_data_destroy_notify,
+ const char *operation_name,
+ const char *undo_menu_item_label,
+ const char *undo_menu_item_hint,
+ const char *redo_menu_item_label,
+ const char *redo_menu_item_hint);
+void caja_undo_register_full (GList *atoms,
+ GObject *undo_manager_search_start_object,
+ const char *operation_name,
+ const char *undo_menu_item_label,
+ const char *undo_menu_item_hint,
+ const char *redo_menu_item_label,
+ const char *redo_menu_item_hint);
+void caja_undo_unregister (GObject *target);
+
+/* Performing an undo explicitly. Only for use by objects "out in the field".
+ * The menu bar itself uses a richer API in the undo manager.
+ */
+void caja_undo (GObject *undo_manager_search_start_object);
+
+/* Connecting an undo manager. */
+void caja_undo_share_undo_manager (GObject *destination_object,
+ GObject *source_object);
+
+#endif /* CAJA_UNDO_H */
diff --git a/libcaja-private/caja-users-groups-cache.c b/libcaja-private/caja-users-groups-cache.c
new file mode 100644
index 00000000..41682854
--- /dev/null
+++ b/libcaja-private/caja-users-groups-cache.c
@@ -0,0 +1,295 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-users-groups-cache.c: cache of users' and groups' names.
+
+ Copyright (C) 2006 Zbigniew Chyla
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with this program; if not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Zbigniew Chyla <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-users-groups-cache.h"
+
+#include <glib.h>
+#include <grp.h>
+#include <pwd.h>
+
+
+typedef struct _ExpiringCache ExpiringCache;
+
+/* times in seconds */
+#define USERS_CACHE_EXPIRE_TIME 60
+#define GROUPS_CACHE_EXPIRE_TIME 60
+
+/* cache of users' names */
+static ExpiringCache *users_cache = NULL;
+
+/* cache of groups' names */
+static ExpiringCache *groups_cache = NULL;
+
+
+/**
+ * Generic implementation of cache with guint keys and values which expire
+ * after specified amount of time.
+ */
+
+typedef gpointer (*ExpiringCacheGetValFunc) (guint key);
+
+
+struct _ExpiringCache
+{
+ /* Expiration time of cached value */
+ time_t expire_time;
+
+ /* Called to obtain a value by a key */
+ ExpiringCacheGetValFunc get_value_func;
+
+ /* Called to destroy a value */
+ GDestroyNotify value_destroy_func;
+
+ /* Stores cached values */
+ GHashTable *cached_values;
+};
+
+
+typedef struct _ExpiringCacheEntry ExpiringCacheEntry;
+
+struct _ExpiringCacheEntry
+{
+ ExpiringCache *cache;
+ guint key;
+ gpointer value;
+};
+
+
+static ExpiringCache *
+expiring_cache_new (time_t expire_time, ExpiringCacheGetValFunc get_value_func,
+ GDestroyNotify value_destroy_func)
+{
+ ExpiringCache *cache;
+
+ g_assert (get_value_func != NULL);
+
+ cache = g_new (ExpiringCache, 1);
+ cache->expire_time = expire_time;
+ cache->get_value_func = get_value_func;
+ cache->value_destroy_func = value_destroy_func;
+ cache->cached_values = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL);
+
+ return cache;
+}
+
+static ExpiringCacheEntry *
+expiring_cache_entry_new (ExpiringCache *cache, guint key, gpointer value)
+{
+ ExpiringCacheEntry *entry;
+
+ entry = g_slice_new (ExpiringCacheEntry);
+ entry->cache = cache;
+ entry->key = key;
+ entry->value = value;
+
+ return entry;
+}
+
+static void
+expiring_cache_entry_destroy (ExpiringCacheEntry *entry)
+{
+ if (entry->cache->value_destroy_func != NULL)
+ {
+ entry->cache->value_destroy_func (entry->value);
+ }
+ g_slice_free (ExpiringCacheEntry, entry);
+}
+
+static gboolean
+cb_cache_entry_expired (ExpiringCacheEntry *entry)
+{
+ g_hash_table_remove (entry->cache->cached_values, GSIZE_TO_POINTER (entry->key));
+ expiring_cache_entry_destroy (entry);
+
+ return FALSE;
+}
+
+static gpointer
+expiring_cache_get_value (ExpiringCache *cache, guint key)
+{
+ ExpiringCacheEntry *entry;
+
+ g_assert (cache != NULL);
+
+ if (!g_hash_table_lookup_extended (cache->cached_values, GSIZE_TO_POINTER (key),
+ NULL, (gpointer) &entry))
+ {
+ entry = expiring_cache_entry_new (cache, key, cache->get_value_func (key));
+ g_hash_table_insert (cache->cached_values, GSIZE_TO_POINTER (key), entry);
+ g_timeout_add_seconds (cache->expire_time, (GSourceFunc) cb_cache_entry_expired, entry);
+ }
+
+ return entry->value;
+}
+
+
+/*
+ * Cache of users' names based on ExpiringCache.
+ */
+
+typedef struct _UserInfo UserInfo;
+
+struct _UserInfo
+{
+ char *name;
+ char *gecos;
+};
+
+
+static UserInfo *
+user_info_new (struct passwd *password_info)
+{
+ UserInfo *uinfo;
+
+ if (password_info != NULL)
+ {
+ uinfo = g_slice_new (UserInfo);
+ uinfo->name = g_strdup (password_info->pw_name);
+ uinfo->gecos = g_strdup (password_info->pw_gecos);
+ }
+ else
+ {
+ uinfo = NULL;
+ }
+
+ return uinfo;
+}
+
+static void
+user_info_free (UserInfo *uinfo)
+{
+ if (uinfo != NULL)
+ {
+ g_free (uinfo->name);
+ g_free (uinfo->gecos);
+ g_slice_free (UserInfo, uinfo);
+ }
+}
+
+static gpointer
+users_cache_get_value (guint key)
+{
+ return user_info_new (getpwuid (key));
+}
+
+static UserInfo *
+get_cached_user_info(guint uid)
+{
+ if (users_cache == NULL)
+ {
+ users_cache = expiring_cache_new (USERS_CACHE_EXPIRE_TIME, users_cache_get_value,
+ (GDestroyNotify) user_info_free);
+ }
+
+ return expiring_cache_get_value (users_cache, uid);
+}
+
+/**
+ * caja_users_cache_get_name:
+ *
+ * Returns name of user with given uid (using cached data if possible) or
+ * NULL in case a user with given uid can't be found.
+ *
+ * Returns: Newly allocated string or NULL.
+ */
+char *
+caja_users_cache_get_name (uid_t uid)
+{
+ UserInfo *uinfo;
+
+ uinfo = get_cached_user_info (uid);
+ if (uinfo != NULL)
+ {
+ return g_strdup (uinfo->name);
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+/**
+ * caja_users_cache_get_gecos:
+ *
+ * Returns gecos of user with given uid (using cached data if possible) or
+ * NULL in case a user with given uid can't be found.
+ *
+ * Returns: Newly allocated string or NULL.
+ */
+char *
+caja_users_cache_get_gecos (uid_t uid)
+{
+ UserInfo *uinfo;
+
+ uinfo = get_cached_user_info (uid);
+ if (uinfo != NULL)
+ {
+ return g_strdup (uinfo->gecos);
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+/*
+ * Cache of groups' names based on ExpiringCache.
+ */
+
+static gpointer
+groups_cache_get_value (guint key)
+{
+ struct group *group_info;
+
+ group_info = getgrgid (GPOINTER_TO_SIZE (key));
+ if (group_info != NULL)
+ {
+ return g_strdup (group_info->gr_name);
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+
+/**
+ * caja_groups_cache_get_name:
+ *
+ * Returns name of group with given gid (using cached data if possible) or
+ * NULL in case a group with given gid can't be found.
+ *
+ * Returns: Newly allocated string or NULL.
+ */
+char *
+caja_groups_cache_get_name (gid_t gid)
+{
+ if (groups_cache == NULL)
+ {
+ groups_cache = expiring_cache_new (GROUPS_CACHE_EXPIRE_TIME, groups_cache_get_value, g_free);
+ }
+
+ return g_strdup (expiring_cache_get_value (groups_cache, gid));
+}
diff --git a/libcaja-private/caja-users-groups-cache.h b/libcaja-private/caja-users-groups-cache.h
new file mode 100644
index 00000000..719be0d7
--- /dev/null
+++ b/libcaja-private/caja-users-groups-cache.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-users-groups-cache.h: cache of users' and groups' names.
+
+ Copyright (C) 2006 Zbigniew Chyla
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with this program; if not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Zbigniew Chyla <[email protected]>
+*/
+
+#ifndef CAJA_USERS_GROUPS_CACHE_H
+#define CAJA_USERS_GROUPS_CACHE_H
+
+#include <sys/types.h>
+
+char *caja_users_cache_get_name (uid_t uid);
+char *caja_users_cache_get_gecos (uid_t uid);
+char *caja_groups_cache_get_name (gid_t gid);
+
+#endif /* CAJA_USERS_GROUPS_CACHE_H */
diff --git a/libcaja-private/caja-vfs-directory.c b/libcaja-private/caja-vfs-directory.c
new file mode 100644
index 00000000..152ed70b
--- /dev/null
+++ b/libcaja-private/caja-vfs-directory.c
@@ -0,0 +1,170 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-vfs-directory.c: Subclass of CajaDirectory 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Darin Adler <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-vfs-directory.h"
+
+#include "caja-directory-private.h"
+#include <eel/eel-gtk-macros.h>
+#include "caja-file-private.h"
+
+static void caja_vfs_directory_init (gpointer object,
+ gpointer klass);
+static void caja_vfs_directory_class_init (gpointer klass);
+
+EEL_CLASS_BOILERPLATE (CajaVFSDirectory,
+ caja_vfs_directory,
+ CAJA_TYPE_DIRECTORY)
+
+static void
+caja_vfs_directory_init (gpointer object, gpointer klass)
+{
+ CajaVFSDirectory *directory;
+
+ directory = CAJA_VFS_DIRECTORY (object);
+}
+
+static gboolean
+vfs_contains_file (CajaDirectory *directory,
+ CajaFile *file)
+{
+ g_assert (CAJA_IS_VFS_DIRECTORY (directory));
+ g_assert (CAJA_IS_FILE (file));
+
+ return file->details->directory == directory;
+}
+
+static void
+vfs_call_when_ready (CajaDirectory *directory,
+ CajaFileAttributes file_attributes,
+ gboolean wait_for_file_list,
+ CajaDirectoryCallback callback,
+ gpointer callback_data)
+{
+ g_assert (CAJA_IS_VFS_DIRECTORY (directory));
+
+ caja_directory_call_when_ready_internal
+ (directory,
+ NULL,
+ file_attributes,
+ wait_for_file_list,
+ callback,
+ NULL,
+ callback_data);
+}
+
+static void
+vfs_cancel_callback (CajaDirectory *directory,
+ CajaDirectoryCallback callback,
+ gpointer callback_data)
+{
+ g_assert (CAJA_IS_VFS_DIRECTORY (directory));
+
+ caja_directory_cancel_callback_internal
+ (directory,
+ NULL,
+ callback,
+ NULL,
+ callback_data);
+}
+
+static void
+vfs_file_monitor_add (CajaDirectory *directory,
+ gconstpointer client,
+ gboolean monitor_hidden_files,
+ gboolean monitor_backup_files,
+ CajaFileAttributes file_attributes,
+ CajaDirectoryCallback callback,
+ gpointer callback_data)
+{
+ g_assert (CAJA_IS_VFS_DIRECTORY (directory));
+ g_assert (client != NULL);
+
+ caja_directory_monitor_add_internal
+ (directory, NULL,
+ client,
+ monitor_hidden_files,
+ monitor_backup_files,
+ file_attributes,
+ callback, callback_data);
+}
+
+static void
+vfs_file_monitor_remove (CajaDirectory *directory,
+ gconstpointer client)
+{
+ g_assert (CAJA_IS_VFS_DIRECTORY (directory));
+ g_assert (client != NULL);
+
+ caja_directory_monitor_remove_internal (directory, NULL, client);
+}
+
+static void
+vfs_force_reload (CajaDirectory *directory)
+{
+ CajaFileAttributes all_attributes;
+
+ g_assert (CAJA_IS_DIRECTORY (directory));
+
+ all_attributes = caja_file_get_all_attributes ();
+ caja_directory_force_reload_internal (directory,
+ all_attributes);
+}
+
+static gboolean
+vfs_are_all_files_seen (CajaDirectory *directory)
+{
+ g_assert (CAJA_IS_VFS_DIRECTORY (directory));
+
+ return directory->details->directory_loaded;
+}
+
+static gboolean
+vfs_is_not_empty (CajaDirectory *directory)
+{
+ g_assert (CAJA_IS_VFS_DIRECTORY (directory));
+ g_assert (caja_directory_is_anyone_monitoring_file_list (directory));
+
+ return directory->details->file_list != NULL;
+}
+
+static void
+caja_vfs_directory_class_init (gpointer klass)
+{
+ GObjectClass *object_class;
+ CajaDirectoryClass *directory_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ directory_class = CAJA_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/libcaja-private/caja-vfs-directory.h b/libcaja-private/caja-vfs-directory.h
new file mode 100644
index 00000000..2f8838f4
--- /dev/null
+++ b/libcaja-private/caja-vfs-directory.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-vfs-directory.h: Subclass of CajaDirectory 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Darin Adler <[email protected]>
+*/
+
+#ifndef CAJA_VFS_DIRECTORY_H
+#define CAJA_VFS_DIRECTORY_H
+
+#include <libcaja-private/caja-directory.h>
+
+#define CAJA_TYPE_VFS_DIRECTORY caja_vfs_directory_get_type()
+#define CAJA_VFS_DIRECTORY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_VFS_DIRECTORY, CajaVFSDirectory))
+#define CAJA_VFS_DIRECTORY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_VFS_DIRECTORY, CajaVFSDirectoryClass))
+#define CAJA_IS_VFS_DIRECTORY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_VFS_DIRECTORY))
+#define CAJA_IS_VFS_DIRECTORY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_VFS_DIRECTORY))
+#define CAJA_VFS_DIRECTORY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_VFS_DIRECTORY, CajaVFSDirectoryClass))
+
+typedef struct CajaVFSDirectoryDetails CajaVFSDirectoryDetails;
+
+typedef struct
+{
+ CajaDirectory parent_slot;
+} CajaVFSDirectory;
+
+typedef struct
+{
+ CajaDirectoryClass parent_slot;
+} CajaVFSDirectoryClass;
+
+GType caja_vfs_directory_get_type (void);
+
+#endif /* CAJA_VFS_DIRECTORY_H */
diff --git a/libcaja-private/caja-vfs-file.c b/libcaja-private/caja-vfs-file.c
new file mode 100644
index 00000000..797ca79d
--- /dev/null
+++ b/libcaja-private/caja-vfs-file.c
@@ -0,0 +1,781 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-vfs-file.c: Subclass of CajaFile 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Darin Adler <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-vfs-file.h"
+
+#include "caja-directory-notify.h"
+#include "caja-directory-private.h"
+#include "caja-file-private.h"
+#include "caja-autorun.h"
+#include <eel/eel-gtk-macros.h>
+#include <glib/gi18n.h>
+
+static void caja_vfs_file_init (gpointer object,
+ gpointer klass);
+static void caja_vfs_file_class_init (gpointer klass);
+
+EEL_CLASS_BOILERPLATE (CajaVFSFile,
+ caja_vfs_file,
+ CAJA_TYPE_FILE)
+
+static void
+vfs_file_monitor_add (CajaFile *file,
+ gconstpointer client,
+ CajaFileAttributes attributes)
+{
+ caja_directory_monitor_add_internal
+ (file->details->directory, file,
+ client, TRUE, TRUE, attributes, NULL, NULL);
+}
+
+static void
+vfs_file_monitor_remove (CajaFile *file,
+ gconstpointer client)
+{
+ caja_directory_monitor_remove_internal
+ (file->details->directory, file, client);
+}
+
+static void
+vfs_file_call_when_ready (CajaFile *file,
+ CajaFileAttributes file_attributes,
+ CajaFileCallback callback,
+ gpointer callback_data)
+
+{
+ caja_directory_call_when_ready_internal
+ (file->details->directory, file,
+ file_attributes, FALSE, NULL, callback, callback_data);
+}
+
+static void
+vfs_file_cancel_call_when_ready (CajaFile *file,
+ CajaFileCallback callback,
+ gpointer callback_data)
+{
+ caja_directory_cancel_callback_internal
+ (file->details->directory, file,
+ NULL, callback, callback_data);
+}
+
+static gboolean
+vfs_file_check_if_ready (CajaFile *file,
+ CajaFileAttributes file_attributes)
+{
+ return caja_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)
+{
+ CajaFile *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 (caja_file_update_info (file, new_info))
+ {
+ caja_file_changed (file);
+ }
+ g_object_unref (new_info);
+ }
+ caja_file_unref (file);
+ if (error)
+ {
+ g_error_free (error);
+ }
+}
+
+static void
+set_metadata_callback (GObject *source_object,
+ GAsyncResult *result,
+ gpointer callback_data)
+{
+ CajaFile *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),
+ CAJA_FILE_DEFAULT_ATTRIBUTES,
+ 0,
+ G_PRIORITY_DEFAULT,
+ NULL,
+ set_metadata_get_info_callback, file);
+ }
+ else
+ {
+ caja_file_unref (file);
+ g_error_free (error);
+ }
+}
+
+static void
+vfs_file_set_metadata (CajaFile *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 = caja_file_get_location (file);
+ g_file_set_attributes_async (location,
+ info,
+ 0,
+ G_PRIORITY_DEFAULT,
+ NULL,
+ set_metadata_callback,
+ caja_file_ref (file));
+ g_object_unref (location);
+ g_object_unref (info);
+}
+
+static void
+vfs_file_set_metadata_as_list (CajaFile *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 = caja_file_get_location (file);
+ g_file_set_attributes_async (location,
+ info,
+ 0,
+ G_PRIORITY_DEFAULT,
+ NULL,
+ set_metadata_callback,
+ caja_file_ref (file));
+ g_object_unref (info);
+ g_object_unref (location);
+}
+
+static gboolean
+vfs_file_get_item_count (CajaFile *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 CajaRequestStatus
+vfs_file_get_deep_counts (CajaFile *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 (!caja_file_is_directory (file))
+ {
+ return CAJA_REQUEST_DONE;
+ }
+
+ if (file->details->deep_counts_status != CAJA_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 = caja_file_get_file_type (file);
+ if (type == G_FILE_TYPE_UNKNOWN
+ || type == G_FILE_TYPE_DIRECTORY)
+ {
+ return CAJA_REQUEST_NOT_STARTED;
+ }
+
+ /* For other types, we are done, and the zeros are permanent. */
+ return CAJA_REQUEST_DONE;
+}
+
+static gboolean
+vfs_file_get_date (CajaFile *file,
+ CajaDateType date_type,
+ time_t *date)
+{
+ switch (date_type)
+ {
+ case CAJA_DATE_TYPE_CHANGED:
+ /* Before we have info on a file, the date is unknown. */
+ if (file->details->ctime == 0)
+ {
+ return FALSE;
+ }
+ if (date != NULL)
+ {
+ *date = file->details->ctime;
+ }
+ return TRUE;
+ case CAJA_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 CAJA_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 CAJA_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;
+ case CAJA_DATE_TYPE_PERMISSIONS_CHANGED:
+ /* Before we have info on a file, the date is unknown. */
+ if (file->details->mtime == 0 || file->details->ctime == 0)
+ {
+ return FALSE;
+ }
+ /* mtime is when the contents changed; ctime is when the
+ * contents or the permissions (inc. owner/group) changed.
+ * So we can only know when the permissions changed if mtime
+ * and ctime are different.
+ */
+ if (file->details->mtime == file->details->ctime)
+ {
+ return FALSE;
+ }
+ if (date != NULL)
+ {
+ *date = file->details->ctime;
+ }
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static char *
+vfs_file_get_where_string (CajaFile *file)
+{
+ return caja_file_get_parent_uri_for_display (file);
+}
+
+static void
+vfs_file_mount_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer callback_data)
+{
+ CajaFileOperation *op;
+ GFile *mounted_on;
+ GError *error;
+
+ op = callback_data;
+
+ error = NULL;
+ mounted_on = g_file_mount_mountable_finish (G_FILE (source_object),
+ res, &error);
+ caja_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 (CajaFile *file,
+ GMountOperation *mount_op,
+ GCancellable *cancellable,
+ CajaFileOperationCallback callback,
+ gpointer callback_data)
+{
+ CajaFileOperation *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 = caja_file_operation_new (file, callback, callback_data);
+ if (cancellable)
+ {
+ g_object_unref (op->cancellable);
+ op->cancellable = g_object_ref (cancellable);
+ }
+
+ location = caja_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)
+{
+ CajaFileOperation *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;
+ }
+
+ caja_file_operation_complete (op, G_FILE (source_object), error);
+ if (error)
+ {
+ g_error_free (error);
+ }
+}
+
+static void
+vfs_file_unmount (CajaFile *file,
+ GMountOperation *mount_op,
+ GCancellable *cancellable,
+ CajaFileOperationCallback callback,
+ gpointer callback_data)
+{
+ CajaFileOperation *op;
+ GFile *location;
+
+ op = caja_file_operation_new (file, callback, callback_data);
+ if (cancellable)
+ {
+ g_object_unref (op->cancellable);
+ op->cancellable = g_object_ref (cancellable);
+ }
+
+ location = caja_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)
+{
+ CajaFileOperation *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;
+ }
+
+ caja_file_operation_complete (op, G_FILE (source_object), error);
+ if (error)
+ {
+ g_error_free (error);
+ }
+}
+
+static void
+vfs_file_eject (CajaFile *file,
+ GMountOperation *mount_op,
+ GCancellable *cancellable,
+ CajaFileOperationCallback callback,
+ gpointer callback_data)
+{
+ CajaFileOperation *op;
+ GFile *location;
+
+ op = caja_file_operation_new (file, callback, callback_data);
+ if (cancellable)
+ {
+ g_object_unref (op->cancellable);
+ op->cancellable = g_object_ref (cancellable);
+ }
+
+ location = caja_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)
+{
+ CajaFileOperation *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;
+ }
+
+ caja_file_operation_complete (op, G_FILE (source_object), error);
+ if (error)
+ {
+ g_error_free (error);
+ }
+}
+
+
+static void
+vfs_file_start (CajaFile *file,
+ GMountOperation *mount_op,
+ GCancellable *cancellable,
+ CajaFileOperationCallback callback,
+ gpointer callback_data)
+{
+ CajaFileOperation *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 = caja_file_operation_new (file, callback, callback_data);
+ if (cancellable)
+ {
+ g_object_unref (op->cancellable);
+ op->cancellable = g_object_ref (cancellable);
+ }
+
+ location = caja_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)
+{
+ CajaFileOperation *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;
+ }
+
+ caja_file_operation_complete (op, G_FILE (source_object), error);
+ if (error)
+ {
+ g_error_free (error);
+ }
+}
+
+static void
+vfs_file_stop (CajaFile *file,
+ GMountOperation *mount_op,
+ GCancellable *cancellable,
+ CajaFileOperationCallback callback,
+ gpointer callback_data)
+{
+ CajaFileOperation *op;
+ GFile *location;
+
+ op = caja_file_operation_new (file, callback, callback_data);
+ if (cancellable)
+ {
+ g_object_unref (op->cancellable);
+ op->cancellable = g_object_ref (cancellable);
+ }
+
+ location = caja_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)
+{
+ CajaFileOperation *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;
+ }
+
+ caja_file_operation_complete (op, G_FILE (source_object), error);
+ if (error)
+ {
+ g_error_free (error);
+ }
+}
+
+static void
+vfs_file_poll_for_media (CajaFile *file)
+{
+ CajaFileOperation *op;
+ GFile *location;
+
+ op = caja_file_operation_new (file, NULL, NULL);
+
+ location = caja_file_get_location (file);
+ g_file_poll_mountable (location,
+ op->cancellable,
+ vfs_file_poll_callback,
+ op);
+ g_object_unref (location);
+}
+
+static void
+caja_vfs_file_init (gpointer object, gpointer klass)
+{
+ CajaVFSFile *file;
+
+ file = CAJA_VFS_FILE (object);
+
+}
+
+static void
+caja_vfs_file_class_init (gpointer klass)
+{
+ CajaFileClass *file_class;
+
+ file_class = CAJA_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/libcaja-private/caja-vfs-file.h b/libcaja-private/caja-vfs-file.h
new file mode 100644
index 00000000..3f4c18a6
--- /dev/null
+++ b/libcaja-private/caja-vfs-file.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-vfs-file.h: Subclass of CajaFile 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Darin Adler <[email protected]>
+*/
+
+#ifndef CAJA_VFS_FILE_H
+#define CAJA_VFS_FILE_H
+
+#include <libcaja-private/caja-file.h>
+
+#define CAJA_TYPE_VFS_FILE caja_vfs_file_get_type()
+#define CAJA_VFS_FILE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_VFS_FILE, CajaVFSFile))
+#define CAJA_VFS_FILE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_VFS_FILE, CajaVFSFileClass))
+#define CAJA_IS_VFS_FILE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_VFS_FILE))
+#define CAJA_IS_VFS_FILE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_VFS_FILE))
+#define CAJA_VFS_FILE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_VFS_FILE, CajaVFSFileClass))
+
+typedef struct CajaVFSFileDetails CajaVFSFileDetails;
+
+typedef struct
+{
+ CajaFile parent_slot;
+} CajaVFSFile;
+
+typedef struct
+{
+ CajaFileClass parent_slot;
+} CajaVFSFileClass;
+
+GType caja_vfs_file_get_type (void);
+
+#endif /* CAJA_VFS_FILE_H */
diff --git a/libcaja-private/caja-view-factory.c b/libcaja-private/caja-view-factory.c
new file mode 100644
index 00000000..cfa720d5
--- /dev/null
+++ b/libcaja-private/caja-view-factory.c
@@ -0,0 +1,126 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-view-factory.c: register and create CajaViews
+
+ 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Alexander Larsson <[email protected]>
+*/
+
+#include "caja-view-factory.h"
+
+static GList *registered_views;
+
+void
+caja_view_factory_register (CajaViewInfo *view_info)
+{
+ g_return_if_fail (view_info != NULL);
+ g_return_if_fail (view_info->id != NULL);
+ g_return_if_fail (caja_view_factory_lookup (view_info->id) == NULL);
+
+ registered_views = g_list_append (registered_views, view_info);
+}
+
+const CajaViewInfo *
+caja_view_factory_lookup (const char *id)
+{
+ GList *l;
+ CajaViewInfo *view_info;
+
+ g_return_val_if_fail (id != NULL, NULL);
+
+
+ for (l = registered_views; l != NULL; l = l->next)
+ {
+ view_info = l->data;
+
+ if (strcmp (view_info->id, id) == 0)
+ {
+ return view_info;
+ }
+ }
+ return NULL;
+}
+
+CajaView *
+caja_view_factory_create (const char *id,
+ CajaWindowSlotInfo *slot)
+{
+ const CajaViewInfo *view_info;
+ CajaView *view;
+
+ view_info = caja_view_factory_lookup (id);
+ if (view_info == NULL)
+ {
+ return NULL;
+ }
+
+ view = view_info->create (slot);
+ if (g_object_is_floating (view))
+ {
+ g_object_ref_sink (view);
+ }
+ return view;
+}
+
+gboolean
+caja_view_factory_view_supports_uri (const char *id,
+ GFile *location,
+ GFileType file_type,
+ const char *mime_type)
+{
+ const CajaViewInfo *view_info;
+ char *uri;
+ gboolean res;
+
+ view_info = caja_view_factory_lookup (id);
+ if (view_info == NULL)
+ {
+ return FALSE;
+ }
+ uri = g_file_get_uri (location);
+ res = view_info->supports_uri (uri, file_type, mime_type);
+ g_free (uri);
+ return res;
+
+}
+
+GList *
+caja_view_factory_get_views_for_uri (const char *uri,
+ GFileType file_type,
+ const char *mime_type)
+{
+ GList *l, *res;
+ const CajaViewInfo *view_info;
+
+ res = NULL;
+
+ for (l = registered_views; l != NULL; l = l->next)
+ {
+ view_info = l->data;
+
+ if (view_info->supports_uri (uri, file_type, mime_type))
+ {
+ res = g_list_prepend (res, g_strdup (view_info->id));
+ }
+ }
+
+ return g_list_reverse (res);
+}
+
+
diff --git a/libcaja-private/caja-view-factory.h b/libcaja-private/caja-view-factory.h
new file mode 100644
index 00000000..2ddec69d
--- /dev/null
+++ b/libcaja-private/caja-view-factory.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-view-factory.h: register and create CajaViews
+
+ 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Alexander Larsson <[email protected]>
+*/
+
+#ifndef CAJA_VIEW_FACTORY_H
+#define CAJA_VIEW_FACTORY_H
+
+#include <string.h>
+
+#include <libcaja-private/caja-view.h>
+#include <libcaja-private/caja-window-slot-info.h>
+#include <gio/gio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+ typedef struct _CajaViewInfo CajaViewInfo;
+
+ struct _CajaViewInfo
+ {
+ char *id;
+ char *view_combo_label; /* Foo View (used in preferences dialog and navigation combo) */
+ char *view_menu_label_with_mnemonic; /* View -> _Foo (this is the "_Foo" part) */
+ char *error_label; /* The foo view encountered an error. */
+ char *startup_error_label; /* The foo view encountered an error while starting up. */
+ char *display_location_label; /* Display this location with the foo view. */
+ CajaView * (*create) (CajaWindowSlotInfo *slot);
+ /* MATECOMPONENTTODO: More args here */
+ gboolean (*supports_uri) (const char *uri,
+ GFileType file_type,
+ const char *mime_type);
+ };
+
+
+ void caja_view_factory_register (CajaViewInfo *view_info);
+ const CajaViewInfo *caja_view_factory_lookup (const char *id);
+ CajaView * caja_view_factory_create (const char *id,
+ CajaWindowSlotInfo *slot);
+ gboolean caja_view_factory_view_supports_uri (const char *id,
+ GFile *location,
+ GFileType file_type,
+ const char *mime_type);
+ GList * caja_view_factory_get_views_for_uri (const char *uri,
+ GFileType file_type,
+ const char *mime_type);
+
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CAJA_VIEW_FACTORY_H */
diff --git a/libcaja-private/caja-view.c b/libcaja-private/caja-view.c
new file mode 100644
index 00000000..03ab1f4d
--- /dev/null
+++ b/libcaja-private/caja-view.c
@@ -0,0 +1,330 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-view.c: Interface for caja views
+
+ 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Alexander Larsson <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-view.h"
+
+enum
+{
+ TITLE_CHANGED,
+ ZOOM_LEVEL_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint caja_view_signals[LAST_SIGNAL] = { 0 };
+
+static void
+caja_view_base_init (gpointer g_class)
+{
+ static gboolean initialized = FALSE;
+
+ if (! initialized)
+ {
+ caja_view_signals[TITLE_CHANGED] =
+ g_signal_new ("title_changed",
+ CAJA_TYPE_VIEW,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaViewIface, title_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ caja_view_signals[ZOOM_LEVEL_CHANGED] =
+ g_signal_new ("zoom_level_changed",
+ CAJA_TYPE_VIEW,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaViewIface, zoom_level_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ initialized = TRUE;
+ }
+}
+
+GType
+caja_view_get_type (void)
+{
+ static GType type = 0;
+
+ if (!type)
+ {
+ const GTypeInfo info =
+ {
+ sizeof (CajaViewIface),
+ caja_view_base_init,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ 0,
+ 0,
+ NULL
+ };
+
+ type = g_type_register_static (G_TYPE_INTERFACE,
+ "CajaView",
+ &info, 0);
+ g_type_interface_add_prerequisite (type, G_TYPE_OBJECT);
+ }
+
+ return type;
+}
+
+const char *
+caja_view_get_view_id (CajaView *view)
+{
+ g_return_val_if_fail (CAJA_IS_VIEW (view), NULL);
+
+ return (* CAJA_VIEW_GET_IFACE (view)->get_view_id) (view);
+}
+
+GtkWidget *
+caja_view_get_widget (CajaView *view)
+{
+ g_return_val_if_fail (CAJA_IS_VIEW (view), NULL);
+
+ return (* CAJA_VIEW_GET_IFACE (view)->get_widget) (view);
+}
+
+void
+caja_view_load_location (CajaView *view,
+ const char *location_uri)
+{
+ g_return_if_fail (CAJA_IS_VIEW (view));
+ g_return_if_fail (location_uri != NULL);
+
+ (* CAJA_VIEW_GET_IFACE (view)->load_location) (view,
+ location_uri);
+}
+
+void
+caja_view_stop_loading (CajaView *view)
+{
+ g_return_if_fail (CAJA_IS_VIEW (view));
+
+ (* CAJA_VIEW_GET_IFACE (view)->stop_loading) (view);
+}
+
+int
+caja_view_get_selection_count (CajaView *view)
+{
+ g_return_val_if_fail (CAJA_IS_VIEW (view), 0);
+
+ return (* CAJA_VIEW_GET_IFACE (view)->get_selection_count) (view);
+}
+
+GList *
+caja_view_get_selection (CajaView *view)
+{
+ g_return_val_if_fail (CAJA_IS_VIEW (view), NULL);
+
+ return (* CAJA_VIEW_GET_IFACE (view)->get_selection) (view);
+}
+
+void
+caja_view_set_selection (CajaView *view,
+ GList *list)
+{
+ g_return_if_fail (CAJA_IS_VIEW (view));
+
+ (* CAJA_VIEW_GET_IFACE (view)->set_selection) (view,
+ list);
+}
+
+void
+caja_view_set_is_active (CajaView *view,
+ gboolean is_active)
+{
+ g_return_if_fail (CAJA_IS_VIEW (view));
+
+ (* CAJA_VIEW_GET_IFACE (view)->set_is_active) (view,
+ is_active);
+}
+
+void
+caja_view_invert_selection (CajaView *view)
+{
+ g_return_if_fail (CAJA_IS_VIEW (view));
+
+ (* CAJA_VIEW_GET_IFACE (view)->invert_selection) (view);
+}
+
+char *
+caja_view_get_first_visible_file (CajaView *view)
+{
+ g_return_val_if_fail (CAJA_IS_VIEW (view), NULL);
+
+ return (* CAJA_VIEW_GET_IFACE (view)->get_first_visible_file) (view);
+}
+
+void
+caja_view_scroll_to_file (CajaView *view,
+ const char *uri)
+{
+ g_return_if_fail (CAJA_IS_VIEW (view));
+
+ (* CAJA_VIEW_GET_IFACE (view)->scroll_to_file) (view, uri);
+}
+
+char *
+caja_view_get_title (CajaView *view)
+{
+ g_return_val_if_fail (CAJA_IS_VIEW (view), NULL);
+
+ if (CAJA_VIEW_GET_IFACE (view)->get_title != NULL)
+ {
+ return (* CAJA_VIEW_GET_IFACE (view)->get_title) (view);
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+
+gboolean
+caja_view_supports_zooming (CajaView *view)
+{
+ g_return_val_if_fail (CAJA_IS_VIEW (view), FALSE);
+
+ return (* CAJA_VIEW_GET_IFACE (view)->supports_zooming) (view);
+}
+
+void
+caja_view_bump_zoom_level (CajaView *view,
+ int zoom_increment)
+{
+ g_return_if_fail (CAJA_IS_VIEW (view));
+
+ (* CAJA_VIEW_GET_IFACE (view)->bump_zoom_level) (view,
+ zoom_increment);
+}
+
+void
+caja_view_zoom_to_level (CajaView *view,
+ CajaZoomLevel level)
+{
+ g_return_if_fail (CAJA_IS_VIEW (view));
+
+ (* CAJA_VIEW_GET_IFACE (view)->zoom_to_level) (view,
+ level);
+}
+
+void
+caja_view_restore_default_zoom_level (CajaView *view)
+{
+ g_return_if_fail (CAJA_IS_VIEW (view));
+
+ (* CAJA_VIEW_GET_IFACE (view)->restore_default_zoom_level) (view);
+}
+
+gboolean
+caja_view_can_zoom_in (CajaView *view)
+{
+ g_return_val_if_fail (CAJA_IS_VIEW (view), FALSE);
+
+ return (* CAJA_VIEW_GET_IFACE (view)->can_zoom_in) (view);
+}
+
+gboolean
+caja_view_can_zoom_out (CajaView *view)
+{
+ g_return_val_if_fail (CAJA_IS_VIEW (view), FALSE);
+
+ return (* CAJA_VIEW_GET_IFACE (view)->can_zoom_out) (view);
+}
+
+CajaZoomLevel
+caja_view_get_zoom_level (CajaView *view)
+{
+ g_return_val_if_fail (CAJA_IS_VIEW (view), CAJA_ZOOM_LEVEL_STANDARD);
+
+ return (* CAJA_VIEW_GET_IFACE (view)->get_zoom_level) (view);
+}
+
+void
+caja_view_grab_focus (CajaView *view)
+{
+ g_return_if_fail (CAJA_IS_VIEW (view));
+
+ if (CAJA_VIEW_GET_IFACE (view)->grab_focus != NULL)
+ {
+ (* CAJA_VIEW_GET_IFACE (view)->grab_focus) (view);
+ }
+}
+
+void
+caja_view_update_menus (CajaView *view)
+{
+ g_return_if_fail (CAJA_IS_VIEW (view));
+
+ if (CAJA_VIEW_GET_IFACE (view)->update_menus != NULL)
+ {
+ (* CAJA_VIEW_GET_IFACE (view)->update_menus) (view);
+ }
+}
+
+void
+caja_view_pop_up_location_context_menu (CajaView *view,
+ GdkEventButton *event,
+ const char *location)
+{
+ g_return_if_fail (CAJA_IS_VIEW (view));
+
+ if (CAJA_VIEW_GET_IFACE (view)->pop_up_location_context_menu != NULL)
+ {
+ (* CAJA_VIEW_GET_IFACE (view)->pop_up_location_context_menu) (view, event, location);
+ }
+}
+
+void
+caja_view_drop_proxy_received_uris (CajaView *view,
+ GList *uris,
+ const char *target_location,
+ GdkDragAction action)
+{
+ g_return_if_fail (CAJA_IS_VIEW (view));
+
+ if (CAJA_VIEW_GET_IFACE (view)->drop_proxy_received_uris != NULL)
+ {
+ (* CAJA_VIEW_GET_IFACE (view)->drop_proxy_received_uris) (view, uris, target_location, action);
+ }
+}
+
+void
+caja_view_drop_proxy_received_netscape_url (CajaView *view,
+ const char *source_url,
+ const char *target_location,
+ GdkDragAction action)
+{
+ g_return_if_fail (CAJA_IS_VIEW (view));
+
+ if (CAJA_VIEW_GET_IFACE (view)->drop_proxy_received_netscape_url != NULL)
+ {
+ (* CAJA_VIEW_GET_IFACE (view)->drop_proxy_received_netscape_url) (view, source_url, target_location, action);
+ }
+}
+
+
diff --git a/libcaja-private/caja-view.h b/libcaja-private/caja-view.h
new file mode 100644
index 00000000..a7973fa4
--- /dev/null
+++ b/libcaja-private/caja-view.h
@@ -0,0 +1,193 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-view.h: Interface for caja views
+
+ 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Alexander Larsson <[email protected]>
+*/
+
+#ifndef CAJA_VIEW_H
+#define CAJA_VIEW_H
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+/* For CajaZoomLevel */
+#include <libcaja-private/caja-icon-info.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define CAJA_TYPE_VIEW (caja_view_get_type ())
+#define CAJA_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_VIEW, CajaView))
+#define CAJA_IS_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_VIEW))
+#define CAJA_VIEW_GET_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), CAJA_TYPE_VIEW, CajaViewIface))
+
+
+ typedef struct _CajaView CajaView; /* dummy typedef */
+ typedef struct _CajaViewIface CajaViewIface;
+
+ struct _CajaViewIface
+ {
+ GTypeInterface g_iface;
+
+ /* Signals: */
+
+ /* emitted when the view-specific title as returned by get_title changes */
+ void (* title_changed) (CajaView *view);
+
+ void (* zoom_level_changed) (CajaView *view);
+
+ /* VTable: */
+
+ /* Get the id string for this view. Its a constant string, not memory managed */
+ const char * (* get_view_id) (CajaView *view);
+
+ /* Get the widget for this view, can be the same object or a different
+ object owned by the view. Doesn't ref the widget. */
+ GtkWidget * (* get_widget) (CajaView *view);
+
+ /* Called to tell the view to start loading a location, or to reload it.
+ The view responds with a load_underway as soon as it starts loading,
+ and a load_complete when the location is completely read. */
+ void (* load_location) (CajaView *view,
+ const char *location_uri);
+
+ /* Called to tell the view to stop loading the location its currently loading */
+ void (* stop_loading) (CajaView *view);
+
+ /* Returns the number of selected items in the view */
+ int (* get_selection_count) (CajaView *view);
+
+ /* Returns a list of uris for th selected items in the view, caller frees it */
+ GList * (* get_selection) (CajaView *view);
+
+ /* This is called when the window wants to change the selection in the view */
+ void (* set_selection) (CajaView *view,
+ GList *list);
+
+ /* Inverts the selection in the view */
+ void (* invert_selection) (CajaView *view);
+
+ /* Return the uri of the first visible file */
+ char * (* get_first_visible_file) (CajaView *view);
+ /* Scroll the view so that the file specified by the uri is at the top
+ of the view */
+ void (* scroll_to_file) (CajaView *view,
+ const char *uri);
+
+ /* This function can supply a special window title, if you don't want one
+ have this function return NULL, or just don't supply a function */
+ char * (* get_title) (CajaView *view);
+
+
+ /* Zoom support */
+ gboolean (* supports_zooming) (CajaView *view);
+ void (* bump_zoom_level) (CajaView *view,
+ int zoom_increment);
+ void (* zoom_to_level) (CajaView *view,
+ CajaZoomLevel level);
+ CajaZoomLevel (* get_zoom_level) (CajaView *view);
+ void (* restore_default_zoom_level) (CajaView *view);
+ gboolean (* can_zoom_in) (CajaView *view);
+ gboolean (* can_zoom_out) (CajaView *view);
+
+ void (* grab_focus) (CajaView *view);
+ void (* update_menus) (CajaView *view);
+
+ /* Request popup of context menu referring to the open location.
+ * This is triggered in spatial windows by right-clicking the location button,
+ * in navigational windows by right-clicking the "Location:" label in the
+ * navigation bar or any of the buttons in the pathbar.
+ * The location parameter specifies the location this popup should be displayed for.
+ * If it is NULL, the currently displayed location should be used.
+ * The view may display the popup synchronously, asynchronously
+ * or not react to the popup request at all. */
+ void (* pop_up_location_context_menu) (CajaView *view,
+ GdkEventButton *event,
+ const char *location);
+
+ void (* drop_proxy_received_uris) (CajaView *view,
+ GList *uris,
+ const char *target_location,
+ GdkDragAction action);
+ void (* drop_proxy_received_netscape_url) (CajaView *view,
+ const char *source_url,
+ const char *target_location,
+ GdkDragAction action);
+ void (* set_is_active) (CajaView *view,
+ gboolean is_active);
+
+ /* Padding for future expansion */
+ void (*_reserved1) (void);
+ void (*_reserved2) (void);
+ void (*_reserved3) (void);
+ void (*_reserved4) (void);
+ void (*_reserved5) (void);
+ void (*_reserved6) (void);
+ void (*_reserved7) (void);
+ };
+
+ GType caja_view_get_type (void);
+
+ const char * caja_view_get_view_id (CajaView *view);
+ GtkWidget * caja_view_get_widget (CajaView *view);
+ void caja_view_load_location (CajaView *view,
+ const char *location_uri);
+ void caja_view_stop_loading (CajaView *view);
+ int caja_view_get_selection_count (CajaView *view);
+ GList * caja_view_get_selection (CajaView *view);
+ void caja_view_set_selection (CajaView *view,
+ GList *list);
+ void caja_view_invert_selection (CajaView *view);
+ char * caja_view_get_first_visible_file (CajaView *view);
+ void caja_view_scroll_to_file (CajaView *view,
+ const char *uri);
+ char * caja_view_get_title (CajaView *view);
+ gboolean caja_view_supports_zooming (CajaView *view);
+ void caja_view_bump_zoom_level (CajaView *view,
+ int zoom_increment);
+ void caja_view_zoom_to_level (CajaView *view,
+ CajaZoomLevel level);
+ void caja_view_restore_default_zoom_level (CajaView *view);
+ gboolean caja_view_can_zoom_in (CajaView *view);
+ gboolean caja_view_can_zoom_out (CajaView *view);
+ CajaZoomLevel caja_view_get_zoom_level (CajaView *view);
+ void caja_view_pop_up_location_context_menu (CajaView *view,
+ GdkEventButton *event,
+ const char *location);
+ void caja_view_grab_focus (CajaView *view);
+ void caja_view_update_menus (CajaView *view);
+ void caja_view_drop_proxy_received_uris (CajaView *view,
+ GList *uris,
+ const char *target_location,
+ GdkDragAction action);
+ void caja_view_drop_proxy_received_netscape_url (CajaView *view,
+ const char *source_url,
+ const char *target_location,
+ GdkDragAction action);
+ void caja_view_set_is_active (CajaView *view,
+ gboolean is_active);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CAJA_VIEW_H */
diff --git a/libcaja-private/caja-window-info.c b/libcaja-private/caja-window-info.c
new file mode 100644
index 00000000..1d23e409
--- /dev/null
+++ b/libcaja-private/caja-window-info.c
@@ -0,0 +1,291 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-window-info.c: Interface for caja window
+
+ 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Alexander Larsson <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-window-info.h"
+
+enum
+{
+ LOADING_URI,
+ SELECTION_CHANGED,
+ TITLE_CHANGED,
+ HIDDEN_FILES_MODE_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint caja_window_info_signals[LAST_SIGNAL] = { 0 };
+
+static void
+caja_window_info_base_init (gpointer g_class)
+{
+ static gboolean initialized = FALSE;
+
+ if (! initialized)
+ {
+ caja_window_info_signals[LOADING_URI] =
+ g_signal_new ("loading_uri",
+ CAJA_TYPE_WINDOW_INFO,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaWindowInfoIface, loading_uri),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE, 1,
+ G_TYPE_STRING);
+
+ caja_window_info_signals[SELECTION_CHANGED] =
+ g_signal_new ("selection_changed",
+ CAJA_TYPE_WINDOW_INFO,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaWindowInfoIface, selection_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ caja_window_info_signals[TITLE_CHANGED] =
+ g_signal_new ("title_changed",
+ CAJA_TYPE_WINDOW_INFO,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaWindowInfoIface, title_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE, 1,
+ G_TYPE_STRING);
+
+ caja_window_info_signals[HIDDEN_FILES_MODE_CHANGED] =
+ g_signal_new ("hidden_files_mode_changed",
+ CAJA_TYPE_WINDOW_INFO,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaWindowInfoIface, hidden_files_mode_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ initialized = TRUE;
+ }
+}
+
+GType
+caja_window_info_get_type (void)
+{
+ static GType type = 0;
+
+ if (!type)
+ {
+ const GTypeInfo info =
+ {
+ sizeof (CajaWindowInfoIface),
+ caja_window_info_base_init,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ 0,
+ 0,
+ NULL
+ };
+
+ type = g_type_register_static (G_TYPE_INTERFACE,
+ "CajaWindowInfo",
+ &info, 0);
+ g_type_interface_add_prerequisite (type, G_TYPE_OBJECT);
+ }
+
+ return type;
+}
+
+void
+caja_window_info_report_load_underway (CajaWindowInfo *window,
+ CajaView *view)
+{
+ g_return_if_fail (CAJA_IS_WINDOW_INFO (window));
+ g_return_if_fail (CAJA_IS_VIEW (view));
+
+ (* CAJA_WINDOW_INFO_GET_IFACE (window)->report_load_underway) (window,
+ view);
+}
+
+void
+caja_window_info_report_load_complete (CajaWindowInfo *window,
+ CajaView *view)
+{
+ g_return_if_fail (CAJA_IS_WINDOW_INFO (window));
+ g_return_if_fail (CAJA_IS_VIEW (view));
+
+ (* CAJA_WINDOW_INFO_GET_IFACE (window)->report_load_complete) (window,
+ view);
+}
+
+void
+caja_window_info_report_view_failed (CajaWindowInfo *window,
+ CajaView *view)
+{
+ g_return_if_fail (CAJA_IS_WINDOW_INFO (window));
+ g_return_if_fail (CAJA_IS_VIEW (view));
+
+ (* CAJA_WINDOW_INFO_GET_IFACE (window)->report_view_failed) (window,
+ view);
+}
+
+void
+caja_window_info_report_selection_changed (CajaWindowInfo *window)
+{
+ g_return_if_fail (CAJA_IS_WINDOW_INFO (window));
+
+ (* CAJA_WINDOW_INFO_GET_IFACE (window)->report_selection_changed) (window);
+}
+
+void
+caja_window_info_view_visible (CajaWindowInfo *window,
+ CajaView *view)
+{
+ g_return_if_fail (CAJA_IS_WINDOW_INFO (window));
+
+ (* CAJA_WINDOW_INFO_GET_IFACE (window)->view_visible) (window, view);
+}
+
+void
+caja_window_info_close (CajaWindowInfo *window)
+{
+ g_return_if_fail (CAJA_IS_WINDOW_INFO (window));
+
+ (* CAJA_WINDOW_INFO_GET_IFACE (window)->close_window) (window);
+}
+
+void
+caja_window_info_push_status (CajaWindowInfo *window,
+ const char *status)
+{
+ g_return_if_fail (CAJA_IS_WINDOW_INFO (window));
+
+ (* CAJA_WINDOW_INFO_GET_IFACE (window)->push_status) (window,
+ status);
+}
+
+CajaWindowType
+caja_window_info_get_window_type (CajaWindowInfo *window)
+{
+ g_return_val_if_fail (CAJA_IS_WINDOW_INFO (window), CAJA_WINDOW_SPATIAL);
+
+ return (* CAJA_WINDOW_INFO_GET_IFACE (window)->get_window_type) (window);
+}
+
+char *
+caja_window_info_get_title (CajaWindowInfo *window)
+{
+ g_return_val_if_fail (CAJA_IS_WINDOW_INFO (window), NULL);
+
+ return (* CAJA_WINDOW_INFO_GET_IFACE (window)->get_title) (window);
+}
+
+GList *
+caja_window_info_get_history (CajaWindowInfo *window)
+{
+ g_return_val_if_fail (CAJA_IS_WINDOW_INFO (window), NULL);
+
+ return (* CAJA_WINDOW_INFO_GET_IFACE (window)->get_history) (window);
+}
+
+char *
+caja_window_info_get_current_location (CajaWindowInfo *window)
+{
+ g_return_val_if_fail (CAJA_IS_WINDOW_INFO (window), NULL);
+
+ return (* CAJA_WINDOW_INFO_GET_IFACE (window)->get_current_location) (window);
+}
+
+int
+caja_window_info_get_selection_count (CajaWindowInfo *window)
+{
+ g_return_val_if_fail (CAJA_IS_WINDOW_INFO (window), 0);
+
+ return (* CAJA_WINDOW_INFO_GET_IFACE (window)->get_selection_count) (window);
+}
+
+GList *
+caja_window_info_get_selection (CajaWindowInfo *window)
+{
+ g_return_val_if_fail (CAJA_IS_WINDOW_INFO (window), NULL);
+
+ return (* CAJA_WINDOW_INFO_GET_IFACE (window)->get_selection) (window);
+}
+
+CajaWindowShowHiddenFilesMode
+caja_window_info_get_hidden_files_mode (CajaWindowInfo *window)
+{
+ g_return_val_if_fail (CAJA_IS_WINDOW_INFO (window), CAJA_WINDOW_SHOW_HIDDEN_FILES_DEFAULT);
+
+ return (* CAJA_WINDOW_INFO_GET_IFACE (window)->get_hidden_files_mode) (window);
+}
+
+void
+caja_window_info_set_hidden_files_mode (CajaWindowInfo *window,
+ CajaWindowShowHiddenFilesMode mode)
+{
+ g_return_if_fail (CAJA_IS_WINDOW_INFO (window));
+
+ (* CAJA_WINDOW_INFO_GET_IFACE (window)->set_hidden_files_mode) (window,
+ mode);
+}
+
+GtkUIManager *
+caja_window_info_get_ui_manager (CajaWindowInfo *window)
+{
+ g_return_val_if_fail (CAJA_IS_WINDOW_INFO (window), NULL);
+
+ return (* CAJA_WINDOW_INFO_GET_IFACE (window)->get_ui_manager) (window);
+}
+
+CajaWindowSlotInfo *
+caja_window_info_get_active_slot (CajaWindowInfo *window)
+{
+ g_return_val_if_fail (CAJA_IS_WINDOW_INFO (window), NULL);
+
+ return (* CAJA_WINDOW_INFO_GET_IFACE (window)->get_active_slot) (window);
+}
+
+CajaWindowSlotInfo *
+caja_window_info_get_extra_slot (CajaWindowInfo *window)
+{
+ g_return_val_if_fail (CAJA_IS_WINDOW_INFO (window), NULL);
+
+ return (* CAJA_WINDOW_INFO_GET_IFACE (window)->get_extra_slot) (window);
+}
+
+gboolean
+caja_window_info_get_initiated_unmount (CajaWindowInfo *window)
+{
+ g_return_val_if_fail (CAJA_IS_WINDOW_INFO (window), FALSE);
+
+ return (* CAJA_WINDOW_INFO_GET_IFACE (window)->get_initiated_unmount) (window);
+}
+
+void
+caja_window_info_set_initiated_unmount (CajaWindowInfo *window, gboolean initiated_unmount)
+{
+ g_return_if_fail (CAJA_IS_WINDOW_INFO (window));
+
+ (* CAJA_WINDOW_INFO_GET_IFACE (window)->set_initiated_unmount) (window,
+ initiated_unmount);
+
+}
diff --git a/libcaja-private/caja-window-info.h b/libcaja-private/caja-window-info.h
new file mode 100644
index 00000000..8852ad7b
--- /dev/null
+++ b/libcaja-private/caja-window-info.h
@@ -0,0 +1,189 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-window-info.h: Interface for caja windows
+
+ 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, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Alexander Larsson <[email protected]>
+*/
+
+#ifndef CAJA_WINDOW_INFO_H
+#define CAJA_WINDOW_INFO_H
+
+#include <glib-object.h>
+#include <libcaja-private/caja-view.h>
+#include <gtk/gtk.h>
+#include "../src/caja-bookmark-list.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+ typedef enum
+ {
+ CAJA_WINDOW_SHOW_HIDDEN_FILES_DEFAULT,
+ CAJA_WINDOW_SHOW_HIDDEN_FILES_ENABLE,
+ CAJA_WINDOW_SHOW_HIDDEN_FILES_DISABLE
+ }
+ CajaWindowShowHiddenFilesMode;
+
+
+ typedef enum
+ {
+ CAJA_WINDOW_OPEN_ACCORDING_TO_MODE,
+ CAJA_WINDOW_OPEN_IN_SPATIAL,
+ CAJA_WINDOW_OPEN_IN_NAVIGATION
+ } CajaWindowOpenMode;
+
+ typedef enum
+ {
+ /* used in spatial mode */
+ CAJA_WINDOW_OPEN_FLAG_CLOSE_BEHIND = 1<<0,
+ /* used in navigation mode */
+ CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW = 1<<1,
+ CAJA_WINDOW_OPEN_FLAG_NEW_TAB = 1<<2
+ } CajaWindowOpenFlags;
+
+ typedef enum
+ {
+ CAJA_WINDOW_SPATIAL,
+ CAJA_WINDOW_NAVIGATION,
+ CAJA_WINDOW_DESKTOP
+ } CajaWindowType;
+
+#define CAJA_TYPE_WINDOW_INFO (caja_window_info_get_type ())
+#define CAJA_WINDOW_INFO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_WINDOW_INFO, CajaWindowInfo))
+#define CAJA_IS_WINDOW_INFO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_WINDOW_INFO))
+#define CAJA_WINDOW_INFO_GET_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), CAJA_TYPE_WINDOW_INFO, CajaWindowInfoIface))
+
+#ifndef CAJA_WINDOW_DEFINED
+#define CAJA_WINDOW_DEFINED
+ /* Using CajaWindow for the vtable to make implementing this in
+ * CajaWindow easier */
+ typedef struct CajaWindow CajaWindow;
+#endif
+
+#ifndef CAJA_WINDOW_SLOT_DEFINED
+#define CAJA_WINDOW_SLOT_DEFINED
+ typedef struct CajaWindowSlot CajaWindowSlot;
+#endif
+
+
+ typedef CajaWindowSlot CajaWindowSlotInfo;
+ typedef CajaWindow CajaWindowInfo;
+
+ typedef struct _CajaWindowInfoIface CajaWindowInfoIface;
+
+ struct _CajaWindowInfoIface
+ {
+ GTypeInterface g_iface;
+
+ /* signals: */
+
+ void (* loading_uri) (CajaWindowInfo *window,
+ const char *uri);
+ /* Emitted when the view in the window changes the selection */
+ void (* selection_changed) (CajaWindowInfo *window);
+ void (* title_changed) (CajaWindowInfo *window,
+ const char *title);
+ void (* hidden_files_mode_changed)(CajaWindowInfo *window);
+
+ /* VTable: */
+ /* A view calls this once after a load_location, once it starts loading the
+ * directory. Might be called directly, or later on the mainloop.
+ * This can also be called at any other time if the view needs to
+ * re-load the location. But the view needs to call load_complete first if
+ * its currently loading. */
+ void (* report_load_underway) (CajaWindowInfo *window,
+ CajaView *view);
+ /* A view calls this once after reporting load_underway, when the location
+ has been fully loaded, or when the load was stopped
+ (by an error or by the user). */
+ void (* report_load_complete) (CajaWindowInfo *window,
+ CajaView *view);
+ /* This can be called at any time when there has been a catastrophic failure of
+ the view. It will result in the view being removed. */
+ void (* report_view_failed) (CajaWindowInfo *window,
+ CajaView *view);
+ void (* report_selection_changed) (CajaWindowInfo *window);
+
+ /* Returns the number of selected items in the view */
+ int (* get_selection_count) (CajaWindowInfo *window);
+
+ /* Returns a list of uris for th selected items in the view, caller frees it */
+ GList *(* get_selection) (CajaWindowInfo *window);
+
+ char * (* get_current_location) (CajaWindowInfo *window);
+ void (* push_status) (CajaWindowInfo *window,
+ const char *status);
+ char * (* get_title) (CajaWindowInfo *window);
+ GList *(* get_history) (CajaWindowInfo *window);
+ CajaWindowType
+ (* get_window_type) (CajaWindowInfo *window);
+ CajaWindowShowHiddenFilesMode
+ (* get_hidden_files_mode) (CajaWindowInfo *window);
+ void (* set_hidden_files_mode) (CajaWindowInfo *window,
+ CajaWindowShowHiddenFilesMode mode);
+
+ CajaWindowSlotInfo * (* get_active_slot) (CajaWindowInfo *window);
+ CajaWindowSlotInfo * (* get_extra_slot) (CajaWindowInfo *window);
+
+ gboolean (* get_initiated_unmount) (CajaWindowInfo *window);
+ void (* set_initiated_unmount) (CajaWindowInfo *window,
+ gboolean initiated_unmount);
+
+ void (* view_visible) (CajaWindowInfo *window,
+ CajaView *view);
+ void (* close_window) (CajaWindowInfo *window);
+ GtkUIManager * (* get_ui_manager) (CajaWindowInfo *window);
+ };
+
+ GType caja_window_info_get_type (void);
+ void caja_window_info_report_load_underway (CajaWindowInfo *window,
+ CajaView *view);
+ void caja_window_info_report_load_complete (CajaWindowInfo *window,
+ CajaView *view);
+ void caja_window_info_report_view_failed (CajaWindowInfo *window,
+ CajaView *view);
+ void caja_window_info_report_selection_changed (CajaWindowInfo *window);
+ CajaWindowSlotInfo * caja_window_info_get_active_slot (CajaWindowInfo *window);
+ CajaWindowSlotInfo * caja_window_info_get_extra_slot (CajaWindowInfo *window);
+ void caja_window_info_view_visible (CajaWindowInfo *window,
+ CajaView *view);
+ void caja_window_info_close (CajaWindowInfo *window);
+ void caja_window_info_push_status (CajaWindowInfo *window,
+ const char *status);
+ CajaWindowType caja_window_info_get_window_type (CajaWindowInfo *window);
+ char * caja_window_info_get_title (CajaWindowInfo *window);
+ GList * caja_window_info_get_history (CajaWindowInfo *window);
+ char * caja_window_info_get_current_location (CajaWindowInfo *window);
+ int caja_window_info_get_selection_count (CajaWindowInfo *window);
+ GList * caja_window_info_get_selection (CajaWindowInfo *window);
+ CajaWindowShowHiddenFilesMode caja_window_info_get_hidden_files_mode (CajaWindowInfo *window);
+ void caja_window_info_set_hidden_files_mode (CajaWindowInfo *window,
+ CajaWindowShowHiddenFilesMode mode);
+ gboolean caja_window_info_get_initiated_unmount (CajaWindowInfo *window);
+ void caja_window_info_set_initiated_unmount (CajaWindowInfo *window,
+ gboolean initiated_unmount);
+ GtkUIManager * caja_window_info_get_ui_manager (CajaWindowInfo *window);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CAJA_WINDOW_INFO_H */
diff --git a/libcaja-private/caja-window-slot-info.c b/libcaja-private/caja-window-slot-info.c
new file mode 100644
index 00000000..902530ee
--- /dev/null
+++ b/libcaja-private/caja-window-slot-info.c
@@ -0,0 +1,157 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-window-slot-info.c: Interface for caja window slots
+
+ Copyright (C) 2008 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with this program; if not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Christian Neumair <[email protected]>
+*/
+#include "caja-window-slot-info.h"
+
+enum
+{
+ ACTIVE,
+ INACTIVE,
+ LAST_SIGNAL
+};
+
+static guint caja_window_slot_info_signals[LAST_SIGNAL] = { 0 };
+
+static void
+caja_window_slot_info_base_init (gpointer g_class)
+{
+ static gboolean initialized = FALSE;
+
+ if (!initialized)
+ {
+ caja_window_slot_info_signals[ACTIVE] =
+ g_signal_new ("active",
+ CAJA_TYPE_WINDOW_SLOT_INFO,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaWindowSlotInfoIface, active),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ caja_window_slot_info_signals[INACTIVE] =
+ g_signal_new ("inactive",
+ CAJA_TYPE_WINDOW_SLOT_INFO,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaWindowSlotInfoIface, inactive),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ initialized = TRUE;
+ }
+}
+
+GType
+caja_window_slot_info_get_type (void)
+{
+ static GType type = 0;
+
+ if (!type)
+ {
+ const GTypeInfo info =
+ {
+ sizeof (CajaWindowSlotInfoIface),
+ caja_window_slot_info_base_init,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ 0,
+ 0,
+ NULL
+ };
+
+ type = g_type_register_static (G_TYPE_INTERFACE,
+ "CajaWindowSlotInfo",
+ &info, 0);
+ g_type_interface_add_prerequisite (type, G_TYPE_OBJECT);
+ }
+
+ return type;
+}
+
+void
+caja_window_slot_info_set_status (CajaWindowSlotInfo *slot,
+ const char *status)
+{
+ g_assert (CAJA_IS_WINDOW_SLOT_INFO (slot));
+
+ (* CAJA_WINDOW_SLOT_INFO_GET_IFACE (slot)->set_status) (slot,
+ status);
+}
+
+void
+caja_window_slot_info_make_hosting_pane_active (CajaWindowSlotInfo *slot)
+{
+ g_assert (CAJA_IS_WINDOW_SLOT_INFO (slot));
+ (* CAJA_WINDOW_SLOT_INFO_GET_IFACE (slot)->make_hosting_pane_active) (slot);
+}
+
+void
+caja_window_slot_info_open_location (CajaWindowSlotInfo *slot,
+ GFile *location,
+ CajaWindowOpenMode mode,
+ CajaWindowOpenFlags flags,
+ GList *selection)
+{
+ g_assert (CAJA_IS_WINDOW_SLOT_INFO (slot));
+
+ (* CAJA_WINDOW_SLOT_INFO_GET_IFACE (slot)->open_location) (slot,
+ location,
+ mode,
+ flags,
+ selection);
+}
+
+char *
+caja_window_slot_info_get_title (CajaWindowSlotInfo *slot)
+{
+ g_assert (CAJA_IS_WINDOW_SLOT_INFO (slot));
+
+ return (* CAJA_WINDOW_SLOT_INFO_GET_IFACE (slot)->get_title) (slot);
+}
+
+char *
+caja_window_slot_info_get_current_location (CajaWindowSlotInfo *slot)
+{
+ g_assert (CAJA_IS_WINDOW_SLOT_INFO (slot));
+
+ return (* CAJA_WINDOW_SLOT_INFO_GET_IFACE (slot)->get_current_location) (slot);
+}
+
+CajaView *
+caja_window_slot_info_get_current_view (CajaWindowSlotInfo *slot)
+{
+ g_assert (CAJA_IS_WINDOW_SLOT_INFO (slot));
+
+ return (* CAJA_WINDOW_SLOT_INFO_GET_IFACE (slot)->get_current_view) (slot);
+}
+
+CajaWindowInfo *
+caja_window_slot_info_get_window (CajaWindowSlotInfo *slot)
+{
+ g_assert (CAJA_IS_WINDOW_SLOT_INFO (slot));
+
+ return (* CAJA_WINDOW_SLOT_INFO_GET_IFACE (slot)->get_window) (slot);
+}
+
diff --git a/libcaja-private/caja-window-slot-info.h b/libcaja-private/caja-window-slot-info.h
new file mode 100644
index 00000000..0a158ad7
--- /dev/null
+++ b/libcaja-private/caja-window-slot-info.h
@@ -0,0 +1,97 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-window-slot-info.h: Interface for caja window slots
+
+ Copyright (C) 2008 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with this program; if not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Christian Neumair <[email protected]>
+*/
+
+#ifndef CAJA_WINDOW_SLOT_INFO_H
+#define CAJA_WINDOW_SLOT_INFO_H
+
+#include "caja-window-info.h"
+#include "caja-view.h"
+
+
+#define CAJA_TYPE_WINDOW_SLOT_INFO (caja_window_slot_info_get_type ())
+#define CAJA_WINDOW_SLOT_INFO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_WINDOW_SLOT_INFO, CajaWindowSlotInfo))
+#define CAJA_IS_WINDOW_SLOT_INFO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_WINDOW_SLOT_INFO))
+#define CAJA_WINDOW_SLOT_INFO_GET_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), CAJA_TYPE_WINDOW_SLOT_INFO, CajaWindowSlotInfoIface))
+
+typedef struct _CajaWindowSlotInfoIface CajaWindowSlotInfoIface;
+
+struct _CajaWindowSlotInfoIface
+{
+ GTypeInterface g_iface;
+
+ /* signals */
+
+ /* emitted right after this slot becomes active.
+ * Views should connect to this signal and merge their UI
+ * into the main window.
+ */
+ void (* active) (CajaWindowSlotInfo *slot);
+ /* emitted right before this slot becomes inactive.
+ * Views should connect to this signal and unmerge their UI
+ * from the main window.
+ */
+ void (* inactive) (CajaWindowSlotInfo *slot);
+
+ /* returns the window info associated with this slot */
+ CajaWindowInfo * (* get_window) (CajaWindowSlotInfo *slot);
+
+ /* Returns the number of selected items in the view */
+ int (* get_selection_count) (CajaWindowSlotInfo *slot);
+
+ /* Returns a list of uris for th selected items in the view, caller frees it */
+ GList *(* get_selection) (CajaWindowSlotInfo *slot);
+
+ char * (* get_current_location) (CajaWindowSlotInfo *slot);
+ CajaView * (* get_current_view) (CajaWindowSlotInfo *slot);
+ void (* set_status) (CajaWindowSlotInfo *slot,
+ const char *status);
+ char * (* get_title) (CajaWindowSlotInfo *slot);
+
+ void (* open_location) (CajaWindowSlotInfo *slot,
+ GFile *location,
+ CajaWindowOpenMode mode,
+ CajaWindowOpenFlags flags,
+ GList *selection);
+ void (* make_hosting_pane_active) (CajaWindowSlotInfo *slot);
+};
+
+
+GType caja_window_slot_info_get_type (void);
+CajaWindowInfo * caja_window_slot_info_get_window (CajaWindowSlotInfo *slot);
+void caja_window_slot_info_open_location (CajaWindowSlotInfo *slot,
+ GFile *location,
+ CajaWindowOpenMode mode,
+ CajaWindowOpenFlags flags,
+ GList *selection);
+void caja_window_slot_info_set_status (CajaWindowSlotInfo *slot,
+ const char *status);
+void caja_window_slot_info_make_hosting_pane_active (CajaWindowSlotInfo *slot);
+
+char * caja_window_slot_info_get_current_location (CajaWindowSlotInfo *slot);
+CajaView * caja_window_slot_info_get_current_view (CajaWindowSlotInfo *slot);
+int caja_window_slot_info_get_selection_count (CajaWindowSlotInfo *slot);
+GList * caja_window_slot_info_get_selection (CajaWindowSlotInfo *slot);
+char * caja_window_slot_info_get_title (CajaWindowSlotInfo *slot);
+
+#endif /* CAJA_WINDOW_SLOT_INFO_H */