diff options
Diffstat (limited to 'libcaja-private')
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, + >k_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, ×tamp, &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, ¤t); + + 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 *)¶meters->slot_info); + } + if (parameters->parent_window) + { + g_object_remove_weak_pointer (G_OBJECT (parameters->parent_window), (gpointer *)¶meters->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 *)¶meters_install->slot_info); + } + if (parameters_install->parent_window) + { + g_object_remove_weak_pointer (G_OBJECT (parameters_install->parent_window), (gpointer *)¶meters_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 *)¶meters_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 *)¶meters_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 *)¶meters_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 *)¶meters_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, + ¶meters->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, + ¶meters->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, + ¶meters->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 *)¶meters->slot_info); + if (parent_window) + { + parameters->parent_window = parent_window; + g_object_add_weak_pointer (G_OBJECT (parameters->parent_window), (gpointer *)¶meters->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 (¤t_time); + + /* Don't try to create a thumbnail if the file was modified recently. + This prevents constant re-thumbnailing of changing files. */ + if (current_time < current_orig_mtime + THUMBNAIL_CREATION_DELAY_SECS && + current_time >= current_orig_mtime) + { +#ifdef DEBUG_THUMBNAILS + g_message ("(Thumbnail Thread) Skipping: %s\n", + info->image_uri); +#endif + /* Reschedule thumbnailing via a change notification */ + g_timeout_add_seconds (1, thumbnail_thread_notify_file_changed, + g_strdup (info->image_uri)); + continue; + } + + /* Create the thumbnail. */ +#ifdef DEBUG_THUMBNAILS + g_message ("(Thumbnail Thread) Creating thumbnail: %s\n", + info->image_uri); +#endif + + pixbuf = 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 */ |