diff options
Diffstat (limited to 'src/file-manager')
32 files changed, 34284 insertions, 0 deletions
diff --git a/src/file-manager/Makefile.am b/src/file-manager/Makefile.am new file mode 100644 index 00000000..4090fc91 --- /dev/null +++ b/src/file-manager/Makefile.am @@ -0,0 +1,61 @@ +include $(top_srcdir)/Makefile.shared + +noinst_LTLIBRARIES=libcaja-file-manager.la + +INCLUDES = \ + -I$(top_srcdir) \ + -I$(top_srcdir)/cut-n-paste-code \ + $(CORE_CFLAGS) \ + $(WARNING_CFLAGS) \ + -DCAJA_DATADIR=\""$(datadir)/caja"\" \ + -DDATADIR=\""$(datadir)"\" \ + $(DISABLE_DEPRECATED_CFLAGS) \ + $(NULL) + + + +libcaja_file_manager_la_SOURCES = \ + fm-actions.h \ + fm-desktop-icon-view.c \ + fm-desktop-icon-view.h \ + fm-directory-view.c \ + fm-directory-view.h \ + fm-ditem-page.c \ + fm-ditem-page.h \ + fm-error-reporting.c \ + fm-error-reporting.h \ + fm-icon-container.c \ + fm-icon-container.h \ + fm-icon-view.c \ + fm-icon-view.h \ + fm-list-model.c \ + fm-list-model.h \ + fm-list-view-private.h \ + fm-list-view.c \ + fm-list-view.h \ + fm-properties-window.c \ + fm-properties-window.h \ + fm-tree-model.c \ + fm-tree-model.h \ + fm-tree-view.c \ + fm-tree-view.h \ + caja-audio-mime-types.h \ + $(NULL) + +EMPTY_VIEW_SOURCES = \ + fm-empty-view.c \ + fm-empty-view.h + +if ENABLE_EMPTY_VIEW +libcaja_file_manager_la_SOURCES += $(EMPTY_VIEW_SOURCES) +endif + +uidir = $(datadir)/caja/ui +ui_DATA = \ + caja-desktop-icon-view-ui.xml \ + caja-directory-view-ui.xml \ + caja-icon-view-ui.xml \ + caja-list-view-ui.xml \ + $(NULL) + +EXTRA_DIST = $(ui_DATA) diff --git a/src/file-manager/caja-audio-mime-types.h b/src/file-manager/caja-audio-mime-types.h new file mode 100644 index 00000000..74194cc8 --- /dev/null +++ b/src/file-manager/caja-audio-mime-types.h @@ -0,0 +1,46 @@ +/* generated with mime-types-include.sh in the totem module, don't edit or + commit in the caja module without filing a bug against totem */ +static const char* audio_mime_types[] = +{ + "audio/3gpp", + "audio/ac3", + "audio/AMR", + "audio/AMR-WB", + "audio/basic", + "audio/midi", + "audio/mp4", + "audio/mpeg", + "audio/ogg", + "audio/prs.sid", + "audio/vnd.rn-realaudio", + "audio/x-aiff", + "audio/x-ape", + "audio/x-flac", + "audio/x-gsm", + "audio/x-it", + "audio/x-m4a", + "audio/x-matroska", + "audio/x-mod", + "audio/x-mp3", + "audio/x-mpeg", + "audio/x-ms-asf", + "audio/x-ms-asx", + "audio/x-ms-wax", + "audio/x-ms-wma", + "audio/x-musepack", + "audio/x-pn-aiff", + "audio/x-pn-au", + "audio/x-pn-wav", + "audio/x-pn-windows-acm", + "audio/x-realaudio", + "audio/x-real-audio", + "audio/x-sbc", + "audio/x-speex", + "audio/x-tta", + "audio/x-wav", + "audio/x-wavpack", + "audio/x-vorbis", + "audio/x-vorbis+ogg", + "audio/x-xm", + "application/x-flac", +}; diff --git a/src/file-manager/caja-desktop-icon-view-ui.xml b/src/file-manager/caja-desktop-icon-view-ui.xml new file mode 100644 index 00000000..d437801d --- /dev/null +++ b/src/file-manager/caja-desktop-icon-view-ui.xml @@ -0,0 +1,21 @@ +<ui> +<popup name="background"> + <placeholder name="Before Zoom Items"> + <placeholder name="New Window Items"> + </placeholder> + <placeholder name="New Object Items"> + <menuitem name="New Launcher" action="New Launcher Desktop"/> + </placeholder> + </placeholder> + <placeholder name="After Zoom Items"> + <placeholder name="Background Items"> + <menuitem name="Change Background" action="Change Background"/> + </placeholder> + </placeholder> +</popup> +<popup name="selection"> + <placeholder name="Empty Trash Holder"> + <menuitem name="Empty Trash" action="Empty Trash Conditional"/> + </placeholder> +</popup> +</ui> diff --git a/src/file-manager/caja-directory-view-ui.xml b/src/file-manager/caja-directory-view-ui.xml new file mode 100644 index 00000000..80b9e881 --- /dev/null +++ b/src/file-manager/caja-directory-view-ui.xml @@ -0,0 +1,231 @@ +<ui> +<accelerator action="OpenAccel"/> +<accelerator action="OpenCloseParent"/> +<accelerator action="PropertiesAccel"/> +<accelerator action="RenameSelectAll"/> +<menubar name="MenuBar"> + <menu action="File"> + <placeholder name="New Items Placeholder"> + <menuitem name="New Folder" action="New Folder"/> + <menu action="New Documents"> + <menuitem name="No Templates" action="No Templates"/> + <placeholder name="New Documents Placeholder"/> + <separator name="After New Documents"/> + <menuitem name="New Empty File" action="New Empty File"/> + </menu> + <menuitem name="New Launcher" action="New Launcher"/> + </placeholder> + <placeholder name="Open Placeholder"> + <menuitem name="Open" action="Open"/> + <menuitem name="OpenInNewTab" action="OpenInNewTab"/> + <menuitem name="OpenAlternate" action="OpenAlternate"/> + <placeholder name="Applications Placeholder"> + </placeholder> + <menu action="Open With"> + <placeholder name="Applications Placeholder"/> + <separator name="Open With Separator"/> + <menuitem name="OtherApplication" action="OtherApplication1"/> + </menu> + <placeholder name="OtherApplicationPlaceholder"> + <menuitem name="OtherApplication" action="OtherApplication2"/> + </placeholder> + <menu action="Scripts"> + <placeholder name="Scripts Placeholder"/> + <separator name="After Scripts"/> + <menuitem name="Open Scripts Folder" action="Open Scripts Folder"/> + </menu> + </placeholder> + <placeholder name="File Items Placeholder"> + <menuitem name="Self Mount Volume" action="Self Mount Volume"/> + <menuitem name="Self Unmount Volume" action="Self Unmount Volume"/> + <menuitem name="Self Eject Volume" action="Self Eject Volume"/> + <menuitem name="Self Format Volume" action="Self Format Volume"/> + <menuitem name="Self Start Volume" action="Self Start Volume"/> + <menuitem name="Self Stop Volume" action="Self Stop Volume"/> + <menuitem name="Self Poll" action="Self Poll"/> + <separator name="Properties Separator"/> + <menuitem name="Properties" action="Properties"/> + </placeholder> + <placeholder name="Global File Items Placeholder"> + <menuitem name="Empty Trash" action="Empty Trash"/> + <menuitem name="Save Search" action="Save Search"/> + <menuitem name="Save Search As" action="Save Search As"/> + </placeholder> + </menu> + <menu action="Edit"> + <placeholder name="Clipboard Actions"> + <menuitem name="Cut" action="Cut"/> + <menuitem name="Copy" action="Copy"/> + <menuitem name="Paste" action="Paste"/> + </placeholder> + <placeholder name="Select Items"> + <menuitem name="Select All" action="Select All"/> + <menuitem name="Select Pattern" action="Select Pattern"/> + <menuitem name="Invert Selection" action="Invert Selection"/> + </placeholder> + <placeholder name="File Items Placeholder"> + <menuitem name="Duplicate" action="Duplicate"/> + <menuitem name="Create Link" action="Create Link"/> + <menuitem name="Rename" action="Rename"/> + <menu action="CopyToMenu"> + <menuitem name="Copy to next pane" action="Copy to next pane"/> + <menuitem name="Copy to Home" action="Copy to Home"/> + <menuitem name="Copy to Desktop" action="Copy to Desktop"/> + </menu> + <menu action="MoveToMenu"> + <menuitem name="Move to next pane" action="Move to next pane"/> + <menuitem name="Copy to Home" action="Move to Home"/> + <menuitem name="Copy to Desktop" action="Move to Desktop"/> + </menu> + </placeholder> + <placeholder name="Dangerous File Items Placeholder"> + <menuitem name="Trash" action="Trash"/> + <menuitem name="Delete" action="Delete"/> + <menuitem name="Restore From Trash" action="Restore From Trash"/> + </placeholder> + <placeholder name="Extension Actions"/> + </menu> + <menu action="View"> + <placeholder name="View Preferences Placeholder"> + <menuitem name="Reset to Defaults" action="Reset to Defaults"/> + <menuitem name="Show Hidden Files" action="Show Hidden Files"/> + </placeholder> + </menu> +</menubar> +<popup name="background"> + <placeholder name="Before Zoom Items"> + <placeholder name="New Object Items"> + <menuitem name="New Folder" action="New Folder"/> + <menuitem name="New Launcher" action="New Launcher"/> + <menu action="New Documents"> + <menuitem name="No Templates" action="No Templates"/> + <placeholder name="New Documents Placeholder"/> + <separator name="After New Documents"/> + <menuitem name="New Empty File" action="New Empty File"/> + </menu> + <menu action="Scripts"> + <placeholder name="Scripts Placeholder"/> + <separator name="After Scripts"/> + <menuitem name="Open Scripts Folder" action="Open Scripts Folder"/> + </menu> + </placeholder> + <separator name="View items separator"/> + <placeholder name="View Items"/> + <separator name="Clipboard separator"/> + <placeholder name="File Clipboard Actions"> + <menuitem name="Paste" action="Paste"/> + </placeholder> + </placeholder> + + <separator name="Folder Items separator"/> + <placeholder name="Folder Items Placeholder"> + <menuitem name="Self Mount Volume" action="Self Mount Volume"/> + <menuitem name="Self Unmount Volume" action="Self Unmount Volume"/> + <menuitem name="Self Eject Volume" action="Self Eject Volume"/> + <menuitem name="Self Format Volume" action="Self Format Volume"/> + <menuitem name="Self Start Volume" action="Self Start Volume"/> + <menuitem name="Self Stop Volume" action="Self Stop Volume"/> + <menuitem name="Self Poll" action="Self Poll"/> + <separator name="Properties separator"/> + <menuitem name="Properties" action="Properties"/> + </placeholder> + +</popup> +<popup name="selection"> + <placeholder name="Open Placeholder"> + <menuitem name="Open" action="Open"/> + <menuitem name="OpenInNewTab" action="OpenInNewTab"/> + <menuitem name="OpenAlternate" action="OpenAlternate"/> + <menuitem name="OpenFolderWindow" action="OpenFolderWindow"/> + <separator name="applications separator"/> + <placeholder name="Applications Placeholder"/> + <menu action="Open With"> + <placeholder name="Applications Placeholder"/> + <separator name="open with separator"/> + <menuitem name="OtherApplication" action="OtherApplication1"/> + </menu> + <placeholder name="OtherApplicationPlaceholder"> + <menuitem name="OtherApplication2" action="OtherApplication2"/> + </placeholder> + <menu action="Scripts"> + <placeholder name="Scripts Placeholder"/> + <separator name="After Scripts"/> + <menuitem name="Open Scripts Folder" action="Open Scripts Folder"/> + </menu> + </placeholder> + <separator name="Clipboard separator"/> + <placeholder name="File Clipboard Actions"> + <menuitem name="Cut" action="Cut"/> + <menuitem name="Copy" action="Copy"/> + <menuitem name="Paste Files Into" action="Paste Files Into"/> + </placeholder> + <separator name="File actions separator"/> + <placeholder name="File Actions"> + <menuitem name="Create Link" action="Create Link"/> + <menuitem name="Rename" action="Rename"/> + <menu action="CopyToMenu"> + <menuitem name="Copy to next pane" action="Copy to next pane"/> + <menuitem name="Copy to Home" action="Copy to Home"/> + <menuitem name="Copy to Desktop" action="Copy to Desktop"/> + </menu> + <menu action="MoveToMenu"> + <menuitem name="Move to next pane" action="Move to next pane"/> + <menuitem name="Copy to Home" action="Move to Home"/> + <menuitem name="Copy to Desktop" action="Move to Desktop"/> + </menu> + </placeholder> + <separator name="Dangerous separator"/> + <placeholder name="Dangerous File Actions"> + <menuitem name="Trash" action="Trash"/> + <menuitem name="Delete" action="Delete"/> + <menuitem name="Restore From Trash" action="Restore From Trash"/> + </placeholder> + <separator name="Appearance separator"/> + <placeholder name="Icon Appearance Items"> + </placeholder> + <separator name="Extension actions separator"/> + <placeholder name="Extension Actions"/> + <separator name="Removable separator"/> + <placeholder name="Removable Media Placeholder"> + <menuitem name="Mount Volume" action="Mount Volume"/> + <menuitem name="Unmount Volume" action="Unmount Volume"/> + <menuitem name="Eject Volume" action="Eject Volume"/> + <menuitem name="Format Volume" action="Format Volume"/> + <menuitem name="Start Volume" action="Start Volume"/> + <menuitem name="Stop Volume" action="Stop Volume"/> + <menuitem name="Poll" action="Poll"/> + </placeholder> + <menuitem name="Connect To Server Link" action="Connect To Server Link"/> + <separator name="Properties Separator"/> + <menuitem name="Properties" action="Properties"/> +</popup> +<popup name="location"> + <placeholder name="Open Placeholder"> + <menuitem name="LocationOpenInNewTab" action="LocationOpenInNewTab"/> + <menuitem name="LocationOpenAlternate" action="LocationOpenAlternate"/> + <menuitem name="LocationOpenFolderWindow" action="LocationOpenFolderWindow"/> + </placeholder> + <separator name="Location After Open Separator"/> + <placeholder name="Clipboard Actions"> + <menuitem name="Cut" action="LocationCut"/> + <menuitem name="Copy" action="LocationCopy"/> + <menuitem name="LocationPasteFilesInto" action="LocationPasteFilesInto"/> + </placeholder> + <separator name="Location After Clipboard Separator"/> + <placeholder name="Dangerous File Actions"> + <menuitem name="Trash" action="LocationTrash"/> + <menuitem name="Delete" action="LocationDelete"/> + <menuitem name="Restore From Trash" action="LocationRestoreFromTrash"/> + </placeholder> + <separator name="Location After Dangerous Separator"/> + <menuitem name="Location Mount Volume" action="Location Mount Volume"/> + <menuitem name="Location Unmount Volume" action="Location Unmount Volume"/> + <menuitem name="Location Eject Volume" action="Location Eject Volume"/> + <menuitem name="Location Format Volume" action="Location Format Volume"/> + <menuitem name="Location Start Volume" action="Location Start Volume"/> + <menuitem name="Location Stop Volume" action="Location Stop Volume"/> + <menuitem name="Location Poll" action="Location Poll"/> + <separator name="Properties Separator"/> + <menuitem name="LocationProperties" action="LocationProperties"/> +</popup> +</ui> diff --git a/src/file-manager/caja-icon-view-ui.xml b/src/file-manager/caja-icon-view-ui.xml new file mode 100644 index 00000000..89c4cb6e --- /dev/null +++ b/src/file-manager/caja-icon-view-ui.xml @@ -0,0 +1,56 @@ +<ui> +<menubar name="MenuBar"> + <menu action="Edit"> + <placeholder name="Edit Items Placeholder"> + <menuitem name="Stretch" action="Stretch"/> + <menuitem name="Unstretch" action="Unstretch"/> + </placeholder> + </menu> + <menu action="View"> + <placeholder name="View Items Placeholder"> + <menu action="Arrange Items"> + <menuitem name="Manual Layout" action="Manual Layout"/> + <placeholder name="Auto Layout"> + <menuitem name="Sort by Name" action="Sort by Name"/> + <menuitem name="Sort by Size" action="Sort by Size"/> + <menuitem name="Sort by Type" action="Sort by Type"/> + <menuitem name="Sort by Modification Date" action="Sort by Modification Date"/> + <menuitem name="Sort by Emblems" action="Sort by Emblems"/> + <menuitem name="Sort by Trash Time" action="Sort by Trash Time"/> + </placeholder> + <separator name="Layout separator"/> + <menuitem name="Tighter Layout" action="Tighter Layout"/> + <menuitem name="Reversed Order" action="Reversed Order"/> + </menu> + <menuitem name="Clean Up" action="Clean Up"/> + <menuitem name="Keep Aligned" action="Keep Aligned"/> + </placeholder> + + </menu> +</menubar> +<popup name="background"> + <placeholder name="Before Zoom Items"> + <placeholder name="View Items"> + <menu action="Arrange Items"> + <menuitem name="Manual Layout" action="Manual Layout"/> + <placeholder name="Auto Layout"> + <menuitem name="Sort by Name" action="Sort by Name"/> + <menuitem name="Sort by Size" action="Sort by Size"/> + <menuitem name="Sort by Type" action="Sort by Type"/> + <menuitem name="Sort by Modification Date" action="Sort by Modification Date"/> + <menuitem name="Sort by Emblems" action="Sort by Emblems"/> + <menuitem name="Sort by Trash Time" action="Sort by Trash Time"/> + </placeholder> + <separator name="Layout separator"/> + <menuitem name="Tighter Layout" action="Tighter Layout"/> + <menuitem name="Reversed Order" action="Reversed Order"/> + </menu> + <menuitem name="Clean Up" action="Clean Up"/> + <menuitem name="Keep Aligned" action="Keep Aligned"/> + </placeholder> + </placeholder> +</popup> +<popup name="selection"> + <placeholder name="Icon Appearance Items"/> +</popup> +</ui> diff --git a/src/file-manager/caja-list-view-ui.xml b/src/file-manager/caja-list-view-ui.xml new file mode 100644 index 00000000..ad9e6255 --- /dev/null +++ b/src/file-manager/caja-list-view-ui.xml @@ -0,0 +1,9 @@ +<ui> +<menubar name="MenuBar"> + <menu action="View"> + <placeholder name="View Items Placeholder"> + <menuitem name="Visible Columns" action="Visible Columns"/> + </placeholder> + </menu> +</menubar> +</ui> diff --git a/src/file-manager/fm-actions.h b/src/file-manager/fm-actions.h new file mode 100644 index 00000000..077d82c1 --- /dev/null +++ b/src/file-manager/fm-actions.h @@ -0,0 +1,110 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-actions.h + * + * 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. + * + * Authors: Alexander Larsson < [email protected]> + */ + +#ifndef FM_ACTIONS_H +#define FM_ACTIONS_H + +#define FM_ACTION_OPEN "Open" +#define FM_ACTION_OPEN_ALTERNATE "OpenAlternate" +#define FM_ACTION_OPEN_IN_NEW_TAB "OpenInNewTab" +#define FM_ACTION_OPEN_FOLDER_WINDOW "OpenFolderWindow" +#define FM_ACTION_LOCATION_OPEN_ALTERNATE "LocationOpenAlternate" +#define FM_ACTION_LOCATION_OPEN_IN_NEW_TAB "LocationOpenInNewTab" +#define FM_ACTION_LOCATION_OPEN_FOLDER_WINDOW "LocationOpenFolderWindow" +#define FM_ACTION_OTHER_APPLICATION1 "OtherApplication1" +#define FM_ACTION_OTHER_APPLICATION2 "OtherApplication2" +#define FM_ACTION_NEW_FOLDER "New Folder" +#define FM_ACTION_PROPERTIES "Properties" +#define FM_ACTION_PROPERTIES_ACCEL "PropertiesAccel" +#define FM_ACTION_LOCATION_PROPERTIES "LocationProperties" +#define FM_ACTION_NO_TEMPLATES "No Templates" +#define FM_ACTION_EMPTY_TRASH "Empty Trash" +#define FM_ACTION_SAVE_SEARCH "Save Search" +#define FM_ACTION_SAVE_SEARCH_AS "Save Search As" +#define FM_ACTION_CUT "Cut" +#define FM_ACTION_LOCATION_CUT "LocationCut" +#define FM_ACTION_COPY "Copy" +#define FM_ACTION_LOCATION_COPY "LocationCopy" +#define FM_ACTION_PASTE "Paste" +#define FM_ACTION_PASTE_FILES_INTO "Paste Files Into" +#define FM_ACTION_COPY_TO_NEXT_PANE "Copy to next pane" +#define FM_ACTION_MOVE_TO_NEXT_PANE "Move to next pane" +#define FM_ACTION_COPY_TO_HOME "Copy to Home" +#define FM_ACTION_MOVE_TO_HOME "Move to Home" +#define FM_ACTION_COPY_TO_DESKTOP "Copy to Desktop" +#define FM_ACTION_MOVE_TO_DESKTOP "Move to Desktop" +#define FM_ACTION_LOCATION_PASTE_FILES_INTO "LocationPasteFilesInto" +#define FM_ACTION_NEW_LAUNCHER "New Launcher" +#define FM_ACTION_NEW_LAUNCHER_DESKTOP "New Launcher Desktop" +#define FM_ACTION_RENAME "Rename" +#define FM_ACTION_DUPLICATE "Duplicate" +#define FM_ACTION_CREATE_LINK "Create Link" +#define FM_ACTION_SELECT_ALL "Select All" +#define FM_ACTION_INVERT_SELECTION "Invert Selection" +#define FM_ACTION_SELECT_PATTERN "Select Pattern" +#define FM_ACTION_TRASH "Trash" +#define FM_ACTION_LOCATION_TRASH "LocationTrash" +#define FM_ACTION_DELETE "Delete" +#define FM_ACTION_LOCATION_DELETE "LocationDelete" +#define FM_ACTION_RESTORE_FROM_TRASH "Restore From Trash" +#define FM_ACTION_LOCATION_RESTORE_FROM_TRASH "LocationRestoreFromTrash" +#define FM_ACTION_SHOW_HIDDEN_FILES "Show Hidden Files" +#define FM_ACTION_CONNECT_TO_SERVER_LINK "Connect To Server Link" +#define FM_ACTION_MOUNT_VOLUME "Mount Volume" +#define FM_ACTION_UNMOUNT_VOLUME "Unmount Volume" +#define FM_ACTION_EJECT_VOLUME "Eject Volume" +#define FM_ACTION_FORMAT_VOLUME "Format Volume" +#define FM_ACTION_START_VOLUME "Start Volume" +#define FM_ACTION_STOP_VOLUME "Stop Volume" +#define FM_ACTION_POLL "Poll" +#define FM_ACTION_SELF_MOUNT_VOLUME "Self Mount Volume" +#define FM_ACTION_SELF_UNMOUNT_VOLUME "Self Unmount Volume" +#define FM_ACTION_SELF_EJECT_VOLUME "Self Eject Volume" +#define FM_ACTION_SELF_FORMAT_VOLUME "Self Format Volume" +#define FM_ACTION_SELF_START_VOLUME "Self Start Volume" +#define FM_ACTION_SELF_STOP_VOLUME "Self Stop Volume" +#define FM_ACTION_SELF_POLL "Self Poll" +#define FM_ACTION_LOCATION_MOUNT_VOLUME "Location Mount Volume" +#define FM_ACTION_LOCATION_UNMOUNT_VOLUME "Location Unmount Volume" +#define FM_ACTION_LOCATION_EJECT_VOLUME "Location Eject Volume" +#define FM_ACTION_LOCATION_FORMAT_VOLUME "Location Format Volume" +#define FM_ACTION_LOCATION_START_VOLUME "Location Start Volume" +#define FM_ACTION_LOCATION_STOP_VOLUME "Location Stop Volume" +#define FM_ACTION_LOCATION_POLL "Location Poll" +#define FM_ACTION_SCRIPTS "Scripts" +#define FM_ACTION_NEW_DOCUMENTS "New Documents" +#define FM_ACTION_NEW_EMPTY_FILE "New Empty File" +#define FM_ACTION_EMPTY_TRASH_CONDITIONAL "Empty Trash Conditional" +#define FM_ACTION_MANUAL_LAYOUT "Manual Layout" +#define FM_ACTION_TIGHTER_LAYOUT "Tighter Layout" +#define FM_ACTION_REVERSED_ORDER "Reversed Order" +#define FM_ACTION_CLEAN_UP "Clean Up" +#define FM_ACTION_KEEP_ALIGNED "Keep Aligned" +#define FM_ACTION_ARRANGE_ITEMS "Arrange Items" +#define FM_ACTION_STRETCH "Stretch" +#define FM_ACTION_UNSTRETCH "Unstretch" +#define FM_ACTION_ZOOM_ITEMS "Zoom Items" +#define FM_ACTION_SORT_TRASH_TIME "Sort by Trash Time" + +#endif /* FM_ACTIONS_H */ diff --git a/src/file-manager/fm-desktop-icon-view.c b/src/file-manager/fm-desktop-icon-view.c new file mode 100644 index 00000000..4a1bab93 --- /dev/null +++ b/src/file-manager/fm-desktop-icon-view.c @@ -0,0 +1,881 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-desktop-icon-view.c - implementation of icon view for managing the desktop. + + Copyright (C) 2000, 2001 Eazel, Inc.mou + + 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: Mike Engber <[email protected]> + Gene Z. Ragan <[email protected]> + Miguel de Icaza <[email protected]> +*/ + +#include <config.h> +#include "fm-icon-container.h" +#include "fm-desktop-icon-view.h" +#include "fm-actions.h" + +#include <X11/Xatom.h> +#include <gtk/gtk.h> +#include <dirent.h> +#include <eel/eel-glib-extensions.h> +#include <eel/eel-mate-extensions.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 <fcntl.h> +#include <gdk/gdkx.h> +#include <glib/gi18n.h> +#include <libcaja-private/caja-desktop-icon-file.h> +#include <libcaja-private/caja-directory-background.h> +#include <libcaja-private/caja-directory-notify.h> +#include <libcaja-private/caja-file-changes-queue.h> +#include <libcaja-private/caja-file-operations.h> +#include <libcaja-private/caja-file-utilities.h> +#include <libcaja-private/caja-ui-utilities.h> +#include <libcaja-private/caja-global-preferences.h> +#include <libcaja-private/caja-view-factory.h> +#include <libcaja-private/caja-link.h> +#include <libcaja-private/caja-metadata.h> +#include <libcaja-private/caja-monitor.h> +#include <libcaja-private/caja-program-choosing.h> +#include <libcaja-private/caja-trash-monitor.h> +#include <limits.h> +#include <stddef.h> +#include <stdio.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +/* Timeout to check the desktop directory for updates */ +#define RESCAN_TIMEOUT 4 + +struct FMDesktopIconViewDetails +{ + GdkWindow *root_window; + GtkActionGroup *desktop_action_group; + guint desktop_merge_id; + + /* For the desktop rescanning + */ + gulong delayed_init_signal; + guint reload_desktop_timeout; + gboolean pending_rescan; +}; + +static void fm_desktop_icon_view_init (FMDesktopIconView *desktop_icon_view); +static void fm_desktop_icon_view_class_init (FMDesktopIconViewClass *klass); +static void default_zoom_level_changed (gpointer user_data); +static gboolean real_supports_auto_layout (FMIconView *view); +static gboolean real_supports_scaling (FMIconView *view); +static gboolean real_supports_keep_aligned (FMIconView *view); +static gboolean real_supports_labels_beside_icons (FMIconView *view); +static void real_merge_menus (FMDirectoryView *view); +static void real_update_menus (FMDirectoryView *view); +static gboolean real_supports_zooming (FMDirectoryView *view); +static void fm_desktop_icon_view_update_icon_container_fonts (FMDesktopIconView *view); + +EEL_CLASS_BOILERPLATE (FMDesktopIconView, + fm_desktop_icon_view, + FM_TYPE_ICON_VIEW) + +static char *desktop_directory; +static time_t desktop_dir_modify_time; + +static void +desktop_directory_changed_callback (gpointer callback_data) +{ + g_free (desktop_directory); + desktop_directory = caja_get_desktop_directory (); +} + +static void +lockdown_disable_command_line_changed_callback (gpointer callback_data) +{ + fm_directory_view_update_menus (FM_DIRECTORY_VIEW (callback_data)); +} + +static CajaIconContainer * +get_icon_container (FMDesktopIconView *icon_view) +{ + g_return_val_if_fail (FM_IS_DESKTOP_ICON_VIEW (icon_view), NULL); + g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (gtk_bin_get_child (GTK_BIN (icon_view))), NULL); + + return CAJA_ICON_CONTAINER (gtk_bin_get_child (GTK_BIN (icon_view))); +} + +static void +icon_container_set_workarea (CajaIconContainer *icon_container, + GdkScreen *screen, + long *workareas, + int n_items) +{ + int left, right, top, bottom; + int screen_width, screen_height; + int i; + + left = right = top = bottom = 0; + + screen_width = gdk_screen_get_width (screen); + screen_height = gdk_screen_get_height (screen); + + for (i = 0; i < n_items; i += 4) + { + int x = workareas [i]; + int y = workareas [i + 1]; + int width = workareas [i + 2]; + int height = workareas [i + 3]; + + if ((x + width) > screen_width || (y + height) > screen_height) + continue; + + left = MAX (left, x); + right = MAX (right, screen_width - width - x); + top = MAX (top, y); + bottom = MAX (bottom, screen_height - height - y); + } + + caja_icon_container_set_margins (icon_container, + left, right, top, bottom); +} + +static void +net_workarea_changed (FMDesktopIconView *icon_view, + GdkWindow *window) +{ + long *nworkareas = NULL; + long *workareas = NULL; + GdkAtom type_returned; + int format_returned; + int length_returned; + CajaIconContainer *icon_container; + GdkScreen *screen; + + g_return_if_fail (FM_IS_DESKTOP_ICON_VIEW (icon_view)); + + icon_container = get_icon_container (icon_view); + + /* Find the number of desktops so we know how long the + * workareas array is going to be (each desktop will have four + * elements in the workareas array describing + * x,y,width,height) */ + gdk_error_trap_push (); + if (!gdk_property_get (window, + gdk_atom_intern ("_NET_NUMBER_OF_DESKTOPS", FALSE), + gdk_x11_xatom_to_atom (XA_CARDINAL), + 0, 4, FALSE, + &type_returned, + &format_returned, + &length_returned, + (guchar **) &nworkareas)) + { + g_warning("Can not calculate _NET_NUMBER_OF_DESKTOPS"); + } + if (gdk_error_trap_pop() + || nworkareas == NULL + || type_returned != gdk_x11_xatom_to_atom (XA_CARDINAL) + || format_returned != 32) + g_warning("Can not calculate _NET_NUMBER_OF_DESKTOPS"); + + /* Note : gdk_property_get() is broken (API documents admit + * this). As a length argument, it expects the number of + * _bytes_ of data you require. Internally, gdk_property_get + * converts that value to a count of 32 bit (4 byte) elements. + * However, the length returned is in bytes, but is calculated + * via the count of returned elements * sizeof(long). This + * means on a 64 bit system, the number of bytes you have to + * request does not correspond to the number of bytes you get + * back, and is the reason for the workaround below. + */ + gdk_error_trap_push (); + if (nworkareas == NULL || (*nworkareas < 1) + || !gdk_property_get (window, + gdk_atom_intern ("_NET_WORKAREA", FALSE), + gdk_x11_xatom_to_atom (XA_CARDINAL), + 0, ((*nworkareas) * 4 * 4), FALSE, + &type_returned, + &format_returned, + &length_returned, + (guchar **) &workareas)) + { + g_warning("Can not get _NET_WORKAREA"); + workareas = NULL; + } + + if (gdk_error_trap_pop () + || workareas == NULL + || type_returned != gdk_x11_xatom_to_atom (XA_CARDINAL) + || ((*nworkareas) * 4 * sizeof(long)) != length_returned + || format_returned != 32) + { + g_warning("Can not determine workarea, guessing at layout"); + caja_icon_container_set_margins (icon_container, + 0, 0, 0, 0); + } + else + { + screen = gdk_drawable_get_screen (GDK_DRAWABLE (window)); + + icon_container_set_workarea ( + icon_container, screen, workareas, length_returned / sizeof (long)); + } + + if (nworkareas != NULL) + g_free (nworkareas); + + if (workareas != NULL) + g_free (workareas); +} + +static GdkFilterReturn +desktop_icon_view_property_filter (GdkXEvent *gdk_xevent, + GdkEvent *event, + gpointer data) +{ + XEvent *xevent = gdk_xevent; + FMDesktopIconView *icon_view; + + icon_view = FM_DESKTOP_ICON_VIEW (data); + + switch (xevent->type) + { + case PropertyNotify: + if (xevent->xproperty.atom == gdk_x11_get_xatom_by_name ("_NET_WORKAREA")) + net_workarea_changed (icon_view, event->any.window); + break; + default: + break; + } + + return GDK_FILTER_CONTINUE; +} + +static void +fm_desktop_icon_view_destroy (GtkObject *object) +{ + FMDesktopIconView *icon_view; + GtkUIManager *ui_manager; + + icon_view = FM_DESKTOP_ICON_VIEW (object); + + /* Remove desktop rescan timeout. */ + if (icon_view->details->reload_desktop_timeout != 0) + { + g_source_remove (icon_view->details->reload_desktop_timeout); + icon_view->details->reload_desktop_timeout = 0; + } + + ui_manager = fm_directory_view_get_ui_manager (FM_DIRECTORY_VIEW (icon_view)); + if (ui_manager != NULL) + { + caja_ui_unmerge_ui (ui_manager, + &icon_view->details->desktop_merge_id, + &icon_view->details->desktop_action_group); + } + + GTK_OBJECT_CLASS (parent_class)->destroy (object); +} + +static void +fm_desktop_icon_view_finalize (GObject *object) +{ + FMDesktopIconView *icon_view; + + icon_view = FM_DESKTOP_ICON_VIEW (object); + + eel_preferences_remove_callback (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_ZOOM_LEVEL, + default_zoom_level_changed, + icon_view); + + eel_preferences_remove_callback (CAJA_PREFERENCES_LOCKDOWN_COMMAND_LINE, + lockdown_disable_command_line_changed_callback, + icon_view); + + g_free (icon_view->details); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +fm_desktop_icon_view_class_init (FMDesktopIconViewClass *class) +{ + G_OBJECT_CLASS (class)->finalize = fm_desktop_icon_view_finalize; + + GTK_OBJECT_CLASS (class)->destroy = fm_desktop_icon_view_destroy; + + FM_DIRECTORY_VIEW_CLASS (class)->merge_menus = real_merge_menus; + FM_DIRECTORY_VIEW_CLASS (class)->update_menus = real_update_menus; + FM_DIRECTORY_VIEW_CLASS (class)->supports_zooming = real_supports_zooming; + + FM_ICON_VIEW_CLASS (class)->supports_auto_layout = real_supports_auto_layout; + FM_ICON_VIEW_CLASS (class)->supports_scaling = real_supports_scaling; + FM_ICON_VIEW_CLASS (class)->supports_keep_aligned = real_supports_keep_aligned; + FM_ICON_VIEW_CLASS (class)->supports_labels_beside_icons = real_supports_labels_beside_icons; +} + +static void +fm_desktop_icon_view_handle_middle_click (CajaIconContainer *icon_container, + GdkEventButton *event, + FMDesktopIconView *desktop_icon_view) +{ + XButtonEvent x_event; + + /* During a mouse click we have the pointer and keyboard grab. + * We will send a fake event to the root window which will cause it + * to try to get the grab so we need to let go ourselves. + */ + gdk_pointer_ungrab (GDK_CURRENT_TIME); + gdk_keyboard_ungrab (GDK_CURRENT_TIME); + + /* Stop the event because we don't want anyone else dealing with it. */ + gdk_flush (); + g_signal_stop_emission_by_name (icon_container, "middle_click"); + + /* build an X event to represent the middle click. */ + x_event.type = ButtonPress; + x_event.send_event = True; + x_event.display = GDK_DISPLAY (); + x_event.window = GDK_ROOT_WINDOW (); + x_event.root = GDK_ROOT_WINDOW (); + x_event.subwindow = 0; + x_event.time = event->time; + x_event.x = event->x; + x_event.y = event->y; + x_event.x_root = event->x_root; + x_event.y_root = event->y_root; + x_event.state = event->state; + x_event.button = event->button; + x_event.same_screen = True; + + /* Send it to the root window, the window manager will handle it. */ + XSendEvent (GDK_DISPLAY (), GDK_ROOT_WINDOW (), True, + ButtonPressMask, (XEvent *) &x_event); +} + +static void +unrealized_callback (GtkWidget *widget, FMDesktopIconView *desktop_icon_view) +{ + g_return_if_fail (desktop_icon_view->details->root_window != NULL); + + /* Remove the property filter */ + gdk_window_remove_filter (desktop_icon_view->details->root_window, + desktop_icon_view_property_filter, + desktop_icon_view); + desktop_icon_view->details->root_window = NULL; +} + +static void +realized_callback (GtkWidget *widget, FMDesktopIconView *desktop_icon_view) +{ + GdkWindow *root_window; + GdkScreen *screen; + GtkAllocation allocation; + + g_return_if_fail (desktop_icon_view->details->root_window == NULL); + + screen = gtk_widget_get_screen (widget); + + /* Ugly HACK for the problem that the views realize at the + * wrong size and then get resized. (This is a problem with + * MateComponentPlug.) This was leading to problems where initial + * layout was done at 60x60 stacking all desktop icons in + * the top left corner. + */ + allocation.x = 0; + allocation.y = 0; + allocation.width = gdk_screen_get_width (screen); + allocation.height = gdk_screen_get_height (screen); + gtk_widget_size_allocate (GTK_WIDGET(get_icon_container(desktop_icon_view)), + &allocation); + + root_window = gdk_screen_get_root_window (screen); + + desktop_icon_view->details->root_window = root_window; + + /* Read out the workarea geometry and update the icon container accordingly */ + net_workarea_changed (desktop_icon_view, root_window); + + /* Setup the property filter */ + gdk_window_set_events (root_window, GDK_PROPERTY_CHANGE_MASK); + gdk_window_add_filter (root_window, + desktop_icon_view_property_filter, + desktop_icon_view); +} + +static CajaZoomLevel +get_default_zoom_level (void) +{ + static gboolean auto_storage_added = FALSE; + static CajaZoomLevel default_zoom_level = CAJA_ZOOM_LEVEL_STANDARD; + + if (!auto_storage_added) + { + auto_storage_added = TRUE; + eel_preferences_add_auto_enum (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_ZOOM_LEVEL, + (int *) &default_zoom_level); + } + + return CLAMP (default_zoom_level, CAJA_ZOOM_LEVEL_SMALLEST, CAJA_ZOOM_LEVEL_LARGEST); +} + +static void +default_zoom_level_changed (gpointer user_data) +{ + CajaZoomLevel new_level; + FMDesktopIconView *desktop_icon_view; + + desktop_icon_view = FM_DESKTOP_ICON_VIEW (user_data); + new_level = get_default_zoom_level (); + + caja_icon_container_set_zoom_level (get_icon_container (desktop_icon_view), + new_level); +} + +static gboolean +do_desktop_rescan (gpointer data) +{ + FMDesktopIconView *desktop_icon_view; + struct stat buf; + + desktop_icon_view = FM_DESKTOP_ICON_VIEW (data); + if (desktop_icon_view->details->pending_rescan) + { + return TRUE; + } + + if (stat (desktop_directory, &buf) == -1) + { + return TRUE; + } + + if (buf.st_ctime == desktop_dir_modify_time) + { + return TRUE; + } + + desktop_icon_view->details->pending_rescan = TRUE; + + caja_directory_force_reload ( + fm_directory_view_get_model ( + FM_DIRECTORY_VIEW (desktop_icon_view))); + return TRUE; +} + +static void +done_loading (GtkObject *DirectoryView, FMDesktopIconView *desktop_icon_view) +{ + struct stat buf; + + desktop_icon_view->details->pending_rescan = FALSE; + if (stat (desktop_directory, &buf) == -1) + { + return; + } + + desktop_dir_modify_time = buf.st_ctime; +} + +/* This function is used because the CajaDirectory model does not + * exist always in the desktop_icon_view, so we wait until it has been + * instantiated. + */ +static void +delayed_init (FMDesktopIconView *desktop_icon_view) +{ + /* Keep track of the load time. */ + g_signal_connect_object (fm_directory_view_get_model (FM_DIRECTORY_VIEW (desktop_icon_view)), + "done_loading", + G_CALLBACK (done_loading), desktop_icon_view, 0); + + /* Monitor desktop directory. */ + desktop_icon_view->details->reload_desktop_timeout = + g_timeout_add_seconds (RESCAN_TIMEOUT, do_desktop_rescan, desktop_icon_view); + + g_signal_handler_disconnect (desktop_icon_view, + desktop_icon_view->details->delayed_init_signal); + + desktop_icon_view->details->delayed_init_signal = 0; +} + +static void +font_changed_callback (gpointer callback_data) +{ + g_return_if_fail (FM_IS_DESKTOP_ICON_VIEW (callback_data)); + + fm_desktop_icon_view_update_icon_container_fonts (FM_DESKTOP_ICON_VIEW (callback_data)); +} + +static void +fm_desktop_icon_view_update_icon_container_fonts (FMDesktopIconView *icon_view) +{ + CajaIconContainer *icon_container; + char *font; + + icon_container = get_icon_container (icon_view); + g_assert (icon_container != NULL); + + font = eel_preferences_get (CAJA_PREFERENCES_DESKTOP_FONT); + + caja_icon_container_set_font (icon_container, font); + + g_free (font); +} + +static void +fm_desktop_icon_view_init (FMDesktopIconView *desktop_icon_view) +{ + CajaIconContainer *icon_container; + GtkAllocation allocation; + GtkAdjustment *hadj, *vadj; + + if (desktop_directory == NULL) + { + eel_preferences_add_callback (CAJA_PREFERENCES_DESKTOP_IS_HOME_DIR, + desktop_directory_changed_callback, + NULL); + desktop_directory_changed_callback (NULL); + } + + fm_icon_view_filter_by_screen (FM_ICON_VIEW (desktop_icon_view), TRUE); + icon_container = get_icon_container (desktop_icon_view); + caja_icon_container_set_use_drop_shadows (icon_container, TRUE); + fm_icon_container_set_sort_desktop (FM_ICON_CONTAINER (icon_container), TRUE); + + /* Set up details */ + desktop_icon_view->details = g_new0 (FMDesktopIconViewDetails, 1); + + /* Do a reload on the desktop if we don't have FAM, a smarter + * way to keep track of the items on the desktop. + */ + if (!caja_monitor_active ()) + { + desktop_icon_view->details->delayed_init_signal = g_signal_connect_object + (desktop_icon_view, "begin_loading", + G_CALLBACK (delayed_init), desktop_icon_view, 0); + } + + caja_icon_container_set_is_fixed_size (icon_container, TRUE); + caja_icon_container_set_is_desktop (icon_container, TRUE); + caja_icon_container_set_store_layout_timestamps (icon_container, TRUE); + + /* Set allocation to be at 0, 0 */ + gtk_widget_get_allocation (GTK_WIDGET (icon_container), &allocation); + allocation.x = 0; + allocation.y = 0; + gtk_widget_set_allocation (GTK_WIDGET (icon_container), &allocation); + + gtk_widget_queue_resize (GTK_WIDGET (icon_container)); + + hadj = gtk_layout_get_hadjustment (GTK_LAYOUT (icon_container)); + vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (icon_container)); + + eel_gtk_adjustment_set_value (hadj, 0); + eel_gtk_adjustment_set_value (vadj, 0); + + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (desktop_icon_view), + GTK_SHADOW_NONE); + + fm_directory_view_ignore_hidden_file_preferences + (FM_DIRECTORY_VIEW (desktop_icon_view)); + + fm_directory_view_set_show_foreign (FM_DIRECTORY_VIEW (desktop_icon_view), + FALSE); + + /* Set our default layout mode */ + caja_icon_container_set_layout_mode (icon_container, + gtk_widget_get_direction (GTK_WIDGET(icon_container)) == GTK_TEXT_DIR_RTL ? + CAJA_ICON_LAYOUT_T_B_R_L : + CAJA_ICON_LAYOUT_T_B_L_R); + + g_signal_connect_object (icon_container, "middle_click", + G_CALLBACK (fm_desktop_icon_view_handle_middle_click), desktop_icon_view, 0); + g_signal_connect_object (desktop_icon_view, "realize", + G_CALLBACK (realized_callback), desktop_icon_view, 0); + g_signal_connect_object (desktop_icon_view, "unrealize", + G_CALLBACK (unrealized_callback), desktop_icon_view, 0); + + eel_preferences_add_callback (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_ZOOM_LEVEL, + default_zoom_level_changed, + desktop_icon_view); + + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_DESKTOP_FONT, + font_changed_callback, + desktop_icon_view, G_OBJECT (desktop_icon_view)); + + default_zoom_level_changed (desktop_icon_view); + fm_desktop_icon_view_update_icon_container_fonts (desktop_icon_view); + + eel_preferences_add_callback (CAJA_PREFERENCES_LOCKDOWN_COMMAND_LINE, + lockdown_disable_command_line_changed_callback, + desktop_icon_view); + +} + +static void +action_new_launcher_callback (GtkAction *action, gpointer data) +{ + char *desktop_directory; + + g_assert (FM_DIRECTORY_VIEW (data)); + + desktop_directory = caja_get_desktop_directory (); + + caja_launch_application_from_command (gtk_widget_get_screen (GTK_WIDGET (data)), + "mate-desktop-item-edit", + "mate-desktop-item-edit", + FALSE, + "--create-new", desktop_directory, NULL); + g_free (desktop_directory); + +} + +static void +action_change_background_callback (GtkAction *action, + gpointer data) +{ + g_assert (FM_DIRECTORY_VIEW (data)); + + caja_launch_application_from_command (gtk_widget_get_screen (GTK_WIDGET (data)), + _("Background"), + "mate-appearance-properties", + FALSE, + "--show-page=background", NULL); +} + +static void +action_empty_trash_conditional_callback (GtkAction *action, + gpointer data) +{ + g_assert (FM_IS_DIRECTORY_VIEW (data)); + + caja_file_operations_empty_trash (GTK_WIDGET (data)); +} + +static gboolean +trash_link_is_selection (FMDirectoryView *view) +{ + GList *selection; + CajaDesktopLink *link; + gboolean result; + + result = FALSE; + + selection = fm_directory_view_get_selection (view); + + if (eel_g_list_exactly_one_item (selection) && + CAJA_IS_DESKTOP_ICON_FILE (selection->data)) + { + link = caja_desktop_icon_file_get_link (CAJA_DESKTOP_ICON_FILE (selection->data)); + /* link may be NULL if the link was recently removed (unmounted) */ + if (link != NULL && + caja_desktop_link_get_link_type (link) == CAJA_DESKTOP_LINK_TRASH) + { + result = TRUE; + } + if (link) + { + g_object_unref (link); + } + } + + caja_file_list_free (selection); + + return result; +} + +static void +real_update_menus (FMDirectoryView *view) +{ + FMDesktopIconView *desktop_view; + char *label; + gboolean disable_command_line; + gboolean include_empty_trash; + GtkAction *action; + + g_assert (FM_IS_DESKTOP_ICON_VIEW (view)); + + EEL_CALL_PARENT (FM_DIRECTORY_VIEW_CLASS, update_menus, (view)); + + desktop_view = FM_DESKTOP_ICON_VIEW (view); + + /* New Launcher */ + disable_command_line = eel_preferences_get_boolean (CAJA_PREFERENCES_LOCKDOWN_COMMAND_LINE); + action = gtk_action_group_get_action (desktop_view->details->desktop_action_group, + FM_ACTION_NEW_LAUNCHER_DESKTOP); + gtk_action_set_visible (action, + !disable_command_line); + + /* Empty Trash */ + include_empty_trash = trash_link_is_selection (view); + action = gtk_action_group_get_action (desktop_view->details->desktop_action_group, + FM_ACTION_EMPTY_TRASH_CONDITIONAL); + gtk_action_set_visible (action, + include_empty_trash); + if (include_empty_trash) + { + label = g_strdup (_("E_mpty Trash")); + g_object_set (action , "label", label, NULL); + gtk_action_set_sensitive (action, + !caja_trash_monitor_is_empty ()); + g_free (label); + } +} + +static const GtkActionEntry desktop_view_entries[] = +{ + /* name, stock id */ + { + "New Launcher Desktop", NULL, + /* label, accelerator */ + N_("Create L_auncher..."), NULL, + /* tooltip */ + N_("Create a new launcher"), + G_CALLBACK (action_new_launcher_callback) + }, + /* name, stock id */ + { + "Change Background", NULL, + /* label, accelerator */ + N_("Change Desktop _Background"), NULL, + /* tooltip */ + N_("Show a window that lets you set your desktop background's pattern or color"), + G_CALLBACK (action_change_background_callback) + }, + /* name, stock id */ + { + "Empty Trash Conditional", NULL, + /* label, accelerator */ + N_("Empty Trash"), NULL, + /* tooltip */ + N_("Delete all items in the Trash"), + G_CALLBACK (action_empty_trash_conditional_callback) + }, +}; + +static void +real_merge_menus (FMDirectoryView *view) +{ + FMDesktopIconView *desktop_view; + GtkUIManager *ui_manager; + GtkActionGroup *action_group; + const char *ui; + + EEL_CALL_PARENT (FM_DIRECTORY_VIEW_CLASS, merge_menus, (view)); + + desktop_view = FM_DESKTOP_ICON_VIEW (view); + + ui_manager = fm_directory_view_get_ui_manager (view); + + action_group = gtk_action_group_new ("DesktopViewActions"); + gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE); + desktop_view->details->desktop_action_group = action_group; + gtk_action_group_add_actions (action_group, + desktop_view_entries, G_N_ELEMENTS (desktop_view_entries), + view); + + gtk_ui_manager_insert_action_group (ui_manager, action_group, 0); + g_object_unref (action_group); /* owned by ui manager */ + + ui = caja_ui_string_get ("caja-desktop-icon-view-ui.xml"); + desktop_view->details->desktop_merge_id = + gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, NULL); +} + +static gboolean +real_supports_auto_layout (FMIconView *view) +{ + /* Can't use auto-layout on the desktop, because doing so + * would cause all sorts of complications involving the + * fixed-size window. + */ + return FALSE; +} + +static gboolean +real_supports_scaling (FMIconView *view) +{ + return TRUE; +} + +static gboolean +real_supports_keep_aligned (FMIconView *view) +{ + return TRUE; +} + +static gboolean +real_supports_labels_beside_icons (FMIconView *view) +{ + return FALSE; +} + +static gboolean +real_supports_zooming (FMDirectoryView *view) +{ + /* Can't zoom on the desktop, because doing so would cause all + * sorts of complications involving the fixed-size window. + */ + return FALSE; +} + +static CajaView * +fm_desktop_icon_view_create (CajaWindowSlotInfo *slot) +{ + FMIconView *view; + + view = g_object_new (FM_TYPE_DESKTOP_ICON_VIEW, + "window-slot", slot, + NULL); + return CAJA_VIEW (view); +} + +static gboolean +fm_desktop_icon_view_supports_uri (const char *uri, + GFileType file_type, + const char *mime_type) +{ + if (g_str_has_prefix (uri, EEL_DESKTOP_URI)) + { + return TRUE; + } + + return FALSE; +} + +static CajaViewInfo fm_desktop_icon_view = +{ + FM_DESKTOP_ICON_VIEW_ID, + "Desktop View", + "_Desktop", + N_("The desktop view encountered an error."), + N_("The desktop view encountered an error while starting up."), + "Display this location with the desktop view.", + fm_desktop_icon_view_create, + fm_desktop_icon_view_supports_uri +}; + +void +fm_desktop_icon_view_register (void) +{ + fm_desktop_icon_view.error_label = _(fm_desktop_icon_view.error_label); + fm_desktop_icon_view.startup_error_label = _(fm_desktop_icon_view.startup_error_label); + + caja_view_factory_register (&fm_desktop_icon_view); +} diff --git a/src/file-manager/fm-desktop-icon-view.h b/src/file-manager/fm-desktop-icon-view.h new file mode 100644 index 00000000..f5702296 --- /dev/null +++ b/src/file-manager/fm-desktop-icon-view.h @@ -0,0 +1,60 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-icon-view.h - interface for icon view of directory. + + 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: Mike Engber <[email protected]> +*/ + +#ifndef FM_DESKTOP_ICON_VIEW_H +#define FM_DESKTOP_ICON_VIEW_H + +#include "fm-icon-view.h" + +#define FM_TYPE_DESKTOP_ICON_VIEW fm_desktop_icon_view_get_type() +#define FM_DESKTOP_ICON_VIEW(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_DESKTOP_ICON_VIEW, FMDesktopIconView)) +#define FM_DESKTOP_ICON_VIEW_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_DESKTOP_ICON_VIEW, FMDesktopIconViewClass)) +#define FM_IS_DESKTOP_ICON_VIEW(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_DESKTOP_ICON_VIEW)) +#define FM_IS_DESKTOP_ICON_VIEW_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_DESKTOP_ICON_VIEW)) +#define FM_DESKTOP_ICON_VIEW_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_DESKTOP_ICON_VIEW, FMDesktopIconViewClass)) + +#define FM_DESKTOP_ICON_VIEW_ID "OAFIID:Caja_File_Manager_Desktop_Icon_View" + +typedef struct FMDesktopIconViewDetails FMDesktopIconViewDetails; +typedef struct +{ + FMIconView parent; + FMDesktopIconViewDetails *details; +} FMDesktopIconView; + +typedef struct +{ + FMIconViewClass parent_class; +} FMDesktopIconViewClass; + +/* GObject support */ +GType fm_desktop_icon_view_get_type (void); +void fm_desktop_icon_view_register (void); + +#endif /* FM_DESKTOP_ICON_VIEW_H */ diff --git a/src/file-manager/fm-directory-view.c b/src/file-manager/fm-directory-view.c new file mode 100644 index 00000000..a192aa58 --- /dev/null +++ b/src/file-manager/fm-directory-view.c @@ -0,0 +1,10936 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-directory-view.c + * + * 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, + * John Sullivan <[email protected]>, + * Darin Adler <[email protected]>, + * Pavel Cisler <[email protected]>, + * David Emory Watson <[email protected]> + */ + +#include <config.h> +#include <math.h> +#include "fm-directory-view.h" +#include "fm-list-view.h" +#include "fm-desktop-icon-view.h" + +#include "fm-actions.h" +#include "fm-error-reporting.h" +#include "fm-properties-window.h" +#include "libcaja-private/caja-open-with-dialog.h" + +#include <eel/eel-background.h> +#include <eel/eel-glib-extensions.h> +#include <eel/eel-mate-extensions.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 <eel/eel-marshal.h> +#include <gdk/gdkkeysyms.h> +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> +#include <gio/gio.h> +#include <libcaja-private/caja-recent.h> +#include <libcaja-extension/caja-menu-provider.h> +#include <libcaja-private/caja-clipboard.h> +#include <libcaja-private/caja-clipboard-monitor.h> +#include <libcaja-private/caja-debug-log.h> +#include <libcaja-private/caja-desktop-icon-file.h> +#include <libcaja-private/caja-desktop-directory.h> +#include <libcaja-private/caja-search-directory.h> +#include <libcaja-private/caja-directory-background.h> +#include <libcaja-private/caja-directory.h> +#include <libcaja-private/caja-dnd.h> +#include <libcaja-private/caja-file-attributes.h> +#include <libcaja-private/caja-file-changes-queue.h> +#include <libcaja-private/caja-file-dnd.h> +#include <libcaja-private/caja-file-operations.h> +#include <libcaja-private/caja-file-utilities.h> +#include <libcaja-private/caja-file-private.h> /* for caja_file_get_existing_by_uri */ +#include <libcaja-private/caja-global-preferences.h> +#include <libcaja-private/caja-link.h> +#include <libcaja-private/caja-marshal.h> +#include <libcaja-private/caja-metadata.h> +#include <libcaja-private/caja-mime-actions.h> +#include <libcaja-private/caja-module.h> +#include <libcaja-private/caja-program-choosing.h> +#include <libcaja-private/caja-trash-monitor.h> +#include <libcaja-private/caja-ui-utilities.h> +#include <libcaja-private/caja-signaller.h> +#include <libcaja-private/caja-autorun.h> +#include <libcaja-private/caja-icon-names.h> + +/* Minimum starting update inverval */ +#define UPDATE_INTERVAL_MIN 100 +/* Maximum update interval */ +#define UPDATE_INTERVAL_MAX 2000 +/* Amount of miliseconds the update interval is increased */ +#define UPDATE_INTERVAL_INC 250 +/* Interval at which the update interval is increased */ +#define UPDATE_INTERVAL_TIMEOUT_INTERVAL 250 +/* Milliseconds that have to pass without a change to reset the update interval */ +#define UPDATE_INTERVAL_RESET 1000 + +#define SILENT_WINDOW_OPEN_LIMIT 5 + +#define DUPLICATE_HORIZONTAL_ICON_OFFSET 70 +#define DUPLICATE_VERTICAL_ICON_OFFSET 30 + +#define MAX_QUEUED_UPDATES 500 + +#define FM_DIRECTORY_VIEW_MENU_PATH_APPLICATIONS_SUBMENU_PLACEHOLDER "/MenuBar/File/Open Placeholder/Open With/Applications Placeholder" +#define FM_DIRECTORY_VIEW_MENU_PATH_APPLICATIONS_PLACEHOLDER "/MenuBar/File/Open Placeholder/Applications Placeholder" +#define FM_DIRECTORY_VIEW_MENU_PATH_SCRIPTS_PLACEHOLDER "/MenuBar/File/Open Placeholder/Scripts/Scripts Placeholder" +#define FM_DIRECTORY_VIEW_MENU_PATH_EXTENSION_ACTIONS_PLACEHOLDER "/MenuBar/Edit/Extension Actions" +#define FM_DIRECTORY_VIEW_MENU_PATH_NEW_DOCUMENTS_PLACEHOLDER "/MenuBar/File/New Items Placeholder/New Documents/New Documents Placeholder" +#define FM_DIRECTORY_VIEW_MENU_PATH_OPEN "/MenuBar/File/Open Placeholder/Open" + +#define FM_DIRECTORY_VIEW_POPUP_PATH_SELECTION "/selection" +#define FM_DIRECTORY_VIEW_POPUP_PATH_APPLICATIONS_SUBMENU_PLACEHOLDER "/selection/Open Placeholder/Open With/Applications Placeholder" +#define FM_DIRECTORY_VIEW_POPUP_PATH_APPLICATIONS_PLACEHOLDER "/selection/Open Placeholder/Applications Placeholder" +#define FM_DIRECTORY_VIEW_POPUP_PATH_SCRIPTS_PLACEHOLDER "/selection/Open Placeholder/Scripts/Scripts Placeholder" +#define FM_DIRECTORY_VIEW_POPUP_PATH_EXTENSION_ACTIONS "/selection/Extension Actions" +#define FM_DIRECTORY_VIEW_POPUP_PATH_OPEN "/selection/Open Placeholder/Open" + +#define FM_DIRECTORY_VIEW_POPUP_PATH_BACKGROUND "/background" +#define FM_DIRECTORY_VIEW_POPUP_PATH_BACKGROUND_SCRIPTS_PLACEHOLDER "/background/Before Zoom Items/New Object Items/Scripts/Scripts Placeholder" +#define FM_DIRECTORY_VIEW_POPUP_PATH_BACKGROUND_NEW_DOCUMENTS_PLACEHOLDER "/background/Before Zoom Items/New Object Items/New Documents/New Documents Placeholder" + +#define FM_DIRECTORY_VIEW_POPUP_PATH_LOCATION "/location" + +#define MAX_MENU_LEVELS 5 +#define TEMPLATE_LIMIT 30 + +enum { + ADD_FILE, + BEGIN_FILE_CHANGES, + BEGIN_LOADING, + CLEAR, + END_FILE_CHANGES, + FLUSH_ADDED_FILES, + END_LOADING, + FILE_CHANGED, + LOAD_ERROR, + MOVE_COPY_ITEMS, + REMOVE_FILE, + TRASH, + DELETE, + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_WINDOW_SLOT +}; + + +static guint signals[LAST_SIGNAL]; + +static GdkAtom copied_files_atom; + +static gboolean show_delete_command_auto_value; +static gboolean confirm_trash_auto_value; + +static char *scripts_directory_uri; +static int scripts_directory_uri_length; + +struct FMDirectoryViewDetails +{ + CajaWindowInfo *window; + CajaWindowSlotInfo *slot; + CajaDirectory *model; + CajaFile *directory_as_file; + CajaFile *location_popup_directory_as_file; + GdkEventButton *location_popup_event; + GtkActionGroup *dir_action_group; + guint dir_merge_id; + + GList *scripts_directory_list; + GtkActionGroup *scripts_action_group; + guint scripts_merge_id; + + GList *templates_directory_list; + GtkActionGroup *templates_action_group; + guint templates_merge_id; + + GtkActionGroup *extensions_menu_action_group; + guint extensions_menu_merge_id; + + guint display_selection_idle_id; + guint update_menus_timeout_id; + guint update_status_idle_id; + guint reveal_selection_idle_id; + + guint display_pending_source_id; + guint changes_timeout_id; + + guint update_interval; + guint64 last_queued; + + guint files_added_handler_id; + guint files_changed_handler_id; + guint load_error_handler_id; + guint done_loading_handler_id; + guint file_changed_handler_id; + + guint delayed_rename_file_id; + + GList *new_added_files; + GList *new_changed_files; + + GHashTable *non_ready_files; + + GList *old_added_files; + GList *old_changed_files; + + GList *pending_locations_selected; + + /* whether we are in the active slot */ + gboolean active; + + /* loading indicates whether this view has begun loading a directory. + * This flag should need not be set inside subclasses. FMDirectoryView automatically + * sets 'loading' to TRUE before it begins loading a directory's contents and to FALSE + * after it finishes loading the directory and its view. + */ + gboolean loading; + gboolean menu_states_untrustworthy; + gboolean scripts_invalid; + gboolean templates_invalid; + gboolean reported_load_error; + + /* flag to indicate that no file updates should be dispatched to subclasses. + * This is a workaround for bug #87701 that prevents the list view from + * losing focus when the underlying GtkTreeView is updated. + */ + gboolean updates_frozen; + guint updates_queued; + gboolean needs_reload; + + gboolean sort_directories_first; + + gboolean show_foreign_files; + gboolean show_hidden_files; + gboolean show_backup_files; + gboolean ignore_hidden_file_preferences; + + gboolean batching_selection_level; + gboolean selection_changed_while_batched; + + gboolean selection_was_removed; + + gboolean metadata_for_directory_as_file_pending; + gboolean metadata_for_files_in_directory_pending; + + gboolean selection_change_is_due_to_shell; + gboolean send_selection_change_to_shell; + + GtkActionGroup *open_with_action_group; + guint open_with_merge_id; + + GList *subdirectory_list; + + gboolean allow_moves; + + GdkPoint context_menu_position; +}; + +typedef struct { + CajaFile *file; + CajaDirectory *directory; +} FileAndDirectory; + +/* forward declarations */ + +static gboolean display_selection_info_idle_callback (gpointer data); +static void fm_directory_view_class_init (FMDirectoryViewClass *klass); +static void fm_directory_view_init (FMDirectoryView *view); +static void fm_directory_view_duplicate_selection (FMDirectoryView *view, + GList *files, + GArray *item_locations); +static void fm_directory_view_create_links_for_files (FMDirectoryView *view, + GList *files, + GArray *item_locations); +static void trash_or_delete_files (GtkWindow *parent_window, + const GList *files, + gboolean delete_if_all_already_in_trash, + FMDirectoryView *view); +static void load_directory (FMDirectoryView *view, + CajaDirectory *directory); +static void fm_directory_view_merge_menus (FMDirectoryView *view); +static void fm_directory_view_unmerge_menus (FMDirectoryView *view); +static void fm_directory_view_init_show_hidden_files (FMDirectoryView *view); +static void fm_directory_view_load_location (CajaView *caja_view, + const char *location); +static void fm_directory_view_stop_loading (CajaView *caja_view); +static void fm_directory_view_drop_proxy_received_uris (FMDirectoryView *view, + const GList *source_uri_list, + const char *target_uri, + GdkDragAction action); +static void fm_directory_view_drop_proxy_received_netscape_url (FMDirectoryView *view, + const char *netscape_url, + const char *target_uri, + GdkDragAction action); +static void clipboard_changed_callback (CajaClipboardMonitor *monitor, + FMDirectoryView *view); +static void open_one_in_new_window (gpointer data, + gpointer callback_data); +static void open_one_in_folder_window (gpointer data, + gpointer callback_data); +static void schedule_update_menus (FMDirectoryView *view); +static void schedule_update_menus_callback (gpointer callback_data); +static void remove_update_menus_timeout_callback (FMDirectoryView *view); +static void schedule_update_status (FMDirectoryView *view); +static void remove_update_status_idle_callback (FMDirectoryView *view); +static void reset_update_interval (FMDirectoryView *view); +static void schedule_idle_display_of_pending_files (FMDirectoryView *view); +static void unschedule_display_of_pending_files (FMDirectoryView *view); +static void disconnect_model_handlers (FMDirectoryView *view); +static void metadata_for_directory_as_file_ready_callback (CajaFile *file, + gpointer callback_data); +static void metadata_for_files_in_directory_ready_callback (CajaDirectory *directory, + GList *files, + gpointer callback_data); +static void fm_directory_view_trash_state_changed_callback (CajaTrashMonitor *trash, + gboolean state, + gpointer callback_data); +static void fm_directory_view_select_file (FMDirectoryView *view, + CajaFile *file); + +static GdkDragAction ask_link_action (FMDirectoryView *view); +static void update_templates_directory (FMDirectoryView *view); +static void user_dirs_changed (FMDirectoryView *view); +static void fm_directory_view_set_is_active (FMDirectoryView *view, + gboolean is_active); + +static gboolean file_list_all_are_folders (GList *file_list); + +static void action_open_scripts_folder_callback (GtkAction *action, + gpointer callback_data); +static void action_cut_files_callback (GtkAction *action, + gpointer callback_data); +static void action_copy_files_callback (GtkAction *action, + gpointer callback_data); +static void action_paste_files_callback (GtkAction *action, + gpointer callback_data); +static void action_copy_to_next_pane_callback (GtkAction *action, + gpointer callback_data); +static void action_move_to_next_pane_callback (GtkAction *action, + gpointer callback_data); +static void action_rename_callback (GtkAction *action, + gpointer callback_data); +static void action_rename_select_all_callback (GtkAction *action, + gpointer callback_data); +static void action_paste_files_into_callback (GtkAction *action, + gpointer callback_data); +static void action_connect_to_server_link_callback (GtkAction *action, + gpointer data); +static void action_mount_volume_callback (GtkAction *action, + gpointer data); +static void action_unmount_volume_callback (GtkAction *action, + gpointer data); +static void action_format_volume_callback (GtkAction *action, + gpointer data); +static void action_start_volume_callback (GtkAction *action, + gpointer data); +static void action_stop_volume_callback (GtkAction *action, + gpointer data); +static void action_detect_media_callback (GtkAction *action, + gpointer data); + +/* location popup-related actions */ + +static void action_location_open_alternate_callback (GtkAction *action, + gpointer callback_data); +static void action_location_open_folder_window_callback (GtkAction *action, + gpointer callback_data); + +static void action_location_cut_callback (GtkAction *action, + gpointer callback_data); +static void action_location_copy_callback (GtkAction *action, + gpointer callback_data); +static void action_location_trash_callback (GtkAction *action, + gpointer callback_data); +static void action_location_delete_callback (GtkAction *action, + gpointer callback_data); +static void action_location_properties_callback (GtkAction *action, + gpointer callback_data); + +static void unschedule_pop_up_location_context_menu (FMDirectoryView *view); + +static inline void fm_directory_view_widget_to_file_operation_position (FMDirectoryView *view, + GdkPoint *position); +static void fm_directory_view_widget_to_file_operation_position_xy (FMDirectoryView *view, + int *x, int *y); + +EEL_CLASS_BOILERPLATE (FMDirectoryView, fm_directory_view, GTK_TYPE_SCROLLED_WINDOW) + +EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, add_file) +EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, bump_zoom_level) +EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, can_zoom_in) +EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, can_zoom_out) +EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, clear) +EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, file_changed) +EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, get_background_widget) +EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, get_selection) +EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, get_selection_for_file_transfer) +EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, get_item_count) +EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, is_empty) +EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, reset_to_defaults) +EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, restore_default_zoom_level) +EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, select_all) +EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, set_selection) +EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, zoom_to_level) +EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, get_zoom_level) +EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, invert_selection) + +typedef struct { + GAppInfo *application; + GList *files; + FMDirectoryView *directory_view; +} ApplicationLaunchParameters; + +typedef struct { + CajaFile *file; + FMDirectoryView *directory_view; +} ScriptLaunchParameters; + +typedef struct { + CajaFile *file; + FMDirectoryView *directory_view; +} CreateTemplateParameters; + +static ApplicationLaunchParameters * +application_launch_parameters_new (GAppInfo *application, + GList *files, + FMDirectoryView *directory_view) +{ + ApplicationLaunchParameters *result; + + result = g_new0 (ApplicationLaunchParameters, 1); + result->application = g_object_ref (application); + result->files = caja_file_list_copy (files); + + if (directory_view != NULL) { + g_object_ref (directory_view); + result->directory_view = directory_view; + } + + return result; +} + +static void +application_launch_parameters_free (ApplicationLaunchParameters *parameters) +{ + g_object_unref (parameters->application); + caja_file_list_free (parameters->files); + + if (parameters->directory_view != NULL) { + g_object_unref (parameters->directory_view); + } + + g_free (parameters); +} + +static GList * +file_and_directory_list_to_files (GList *fad_list) +{ + GList *res, *l; + FileAndDirectory *fad; + + res = NULL; + for (l = fad_list; l != NULL; l = l->next) { + fad = l->data; + res = g_list_prepend (res, caja_file_ref (fad->file)); + } + return g_list_reverse (res); +} + + +static GList * +file_and_directory_list_from_files (CajaDirectory *directory, GList *files) +{ + GList *res, *l; + FileAndDirectory *fad; + + res = NULL; + for (l = files; l != NULL; l = l->next) { + fad = g_new0 (FileAndDirectory, 1); + fad->directory = caja_directory_ref (directory); + fad->file = caja_file_ref (l->data); + res = g_list_prepend (res, fad); + } + return g_list_reverse (res); +} + +static void +file_and_directory_free (FileAndDirectory *fad) +{ + caja_directory_unref (fad->directory); + caja_file_unref (fad->file); + g_free (fad); +} + + +static void +file_and_directory_list_free (GList *list) +{ + GList *l; + + for (l = list; l != NULL; l = l->next) { + file_and_directory_free (l->data); + } + + g_list_free (list); +} + +static gboolean +file_and_directory_equal (gconstpointer v1, + gconstpointer v2) +{ + const FileAndDirectory *fad1, *fad2; + fad1 = v1; + fad2 = v2; + + return (fad1->file == fad2->file && + fad1->directory == fad2->directory); +} + +static guint +file_and_directory_hash (gconstpointer v) +{ + const FileAndDirectory *fad; + + fad = v; + return GPOINTER_TO_UINT (fad->file) ^ GPOINTER_TO_UINT (fad->directory); +} + + + + +static ScriptLaunchParameters * +script_launch_parameters_new (CajaFile *file, + FMDirectoryView *directory_view) +{ + ScriptLaunchParameters *result; + + result = g_new0 (ScriptLaunchParameters, 1); + g_object_ref (directory_view); + result->directory_view = directory_view; + caja_file_ref (file); + result->file = file; + + return result; +} + +static void +script_launch_parameters_free (ScriptLaunchParameters *parameters) +{ + g_object_unref (parameters->directory_view); + caja_file_unref (parameters->file); + g_free (parameters); +} + +static CreateTemplateParameters * +create_template_parameters_new (CajaFile *file, + FMDirectoryView *directory_view) +{ + CreateTemplateParameters *result; + + result = g_new0 (CreateTemplateParameters, 1); + g_object_ref (directory_view); + result->directory_view = directory_view; + caja_file_ref (file); + result->file = file; + + return result; +} + +static void +create_templates_parameters_free (CreateTemplateParameters *parameters) +{ + g_object_unref (parameters->directory_view); + caja_file_unref (parameters->file); + g_free (parameters); +} + +CajaWindowInfo * +fm_directory_view_get_caja_window (FMDirectoryView *view) +{ + g_assert (view->details->window != NULL); + + return view->details->window; +} + +CajaWindowSlotInfo * +fm_directory_view_get_caja_window_slot (FMDirectoryView *view) +{ + g_assert (view->details->slot != NULL); + + return view->details->slot; +} + +/* Returns the GtkWindow that this directory view occupies, or NULL + * if at the moment this directory view is not in a GtkWindow or the + * GtkWindow cannot be determined. Primarily used for parenting dialogs. + */ +GtkWindow * +fm_directory_view_get_containing_window (FMDirectoryView *view) +{ + GtkWidget *window; + + g_assert (FM_IS_DIRECTORY_VIEW (view)); + + window = gtk_widget_get_ancestor (GTK_WIDGET (view), GTK_TYPE_WINDOW); + if (window == NULL) { + return NULL; + } + + return GTK_WINDOW (window); +} + +static gboolean +fm_directory_view_confirm_multiple (GtkWindow *parent_window, + int count, + gboolean 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 (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; +} + +static gboolean +selection_contains_one_item_in_menu_callback (FMDirectoryView *view, GList *selection) +{ + if (eel_g_list_exactly_one_item (selection)) { + return TRUE; + } + + /* If we've requested a menu update that hasn't yet occurred, then + * the mismatch here doesn't surprise us, and we won't complain. + * Otherwise, we will complain. + */ + if (!view->details->menu_states_untrustworthy) { + g_warning ("Expected one selected item, found %'d. No action will be performed.", + g_list_length (selection)); + } + + return FALSE; +} + +static gboolean +selection_not_empty_in_menu_callback (FMDirectoryView *view, GList *selection) +{ + if (selection != NULL) { + return TRUE; + } + + /* If we've requested a menu update that hasn't yet occurred, then + * the mismatch here doesn't surprise us, and we won't complain. + * Otherwise, we will complain. + */ + if (!view->details->menu_states_untrustworthy) { + g_warning ("Empty selection found when selection was expected. No action will be performed."); + } + + return FALSE; +} + +static char * +get_view_directory (FMDirectoryView *view) +{ + char *uri, *path; + GFile *f; + + uri = caja_directory_get_uri (view->details->model); + if (eel_uri_is_desktop (uri)) { + g_free (uri); + uri = caja_get_desktop_directory_uri (); + + } + f = g_file_new_for_uri (uri); + path = g_file_get_path (f); + g_object_unref (f); + g_free (uri); + + return path; +} + +void +fm_directory_view_activate_files (FMDirectoryView *view, + GList *files, + CajaWindowOpenMode mode, + CajaWindowOpenFlags flags, + gboolean confirm_multiple) +{ + char *path; + + path = get_view_directory (view); + caja_mime_activate_files (fm_directory_view_get_containing_window (view), + view->details->slot, + files, + path, + mode, + flags, + confirm_multiple); + + g_free (path); +} + +void +fm_directory_view_activate_file (FMDirectoryView *view, + CajaFile *file, + CajaWindowOpenMode mode, + CajaWindowOpenFlags flags) +{ + char *path; + + path = get_view_directory (view); + caja_mime_activate_file (fm_directory_view_get_containing_window (view), + view->details->slot, + file, + path, + mode, + flags); + + g_free (path); +} + +static void +action_open_callback (GtkAction *action, + gpointer callback_data) +{ + GList *selection; + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (callback_data); + + selection = fm_directory_view_get_selection (view); + fm_directory_view_activate_files (view, + selection, + CAJA_WINDOW_OPEN_ACCORDING_TO_MODE, + 0, + TRUE); + caja_file_list_free (selection); +} + +static void +action_open_close_parent_callback (GtkAction *action, + gpointer callback_data) +{ + GList *selection; + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (callback_data); + + selection = fm_directory_view_get_selection (view); + fm_directory_view_activate_files (view, + selection, + CAJA_WINDOW_OPEN_ACCORDING_TO_MODE, + CAJA_WINDOW_OPEN_FLAG_CLOSE_BEHIND, + TRUE); + caja_file_list_free (selection); +} + + +static void +action_open_alternate_callback (GtkAction *action, + gpointer callback_data) +{ + FMDirectoryView *view; + GList *selection; + GtkWindow *window; + + view = FM_DIRECTORY_VIEW (callback_data); + selection = fm_directory_view_get_selection (view); + + window = fm_directory_view_get_containing_window (view); + + if (fm_directory_view_confirm_multiple (window, g_list_length (selection), FALSE)) { + g_list_foreach (selection, open_one_in_new_window, view); + } + + caja_file_list_free (selection); +} + +static void +action_open_new_tab_callback (GtkAction *action, + gpointer callback_data) +{ + FMDirectoryView *view; + GList *selection; + GtkWindow *window; + + view = FM_DIRECTORY_VIEW (callback_data); + selection = fm_directory_view_get_selection (view); + + window = fm_directory_view_get_containing_window (view); + + if (fm_directory_view_confirm_multiple (window, g_list_length (selection), TRUE)) { + fm_directory_view_activate_files (view, + selection, + CAJA_WINDOW_OPEN_ACCORDING_TO_MODE, + CAJA_WINDOW_OPEN_FLAG_NEW_TAB, + FALSE); + } + + caja_file_list_free (selection); +} + +static void +action_open_folder_window_callback (GtkAction *action, + gpointer callback_data) +{ + FMDirectoryView *view; + GList *selection; + GtkWindow *window; + + view = FM_DIRECTORY_VIEW (callback_data); + selection = fm_directory_view_get_selection (view); + + window = fm_directory_view_get_containing_window (view); + + if (fm_directory_view_confirm_multiple (window, g_list_length (selection), FALSE)) { + g_list_foreach (selection, open_one_in_folder_window, view); + } + + caja_file_list_free (selection); +} + +static void +open_location (FMDirectoryView *directory_view, + const char *new_uri, + CajaWindowOpenMode mode, + CajaWindowOpenFlags flags) +{ + GtkWindow *window; + GFile *location; + + g_assert (FM_IS_DIRECTORY_VIEW (directory_view)); + g_assert (new_uri != NULL); + + window = fm_directory_view_get_containing_window (directory_view); + caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, + "directory view open_location window=%p: %s", window, new_uri); + location = g_file_new_for_uri (new_uri); + caja_window_slot_info_open_location (directory_view->details->slot, + location, mode, flags, NULL); + g_object_unref (location); +} + +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), "directory-view:file"); + + files.next = NULL; + files.prev = NULL; + files.data = file; + caja_launch_application (app, &files, parent_window); +} + +static void +choose_program (FMDirectoryView *view, + CajaFile *file) +{ + GtkWidget *dialog; + char *uri; + char *mime_type; + + g_assert (FM_IS_DIRECTORY_VIEW (view)); + 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), + "directory-view:file", + g_object_ref (file), + (GDestroyNotify)g_object_unref); + + gtk_window_set_screen (GTK_WINDOW (dialog), + gtk_widget_get_screen (GTK_WIDGET (view))); + gtk_widget_show (dialog); + + g_signal_connect_object (dialog, + "application_selected", + G_CALLBACK (application_selected_cb), + fm_directory_view_get_containing_window (view), + 0); + + g_free (uri); + g_free (mime_type); + caja_file_unref (file); +} + +static void +open_with_other_program (FMDirectoryView *view) +{ + GList *selection; + + g_assert (FM_IS_DIRECTORY_VIEW (view)); + + selection = fm_directory_view_get_selection (view); + + if (selection_contains_one_item_in_menu_callback (view, selection)) { + choose_program (view, CAJA_FILE (selection->data)); + } + + caja_file_list_free (selection); +} + +static void +action_other_application_callback (GtkAction *action, + gpointer callback_data) +{ + g_assert (FM_IS_DIRECTORY_VIEW (callback_data)); + + open_with_other_program (FM_DIRECTORY_VIEW (callback_data)); +} + +static void +trash_or_delete_selected_files (FMDirectoryView *view) +{ + GList *selection; + + /* This might be rapidly called multiple times for the same selection + * when using keybindings. So we remember if the current selection + * was already removed (but the view doesn't know about it yet). + */ + if (!view->details->selection_was_removed) { + selection = fm_directory_view_get_selection_for_file_transfer (view); + trash_or_delete_files (fm_directory_view_get_containing_window (view), + selection, TRUE, + view); + caja_file_list_free (selection); + view->details->selection_was_removed = TRUE; + } +} + +static gboolean +real_trash (FMDirectoryView *view) +{ + GtkAction *action; + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_TRASH); + if (gtk_action_get_sensitive (action) && + gtk_action_get_visible (action)) { + trash_or_delete_selected_files (view); + return TRUE; + } + return FALSE; +} + +static void +action_trash_callback (GtkAction *action, + gpointer callback_data) +{ + trash_or_delete_selected_files (FM_DIRECTORY_VIEW (callback_data)); +} + +static void +delete_selected_files (FMDirectoryView *view) +{ + GList *selection; + GList *node; + GList *locations; + + selection = fm_directory_view_get_selection_for_file_transfer (view); + if (selection == NULL) { + return; + } + + locations = NULL; + for (node = selection; 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_delete (locations, fm_directory_view_get_containing_window (view), NULL, NULL); + + eel_g_object_list_free (locations); + caja_file_list_free (selection); +} + +static void +action_delete_callback (GtkAction *action, + gpointer callback_data) +{ + delete_selected_files (FM_DIRECTORY_VIEW (callback_data)); +} + +static void +action_restore_from_trash_callback (GtkAction *action, + gpointer callback_data) +{ + FMDirectoryView *view; + GList *selection; + + view = FM_DIRECTORY_VIEW (callback_data); + + selection = fm_directory_view_get_selection_for_file_transfer (view); + caja_restore_files_from_trash (selection, + fm_directory_view_get_containing_window (view)); + + caja_file_list_free (selection); + +} + +static gboolean +real_delete (FMDirectoryView *view) +{ + GtkAction *action; + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_DELETE); + if (gtk_action_get_sensitive (action) && + gtk_action_get_visible (action)) { + delete_selected_files (view); + return TRUE; + } + return FALSE; +} + +static void +action_duplicate_callback (GtkAction *action, + gpointer callback_data) +{ + FMDirectoryView *view; + GList *selection; + GArray *selected_item_locations; + + view = FM_DIRECTORY_VIEW (callback_data); + selection = fm_directory_view_get_selection_for_file_transfer (view); + if (selection_not_empty_in_menu_callback (view, selection)) { + /* FIXME bugzilla.gnome.org 45061: + * should change things here so that we use a get_icon_locations (view, selection). + * Not a problem in this case but in other places the selection may change by + * the time we go and retrieve the icon positions, relying on the selection + * staying intact to ensure the right sequence and count of positions is fragile. + */ + selected_item_locations = fm_directory_view_get_selected_icon_locations (view); + fm_directory_view_duplicate_selection (view, selection, selected_item_locations); + g_array_free (selected_item_locations, TRUE); + } + + caja_file_list_free (selection); +} + +static void +action_create_link_callback (GtkAction *action, + gpointer callback_data) +{ + FMDirectoryView *view; + GList *selection; + GArray *selected_item_locations; + + g_assert (FM_IS_DIRECTORY_VIEW (callback_data)); + + view = FM_DIRECTORY_VIEW (callback_data); + selection = fm_directory_view_get_selection (view); + if (selection_not_empty_in_menu_callback (view, selection)) { + selected_item_locations = fm_directory_view_get_selected_icon_locations (view); + fm_directory_view_create_links_for_files (view, selection, selected_item_locations); + g_array_free (selected_item_locations, TRUE); + } + + caja_file_list_free (selection); +} + +static void +action_select_all_callback (GtkAction *action, + gpointer callback_data) +{ + g_assert (FM_IS_DIRECTORY_VIEW (callback_data)); + + fm_directory_view_select_all (callback_data); +} + +static void +action_invert_selection_callback (GtkAction *action, + gpointer callback_data) +{ + g_assert (FM_IS_DIRECTORY_VIEW (callback_data)); + + fm_directory_view_invert_selection (callback_data); +} + + +static void +pattern_select_response_cb (GtkWidget *dialog, int response, gpointer user_data) +{ + FMDirectoryView *view; + CajaDirectory *directory; + GtkWidget *entry; + GList *selection; + GError *error; + + view = FM_DIRECTORY_VIEW (user_data); + + switch (response) { + case GTK_RESPONSE_OK : + entry = g_object_get_data (G_OBJECT (dialog), "entry"); + directory = fm_directory_view_get_model (view); + selection = caja_directory_match_pattern (directory, + gtk_entry_get_text (GTK_ENTRY (entry))); + + if (selection) { + fm_directory_view_set_selection (view, selection); + caja_file_list_free (selection); + + fm_directory_view_reveal_selection(view); + } + /* fall through */ + case GTK_RESPONSE_NONE : + case GTK_RESPONSE_DELETE_EVENT : + case GTK_RESPONSE_CANCEL : + gtk_widget_destroy (GTK_WIDGET (dialog)); + break; + case GTK_RESPONSE_HELP : + error = NULL; + gtk_show_uri (gtk_window_get_screen (GTK_WINDOW (dialog)), + "ghelp:user-guide#caja-select-pattern", + gtk_get_current_event_time (), &error); + if (error) { + eel_show_error_dialog (_("There was an error displaying help."), error->message, + GTK_WINDOW (dialog)); + g_error_free (error); + } + break; + default : + g_assert_not_reached (); + } +} + +static void +select_pattern (FMDirectoryView *view) +{ + GtkWidget *dialog; + GtkWidget *label; + GtkWidget *example; + GtkWidget *table; + GtkWidget *entry; + GList *ret; + char *example_pattern; + + ret = NULL; + dialog = gtk_dialog_new_with_buttons (_("Select Items Matching"), + fm_directory_view_get_containing_window (view), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_HELP, + GTK_RESPONSE_HELP, + GTK_STOCK_CANCEL, + GTK_RESPONSE_CANCEL, + GTK_STOCK_OK, + GTK_RESPONSE_OK, + NULL); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), + GTK_RESPONSE_OK); + gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE); + gtk_container_set_border_width (GTK_CONTAINER (dialog), 5); + gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), 2); + + label = gtk_label_new_with_mnemonic (_("_Pattern:")); + example = gtk_label_new (NULL); + example_pattern = g_strdup_printf ("<b>%s</b><i>%s</i>", + _("Examples: "), + "*.png, file\?\?.txt, pict*.\?\?\?"); + gtk_label_set_markup (GTK_LABEL (example), example_pattern); + g_free (example_pattern); + gtk_misc_set_alignment (GTK_MISC (example), 0.0, 0.5); + entry = gtk_entry_new (); + gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE); + + table = gtk_table_new (2, 2, FALSE); + + gtk_table_attach (GTK_TABLE (table), label, + 0, 1, + 0, 1, + GTK_FILL, GTK_FILL, + 5, 5); + + gtk_table_attach (GTK_TABLE (table), entry, + 1, 2, + 0, 1, + GTK_EXPAND | GTK_FILL, GTK_FILL, + 5, 5); + + gtk_table_attach (GTK_TABLE (table), example, + 1, 2, + 1, 2, + GTK_FILL, GTK_FILL, + 5, 0); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry); + gtk_widget_show_all (table); + gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), table); + g_object_set_data (G_OBJECT (dialog), "entry", entry); + g_signal_connect (dialog, "response", + G_CALLBACK (pattern_select_response_cb), + view); + gtk_widget_show_all (dialog); +} + +static void +action_select_pattern_callback (GtkAction *action, + gpointer callback_data) +{ + g_assert (FM_IS_DIRECTORY_VIEW (callback_data)); + + select_pattern(callback_data); +} + +static void +action_reset_to_defaults_callback (GtkAction *action, + gpointer callback_data) +{ + g_assert (FM_IS_DIRECTORY_VIEW (callback_data)); + + fm_directory_view_reset_to_defaults (callback_data); +} + + +static void +hidden_files_mode_changed (CajaWindow *window, + gpointer callback_data) +{ + FMDirectoryView *directory_view; + + directory_view = FM_DIRECTORY_VIEW (callback_data); + + fm_directory_view_init_show_hidden_files (directory_view); +} + +static void +action_save_search_callback (GtkAction *action, + gpointer callback_data) +{ + CajaSearchDirectory *search; + FMDirectoryView *directory_view; + + directory_view = FM_DIRECTORY_VIEW (callback_data); + + if (directory_view->details->model && + CAJA_IS_SEARCH_DIRECTORY (directory_view->details->model)) { + search = CAJA_SEARCH_DIRECTORY (directory_view->details->model); + caja_search_directory_save_search (search); + + /* Save search is disabled */ + schedule_update_menus (directory_view); + } +} + +static void +query_name_entry_changed_cb (GtkWidget *entry, GtkWidget *button) +{ + const char *text; + gboolean sensitive; + + text = gtk_entry_get_text (GTK_ENTRY (entry)); + + sensitive = (text != NULL) && (*text != 0); + + gtk_widget_set_sensitive (button, sensitive); +} + + +static void +action_save_search_as_callback (GtkAction *action, + gpointer callback_data) +{ + FMDirectoryView *directory_view; + CajaSearchDirectory *search; + CajaQuery *query; + GtkWidget *dialog, *table, *label, *entry, *chooser, *save_button; + const char *entry_text; + char *filename, *filename_utf8, *dirname, *path, *uri; + GFile *location; + + directory_view = FM_DIRECTORY_VIEW (callback_data); + + if (directory_view->details->model && + CAJA_IS_SEARCH_DIRECTORY (directory_view->details->model)) { + search = CAJA_SEARCH_DIRECTORY (directory_view->details->model); + + query = caja_search_directory_get_query (search); + + dialog = gtk_dialog_new_with_buttons (_("Save Search as"), + fm_directory_view_get_containing_window (directory_view), + GTK_DIALOG_NO_SEPARATOR, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + NULL); + save_button = gtk_dialog_add_button (GTK_DIALOG (dialog), + GTK_STOCK_SAVE, GTK_RESPONSE_OK); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), + GTK_RESPONSE_OK); + gtk_container_set_border_width (GTK_CONTAINER (dialog), 5); + gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), 2); + gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); + + table = gtk_table_new (2, 2, FALSE); + gtk_container_set_border_width (GTK_CONTAINER (table), 5); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_col_spacings (GTK_TABLE (table), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), table, TRUE, TRUE, 0); + gtk_widget_show (table); + + label = gtk_label_new_with_mnemonic (_("Search _name:")); + gtk_misc_set_alignment (GTK_MISC(label), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0); + gtk_widget_show (label); + entry = gtk_entry_new (); + gtk_table_attach (GTK_TABLE (table), entry, 1, 2, 0, 1, GTK_FILL | GTK_EXPAND, 0, 0, 0); + gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry); + + gtk_widget_set_sensitive (save_button, FALSE); + g_signal_connect (entry, "changed", + G_CALLBACK (query_name_entry_changed_cb), save_button); + + gtk_widget_show (entry); + label = gtk_label_new_with_mnemonic (_("_Folder:")); + gtk_misc_set_alignment (GTK_MISC(label), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2, GTK_FILL, 0, 0, 0); + gtk_widget_show (label); + + chooser = gtk_file_chooser_button_new (_("Select Folder to Save Search In"), + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER); + gtk_table_attach (GTK_TABLE (table), chooser, 1, 2, 1, 2, GTK_FILL | GTK_EXPAND, 0, 0, 0); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), chooser); + gtk_widget_show (chooser); + + gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (chooser), TRUE); + + gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (chooser), + g_get_home_dir ()); + + if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) { + entry_text = gtk_entry_get_text (GTK_ENTRY (entry)); + if (g_str_has_suffix (entry_text, CAJA_SAVED_SEARCH_EXTENSION)) { + filename_utf8 = g_strdup (entry_text); + } else { + filename_utf8 = g_strconcat (entry_text, CAJA_SAVED_SEARCH_EXTENSION, NULL); + } + + filename = g_filename_from_utf8 (filename_utf8, -1, NULL, NULL, NULL); + g_free (filename_utf8); + + dirname = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (chooser)); + + path = g_build_filename (dirname, filename, NULL); + g_free (filename); + g_free (dirname); + + uri = g_filename_to_uri (path, NULL, NULL); + g_free (path); + + caja_search_directory_save_to_file (search, uri); + location = g_file_new_for_uri (uri); + caja_file_changes_queue_file_added (location); + g_object_unref (location); + caja_file_changes_consume_changes (TRUE); + g_free (uri); + } + + gtk_widget_destroy (dialog); + } +} + + +static void +action_empty_trash_callback (GtkAction *action, + gpointer callback_data) +{ + g_assert (FM_IS_DIRECTORY_VIEW (callback_data)); + + caja_file_operations_empty_trash (GTK_WIDGET (callback_data)); +} + +static void +action_new_folder_callback (GtkAction *action, + gpointer callback_data) +{ + g_assert (FM_IS_DIRECTORY_VIEW (callback_data)); + + fm_directory_view_new_folder (FM_DIRECTORY_VIEW (callback_data)); +} + +static void +action_new_empty_file_callback (GtkAction *action, + gpointer callback_data) +{ + g_assert (FM_IS_DIRECTORY_VIEW (callback_data)); + + fm_directory_view_new_file (FM_DIRECTORY_VIEW (callback_data), NULL, NULL); +} + +static void +action_new_launcher_callback (GtkAction *action, + gpointer callback_data) +{ + char *parent_uri; + FMDirectoryView *view; + GtkWindow *window; + + g_assert (FM_IS_DIRECTORY_VIEW (callback_data)); + + view = FM_DIRECTORY_VIEW (callback_data); + + parent_uri = fm_directory_view_get_backing_uri (view); + + window = fm_directory_view_get_containing_window (view); + caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, + "directory view create new launcher in window=%p: %s", window, parent_uri); + caja_launch_application_from_command (gtk_widget_get_screen (GTK_WIDGET (view)), + "mate-desktop-item-edit", + "mate-desktop-item-edit", + FALSE, + "--create-new", parent_uri, NULL); + + g_free (parent_uri); +} + +static void +action_properties_callback (GtkAction *action, + gpointer callback_data) +{ + FMDirectoryView *view; + GList *selection; + GList *files; + + g_assert (FM_IS_DIRECTORY_VIEW (callback_data)); + + view = FM_DIRECTORY_VIEW (callback_data); + selection = fm_directory_view_get_selection (view); + if (g_list_length (selection) == 0) { + if (view->details->directory_as_file != NULL) { + files = g_list_append (NULL, caja_file_ref (view->details->directory_as_file)); + + fm_properties_window_present (files, GTK_WIDGET (view)); + + caja_file_list_free (files); + } + } else { + fm_properties_window_present (selection, GTK_WIDGET (view)); + } + caja_file_list_free (selection); +} + +static void +action_location_properties_callback (GtkAction *action, + gpointer callback_data) +{ + FMDirectoryView *view; + GList *files; + + g_assert (FM_IS_DIRECTORY_VIEW (callback_data)); + + view = FM_DIRECTORY_VIEW (callback_data); + g_assert (CAJA_IS_FILE (view->details->location_popup_directory_as_file)); + + files = g_list_append (NULL, caja_file_ref (view->details->location_popup_directory_as_file)); + + fm_properties_window_present (files, GTK_WIDGET (view)); + + caja_file_list_free (files); +} + +static gboolean +all_files_in_trash (GList *files) +{ + GList *node; + + /* Result is ambiguous if called on NULL, so disallow. */ + g_return_val_if_fail (files != NULL, FALSE); + + for (node = files; node != NULL; node = node->next) { + if (!caja_file_is_in_trash (CAJA_FILE (node->data))) { + return FALSE; + } + } + + return TRUE; +} + +static gboolean +all_selected_items_in_trash (FMDirectoryView *view) +{ + GList *selection; + gboolean result; + + /* If the contents share a parent directory, we need only + * check that parent directory. Otherwise we have to inspect + * each selected item. + */ + selection = fm_directory_view_get_selection (view); + result = (selection == NULL) ? FALSE : all_files_in_trash (selection); + caja_file_list_free (selection); + + return result; +} + +static gboolean +we_are_in_vfolder_desktop_dir (FMDirectoryView *view) +{ + CajaFile *file; + char *mime_type; + + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + + if (view->details->model == NULL) { + return FALSE; + } + + file = caja_directory_get_corresponding_file (view->details->model); + mime_type = caja_file_get_mime_type (file); + caja_file_unref (file); + + if (mime_type != NULL + && strcmp (mime_type, "x-directory/vfolder-desktop") == 0) { + g_free (mime_type); + return TRUE; + } else { + g_free (mime_type); + return FALSE; + } +} + +/* Preferences changed callbacks */ +static void +text_attribute_names_changed_callback (gpointer callback_data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (callback_data); + + EEL_CALL_METHOD + (FM_DIRECTORY_VIEW_CLASS, view, + text_attribute_names_changed, (view)); +} + +static void +image_display_policy_changed_callback (gpointer callback_data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (callback_data); + + EEL_CALL_METHOD + (FM_DIRECTORY_VIEW_CLASS, view, + image_display_policy_changed, (view)); +} + +static void +click_policy_changed_callback (gpointer callback_data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (callback_data); + + EEL_CALL_METHOD + (FM_DIRECTORY_VIEW_CLASS, view, + click_policy_changed, (view)); +} + +gboolean +fm_directory_view_should_sort_directories_first (FMDirectoryView *view) +{ + return view->details->sort_directories_first; +} + +static void +sort_directories_first_changed_callback (gpointer callback_data) +{ + FMDirectoryView *view; + gboolean preference_value; + + view = FM_DIRECTORY_VIEW (callback_data); + + preference_value = + eel_preferences_get_boolean (CAJA_PREFERENCES_SORT_DIRECTORIES_FIRST); + + if (preference_value != view->details->sort_directories_first) { + view->details->sort_directories_first = preference_value; + EEL_CALL_METHOD + (FM_DIRECTORY_VIEW_CLASS, view, + sort_directories_first_changed, (view)); + } +} + +static void +lockdown_disable_command_line_changed_callback (gpointer callback_data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (callback_data); + schedule_update_menus (view); +} + +static void set_up_scripts_directory_global(void) +{ + if (scripts_directory_uri != NULL) + { + return; + } + + char* scripts_directory_path; + const char* override = g_getenv ("MATE22_USER_DIR"); //TODO: quitar? + + if (override) + { + scripts_directory_path = g_build_filename(override, "caja", "scripts", NULL); + } + else + { + scripts_directory_path = g_build_filename(g_get_home_dir(), ".config", "caja", "scripts", NULL); + } + + if (g_mkdir_with_parents(scripts_directory_path, 0755) == 0) + { + scripts_directory_uri = g_filename_to_uri(scripts_directory_path, NULL, NULL); + scripts_directory_uri_length = strlen(scripts_directory_uri); + + /* Emulación de GNOME Nautilus scripts + */ + char* nautilus_scripts_path = g_build_filename(g_get_home_dir(), ".gnome2", "nautilus-scripts", NULL); + + if (g_file_test(nautilus_scripts_path, G_FILE_TEST_IS_DIR) == TRUE) + { + char* nautilus_syslink = g_build_filename(g_get_home_dir(), ".config", "caja", "scripts", "nautilus", NULL); + // G_FILE_TEST_IS_REGULAR + /* En caso de que exista el enlace, o algún otro tipo de archivo con + * el mismo nombre, ignoramos. Incluso si es una carpeta. */ + if (g_file_test(nautilus_syslink, G_FILE_TEST_IS_SYMLINK) == FALSE && + g_file_test(nautilus_syslink, G_FILE_TEST_EXISTS) == FALSE && + g_file_test(nautilus_syslink, G_FILE_TEST_IS_DIR) == FALSE) + { + /* Nos fijamos si es necesario crear un enlace */ + GDir* dir = g_dir_open(nautilus_scripts_path, 0, NULL); + + if (dir) + { + /* Con tener más de un elemento en la carpeta, podemos hacer + * el enlace */ + int count = 0; + + while (g_dir_read_name(dir) != NULL) + { + count++; + } + + if (count > 0) + { + /* creamos un enlace a la carpeta de nautilus */ + symlink(nautilus_scripts_path, nautilus_syslink); + } + + g_dir_close(dir); + } + } + + g_free(nautilus_syslink); + } + + g_free(nautilus_scripts_path); + } + + g_free(scripts_directory_path); +} + +static void +scripts_added_or_changed_callback (CajaDirectory *directory, + GList *files, + gpointer callback_data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (callback_data); + + view->details->scripts_invalid = TRUE; + if (view->details->active) { + schedule_update_menus (view); + } +} + +static void +templates_added_or_changed_callback (CajaDirectory *directory, + GList *files, + gpointer callback_data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (callback_data); + + view->details->templates_invalid = TRUE; + if (view->details->active) { + schedule_update_menus (view); + } +} + +static void +add_directory_to_directory_list (FMDirectoryView *view, + CajaDirectory *directory, + GList **directory_list, + GCallback changed_callback) +{ + CajaFileAttributes attributes; + + if (g_list_find (*directory_list, directory) == NULL) { + caja_directory_ref (directory); + + attributes = + CAJA_FILE_ATTRIBUTES_FOR_ICON | + CAJA_FILE_ATTRIBUTE_INFO | + CAJA_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT; + + caja_directory_file_monitor_add (directory, directory_list, + FALSE, FALSE, attributes, + (CajaDirectoryCallback)changed_callback, view); + + g_signal_connect_object (directory, "files_added", + G_CALLBACK (changed_callback), view, 0); + g_signal_connect_object (directory, "files_changed", + G_CALLBACK (changed_callback), view, 0); + + *directory_list = g_list_append (*directory_list, directory); + } +} + +static void +remove_directory_from_directory_list (FMDirectoryView *view, + CajaDirectory *directory, + GList **directory_list, + GCallback changed_callback) +{ + *directory_list = g_list_remove (*directory_list, directory); + + g_signal_handlers_disconnect_by_func (directory, + G_CALLBACK (changed_callback), + view); + + caja_directory_file_monitor_remove (directory, directory_list); + + caja_directory_unref (directory); +} + + +static void +add_directory_to_scripts_directory_list (FMDirectoryView *view, + CajaDirectory *directory) +{ + add_directory_to_directory_list (view, directory, + &view->details->scripts_directory_list, + G_CALLBACK (scripts_added_or_changed_callback)); +} + +static void +remove_directory_from_scripts_directory_list (FMDirectoryView *view, + CajaDirectory *directory) +{ + remove_directory_from_directory_list (view, directory, + &view->details->scripts_directory_list, + G_CALLBACK (scripts_added_or_changed_callback)); +} + +static void +add_directory_to_templates_directory_list (FMDirectoryView *view, + CajaDirectory *directory) +{ + add_directory_to_directory_list (view, directory, + &view->details->templates_directory_list, + G_CALLBACK (templates_added_or_changed_callback)); +} + +static void +remove_directory_from_templates_directory_list (FMDirectoryView *view, + CajaDirectory *directory) +{ + remove_directory_from_directory_list (view, directory, + &view->details->templates_directory_list, + G_CALLBACK (templates_added_or_changed_callback)); +} + +static void +slot_active (CajaWindowSlot *slot, + FMDirectoryView *view) +{ + g_assert (!view->details->active); + view->details->active = TRUE; + + fm_directory_view_merge_menus (view); + schedule_update_menus (view); +} + +static void +slot_inactive (CajaWindowSlot *slot, + FMDirectoryView *view) +{ + g_assert (view->details->active || + gtk_widget_get_parent (GTK_WIDGET (view)) == NULL); + view->details->active = FALSE; + + fm_directory_view_unmerge_menus (view); + remove_update_menus_timeout_callback (view); +} + +static void +fm_directory_view_grab_focus (CajaView *view) +{ + /* focus the child of the scrolled window if it exists */ + GtkWidget *child; + child = gtk_bin_get_child (GTK_BIN (view)); + if (child) { + gtk_widget_grab_focus (GTK_WIDGET (child)); + } +} + +static void +view_iface_update_menus (CajaView *view) +{ + fm_directory_view_update_menus (FM_DIRECTORY_VIEW (view)); +} + +static GtkWidget * +fm_directory_view_get_widget (CajaView *view) +{ + return GTK_WIDGET (view); +} + +static int +fm_directory_view_get_selection_count (CajaView *view) +{ + /* FIXME: This could be faster if we special cased it in subclasses */ + GList *files; + int len; + + files = fm_directory_view_get_selection (FM_DIRECTORY_VIEW (view)); + len = g_list_length (files); + caja_file_list_free (files); + + return len; +} + +static GList * +fm_directory_view_get_selection_locations (CajaView *view) +{ + GList *files; + GList *locations; + GFile *location; + GList *l; + + files = fm_directory_view_get_selection (FM_DIRECTORY_VIEW (view)); + locations = NULL; + for (l = files; l != NULL; l = l->next) { + location = caja_file_get_location (CAJA_FILE (l->data)); + locations = g_list_prepend (locations, location); + } + caja_file_list_free (files); + + return g_list_reverse (locations); +} + +static GList * +file_list_from_location_list (const GList *uri_list) +{ + GList *file_list; + const GList *node; + + file_list = NULL; + for (node = uri_list; node != NULL; node = node->next) { + file_list = g_list_prepend + (file_list, + caja_file_get (node->data)); + } + return g_list_reverse (file_list); +} + +static void +fm_directory_view_set_selection_locations (CajaView *caja_view, + GList *selection_locations) +{ + GList *selection; + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (caja_view); + + if (!view->details->loading) { + /* If we aren't still loading, set the selection right now, + * and reveal the new selection. + */ + selection = file_list_from_location_list (selection_locations); + view->details->selection_change_is_due_to_shell = TRUE; + fm_directory_view_set_selection (view, selection); + view->details->selection_change_is_due_to_shell = FALSE; + fm_directory_view_reveal_selection (view); + caja_file_list_free (selection); + } else { + /* If we are still loading, set the list of pending URIs instead. + * done_loading() will eventually select the pending URIs and reveal them. + */ + eel_g_object_list_free (view->details->pending_locations_selected); + view->details->pending_locations_selected = + eel_g_object_list_copy (selection_locations); + } +} + + +void +fm_directory_view_init_view_iface (CajaViewIface *iface) +{ + iface->grab_focus = fm_directory_view_grab_focus; + iface->update_menus = view_iface_update_menus; + + iface->get_widget = fm_directory_view_get_widget; + iface->load_location = fm_directory_view_load_location; + iface->stop_loading = fm_directory_view_stop_loading; + + iface->get_selection_count = fm_directory_view_get_selection_count; + iface->get_selection = fm_directory_view_get_selection_locations; + iface->set_selection = fm_directory_view_set_selection_locations; + iface->set_is_active = (gpointer)fm_directory_view_set_is_active; + + iface->supports_zooming = (gpointer)fm_directory_view_supports_zooming; + iface->bump_zoom_level = (gpointer)fm_directory_view_bump_zoom_level; + iface->zoom_to_level = (gpointer)fm_directory_view_zoom_to_level; + iface->restore_default_zoom_level = (gpointer)fm_directory_view_restore_default_zoom_level; + iface->can_zoom_in = (gpointer)fm_directory_view_can_zoom_in; + iface->can_zoom_out = (gpointer)fm_directory_view_can_zoom_out; + iface->get_zoom_level = (gpointer)fm_directory_view_get_zoom_level; + + iface->pop_up_location_context_menu = (gpointer)fm_directory_view_pop_up_location_context_menu; + iface->drop_proxy_received_uris = (gpointer)fm_directory_view_drop_proxy_received_uris; + iface->drop_proxy_received_netscape_url = (gpointer)fm_directory_view_drop_proxy_received_netscape_url; +} + +static void +fm_directory_view_init (FMDirectoryView *view) +{ + static gboolean setup_autos = FALSE; + CajaDirectory *scripts_directory; + CajaDirectory *templates_directory; + char *templates_uri; + + if (!setup_autos) { + setup_autos = TRUE; + eel_preferences_add_auto_boolean (CAJA_PREFERENCES_CONFIRM_TRASH, + &confirm_trash_auto_value); + eel_preferences_add_auto_boolean (CAJA_PREFERENCES_ENABLE_DELETE, + &show_delete_command_auto_value); + } + + view->details = g_new0 (FMDirectoryViewDetails, 1); + + /* Default to true; desktop-icon-view sets to false */ + view->details->show_foreign_files = TRUE; + + view->details->non_ready_files = + g_hash_table_new_full (file_and_directory_hash, + file_and_directory_equal, + (GDestroyNotify)file_and_directory_free, + NULL); + + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (view), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_hadjustment (GTK_SCROLLED_WINDOW (view), NULL); + gtk_scrolled_window_set_vadjustment (GTK_SCROLLED_WINDOW (view), NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (view), GTK_SHADOW_ETCHED_IN); + + set_up_scripts_directory_global (); + scripts_directory = caja_directory_get_by_uri (scripts_directory_uri); + add_directory_to_scripts_directory_list (view, scripts_directory); + caja_directory_unref (scripts_directory); + + if (caja_should_use_templates_directory ()) { + templates_uri = caja_get_templates_directory_uri (); + templates_directory = caja_directory_get_by_uri (templates_uri); + g_free (templates_uri); + add_directory_to_templates_directory_list (view, templates_directory); + caja_directory_unref (templates_directory); + } + update_templates_directory (view); + g_signal_connect_object (caja_signaller_get_current (), + "user_dirs_changed", + G_CALLBACK (user_dirs_changed), + view, G_CONNECT_SWAPPED); + + view->details->sort_directories_first = + eel_preferences_get_boolean (CAJA_PREFERENCES_SORT_DIRECTORIES_FIRST); + + g_signal_connect_object (caja_trash_monitor_get (), "trash_state_changed", + G_CALLBACK (fm_directory_view_trash_state_changed_callback), view, 0); + + /* React to clipboard changes */ + g_signal_connect_object (caja_clipboard_monitor_get (), "clipboard_changed", + G_CALLBACK (clipboard_changed_callback), view, 0); + + /* Register to menu provider extension signal managing menu updates */ + g_signal_connect_object (caja_signaller_get_current (), "popup_menu_changed", + G_CALLBACK (fm_directory_view_update_menus), view, G_CONNECT_SWAPPED); + + gtk_widget_show (GTK_WIDGET (view)); + + eel_preferences_add_callback (CAJA_PREFERENCES_CONFIRM_TRASH, + schedule_update_menus_callback, view); + eel_preferences_add_callback (CAJA_PREFERENCES_ENABLE_DELETE, + schedule_update_menus_callback, view); + eel_preferences_add_callback (CAJA_PREFERENCES_ICON_VIEW_CAPTIONS, + text_attribute_names_changed_callback, view); + eel_preferences_add_callback (CAJA_PREFERENCES_SHOW_IMAGE_FILE_THUMBNAILS, + image_display_policy_changed_callback, view); + eel_preferences_add_callback (CAJA_PREFERENCES_CLICK_POLICY, + click_policy_changed_callback, view); + eel_preferences_add_callback (CAJA_PREFERENCES_SORT_DIRECTORIES_FIRST, + sort_directories_first_changed_callback, view); + eel_preferences_add_callback (CAJA_PREFERENCES_LOCKDOWN_COMMAND_LINE, + lockdown_disable_command_line_changed_callback, view); +} + +static void +real_unmerge_menus (FMDirectoryView *view) +{ + GtkUIManager *ui_manager; + + if (view->details->window == NULL) { + return; + } + + ui_manager = caja_window_info_get_ui_manager (view->details->window); + + caja_ui_unmerge_ui (ui_manager, + &view->details->dir_merge_id, + &view->details->dir_action_group); + caja_ui_unmerge_ui (ui_manager, + &view->details->extensions_menu_merge_id, + &view->details->extensions_menu_action_group); + caja_ui_unmerge_ui (ui_manager, + &view->details->open_with_merge_id, + &view->details->open_with_action_group); + caja_ui_unmerge_ui (ui_manager, + &view->details->scripts_merge_id, + &view->details->scripts_action_group); + caja_ui_unmerge_ui (ui_manager, + &view->details->templates_merge_id, + &view->details->templates_action_group); +} + +static void +fm_directory_view_destroy (GtkObject *object) +{ + FMDirectoryView *view; + GList *node, *next; + + view = FM_DIRECTORY_VIEW (object); + + disconnect_model_handlers (view); + + fm_directory_view_unmerge_menus (view); + + /* We don't own the window, so no unref */ + view->details->slot = NULL; + view->details->window = NULL; + + fm_directory_view_stop (view); + fm_directory_view_clear (view); + + for (node = view->details->scripts_directory_list; node != NULL; node = next) { + next = node->next; + remove_directory_from_scripts_directory_list (view, node->data); + } + + for (node = view->details->templates_directory_list; node != NULL; node = next) { + next = node->next; + remove_directory_from_templates_directory_list (view, node->data); + } + + while (view->details->subdirectory_list != NULL) { + fm_directory_view_remove_subdirectory (view, + view->details->subdirectory_list->data); + } + + remove_update_menus_timeout_callback (view); + remove_update_status_idle_callback (view); + + if (view->details->display_selection_idle_id != 0) { + g_source_remove (view->details->display_selection_idle_id); + view->details->display_selection_idle_id = 0; + } + + if (view->details->reveal_selection_idle_id != 0) { + g_source_remove (view->details->reveal_selection_idle_id); + view->details->reveal_selection_idle_id = 0; + } + + if (view->details->delayed_rename_file_id != 0) { + g_source_remove (view->details->delayed_rename_file_id); + view->details->delayed_rename_file_id = 0; + } + + if (view->details->model) { + caja_directory_unref (view->details->model); + view->details->model = NULL; + } + + if (view->details->directory_as_file) { + caja_file_unref (view->details->directory_as_file); + view->details->directory_as_file = NULL; + } + + EEL_CALL_PARENT (GTK_OBJECT_CLASS, destroy, (object)); +} + +static void +fm_directory_view_finalize (GObject *object) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (object); + + eel_preferences_remove_callback (CAJA_PREFERENCES_CONFIRM_TRASH, + schedule_update_menus_callback, view); + eel_preferences_remove_callback (CAJA_PREFERENCES_ENABLE_DELETE, + schedule_update_menus_callback, view); + eel_preferences_remove_callback (CAJA_PREFERENCES_ICON_VIEW_CAPTIONS, + text_attribute_names_changed_callback, view); + eel_preferences_remove_callback (CAJA_PREFERENCES_SHOW_IMAGE_FILE_THUMBNAILS, + image_display_policy_changed_callback, view); + eel_preferences_remove_callback (CAJA_PREFERENCES_CLICK_POLICY, + click_policy_changed_callback, view); + eel_preferences_remove_callback (CAJA_PREFERENCES_SORT_DIRECTORIES_FIRST, + sort_directories_first_changed_callback, view); + eel_preferences_remove_callback (CAJA_PREFERENCES_LOCKDOWN_COMMAND_LINE, + lockdown_disable_command_line_changed_callback, view); + + unschedule_pop_up_location_context_menu (view); + if (view->details->location_popup_event != NULL) { + gdk_event_free ((GdkEvent *) view->details->location_popup_event); + } + + g_hash_table_destroy (view->details->non_ready_files); + + g_free (view->details); + + EEL_CALL_PARENT (G_OBJECT_CLASS, finalize, (object)); +} + +/** + * fm_directory_view_display_selection_info: + * + * Display information about the current selection, and notify the view frame of the changed selection. + * @view: FMDirectoryView for which to display selection info. + * + **/ +void +fm_directory_view_display_selection_info (FMDirectoryView *view) +{ + GList *selection; + goffset non_folder_size; + gboolean non_folder_size_known; + guint non_folder_count, folder_count, folder_item_count; + gboolean folder_item_count_known; + guint file_item_count; + GList *p; + char *first_item_name; + char *non_folder_str; + char *folder_count_str; + char *folder_item_count_str; + char *status_string; + char *free_space_str; + char *obj_selected_free_space_str; + CajaFile *file; + + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + selection = fm_directory_view_get_selection (view); + + folder_item_count_known = TRUE; + folder_count = 0; + folder_item_count = 0; + non_folder_count = 0; + non_folder_size_known = FALSE; + non_folder_size = 0; + first_item_name = NULL; + folder_count_str = NULL; + non_folder_str = NULL; + folder_item_count_str = NULL; + free_space_str = NULL; + obj_selected_free_space_str = NULL; + + for (p = selection; p != NULL; p = p->next) { + file = p->data; + if (caja_file_is_directory (file)) { + folder_count++; + if (caja_file_get_directory_item_count (file, &file_item_count, NULL)) { + folder_item_count += file_item_count; + } else { + folder_item_count_known = FALSE; + } + } else { + non_folder_count++; + if (!caja_file_can_get_size (file)) { + non_folder_size_known = TRUE; + non_folder_size += caja_file_get_size (file); + } + } + + if (first_item_name == NULL) { + first_item_name = caja_file_get_display_name (file); + } + } + + caja_file_list_free (selection); + + /* Break out cases for localization's sake. But note that there are still pieces + * being assembled in a particular order, which may be a problem for some localizers. + */ + + if (folder_count != 0) { + if (folder_count == 1 && non_folder_count == 0) { + folder_count_str = g_strdup_printf (_("\"%s\" selected"), first_item_name); + } else { + folder_count_str = g_strdup_printf (ngettext("%'d folder selected", + "%'d folders selected", + folder_count), + folder_count); + } + + if (folder_count == 1) { + if (!folder_item_count_known) { + folder_item_count_str = g_strdup (""); + } else { + folder_item_count_str = g_strdup_printf (ngettext(" (containing %'d item)", + " (containing %'d items)", + folder_item_count), + folder_item_count); + } + } + else { + if (!folder_item_count_known) { + folder_item_count_str = g_strdup (""); + } else { + /* translators: this is preceded with a string of form 'N folders' (N more than 1) */ + folder_item_count_str = g_strdup_printf (ngettext(" (containing a total of %'d item)", + " (containing a total of %'d items)", + folder_item_count), + folder_item_count); + } + + } + } + + if (non_folder_count != 0) { + char *items_string; + + if (folder_count == 0) { + if (non_folder_count == 1) { + items_string = g_strdup_printf (_("\"%s\" selected"), + first_item_name); + } else { + items_string = g_strdup_printf (ngettext("%'d item selected", + "%'d items selected", + non_folder_count), + non_folder_count); + } + } else { + /* Folders selected also, use "other" terminology */ + items_string = g_strdup_printf (ngettext("%'d other item selected", + "%'d other items selected", + non_folder_count), + non_folder_count); + } + + if (non_folder_size_known) { + char *size_string; + + #if GLIB_CHECK_VERSION(2, 30, 0) + size_string = g_format_size(non_folder_size); + #else + size_string = g_format_size_for_display(non_folder_size); + #endif + + /* This is marked for translation in case a localiser + * needs to use something other than parentheses. The + * first message gives the number of items selected; + * the message in parentheses the size of those items. + */ + non_folder_str = g_strdup_printf (_("%s (%s)"), + items_string, + size_string); + + g_free (size_string); + g_free (items_string); + } else { + non_folder_str = items_string; + } + } + + free_space_str = caja_file_get_volume_free_space (view->details->directory_as_file); + if (free_space_str != NULL) { + obj_selected_free_space_str = g_strdup_printf (_("Free space: %s"), free_space_str); + } + if (folder_count == 0 && non_folder_count == 0) { + char *item_count_str; + guint item_count; + + item_count = fm_directory_view_get_item_count (view); + + item_count_str = g_strdup_printf (ngettext ("%'u item", "%'u items", item_count), item_count); + + if (free_space_str != NULL) { + status_string = g_strdup_printf (_("%s, Free space: %s"), item_count_str, free_space_str); + g_free (item_count_str); + } else { + status_string = item_count_str; + } + + } else if (folder_count == 0) { + if (free_space_str == NULL) { + status_string = g_strdup (non_folder_str); + } else { + /* Marking this for translation, since you + * might want to change "," to something else. + * After the comma the amount of free space will + * be shown. + */ + status_string = g_strdup_printf (_("%s, %s"), + non_folder_str, + obj_selected_free_space_str); + } + } else if (non_folder_count == 0) { + if (free_space_str == NULL) { + /* No use marking this for translation, since you + * can't reorder the strings, which is the main thing + * you'd want to do. + */ + status_string = g_strdup_printf ("%s%s", + folder_count_str, + folder_item_count_str); + } else { + /* Marking this for translation, since you + * might want to change "," to something else. + * After the comma the amount of free space will + * be shown. + */ + status_string = g_strdup_printf (_("%s%s, %s"), + folder_count_str, + folder_item_count_str, + obj_selected_free_space_str); + } + } else { + if (obj_selected_free_space_str == NULL) { + /* This is marked for translation in case a localizer + * needs to change ", " to something else. The comma + * is between the message about the number of folders + * and the number of items in those folders and the + * message about the number of other items and the + * total size of those items. + */ + status_string = g_strdup_printf (_("%s%s, %s"), + folder_count_str, + folder_item_count_str, + non_folder_str); + } else { + /* This is marked for translation in case a localizer + * needs to change ", " to something else. The first comma + * is between the message about the number of folders + * and the number of items in those folders and the + * message about the number of other items and the + * total size of those items. After the second comma + * the free space is written. + */ + status_string = g_strdup_printf (_("%s%s, %s, %s"), + folder_count_str, + folder_item_count_str, + non_folder_str, + obj_selected_free_space_str); + } + } + + g_free (free_space_str); + g_free (obj_selected_free_space_str); + g_free (first_item_name); + g_free (folder_count_str); + g_free (folder_item_count_str); + g_free (non_folder_str); + + caja_window_slot_info_set_status (view->details->slot, + status_string); + g_free (status_string); +} + +void +fm_directory_view_send_selection_change (FMDirectoryView *view) +{ + caja_window_info_report_selection_changed (view->details->window); + + view->details->send_selection_change_to_shell = FALSE; +} + +gboolean +fm_directory_view_get_allow_moves (FMDirectoryView *view) +{ + return view->details->allow_moves; +} + +static void +fm_directory_view_load_location (CajaView *caja_view, + const char *location) +{ + CajaDirectory *directory; + FMDirectoryView *directory_view; + + directory_view = FM_DIRECTORY_VIEW (caja_view); + + if (eel_uri_is_search (location)) { + directory_view->details->allow_moves = FALSE; + } else { + directory_view->details->allow_moves = TRUE; + } + + directory = caja_directory_get_by_uri (location); + load_directory (directory_view, directory); + caja_directory_unref (directory); +} + +static void +fm_directory_view_stop_loading (CajaView *caja_view) +{ + fm_directory_view_stop (FM_DIRECTORY_VIEW (caja_view)); +} + +static void +fm_directory_view_file_limit_reached (FMDirectoryView *view) +{ + g_assert (FM_IS_DIRECTORY_VIEW (view)); + + EEL_CALL_METHOD (FM_DIRECTORY_VIEW_CLASS, view, + file_limit_reached, (view)); +} + +static void +real_file_limit_reached (FMDirectoryView *view) +{ + CajaFile *file; + GtkDialog *dialog; + char *directory_name; + char *message; + + g_assert (FM_IS_DIRECTORY_VIEW (view)); + + file = fm_directory_view_get_directory_as_file (view); + directory_name = caja_file_get_display_name (file); + + /* Note that the number of items actually displayed varies somewhat due + * to the way files are collected in batches. So you can't assume that + * no more than the constant limit are displayed. + */ + message = g_strdup_printf (_("The folder \"%s\" contains more files than " + "Caja can handle."), + directory_name); + g_free (directory_name); + + dialog = eel_show_warning_dialog (message, + _("Some files will not be displayed."), + fm_directory_view_get_containing_window (view)); + g_free (message); +} + +static void +check_for_directory_hard_limit (FMDirectoryView *view) +{ + if (caja_directory_file_list_length_reached (view->details->model)) { + fm_directory_view_file_limit_reached (view); + } +} + +static gboolean +reveal_selection_idle_callback (gpointer data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (data); + + view->details->reveal_selection_idle_id = 0; + fm_directory_view_reveal_selection (view); + + return FALSE; +} + +static void +done_loading (FMDirectoryView *view, + gboolean all_files_seen) +{ + GList *locations_selected, *selection; + + if (!view->details->loading) { + return; + } + + /* This can be called during destruction, in which case there + * is no CajaWindowInfo any more. + */ + if (view->details->window != NULL) { + if (all_files_seen) { + caja_window_info_report_load_complete (view->details->window, CAJA_VIEW (view)); + } + + schedule_update_menus (view); + schedule_update_status (view); + check_for_directory_hard_limit (view); + reset_update_interval (view); + + locations_selected = view->details->pending_locations_selected; + if (locations_selected != NULL && all_files_seen) { + view->details->pending_locations_selected = NULL; + + selection = file_list_from_location_list (locations_selected); + + view->details->selection_change_is_due_to_shell = TRUE; + fm_directory_view_set_selection (view, selection); + view->details->selection_change_is_due_to_shell = FALSE; + caja_file_list_free (selection); + + if (FM_IS_LIST_VIEW (view)) { + /* HACK: We should be able to directly call reveal_selection here, + * but at this point the GtkTreeView hasn't allocated the new nodes + * yet, and it has a bug in the scroll calculation dealing with this + * special case. It would always make the selection the top row, even + * if no scrolling would be neccessary to reveal it. So we let it + * allocate before revealing. + */ + if (view->details->reveal_selection_idle_id != 0) { + g_source_remove (view->details->reveal_selection_idle_id); + } + view->details->reveal_selection_idle_id = + g_idle_add (reveal_selection_idle_callback, view); + } else { + fm_directory_view_reveal_selection (view); + } + } + eel_g_object_list_free (locations_selected); + fm_directory_view_display_selection_info (view); + } + + fm_directory_view_end_loading (view, all_files_seen); + + view->details->loading = FALSE; +} + + +typedef struct { + GHashTable *debuting_files; + GList *added_files; +} DebutingFilesData; + +static void +debuting_files_data_free (DebutingFilesData *data) +{ + g_hash_table_unref (data->debuting_files); + caja_file_list_free (data->added_files); + g_free (data); +} + +/* This signal handler watch for the arrival of the icons created + * as the result of a file operation. Once the last one is detected + * it selects and reveals them all. + */ +static void +debuting_files_add_file_callback (FMDirectoryView *view, + CajaFile *new_file, + CajaDirectory *directory, + DebutingFilesData *data) +{ + GFile *location; + + location = caja_file_get_location (new_file); + + if (g_hash_table_remove (data->debuting_files, location)) { + caja_file_ref (new_file); + data->added_files = g_list_prepend (data->added_files, new_file); + + if (g_hash_table_size (data->debuting_files) == 0) { + fm_directory_view_set_selection (view, data->added_files); + fm_directory_view_reveal_selection (view); + g_signal_handlers_disconnect_by_func (view, + G_CALLBACK (debuting_files_add_file_callback), + data); + } + } + + g_object_unref (location); +} + +typedef struct { + GList *added_files; + FMDirectoryView *directory_view; +} CopyMoveDoneData; + +static void +copy_move_done_data_free (CopyMoveDoneData *data) +{ + g_assert (data != NULL); + + eel_remove_weak_pointer (&data->directory_view); + caja_file_list_free (data->added_files); + g_free (data); +} + +static void +pre_copy_move_add_file_callback (FMDirectoryView *view, + CajaFile *new_file, + CajaDirectory *directory, + CopyMoveDoneData *data) +{ + caja_file_ref (new_file); + data->added_files = g_list_prepend (data->added_files, new_file); +} + +/* This needs to be called prior to caja_file_operations_copy_move. + * It hooks up a signal handler to catch any icons that get added before + * the copy_done_callback is invoked. The return value should be passed + * as the data for uri_copy_move_done_callback. + */ +static CopyMoveDoneData * +pre_copy_move (FMDirectoryView *directory_view) +{ + CopyMoveDoneData *copy_move_done_data; + + copy_move_done_data = g_new0 (CopyMoveDoneData, 1); + copy_move_done_data->directory_view = directory_view; + + eel_add_weak_pointer (©_move_done_data->directory_view); + + /* We need to run after the default handler adds the folder we want to + * operate on. The ADD_FILE signal is registered as G_SIGNAL_RUN_LAST, so we + * must use connect_after. + */ + g_signal_connect (directory_view, "add_file", + G_CALLBACK (pre_copy_move_add_file_callback), copy_move_done_data); + + return copy_move_done_data; +} + +/* This function is used to pull out any debuting uris that were added + * and (as a side effect) remove them from the debuting uri hash table. + */ +static gboolean +copy_move_done_partition_func (gpointer data, gpointer callback_data) +{ + GFile *location; + gboolean result; + + location = caja_file_get_location (CAJA_FILE (data)); + result = g_hash_table_remove ((GHashTable *) callback_data, location); + g_object_unref (location); + + return result; +} + +static gboolean +remove_not_really_moved_files (gpointer key, + gpointer value, + gpointer callback_data) +{ + GList **added_files; + GFile *loc; + + loc = key; + + if (GPOINTER_TO_INT (value)) { + return FALSE; + } + + added_files = callback_data; + *added_files = g_list_prepend (*added_files, + caja_file_get (loc)); + return TRUE; +} + + +/* When this function is invoked, the file operation is over, but all + * the icons may not have been added to the directory view yet, so + * we can't select them yet. + * + * We're passed a hash table of the uri's to look out for, we hook + * up a signal handler to await their arrival. + */ +static void +copy_move_done_callback (GHashTable *debuting_files, gpointer data) +{ + FMDirectoryView *directory_view; + CopyMoveDoneData *copy_move_done_data; + DebutingFilesData *debuting_files_data; + + copy_move_done_data = (CopyMoveDoneData *) data; + directory_view = copy_move_done_data->directory_view; + + if (directory_view != NULL) { + g_assert (FM_IS_DIRECTORY_VIEW (directory_view)); + + debuting_files_data = g_new (DebutingFilesData, 1); + debuting_files_data->debuting_files = g_hash_table_ref (debuting_files); + debuting_files_data->added_files = eel_g_list_partition + (copy_move_done_data->added_files, + copy_move_done_partition_func, + debuting_files, + ©_move_done_data->added_files); + + /* We're passed the same data used by pre_copy_move_add_file_callback, so disconnecting + * it will free data. We've already siphoned off the added_files we need, and stashed the + * directory_view pointer. + */ + g_signal_handlers_disconnect_by_func (directory_view, + G_CALLBACK (pre_copy_move_add_file_callback), + data); + + /* Any items in the debuting_files hash table that have + * "FALSE" as their value aren't really being copied + * or moved, so we can't wait for an add_file signal + * to come in for those. + */ + g_hash_table_foreach_remove (debuting_files, + remove_not_really_moved_files, + &debuting_files_data->added_files); + + if (g_hash_table_size (debuting_files) == 0) { + /* on the off-chance that all the icons have already been added */ + if (debuting_files_data->added_files != NULL) { + fm_directory_view_set_selection (directory_view, + debuting_files_data->added_files); + fm_directory_view_reveal_selection (directory_view); + } + debuting_files_data_free (debuting_files_data); + } else { + /* We need to run after the default handler adds the folder we want to + * operate on. The ADD_FILE signal is registered as G_SIGNAL_RUN_LAST, so we + * must use connect_after. + */ + g_signal_connect_data (GTK_OBJECT (directory_view), + "add_file", + G_CALLBACK (debuting_files_add_file_callback), + debuting_files_data, + (GClosureNotify) debuting_files_data_free, + G_CONNECT_AFTER); + } + } + + copy_move_done_data_free (copy_move_done_data); +} + +static gboolean +real_file_still_belongs (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory) +{ + if (view->details->model != directory && + g_list_find (view->details->subdirectory_list, directory) == NULL) { + return FALSE; + } + + return caja_directory_contains_file (directory, file); +} + +static gboolean +still_should_show_file (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory) +{ + return fm_directory_view_should_show_file (view, file) + && EEL_INVOKE_METHOD (FM_DIRECTORY_VIEW_CLASS, view, file_still_belongs, (view, file, directory)); +} + +static gboolean +ready_to_load (CajaFile *file) +{ + return caja_file_check_if_ready (file, + CAJA_FILE_ATTRIBUTES_FOR_ICON); +} + +static int +compare_files_cover (gconstpointer a, gconstpointer b, gpointer callback_data) +{ + const FileAndDirectory *fad1, *fad2; + FMDirectoryView *view; + + view = callback_data; + fad1 = a; fad2 = b; + + if (fad1->directory < fad2->directory) { + return -1; + } else if (fad1->directory > fad2->directory) { + return 1; + } else { + return EEL_INVOKE_METHOD (FM_DIRECTORY_VIEW_CLASS, view, compare_files, + (view, fad1->file, fad2->file)); + } +} +static void +sort_files (FMDirectoryView *view, GList **list) +{ + *list = g_list_sort_with_data (*list, compare_files_cover, view); + +} + +/* Go through all the new added and changed files. + * Put any that are not ready to load in the non_ready_files hash table. + * Add all the rest to the old_added_files and old_changed_files lists. + * Sort the old_*_files lists if anything was added to them. + */ +static void +process_new_files (FMDirectoryView *view) +{ + GList *new_added_files, *new_changed_files, *old_added_files, *old_changed_files; + GHashTable *non_ready_files; + GList *node, *next; + FileAndDirectory *pending; + gboolean in_non_ready; + + new_added_files = view->details->new_added_files; + view->details->new_added_files = NULL; + new_changed_files = view->details->new_changed_files; + view->details->new_changed_files = NULL; + + non_ready_files = view->details->non_ready_files; + + old_added_files = view->details->old_added_files; + old_changed_files = view->details->old_changed_files; + + /* Newly added files go into the old_added_files list if they're + * ready, and into the hash table if they're not. + */ + for (node = new_added_files; node != NULL; node = next) { + next = node->next; + pending = (FileAndDirectory *)node->data; + in_non_ready = g_hash_table_lookup (non_ready_files, pending) != NULL; + if (fm_directory_view_should_show_file (view, pending->file)) { + if (ready_to_load (pending->file)) { + if (in_non_ready) { + g_hash_table_remove (non_ready_files, pending); + } + new_added_files = g_list_delete_link (new_added_files, node); + old_added_files = g_list_prepend (old_added_files, pending); + } else { + if (!in_non_ready) { + new_added_files = g_list_delete_link (new_added_files, node); + g_hash_table_insert (non_ready_files, pending, pending); + } + } + } + } + file_and_directory_list_free (new_added_files); + + /* Newly changed files go into the old_added_files list if they're ready + * and were seen non-ready in the past, into the old_changed_files list + * if they are read and were not seen non-ready in the past, and into + * the hash table if they're not ready. + */ + for (node = new_changed_files; node != NULL; node = next) { + next = node->next; + pending = (FileAndDirectory *)node->data; + if (!still_should_show_file (view, pending->file, pending->directory) || ready_to_load (pending->file)) { + if (g_hash_table_lookup (non_ready_files, pending) != NULL) { + g_hash_table_remove (non_ready_files, pending); + if (still_should_show_file (view, pending->file, pending->directory)) { + new_changed_files = g_list_delete_link (new_changed_files, node); + old_added_files = g_list_prepend (old_added_files, pending); + } + } else if (fm_directory_view_should_show_file (view, pending->file)) { + new_changed_files = g_list_delete_link (new_changed_files, node); + old_changed_files = g_list_prepend (old_changed_files, pending); + } + } + } + file_and_directory_list_free (new_changed_files); + + /* If any files were added to old_added_files, then resort it. */ + if (old_added_files != view->details->old_added_files) { + view->details->old_added_files = old_added_files; + sort_files (view, &view->details->old_added_files); + } + + /* Resort old_changed_files too, since file attributes + * relevant to sorting could have changed. + */ + if (old_changed_files != view->details->old_changed_files) { + view->details->old_changed_files = old_changed_files; + sort_files (view, &view->details->old_changed_files); + } + +} + +static void +process_old_files (FMDirectoryView *view) +{ + GList *files_added, *files_changed, *node; + FileAndDirectory *pending; + GList *selection, *files; + gboolean send_selection_change; + + files_added = view->details->old_added_files; + files_changed = view->details->old_changed_files; + + send_selection_change = FALSE; + + if (files_added != NULL || files_changed != NULL) { + g_signal_emit (view, signals[BEGIN_FILE_CHANGES], 0); + + for (node = files_added; node != NULL; node = node->next) { + pending = node->data; + g_signal_emit (view, + signals[ADD_FILE], 0, pending->file, pending->directory); + } + + for (node = files_changed; node != NULL; node = node->next) { + pending = node->data; + g_signal_emit (view, + signals[still_should_show_file (view, pending->file, pending->directory) + ? FILE_CHANGED : REMOVE_FILE], 0, + pending->file, pending->directory); + } + + g_signal_emit (view, signals[END_FILE_CHANGES], 0); + + if (files_changed != NULL) { + selection = fm_directory_view_get_selection (view); + files = file_and_directory_list_to_files (files_changed); + send_selection_change = eel_g_lists_sort_and_check_for_intersection + (&files, &selection); + caja_file_list_free (files); + caja_file_list_free (selection); + } + + file_and_directory_list_free (view->details->old_added_files); + view->details->old_added_files = NULL; + + file_and_directory_list_free (view->details->old_changed_files); + view->details->old_changed_files = NULL; + } + + if (send_selection_change) { + /* Send a selection change since some file names could + * have changed. + */ + fm_directory_view_send_selection_change (view); + } +} + +static void +display_pending_files (FMDirectoryView *view) +{ + + /* Don't dispatch any updates while the view is frozen. */ + if (view->details->updates_frozen) { + return; + } + + process_new_files (view); + process_old_files (view); + + if (view->details->model != NULL + && caja_directory_are_all_files_seen (view->details->model) + && g_hash_table_size (view->details->non_ready_files) == 0) { + done_loading (view, TRUE); + } +} + +void +fm_directory_view_freeze_updates (FMDirectoryView *view) +{ + view->details->updates_frozen = TRUE; + view->details->updates_queued = 0; + view->details->needs_reload = FALSE; +} + +void +fm_directory_view_unfreeze_updates (FMDirectoryView *view) +{ + view->details->updates_frozen = FALSE; + + if (view->details->needs_reload) { + view->details->needs_reload = FALSE; + if (view->details->model != NULL) { + load_directory (view, view->details->model); + } + } else { + schedule_idle_display_of_pending_files (view); + } +} + +static gboolean +display_selection_info_idle_callback (gpointer data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (data); + + g_object_ref (G_OBJECT (view)); + + view->details->display_selection_idle_id = 0; + fm_directory_view_display_selection_info (view); + if (view->details->send_selection_change_to_shell) { + fm_directory_view_send_selection_change (view); + } + + g_object_unref (G_OBJECT (view)); + + return FALSE; +} + +static void +remove_update_menus_timeout_callback (FMDirectoryView *view) +{ + if (view->details->update_menus_timeout_id != 0) { + g_source_remove (view->details->update_menus_timeout_id); + view->details->update_menus_timeout_id = 0; + } +} + +static void +update_menus_if_pending (FMDirectoryView *view) +{ + if (!view->details->menu_states_untrustworthy) { + return; + } + + remove_update_menus_timeout_callback (view); + fm_directory_view_update_menus (view); +} + +static gboolean +update_menus_timeout_callback (gpointer data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (data); + + g_object_ref (G_OBJECT (view)); + + view->details->update_menus_timeout_id = 0; + fm_directory_view_update_menus (view); + + g_object_unref (G_OBJECT (view)); + + return FALSE; +} + +static gboolean +display_pending_callback (gpointer data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (data); + + g_object_ref (G_OBJECT (view)); + + view->details->display_pending_source_id = 0; + + display_pending_files (view); + + g_object_unref (G_OBJECT (view)); + + return FALSE; +} + +static void +schedule_idle_display_of_pending_files (FMDirectoryView *view) +{ + /* Get rid of a pending source as it might be a timeout */ + unschedule_display_of_pending_files (view); + + /* We want higher priority than the idle that handles the relayout + to avoid a resort on each add. But we still want to allow repaints + and other hight prio events while we have pending files to show. */ + view->details->display_pending_source_id = + g_idle_add_full (G_PRIORITY_DEFAULT_IDLE - 20, + display_pending_callback, view, NULL); +} + +static void +schedule_timeout_display_of_pending_files (FMDirectoryView *view, guint interval) +{ + /* No need to schedule an update if there's already one pending. */ + if (view->details->display_pending_source_id != 0) { + return; + } + + view->details->display_pending_source_id = + g_timeout_add (interval, display_pending_callback, view); +} + +static void +unschedule_display_of_pending_files (FMDirectoryView *view) +{ + /* Get rid of source if it's active. */ + if (view->details->display_pending_source_id != 0) { + g_source_remove (view->details->display_pending_source_id); + view->details->display_pending_source_id = 0; + } +} + +static void +queue_pending_files (FMDirectoryView *view, + CajaDirectory *directory, + GList *files, + GList **pending_list) +{ + if (files == NULL) { + return; + } + + /* Don't queue any more updates if we need to reload anyway */ + if (view->details->needs_reload) { + return; + } + + if (view->details->updates_frozen) { + view->details->updates_queued += g_list_length (files); + /* Mark the directory for reload when there are too much queued + * changes to prevent the pending list from growing infinitely. + */ + if (view->details->updates_queued > MAX_QUEUED_UPDATES) { + view->details->needs_reload = TRUE; + return; + } + } + + + + *pending_list = g_list_concat (file_and_directory_list_from_files (directory, files), + *pending_list); + + if (! view->details->loading || caja_directory_are_all_files_seen (directory)) { + schedule_timeout_display_of_pending_files (view, view->details->update_interval); + } +} + +static void +remove_changes_timeout_callback (FMDirectoryView *view) +{ + if (view->details->changes_timeout_id != 0) { + g_source_remove (view->details->changes_timeout_id); + view->details->changes_timeout_id = 0; + } +} + +static void +reset_update_interval (FMDirectoryView *view) +{ + view->details->update_interval = UPDATE_INTERVAL_MIN; + remove_changes_timeout_callback (view); + /* Reschedule a pending timeout to idle */ + if (view->details->display_pending_source_id != 0) { + schedule_idle_display_of_pending_files (view); + } +} + +static gboolean +changes_timeout_callback (gpointer data) +{ + gint64 now; + gint64 time_delta; + gboolean ret; + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (data); + + g_object_ref (G_OBJECT (view)); + + now = eel_get_system_time(); + time_delta = now - view->details->last_queued; + + if (time_delta < UPDATE_INTERVAL_RESET*1000) { + if (view->details->update_interval < UPDATE_INTERVAL_MAX && + view->details->loading) { + /* Increase */ + view->details->update_interval += UPDATE_INTERVAL_INC; + } + ret = TRUE; + } else { + /* Reset */ + reset_update_interval (view); + ret = FALSE; + } + + g_object_unref (G_OBJECT (view)); + + return ret; +} + +static void +schedule_changes (FMDirectoryView *view) +{ + /* Remember when the change was queued */ + view->details->last_queued = eel_get_system_time(); + + /* No need to schedule if there are already changes pending or during loading */ + if (view->details->changes_timeout_id != 0 || + view->details->loading) { + return; + } + + view->details->changes_timeout_id = + g_timeout_add (UPDATE_INTERVAL_TIMEOUT_INTERVAL, changes_timeout_callback, view); +} + +static void +files_added_callback (CajaDirectory *directory, + GList *files, + gpointer callback_data) +{ + FMDirectoryView *view; + GtkWindow *window; + char *uri; + + view = FM_DIRECTORY_VIEW (callback_data); + + window = fm_directory_view_get_containing_window (view); + uri = fm_directory_view_get_uri (view); + caja_debug_log_with_file_list (FALSE, CAJA_DEBUG_LOG_DOMAIN_ASYNC, files, + "files added in window %p: %s", + window, + uri ? uri : "(no directory)"); + g_free (uri); + + schedule_changes (view); + + queue_pending_files (view, directory, files, &view->details->new_added_files); + + /* The number of items could have changed */ + schedule_update_status (view); +} + +static void +files_changed_callback (CajaDirectory *directory, + GList *files, + gpointer callback_data) +{ + FMDirectoryView *view; + GtkWindow *window; + char *uri; + + view = FM_DIRECTORY_VIEW (callback_data); + + window = fm_directory_view_get_containing_window (view); + uri = fm_directory_view_get_uri (view); + caja_debug_log_with_file_list (FALSE, CAJA_DEBUG_LOG_DOMAIN_ASYNC, files, + "files changed in window %p: %s", + window, + uri ? uri : "(no directory)"); + g_free (uri); + + schedule_changes (view); + + queue_pending_files (view, directory, files, &view->details->new_changed_files); + + /* The free space or the number of items could have changed */ + schedule_update_status (view); + + /* A change in MIME type could affect the Open with menu, for + * one thing, so we need to update menus when files change. + */ + schedule_update_menus (view); +} + +static void +done_loading_callback (CajaDirectory *directory, + gpointer callback_data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (callback_data); + + process_new_files (view); + if (g_hash_table_size (view->details->non_ready_files) == 0) { + /* Unschedule a pending update and schedule a new one with the minimal + * update interval. This gives the view a short chance at gathering the + * (cached) deep counts. + */ + unschedule_display_of_pending_files (view); + schedule_timeout_display_of_pending_files (view, UPDATE_INTERVAL_MIN); + } +} + +static void +load_error_callback (CajaDirectory *directory, + GError *error, + gpointer callback_data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (callback_data); + + /* FIXME: By doing a stop, we discard some pending files. Is + * that OK? + */ + fm_directory_view_stop (view); + + /* Emit a signal to tell subclasses that a load error has + * occurred, so they can handle it in the UI. + */ + g_signal_emit (view, + signals[LOAD_ERROR], 0, error); +} + +static void +real_load_error (FMDirectoryView *view, GError *error) +{ + /* Report only one error per failed directory load (from the UI + * point of view, not from the CajaDirectory point of view). + * Otherwise you can get multiple identical errors caused by + * unrelated code that just happens to try to iterate this + * directory. + */ + if (!view->details->reported_load_error) { + fm_report_error_loading_directory + (fm_directory_view_get_directory_as_file (view), + error, + fm_directory_view_get_containing_window (view)); + } + view->details->reported_load_error = TRUE; +} + +void +fm_directory_view_add_subdirectory (FMDirectoryView *view, + CajaDirectory*directory) +{ + CajaFileAttributes attributes; + + g_assert (!g_list_find (view->details->subdirectory_list, directory)); + + caja_directory_ref (directory); + + attributes = + CAJA_FILE_ATTRIBUTES_FOR_ICON | + CAJA_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT | + CAJA_FILE_ATTRIBUTE_INFO | + CAJA_FILE_ATTRIBUTE_LINK_INFO | + CAJA_FILE_ATTRIBUTE_MOUNT | + CAJA_FILE_ATTRIBUTE_EXTENSION_INFO; + + caja_directory_file_monitor_add (directory, + &view->details->model, + view->details->show_hidden_files, + view->details->show_backup_files, + attributes, + files_added_callback, view); + + g_signal_connect + (directory, "files_added", + G_CALLBACK (files_added_callback), view); + g_signal_connect + (directory, "files_changed", + G_CALLBACK (files_changed_callback), view); + + view->details->subdirectory_list = g_list_prepend ( + view->details->subdirectory_list, directory); +} + +void +fm_directory_view_remove_subdirectory (FMDirectoryView *view, + CajaDirectory*directory) +{ + g_assert (g_list_find (view->details->subdirectory_list, directory)); + + view->details->subdirectory_list = g_list_remove ( + view->details->subdirectory_list, directory); + + g_signal_handlers_disconnect_by_func (directory, + G_CALLBACK (files_added_callback), + view); + g_signal_handlers_disconnect_by_func (directory, + G_CALLBACK (files_changed_callback), + view); + + caja_directory_file_monitor_remove (directory, &view->details->model); + + caja_directory_unref (directory); +} + +/** + * fm_directory_view_clear: + * + * Emit the signal to clear the contents of the view. Subclasses must + * override the signal handler for this signal. This is normally called + * only by FMDirectoryView. + * @view: FMDirectoryView to empty. + * + **/ +void +fm_directory_view_clear (FMDirectoryView *view) +{ + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + g_signal_emit (view, signals[CLEAR], 0); +} + +/** + * fm_directory_view_begin_loading: + * + * Emit the signal to prepare for loading the contents of a new location. + * Subclasses might want to override the signal handler for this signal. + * This is normally called only by FMDirectoryView. + * @view: FMDirectoryView that is switching to view a new location. + * + **/ +void +fm_directory_view_begin_loading (FMDirectoryView *view) +{ + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + g_signal_emit (view, signals[BEGIN_LOADING], 0); +} + +/** + * fm_directory_view_end_loading: + * + * Emit the signal after loading the contents of a new location. + * Subclasses might want to override the signal handler for this signal. + * This is normally called only by FMDirectoryView. + * @view: FMDirectoryView that is switching to view a new location. + * + **/ +void +fm_directory_view_end_loading (FMDirectoryView *view, + gboolean all_files_seen) +{ + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + g_signal_emit (view, signals[END_LOADING], 0, all_files_seen); +} + +/** + * fm_directory_view_get_loading: + * @view: an #FMDirectoryView. + * + * Return value: #gboolean inicating whether @view is currently loaded. + * + **/ +gboolean +fm_directory_view_get_loading (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + + return view->details->loading; +} + +/** + * fm_directory_view_bump_zoom_level: + * + * bump the current zoom level by invoking the relevant subclass through the slot + * + **/ +void +fm_directory_view_bump_zoom_level (FMDirectoryView *view, int zoom_increment) +{ + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + if (!fm_directory_view_supports_zooming (view)) { + return; + } + + EEL_CALL_METHOD + (FM_DIRECTORY_VIEW_CLASS, view, + bump_zoom_level, (view, zoom_increment)); +} + +/** + * fm_directory_view_zoom_to_level: + * + * Set the current zoom level by invoking the relevant subclass through the slot + * + **/ +void +fm_directory_view_zoom_to_level (FMDirectoryView *view, + CajaZoomLevel zoom_level) +{ + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + if (!fm_directory_view_supports_zooming (view)) { + return; + } + + EEL_CALL_METHOD + (FM_DIRECTORY_VIEW_CLASS, view, + zoom_to_level, (view, zoom_level)); +} + + +CajaZoomLevel +fm_directory_view_get_zoom_level (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), CAJA_ZOOM_LEVEL_STANDARD); + + if (!fm_directory_view_supports_zooming (view)) { + return CAJA_ZOOM_LEVEL_STANDARD; + } + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_DIRECTORY_VIEW_CLASS, view, + get_zoom_level, (view)); +} + +/** + * fm_directory_view_restore_default_zoom_level: + * + * restore to the default zoom level by invoking the relevant subclass through the slot + * + **/ +void +fm_directory_view_restore_default_zoom_level (FMDirectoryView *view) +{ + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + if (!fm_directory_view_supports_zooming (view)) { + return; + } + + EEL_CALL_METHOD + (FM_DIRECTORY_VIEW_CLASS, view, + restore_default_zoom_level, (view)); +} + +/** + * fm_directory_view_can_zoom_in: + * + * Determine whether the view can be zoomed any closer. + * @view: The zoomable FMDirectoryView. + * + * Return value: TRUE if @view can be zoomed any closer, FALSE otherwise. + * + **/ +gboolean +fm_directory_view_can_zoom_in (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + + if (!fm_directory_view_supports_zooming (view)) { + return FALSE; + } + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_DIRECTORY_VIEW_CLASS, view, + can_zoom_in, (view)); +} + +/** + * fm_directory_view_can_rename_file + * + * Determine whether a file can be renamed. + * @file: A CajaFile + * + * Return value: TRUE if @file can be renamed, FALSE otherwise. + * + **/ +static gboolean +fm_directory_view_can_rename_file (FMDirectoryView *view, CajaFile *file) +{ + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_DIRECTORY_VIEW_CLASS, view, + can_rename_file, (view, file)); +} + +/** + * fm_directory_view_can_zoom_out: + * + * Determine whether the view can be zoomed any further away. + * @view: The zoomable FMDirectoryView. + * + * Return value: TRUE if @view can be zoomed any further away, FALSE otherwise. + * + **/ +gboolean +fm_directory_view_can_zoom_out (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + + if (!fm_directory_view_supports_zooming (view)) { + return FALSE; + } + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_DIRECTORY_VIEW_CLASS, view, + can_zoom_out, (view)); +} + +GtkWidget * +fm_directory_view_get_background_widget (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), NULL); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_DIRECTORY_VIEW_CLASS, view, + get_background_widget, (view)); +} + +EelBackground * +fm_directory_view_get_background (FMDirectoryView *view) +{ + return eel_get_widget_background (fm_directory_view_get_background_widget (view)); +} + +static void +real_set_is_active (FMDirectoryView *view, + gboolean is_active) +{ + EelBackground *bg; + + bg = fm_directory_view_get_background (view); + eel_background_set_active (bg, is_active); +} + +static void +fm_directory_view_set_is_active (FMDirectoryView *view, + gboolean is_active) +{ + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + EEL_CALL_METHOD (FM_DIRECTORY_VIEW_CLASS, view, + set_is_active, (view, is_active)); +} + +/** + * fm_directory_view_get_selection: + * + * Get a list of CajaFile pointers that represents the + * currently-selected items in this view. Subclasses must override + * the signal handler for the 'get_selection' signal. Callers are + * responsible for g_free-ing the list (but not its data). + * @view: FMDirectoryView whose selected items are of interest. + * + * Return value: GList of CajaFile pointers representing the selection. + * + **/ +GList * +fm_directory_view_get_selection (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), NULL); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_DIRECTORY_VIEW_CLASS, view, + get_selection, (view)); +} + +void +fm_directory_view_invert_selection (FMDirectoryView *view) +{ + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + EEL_CALL_METHOD + (FM_DIRECTORY_VIEW_CLASS, view, + invert_selection, (view)); +} + +GList * +fm_directory_view_get_selection_for_file_transfer (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), NULL); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_DIRECTORY_VIEW_CLASS, view, + get_selection_for_file_transfer, (view)); +} + +guint +fm_directory_view_get_item_count (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), 0); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_DIRECTORY_VIEW_CLASS, view, + get_item_count, (view)); +} + +GtkUIManager * +fm_directory_view_get_ui_manager (FMDirectoryView *view) +{ + if (view->details->window == NULL) { + return NULL; + } + return caja_window_info_get_ui_manager (view->details->window); +} + +/** + * fm_directory_view_get_model: + * + * Get the model for this FMDirectoryView. + * @view: FMDirectoryView of interest. + * + * Return value: CajaDirectory for this view. + * + **/ +CajaDirectory * +fm_directory_view_get_model (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), NULL); + + return view->details->model; +} + +GdkAtom +fm_directory_view_get_copied_files_atom (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), GDK_NONE); + + return copied_files_atom; +} + +static void +prepend_uri_one (gpointer data, gpointer callback_data) +{ + CajaFile *file; + GList **result; + + g_assert (CAJA_IS_FILE (data)); + g_assert (callback_data != NULL); + + result = (GList **) callback_data; + file = (CajaFile *) data; + *result = g_list_prepend (*result, caja_file_get_uri (file)); +} + +static void +offset_drop_points (GArray *relative_item_points, + int x_offset, int y_offset) +{ + guint index; + + if (relative_item_points == NULL) { + return; + } + + for (index = 0; index < relative_item_points->len; index++) { + g_array_index (relative_item_points, GdkPoint, index).x += x_offset; + g_array_index (relative_item_points, GdkPoint, index).y += y_offset; + } +} + +static void +fm_directory_view_create_links_for_files (FMDirectoryView *view, GList *files, + GArray *relative_item_points) +{ + GList *uris; + char *dir_uri; + CopyMoveDoneData *copy_move_done_data; + g_assert (relative_item_points->len == 0 + || g_list_length (files) == relative_item_points->len); + + g_assert (FM_IS_DIRECTORY_VIEW (view)); + g_assert (files != NULL); + + /* create a list of URIs */ + uris = NULL; + g_list_foreach (files, prepend_uri_one, &uris); + uris = g_list_reverse (uris); + + g_assert (g_list_length (uris) == g_list_length (files)); + + /* offset the drop locations a bit so that we don't pile + * up the icons on top of each other + */ + offset_drop_points (relative_item_points, + DUPLICATE_HORIZONTAL_ICON_OFFSET, + DUPLICATE_VERTICAL_ICON_OFFSET); + + copy_move_done_data = pre_copy_move (view); + dir_uri = fm_directory_view_get_backing_uri (view); + caja_file_operations_copy_move (uris, relative_item_points, dir_uri, GDK_ACTION_LINK, + GTK_WIDGET (view), copy_move_done_callback, copy_move_done_data); + g_free (dir_uri); + eel_g_list_free_deep (uris); +} + +static void +fm_directory_view_duplicate_selection (FMDirectoryView *view, GList *files, + GArray *relative_item_points) +{ + GList *uris; + CopyMoveDoneData *copy_move_done_data; + + g_assert (FM_IS_DIRECTORY_VIEW (view)); + g_assert (files != NULL); + g_assert (g_list_length (files) == relative_item_points->len + || relative_item_points->len == 0); + + /* create a list of URIs */ + uris = NULL; + g_list_foreach (files, prepend_uri_one, &uris); + uris = g_list_reverse (uris); + + g_assert (g_list_length (uris) == g_list_length (files)); + + /* offset the drop locations a bit so that we don't pile + * up the icons on top of each other + */ + offset_drop_points (relative_item_points, + DUPLICATE_HORIZONTAL_ICON_OFFSET, + DUPLICATE_VERTICAL_ICON_OFFSET); + + copy_move_done_data = pre_copy_move (view); + caja_file_operations_copy_move (uris, relative_item_points, NULL, GDK_ACTION_COPY, + GTK_WIDGET (view), copy_move_done_callback, copy_move_done_data); + eel_g_list_free_deep (uris); +} + +/* special_link_in_selection + * + * Return TRUE if one of our special links is in the selection. + * Special links include the following: + * CAJA_DESKTOP_LINK_TRASH, CAJA_DESKTOP_LINK_HOME, CAJA_DESKTOP_LINK_MOUNT + */ + +static gboolean +special_link_in_selection (FMDirectoryView *view) +{ + gboolean saw_link; + GList *selection, *node; + CajaFile *file; + + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + + saw_link = FALSE; + + selection = fm_directory_view_get_selection (FM_DIRECTORY_VIEW (view)); + + for (node = selection; node != NULL; node = node->next) { + file = CAJA_FILE (node->data); + + saw_link = CAJA_IS_DESKTOP_ICON_FILE (file); + + if (saw_link) { + break; + } + } + + caja_file_list_free (selection); + + return saw_link; +} + +/* desktop_or_home_dir_in_selection + * + * Return TRUE if either the desktop or the home directory is in the selection. + */ + +static gboolean +desktop_or_home_dir_in_selection (FMDirectoryView *view) +{ + gboolean saw_desktop_or_home_dir; + GList *selection, *node; + CajaFile *file; + + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + + saw_desktop_or_home_dir = FALSE; + + selection = fm_directory_view_get_selection (FM_DIRECTORY_VIEW (view)); + + for (node = selection; node != NULL; node = node->next) { + file = CAJA_FILE (node->data); + + saw_desktop_or_home_dir = + caja_file_is_home (file) + || caja_file_is_desktop_directory (file); + + if (saw_desktop_or_home_dir) { + break; + } + } + + caja_file_list_free (selection); + + return saw_desktop_or_home_dir; +} + +static void +trash_or_delete_done_cb (GHashTable *debuting_uris, + gboolean user_cancel, + FMDirectoryView *view) +{ + if (user_cancel) { + view->details->selection_was_removed = FALSE; + } +} + +static void +trash_or_delete_files (GtkWindow *parent_window, + const GList *files, + gboolean delete_if_all_already_in_trash, + FMDirectoryView *view) +{ + 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, + (CajaDeleteCallback) trash_or_delete_done_cb, + view); + eel_g_object_list_free (locations); +} + +static gboolean +can_rename_file (FMDirectoryView *view, CajaFile *file) +{ + return caja_file_can_rename (file); +} + +static void +start_renaming_file (FMDirectoryView *view, + CajaFile *file, + gboolean select_all) +{ + if (file != NULL) { + fm_directory_view_select_file (view, file); + } +} + +typedef struct { + FMDirectoryView *view; + CajaFile *new_file; +} RenameData; + +static gboolean +delayed_rename_file_hack_callback (RenameData *data) +{ + FMDirectoryView *view; + CajaFile *new_file; + + view = data->view; + new_file = data->new_file; + + if (view->details->window != NULL && + view->details->active) { + EEL_CALL_METHOD (FM_DIRECTORY_VIEW_CLASS, view, start_renaming_file, (view, new_file, FALSE)); + fm_directory_view_reveal_selection (view); + } + + return FALSE; +} + +static void +delayed_rename_file_hack_removed (RenameData *data) +{ + g_object_unref (data->view); + caja_file_unref (data->new_file); + g_free (data); +} + + +static void +rename_file (FMDirectoryView *view, CajaFile *new_file) +{ + RenameData *data; + + /* HACK!!!! + This is a work around bug in listview. After the rename is + enabled we will get file changes due to info about the new + file being read, which will cause the model to change. When + the model changes GtkTreeView clears the editing. This hack just + delays editing for some time to try to avoid this problem. + A major problem is that the selection of the row causes us + to load the slow mimetype for the file, which leads to a + file_changed. So, before we delay we select the row. + */ + if (FM_IS_LIST_VIEW (view)) { + fm_directory_view_select_file (view, new_file); + + data = g_new (RenameData, 1); + data->view = g_object_ref (view); + data->new_file = caja_file_ref (new_file); + if (view->details->delayed_rename_file_id != 0) { + g_source_remove (view->details->delayed_rename_file_id); + } + view->details->delayed_rename_file_id = + g_timeout_add_full (G_PRIORITY_DEFAULT, + 100, (GSourceFunc)delayed_rename_file_hack_callback, + data, (GDestroyNotify) delayed_rename_file_hack_removed); + + return; + } + + /* no need to select because start_renaming_file selects + * fm_directory_view_select_file (view, new_file); + */ + EEL_CALL_METHOD (FM_DIRECTORY_VIEW_CLASS, view, start_renaming_file, (view, new_file, FALSE)); + fm_directory_view_reveal_selection (view); +} + +static void +reveal_newly_added_folder (FMDirectoryView *view, CajaFile *new_file, + CajaDirectory *directory, GFile *target_location) +{ + GFile *location; + + location = caja_file_get_location (new_file); + if (g_file_equal (location, target_location)) { + g_signal_handlers_disconnect_by_func (view, + G_CALLBACK (reveal_newly_added_folder), + (void *) target_location); + rename_file (view, new_file); + } + g_object_unref (location); +} + +typedef struct { + FMDirectoryView *directory_view; + GHashTable *added_locations; +} NewFolderData; + + +static void +track_newly_added_locations (FMDirectoryView *view, CajaFile *new_file, + CajaDirectory *directory, gpointer user_data) +{ + NewFolderData *data; + + data = user_data; + + g_hash_table_insert (data->added_locations, caja_file_get_location (new_file), NULL); +} + +static void +new_folder_done (GFile *new_folder, gpointer user_data) +{ + FMDirectoryView *directory_view; + CajaFile *file; + char screen_string[32]; + GdkScreen *screen; + NewFolderData *data; + + data = (NewFolderData *)user_data; + + directory_view = data->directory_view; + + if (directory_view == NULL) { + goto fail; + } + + g_signal_handlers_disconnect_by_func (directory_view, + G_CALLBACK (track_newly_added_locations), + (void *) data); + + if (new_folder == NULL) { + goto fail; + } + + screen = gtk_widget_get_screen (GTK_WIDGET (directory_view)); + g_snprintf (screen_string, sizeof (screen_string), "%d", gdk_screen_get_number (screen)); + + + file = caja_file_get (new_folder); + caja_file_set_metadata + (file, CAJA_METADATA_KEY_SCREEN, + NULL, + screen_string); + + if (g_hash_table_lookup_extended (data->added_locations, new_folder, NULL, NULL)) { + /* The file was already added */ + rename_file (directory_view, file); + } else { + /* We need to run after the default handler adds the folder we want to + * operate on. The ADD_FILE signal is registered as G_SIGNAL_RUN_LAST, so we + * must use connect_after. + */ + g_signal_connect_data (directory_view, + "add_file", + G_CALLBACK (reveal_newly_added_folder), + g_object_ref (new_folder), + (GClosureNotify)g_object_unref, + G_CONNECT_AFTER); + } + caja_file_unref (file); + + fail: + g_hash_table_destroy (data->added_locations); + eel_remove_weak_pointer (&data->directory_view); + g_free (data); +} + + +static NewFolderData * +new_folder_data_new (FMDirectoryView *directory_view) +{ + NewFolderData *data; + + data = g_new (NewFolderData, 1); + data->directory_view = directory_view; + data->added_locations = g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, + g_object_unref, NULL); + eel_add_weak_pointer (&data->directory_view); + + return data; +} + +static GdkPoint * +context_menu_to_file_operation_position (FMDirectoryView *directory_view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (directory_view), NULL); + + if (fm_directory_view_using_manual_layout (directory_view) + && directory_view->details->context_menu_position.x >= 0 + && directory_view->details->context_menu_position.y >= 0) { + EEL_CALL_METHOD (FM_DIRECTORY_VIEW_CLASS, directory_view, + widget_to_file_operation_position, + (directory_view, &directory_view->details->context_menu_position)); + return &directory_view->details->context_menu_position; + } else { + return NULL; + } +} + +static void +update_context_menu_position_from_event (FMDirectoryView *view, + GdkEventButton *event) +{ + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + if (event != NULL) { + view->details->context_menu_position.x = event->x; + view->details->context_menu_position.y = event->y; + } else { + view->details->context_menu_position.x = -1; + view->details->context_menu_position.y = -1; + } +} + +void +fm_directory_view_new_folder (FMDirectoryView *directory_view) +{ + char *parent_uri; + NewFolderData *data; + GdkPoint *pos; + + data = new_folder_data_new (directory_view); + + g_signal_connect_data (directory_view, + "add_file", + G_CALLBACK (track_newly_added_locations), + data, + (GClosureNotify)NULL, + G_CONNECT_AFTER); + + pos = context_menu_to_file_operation_position (directory_view); + + parent_uri = fm_directory_view_get_backing_uri (directory_view); + caja_file_operations_new_folder (GTK_WIDGET (directory_view), + pos, parent_uri, + new_folder_done, data); + + g_free (parent_uri); +} + +static NewFolderData * +setup_new_folder_data (FMDirectoryView *directory_view) +{ + NewFolderData *data; + + data = new_folder_data_new (directory_view); + + g_signal_connect_data (directory_view, + "add_file", + G_CALLBACK (track_newly_added_locations), + data, + (GClosureNotify)NULL, + G_CONNECT_AFTER); + + return data; +} + +static void +fm_directory_view_new_file_with_initial_contents (FMDirectoryView *directory_view, + const char *parent_uri, + const char *filename, + const char *initial_contents, + int length, + GdkPoint *pos) +{ + NewFolderData *data; + + g_assert (parent_uri != NULL); + + data = setup_new_folder_data (directory_view); + + if (pos == NULL) { + pos = context_menu_to_file_operation_position (directory_view); + } + + caja_file_operations_new_file (GTK_WIDGET (directory_view), + pos, parent_uri, filename, + initial_contents, length, + new_folder_done, data); +} + +void +fm_directory_view_new_file (FMDirectoryView *directory_view, + const char *parent_uri, + CajaFile *source) +{ + GdkPoint *pos; + NewFolderData *data; + char *source_uri; + char *container_uri; + + container_uri = NULL; + if (parent_uri == NULL) { + container_uri = fm_directory_view_get_backing_uri (directory_view); + g_assert (container_uri != NULL); + } + + if (source == NULL) { + fm_directory_view_new_file_with_initial_contents (directory_view, + parent_uri != NULL ? parent_uri : container_uri, + NULL, + NULL, + 0, + NULL); + g_free (container_uri); + return; + } + + g_return_if_fail (caja_file_is_local (source)); + + pos = context_menu_to_file_operation_position (directory_view); + + data = setup_new_folder_data (directory_view); + + source_uri = caja_file_get_uri (source); + + caja_file_operations_new_file_from_template (GTK_WIDGET (directory_view), + pos, + parent_uri != NULL ? parent_uri : container_uri, + NULL, + source_uri, + new_folder_done, data); + + g_free (source_uri); + g_free (container_uri); +} + +/* handle the open command */ + +static void +open_one_in_new_window (gpointer data, gpointer callback_data) +{ + g_assert (CAJA_IS_FILE (data)); + g_assert (FM_IS_DIRECTORY_VIEW (callback_data)); + + fm_directory_view_activate_file (FM_DIRECTORY_VIEW (callback_data), + CAJA_FILE (data), + CAJA_WINDOW_OPEN_IN_NAVIGATION, + 0); +} + +static void +open_one_in_folder_window (gpointer data, gpointer callback_data) +{ + g_assert (CAJA_IS_FILE (data)); + g_assert (FM_IS_DIRECTORY_VIEW (callback_data)); + + fm_directory_view_activate_file (FM_DIRECTORY_VIEW (callback_data), + CAJA_FILE (data), + CAJA_WINDOW_OPEN_IN_SPATIAL, + 0); +} + +CajaFile * +fm_directory_view_get_directory_as_file (FMDirectoryView *view) +{ + g_assert (FM_IS_DIRECTORY_VIEW (view)); + + return view->details->directory_as_file; +} + +static void +open_with_launch_application_callback (GtkAction *action, + gpointer callback_data) +{ + ApplicationLaunchParameters *launch_parameters; + + launch_parameters = (ApplicationLaunchParameters *) callback_data; + caja_launch_application + (launch_parameters->application, + launch_parameters->files, + fm_directory_view_get_containing_window (launch_parameters->directory_view)); +} + +static char * +escape_action_name (const char *action_name, + const char *prefix) +{ + GString *s; + + if (action_name == NULL) { + return NULL; + } + + s = g_string_new (prefix); + + while (*action_name != 0) { + switch (*action_name) { + case '\\': + g_string_append (s, "\\\\"); + break; + case '/': + g_string_append (s, "\\s"); + break; + case '&': + g_string_append (s, "\\a"); + break; + case '"': + g_string_append (s, "\\q"); + break; + default: + g_string_append_c (s, *action_name); + } + + action_name ++; + } + return g_string_free (s, FALSE); +} + +static char * +escape_action_path (const char *action_path) +{ + GString *s; + + if (action_path == NULL) { + return NULL; + } + + s = g_string_sized_new (strlen (action_path) + 2); + + while (*action_path != 0) { + switch (*action_path) { + case '\\': + g_string_append (s, "\\\\"); + break; + case '&': + g_string_append (s, "\\a"); + break; + case '"': + g_string_append (s, "\\q"); + break; + default: + g_string_append_c (s, *action_path); + } + + action_path ++; + } + return g_string_free (s, FALSE); +} + + +static void +add_submenu (GtkUIManager *ui_manager, + GtkActionGroup *action_group, + guint merge_id, + const char *parent_path, + const char *uri, + const char *label, + GdkPixbuf *pixbuf, + gboolean add_action) +{ + char *escaped_label; + char *action_name; + char *submenu_name; + char *escaped_submenu_name; + GtkAction *action; + + if (parent_path != NULL) { + action_name = escape_action_name (uri, "submenu_"); + submenu_name = g_path_get_basename (uri); + escaped_submenu_name = escape_action_path (submenu_name); + escaped_label = eel_str_double_underscores (label); + + if (add_action) { + action = gtk_action_new (action_name, + escaped_label, + NULL, + NULL); + if (pixbuf != NULL) { + g_object_set_data_full (G_OBJECT (action), "menu-icon", + g_object_ref (pixbuf), + g_object_unref); + } + + g_object_set (action, "hide-if-empty", FALSE, NULL); + + gtk_action_group_add_action (action_group, + action); + g_object_unref (action); + } + + gtk_ui_manager_add_ui (ui_manager, + merge_id, + parent_path, + escaped_submenu_name, + action_name, + GTK_UI_MANAGER_MENU, + FALSE); + g_free (action_name); + g_free (escaped_label); + g_free (submenu_name); + g_free (escaped_submenu_name); + } +} + +static void +add_application_to_open_with_menu (FMDirectoryView *view, + GAppInfo *application, + GList *files, + int index, + const char *menu_placeholder, + const char *popup_placeholder, + const gboolean submenu) +{ + ApplicationLaunchParameters *launch_parameters; + char *tip; + char *label; + char *action_name; + char *escaped_app; + char *path; + GtkAction *action; + GIcon *app_icon; + GtkWidget *menuitem; + + launch_parameters = application_launch_parameters_new + (application, files, view); + escaped_app = eel_str_double_underscores (g_app_info_get_display_name (application)); + if (submenu) + label = g_strdup_printf ("%s", escaped_app); + else + label = g_strdup_printf (_("Open With %s"), escaped_app); + + tip = g_strdup_printf (ngettext ("Use \"%s\" to open the selected item", + "Use \"%s\" to open the selected items", + g_list_length (files)), + escaped_app); + g_free (escaped_app); + + action_name = g_strdup_printf ("open_with_%d", index); + + action = gtk_action_new (action_name, + label, + tip, + NULL); + + app_icon = g_app_info_get_icon (application); + if (app_icon != NULL) { + g_object_ref (app_icon); + } else { + app_icon = g_themed_icon_new ("application-x-executable"); + } + + gtk_action_set_gicon (action, app_icon); + g_object_unref (app_icon); + + g_signal_connect_data (action, "activate", + G_CALLBACK (open_with_launch_application_callback), + launch_parameters, + (GClosureNotify)application_launch_parameters_free, 0); + + gtk_action_group_add_action (view->details->open_with_action_group, + action); + g_object_unref (action); + + gtk_ui_manager_add_ui (caja_window_info_get_ui_manager (view->details->window), + view->details->open_with_merge_id, + menu_placeholder, + action_name, + action_name, + GTK_UI_MANAGER_MENUITEM, + FALSE); + + path = g_strdup_printf ("%s/%s", menu_placeholder, action_name); + menuitem = gtk_ui_manager_get_widget ( + caja_window_info_get_ui_manager (view->details->window), + path); + gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (menuitem), TRUE); + g_free (path); + + gtk_ui_manager_add_ui (caja_window_info_get_ui_manager (view->details->window), + view->details->open_with_merge_id, + popup_placeholder, + action_name, + action_name, + GTK_UI_MANAGER_MENUITEM, + FALSE); + + path = g_strdup_printf ("%s/%s", popup_placeholder, action_name); + menuitem = gtk_ui_manager_get_widget ( + caja_window_info_get_ui_manager (view->details->window), + path); + gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (menuitem), TRUE); + + g_free (path); + g_free (action_name); + g_free (label); + g_free (tip); +} + +static void +get_x_content_async_callback (char **content, + gpointer user_data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (user_data); + + if (view->details->window != NULL) { + schedule_update_menus (view); + } + g_object_unref (view); +} + +static void +add_x_content_apps (FMDirectoryView *view, CajaFile *file, GList **applications) +{ + GMount *mount; + char **x_content_types; + unsigned int n; + + g_return_if_fail (applications != NULL); + + mount = caja_file_get_mount (file); + + if (mount == NULL) { + return; + } + + x_content_types = caja_autorun_get_cached_x_content_types_for_mount (mount); + if (x_content_types != NULL) { + for (n = 0; x_content_types[n] != NULL; n++) { + char *x_content_type = x_content_types[n]; + GList *app_info_for_x_content_type; + + app_info_for_x_content_type = g_app_info_get_all_for_type (x_content_type); + *applications = g_list_concat (*applications, app_info_for_x_content_type); + } + g_strfreev (x_content_types); + } else { + caja_autorun_get_x_content_types_for_mount_async (mount, + get_x_content_async_callback, + NULL, + g_object_ref (view)); + + } + + g_object_unref (mount); +} + +static void +reset_open_with_menu (FMDirectoryView *view, GList *selection) +{ + GList *applications, *node; + CajaFile *file; + gboolean submenu_visible, filter_default; + int num_applications; + int index; + gboolean other_applications_visible; + gboolean open_with_chooser_visible; + GtkUIManager *ui_manager; + GtkAction *action; + GAppInfo *default_app; + + /* Clear any previous inserted items in the applications and viewers placeholders */ + + ui_manager = caja_window_info_get_ui_manager (view->details->window); + caja_ui_unmerge_ui (ui_manager, + &view->details->open_with_merge_id, + &view->details->open_with_action_group); + + caja_ui_prepare_merge_ui (ui_manager, + "OpenWithGroup", + &view->details->open_with_merge_id, + &view->details->open_with_action_group); + + num_applications = 0; + + other_applications_visible = (selection != NULL); + filter_default = (selection != NULL); + + for (node = selection; node != NULL; node = node->next) { + + file = CAJA_FILE (node->data); + + other_applications_visible &= + (!caja_mime_file_opens_in_view (file) || + caja_file_is_directory (file)); + } + + default_app = NULL; + if (filter_default) { + default_app = caja_mime_get_default_application_for_files (selection); + } + + applications = NULL; + if (other_applications_visible) { + applications = caja_mime_get_applications_for_files (selection); + } + + if (g_list_length (selection) == 1) { + add_x_content_apps (view, CAJA_FILE (selection->data), &applications); + } + + + num_applications = g_list_length (applications); + + if (file_list_all_are_folders (selection)) { + submenu_visible = (num_applications > 2); + } else { + submenu_visible = (num_applications > 3); + } + + for (node = applications, index = 0; node != NULL; node = node->next, index++) { + GAppInfo *application; + char *menu_path; + char *popup_path; + + application = node->data; + + if (default_app != NULL && g_app_info_equal (default_app, application)) { + continue; + } + + if (submenu_visible) { + menu_path = FM_DIRECTORY_VIEW_MENU_PATH_APPLICATIONS_SUBMENU_PLACEHOLDER; + popup_path = FM_DIRECTORY_VIEW_POPUP_PATH_APPLICATIONS_SUBMENU_PLACEHOLDER; + } else { + menu_path = FM_DIRECTORY_VIEW_MENU_PATH_APPLICATIONS_PLACEHOLDER; + popup_path = FM_DIRECTORY_VIEW_POPUP_PATH_APPLICATIONS_PLACEHOLDER; + } + + gtk_ui_manager_add_ui (caja_window_info_get_ui_manager (view->details->window), + view->details->open_with_merge_id, + menu_path, + "separator", + NULL, + GTK_UI_MANAGER_SEPARATOR, + FALSE); + + add_application_to_open_with_menu (view, + node->data, + selection, + index, + menu_path, popup_path, submenu_visible); + } + eel_g_object_list_free (applications); + if (default_app != NULL) { + g_object_unref (default_app); + } + + open_with_chooser_visible = other_applications_visible && + g_list_length (selection) == 1; + + if (submenu_visible) { + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_OTHER_APPLICATION1); + gtk_action_set_visible (action, open_with_chooser_visible); + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_OTHER_APPLICATION2); + gtk_action_set_visible (action, FALSE); + } else { + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_OTHER_APPLICATION1); + gtk_action_set_visible (action, FALSE); + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_OTHER_APPLICATION2); + gtk_action_set_visible (action, open_with_chooser_visible); + } +} + +static GList * +get_all_extension_menu_items (GtkWidget *window, + GList *selection) +{ + GList *items; + GList *providers; + GList *l; + + providers = caja_module_get_extensions_for_type (CAJA_TYPE_MENU_PROVIDER); + items = NULL; + + for (l = providers; l != NULL; l = l->next) { + CajaMenuProvider *provider; + GList *file_items; + + provider = CAJA_MENU_PROVIDER (l->data); + file_items = caja_menu_provider_get_file_items (provider, + window, + selection); + items = g_list_concat (items, file_items); + } + + caja_module_extension_list_free (providers); + + return items; +} + +typedef struct +{ + CajaMenuItem *item; + FMDirectoryView *view; + GList *selection; + GtkAction *action; +} ExtensionActionCallbackData; + + +static void +extension_action_callback_data_free (ExtensionActionCallbackData *data) +{ + g_object_unref (data->item); + caja_file_list_free (data->selection); + + g_free (data); +} + +static gboolean +search_in_menu_items (GList* items, const char *item_name) +{ + GList* list; + + for (list = items; list != NULL; list = list->next) { + CajaMenu* menu; + char *name; + + g_object_get (list->data, "name", &name, NULL); + if (strcmp (name, item_name) == 0) { + g_free (name); + return TRUE; + } + g_free (name); + + menu = NULL; + g_object_get (list->data, "menu", &menu, NULL); + if (menu != NULL) { + gboolean ret; + GList* submenus; + + submenus = caja_menu_get_items (menu); + ret = search_in_menu_items (submenus, item_name); + caja_menu_item_list_free (submenus); + g_object_unref (menu); + if (ret) { + return TRUE; + } + } + } + return FALSE; +} + +static void +extension_action_callback (GtkAction *action, + gpointer callback_data) +{ + ExtensionActionCallbackData *data; + char *item_name; + gboolean is_valid; + GList *l; + GList *items; + + data = callback_data; + + /* Make sure the selected menu item is valid for the final sniffed + * mime type */ + g_object_get (data->item, "name", &item_name, NULL); + items = get_all_extension_menu_items (gtk_widget_get_toplevel (GTK_WIDGET (data->view)), + data->selection); + + is_valid = search_in_menu_items (items, item_name); + + for (l = items; l != NULL; l = l->next) { + g_object_unref (l->data); + } + g_list_free (items); + + g_free (item_name); + + if (is_valid) { + caja_menu_item_activate (data->item); + } +} + +static GdkPixbuf * +get_menu_icon (const char *icon_name) +{ + CajaIconInfo *info; + GdkPixbuf *pixbuf; + int size; + + size = caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU); + + 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; +} + +static GdkPixbuf * +get_menu_icon_for_file (CajaFile *file) +{ + CajaIconInfo *info; + GdkPixbuf *pixbuf; + int size; + + size = caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU); + + info = caja_file_get_icon (file, size, 0); + pixbuf = caja_icon_info_get_pixbuf_nodefault_at_size (info, size); + g_object_unref (info); + + return pixbuf; +} + +static GtkAction * +add_extension_action_for_files (FMDirectoryView *view, + CajaMenuItem *item, + GList *files) +{ + char *name, *label, *tip, *icon; + gboolean sensitive, priority; + GtkAction *action; + GdkPixbuf *pixbuf; + ExtensionActionCallbackData *data; + + g_object_get (G_OBJECT (item), + "name", &name, "label", &label, + "tip", &tip, "icon", &icon, + "sensitive", &sensitive, + "priority", &priority, + NULL); + + action = gtk_action_new (name, + label, + tip, + icon); + + if (icon != NULL) { + pixbuf = get_menu_icon (icon); + 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); + + data = g_new0 (ExtensionActionCallbackData, 1); + data->item = g_object_ref (item); + data->view = view; + data->selection = caja_file_list_copy (files); + data->action = action; + + g_signal_connect_data (action, "activate", + G_CALLBACK (extension_action_callback), + data, + (GClosureNotify)extension_action_callback_data_free, 0); + + gtk_action_group_add_action (view->details->extensions_menu_action_group, + GTK_ACTION (action)); + g_object_unref (action); + + g_free (name); + g_free (label); + g_free (tip); + g_free (icon); + + return action; +} + +static void +add_extension_menu_items (FMDirectoryView *view, + GList *files, + GList *menu_items, + const char *subdirectory) +{ + GtkUIManager *ui_manager; + GList *l; + + ui_manager = caja_window_info_get_ui_manager (view->details->window); + + for (l = menu_items; l; l = l->next) { + CajaMenuItem *item; + CajaMenu *menu; + GtkAction *action; + char *path; + + item = CAJA_MENU_ITEM (l->data); + + g_object_get (item, "menu", &menu, NULL); + + action = add_extension_action_for_files (view, item, files); + + path = g_build_path ("/", FM_DIRECTORY_VIEW_POPUP_PATH_EXTENSION_ACTIONS, subdirectory, NULL); + gtk_ui_manager_add_ui (ui_manager, + view->details->extensions_menu_merge_id, + path, + gtk_action_get_name (action), + gtk_action_get_name (action), + (menu != NULL) ? GTK_UI_MANAGER_MENU : GTK_UI_MANAGER_MENUITEM, + FALSE); + g_free (path); + + path = g_build_path ("/", FM_DIRECTORY_VIEW_MENU_PATH_EXTENSION_ACTIONS_PLACEHOLDER, subdirectory, NULL); + gtk_ui_manager_add_ui (ui_manager, + view->details->extensions_menu_merge_id, + path, + gtk_action_get_name (action), + gtk_action_get_name (action), + (menu != NULL) ? GTK_UI_MANAGER_MENU : GTK_UI_MANAGER_MENUITEM, + FALSE); + g_free (path); + + /* recursively fill the menu */ + if (menu != NULL) { + char *subdir; + GList *children; + + children = caja_menu_get_items (menu); + + subdir = g_build_path ("/", subdirectory, gtk_action_get_name (action), NULL); + add_extension_menu_items (view, + files, + children, + subdir); + + caja_menu_item_list_free (children); + g_free (subdir); + } + } +} + +static void +reset_extension_actions_menu (FMDirectoryView *view, GList *selection) +{ + GList *items; + GtkUIManager *ui_manager; + + /* Clear any previous inserted items in the extension actions placeholder */ + ui_manager = caja_window_info_get_ui_manager (view->details->window); + + caja_ui_unmerge_ui (ui_manager, + &view->details->extensions_menu_merge_id, + &view->details->extensions_menu_action_group); + + caja_ui_prepare_merge_ui (ui_manager, + "DirExtensionsMenuGroup", + &view->details->extensions_menu_merge_id, + &view->details->extensions_menu_action_group); + + items = get_all_extension_menu_items (gtk_widget_get_toplevel (GTK_WIDGET (view)), + selection); + if (items != NULL) { + add_extension_menu_items (view, selection, items, ""); + + g_list_foreach (items, (GFunc) g_object_unref, NULL); + g_list_free (items); + } +} + +static char * +change_to_view_directory (FMDirectoryView *view) +{ + char *path; + char *old_path; + + old_path = g_get_current_dir (); + + path = get_view_directory (view); + + /* FIXME: What to do about non-local directories? */ + if (path != NULL) { + g_chdir (path); + } + + g_free (path); + + return old_path; +} + +static char ** +get_file_names_as_parameter_array (GList *selection, + CajaDirectory *model) +{ + CajaFile *file; + char **parameters; + GList *node; + GFile *file_location; + GFile *model_location; + int i; + + if (model == NULL) { + return NULL; + } + + parameters = g_new (char *, g_list_length (selection) + 1); + + model_location = caja_directory_get_location (model); + + for (node = selection, i = 0; node != NULL; node = node->next, i++) { + file = CAJA_FILE (node->data); + + if (!caja_file_is_local (file)) { + parameters[i] = NULL; + g_strfreev (parameters); + return NULL; + } + + file_location = caja_file_get_location (CAJA_FILE (node->data)); + parameters[i] = g_file_get_relative_path (model_location, file_location); + if (parameters[i] == NULL) { + parameters[i] = g_file_get_path (file_location); + } + g_object_unref (file_location); + } + + g_object_unref (model_location); + + parameters[i] = NULL; + return parameters; +} + +static char * +get_file_paths_or_uris_as_newline_delimited_string (GList *selection, gboolean get_paths) +{ + char *path; + char *uri; + char *result; + CajaDesktopLink *link; + GString *expanding_string; + GList *node; + GFile *location; + + expanding_string = g_string_new (""); + for (node = selection; node != NULL; node = node->next) { + uri = NULL; + if (CAJA_IS_DESKTOP_ICON_FILE (node->data)) { + link = caja_desktop_icon_file_get_link (CAJA_DESKTOP_ICON_FILE (node->data)); + if (link != NULL) { + location = caja_desktop_link_get_activation_location (link); + uri = g_file_get_uri (location); + g_object_unref (location); + g_object_unref (G_OBJECT (link)); + } + } else { + uri = caja_file_get_uri (CAJA_FILE (node->data)); + } + if (uri == NULL) { + continue; + } + + if (get_paths) { + path = g_filename_from_uri (uri, NULL, NULL); + if (path != NULL) { + g_string_append (expanding_string, path); + g_free (path); + g_string_append (expanding_string, "\n"); + } + } else { + g_string_append (expanding_string, uri); + g_string_append (expanding_string, "\n"); + } + g_free (uri); + } + + result = expanding_string->str; + g_string_free (expanding_string, FALSE); + + return result; +} + +static char * +get_file_paths_as_newline_delimited_string (GList *selection) +{ + return get_file_paths_or_uris_as_newline_delimited_string (selection, TRUE); +} + +static char * +get_file_uris_as_newline_delimited_string (GList *selection) +{ + return get_file_paths_or_uris_as_newline_delimited_string (selection, FALSE); +} + +/* returns newly allocated strings for setting the environment variables */ +static void +get_strings_for_environment_variables (FMDirectoryView *view, GList *selected_files, + char **file_paths, char **uris, char **uri) +{ + char *directory_uri; + + /* We need to check that the directory uri starts with "file:" since + * caja_directory_is_local returns FALSE for nfs. + */ + directory_uri = caja_directory_get_uri (view->details->model); + if (eel_str_has_prefix (directory_uri, "file:") || + eel_uri_is_desktop (directory_uri) || + eel_uri_is_trash (directory_uri)) { + *file_paths = get_file_paths_as_newline_delimited_string (selected_files); + } else { + *file_paths = g_strdup (""); + } + g_free (directory_uri); + + *uris = get_file_uris_as_newline_delimited_string (selected_files); + + *uri = caja_directory_get_uri (view->details->model); + if (eel_uri_is_desktop (*uri)) { + g_free (*uri); + *uri = caja_get_desktop_directory_uri (); + } +} + +static FMDirectoryView * +get_directory_view_of_extra_pane (FMDirectoryView *view) +{ + CajaWindowSlotInfo *slot; + CajaView *next_view; + + slot = caja_window_info_get_extra_slot (fm_directory_view_get_caja_window (view)); + if (slot != NULL) { + next_view = caja_window_slot_info_get_current_view (slot); + + if (FM_IS_DIRECTORY_VIEW (next_view)) { + return FM_DIRECTORY_VIEW (next_view); + } + } + return NULL; +} + +/* + * Set up some environment variables that scripts can use + * to take advantage of the current Caja state. + */ +static void set_script_environment_variables(FMDirectoryView* view, GList* selected_files) +{ + char* file_paths; + char* uris; + char* uri; + char* geometry_string; + FMDirectoryView* next_view; + + get_strings_for_environment_variables(view, selected_files, &file_paths, &uris, &uri); + + g_setenv("CAJA_SCRIPT_SELECTED_FILE_PATHS", file_paths, TRUE); + g_setenv("NAUTILUS_SCRIPT_SELECTED_FILE_PATHS", file_paths, TRUE); // compatibilidad GNOME + + g_free(file_paths); + + g_setenv("CAJA_SCRIPT_SELECTED_URIS", uris, TRUE); + g_setenv("NAUTILUS_SCRIPT_SELECTED_URIS", uris, TRUE); // compatibilidad GNOME + + g_free(uris); + + g_setenv("CAJA_SCRIPT_CURRENT_URI", uri, TRUE); + g_setenv("NAUTILUS_SCRIPT_CURRENT_URI", uri, TRUE); // compatibilidad GNOME + + + g_free(uri); + + geometry_string = eel_gtk_window_get_geometry_string(GTK_WINDOW (fm_directory_view_get_containing_window (view))); + + g_setenv("CAJA_SCRIPT_WINDOW_GEOMETRY", geometry_string, TRUE); + g_setenv("NAUTILUS_SCRIPT_WINDOW_GEOMETRY", geometry_string, TRUE); // compatibilidad GNOME + + g_free(geometry_string); + + /* next pane */ + next_view = get_directory_view_of_extra_pane(view); + + if (next_view) + { + GList* next_pane_selected_files = fm_directory_view_get_selection (next_view); + + get_strings_for_environment_variables(next_view, next_pane_selected_files, &file_paths, &uris, &uri); + + caja_file_list_free(next_pane_selected_files); + } + else + { + file_paths = g_strdup(""); + uris = g_strdup(""); + uri = g_strdup(""); + } + + g_setenv("CAJA_SCRIPT_NEXT_PANE_SELECTED_FILE_PATHS", file_paths, TRUE); + g_setenv("NAUTILUS_SCRIPT_NEXT_PANE_SELECTED_FILE_PATHS", file_paths, TRUE); // compatibilidad GNOME + g_free(file_paths); + + g_setenv("CAJA_SCRIPT_NEXT_PANE_SELECTED_URIS", uris, TRUE); + g_setenv("NAUTILUS_SCRIPT_NEXT_PANE_SELECTED_URIS", uris, TRUE); // compatibilidad GNOME + g_free(uris); + + g_setenv("CAJA_SCRIPT_NEXT_PANE_CURRENT_URI", uri, TRUE); + g_setenv("NAUTILUS_SCRIPT_NEXT_PANE_CURRENT_URI", uri, TRUE); // compatibilidad GNOME + g_free(uri); +} + +/* Unset all the special script environment variables. */ +static void unset_script_environment_variables(void) +{ + g_unsetenv("CAJA_SCRIPT_SELECTED_FILE_PATHS"); + g_unsetenv("NAUTILUS_SCRIPT_SELECTED_FILE_PATHS"); + + g_unsetenv("CAJA_SCRIPT_SELECTED_URIS"); + g_unsetenv("NAUTILUS_SCRIPT_SELECTED_URIS"); + + g_unsetenv("CAJA_SCRIPT_CURRENT_URI"); + g_unsetenv("NAUTILUS_SCRIPT_CURRENT_URI"); + + g_unsetenv("CAJA_SCRIPT_WINDOW_GEOMETRY"); + g_unsetenv("NAUTILUS_SCRIPT_WINDOW_GEOMETRY"); + + g_unsetenv("CAJA_SCRIPT_NEXT_PANE_SELECTED_FILE_PATHS"); + g_unsetenv("NAUTILUS_SCRIPT_NEXT_PANE_SELECTED_FILE_PATHS"); + + g_unsetenv("CAJA_SCRIPT_NEXT_PANE_SELECTED_URIS"); + g_unsetenv("NAUTILUS_SCRIPT_NEXT_PANE_SELECTED_URIS"); + + g_unsetenv("CAJA_SCRIPT_NEXT_PANE_CURRENT_URI"); + g_unsetenv("NAUTILUS_SCRIPT_NEXT_PANE_CURRENT_URI"); +} + +static void +run_script_callback (GtkAction *action, gpointer callback_data) +{ + ScriptLaunchParameters *launch_parameters; + GdkScreen *screen; + GList *selected_files; + char *file_uri; + char *local_file_path; + char *quoted_path; + char *old_working_dir; + char **parameters, *name; + GtkWindow *window; + + launch_parameters = (ScriptLaunchParameters *) callback_data; + + file_uri = caja_file_get_uri (launch_parameters->file); + local_file_path = g_filename_from_uri (file_uri, NULL, NULL); + g_assert (local_file_path != NULL); + g_free (file_uri); + + quoted_path = g_shell_quote (local_file_path); + g_free (local_file_path); + + old_working_dir = change_to_view_directory (launch_parameters->directory_view); + + selected_files = fm_directory_view_get_selection (launch_parameters->directory_view); + set_script_environment_variables (launch_parameters->directory_view, selected_files); + + parameters = get_file_names_as_parameter_array (selected_files, + launch_parameters->directory_view->details->model); + + screen = gtk_widget_get_screen (GTK_WIDGET (launch_parameters->directory_view)); + + name = caja_file_get_name (launch_parameters->file); + /* FIXME: handle errors with dialog? Or leave up to each script? */ + window = fm_directory_view_get_containing_window (launch_parameters->directory_view); + caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, + "directory view run_script_callback, window=%p, name=\"%s\", script_path=\"%s\" (omitting script parameters)", + window, name, local_file_path); + caja_launch_application_from_command_array (screen, name, quoted_path, FALSE, + (const char * const *) parameters); + g_free (name); + g_strfreev (parameters); + + caja_file_list_free (selected_files); + unset_script_environment_variables (); + g_chdir (old_working_dir); + g_free (old_working_dir); + g_free (quoted_path); +} + +static void +add_script_to_scripts_menus (FMDirectoryView *directory_view, + CajaFile *file, + const char *menu_path, + const char *popup_path, + const char *popup_bg_path) +{ + ScriptLaunchParameters *launch_parameters; + char *tip; + char *name; + char *uri; + char *action_name; + char *escaped_label; + GdkPixbuf *pixbuf; + GtkUIManager *ui_manager; + GtkAction *action; + + name = caja_file_get_display_name (file); + uri = caja_file_get_uri (file); + tip = g_strdup_printf (_("Run \"%s\" on any selected items"), name); + + launch_parameters = script_launch_parameters_new (file, directory_view); + + action_name = escape_action_name (uri, "script_"); + escaped_label = eel_str_double_underscores (name); + + action = gtk_action_new (action_name, + escaped_label, + tip, + NULL); + + pixbuf = get_menu_icon_for_file (file); + if (pixbuf != NULL) { + g_object_set_data_full (G_OBJECT (action), "menu-icon", + pixbuf, + g_object_unref); + } + + g_signal_connect_data (action, "activate", + G_CALLBACK (run_script_callback), + launch_parameters, + (GClosureNotify)script_launch_parameters_free, 0); + + gtk_action_group_add_action_with_accel (directory_view->details->scripts_action_group, + action, NULL); + g_object_unref (action); + + ui_manager = caja_window_info_get_ui_manager (directory_view->details->window); + + gtk_ui_manager_add_ui (ui_manager, + directory_view->details->scripts_merge_id, + menu_path, + action_name, + action_name, + GTK_UI_MANAGER_MENUITEM, + FALSE); + gtk_ui_manager_add_ui (ui_manager, + directory_view->details->scripts_merge_id, + popup_path, + action_name, + action_name, + GTK_UI_MANAGER_MENUITEM, + FALSE); + gtk_ui_manager_add_ui (ui_manager, + directory_view->details->scripts_merge_id, + popup_bg_path, + action_name, + action_name, + GTK_UI_MANAGER_MENUITEM, + FALSE); + + g_free (name); + g_free (uri); + g_free (tip); + g_free (action_name); + g_free (escaped_label); +} + +static void +add_submenu_to_directory_menus (FMDirectoryView *directory_view, + GtkActionGroup *action_group, + guint merge_id, + CajaFile *file, + const char *menu_path, + const char *popup_path, + const char *popup_bg_path) +{ + char *name; + GdkPixbuf *pixbuf; + char *uri; + GtkUIManager *ui_manager; + + ui_manager = caja_window_info_get_ui_manager (directory_view->details->window); + uri = caja_file_get_uri (file); + name = caja_file_get_display_name (file); + pixbuf = get_menu_icon_for_file (file); + add_submenu (ui_manager, action_group, merge_id, menu_path, uri, name, pixbuf, TRUE); + add_submenu (ui_manager, action_group, merge_id, popup_path, uri, name, pixbuf, FALSE); + add_submenu (ui_manager, action_group, merge_id, popup_bg_path, uri, name, pixbuf, FALSE); + if (pixbuf) { + g_object_unref (pixbuf); + } + g_free (name); + g_free (uri); +} + +static gboolean +directory_belongs_in_scripts_menu (const char *uri) +{ + int num_levels; + int i; + + if (!eel_str_has_prefix (uri, scripts_directory_uri)) { + return FALSE; + } + + num_levels = 0; + for (i = scripts_directory_uri_length; uri[i] != '\0'; i++) { + if (uri[i] == '/') { + num_levels++; + } + } + + if (num_levels > MAX_MENU_LEVELS) { + return FALSE; + } + + return TRUE; +} + +static gboolean +update_directory_in_scripts_menu (FMDirectoryView *view, CajaDirectory *directory) +{ + char *menu_path, *popup_path, *popup_bg_path; + GList *file_list, *filtered, *node; + gboolean any_scripts; + CajaFile *file; + CajaDirectory *dir; + char *uri; + char *escaped_path; + + uri = caja_directory_get_uri (directory); + escaped_path = escape_action_path (uri + scripts_directory_uri_length); + g_free (uri); + menu_path = g_strconcat (FM_DIRECTORY_VIEW_MENU_PATH_SCRIPTS_PLACEHOLDER, + escaped_path, + NULL); + popup_path = g_strconcat (FM_DIRECTORY_VIEW_POPUP_PATH_SCRIPTS_PLACEHOLDER, + escaped_path, + NULL); + popup_bg_path = g_strconcat (FM_DIRECTORY_VIEW_POPUP_PATH_BACKGROUND_SCRIPTS_PLACEHOLDER, + escaped_path, + NULL); + g_free (escaped_path); + + file_list = caja_directory_get_file_list (directory); + filtered = caja_file_list_filter_hidden_and_backup (file_list, FALSE, FALSE); + caja_file_list_free (file_list); + + file_list = caja_file_list_sort_by_display_name (filtered); + + any_scripts = FALSE; + for (node = file_list; node != NULL; node = node->next) { + file = node->data; + + if (caja_file_is_launchable (file)) { + add_script_to_scripts_menus (view, file, menu_path, popup_path, popup_bg_path); + any_scripts = TRUE; + } else if (caja_file_is_directory (file)) { + uri = caja_file_get_uri (file); + if (directory_belongs_in_scripts_menu (uri)) { + dir = caja_directory_get_by_uri (uri); + add_directory_to_scripts_directory_list (view, dir); + caja_directory_unref (dir); + + add_submenu_to_directory_menus (view, + view->details->scripts_action_group, + view->details->scripts_merge_id, + file, menu_path, popup_path, popup_bg_path); + + any_scripts = TRUE; + } + g_free (uri); + } + } + + caja_file_list_free (file_list); + + g_free (popup_path); + g_free (popup_bg_path); + g_free (menu_path); + + return any_scripts; +} + +static void +update_scripts_menu (FMDirectoryView *view) +{ + gboolean any_scripts; + GList *sorted_copy, *node; + CajaDirectory *directory; + char *uri; + GtkUIManager *ui_manager; + GtkAction *action; + + /* There is a race condition here. If we don't mark the scripts menu as + valid before we begin our task then we can lose script menu updates that + occur before we finish. */ + view->details->scripts_invalid = FALSE; + + ui_manager = caja_window_info_get_ui_manager (view->details->window); + caja_ui_unmerge_ui (ui_manager, + &view->details->scripts_merge_id, + &view->details->scripts_action_group); + + caja_ui_prepare_merge_ui (ui_manager, + "ScriptsGroup", + &view->details->scripts_merge_id, + &view->details->scripts_action_group); + + /* As we walk through the directories, remove any that no longer belong. */ + any_scripts = FALSE; + sorted_copy = caja_directory_list_sort_by_uri + (caja_directory_list_copy (view->details->scripts_directory_list)); + for (node = sorted_copy; node != NULL; node = node->next) { + directory = node->data; + + uri = caja_directory_get_uri (directory); + if (!directory_belongs_in_scripts_menu (uri)) { + remove_directory_from_scripts_directory_list (view, directory); + } else if (update_directory_in_scripts_menu (view, directory)) { + any_scripts = TRUE; + } + g_free (uri); + } + caja_directory_list_free (sorted_copy); + + action = gtk_action_group_get_action (view->details->dir_action_group, FM_ACTION_SCRIPTS); + gtk_action_set_visible (action, any_scripts); +} + +static void +create_template_callback (GtkAction *action, gpointer callback_data) +{ + CreateTemplateParameters *parameters; + + parameters = callback_data; + + fm_directory_view_new_file (parameters->directory_view, NULL, parameters->file); +} + +static void +add_template_to_templates_menus (FMDirectoryView *directory_view, + CajaFile *file, + const char *menu_path, + const char *popup_bg_path) +{ + char *tmp, *tip, *uri, *name; + char *escaped_label; + GdkPixbuf *pixbuf; + char *action_name; + CreateTemplateParameters *parameters; + GtkUIManager *ui_manager; + GtkAction *action; + + tmp = caja_file_get_display_name (file); + name = eel_filename_strip_extension (tmp); + g_free (tmp); + + uri = caja_file_get_uri (file); + tip = g_strdup_printf (_("Create Document from template \"%s\""), name); + + action_name = escape_action_name (uri, "template_"); + escaped_label = eel_str_double_underscores (name); + + parameters = create_template_parameters_new (file, directory_view); + + action = gtk_action_new (action_name, + escaped_label, + tip, + NULL); + + pixbuf = get_menu_icon_for_file (file); + if (pixbuf != NULL) { + g_object_set_data_full (G_OBJECT (action), "menu-icon", + pixbuf, + g_object_unref); + } + + g_signal_connect_data (action, "activate", + G_CALLBACK (create_template_callback), + parameters, + (GClosureNotify)create_templates_parameters_free, 0); + + gtk_action_group_add_action (directory_view->details->templates_action_group, + action); + g_object_unref (action); + + ui_manager = caja_window_info_get_ui_manager (directory_view->details->window); + + gtk_ui_manager_add_ui (ui_manager, + directory_view->details->templates_merge_id, + menu_path, + action_name, + action_name, + GTK_UI_MANAGER_MENUITEM, + FALSE); + + gtk_ui_manager_add_ui (ui_manager, + directory_view->details->templates_merge_id, + popup_bg_path, + action_name, + action_name, + GTK_UI_MANAGER_MENUITEM, + FALSE); + + g_free (escaped_label); + g_free (name); + g_free (tip); + g_free (uri); + g_free (action_name); +} + +static void +update_templates_directory (FMDirectoryView *view) +{ + CajaDirectory *templates_directory; + GList *node, *next; + char *templates_uri; + + for (node = view->details->templates_directory_list; node != NULL; node = next) { + next = node->next; + remove_directory_from_templates_directory_list (view, node->data); + } + + if (caja_should_use_templates_directory ()) { + templates_uri = caja_get_templates_directory_uri (); + templates_directory = caja_directory_get_by_uri (templates_uri); + g_free (templates_uri); + add_directory_to_templates_directory_list (view, templates_directory); + caja_directory_unref (templates_directory); + } +} + +static void +user_dirs_changed (FMDirectoryView *view) +{ + update_templates_directory (view); + view->details->templates_invalid = TRUE; + schedule_update_menus (view); +} + +static gboolean +directory_belongs_in_templates_menu (const char *templates_directory_uri, + const char *uri) +{ + int num_levels; + int i; + + if (templates_directory_uri == NULL) { + return FALSE; + } + + if (!g_str_has_prefix (uri, templates_directory_uri)) { + return FALSE; + } + + num_levels = 0; + for (i = strlen (templates_directory_uri); uri[i] != '\0'; i++) { + if (uri[i] == '/') { + num_levels++; + } + } + + if (num_levels > MAX_MENU_LEVELS) { + return FALSE; + } + + return TRUE; +} + +static gboolean +update_directory_in_templates_menu (FMDirectoryView *view, + const char *templates_directory_uri, + CajaDirectory *directory) +{ + char *menu_path, *popup_bg_path; + GList *file_list, *filtered, *node; + gboolean any_templates; + CajaFile *file; + CajaDirectory *dir; + char *escaped_path; + char *uri; + int num; + + /* We know this directory belongs to the template dir, so it must exist */ + g_assert (templates_directory_uri); + + uri = caja_directory_get_uri (directory); + escaped_path = escape_action_path (uri + strlen (templates_directory_uri)); + g_free (uri); + menu_path = g_strconcat (FM_DIRECTORY_VIEW_MENU_PATH_NEW_DOCUMENTS_PLACEHOLDER, + escaped_path, + NULL); + popup_bg_path = g_strconcat (FM_DIRECTORY_VIEW_POPUP_PATH_BACKGROUND_NEW_DOCUMENTS_PLACEHOLDER, + escaped_path, + NULL); + g_free (escaped_path); + + file_list = caja_directory_get_file_list (directory); + filtered = caja_file_list_filter_hidden_and_backup (file_list, FALSE, FALSE); + caja_file_list_free (file_list); + + file_list = caja_file_list_sort_by_display_name (filtered); + + num = 0; + any_templates = FALSE; + for (node = file_list; num < TEMPLATE_LIMIT && node != NULL; node = node->next, num++) { + file = node->data; + + if (caja_file_is_directory (file)) { + uri = caja_file_get_uri (file); + if (directory_belongs_in_templates_menu (templates_directory_uri, uri)) { + dir = caja_directory_get_by_uri (uri); + add_directory_to_templates_directory_list (view, dir); + caja_directory_unref (dir); + + add_submenu_to_directory_menus (view, + view->details->templates_action_group, + view->details->templates_merge_id, + file, menu_path, NULL, popup_bg_path); + + any_templates = TRUE; + } + g_free (uri); + } else if (caja_file_can_read (file)) { + add_template_to_templates_menus (view, file, menu_path, popup_bg_path); + any_templates = TRUE; + } + } + + caja_file_list_free (file_list); + + g_free (popup_bg_path); + g_free (menu_path); + + return any_templates; +} + + + +static void +update_templates_menu (FMDirectoryView *view) +{ + gboolean any_templates; + GList *sorted_copy, *node; + CajaDirectory *directory; + GtkUIManager *ui_manager; + char *uri; + GtkAction *action; + char *templates_directory_uri; + + if (caja_should_use_templates_directory ()) { + templates_directory_uri = caja_get_templates_directory_uri (); + } else { + templates_directory_uri = NULL; + } + + /* There is a race condition here. If we don't mark the scripts menu as + valid before we begin our task then we can lose template menu updates that + occur before we finish. */ + view->details->templates_invalid = FALSE; + + ui_manager = caja_window_info_get_ui_manager (view->details->window); + caja_ui_unmerge_ui (ui_manager, + &view->details->templates_merge_id, + &view->details->templates_action_group); + + caja_ui_prepare_merge_ui (ui_manager, + "TemplatesGroup", + &view->details->templates_merge_id, + &view->details->templates_action_group); + + /* As we walk through the directories, remove any that no longer belong. */ + any_templates = FALSE; + sorted_copy = caja_directory_list_sort_by_uri + (caja_directory_list_copy (view->details->templates_directory_list)); + for (node = sorted_copy; node != NULL; node = node->next) { + directory = node->data; + + uri = caja_directory_get_uri (directory); + if (!directory_belongs_in_templates_menu (templates_directory_uri, uri)) { + remove_directory_from_templates_directory_list (view, directory); + } else if (update_directory_in_templates_menu (view, + templates_directory_uri, + directory)) { + any_templates = TRUE; + } + g_free (uri); + } + caja_directory_list_free (sorted_copy); + + action = gtk_action_group_get_action (view->details->dir_action_group, FM_ACTION_NO_TEMPLATES); + gtk_action_set_visible (action, !any_templates); + + g_free (templates_directory_uri); +} + + +static void +action_open_scripts_folder_callback (GtkAction *action, + gpointer callback_data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (callback_data); + + open_location (view, scripts_directory_uri, CAJA_WINDOW_OPEN_ACCORDING_TO_MODE, 0); + + eel_show_info_dialog_with_details + (_("All executable files in this folder will appear in the " + "Scripts menu."), + _("Choosing a script from the menu will run " + "that script with any selected items as input."), + _("All executable files in this folder will appear in the " + "Scripts menu. Choosing a script from the menu will run " + "that script.\n\n" + "When executed from a local folder, scripts will be passed " + "the selected file names. When executed from a remote folder " + "(e.g. a folder showing web or ftp content), scripts will " + "be passed no parameters.\n\n" + "In all cases, the following environment variables will be " + "set by Caja, which the scripts may use:\n\n" + "CAJA_SCRIPT_SELECTED_FILE_PATHS: newline-delimited paths for selected files (only if local)\n\n" + "CAJA_SCRIPT_SELECTED_URIS: newline-delimited URIs for selected files\n\n" + "CAJA_SCRIPT_CURRENT_URI: URI for current location\n\n" + "CAJA_SCRIPT_WINDOW_GEOMETRY: position and size of current window\n\n" + "CAJA_SCRIPT_NEXT_PANE_SELECTED_FILE_PATHS: newline-delimited paths for selected files in the inactive pane of a split-view window (only if local)\n\n" + "CAJA_SCRIPT_NEXT_PANE_SELECTED_URIS: newline-delimited URIs for selected files in the inactive pane of a split-view window\n\n" + "CAJA_SCRIPT_NEXT_PANE_CURRENT_URI: URI for current location in the inactive pane of a split-view window"), + fm_directory_view_get_containing_window (view)); +} + +static GtkMenu * +create_popup_menu (FMDirectoryView *view, const char *popup_path) +{ + GtkWidget *menu; + + menu = gtk_ui_manager_get_widget (caja_window_info_get_ui_manager (view->details->window), + popup_path); + gtk_menu_set_screen (GTK_MENU (menu), + gtk_widget_get_screen (GTK_WIDGET (view))); + gtk_widget_show (GTK_WIDGET (menu)); + + return GTK_MENU (menu); +} + +static void +copy_or_cut_files (FMDirectoryView *view, + GList *clipboard_contents, + gboolean cut) +{ + int count; + char *status_string, *name; + CajaClipboardInfo info; + GtkTargetList *target_list; + GtkTargetEntry *targets; + int n_targets; + + info.files = clipboard_contents; + info.cut = cut; + + target_list = gtk_target_list_new (NULL, 0); + gtk_target_list_add (target_list, copied_files_atom, 0, 0); + gtk_target_list_add_uri_targets (target_list, 0); + gtk_target_list_add_text_targets (target_list, 0); + + targets = gtk_target_table_new_from_list (target_list, &n_targets); + gtk_target_list_unref (target_list); + + gtk_clipboard_set_with_data (caja_clipboard_get (GTK_WIDGET (view)), + targets, n_targets, + caja_get_clipboard_callback, caja_clear_clipboard_callback, + NULL); + gtk_target_table_free (targets, n_targets); + + caja_clipboard_monitor_set_clipboard_info (caja_clipboard_monitor_get (), &info); + + count = g_list_length (clipboard_contents); + if (count == 1) { + name = caja_file_get_display_name (clipboard_contents->data); + if (cut) { + status_string = g_strdup_printf (_("\"%s\" will be moved " + "if you select the Paste command"), + name); + } else { + status_string = g_strdup_printf (_("\"%s\" will be copied " + "if you select the Paste command"), + name); + } + g_free (name); + } else { + if (cut) { + status_string = g_strdup_printf (ngettext("The %'d selected item will be moved " + "if you select the Paste command", + "The %'d selected items will be moved " + "if you select the Paste command", + count), + count); + } else { + status_string = g_strdup_printf (ngettext("The %'d selected item will be copied " + "if you select the Paste command", + "The %'d selected items will be copied " + "if you select the Paste command", + count), + count); + } + } + + caja_window_slot_info_set_status (view->details->slot, + status_string); + g_free (status_string); +} + +static void +action_copy_files_callback (GtkAction *action, + gpointer callback_data) +{ + FMDirectoryView *view; + GList *selection; + + view = FM_DIRECTORY_VIEW (callback_data); + + selection = fm_directory_view_get_selection_for_file_transfer (view); + copy_or_cut_files (view, selection, FALSE); + caja_file_list_free (selection); +} + +static void +move_copy_selection_to_location (FMDirectoryView *view, + int copy_action, + char *target_uri) +{ + GList *selection, *uris, *l; + + selection = fm_directory_view_get_selection_for_file_transfer (view); + if (selection == NULL) { + return; + } + + uris = NULL; + for (l = selection; l != NULL; l = l->next) { + uris = g_list_prepend (uris, + caja_file_get_uri ((CajaFile *) l->data)); + } + uris = g_list_reverse (uris); + + fm_directory_view_move_copy_items (uris, NULL, target_uri, + copy_action, + 0, 0, + view); + + eel_g_list_free_deep (uris); + caja_file_list_free (selection); +} + +static void +move_copy_selection_to_next_pane (FMDirectoryView *view, + int copy_action) +{ + CajaWindowSlotInfo *slot; + char *dest_location; + + slot = caja_window_info_get_extra_slot (fm_directory_view_get_caja_window (view)); + g_return_if_fail (slot != NULL); + + dest_location = caja_window_slot_info_get_current_location (slot); + g_return_if_fail (dest_location != NULL); + + move_copy_selection_to_location (view, copy_action, dest_location); +} + +static void +action_copy_to_next_pane_callback (GtkAction *action, gpointer callback_data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (callback_data); + move_copy_selection_to_next_pane (view, + GDK_ACTION_COPY); +} + +static void +action_move_to_next_pane_callback (GtkAction *action, gpointer callback_data) +{ + CajaWindowSlotInfo *slot; + char *dest_location; + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (callback_data); + + slot = caja_window_info_get_extra_slot (fm_directory_view_get_caja_window (view)); + g_return_if_fail (slot != NULL); + + dest_location = caja_window_slot_info_get_current_location (slot); + g_return_if_fail (dest_location != NULL); + + move_copy_selection_to_location (view, GDK_ACTION_MOVE, dest_location); +} + +static void +action_copy_to_home_callback (GtkAction *action, gpointer callback_data) +{ + FMDirectoryView *view; + char *dest_location; + + view = FM_DIRECTORY_VIEW (callback_data); + + dest_location = caja_get_home_directory_uri (); + move_copy_selection_to_location (view, GDK_ACTION_COPY, dest_location); + g_free (dest_location); +} + +static void +action_move_to_home_callback (GtkAction *action, gpointer callback_data) +{ + FMDirectoryView *view; + char *dest_location; + + view = FM_DIRECTORY_VIEW (callback_data); + + dest_location = caja_get_home_directory_uri (); + move_copy_selection_to_location (view, GDK_ACTION_MOVE, dest_location); + g_free (dest_location); +} + +static void +action_copy_to_desktop_callback (GtkAction *action, gpointer callback_data) +{ + FMDirectoryView *view; + char *dest_location; + + view = FM_DIRECTORY_VIEW (callback_data); + + dest_location = caja_get_desktop_directory_uri (); + move_copy_selection_to_location (view, GDK_ACTION_COPY, dest_location); + g_free (dest_location); +} + +static void +action_move_to_desktop_callback (GtkAction *action, gpointer callback_data) +{ + FMDirectoryView *view; + char *dest_location; + + view = FM_DIRECTORY_VIEW (callback_data); + + dest_location = caja_get_desktop_directory_uri (); + move_copy_selection_to_location (view, GDK_ACTION_MOVE, dest_location); + g_free (dest_location); +} + +static void +action_cut_files_callback (GtkAction *action, + gpointer callback_data) +{ + FMDirectoryView *view; + GList *selection; + + view = FM_DIRECTORY_VIEW (callback_data); + + selection = fm_directory_view_get_selection_for_file_transfer (view); + copy_or_cut_files (view, selection, TRUE); + caja_file_list_free (selection); +} + +static void +paste_clipboard_data (FMDirectoryView *view, + GtkSelectionData *selection_data, + char *destination_uri) +{ + gboolean cut; + GList *item_uris; + + cut = FALSE; + item_uris = caja_clipboard_get_uri_list_from_selection_data (selection_data, &cut, + copied_files_atom); + + if (item_uris == NULL|| destination_uri == NULL) { + caja_window_slot_info_set_status (view->details->slot, + _("There is nothing on the clipboard to paste.")); + } else { + fm_directory_view_move_copy_items (item_uris, NULL, destination_uri, + cut ? GDK_ACTION_MOVE : GDK_ACTION_COPY, + 0, 0, + view); + + /* If items are cut then remove from clipboard */ + if (cut) { + gtk_clipboard_clear (caja_clipboard_get (GTK_WIDGET (view))); + } + + eel_g_list_free_deep (item_uris); + } +} + +static void +paste_clipboard_received_callback (GtkClipboard *clipboard, + GtkSelectionData *selection_data, + gpointer data) +{ + FMDirectoryView *view; + char *view_uri; + + view = FM_DIRECTORY_VIEW (data); + + view_uri = fm_directory_view_get_backing_uri (view); + + if (view->details->window != NULL) { + paste_clipboard_data (view, selection_data, view_uri); + } + + g_free (view_uri); + + g_object_unref (view); +} + +typedef struct { + FMDirectoryView *view; + CajaFile *target; +} PasteIntoData; + +static void +paste_into_clipboard_received_callback (GtkClipboard *clipboard, + GtkSelectionData *selection_data, + gpointer callback_data) +{ + PasteIntoData *data; + FMDirectoryView *view; + char *directory_uri; + + data = (PasteIntoData *) callback_data; + + view = FM_DIRECTORY_VIEW (data->view); + + if (view->details->window != NULL) { + directory_uri = caja_file_get_activation_uri (data->target); + + paste_clipboard_data (view, selection_data, directory_uri); + + g_free (directory_uri); + } + + g_object_unref (view); + caja_file_unref (data->target); + g_free (data); +} + +static void +action_paste_files_callback (GtkAction *action, + gpointer callback_data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (callback_data); + + g_object_ref (view); + gtk_clipboard_request_contents (caja_clipboard_get (GTK_WIDGET (view)), + copied_files_atom, + paste_clipboard_received_callback, + view); +} + +static void +paste_into (FMDirectoryView *view, + CajaFile *target) +{ + PasteIntoData *data; + + g_assert (FM_IS_DIRECTORY_VIEW (view)); + g_assert (CAJA_IS_FILE (target)); + + data = g_new (PasteIntoData, 1); + + data->view = g_object_ref (view); + data->target = caja_file_ref (target); + + gtk_clipboard_request_contents (caja_clipboard_get (GTK_WIDGET (view)), + copied_files_atom, + paste_into_clipboard_received_callback, + data); +} + +static void +action_paste_files_into_callback (GtkAction *action, + gpointer callback_data) +{ + FMDirectoryView *view; + GList *selection; + + view = FM_DIRECTORY_VIEW (callback_data); + selection = fm_directory_view_get_selection (view); + if (selection != NULL) { + paste_into (view, CAJA_FILE (selection->data)); + caja_file_list_free (selection); + } + +} + +static void +real_action_rename (FMDirectoryView *view, + gboolean select_all) +{ + CajaFile *file; + GList *selection; + + g_assert (FM_IS_DIRECTORY_VIEW (view)); + + selection = fm_directory_view_get_selection (view); + + if (selection_not_empty_in_menu_callback (view, selection)) { + file = CAJA_FILE (selection->data); + if (!select_all) { + /* directories don't have a file extension, so + * they are always pre-selected as a whole */ + select_all = caja_file_is_directory (file); + } + EEL_CALL_METHOD (FM_DIRECTORY_VIEW_CLASS, view, start_renaming_file, (view, file, select_all)); + } + + caja_file_list_free (selection); +} + +static void +action_rename_callback (GtkAction *action, + gpointer callback_data) +{ + real_action_rename (FM_DIRECTORY_VIEW (callback_data), FALSE); +} + +static void +action_rename_select_all_callback (GtkAction *action, + gpointer callback_data) +{ + real_action_rename (FM_DIRECTORY_VIEW (callback_data), TRUE); +} + +static void +file_mount_callback (CajaFile *file, + GFile *result_location, + GError *error, + gpointer callback_data) +{ + if (error != NULL && + (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, NULL); + } +} + +static void +file_unmount_callback (CajaFile *file, + GFile *result_location, + GError *error, + gpointer callback_data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (callback_data); + fm_directory_view_set_initiated_unmount (view, FALSE); + g_object_unref (view); + + if (error != NULL && + (error->domain != G_IO_ERROR || + (error->code != G_IO_ERROR_CANCELLED && + error->code != G_IO_ERROR_FAILED_HANDLED))) { + eel_show_error_dialog (_("Unable to unmount location"), + error->message, NULL); + } +} + +static void +file_eject_callback (CajaFile *file, + GFile *result_location, + GError *error, + gpointer callback_data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (callback_data); + fm_directory_view_set_initiated_unmount (view, FALSE); + g_object_unref (view); + + if (error != NULL && + (error->domain != G_IO_ERROR || + (error->code != G_IO_ERROR_CANCELLED && + error->code != G_IO_ERROR_FAILED_HANDLED))) { + eel_show_error_dialog (_("Unable to eject location"), + error->message, NULL); + } +} + +static void +file_stop_callback (CajaFile *file, + GFile *result_location, + GError *error, + gpointer callback_data) +{ + if (error != NULL && + (error->domain != G_IO_ERROR || + (error->code != G_IO_ERROR_CANCELLED && + error->code != G_IO_ERROR_FAILED_HANDLED))) { + eel_show_error_dialog (_("Unable to stop drive"), + error->message, NULL); + } +} + +static void +action_mount_volume_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + GList *selection, *l; + FMDirectoryView *view; + GMountOperation *mount_op; + + view = FM_DIRECTORY_VIEW (data); + + selection = fm_directory_view_get_selection (view); + for (l = selection; l != NULL; l = l->next) { + file = CAJA_FILE (l->data); + + if (caja_file_can_mount (file)) { + mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); + g_mount_operation_set_password_save (mount_op, G_PASSWORD_SAVE_FOR_SESSION); + caja_file_mount (file, mount_op, NULL, + file_mount_callback, NULL); + g_object_unref (mount_op); + } + } + caja_file_list_free (selection); +} + +static void +action_unmount_volume_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + GList *selection, *l; + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (data); + + selection = fm_directory_view_get_selection (view); + + for (l = selection; l != NULL; l = l->next) { + file = CAJA_FILE (l->data); + if (caja_file_can_unmount (file)) { + GMountOperation *mount_op; + mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); + fm_directory_view_set_initiated_unmount (view, TRUE); + caja_file_unmount (file, mount_op, NULL, + file_unmount_callback, g_object_ref (view)); + g_object_unref (mount_op); + } + } + caja_file_list_free (selection); +} + +static void +action_format_volume_callback (GtkAction *action, + gpointer data) +{ +#ifdef TODO_GIO + CajaFile *file; + GList *selection, *l; + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (data); + + selection = fm_directory_view_get_selection (view); + for (l = selection; l != NULL; l = l->next) { + file = CAJA_FILE (l->data); + + if (something) { + g_spawn_command_line_async ("gfloppy", NULL); + } + } + caja_file_list_free (selection); +#endif +} + +static void +action_eject_volume_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + GList *selection, *l; + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (data); + + selection = fm_directory_view_get_selection (view); + for (l = selection; l != NULL; l = l->next) { + file = CAJA_FILE (l->data); + + if (caja_file_can_eject (file)) { + GMountOperation *mount_op; + mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); + fm_directory_view_set_initiated_unmount (view, TRUE); + caja_file_eject (file, mount_op, NULL, + file_eject_callback, g_object_ref (view)); + g_object_unref (mount_op); + } + } + caja_file_list_free (selection); +} + +static void +file_start_callback (CajaFile *file, + GFile *result_location, + GError *error, + gpointer callback_data) +{ + if (error != NULL && + (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 start location"), + error->message, NULL); + } +} + +static void +action_start_volume_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + GList *selection, *l; + FMDirectoryView *view; + GMountOperation *mount_op; + + view = FM_DIRECTORY_VIEW (data); + + selection = fm_directory_view_get_selection (view); + for (l = selection; l != NULL; l = l->next) { + file = CAJA_FILE (l->data); + + if (caja_file_can_start (file) || caja_file_can_start_degraded (file)) { + mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); + caja_file_start (file, mount_op, NULL, + file_start_callback, NULL); + g_object_unref (mount_op); + } + } + caja_file_list_free (selection); +} + +static void +action_stop_volume_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + GList *selection, *l; + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (data); + + selection = fm_directory_view_get_selection (view); + for (l = selection; l != NULL; l = l->next) { + file = CAJA_FILE (l->data); + + if (caja_file_can_stop (file)) { + GMountOperation *mount_op; + mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); + caja_file_stop (file, mount_op, NULL, + file_stop_callback, NULL); + g_object_unref (mount_op); + } + } + caja_file_list_free (selection); +} + +static void +action_detect_media_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + GList *selection, *l; + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (data); + + selection = fm_directory_view_get_selection (view); + for (l = selection; l != NULL; l = l->next) { + file = CAJA_FILE (l->data); + + if (caja_file_can_poll_for_media (file) && !caja_file_is_media_check_automatic (file)) { + caja_file_poll_for_media (file); + } + } + caja_file_list_free (selection); +} + +static void +action_self_mount_volume_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + FMDirectoryView *view; + GMountOperation *mount_op; + + view = FM_DIRECTORY_VIEW (data); + + file = fm_directory_view_get_directory_as_file (view); + if (file == NULL) { + return; + } + + mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); + g_mount_operation_set_password_save (mount_op, G_PASSWORD_SAVE_FOR_SESSION); + caja_file_mount (file, mount_op, NULL, file_mount_callback, NULL); + g_object_unref (mount_op); +} + +static void +action_self_unmount_volume_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + FMDirectoryView *view; + GMountOperation *mount_op; + + view = FM_DIRECTORY_VIEW (data); + + file = fm_directory_view_get_directory_as_file (view); + if (file == NULL) { + return; + } + + mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); + fm_directory_view_set_initiated_unmount (view, TRUE); + caja_file_unmount (file, mount_op, NULL, file_unmount_callback, g_object_ref (view)); + g_object_unref (mount_op); +} + +static void +action_self_eject_volume_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + FMDirectoryView *view; + GMountOperation *mount_op; + + view = FM_DIRECTORY_VIEW (data); + + file = fm_directory_view_get_directory_as_file (view); + if (file == NULL) { + return; + } + + mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); + fm_directory_view_set_initiated_unmount (view, TRUE); + caja_file_eject (file, mount_op, NULL, file_eject_callback, g_object_ref (view)); + g_object_unref (mount_op); +} + +static void +action_self_format_volume_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (data); + + file = fm_directory_view_get_directory_as_file (view); + if (file == NULL) { + return; + } + +#ifdef TODO_GIO + if (something) { + g_spawn_command_line_async ("gfloppy", NULL); + } +#endif +} + +static void +action_self_start_volume_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + FMDirectoryView *view; + GMountOperation *mount_op; + + view = FM_DIRECTORY_VIEW (data); + + file = fm_directory_view_get_directory_as_file (view); + if (file == NULL) { + return; + } + + mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); + caja_file_start (file, mount_op, NULL, file_start_callback, NULL); + g_object_unref (mount_op); +} + +static void +action_self_stop_volume_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + FMDirectoryView *view; + GMountOperation *mount_op; + + view = FM_DIRECTORY_VIEW (data); + + file = fm_directory_view_get_directory_as_file (view); + if (file == NULL) { + return; + } + + mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); + caja_file_stop (file, mount_op, NULL, + file_stop_callback, NULL); + g_object_unref (mount_op); +} + +static void +action_self_detect_media_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (data); + + file = fm_directory_view_get_directory_as_file (view); + if (file == NULL) { + return; + } + + caja_file_poll_for_media (file); +} + +static void +action_location_mount_volume_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + FMDirectoryView *view; + GMountOperation *mount_op; + + view = FM_DIRECTORY_VIEW (data); + + file = view->details->location_popup_directory_as_file; + if (file == NULL) { + return; + } + + mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); + g_mount_operation_set_password_save (mount_op, G_PASSWORD_SAVE_FOR_SESSION); + caja_file_mount (file, mount_op, NULL, file_mount_callback, NULL); + g_object_unref (mount_op); +} + +static void +action_location_unmount_volume_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + FMDirectoryView *view; + GMountOperation *mount_op; + + view = FM_DIRECTORY_VIEW (data); + + file = view->details->location_popup_directory_as_file; + if (file == NULL) { + return; + } + + mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); + fm_directory_view_set_initiated_unmount (view, TRUE); + caja_file_unmount (file, mount_op, NULL, + file_unmount_callback, g_object_ref (view)); + g_object_unref (mount_op); +} + +static void +action_location_eject_volume_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + FMDirectoryView *view; + GMountOperation *mount_op; + + view = FM_DIRECTORY_VIEW (data); + + file = view->details->location_popup_directory_as_file; + if (file == NULL) { + return; + } + + mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); + fm_directory_view_set_initiated_unmount (view, TRUE); + caja_file_eject (file, mount_op, NULL, + file_eject_callback, g_object_ref (view)); + g_object_unref (mount_op); +} + +static void +action_location_format_volume_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (data); + + file = view->details->location_popup_directory_as_file; + if (file == NULL) { + return; + } + +#ifdef TODO_GIO + if (something) { + g_spawn_command_line_async ("gfloppy", NULL); + } +#endif +} + +static void +action_location_start_volume_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + FMDirectoryView *view; + GMountOperation *mount_op; + + view = FM_DIRECTORY_VIEW (data); + + file = view->details->location_popup_directory_as_file; + if (file == NULL) { + return; + } + + mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); + caja_file_start (file, mount_op, NULL, file_start_callback, NULL); + g_object_unref (mount_op); +} + +static void +action_location_stop_volume_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + FMDirectoryView *view; + GMountOperation *mount_op; + + view = FM_DIRECTORY_VIEW (data); + + file = view->details->location_popup_directory_as_file; + if (file == NULL) { + return; + } + + mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); + caja_file_stop (file, mount_op, NULL, + file_stop_callback, NULL); + g_object_unref (mount_op); +} + +static void +action_location_detect_media_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (data); + + file = view->details->location_popup_directory_as_file; + if (file == NULL) { + return; + } + + caja_file_poll_for_media (file); +} + +static void +connect_to_server_response_callback (GtkDialog *dialog, + int response_id, + gpointer data) +{ + GtkEntry *entry; + char *uri; + const char *name; + char *icon; + + entry = GTK_ENTRY (data); + + switch (response_id) { + case GTK_RESPONSE_OK: + uri = g_object_get_data (G_OBJECT (dialog), "link-uri"); + icon = g_object_get_data (G_OBJECT (dialog), "link-icon"); + name = gtk_entry_get_text (entry); +#ifdef GIO_CONVERSION_DONE + mate_vfs_connect_to_server (uri, (char *)name, icon); +#endif + gtk_widget_destroy (GTK_WIDGET (dialog)); + 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 +entry_activate_callback (GtkEntry *entry, + gpointer user_data) +{ + GtkDialog *dialog; + + dialog = GTK_DIALOG (user_data); + gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); +} + +static void +action_connect_to_server_link_callback (GtkAction *action, + gpointer data) +{ + CajaFile *file; + GList *selection; + FMDirectoryView *view; + char *uri; + CajaIconInfo *icon; + const char *icon_name; + char *name; + GtkWidget *dialog; + GtkWidget *label; + GtkWidget *entry; + GtkWidget *box; + char *title; + + view = FM_DIRECTORY_VIEW (data); + + selection = fm_directory_view_get_selection (view); + + if (!eel_g_list_exactly_one_item (selection)) { + caja_file_list_free (selection); + return; + } + + file = CAJA_FILE (selection->data); + + uri = caja_file_get_activation_uri (file); + icon = caja_file_get_icon (file, CAJA_ICON_SIZE_STANDARD, 0); + icon_name = caja_icon_info_get_used_name (icon); + name = caja_file_get_display_name (file); + + if (uri != NULL) { + title = g_strdup_printf (_("Connect to Server %s"), name); + dialog = gtk_dialog_new_with_buttons (title, + fm_directory_view_get_containing_window (view), + GTK_DIALOG_NO_SEPARATOR, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + _("_Connect"), GTK_RESPONSE_OK, + NULL); + + g_object_set_data_full (G_OBJECT (dialog), "link-uri", g_strdup (uri), g_free); + g_object_set_data_full (G_OBJECT (dialog), "link-icon", g_strdup (icon_name), g_free); + + gtk_container_set_border_width (GTK_CONTAINER (dialog), 5); + gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), 2); + + box = gtk_hbox_new (FALSE, 12); + gtk_widget_show (box); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + box, TRUE, TRUE, 0); + + label = gtk_label_new_with_mnemonic (_("Link _name:")); + gtk_widget_show (label); + + gtk_box_pack_start (GTK_BOX (box), label, TRUE, TRUE, 12); + + entry = gtk_entry_new (); + if (name) { + gtk_entry_set_text (GTK_ENTRY (entry), name); + } + g_signal_connect (entry, + "activate", + G_CALLBACK (entry_activate_callback), + dialog); + + gtk_widget_show (entry); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry); + + gtk_box_pack_start (GTK_BOX (box), entry, TRUE, TRUE, 12); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog), + GTK_RESPONSE_OK); + g_signal_connect (dialog, "response", + G_CALLBACK (connect_to_server_response_callback), + entry); + gtk_widget_show (dialog); + } + + g_free (uri); + g_object_unref (icon); + g_free (name); +} + +static void +action_location_open_alternate_callback (GtkAction *action, + gpointer callback_data) +{ + FMDirectoryView *view; + CajaFile *file; + + view = FM_DIRECTORY_VIEW (callback_data); + + file = view->details->location_popup_directory_as_file; + if (file == NULL) { + return; + } + + fm_directory_view_activate_file (view, + file, + CAJA_WINDOW_OPEN_IN_NAVIGATION, + 0); +} + +static void +action_location_open_in_new_tab_callback (GtkAction *action, + gpointer callback_data) +{ + FMDirectoryView *view; + CajaFile *file; + + view = FM_DIRECTORY_VIEW (callback_data); + + file = view->details->location_popup_directory_as_file; + if (file == NULL) { + return; + } + + fm_directory_view_activate_file (view, + file, + CAJA_WINDOW_OPEN_ACCORDING_TO_MODE, + CAJA_WINDOW_OPEN_FLAG_NEW_TAB); +} + +static void +action_location_open_folder_window_callback (GtkAction *action, + gpointer callback_data) +{ + FMDirectoryView *view; + CajaFile *file; + + view = FM_DIRECTORY_VIEW (callback_data); + + file = view->details->location_popup_directory_as_file; + g_return_if_fail (file != NULL); + + fm_directory_view_activate_file (view, + file, + CAJA_WINDOW_OPEN_IN_SPATIAL, + 0); +} + +static void +action_location_cut_callback (GtkAction *action, + gpointer callback_data) +{ + FMDirectoryView *view; + CajaFile *file; + GList *files; + + view = FM_DIRECTORY_VIEW (callback_data); + + file = view->details->location_popup_directory_as_file; + g_return_if_fail (file != NULL); + + files = g_list_append (NULL, file); + copy_or_cut_files (view, files, TRUE); + g_list_free (files); +} + +static void +action_location_copy_callback (GtkAction *action, + gpointer callback_data) +{ + FMDirectoryView *view; + CajaFile *file; + GList *files; + + view = FM_DIRECTORY_VIEW (callback_data); + + file = view->details->location_popup_directory_as_file; + g_return_if_fail (file != NULL); + + files = g_list_append (NULL, file); + copy_or_cut_files (view, files, FALSE); + g_list_free (files); +} + +static void +action_location_paste_files_into_callback (GtkAction *action, + gpointer callback_data) +{ + FMDirectoryView *view; + CajaFile *file; + + view = FM_DIRECTORY_VIEW (callback_data); + + file = view->details->location_popup_directory_as_file; + g_return_if_fail (file != NULL); + + paste_into (view, file); +} + +static void +action_location_trash_callback (GtkAction *action, + gpointer callback_data) +{ + FMDirectoryView *view; + CajaFile *file; + GList *files; + + view = FM_DIRECTORY_VIEW (callback_data); + + file = view->details->location_popup_directory_as_file; + g_return_if_fail (file != NULL); + + files = g_list_append (NULL, file); + trash_or_delete_files (fm_directory_view_get_containing_window (view), + files, TRUE, + view); + g_list_free (files); +} + +static void +action_location_delete_callback (GtkAction *action, + gpointer callback_data) +{ + FMDirectoryView *view; + CajaFile *file; + GFile *location; + GList *files; + + view = FM_DIRECTORY_VIEW (callback_data); + + file = view->details->location_popup_directory_as_file; + g_return_if_fail (file != NULL); + + location = caja_file_get_location (file); + + files = g_list_append (NULL, location); + caja_file_operations_delete (files, fm_directory_view_get_containing_window (view), + NULL, NULL); + + eel_g_object_list_free (files); +} + +static void +action_location_restore_from_trash_callback (GtkAction *action, + gpointer callback_data) +{ + FMDirectoryView *view; + CajaFile *file; + GList l; + + view = FM_DIRECTORY_VIEW (callback_data); + file = view->details->location_popup_directory_as_file; + + l.prev = NULL; + l.next = NULL; + l.data = file; + caja_restore_files_from_trash (&l, + fm_directory_view_get_containing_window (view)); +} + +static void +fm_directory_view_init_show_hidden_files (FMDirectoryView *view) +{ + CajaWindowShowHiddenFilesMode mode; + gboolean show_hidden_changed; + gboolean show_hidden_default_setting; + + if (view->details->ignore_hidden_file_preferences) { + return; + } + + show_hidden_changed = FALSE; + mode = caja_window_info_get_hidden_files_mode (view->details->window); + + if (mode == CAJA_WINDOW_SHOW_HIDDEN_FILES_DEFAULT) { + show_hidden_default_setting = eel_preferences_get_boolean (CAJA_PREFERENCES_SHOW_HIDDEN_FILES); + if (show_hidden_default_setting != view->details->show_hidden_files) { + view->details->show_hidden_files = show_hidden_default_setting; + view->details->show_backup_files = show_hidden_default_setting; + show_hidden_changed = TRUE; + } + } else { + if (mode == CAJA_WINDOW_SHOW_HIDDEN_FILES_ENABLE) { + show_hidden_changed = !view->details->show_hidden_files; + view->details->show_hidden_files = TRUE; + view->details->show_backup_files = TRUE; + } else { + show_hidden_changed = view->details->show_hidden_files; + view->details->show_hidden_files = FALSE; + view->details->show_backup_files = FALSE; + } + } + + if (show_hidden_changed && (view->details->model != NULL)) { + load_directory (view, view->details->model); + } + +} + +static const GtkActionEntry directory_view_entries[] = { + /* name, stock id, label */ { "New Documents", "document-new", N_("Create _Document") }, + /* name, stock id, label */ { "Open With", NULL, N_("Open Wit_h"), + NULL, N_("Choose a program with which to open the selected item") }, + /* name, stock id */ { "Properties", GTK_STOCK_PROPERTIES, + /* label, accelerator */ N_("_Properties"), "<alt>Return", + /* tooltip */ N_("View or modify the properties of each selected item"), + G_CALLBACK (action_properties_callback) }, + /* name, stock id */ { "PropertiesAccel", NULL, + /* label, accelerator */ "PropertiesAccel", "<control>I", + /* tooltip */ NULL, + G_CALLBACK (action_properties_callback) }, + /* name, stock id */ { "New Folder", "folder-new", + /* label, accelerator */ N_("Create _Folder"), "<control><shift>N", + /* tooltip */ N_("Create a new empty folder inside this folder"), + G_CALLBACK (action_new_folder_callback) }, + /* name, stock id, label */ { "No Templates", NULL, N_("No templates installed") }, + /* name, stock id */ { "New Empty File", NULL, + /* translators: this is used to indicate that a file doesn't contain anything */ + /* label, accelerator */ N_("_Empty File"), NULL, + /* tooltip */ N_("Create a new empty file inside this folder"), + G_CALLBACK (action_new_empty_file_callback) }, + /* name, stock id */ { "New Launcher", NULL, + /* label, accelerator */ N_("Create L_auncher..."), NULL, + /* tooltip */ N_("Create a new launcher"), + G_CALLBACK (action_new_launcher_callback) }, + /* name, stock id */ { "Open", NULL, + /* label, accelerator */ N_("_Open"), "<control>o", + /* tooltip */ N_("Open the selected item in this window"), + G_CALLBACK (action_open_callback) }, + /* name, stock id */ { "OpenAccel", NULL, + /* label, accelerator */ "OpenAccel", "<alt>Down", + /* tooltip */ NULL, + G_CALLBACK (action_open_callback) }, + /* name, stock id */ { "OpenAlternate", NULL, + /* label, accelerator */ N_("Open in Navigation Window"), "<control><shift>o", + /* tooltip */ N_("Open each selected item in a navigation window"), + G_CALLBACK (action_open_alternate_callback) }, + /* name, stock id */ { "OpenInNewTab", NULL, + /* label, accelerator */ N_("Open in New _Tab"), "<control><shift>o", + /* tooltip */ N_("Open each selected item in a new tab"), + G_CALLBACK (action_open_new_tab_callback) }, + /* name, stock id */ { "OpenFolderWindow", NULL, + /* label, accelerator */ N_("Open in _Folder Window"), NULL, + /* tooltip */ N_("Open each selected item in a folder window"), + G_CALLBACK (action_open_folder_window_callback) }, + /* name, stock id */ { "OtherApplication1", NULL, + /* label, accelerator */ N_("Other _Application..."), NULL, + /* tooltip */ N_("Choose another application with which to open the selected item"), + G_CALLBACK (action_other_application_callback) }, + /* name, stock id */ { "OtherApplication2", NULL, + /* label, accelerator */ N_("Open With Other _Application..."), NULL, + /* tooltip */ N_("Choose another application with which to open the selected item"), + G_CALLBACK (action_other_application_callback) }, + /* name, stock id */ { "Open Scripts Folder", NULL, + /* label, accelerator */ N_("_Open Scripts Folder"), NULL, + /* tooltip */ N_("Show the folder containing the scripts that appear in this menu"), + G_CALLBACK (action_open_scripts_folder_callback) }, + /* name, stock id */ { "Empty Trash", NULL, + /* label, accelerator */ N_("E_mpty Trash"), NULL, + /* tooltip */ N_("Delete all items in the Trash"), + G_CALLBACK (action_empty_trash_callback) }, + /* name, stock id */ { "Cut", GTK_STOCK_CUT, + /* label, accelerator */ NULL, NULL, + /* tooltip */ N_("Prepare the selected files to be moved with a Paste command"), + G_CALLBACK (action_cut_files_callback) }, + /* name, stock id */ { "Copy", GTK_STOCK_COPY, + /* label, accelerator */ NULL, NULL, + /* tooltip */ N_("Prepare the selected files to be copied with a Paste command"), + G_CALLBACK (action_copy_files_callback) }, + /* name, stock id */ { "Paste", GTK_STOCK_PASTE, + /* label, accelerator */ NULL, NULL, + /* tooltip */ N_("Move or copy files previously selected by a Cut or Copy command"), + G_CALLBACK (action_paste_files_callback) }, + /* We make accelerator "" instead of null here to not inherit the stock + accelerator for paste */ + /* name, stock id */ { "Paste Files Into", GTK_STOCK_PASTE, + /* label, accelerator */ N_("_Paste Into Folder"), "", + /* tooltip */ N_("Move or copy files previously selected by a Cut or Copy command into the selected folder"), + G_CALLBACK (action_paste_files_into_callback) }, + /* name, stock id, label */ { "CopyToMenu", NULL, N_("Cop_y to") }, + /* name, stock id, label */ { "MoveToMenu", NULL, N_("M_ove to") }, + /* name, stock id */ { "Select All", NULL, + /* label, accelerator */ N_("Select _All"), "<control>A", + /* tooltip */ N_("Select all items in this window"), + G_CALLBACK (action_select_all_callback) }, + /* name, stock id */ { "Select Pattern", NULL, + /* label, accelerator */ N_("Select I_tems Matching..."), "<control>S", + /* tooltip */ N_("Select items in this window matching a given pattern"), + G_CALLBACK (action_select_pattern_callback) }, + /* name, stock id */ { "Invert Selection", NULL, + /* label, accelerator */ N_("_Invert Selection"), "<control><shift>I", + /* tooltip */ N_("Select all and only the items that are not currently selected"), + G_CALLBACK (action_invert_selection_callback) }, + /* name, stock id */ { "Duplicate", NULL, + /* label, accelerator */ N_("D_uplicate"), NULL, + /* tooltip */ N_("Duplicate each selected item"), + G_CALLBACK (action_duplicate_callback) }, + /* name, stock id */ { "Create Link", NULL, + /* label, accelerator */ N_("Ma_ke Link"), "<control>M", + /* tooltip */ N_("Create a symbolic link for each selected item"), + G_CALLBACK (action_create_link_callback) }, + /* name, stock id */ { "Rename", NULL, + /* label, accelerator */ N_("_Rename..."), "F2", + /* tooltip */ N_("Rename selected item"), + G_CALLBACK (action_rename_callback) }, + /* name, stock id */ { "RenameSelectAll", NULL, + /* label, accelerator */ "RenameSelectAll", "<shift>F2", + /* tooltip */ NULL, + G_CALLBACK (action_rename_select_all_callback) }, + /* name, stock id */ { "Trash", NULL, + /* label, accelerator */ N_("Mo_ve to Trash"), NULL, + /* tooltip */ N_("Move each selected item to the Trash"), + G_CALLBACK (action_trash_callback) }, + /* name, stock id */ { "Delete", NULL, + /* label, accelerator */ N_("_Delete"), "<shift>Delete", + /* tooltip */ N_("Delete each selected item, without moving to the Trash"), + G_CALLBACK (action_delete_callback) }, + /* name, stock id */ { "Restore From Trash", NULL, + /* label, accelerator */ N_("_Restore"), NULL, + NULL, + G_CALLBACK (action_restore_from_trash_callback) }, + /* + * multiview-TODO: decide whether "Reset to Defaults" should + * be window-wide, and not just view-wide. + * Since this also resets the "Show hidden files" mode, + * it is a mixture of both ATM. + */ + /* name, stock id */ { "Reset to Defaults", NULL, + /* label, accelerator */ N_("Reset View to _Defaults"), NULL, + /* tooltip */ N_("Reset sorting order and zoom level to match preferences for this view"), + G_CALLBACK (action_reset_to_defaults_callback) }, + /* name, stock id */ { "Connect To Server Link", NULL, + /* label, accelerator */ N_("Connect To This Server"), NULL, + /* tooltip */ N_("Make a permanent connection to this server"), + G_CALLBACK (action_connect_to_server_link_callback) }, + /* name, stock id */ { "Mount Volume", NULL, + /* label, accelerator */ N_("_Mount"), NULL, + /* tooltip */ N_("Mount the selected volume"), + G_CALLBACK (action_mount_volume_callback) }, + /* name, stock id */ { "Unmount Volume", NULL, + /* label, accelerator */ N_("_Unmount"), NULL, + /* tooltip */ N_("Unmount the selected volume"), + G_CALLBACK (action_unmount_volume_callback) }, + /* name, stock id */ { "Eject Volume", NULL, + /* label, accelerator */ N_("_Eject"), NULL, + /* tooltip */ N_("Eject the selected volume"), + G_CALLBACK (action_eject_volume_callback) }, + /* name, stock id */ { "Format Volume", NULL, + /* label, accelerator */ N_("_Format"), NULL, + /* tooltip */ N_("Format the selected volume"), + G_CALLBACK (action_format_volume_callback) }, + /* name, stock id */ { "Start Volume", NULL, + /* label, accelerator */ N_("_Start"), NULL, + /* tooltip */ N_("Start the selected volume"), + G_CALLBACK (action_start_volume_callback) }, + /* name, stock id */ { "Stop Volume", NULL, + /* label, accelerator */ N_("_Stop"), NULL, + /* tooltip */ N_("Stop the selected volume"), + G_CALLBACK (action_stop_volume_callback) }, + /* name, stock id */ { "Poll", NULL, + /* label, accelerator */ N_("_Detect Media"), NULL, + /* tooltip */ N_("Detect media in the selected drive"), + G_CALLBACK (action_detect_media_callback) }, + /* name, stock id */ { "Self Mount Volume", NULL, + /* label, accelerator */ N_("_Mount"), NULL, + /* tooltip */ N_("Mount the volume associated with the open folder"), + G_CALLBACK (action_self_mount_volume_callback) }, + /* name, stock id */ { "Self Unmount Volume", NULL, + /* label, accelerator */ N_("_Unmount"), NULL, + /* tooltip */ N_("Unmount the volume associated with the open folder"), + G_CALLBACK (action_self_unmount_volume_callback) }, + /* name, stock id */ { "Self Eject Volume", NULL, + /* label, accelerator */ N_("_Eject"), NULL, + /* tooltip */ N_("Eject the volume associated with the open folder"), + G_CALLBACK (action_self_eject_volume_callback) }, + /* name, stock id */ { "Self Format Volume", NULL, + /* label, accelerator */ N_("_Format"), NULL, + /* tooltip */ N_("Format the volume associated with the open folder"), + G_CALLBACK (action_self_format_volume_callback) }, + /* name, stock id */ { "Self Start Volume", NULL, + /* label, accelerator */ N_("_Start"), NULL, + /* tooltip */ N_("Start the volume associated with the open folder"), + G_CALLBACK (action_self_start_volume_callback) }, + /* name, stock id */ { "Self Stop Volume", NULL, + /* label, accelerator */ N_("_Stop"), NULL, + /* tooltip */ N_("Stop the volume associated with the open folder"), + G_CALLBACK (action_self_stop_volume_callback) }, + /* name, stock id */ { "Self Poll", NULL, + /* label, accelerator */ N_("_Detect Media"), NULL, + /* tooltip */ N_("Detect media in the selected drive"), + G_CALLBACK (action_self_detect_media_callback) }, + /* name, stock id */ { "OpenCloseParent", NULL, + /* label, accelerator */ N_("Open File and Close window"), "<alt><shift>Down", + /* tooltip */ NULL, + G_CALLBACK (action_open_close_parent_callback) }, + /* name, stock id */ { "Save Search", NULL, + /* label, accelerator */ N_("Sa_ve Search"), NULL, + /* tooltip */ N_("Save the edited search"), + G_CALLBACK (action_save_search_callback) }, + /* name, stock id */ { "Save Search As", NULL, + /* label, accelerator */ N_("Sa_ve Search As..."), NULL, + /* tooltip */ N_("Save the current search as a file"), + G_CALLBACK (action_save_search_as_callback) }, + + /* Location-specific actions */ + /* name, stock id */ { FM_ACTION_LOCATION_OPEN_ALTERNATE, NULL, + /* label, accelerator */ N_("Open in Navigation Window"), "", + /* tooltip */ N_("Open this folder in a navigation window"), + G_CALLBACK (action_location_open_alternate_callback) }, + /* name, stock id */ { FM_ACTION_LOCATION_OPEN_IN_NEW_TAB, NULL, + /* label, accelerator */ N_("Open in New _Tab"), "", + /* tooltip */ N_("Open this folder in a new tab"), + G_CALLBACK (action_location_open_in_new_tab_callback) }, + + /* name, stock id */ { FM_ACTION_LOCATION_OPEN_FOLDER_WINDOW, NULL, + /* label, accelerator */ N_("Open in _Folder Window"), "", + /* tooltip */ N_("Open this folder in a folder window"), + G_CALLBACK (action_location_open_folder_window_callback) }, + + /* name, stock id */ { FM_ACTION_LOCATION_CUT, GTK_STOCK_CUT, + /* label, accelerator */ NULL, "", + /* tooltip */ N_("Prepare this folder to be moved with a Paste command"), + G_CALLBACK (action_location_cut_callback) }, + /* name, stock id */ { FM_ACTION_LOCATION_COPY, GTK_STOCK_COPY, + /* label, accelerator */ NULL, "", + /* tooltip */ N_("Prepare this folder to be copied with a Paste command"), + G_CALLBACK (action_location_copy_callback) }, + /* name, stock id */ { FM_ACTION_LOCATION_PASTE_FILES_INTO, GTK_STOCK_PASTE, + /* label, accelerator */ N_("_Paste Into Folder"), "", + /* tooltip */ N_("Move or copy files previously selected by a Cut or Copy command into this folder"), + G_CALLBACK (action_location_paste_files_into_callback) }, + + /* name, stock id */ { FM_ACTION_LOCATION_TRASH, NULL, + /* label, accelerator */ N_("Mo_ve to Trash"), "", + /* tooltip */ N_("Move this folder to the Trash"), + G_CALLBACK (action_location_trash_callback) }, + /* name, stock id */ { FM_ACTION_LOCATION_DELETE, CAJA_ICON_DELETE, + /* label, accelerator */ N_("_Delete"), "", + /* tooltip */ N_("Delete this folder, without moving to the Trash"), + G_CALLBACK (action_location_delete_callback) }, + /* name, stock id */ { FM_ACTION_LOCATION_RESTORE_FROM_TRASH, NULL, + /* label, accelerator */ N_("_Restore"), NULL, NULL, + G_CALLBACK (action_location_restore_from_trash_callback) }, + + /* name, stock id */ { "Location Mount Volume", NULL, + /* label, accelerator */ N_("_Mount"), NULL, + /* tooltip */ N_("Mount the volume associated with this folder"), + G_CALLBACK (action_location_mount_volume_callback) }, + /* name, stock id */ { "Location Unmount Volume", NULL, + /* label, accelerator */ N_("_Unmount"), NULL, + /* tooltip */ N_("Unmount the volume associated with this folder"), + G_CALLBACK (action_location_unmount_volume_callback) }, + /* name, stock id */ { "Location Eject Volume", NULL, + /* label, accelerator */ N_("_Eject"), NULL, + /* tooltip */ N_("Eject the volume associated with this folder"), + G_CALLBACK (action_location_eject_volume_callback) }, + /* name, stock id */ { "Location Format Volume", NULL, + /* label, accelerator */ N_("_Format"), NULL, + /* tooltip */ N_("Format the volume associated with this folder"), + G_CALLBACK (action_location_format_volume_callback) }, + /* name, stock id */ { "Location Start Volume", NULL, + /* label, accelerator */ N_("_Start"), NULL, + /* tooltip */ N_("Start the volume associated with this folder"), + G_CALLBACK (action_location_start_volume_callback) }, + /* name, stock id */ { "Location Stop Volume", NULL, + /* label, accelerator */ N_("_Stop"), NULL, + /* tooltip */ N_("Stop the volume associated with this folder"), + G_CALLBACK (action_location_stop_volume_callback) }, + /* name, stock id */ { "Location Poll", NULL, + /* label, accelerator */ N_("_Detect Media"), NULL, + /* tooltip */ N_("Detect media in the selected drive"), + G_CALLBACK (action_location_detect_media_callback) }, + + /* name, stock id */ { "LocationProperties", GTK_STOCK_PROPERTIES, + /* label, accelerator */ N_("_Properties"), NULL, + /* tooltip */ N_("View or modify the properties of this folder"), + G_CALLBACK (action_location_properties_callback) }, + + /* name, stock id, label */ {FM_ACTION_COPY_TO_NEXT_PANE, NULL, N_("_Other pane"), + NULL, N_("Copy the current selection to the other pane in the window"), + G_CALLBACK (action_copy_to_next_pane_callback) }, + /* name, stock id, label */ {FM_ACTION_MOVE_TO_NEXT_PANE, NULL, N_("_Other pane"), + NULL, N_("Move the current selection to the other pane in the window"), + G_CALLBACK (action_move_to_next_pane_callback) }, + /* name, stock id, label */ {FM_ACTION_COPY_TO_HOME, CAJA_ICON_HOME, + N_("_Home Folder"), NULL, + N_("Copy the current selection to the home folder"), + G_CALLBACK (action_copy_to_home_callback) }, + /* name, stock id, label */ {FM_ACTION_MOVE_TO_HOME, CAJA_ICON_HOME, + N_("_Home Folder"), NULL, + N_("Move the current selection to the home folder"), + G_CALLBACK (action_move_to_home_callback) }, + /* name, stock id, label */ {FM_ACTION_COPY_TO_DESKTOP, CAJA_ICON_DESKTOP, + N_("_Desktop"), NULL, + N_("Copy the current selection to the desktop"), + G_CALLBACK (action_copy_to_desktop_callback) }, + /* name, stock id, label */ {FM_ACTION_MOVE_TO_DESKTOP, CAJA_ICON_DESKTOP, + N_("_Desktop"), NULL, + N_("Move the current selection to the desktop"), + G_CALLBACK (action_move_to_desktop_callback) }, +}; + +static void +connect_proxy (FMDirectoryView *view, + GtkAction *action, + GtkWidget *proxy, + GtkActionGroup *action_group) +{ + GdkPixbuf *pixbuf; + GtkWidget *image; + + if (strcmp (gtk_action_get_name (action), FM_ACTION_NEW_EMPTY_FILE) == 0 && + GTK_IS_IMAGE_MENU_ITEM (proxy)) { + pixbuf = get_menu_icon ("text-x-generic"); + if (pixbuf != NULL) { + image = gtk_image_new_from_pixbuf (pixbuf); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (proxy), image); + + g_object_unref (pixbuf); + } + } +} + +static void +pre_activate (FMDirectoryView *view, + GtkAction *action, + GtkActionGroup *action_group) +{ + GdkEvent *event; + GtkWidget *proxy; + gboolean activated_from_popup; + + /* check whether action was activated through a popup menu. + * If not, unset the last stored context menu popup position */ + activated_from_popup = FALSE; + + event = gtk_get_current_event (); + proxy = gtk_get_event_widget (event); + + if (proxy != NULL) { + GtkWidget *toplevel; + GdkWindowTypeHint hint; + + toplevel = gtk_widget_get_toplevel (proxy); + + if (GTK_IS_WINDOW (toplevel)) { + hint = gtk_window_get_type_hint (GTK_WINDOW (toplevel)); + + if (hint == GDK_WINDOW_TYPE_HINT_POPUP_MENU) { + activated_from_popup = TRUE; + } + } + } + + if (!activated_from_popup) { + update_context_menu_position_from_event (view, NULL); + } +} + +static void +real_merge_menus (FMDirectoryView *view) +{ + GtkActionGroup *action_group; + GtkUIManager *ui_manager; + GtkAction *action; + const char *ui; + char *tooltip; + + ui_manager = caja_window_info_get_ui_manager (view->details->window); + + action_group = gtk_action_group_new ("DirViewActions"); + gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE); + view->details->dir_action_group = action_group; + gtk_action_group_add_actions (action_group, + directory_view_entries, G_N_ELEMENTS (directory_view_entries), + view); + + /* Translators: %s is a directory */ + tooltip = g_strdup_printf(_("Run or manage scripts from %s"), "~/.config/caja/scripts"); + /* Create a script action here specially because its tooltip is dynamic */ + action = gtk_action_new ("Scripts", _("_Scripts"), tooltip, NULL); + gtk_action_group_add_action (action_group, action); + g_object_unref (action); + g_free (tooltip); + + action = gtk_action_group_get_action (action_group, FM_ACTION_NO_TEMPLATES); + gtk_action_set_sensitive (action, FALSE); + + g_signal_connect_object (action_group, "connect-proxy", + G_CALLBACK (connect_proxy), G_OBJECT (view), + G_CONNECT_SWAPPED); + g_signal_connect_object (action_group, "pre-activate", + G_CALLBACK (pre_activate), G_OBJECT (view), + G_CONNECT_SWAPPED); + + /* Insert action group at end so clipboard action group ends up before it */ + gtk_ui_manager_insert_action_group (ui_manager, action_group, -1); + g_object_unref (action_group); /* owned by ui manager */ + + ui = caja_ui_string_get ("caja-directory-view-ui.xml"); + view->details->dir_merge_id = gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, NULL); + g_signal_connect_object (fm_directory_view_get_background (view), "settings_changed", + G_CALLBACK (schedule_update_menus), G_OBJECT (view), + G_CONNECT_SWAPPED); + + view->details->scripts_invalid = TRUE; + view->details->templates_invalid = TRUE; +} + + +static gboolean +can_paste_into_file (CajaFile *file) +{ + if (caja_file_is_directory (file) && + caja_file_can_write (file)) { + return TRUE; + } + if (caja_file_has_activation_uri (file)) { + GFile *location; + CajaFile *activation_file; + gboolean res; + + location = caja_file_get_activation_location (file); + activation_file = caja_file_get (location); + g_object_unref (location); + + /* The target location might not have data for it read yet, + and we can't want to do sync I/O, so treat the unknown + case as can-write */ + res = (caja_file_get_file_type (activation_file) == G_FILE_TYPE_UNKNOWN) || + (caja_file_get_file_type (activation_file) == G_FILE_TYPE_DIRECTORY && + caja_file_can_write (activation_file)); + + caja_file_unref (activation_file); + + return res; + } + + return FALSE; +} + +static void +clipboard_targets_received (GtkClipboard *clipboard, + GdkAtom *targets, + int n_targets, + gpointer user_data) +{ + FMDirectoryView *view; + gboolean can_paste; + int i; + GList *selection; + int count; + GtkAction *action; + + view = FM_DIRECTORY_VIEW (user_data); + can_paste = FALSE; + + if (view->details->window == NULL || + !view->details->active) { + /* We've been destroyed or became inactive since call */ + g_object_unref (view); + return; + } + + if (targets) { + for (i=0; i < n_targets; i++) { + if (targets[i] == copied_files_atom) { + can_paste = TRUE; + } + } + } + + + selection = fm_directory_view_get_selection (view); + count = g_list_length (selection); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_PASTE); + gtk_action_set_sensitive (action, + can_paste && !fm_directory_view_is_read_only (view)); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_PASTE_FILES_INTO); + gtk_action_set_sensitive (action, + can_paste && count == 1 && + can_paste_into_file (CAJA_FILE (selection->data))); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_LOCATION_PASTE_FILES_INTO); + g_object_set_data (G_OBJECT (action), + "can-paste-according-to-clipboard", + GINT_TO_POINTER (can_paste)); + gtk_action_set_sensitive (action, + GPOINTER_TO_INT (g_object_get_data (G_OBJECT (action), + "can-paste-according-to-clipboard")) && + GPOINTER_TO_INT (g_object_get_data (G_OBJECT (action), + "can-paste-according-to-destination"))); + + caja_file_list_free (selection); + + g_object_unref (view); +} + +static gboolean +showing_trash_directory (FMDirectoryView *view) +{ + CajaFile *file; + + file = fm_directory_view_get_directory_as_file (view); + if (file != NULL) { + return caja_file_is_in_trash (file); + } + return FALSE; +} + +static gboolean +should_show_empty_trash (FMDirectoryView *view) +{ + return (showing_trash_directory (view) || caja_window_info_get_window_type (view->details->window) == CAJA_WINDOW_NAVIGATION); +} + +static gboolean +file_list_all_are_folders (GList *file_list) +{ + GList *l; + CajaFile *file, *linked_file; + char *activation_uri; + gboolean is_dir; + + for (l = file_list; l != NULL; l = l->next) { + file = CAJA_FILE (l->data); + if (caja_file_is_caja_link (file) && + !CAJA_IS_DESKTOP_ICON_FILE (file)) { + if (caja_file_is_launcher (file)) { + return FALSE; + } + + activation_uri = caja_file_get_activation_uri (file); + + if (activation_uri == NULL) { + g_free (activation_uri); + return FALSE; + } + + linked_file = caja_file_get_existing_by_uri (activation_uri); + + /* We might not actually know the type of the linked file yet, + * however we don't want to schedule a read, since that might do things + * like ask for password etc. This is a bit unfortunate, but I don't + * know any way around it, so we do various heuristics here + * to get things mostly right + */ + is_dir = + (linked_file != NULL && + caja_file_is_directory (linked_file)) || + (activation_uri != NULL && + activation_uri[strlen (activation_uri) - 1] == '/'); + + caja_file_unref (linked_file); + g_free (activation_uri); + + if (!is_dir) { + return FALSE; + } + } else if (!(caja_file_is_directory (file) || + CAJA_IS_DESKTOP_ICON_FILE (file))) { + return FALSE; + } + } + return TRUE; +} + +static void +file_should_show_foreach (CajaFile *file, + gboolean *show_mount, + gboolean *show_unmount, + gboolean *show_eject, + gboolean *show_connect, + gboolean *show_format, + gboolean *show_start, + gboolean *show_stop, + gboolean *show_poll, + GDriveStartStopType *start_stop_type) +{ + char *uri; + + *show_mount = FALSE; + *show_unmount = FALSE; + *show_eject = FALSE; + *show_connect = FALSE; + *show_format = FALSE; + *show_start = FALSE; + *show_stop = FALSE; + *show_poll = FALSE; + + if (caja_file_can_eject (file)) { + *show_eject = TRUE; + } + + if (caja_file_can_mount (file)) { + *show_mount = TRUE; + +#ifdef TODO_GIO + if (something && + g_find_program_in_path ("gfloppy")) { + *show_format = TRUE; + } +#endif + } + + if (caja_file_can_start (file) || caja_file_can_start_degraded (file)) { + *show_start = TRUE; + } + + if (caja_file_can_stop (file)) { + *show_stop = TRUE; + } + + /* Dot not show both Unmount and Eject/Safe Removal; too confusing to + * have too many menu entries */ + if (caja_file_can_unmount (file) && !*show_eject && !*show_stop) { + *show_unmount = TRUE; + } + + if (caja_file_can_poll_for_media (file) && !caja_file_is_media_check_automatic (file)) { + *show_poll = TRUE; + } + + *start_stop_type = caja_file_get_start_stop_type (file); + + if (caja_file_is_caja_link (file)) { + uri = caja_file_get_activation_uri (file); + if (uri != NULL && + (eel_istr_has_prefix (uri, "ftp:") || + eel_istr_has_prefix (uri, "ssh:") || + eel_istr_has_prefix (uri, "sftp:") || + eel_istr_has_prefix (uri, "dav:") || + eel_istr_has_prefix (uri, "davs:"))) { + *show_connect = TRUE; + } + g_free (uri); + } +} + +static void +file_should_show_self (CajaFile *file, + gboolean *show_mount, + gboolean *show_unmount, + gboolean *show_eject, + gboolean *show_format, + gboolean *show_start, + gboolean *show_stop, + gboolean *show_poll, + GDriveStartStopType *start_stop_type) +{ + *show_mount = FALSE; + *show_unmount = FALSE; + *show_eject = FALSE; + *show_format = FALSE; + *show_start = FALSE; + *show_stop = FALSE; + *show_poll = FALSE; + + if (file == NULL) { + return; + } + + if (caja_file_can_eject (file)) { + *show_eject = TRUE; + } + + if (caja_file_can_mount (file)) { + *show_mount = TRUE; + } + +#ifdef TODO_GIO + if (something && g_find_program_in_path ("gfloppy")) { + *show_format = TRUE; + } +#endif + + if (caja_file_can_start (file) || caja_file_can_start_degraded (file)) { + *show_start = TRUE; + } + + if (caja_file_can_stop (file)) { + *show_stop = TRUE; + } + + /* Dot not show both Unmount and Eject/Safe Removal; too confusing to + * have too many menu entries */ + if (caja_file_can_unmount (file) && !*show_eject && !*show_stop) { + *show_unmount = TRUE; + } + + if (caja_file_can_poll_for_media (file) && !caja_file_is_media_check_automatic (file)) { + *show_poll = TRUE; + } + + *start_stop_type = caja_file_get_start_stop_type (file); + +} + +static gboolean +files_are_all_directories (GList *files) +{ + CajaFile *file; + GList *l; + gboolean all_directories; + + all_directories = TRUE; + + for (l = files; l != NULL; l = l->next) { + file = CAJA_FILE (l->data); + all_directories &= caja_file_is_directory (file); + } + + return all_directories; +} + +static gboolean +files_is_none_directory (GList *files) +{ + CajaFile *file; + GList *l; + gboolean no_directory; + + no_directory = TRUE; + + for (l = files; l != NULL; l = l->next) { + file = CAJA_FILE (l->data); + no_directory &= !caja_file_is_directory (file); + } + + return no_directory; +} + +static void +update_restore_from_trash_action (GtkAction *action, + GList *files, + gboolean is_self) +{ + CajaFile *original_file; + CajaFile *original_dir; + GHashTable *original_dirs_hash; + GList *original_dirs; + GFile *original_location; + char *tooltip, *original_name; + + original_file = NULL; + original_dir = NULL; + original_dirs = NULL; + original_dirs_hash = NULL; + original_location = NULL; + original_name = NULL; + + if (files != NULL) { + if (g_list_length (files) == 1) { + original_file = caja_file_get_trash_original_file (files->data); + } else { + original_dirs_hash = caja_trashed_files_get_original_directories (files, NULL); + if (original_dirs_hash != NULL) { + original_dirs = g_hash_table_get_keys (original_dirs_hash); + if (g_list_length (original_dirs) == 1) { + original_dir = caja_file_ref (CAJA_FILE (original_dirs->data)); + } + } + } + } + + if (original_file != NULL || original_dirs != NULL) { + gtk_action_set_visible (action, TRUE); + + if (original_file != NULL) { + original_location = caja_file_get_location (original_file); + } else if (original_dir != NULL) { + original_location = caja_file_get_location (original_dir); + } + + if (original_location != NULL) { + original_name = g_file_get_parse_name (original_location); + } + + if (is_self) { + g_assert (g_list_length (files) == 1); + g_assert (original_location != NULL); + tooltip = g_strdup_printf (_("Move the open folder out of the trash to \"%s\""), original_name); + } else if (files_are_all_directories (files)) { + if (original_name != NULL) { + tooltip = g_strdup_printf (ngettext ("Move the selected folder out of the trash to \"%s\"", + "Move the selected folders out of the trash to \"%s\"", + g_list_length (files)), original_name); + } else { + tooltip = g_strdup_printf (ngettext ("Move the selected folder out of the trash", + "Move the selected folders out of the trash", + g_list_length (files))); + } + } else if (files_is_none_directory (files)) { + if (original_name != NULL) { + tooltip = g_strdup_printf (ngettext ("Move the selected file out of the trash to \"%s\"", + "Move the selected files out of the trash to \"%s\"", + g_list_length (files)), original_name); + } else { + tooltip = g_strdup_printf (ngettext ("Move the selected file out of the trash", + "Move the selected files out of the trash", + g_list_length (files))); + } + } else { + if (original_name != NULL) { + tooltip = g_strdup_printf (ngettext ("Move the selected item out of the trash to \"%s\"", + "Move the selected items out of the trash to \"%s\"", + g_list_length (files)), original_name); + } else { + tooltip = g_strdup_printf (ngettext ("Move the selected item out of the trash", + "Move the selected items out of the trash", + g_list_length (files))); + } + } + g_free (original_name); + + g_object_set (action, "tooltip", tooltip, NULL); + + if (original_location != NULL) { + g_object_unref (original_location); + } + } else { + gtk_action_set_visible (action, FALSE); + } + + caja_file_unref (original_file); + caja_file_unref (original_dir); + g_list_free (original_dirs); + + if (original_dirs_hash != NULL) { + g_hash_table_destroy (original_dirs_hash); + } +} + +static void +real_update_menus_volumes (FMDirectoryView *view, + GList *selection, + gint selection_count) +{ + GList *l; + CajaFile *file; + gboolean show_mount; + gboolean show_unmount; + gboolean show_eject; + gboolean show_connect; + gboolean show_format; + gboolean show_start; + gboolean show_stop; + gboolean show_poll; + GDriveStartStopType start_stop_type; + gboolean show_self_mount; + gboolean show_self_unmount; + gboolean show_self_eject; + gboolean show_self_format; + gboolean show_self_start; + gboolean show_self_stop; + gboolean show_self_poll; + GDriveStartStopType self_start_stop_type; + GtkAction *action; + + show_mount = (selection != NULL); + show_unmount = (selection != NULL); + show_eject = (selection != NULL); + show_connect = (selection != NULL && selection_count == 1); + show_format = (selection != NULL && selection_count == 1); + show_start = (selection != NULL && selection_count == 1); + show_stop = (selection != NULL && selection_count == 1); + show_poll = (selection != NULL && selection_count == 1); + start_stop_type = G_DRIVE_START_STOP_TYPE_UNKNOWN; + self_start_stop_type = G_DRIVE_START_STOP_TYPE_UNKNOWN; + + for (l = selection; l != NULL && (show_mount || show_unmount + || show_eject || show_connect + || show_format || show_start + || show_stop || show_poll); + l = l->next) { + gboolean show_mount_one; + gboolean show_unmount_one; + gboolean show_eject_one; + gboolean show_connect_one; + gboolean show_format_one; + gboolean show_start_one; + gboolean show_stop_one; + gboolean show_poll_one; + + file = CAJA_FILE (l->data); + file_should_show_foreach (file, + &show_mount_one, + &show_unmount_one, + &show_eject_one, + &show_connect_one, + &show_format_one, + &show_start_one, + &show_stop_one, + &show_poll_one, + &start_stop_type); + + show_mount &= show_mount_one; + show_unmount &= show_unmount_one; + show_eject &= show_eject_one; + show_connect &= show_connect_one; + show_format &= show_format_one; + show_start &= show_start_one; + show_stop &= show_stop_one; + show_poll &= show_poll_one; + } + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_CONNECT_TO_SERVER_LINK); + gtk_action_set_visible (action, show_connect); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_MOUNT_VOLUME); + gtk_action_set_visible (action, show_mount); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_UNMOUNT_VOLUME); + gtk_action_set_visible (action, show_unmount); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_EJECT_VOLUME); + gtk_action_set_visible (action, show_eject); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_FORMAT_VOLUME); + gtk_action_set_visible (action, show_format); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_START_VOLUME); + gtk_action_set_visible (action, show_start); + if (show_start) { + switch (start_stop_type) { + default: + case G_DRIVE_START_STOP_TYPE_UNKNOWN: + gtk_action_set_label (action, _("_Start")); + gtk_action_set_tooltip (action, _("Start the selected drive")); + break; + case G_DRIVE_START_STOP_TYPE_SHUTDOWN: + gtk_action_set_label (action, _("_Start")); + gtk_action_set_tooltip (action, _("Start the selected drive")); + break; + case G_DRIVE_START_STOP_TYPE_NETWORK: + gtk_action_set_label (action, _("_Connect")); + gtk_action_set_tooltip (action, _("Connect to the selected drive")); + break; + case G_DRIVE_START_STOP_TYPE_MULTIDISK: + gtk_action_set_label (action, _("_Start Multi-disk Drive")); + gtk_action_set_tooltip (action, _("Start the selected multi-disk drive")); + break; + case G_DRIVE_START_STOP_TYPE_PASSWORD: + gtk_action_set_label (action, _("U_nlock Drive")); + gtk_action_set_tooltip (action, _("Unlock the selected drive")); + break; + } + } + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_STOP_VOLUME); + gtk_action_set_visible (action, show_stop); + if (show_stop) { + switch (start_stop_type) { + default: + case G_DRIVE_START_STOP_TYPE_UNKNOWN: + gtk_action_set_label (action, _("_Stop")); + gtk_action_set_tooltip (action, _("Stop the selected drive")); + break; + case G_DRIVE_START_STOP_TYPE_SHUTDOWN: + gtk_action_set_label (action, _("_Safely Remove Drive")); + gtk_action_set_tooltip (action, _("Safely remove the selected drive")); + break; + case G_DRIVE_START_STOP_TYPE_NETWORK: + gtk_action_set_label (action, _("_Disconnect")); + gtk_action_set_tooltip (action, _("Disconnect the selected drive")); + break; + case G_DRIVE_START_STOP_TYPE_MULTIDISK: + gtk_action_set_label (action, _("_Stop Multi-disk Drive")); + gtk_action_set_tooltip (action, _("Stop the selected multi-disk drive")); + break; + case G_DRIVE_START_STOP_TYPE_PASSWORD: + gtk_action_set_label (action, _("_Lock Drive")); + gtk_action_set_tooltip (action, _("Lock the selected drive")); + break; + } + } + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_POLL); + gtk_action_set_visible (action, show_poll); + + show_self_mount = show_self_unmount = show_self_eject = + show_self_format = show_self_start = show_self_stop = show_self_poll = FALSE; + + file = fm_directory_view_get_directory_as_file (view); + file_should_show_self (file, + &show_self_mount, + &show_self_unmount, + &show_self_eject, + &show_self_format, + &show_self_start, + &show_self_stop, + &show_self_poll, + &self_start_stop_type); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_SELF_MOUNT_VOLUME); + gtk_action_set_visible (action, show_self_mount); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_SELF_UNMOUNT_VOLUME); + gtk_action_set_visible (action, show_self_unmount); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_SELF_EJECT_VOLUME); + gtk_action_set_visible (action, show_self_eject); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_SELF_FORMAT_VOLUME); + gtk_action_set_visible (action, show_self_format); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_SELF_START_VOLUME); + gtk_action_set_visible (action, show_self_start); + if (show_self_start) { + switch (self_start_stop_type) { + default: + case G_DRIVE_START_STOP_TYPE_UNKNOWN: + gtk_action_set_label (action, _("_Start")); + gtk_action_set_tooltip (action, _("Start the drive associated with the open folder")); + break; + case G_DRIVE_START_STOP_TYPE_SHUTDOWN: + gtk_action_set_label (action, _("_Start")); + gtk_action_set_tooltip (action, _("Start the drive associated with the open folder")); + break; + case G_DRIVE_START_STOP_TYPE_NETWORK: + gtk_action_set_label (action, _("_Connect")); + gtk_action_set_tooltip (action, _("Connect to the drive associated with the open folder")); + break; + case G_DRIVE_START_STOP_TYPE_MULTIDISK: + gtk_action_set_label (action, _("_Start Multi-disk Drive")); + gtk_action_set_tooltip (action, _("Start the multi-disk drive associated with the open folder")); + break; + case G_DRIVE_START_STOP_TYPE_PASSWORD: + gtk_action_set_label (action, _("_Unlock Drive")); + gtk_action_set_tooltip (action, _("Unlock the drive associated with the open folder")); + break; + } + } + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_SELF_STOP_VOLUME); + gtk_action_set_visible (action, show_self_stop); + if (show_self_stop) { + switch (self_start_stop_type) { + default: + case G_DRIVE_START_STOP_TYPE_UNKNOWN: + gtk_action_set_label (action, _("_Stop")); + gtk_action_set_tooltip (action, _("_Stop the drive associated with the open folder")); + break; + case G_DRIVE_START_STOP_TYPE_SHUTDOWN: + gtk_action_set_label (action, _("_Safely Remove Drive")); + gtk_action_set_tooltip (action, _("Safely remove the drive associated with the open folder")); + break; + case G_DRIVE_START_STOP_TYPE_NETWORK: + gtk_action_set_label (action, _("_Disconnect")); + gtk_action_set_tooltip (action, _("Disconnect the drive associated with the open folder")); + break; + case G_DRIVE_START_STOP_TYPE_MULTIDISK: + gtk_action_set_label (action, _("_Stop Multi-disk Drive")); + gtk_action_set_tooltip (action, _("Stop the multi-disk drive associated with the open folder")); + break; + case G_DRIVE_START_STOP_TYPE_PASSWORD: + gtk_action_set_label (action, _("_Lock Drive")); + gtk_action_set_tooltip (action, _("Lock the drive associated with the open folder")); + break; + } + } + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_SELF_POLL); + gtk_action_set_visible (action, show_self_poll); + +} + +static void +real_update_location_menu_volumes (FMDirectoryView *view) +{ + GtkAction *action; + CajaFile *file; + gboolean show_mount; + gboolean show_unmount; + gboolean show_eject; + gboolean show_connect; + gboolean show_format; + gboolean show_start; + gboolean show_stop; + gboolean show_poll; + GDriveStartStopType start_stop_type; + + g_assert (FM_IS_DIRECTORY_VIEW (view)); + g_assert (CAJA_IS_FILE (view->details->location_popup_directory_as_file)); + + file = CAJA_FILE (view->details->location_popup_directory_as_file); + file_should_show_foreach (file, + &show_mount, + &show_unmount, + &show_eject, + &show_connect, + &show_format, + &show_start, + &show_stop, + &show_poll, + &start_stop_type); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_LOCATION_MOUNT_VOLUME); + gtk_action_set_visible (action, show_mount); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_LOCATION_UNMOUNT_VOLUME); + gtk_action_set_visible (action, show_unmount); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_LOCATION_EJECT_VOLUME); + gtk_action_set_visible (action, show_eject); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_LOCATION_FORMAT_VOLUME); + gtk_action_set_visible (action, show_format); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_LOCATION_START_VOLUME); + gtk_action_set_visible (action, show_start); + if (show_start) { + switch (start_stop_type) { + default: + case G_DRIVE_START_STOP_TYPE_UNKNOWN: + gtk_action_set_label (action, _("_Start")); + gtk_action_set_tooltip (action, _("Start the selected drive")); + break; + case G_DRIVE_START_STOP_TYPE_SHUTDOWN: + gtk_action_set_label (action, _("_Start")); + gtk_action_set_tooltip (action, _("Start the selected drive")); + break; + case G_DRIVE_START_STOP_TYPE_NETWORK: + gtk_action_set_label (action, _("_Connect")); + gtk_action_set_tooltip (action, _("Connect to the selected drive")); + break; + case G_DRIVE_START_STOP_TYPE_MULTIDISK: + gtk_action_set_label (action, _("_Start Multi-disk Drive")); + gtk_action_set_tooltip (action, _("Start the selected multi-disk drive")); + break; + case G_DRIVE_START_STOP_TYPE_PASSWORD: + gtk_action_set_label (action, _("_Unlock Drive")); + gtk_action_set_tooltip (action, _("Unlock the selected drive")); + break; + } + } + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_LOCATION_STOP_VOLUME); + gtk_action_set_visible (action, show_stop); + if (show_stop) { + switch (start_stop_type) { + default: + case G_DRIVE_START_STOP_TYPE_UNKNOWN: + gtk_action_set_label (action, _("_Stop")); + gtk_action_set_tooltip (action, _("Stop the selected volume")); + break; + case G_DRIVE_START_STOP_TYPE_SHUTDOWN: + gtk_action_set_label (action, _("_Safely Remove Drive")); + gtk_action_set_tooltip (action, _("Safely remove the selected drive")); + break; + case G_DRIVE_START_STOP_TYPE_NETWORK: + gtk_action_set_label (action, _("_Disconnect")); + gtk_action_set_tooltip (action, _("Disconnect the selected drive")); + break; + case G_DRIVE_START_STOP_TYPE_MULTIDISK: + gtk_action_set_label (action, _("_Stop Multi-disk Drive")); + gtk_action_set_tooltip (action, _("Stop the selected multi-disk drive")); + break; + case G_DRIVE_START_STOP_TYPE_PASSWORD: + gtk_action_set_label (action, _("_Lock Drive")); + gtk_action_set_tooltip (action, _("Lock the selected drive")); + break; + } + } + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_LOCATION_POLL); + gtk_action_set_visible (action, show_poll); +} + +/* TODO: we should split out this routine into two functions: + * Update on clipboard changes + * Update on selection changes + */ +static void +real_update_paste_menu (FMDirectoryView *view, + GList *selection, + gint selection_count) +{ + gboolean can_paste_files_into; + gboolean selection_is_read_only; + gboolean is_read_only; + GtkAction *action; + + selection_is_read_only = selection_count == 1 && + (!caja_file_can_write (CAJA_FILE (selection->data)) && + !caja_file_has_activation_uri (CAJA_FILE (selection->data))); + + is_read_only = fm_directory_view_is_read_only (view); + + can_paste_files_into = (selection_count == 1 && + can_paste_into_file (CAJA_FILE (selection->data))); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_PASTE); + gtk_action_set_sensitive (action, !is_read_only); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_PASTE_FILES_INTO); + gtk_action_set_visible (action, can_paste_files_into); + gtk_action_set_sensitive (action, !selection_is_read_only); + + /* Ask the clipboard */ + g_object_ref (view); /* Need to keep the object alive until we get the reply */ + gtk_clipboard_request_targets (caja_clipboard_get (GTK_WIDGET (view)), + clipboard_targets_received, + view); +} + +static void +real_update_location_menu (FMDirectoryView *view) +{ + GtkAction *action; + CajaFile *file; + gboolean is_special_link; + gboolean is_desktop_or_home_dir; + gboolean can_delete_file, show_delete; + gboolean show_separate_delete_command; + gboolean show_open_folder_window; + gboolean show_open_in_new_tab; + GList l; + char *label; + char *tip; + + show_open_folder_window = FALSE; + show_open_in_new_tab = FALSE; + + if (caja_window_info_get_window_type (view->details->window) == CAJA_WINDOW_NAVIGATION) { + if (eel_preferences_get_boolean (CAJA_PREFERENCES_ALWAYS_USE_BROWSER)) { + label = _("Open in New _Window"); + } else { + label = _("Browse in New _Window"); + show_open_folder_window = TRUE; + } + + show_open_in_new_tab = TRUE; + } else { + label = g_strdup (ngettext ("_Browse Folder", + "_Browse Folders", 1)); + } + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_LOCATION_OPEN_ALTERNATE); + g_object_set (action, + "label", label, + NULL); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_LOCATION_OPEN_IN_NEW_TAB); + gtk_action_set_visible (action, show_open_in_new_tab); + + if (show_open_in_new_tab) { + if (eel_preferences_get_boolean (CAJA_PREFERENCES_ALWAYS_USE_BROWSER)) { + label = _("Open in New _Tab"); + } else { + label = _("Browse in New _Tab"); + } + g_object_set (action, + "label", label, + NULL); + } + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_LOCATION_OPEN_FOLDER_WINDOW); + gtk_action_set_visible (action, show_open_folder_window); + + file = view->details->location_popup_directory_as_file; + g_assert (CAJA_IS_FILE (file)); + g_assert (caja_file_check_if_ready (file, CAJA_FILE_ATTRIBUTE_INFO | + CAJA_FILE_ATTRIBUTE_MOUNT | + CAJA_FILE_ATTRIBUTE_FILESYSTEM_INFO)); + + is_special_link = CAJA_IS_DESKTOP_ICON_FILE (file); + is_desktop_or_home_dir = caja_file_is_home (file) + || caja_file_is_desktop_directory (file); + + can_delete_file = + caja_file_can_delete (file) && + !is_special_link && + !is_desktop_or_home_dir; + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_LOCATION_CUT); + gtk_action_set_sensitive (action, can_delete_file); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_LOCATION_PASTE_FILES_INTO); + g_object_set_data (G_OBJECT (action), + "can-paste-according-to-destination", + GINT_TO_POINTER (can_paste_into_file (file))); + gtk_action_set_sensitive (action, + GPOINTER_TO_INT (g_object_get_data (G_OBJECT (action), + "can-paste-according-to-clipboard")) && + GPOINTER_TO_INT (g_object_get_data (G_OBJECT (action), + "can-paste-according-to-destination"))); + + show_delete = TRUE; + + if (file != NULL && + caja_file_is_in_trash (file)) { + if (caja_file_is_self_owned (file)) { + show_delete = FALSE; + } + + label = _("_Delete Permanently"); + tip = _("Delete the open folder permanently"); + show_separate_delete_command = FALSE; + } else { + label = _("Mo_ve to Trash"); + tip = _("Move the open folder to the Trash"); + show_separate_delete_command = show_delete_command_auto_value; + } + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_LOCATION_TRASH); + g_object_set (action, + "label", label, + "tooltip", tip, + "icon-name", (file != NULL && + caja_file_is_in_trash (file)) ? + CAJA_ICON_DELETE : CAJA_ICON_TRASH_FULL, + NULL); + gtk_action_set_sensitive (action, can_delete_file); + gtk_action_set_visible (action, show_delete); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_LOCATION_DELETE); + gtk_action_set_visible (action, show_separate_delete_command); + if (show_separate_delete_command) { + gtk_action_set_sensitive (action, can_delete_file); + g_object_set (action, + "icon-name", CAJA_ICON_DELETE, + "sensitive", can_delete_file, + NULL); + } + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_LOCATION_RESTORE_FROM_TRASH); + l.prev = NULL; + l.next = NULL; + l.data = file; + update_restore_from_trash_action (action, &l, TRUE); + + real_update_location_menu_volumes (view); + + /* we silently assume that fm_directory_view_supports_properties always returns the same value. + * Therefore, we don't update the sensitivity of FM_ACTION_LOCATION_PROPERTIES */ +} + +static void +clipboard_changed_callback (CajaClipboardMonitor *monitor, FMDirectoryView *view) +{ + GList *selection; + gint selection_count; + + if (!view->details->active) { + return; + } + + selection = fm_directory_view_get_selection (view); + selection_count = g_list_length (selection); + + real_update_paste_menu (view, selection, selection_count); + + caja_file_list_free (selection); + +} + +static gboolean +can_delete_all (GList *files) +{ + CajaFile *file; + GList *l; + + for (l = files; l != NULL; l = l->next) { + file = l->data; + if (!caja_file_can_delete (file)) { + return FALSE; + } + } + return TRUE; +} + +static gboolean +has_writable_extra_pane (FMDirectoryView *view) +{ + FMDirectoryView *other_view; + + other_view = get_directory_view_of_extra_pane (view); + if (other_view != NULL) { + return !fm_directory_view_is_read_only (other_view); + } + return FALSE; +} + +static void +real_update_menus (FMDirectoryView *view) +{ + GList *selection, *l; + gint selection_count; + const char *tip, *label; + char *label_with_underscore; + gboolean selection_contains_special_link; + gboolean selection_contains_desktop_or_home_dir; + gboolean can_create_files; + gboolean can_delete_files; + gboolean can_copy_files; + gboolean can_link_files; + gboolean can_duplicate_files; + gboolean show_separate_delete_command; + gboolean vfolder_directory; + gboolean disable_command_line; + gboolean show_open_alternate; + gboolean can_open; + gboolean show_app; + gboolean show_save_search; + gboolean save_search_sensitive; + gboolean show_save_search_as; + gboolean show_open_folder_window; + GtkAction *action; + GAppInfo *app; + GIcon *app_icon; + GtkWidget *menuitem; + gboolean next_pane_is_writable; + gboolean show_properties; + + selection = fm_directory_view_get_selection (view); + selection_count = g_list_length (selection); + + selection_contains_special_link = special_link_in_selection (view); + selection_contains_desktop_or_home_dir = desktop_or_home_dir_in_selection (view); + + can_create_files = fm_directory_view_supports_creating_files (view); + can_delete_files = + can_delete_all (selection) && + selection_count != 0 && + !selection_contains_special_link && + !selection_contains_desktop_or_home_dir; + can_copy_files = selection_count != 0 + && !selection_contains_special_link; + + can_duplicate_files = can_create_files && can_copy_files; + can_link_files = can_create_files && can_copy_files; + + vfolder_directory = we_are_in_vfolder_desktop_dir (view); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_RENAME); + gtk_action_set_sensitive (action, + selection_count == 1 && + fm_directory_view_can_rename_file (view, selection->data)); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_NEW_FOLDER); + gtk_action_set_sensitive (action, can_create_files); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_OPEN); + gtk_action_set_sensitive (action, selection_count != 0); + + can_open = show_app = selection_count != 0; + + for (l = selection; l != NULL; l = l->next) { + CajaFile *file; + + file = CAJA_FILE (selection->data); + + if (!caja_mime_file_opens_in_external_app (file)) { + show_app = FALSE; + } + + if (!show_app) { + break; + } + } + + label_with_underscore = NULL; + + app = NULL; + app_icon = NULL; + + if (can_open && show_app) { + app = caja_mime_get_default_application_for_files (selection); + } + + if (app != NULL) { + char *escaped_app; + + escaped_app = eel_str_double_underscores (g_app_info_get_display_name (app)); + label_with_underscore = g_strdup_printf (_("_Open With %s"), + escaped_app); + + app_icon = g_app_info_get_icon (app); + if (app_icon != NULL) { + g_object_ref (app_icon); + } + + g_free (escaped_app); + g_object_unref (app); + } + + g_object_set (action, "label", + label_with_underscore ? label_with_underscore : _("_Open"), + NULL); + + menuitem = gtk_ui_manager_get_widget ( + caja_window_info_get_ui_manager (view->details->window), + FM_DIRECTORY_VIEW_MENU_PATH_OPEN); + + /* Only force displaying the icon if it is an application icon */ + gtk_image_menu_item_set_always_show_image ( + GTK_IMAGE_MENU_ITEM (menuitem), app_icon != NULL); + + menuitem = gtk_ui_manager_get_widget ( + caja_window_info_get_ui_manager (view->details->window), + FM_DIRECTORY_VIEW_POPUP_PATH_OPEN); + + /* Only force displaying the icon if it is an application icon */ + gtk_image_menu_item_set_always_show_image ( + GTK_IMAGE_MENU_ITEM (menuitem), app_icon != NULL); + + if (app_icon == NULL) { + app_icon = g_themed_icon_new (GTK_STOCK_OPEN); + } + + gtk_action_set_gicon (action, app_icon); + g_object_unref (app_icon); + + gtk_action_set_visible (action, can_open); + + g_free (label_with_underscore); + + show_open_alternate = file_list_all_are_folders (selection) && + selection_count > 0 && + !(caja_window_info_get_window_type (view->details->window) == CAJA_WINDOW_DESKTOP && + eel_preferences_get_boolean (CAJA_PREFERENCES_ALWAYS_USE_BROWSER)); + show_open_folder_window = FALSE; + if (caja_window_info_get_window_type (view->details->window) == CAJA_WINDOW_NAVIGATION) { + if (eel_preferences_get_boolean (CAJA_PREFERENCES_ALWAYS_USE_BROWSER)) { + if (selection_count == 0 || selection_count == 1) { + label_with_underscore = g_strdup (_("Open in New _Window")); + } else { + label_with_underscore = g_strdup_printf (ngettext("Open in %'d New _Window", + "Open in %'d New _Windows", + selection_count), + selection_count); + } + } else { + if (selection_count == 0 || selection_count == 1) { + label_with_underscore = g_strdup (_("Browse in New _Window")); + } else { + label_with_underscore = g_strdup_printf (ngettext("Browse in %'d New _Window", + "Browse in %'d New _Windows", + selection_count), + selection_count); + } + show_open_folder_window = show_open_alternate; + } + } else { + label_with_underscore = g_strdup (ngettext ("_Browse Folder", + "_Browse Folders", + selection_count)); + } + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_OPEN_ALTERNATE); + g_object_set (action, "label", + label_with_underscore, + NULL); + g_free (label_with_underscore); + + gtk_action_set_sensitive (action, selection_count != 0); + gtk_action_set_visible (action, show_open_alternate); + + /* Open in New Tab action */ + if (caja_window_info_get_window_type (view->details->window) == CAJA_WINDOW_NAVIGATION) { + + if (eel_preferences_get_boolean (CAJA_PREFERENCES_ALWAYS_USE_BROWSER)) { + if (selection_count == 0 || selection_count == 1) { + label_with_underscore = g_strdup (_("Open in New _Tab")); + } else { + label_with_underscore = g_strdup_printf (ngettext("Open in %'d New _Tab", + "Open in %'d New _Tabs", + selection_count), + selection_count); + } + } else { + if (selection_count == 0 || selection_count == 1) { + label_with_underscore = g_strdup (_("Browse in New _Tab")); + } else { + label_with_underscore = g_strdup_printf (ngettext("Browse in %'d New _Tab", + "Browse in %'d New _Tabs", + selection_count), + selection_count); + } + } + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_OPEN_IN_NEW_TAB); + gtk_action_set_sensitive (action, selection_count != 0); + gtk_action_set_visible (action, show_open_alternate); + g_object_set (action, "label", + label_with_underscore, + NULL); + g_free (label_with_underscore); + } else { + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_OPEN_IN_NEW_TAB); + gtk_action_set_visible (action, FALSE); + } + + /* next pane actions, only in navigation mode */ + if (caja_window_info_get_window_type (view->details->window) != CAJA_WINDOW_NAVIGATION) { + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_COPY_TO_NEXT_PANE); + gtk_action_set_visible (action, FALSE); + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_MOVE_TO_NEXT_PANE); + gtk_action_set_visible (action, FALSE); + } + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_OPEN_FOLDER_WINDOW); + gtk_action_set_visible (action, show_open_folder_window); + + /* Broken into its own function just for convenience */ + reset_open_with_menu (view, selection); + reset_extension_actions_menu (view, selection); + + if (all_selected_items_in_trash (view)) { + label = _("_Delete Permanently"); + tip = _("Delete all selected items permanently"); + show_separate_delete_command = FALSE; + } else { + label = _("Mo_ve to Trash"); + tip = _("Move each selected item to the Trash"); + show_separate_delete_command = show_delete_command_auto_value; + } + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_TRASH); + g_object_set (action, + "label", label, + "tooltip", tip, + "icon-name", all_selected_items_in_trash (view) ? + CAJA_ICON_DELETE : CAJA_ICON_TRASH_FULL, + NULL); + gtk_action_set_sensitive (action, can_delete_files); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_DELETE); + gtk_action_set_visible (action, show_separate_delete_command); + + if (show_separate_delete_command) { + g_object_set (action, + "label", _("_Delete"), + "icon-name", CAJA_ICON_DELETE, + NULL); + } + gtk_action_set_sensitive (action, can_delete_files); + + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_RESTORE_FROM_TRASH); + update_restore_from_trash_action (action, selection, FALSE); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_DUPLICATE); + gtk_action_set_sensitive (action, can_duplicate_files); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_CREATE_LINK); + gtk_action_set_sensitive (action, can_link_files); + g_object_set (action, "label", + ngettext ("Ma_ke Link", + "Ma_ke Links", + selection_count), + NULL); + + show_properties = (!FM_IS_DESKTOP_ICON_VIEW (view) || selection_count > 0) && + fm_directory_view_supports_properties (view); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_PROPERTIES); + + gtk_action_set_sensitive (action, show_properties); + + if (selection_count == 0) { + gtk_action_set_tooltip (action, _("View or modify the properties of the open folder")); + } else { + gtk_action_set_tooltip (action, _("View or modify the properties of each selected item")); + } + + gtk_action_set_visible (action, show_properties); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_PROPERTIES_ACCEL); + + gtk_action_set_sensitive (action, show_properties); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_EMPTY_TRASH); + g_object_set (action, + "label", _("E_mpty Trash"), + NULL); + gtk_action_set_sensitive (action, !caja_trash_monitor_is_empty ()); + gtk_action_set_visible (action, should_show_empty_trash (view)); + + show_save_search = FALSE; + save_search_sensitive = FALSE; + show_save_search_as = FALSE; + if (view->details->model && + CAJA_IS_SEARCH_DIRECTORY (view->details->model)) { + CajaSearchDirectory *search; + + search = CAJA_SEARCH_DIRECTORY (view->details->model); + if (caja_search_directory_is_saved_search (search)) { + show_save_search = TRUE; + save_search_sensitive = caja_search_directory_is_modified (search); + } else { + show_save_search_as = TRUE; + } + } + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_SAVE_SEARCH); + gtk_action_set_visible (action, show_save_search); + gtk_action_set_sensitive (action, save_search_sensitive); + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_SAVE_SEARCH_AS); + gtk_action_set_visible (action, show_save_search_as); + + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_SELECT_ALL); + gtk_action_set_sensitive (action, !fm_directory_view_is_empty (view)); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_SELECT_PATTERN); + gtk_action_set_sensitive (action, !fm_directory_view_is_empty (view)); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_INVERT_SELECTION); + gtk_action_set_sensitive (action, !fm_directory_view_is_empty (view)); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_CUT); + gtk_action_set_sensitive (action, can_delete_files); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_COPY); + gtk_action_set_sensitive (action, can_copy_files); + + real_update_paste_menu (view, selection, selection_count); + + disable_command_line = eel_preferences_get_boolean (CAJA_PREFERENCES_LOCKDOWN_COMMAND_LINE); + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_NEW_LAUNCHER); + gtk_action_set_visible (action, vfolder_directory && !disable_command_line); + gtk_action_set_sensitive (action, can_create_files); + + real_update_menus_volumes (view, selection, selection_count); + + caja_file_list_free (selection); + + if (view->details->scripts_invalid) { + update_scripts_menu (view); + } + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_NEW_DOCUMENTS); + gtk_action_set_sensitive (action, can_create_files); + + if (can_create_files && view->details->templates_invalid) { + update_templates_menu (view); + } + + next_pane_is_writable = has_writable_extra_pane (view); + + /* next pane: works if file is copyable, and next pane is writable */ + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_COPY_TO_NEXT_PANE); + gtk_action_set_sensitive (action, can_copy_files && next_pane_is_writable); + + /* move to next pane: works if file is cuttable, and next pane is writable */ + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_MOVE_TO_NEXT_PANE); + gtk_action_set_sensitive (action, can_delete_files && next_pane_is_writable); + + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_COPY_TO_HOME); + gtk_action_set_sensitive (action, can_copy_files); + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_COPY_TO_DESKTOP); + gtk_action_set_sensitive (action, can_copy_files); + + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_MOVE_TO_HOME); + gtk_action_set_sensitive (action, can_delete_files); + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_MOVE_TO_DESKTOP); + gtk_action_set_sensitive (action, can_delete_files); + + action = gtk_action_group_get_action (view->details->dir_action_group, + "CopyToMenu"); + gtk_action_set_sensitive (action, can_copy_files); + action = gtk_action_group_get_action (view->details->dir_action_group, + "MoveToMenu"); + gtk_action_set_sensitive (action, can_delete_files); +} + +/** + * fm_directory_view_pop_up_selection_context_menu + * + * Pop up a context menu appropriate to the selected items. + * @view: FMDirectoryView of interest. + * @event: The event that triggered this context menu. + * + * Return value: CajaDirectory for this view. + * + **/ +void +fm_directory_view_pop_up_selection_context_menu (FMDirectoryView *view, + GdkEventButton *event) +{ + g_assert (FM_IS_DIRECTORY_VIEW (view)); + + /* Make the context menu items not flash as they update to proper disabled, + * etc. states by forcing menus to update now. + */ + update_menus_if_pending (view); + + update_context_menu_position_from_event (view, event); + + eel_pop_up_context_menu (create_popup_menu + (view, FM_DIRECTORY_VIEW_POPUP_PATH_SELECTION), + EEL_DEFAULT_POPUP_MENU_DISPLACEMENT, + EEL_DEFAULT_POPUP_MENU_DISPLACEMENT, + event); +} + +/** + * fm_directory_view_pop_up_background_context_menu + * + * Pop up a context menu appropriate to the view globally at the last right click location. + * @view: FMDirectoryView of interest. + * + * Return value: CajaDirectory for this view. + * + **/ +void +fm_directory_view_pop_up_background_context_menu (FMDirectoryView *view, + GdkEventButton *event) +{ + g_assert (FM_IS_DIRECTORY_VIEW (view)); + + /* Make the context menu items not flash as they update to proper disabled, + * etc. states by forcing menus to update now. + */ + update_menus_if_pending (view); + + update_context_menu_position_from_event (view, event); + + + eel_pop_up_context_menu (create_popup_menu + (view, FM_DIRECTORY_VIEW_POPUP_PATH_BACKGROUND), + EEL_DEFAULT_POPUP_MENU_DISPLACEMENT, + EEL_DEFAULT_POPUP_MENU_DISPLACEMENT, + event); +} + +static void +real_pop_up_location_context_menu (FMDirectoryView *view) +{ + /* always update the menu before showing it. Shouldn't be too expensive. */ + real_update_location_menu (view); + + update_context_menu_position_from_event (view, view->details->location_popup_event); + + eel_pop_up_context_menu (create_popup_menu + (view, FM_DIRECTORY_VIEW_POPUP_PATH_LOCATION), + EEL_DEFAULT_POPUP_MENU_DISPLACEMENT, + EEL_DEFAULT_POPUP_MENU_DISPLACEMENT, + view->details->location_popup_event); +} + +static void +location_popup_file_attributes_ready (CajaFile *file, + gpointer data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (data); + g_assert (FM_IS_DIRECTORY_VIEW (view)); + + g_assert (file == view->details->location_popup_directory_as_file); + + real_pop_up_location_context_menu (view); +} + +static void +unschedule_pop_up_location_context_menu (FMDirectoryView *view) +{ + if (view->details->location_popup_directory_as_file != NULL) { + g_assert (CAJA_IS_FILE (view->details->location_popup_directory_as_file)); + caja_file_cancel_call_when_ready (view->details->location_popup_directory_as_file, + location_popup_file_attributes_ready, + view); + caja_file_unref (view->details->location_popup_directory_as_file); + view->details->location_popup_directory_as_file = NULL; + } +} + +static void +schedule_pop_up_location_context_menu (FMDirectoryView *view, + GdkEventButton *event, + CajaFile *file) +{ + g_assert (CAJA_IS_FILE (file)); + + if (view->details->location_popup_event != NULL) { + gdk_event_free ((GdkEvent *) view->details->location_popup_event); + } + view->details->location_popup_event = (GdkEventButton *) gdk_event_copy ((GdkEvent *)event); + + if (file == view->details->location_popup_directory_as_file) { + if (caja_file_check_if_ready (file, CAJA_FILE_ATTRIBUTE_INFO | + CAJA_FILE_ATTRIBUTE_MOUNT | + CAJA_FILE_ATTRIBUTE_FILESYSTEM_INFO)) { + real_pop_up_location_context_menu (view); + } + } else { + unschedule_pop_up_location_context_menu (view); + + view->details->location_popup_directory_as_file = caja_file_ref (file); + caja_file_call_when_ready (view->details->location_popup_directory_as_file, + CAJA_FILE_ATTRIBUTE_INFO | + CAJA_FILE_ATTRIBUTE_MOUNT | + CAJA_FILE_ATTRIBUTE_FILESYSTEM_INFO, + location_popup_file_attributes_ready, + view); + } +} + +/** + * fm_directory_view_pop_up_location_context_menu + * + * Pop up a context menu appropriate to the view globally. + * @view: FMDirectoryView of interest. + * @event: GdkEventButton triggering the popup. + * @location: The location the popup-menu should be created for, + * or NULL for the currently displayed location. + * + **/ +void +fm_directory_view_pop_up_location_context_menu (FMDirectoryView *view, + GdkEventButton *event, + const char *location) +{ + CajaFile *file; + + g_assert (FM_IS_DIRECTORY_VIEW (view)); + + if (location != NULL) { + file = caja_file_get_by_uri (location); + } else { + file = caja_file_ref (view->details->directory_as_file); + } + + if (file != NULL) { + schedule_pop_up_location_context_menu (view, event, file); + caja_file_unref (file); + } +} + +static void +fm_directory_view_drop_proxy_received_uris (FMDirectoryView *view, + const GList *source_uri_list, + const char *target_uri, + GdkDragAction action) +{ + char *container_uri; + + container_uri = NULL; + if (target_uri == NULL) { + container_uri = fm_directory_view_get_backing_uri (view); + g_assert (container_uri != NULL); + } + + if (action == GDK_ACTION_ASK) { + action = caja_drag_drop_action_ask + (GTK_WIDGET (view), + GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK); + if (action == 0) { + return; + } + } + + caja_clipboard_clear_if_colliding_uris (GTK_WIDGET (view), + source_uri_list, + fm_directory_view_get_copied_files_atom (view)); + + fm_directory_view_move_copy_items (source_uri_list, NULL, + target_uri != NULL ? target_uri : container_uri, + action, 0, 0, view); + + g_free (container_uri); +} + +static void +fm_directory_view_drop_proxy_received_netscape_url (FMDirectoryView *view, + const char *netscape_url, + const char *target_uri, + GdkDragAction action) +{ + fm_directory_view_handle_netscape_url_drop (view, + netscape_url, + target_uri, + action, 0, 0); +} + +static void +schedule_update_menus (FMDirectoryView *view) +{ + g_assert (FM_IS_DIRECTORY_VIEW (view)); + + /* Don't schedule updates after destroy (#349551), + * or if we are not active. + */ + if (view->details->window == NULL || + !view->details->active) { + return; + } + + view->details->menu_states_untrustworthy = TRUE; + + /* Schedule a menu update with the current update interval */ + if (view->details->update_menus_timeout_id == 0) { + view->details->update_menus_timeout_id + = g_timeout_add (view->details->update_interval, update_menus_timeout_callback, view); + } +} + +static void +remove_update_status_idle_callback (FMDirectoryView *view) +{ + if (view->details->update_status_idle_id != 0) { + g_source_remove (view->details->update_status_idle_id); + view->details->update_status_idle_id = 0; + } +} + +static gboolean +update_status_idle_callback (gpointer data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (data); + fm_directory_view_display_selection_info (view); + view->details->update_status_idle_id = 0; + return FALSE; +} + +static void +schedule_update_status (FMDirectoryView *view) +{ + g_assert (FM_IS_DIRECTORY_VIEW (view)); + + /* Make sure we haven't already destroyed it */ + if (view->details->window == NULL) { + return; + } + + if (view->details->loading) { + /* Don't update status bar while loading the dir */ + return; + } + + if (view->details->update_status_idle_id == 0) { + view->details->update_status_idle_id = + g_idle_add_full (G_PRIORITY_DEFAULT_IDLE - 20, + update_status_idle_callback, view, NULL); + } +} + +/** + * fm_directory_view_notify_selection_changed: + * + * Notify this view that the selection has changed. This is normally + * called only by subclasses. + * @view: FMDirectoryView whose selection has changed. + * + **/ +void +fm_directory_view_notify_selection_changed (FMDirectoryView *view) +{ + GList *selection; + GtkWindow *window; + + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + if (caja_debug_log_is_domain_enabled (CAJA_DEBUG_LOG_DOMAIN_USER)) { + selection = fm_directory_view_get_selection (view); + + window = fm_directory_view_get_containing_window (view); + caja_debug_log_with_file_list (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, selection, + "selection changed in window %p", + window); + caja_file_list_free (selection); + } + + view->details->selection_was_removed = FALSE; + + if (!view->details->selection_change_is_due_to_shell) { + view->details->send_selection_change_to_shell = TRUE; + } + + /* Schedule a display of the new selection. */ + if (view->details->display_selection_idle_id == 0) { + view->details->display_selection_idle_id + = g_idle_add (display_selection_info_idle_callback, + view); + } + + if (view->details->batching_selection_level != 0) { + view->details->selection_changed_while_batched = TRUE; + } else { + /* Here is the work we do only when we're not + * batching selection changes. In other words, it's the slower + * stuff that we don't want to slow down selection techniques + * such as rubberband-selecting in icon view. + */ + + /* Schedule an update of menu item states to match selection */ + schedule_update_menus (view); + } +} + +static void +file_changed_callback (CajaFile *file, gpointer callback_data) +{ + FMDirectoryView *view = FM_DIRECTORY_VIEW (callback_data); + + schedule_changes (view); + + schedule_update_menus (view); + schedule_update_status (view); + + /* We might have different capabilities, so we need to update + * relative icon emblems . (Writeable etc). + * Don't do this for trash, as it never changes writability + * but does change a lot for the file count attribute. + */ + if (!caja_file_is_in_trash (file)) { + EEL_CALL_METHOD + (FM_DIRECTORY_VIEW_CLASS, view, emblems_changed, (view)); + } +} + +/** + * load_directory: + * + * Switch the displayed location to a new uri. If the uri is not valid, + * the location will not be switched; user feedback will be provided instead. + * @view: FMDirectoryView whose location will be changed. + * @uri: A string representing the uri to switch to. + * + **/ +static void +load_directory (FMDirectoryView *view, + CajaDirectory *directory) +{ + CajaDirectory *old_directory; + CajaFile *old_file; + CajaFileAttributes attributes; + + g_assert (FM_IS_DIRECTORY_VIEW (view)); + g_assert (CAJA_IS_DIRECTORY (directory)); + + fm_directory_view_stop (view); + fm_directory_view_clear (view); + + view->details->loading = TRUE; + + /* Update menus when directory is empty, before going to new + * location, so they won't have any false lingering knowledge + * of old selection. + */ + schedule_update_menus (view); + + while (view->details->subdirectory_list != NULL) { + fm_directory_view_remove_subdirectory (view, + view->details->subdirectory_list->data); + } + + disconnect_model_handlers (view); + + old_directory = view->details->model; + caja_directory_ref (directory); + view->details->model = directory; + caja_directory_unref (old_directory); + + old_file = view->details->directory_as_file; + view->details->directory_as_file = + caja_directory_get_corresponding_file (directory); + caja_file_unref (old_file); + + view->details->reported_load_error = FALSE; + + /* FIXME bugzilla.gnome.org 45062: In theory, we also need to monitor metadata here (as + * well as doing a call when ready), in case external forces + * change the directory's file metadata. + */ + attributes = + CAJA_FILE_ATTRIBUTE_INFO | + CAJA_FILE_ATTRIBUTE_MOUNT | + CAJA_FILE_ATTRIBUTE_FILESYSTEM_INFO; + view->details->metadata_for_directory_as_file_pending = TRUE; + view->details->metadata_for_files_in_directory_pending = TRUE; + caja_file_call_when_ready + (view->details->directory_as_file, + attributes, + metadata_for_directory_as_file_ready_callback, view); + caja_directory_call_when_ready + (view->details->model, + attributes, + FALSE, + metadata_for_files_in_directory_ready_callback, view); + + /* If capabilities change, then we need to update the menus + * because of New Folder, and relative emblems. + */ + attributes = + CAJA_FILE_ATTRIBUTE_INFO | + CAJA_FILE_ATTRIBUTE_FILESYSTEM_INFO; + caja_file_monitor_add (view->details->directory_as_file, + &view->details->directory_as_file, + attributes); + + view->details->file_changed_handler_id = g_signal_connect + (view->details->directory_as_file, "changed", + G_CALLBACK (file_changed_callback), view); +} + +static void +finish_loading (FMDirectoryView *view) +{ + CajaFileAttributes attributes; + + caja_window_info_report_load_underway (view->details->window, + CAJA_VIEW (view)); + + /* Tell interested parties that we've begun loading this directory now. + * Subclasses use this to know that the new metadata is now available. + */ + fm_directory_view_begin_loading (view); + + /* Assume we have now all information to show window */ + caja_window_info_view_visible (view->details->window, CAJA_VIEW (view)); + + if (caja_directory_are_all_files_seen (view->details->model)) { + /* Unschedule a pending update and schedule a new one with the minimal + * update interval. This gives the view a short chance at gathering the + * (cached) deep counts. + */ + unschedule_display_of_pending_files (view); + schedule_timeout_display_of_pending_files (view, UPDATE_INTERVAL_MIN); + } + + /* Start loading. */ + + /* Connect handlers to learn about loading progress. */ + view->details->done_loading_handler_id = g_signal_connect + (view->details->model, "done_loading", + G_CALLBACK (done_loading_callback), view); + view->details->load_error_handler_id = g_signal_connect + (view->details->model, "load_error", + G_CALLBACK (load_error_callback), view); + + /* Monitor the things needed to get the right icon. Also + * monitor a directory's item count because the "size" + * attribute is based on that, and the file's metadata + * and possible custom name. + */ + attributes = + CAJA_FILE_ATTRIBUTES_FOR_ICON | + CAJA_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT | + CAJA_FILE_ATTRIBUTE_INFO | + CAJA_FILE_ATTRIBUTE_LINK_INFO | + CAJA_FILE_ATTRIBUTE_MOUNT | + CAJA_FILE_ATTRIBUTE_EXTENSION_INFO; + + caja_directory_file_monitor_add (view->details->model, + &view->details->model, + view->details->show_hidden_files, + view->details->show_backup_files, + attributes, + files_added_callback, view); + + view->details->files_added_handler_id = g_signal_connect + (view->details->model, "files_added", + G_CALLBACK (files_added_callback), view); + view->details->files_changed_handler_id = g_signal_connect + (view->details->model, "files_changed", + G_CALLBACK (files_changed_callback), view); +} + +static void +finish_loading_if_all_metadata_loaded (FMDirectoryView *view) +{ + if (!view->details->metadata_for_directory_as_file_pending && + !view->details->metadata_for_files_in_directory_pending) { + finish_loading (view); + } +} + +static void +metadata_for_directory_as_file_ready_callback (CajaFile *file, + gpointer callback_data) +{ + FMDirectoryView *view; + + view = callback_data; + + g_assert (FM_IS_DIRECTORY_VIEW (view)); + g_assert (view->details->directory_as_file == file); + g_assert (view->details->metadata_for_directory_as_file_pending); + + view->details->metadata_for_directory_as_file_pending = FALSE; + + finish_loading_if_all_metadata_loaded (view); +} + +static void +metadata_for_files_in_directory_ready_callback (CajaDirectory *directory, + GList *files, + gpointer callback_data) +{ + FMDirectoryView *view; + + view = callback_data; + + g_assert (FM_IS_DIRECTORY_VIEW (view)); + g_assert (view->details->model == directory); + g_assert (view->details->metadata_for_files_in_directory_pending); + + view->details->metadata_for_files_in_directory_pending = FALSE; + + finish_loading_if_all_metadata_loaded (view); +} + +char ** +fm_directory_view_get_emblem_names_to_exclude (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), NULL); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_DIRECTORY_VIEW_CLASS, view, + get_emblem_names_to_exclude, (view)); +} + +static char ** +real_get_emblem_names_to_exclude (FMDirectoryView *view) +{ + char **excludes; + int i; + + g_assert (FM_IS_DIRECTORY_VIEW (view)); + + excludes = g_new (char *, 3); + + i = 0; + excludes[i++] = g_strdup (CAJA_FILE_EMBLEM_NAME_TRASH); + + if (!caja_file_can_write (view->details->directory_as_file)) { + excludes[i++] = g_strdup (CAJA_FILE_EMBLEM_NAME_CANT_WRITE); + } + + excludes[i++] = NULL; + + return excludes; +} + +/** + * fm_directory_view_merge_menus: + * + * Add this view's menus to the window's menu bar. + * @view: FMDirectoryView in question. + */ +static void +fm_directory_view_merge_menus (FMDirectoryView *view) +{ + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + EEL_CALL_METHOD + (FM_DIRECTORY_VIEW_CLASS, view, + merge_menus, (view)); +} + +static void +fm_directory_view_unmerge_menus (FMDirectoryView *view) +{ + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + EEL_CALL_METHOD + (FM_DIRECTORY_VIEW_CLASS, view, + unmerge_menus, (view)); +} + +static void +disconnect_handler (GObject *object, int *id) +{ + if (*id != 0) { + g_signal_handler_disconnect (object, *id); + *id = 0; + } +} + +static void +disconnect_directory_handler (FMDirectoryView *view, int *id) +{ + disconnect_handler (G_OBJECT (view->details->model), id); +} + +static void +disconnect_directory_as_file_handler (FMDirectoryView *view, int *id) +{ + disconnect_handler (G_OBJECT (view->details->directory_as_file), id); +} + +static void +disconnect_model_handlers (FMDirectoryView *view) +{ + if (view->details->model == NULL) { + return; + } + disconnect_directory_handler (view, &view->details->files_added_handler_id); + disconnect_directory_handler (view, &view->details->files_changed_handler_id); + disconnect_directory_handler (view, &view->details->done_loading_handler_id); + disconnect_directory_handler (view, &view->details->load_error_handler_id); + disconnect_directory_as_file_handler (view, &view->details->file_changed_handler_id); + caja_file_cancel_call_when_ready (view->details->directory_as_file, + metadata_for_directory_as_file_ready_callback, + view); + caja_directory_cancel_callback (view->details->model, + metadata_for_files_in_directory_ready_callback, + view); + caja_directory_file_monitor_remove (view->details->model, + &view->details->model); + caja_file_monitor_remove (view->details->directory_as_file, + &view->details->directory_as_file); +} + +/** + * fm_directory_view_reset_to_defaults: + * + * set sorting order, zoom level, etc. to match defaults + * + **/ +void +fm_directory_view_reset_to_defaults (FMDirectoryView *view) +{ + CajaWindowShowHiddenFilesMode mode; + + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + EEL_CALL_METHOD + (FM_DIRECTORY_VIEW_CLASS, view, + reset_to_defaults, (view)); + mode = caja_window_info_get_hidden_files_mode (view->details->window); + if (mode != CAJA_WINDOW_SHOW_HIDDEN_FILES_DEFAULT) { + caja_window_info_set_hidden_files_mode (view->details->window, + CAJA_WINDOW_SHOW_HIDDEN_FILES_DEFAULT); + } +} + +/** + * fm_directory_view_select_all: + * + * select all the items in the view + * + **/ +void +fm_directory_view_select_all (FMDirectoryView *view) +{ + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + EEL_CALL_METHOD + (FM_DIRECTORY_VIEW_CLASS, view, + select_all, (view)); +} + +/** + * fm_directory_view_set_selection: + * + * set the selection to the items identified in @selection. @selection + * should be a list of CajaFiles + * + **/ +void +fm_directory_view_set_selection (FMDirectoryView *view, GList *selection) +{ + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + EEL_CALL_METHOD + (FM_DIRECTORY_VIEW_CLASS, view, + set_selection, (view, selection)); +} + +static void +fm_directory_view_select_file (FMDirectoryView *view, CajaFile *file) +{ + GList file_list; + + file_list.data = file; + file_list.next = NULL; + file_list.prev = NULL; + fm_directory_view_set_selection (view, &file_list); +} + +/** + * fm_directory_view_get_selected_icon_locations: + * + * return an array of locations of selected icons if available + * Return value: GArray of GdkPoints + * + **/ +GArray * +fm_directory_view_get_selected_icon_locations (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), NULL); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_DIRECTORY_VIEW_CLASS, view, + get_selected_icon_locations, (view)); +} + +/** + * fm_directory_view_reveal_selection: + * + * Scroll as necessary to reveal the selected items. + **/ +void +fm_directory_view_reveal_selection (FMDirectoryView *view) +{ + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + EEL_CALL_METHOD + (FM_DIRECTORY_VIEW_CLASS, view, + reveal_selection, (view)); +} + +static gboolean +remove_all (gpointer key, gpointer value, gpointer callback_data) +{ + return TRUE; +} + +/** + * fm_directory_view_stop: + * + * Stop the current ongoing process, such as switching to a new uri. + * @view: FMDirectoryView in question. + * + **/ +void +fm_directory_view_stop (FMDirectoryView *view) +{ + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + unschedule_display_of_pending_files (view); + reset_update_interval (view); + + /* Free extra undisplayed files */ + file_and_directory_list_free (view->details->new_added_files); + view->details->new_added_files = NULL; + file_and_directory_list_free (view->details->new_changed_files); + view->details->new_changed_files = NULL; + g_hash_table_foreach_remove (view->details->non_ready_files, remove_all, NULL); + file_and_directory_list_free (view->details->old_added_files); + view->details->old_added_files = NULL; + file_and_directory_list_free (view->details->old_changed_files); + view->details->old_changed_files = NULL; + eel_g_object_list_free (view->details->pending_locations_selected); + view->details->pending_locations_selected = NULL; + + if (view->details->model != NULL) { + caja_directory_file_monitor_remove (view->details->model, view); + } + done_loading (view, FALSE); +} + +gboolean +fm_directory_view_is_read_only (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_DIRECTORY_VIEW_CLASS, view, + is_read_only, (view)); +} + +gboolean +fm_directory_view_is_empty (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_DIRECTORY_VIEW_CLASS, view, + is_empty, (view)); +} + +gboolean +fm_directory_view_is_editable (FMDirectoryView *view) +{ + CajaDirectory *directory; + + directory = fm_directory_view_get_model (view); + + if (directory != NULL) { + return caja_directory_is_editable (directory); + } + + return TRUE; +} + +void +fm_directory_view_set_initiated_unmount (FMDirectoryView *view, + gboolean initiated_unmount) +{ + if (view->details->window != NULL) { + caja_window_info_set_initiated_unmount(view->details->window, + initiated_unmount); + } +} + +static gboolean +real_is_read_only (FMDirectoryView *view) +{ + CajaFile *file; + + if (!fm_directory_view_is_editable (view)) { + return TRUE; + } + + file = fm_directory_view_get_directory_as_file (view); + if (file != NULL) { + return !caja_file_can_write (file); + } + return FALSE; +} + +gboolean +fm_directory_view_supports_creating_files (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_DIRECTORY_VIEW_CLASS, view, + supports_creating_files, (view)); +} + +gboolean +fm_directory_view_accepts_dragged_files (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_DIRECTORY_VIEW_CLASS, view, + accepts_dragged_files, (view)); +} + +/** + * fm_directory_view_should_show_file + * + * Returns whether or not this file should be displayed based on + * current filtering options. + */ +gboolean +fm_directory_view_should_show_file (FMDirectoryView *view, CajaFile *file) +{ + return caja_file_should_show (file, + view->details->show_hidden_files, + view->details->show_backup_files, + view->details->show_foreign_files); +} + +static gboolean +real_supports_creating_files (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + + return !fm_directory_view_is_read_only (view) && !showing_trash_directory (view); +} + +static gboolean +real_accepts_dragged_files (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + + return !fm_directory_view_is_read_only (view); +} + +gboolean +fm_directory_view_supports_properties (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_DIRECTORY_VIEW_CLASS, view, + supports_properties, (view)); +} + +static gboolean +real_supports_properties (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + + return TRUE; +} + +gboolean +fm_directory_view_supports_zooming (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_DIRECTORY_VIEW_CLASS, view, + supports_zooming, (view)); +} + +static gboolean +real_supports_zooming (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + + return TRUE; +} + +gboolean +fm_directory_view_using_manual_layout (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_DIRECTORY_VIEW_CLASS, view, + using_manual_layout, (view)); +} + +static gboolean +real_using_manual_layout (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + + return FALSE; +} + +/** + * fm_directory_view_update_menus: + * + * Update the sensitivity and wording of dynamic menu items. + * @view: FMDirectoryView in question. + */ +void +fm_directory_view_update_menus (FMDirectoryView *view) +{ + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + if (!view->details->active) { + return; + } + + + EEL_CALL_METHOD + (FM_DIRECTORY_VIEW_CLASS, view, + update_menus, (view)); + + view->details->menu_states_untrustworthy = FALSE; +} + +static void +schedule_update_menus_callback (gpointer callback_data) +{ + schedule_update_menus (FM_DIRECTORY_VIEW (callback_data)); +} + +void +fm_directory_view_ignore_hidden_file_preferences (FMDirectoryView *view) +{ + g_return_if_fail (view->details->model == NULL); + + if (view->details->ignore_hidden_file_preferences) { + return; + } + + view->details->show_hidden_files = FALSE; + view->details->show_backup_files = FALSE; + view->details->ignore_hidden_file_preferences = TRUE; +} + +void +fm_directory_view_set_show_foreign (FMDirectoryView *view, + gboolean show_foreign) +{ + view->details->show_foreign_files = show_foreign; +} + +char * +fm_directory_view_get_uri (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), NULL); + if (view->details->model == NULL) { + return NULL; + } + return caja_directory_get_uri (view->details->model); +} + +/* Get the real directory where files will be stored and created */ +char * +fm_directory_view_get_backing_uri (FMDirectoryView *view) +{ + CajaDirectory *directory; + char *uri; + + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), NULL); + + if (view->details->model == NULL) { + return NULL; + } + + directory = view->details->model; + + if (CAJA_IS_DESKTOP_DIRECTORY (directory)) { + directory = caja_desktop_directory_get_real_directory (CAJA_DESKTOP_DIRECTORY (directory)); + } else { + caja_directory_ref (directory); + } + + uri = caja_directory_get_uri (directory); + + caja_directory_unref (directory); + + return uri; +} + +void +fm_directory_view_move_copy_items (const GList *item_uris, + GArray *relative_item_points, + const char *target_uri, + int copy_action, + int x, int y, + FMDirectoryView *view) +{ + CajaFile *target_file; + + g_assert (relative_item_points == NULL + || relative_item_points->len == 0 + || g_list_length ((GList *)item_uris) == relative_item_points->len); + + /* add the drop location to the icon offsets */ + offset_drop_points (relative_item_points, x, y); + + target_file = caja_file_get_existing_by_uri (target_uri); + /* special-case "command:" here instead of starting a move/copy */ + if (target_file != NULL && caja_file_is_launcher (target_file)) { + caja_file_unref (target_file); + caja_launch_desktop_file ( + gtk_widget_get_screen (GTK_WIDGET (view)), + target_uri, item_uris, + fm_directory_view_get_containing_window (view)); + return; + } else if (copy_action == GDK_ACTION_COPY && + caja_is_file_roller_installed () && + target_file != NULL && + caja_file_is_archive (target_file)) { + char *command, *quoted_uri, *tmp; + const GList *l; + GdkScreen *screen; + + /* Handle dropping onto a file-roller archiver file, instead of starting a move/copy */ + + caja_file_unref (target_file); + + quoted_uri = g_shell_quote (target_uri); + command = g_strconcat ("file-roller -a ", quoted_uri, NULL); + g_free (quoted_uri); + + for (l = item_uris; l != NULL; l = l->next) { + quoted_uri = g_shell_quote ((char *) l->data); + + tmp = g_strconcat (command, " ", quoted_uri, NULL); + g_free (command); + command = tmp; + + g_free (quoted_uri); + } + + screen = gtk_widget_get_screen (GTK_WIDGET (view)); + if (screen == NULL) { + screen = gdk_screen_get_default (); + } + gdk_spawn_command_line_on_screen (screen, command, NULL); + g_free (command); + + return; + } + caja_file_unref (target_file); + + caja_file_operations_copy_move + (item_uris, relative_item_points, + target_uri, copy_action, GTK_WIDGET (view), + copy_move_done_callback, pre_copy_move (view)); +} + +gboolean +fm_directory_view_can_accept_item (CajaFile *target_item, + const char *item_uri, + FMDirectoryView *view) +{ + g_return_val_if_fail (CAJA_IS_FILE (target_item), FALSE); + g_return_val_if_fail (item_uri != NULL, FALSE); + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + + return caja_drag_can_accept_item (target_item, item_uri); +} + +static void +fm_directory_view_trash_state_changed_callback (CajaTrashMonitor *trash_monitor, + gboolean state, gpointer callback_data) +{ + FMDirectoryView *view; + + view = (FMDirectoryView *) callback_data; + g_assert (FM_IS_DIRECTORY_VIEW (view)); + + schedule_update_menus (view); +} + +void +fm_directory_view_start_batching_selection_changes (FMDirectoryView *view) +{ + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + + ++view->details->batching_selection_level; + view->details->selection_changed_while_batched = FALSE; +} + +void +fm_directory_view_stop_batching_selection_changes (FMDirectoryView *view) +{ + g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + g_return_if_fail (view->details->batching_selection_level > 0); + + if (--view->details->batching_selection_level == 0) { + if (view->details->selection_changed_while_batched) { + fm_directory_view_notify_selection_changed (view); + } + } +} + +static void +revert_slashes (char *string) +{ + while (*string != 0) { + if (*string == '/') { + *string = '\\'; + } + string++; + } +} + + +static GdkDragAction +ask_link_action (FMDirectoryView *view) +{ + int button_pressed; + GdkDragAction result; + GtkWindow *parent_window; + GtkWidget *dialog; + + parent_window = NULL; + + /* Don't use desktop window as parent, since that means + we show up an all desktops etc */ + if (! FM_IS_DESKTOP_ICON_VIEW (view)) { + parent_window = GTK_WINDOW (fm_directory_view_get_containing_window (view)); + } + + dialog = gtk_message_dialog_new (parent_window, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_NONE, + _("Download location?")); + + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), + _("You can download it or make a link to it.")); + + gtk_dialog_add_button (GTK_DIALOG (dialog), + _("Make a _Link"), 0); + gtk_dialog_add_button (GTK_DIALOG (dialog), + GTK_STOCK_CANCEL, 1); + gtk_dialog_add_button (GTK_DIALOG (dialog), + _("_Download"), 2); + + gtk_window_set_title (GTK_WINDOW (dialog), ""); /* as per HIG */ + gtk_window_set_focus_on_map (GTK_WINDOW (dialog), TRUE); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), 2); + + gtk_window_present (GTK_WINDOW (dialog)); + + button_pressed = gtk_dialog_run (GTK_DIALOG (dialog)); + + gtk_widget_destroy (dialog); + + switch (button_pressed) { + case 0: + result = GDK_ACTION_LINK; + break; + case 1: + case GTK_RESPONSE_DELETE_EVENT: + result = 0; + break; + case 2: + result = GDK_ACTION_COPY; + break; + default: + g_assert_not_reached (); + result = 0; + } + + return result; +} + +typedef struct { + FMDirectoryView *view; + GCancellable *cancellable; + char *encoded_url; + char *target_uri; + int x; + int y; + guint timeout; +} NetscapeUrlDropAsk; + +static void +handle_netscape_url_drop_ask_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + NetscapeUrlDropAsk *data; + GdkDragAction action; + GFileInfo *info; + GFile *f; + const char *mime_type; + + data = user_data; + f = G_FILE (source_object); + + info = g_file_query_info_finish (f, res, NULL); + mime_type = NULL; + + if (info) { + mime_type = g_file_info_get_content_type (info); + } + + if (mime_type != NULL && + (g_content_type_equals (mime_type, "text/html") || + g_content_type_equals (mime_type, "text/xml") || + g_content_type_equals (mime_type, "application/xhtml+xml"))) { + action = GDK_ACTION_LINK; + } else if (mime_type != NULL && + g_content_type_equals (mime_type, "text/plain")) { + action = ask_link_action (data->view); + } else { + action = GDK_ACTION_COPY; + } + if (info) { + g_object_unref (info); + } + + if (action != 0) { + fm_directory_view_handle_netscape_url_drop (data->view, + data->encoded_url, + data->target_uri, + action, + data->x, data->y); + } + + g_object_unref (data->view); + g_object_unref (data->cancellable); + if (data->timeout != 0) { + g_source_remove (data->timeout); + } + g_free (data->encoded_url); + g_free (data->target_uri); + g_free (data); +} + +static gboolean +handle_netscape_url_drop_timeout (gpointer user_data) +{ + NetscapeUrlDropAsk *data; + + data = user_data; + + g_cancellable_cancel (data->cancellable); + data->timeout = 0; + + return FALSE; +} + +static inline void +fm_directory_view_widget_to_file_operation_position (FMDirectoryView *view, + GdkPoint *position) +{ + EEL_CALL_METHOD (FM_DIRECTORY_VIEW_CLASS, view, + widget_to_file_operation_position, + (view, position)); +} + +static void +fm_directory_view_widget_to_file_operation_position_xy (FMDirectoryView *view, + int *x, int *y) +{ + GdkPoint position; + + position.x = *x; + position.y = *y; + fm_directory_view_widget_to_file_operation_position (view, &position); + *x = position.x; + *y = position.y; +} + +void +fm_directory_view_handle_netscape_url_drop (FMDirectoryView *view, + const char *encoded_url, + const char *target_uri, + GdkDragAction action, + int x, + int y) +{ + GdkPoint point; + GdkScreen *screen; + int screen_num; + char *url, *title; + char *link_name, *link_display_name; + char *container_uri; + GArray *points; + char **bits; + GList *uri_list = NULL; + GFile *f; + + if (encoded_url == NULL) { + return; + } + + container_uri = NULL; + if (target_uri == NULL) { + container_uri = fm_directory_view_get_backing_uri (view); + g_assert (container_uri != NULL); + } + + f = g_file_new_for_uri (target_uri != NULL ? target_uri : container_uri); + if (!g_file_is_native (f)) { + eel_show_warning_dialog (_("Drag and drop is not supported."), + _("Drag and drop is only supported on local file systems."), + fm_directory_view_get_containing_window (view)); + g_object_unref (f); + g_free (container_uri); + return; + } + g_object_unref (f); + + /* _NETSCAPE_URL_ works like this: $URL\n$TITLE */ + bits = g_strsplit (encoded_url, "\n", 0); + switch (g_strv_length (bits)) { + case 0: + g_strfreev (bits); + g_free (container_uri); + return; + case 1: + url = bits[0]; + title = NULL; + break; + default: + url = bits[0]; + title = bits[1]; + } + + if (action == GDK_ACTION_ASK) { + NetscapeUrlDropAsk *data; + + f = g_file_new_for_uri (url); + data = g_new0 (NetscapeUrlDropAsk, 1); + data->view = g_object_ref (view); + data->cancellable = g_cancellable_new (); + data->encoded_url = g_strdup (encoded_url); + data->target_uri = g_strdup (target_uri); + data->x = x; + data->y = y; + /* Ensure we wait at most 1 second for mimetype */ + data->timeout = g_timeout_add (1000, + handle_netscape_url_drop_timeout, + data); + g_file_query_info_async (f, + G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0, + 0, data->cancellable, + handle_netscape_url_drop_ask_cb, + data); + + g_free (container_uri); + return; + } + + fm_directory_view_widget_to_file_operation_position_xy (view, &x, &y); + + /* We don't support GDK_ACTION_ASK or GDK_ACTION_PRIVATE + * and we don't support combinations either. */ + if ((action != GDK_ACTION_DEFAULT) && + (action != GDK_ACTION_COPY) && + (action != GDK_ACTION_MOVE) && + (action != GDK_ACTION_LINK)) { + eel_show_warning_dialog (_("Drag and drop is not supported."), + _("An invalid drag type was used."), + fm_directory_view_get_containing_window (view)); + g_free (container_uri); + return; + } + + if (action == GDK_ACTION_LINK) { + if (eel_str_is_empty (title)) { + GFile *f; + + f = g_file_new_for_uri (url); + link_name = g_file_get_basename (f); + g_object_unref (f); + } else { + link_name = g_strdup (title); + } + + if (!eel_str_is_empty (link_name)) { + link_display_name = g_strdup_printf (_("Link to %s"), link_name); + + /* The filename can't contain slashes, strip em. + (the basename of http://foo/ is http://foo/) */ + revert_slashes (link_name); + + point.x = x; + point.y = y; + + screen = gtk_widget_get_screen (GTK_WIDGET (view)); + screen_num = gdk_screen_get_number (screen); + + caja_link_local_create (target_uri != NULL ? target_uri : container_uri, + link_name, + link_display_name, + "mate-fs-bookmark", + url, + &point, + screen_num, + TRUE); + + g_free (link_display_name); + } + g_free (link_name); + } else { + GdkPoint tmp_point = { 0, 0 }; + + /* pass in a 1-item array of icon positions, relative to x, y */ + points = g_array_new (FALSE, TRUE, sizeof (GdkPoint)); + g_array_append_val (points, tmp_point); + + uri_list = g_list_append (uri_list, url); + + fm_directory_view_move_copy_items (uri_list, points, + target_uri != NULL ? target_uri : container_uri, + action, x, y, view); + + g_list_free (uri_list); + g_array_free (points, TRUE); + } + + g_strfreev (bits); + + g_free (container_uri); +} + +void +fm_directory_view_handle_uri_list_drop (FMDirectoryView *view, + const char *item_uris, + const char *target_uri, + GdkDragAction action, + int x, + int y) +{ + gchar **uri_list; + GList *real_uri_list = NULL; + char *container_uri; + int n_uris, i; + GArray *points; + + if (item_uris == NULL) { + return; + } + + container_uri = NULL; + if (target_uri == NULL) { + container_uri = fm_directory_view_get_backing_uri (view); + g_assert (container_uri != NULL); + } + + if (action == GDK_ACTION_ASK) { + action = caja_drag_drop_action_ask + (GTK_WIDGET (view), + GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK); + if (action == 0) { + g_free (container_uri); + return; + } + } + + /* We don't support GDK_ACTION_ASK or GDK_ACTION_PRIVATE + * and we don't support combinations either. */ + if ((action != GDK_ACTION_DEFAULT) && + (action != GDK_ACTION_COPY) && + (action != GDK_ACTION_MOVE) && + (action != GDK_ACTION_LINK)) { + eel_show_warning_dialog (_("Drag and drop is not supported."), + _("An invalid drag type was used."), + fm_directory_view_get_containing_window (view)); + g_free (container_uri); + return; + } + + n_uris = 0; + uri_list = g_uri_list_extract_uris (item_uris); + for (i = 0; uri_list[i] != NULL; i++) { + real_uri_list = g_list_append (real_uri_list, uri_list[i]); + n_uris++; + } + g_free (uri_list); + + /* do nothing if no real uris are left */ + if (n_uris == 0) { + g_free (container_uri); + return; + } + + if (n_uris == 1) { + GdkPoint tmp_point = { 0, 0 }; + + /* pass in a 1-item array of icon positions, relative to x, y */ + points = g_array_new (FALSE, TRUE, sizeof (GdkPoint)); + g_array_append_val (points, tmp_point); + } else { + points = NULL; + } + + fm_directory_view_widget_to_file_operation_position_xy (view, &x, &y); + + fm_directory_view_move_copy_items (real_uri_list, points, + target_uri != NULL ? target_uri : container_uri, + action, x, y, view); + + eel_g_list_free_deep (real_uri_list); + + if (points != NULL) + g_array_free (points, TRUE); + + g_free (container_uri); +} + +void +fm_directory_view_handle_text_drop (FMDirectoryView *view, + const char *text, + const char *target_uri, + GdkDragAction action, + int x, + int y) +{ + int length; + char *container_uri; + GdkPoint pos; + + if (text == NULL) { + return; + } + + g_return_if_fail (action == GDK_ACTION_COPY); + + container_uri = NULL; + if (target_uri == NULL) { + container_uri = fm_directory_view_get_backing_uri (view); + g_assert (container_uri != NULL); + } + + length = strlen (text); + + pos.x = x; + pos.y = y; + fm_directory_view_widget_to_file_operation_position (view, &pos); + + fm_directory_view_new_file_with_initial_contents ( + view, target_uri != NULL ? target_uri : container_uri, + /* Translator: This is the filename used for when you dnd text to a directory */ + _("dropped text.txt"), + text, length, &pos); + + g_free (container_uri); +} + +void +fm_directory_view_handle_raw_drop (FMDirectoryView *view, + const char *raw_data, + int length, + const char *target_uri, + const char *direct_save_uri, + GdkDragAction action, + int x, + int y) +{ + char *container_uri, *filename; + GFile *direct_save_full; + GdkPoint pos; + + if (raw_data == NULL) { + return; + } + + g_return_if_fail (action == GDK_ACTION_COPY); + + container_uri = NULL; + if (target_uri == NULL) { + container_uri = fm_directory_view_get_backing_uri (view); + g_assert (container_uri != NULL); + } + + pos.x = x; + pos.y = y; + fm_directory_view_widget_to_file_operation_position (view, &pos); + + filename = NULL; + if (direct_save_uri != NULL) { + direct_save_full = g_file_new_for_uri (direct_save_uri); + filename = g_file_get_basename (direct_save_full); + } + if (filename == NULL) { + /* Translator: This is the filename used for when you dnd raw + * data to a directory, if the source didn't supply a name. + */ + filename = _("dropped data"); + } + + fm_directory_view_new_file_with_initial_contents ( + view, target_uri != NULL ? target_uri : container_uri, + filename, raw_data, length, &pos); + + g_free (container_uri); +} + +gboolean +fm_directory_view_get_active (FMDirectoryView *view) +{ + g_assert (FM_IS_DIRECTORY_VIEW (view)); + return view->details->active; +} + +static GArray * +real_get_selected_icon_locations (FMDirectoryView *view) +{ + /* By default, just return an empty list. */ + return g_array_new (FALSE, TRUE, sizeof (GdkPoint)); +} + +static void +fm_directory_view_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + FMDirectoryView *directory_view; + CajaWindowSlotInfo *slot; + CajaWindowInfo *window; + + directory_view = FM_DIRECTORY_VIEW (object); + + switch (prop_id) { + case PROP_WINDOW_SLOT: + g_assert (directory_view->details->slot == NULL); + + slot = CAJA_WINDOW_SLOT_INFO (g_value_get_object (value)); + window = caja_window_slot_info_get_window (slot); + + directory_view->details->slot = slot; + directory_view->details->window = window; + + g_signal_connect_object (directory_view->details->slot, + "active", G_CALLBACK (slot_active), + directory_view, 0); + g_signal_connect_object (directory_view->details->slot, + "inactive", G_CALLBACK (slot_inactive), + directory_view, 0); + + g_signal_connect_object (directory_view->details->window, + "hidden-files-mode-changed", G_CALLBACK (hidden_files_mode_changed), + directory_view, 0); + fm_directory_view_init_show_hidden_files (directory_view); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + +gboolean +fm_directory_view_handle_scroll_event (FMDirectoryView *directory_view, + GdkEventScroll *event) +{ + if (event->state & GDK_CONTROL_MASK) { + switch (event->direction) { + case GDK_SCROLL_UP: + /* Zoom In */ + fm_directory_view_bump_zoom_level (directory_view, 1); + return TRUE; + + case GDK_SCROLL_DOWN: + /* Zoom Out */ + fm_directory_view_bump_zoom_level (directory_view, -1); + return TRUE; + + case GDK_SCROLL_LEFT: + case GDK_SCROLL_RIGHT: + break; + + default: + g_assert_not_reached (); + } + } + + return FALSE; +} + +/* handle Shift+Scroll, which will cause a zoom-in/out */ +static gboolean +fm_directory_view_scroll_event (GtkWidget *widget, + GdkEventScroll *event) +{ + FMDirectoryView *directory_view; + + directory_view = FM_DIRECTORY_VIEW (widget); + if (fm_directory_view_handle_scroll_event (directory_view, event)) { + return TRUE; + } + + return GTK_WIDGET_CLASS (parent_class)->scroll_event (widget, event); +} + + +static void +fm_directory_view_parent_set (GtkWidget *widget, + GtkWidget *old_parent) +{ + FMDirectoryView *view; + GtkWidget *parent; + + view = FM_DIRECTORY_VIEW (widget); + + parent = gtk_widget_get_parent (widget); + g_assert (parent == NULL || old_parent == NULL); + + if (GTK_WIDGET_CLASS (parent_class)->parent_set != NULL) { + GTK_WIDGET_CLASS (parent_class)->parent_set (widget, old_parent); + } + + if (parent != NULL) { + g_assert (old_parent == NULL); + + if (view->details->slot == + caja_window_info_get_active_slot (view->details->window)) { + view->details->active = TRUE; + + fm_directory_view_merge_menus (view); + schedule_update_menus (view); + } + } else { + fm_directory_view_unmerge_menus (view); + remove_update_menus_timeout_callback (view); + } +} + +static void +fm_directory_view_class_init (FMDirectoryViewClass *klass) +{ + GtkWidgetClass *widget_class; + GtkScrolledWindowClass *scrolled_window_class; + GtkBindingSet *binding_set; + + widget_class = GTK_WIDGET_CLASS (klass); + scrolled_window_class = GTK_SCROLLED_WINDOW_CLASS (klass); + + G_OBJECT_CLASS (klass)->finalize = fm_directory_view_finalize; + G_OBJECT_CLASS (klass)->set_property = fm_directory_view_set_property; + + GTK_OBJECT_CLASS (klass)->destroy = fm_directory_view_destroy; + + widget_class->scroll_event = fm_directory_view_scroll_event; + widget_class->parent_set = fm_directory_view_parent_set; + + /* Get rid of the strange 3-pixel gap that GtkScrolledWindow + * uses by default. It does us no good. + */ + scrolled_window_class->scrollbar_spacing = 0; + + signals[ADD_FILE] = + g_signal_new ("add_file", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (FMDirectoryViewClass, add_file), + NULL, NULL, + caja_marshal_VOID__OBJECT_OBJECT, + G_TYPE_NONE, 2, CAJA_TYPE_FILE, CAJA_TYPE_DIRECTORY); + signals[BEGIN_FILE_CHANGES] = + g_signal_new ("begin_file_changes", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (FMDirectoryViewClass, begin_file_changes), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + signals[BEGIN_LOADING] = + g_signal_new ("begin_loading", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (FMDirectoryViewClass, begin_loading), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + signals[CLEAR] = + g_signal_new ("clear", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (FMDirectoryViewClass, clear), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + signals[END_FILE_CHANGES] = + g_signal_new ("end_file_changes", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (FMDirectoryViewClass, end_file_changes), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + signals[FLUSH_ADDED_FILES] = + g_signal_new ("flush_added_files", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (FMDirectoryViewClass, flush_added_files), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + signals[END_LOADING] = + g_signal_new ("end_loading", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (FMDirectoryViewClass, end_loading), + NULL, NULL, + g_cclosure_marshal_VOID__BOOLEAN, + G_TYPE_NONE, 1, G_TYPE_BOOLEAN); + signals[FILE_CHANGED] = + g_signal_new ("file_changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (FMDirectoryViewClass, file_changed), + NULL, NULL, + caja_marshal_VOID__OBJECT_OBJECT, + G_TYPE_NONE, 2, CAJA_TYPE_FILE, CAJA_TYPE_DIRECTORY); + signals[LOAD_ERROR] = + g_signal_new ("load_error", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (FMDirectoryViewClass, load_error), + NULL, NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, G_TYPE_POINTER); + signals[REMOVE_FILE] = + g_signal_new ("remove_file", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (FMDirectoryViewClass, remove_file), + NULL, NULL, + caja_marshal_VOID__OBJECT_OBJECT, + G_TYPE_NONE, 2, CAJA_TYPE_FILE, CAJA_TYPE_DIRECTORY); + + klass->accepts_dragged_files = real_accepts_dragged_files; + klass->file_limit_reached = real_file_limit_reached; + klass->file_still_belongs = real_file_still_belongs; + klass->get_emblem_names_to_exclude = real_get_emblem_names_to_exclude; + klass->get_selected_icon_locations = real_get_selected_icon_locations; + klass->is_read_only = real_is_read_only; + klass->load_error = real_load_error; + klass->can_rename_file = can_rename_file; + klass->start_renaming_file = start_renaming_file; + klass->supports_creating_files = real_supports_creating_files; + klass->supports_properties = real_supports_properties; + klass->supports_zooming = real_supports_zooming; + klass->using_manual_layout = real_using_manual_layout; + klass->merge_menus = real_merge_menus; + klass->unmerge_menus = real_unmerge_menus; + klass->update_menus = real_update_menus; + klass->set_is_active = real_set_is_active; + + /* Function pointers that subclasses must override */ + EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, add_file); + EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, bump_zoom_level); + EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, can_zoom_in); + EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, can_zoom_out); + EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, clear); + EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, file_changed); + EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, get_background_widget); + EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, get_selection); + EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, get_selection_for_file_transfer); + EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, get_item_count); + EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, is_empty); + EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, reset_to_defaults); + EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, restore_default_zoom_level); + EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, select_all); + EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, set_selection); + EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, invert_selection); + EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, zoom_to_level); + EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, get_zoom_level); + + copied_files_atom = gdk_atom_intern ("x-special/mate-copied-files", FALSE); + + g_object_class_install_property (G_OBJECT_CLASS (klass), + PROP_WINDOW_SLOT, + g_param_spec_object ("window-slot", + "Window Slot", + "The parent window slot reference", + CAJA_TYPE_WINDOW_SLOT_INFO, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); + + signals[TRASH] = + g_signal_new ("trash", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (FMDirectoryViewClass, trash), + g_signal_accumulator_true_handled, NULL, + eel_marshal_BOOLEAN__VOID, + G_TYPE_BOOLEAN, 0); + signals[DELETE] = + g_signal_new ("delete", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (FMDirectoryViewClass, delete), + g_signal_accumulator_true_handled, NULL, + eel_marshal_BOOLEAN__VOID, + G_TYPE_BOOLEAN, 0); + + binding_set = gtk_binding_set_by_class (klass); + gtk_binding_entry_add_signal (binding_set, GDK_Delete, 0, + "trash", 0); + gtk_binding_entry_add_signal (binding_set, GDK_KP_Delete, 0, + "trash", 0); + gtk_binding_entry_add_signal (binding_set, GDK_KP_Delete, GDK_SHIFT_MASK, + "delete", 0); + + klass->trash = real_trash; + klass->delete = real_delete; +} diff --git a/src/file-manager/fm-directory-view.h b/src/file-manager/fm-directory-view.h new file mode 100644 index 00000000..f38cbad1 --- /dev/null +++ b/src/file-manager/fm-directory-view.h @@ -0,0 +1,495 @@ +/* -*- 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, 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 + * Darin Adler <[email protected]> + * John Sullivan <[email protected]> + * Pavel Cisler <[email protected]> + */ + +#ifndef FM_DIRECTORY_VIEW_H +#define FM_DIRECTORY_VIEW_H + +#include <gtk/gtk.h> +#include <eel/eel-background.h> +#include <libcaja-private/caja-directory.h> +#include <libcaja-private/caja-file.h> +#include <libcaja-private/caja-icon-container.h> +#include <libcaja-private/caja-link.h> +#include <libcaja-private/caja-view.h> +#include <libcaja-private/caja-window-info.h> +#include <libcaja-private/caja-window-slot-info.h> +#include <gio/gio.h> + +typedef struct FMDirectoryView FMDirectoryView; +typedef struct FMDirectoryViewClass FMDirectoryViewClass; + +#define FM_TYPE_DIRECTORY_VIEW fm_directory_view_get_type() +#define FM_DIRECTORY_VIEW(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_DIRECTORY_VIEW, FMDirectoryView)) +#define FM_DIRECTORY_VIEW_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_DIRECTORY_VIEW, FMDirectoryViewClass)) +#define FM_IS_DIRECTORY_VIEW(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_DIRECTORY_VIEW)) +#define FM_IS_DIRECTORY_VIEW_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_DIRECTORY_VIEW)) +#define FM_DIRECTORY_VIEW_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_DIRECTORY_VIEW, FMDirectoryViewClass)) + +typedef struct FMDirectoryViewDetails FMDirectoryViewDetails; + +struct FMDirectoryView +{ + GtkScrolledWindow parent; + FMDirectoryViewDetails *details; +}; + +struct FMDirectoryViewClass +{ + GtkScrolledWindowClass parent_class; + + /* The 'clear' signal is emitted to empty the view of its contents. + * It must be replaced by each subclass. + */ + void (* clear) (FMDirectoryView *view); + + /* The 'begin_file_changes' signal is emitted before a set of files + * are added to the view. It can be replaced by a subclass to do any + * necessary preparation for a set of new files. The default + * implementation does nothing. + */ + void (* begin_file_changes) (FMDirectoryView *view); + + /* The 'add_file' signal is emitted to add one file to the view. + * It must be replaced by each subclass. + */ + void (* add_file) (FMDirectoryView *view, + CajaFile *file, + CajaDirectory *directory); + void (* remove_file) (FMDirectoryView *view, + CajaFile *file, + CajaDirectory *directory); + + /* The 'file_changed' signal is emitted to signal a change in a file, + * including the file being removed. + * It must be replaced by each subclass. + */ + void (* file_changed) (FMDirectoryView *view, + CajaFile *file, + CajaDirectory *directory); + + /* The 'end_file_changes' signal is emitted after a set of files + * are added to the view. It can be replaced by a subclass to do any + * necessary cleanup (typically, cleanup for code in begin_file_changes). + * The default implementation does nothing. + */ + void (* end_file_changes) (FMDirectoryView *view); + + void (* flush_added_files) (FMDirectoryView *view); + + /* The 'begin_loading' signal is emitted before any of the contents + * of a directory are added to the view. It can be replaced by a + * subclass to do any necessary preparation to start dealing with a + * new directory. The default implementation does nothing. + */ + void (* begin_loading) (FMDirectoryView *view); + + /* The 'end_loading' signal is emitted after all of the contents + * of a directory are added to the view. It can be replaced by a + * subclass to do any necessary clean-up. The default implementation + * does nothing. + * + * If all_files_seen is true, the handler may assume that + * no load error ocurred, and all files of the underlying + * directory were loaded. + * + * Otherwise, end_loading was emitted due to cancellation, + * which usually means that not all files are available. + */ + void (* end_loading) (FMDirectoryView *view, + gboolean all_files_seen); + + /* The 'load_error' signal is emitted when the directory model + * reports an error in the process of monitoring the directory's + * contents. The load error indicates that the process of + * loading the contents has ended, but the directory is still + * being monitored. The default implementation handles common + * load failures like ACCESS_DENIED. + */ + void (* load_error) (FMDirectoryView *view, + GError *error); + + /* Function pointers that don't have corresponding signals */ + + /* reset_to_defaults is a function pointer that subclasses must + * override to set sort order, zoom level, etc to match default + * values. + */ + void (* reset_to_defaults) (FMDirectoryView *view); + + /* get_selection is not a signal; it is just a function pointer for + * subclasses to replace (override). Subclasses must replace it + * with a function that returns a newly-allocated GList of + * CajaFile pointers. + */ + GList * (* get_selection) (FMDirectoryView *view); + + /* get_selection_for_file_transfer is a function pointer for + * subclasses to replace (override). Subclasses must replace it + * with a function that returns a newly-allocated GList of + * CajaFile pointers. The difference from get_selection is + * that any files in the selection that also has a parent folder + * in the selection is not included. + */ + GList * (* get_selection_for_file_transfer)(FMDirectoryView *view); + + /* select_all is a function pointer that subclasses must override to + * select all of the items in the view */ + void (* select_all) (FMDirectoryView *view); + + /* set_selection is a function pointer that subclasses must + * override to select the specified items (and unselect all + * others). The argument is a list of CajaFiles. */ + + void (* set_selection) (FMDirectoryView *view, + GList *selection); + + /* invert_selection is a function pointer that subclasses must + * override to invert selection. */ + + void (* invert_selection) (FMDirectoryView *view); + + /* Return an array of locations of selected icons in their view. */ + GArray * (* get_selected_icon_locations) (FMDirectoryView *view); + + guint (* get_item_count) (FMDirectoryView *view); + + /* bump_zoom_level is a function pointer that subclasses must override + * to change the zoom level of an object. */ + void (* bump_zoom_level) (FMDirectoryView *view, + int zoom_increment); + + /* zoom_to_level is a function pointer that subclasses must override + * to set the zoom level of an object to the specified level. */ + void (* zoom_to_level) (FMDirectoryView *view, + CajaZoomLevel level); + + CajaZoomLevel (* get_zoom_level) (FMDirectoryView *view); + + /* restore_default_zoom_level is a function pointer that subclasses must override + * to restore the zoom level of an object to a default setting. */ + void (* restore_default_zoom_level) (FMDirectoryView *view); + + /* can_zoom_in is a function pointer that subclasses must override to + * return whether the view is at maximum size (furthest-in zoom level) */ + gboolean (* can_zoom_in) (FMDirectoryView *view); + + /* can_zoom_out is a function pointer that subclasses must override to + * return whether the view is at minimum size (furthest-out zoom level) */ + gboolean (* can_zoom_out) (FMDirectoryView *view); + + /* reveal_selection is a function pointer that subclasses may + * override to make sure the selected items are sufficiently + * apparent to the user (e.g., scrolled into view). By default, + * this does nothing. + */ + void (* reveal_selection) (FMDirectoryView *view); + + /* get_background is a function pointer that subclasses must + * override to return the EelBackground for this view. + */ + GtkWidget * (* get_background_widget) (FMDirectoryView *view); + + /* merge_menus is a function pointer that subclasses can override to + * add their own menu items to the window's menu bar. + * If overridden, subclasses must call parent class's function. + */ + void (* merge_menus) (FMDirectoryView *view); + void (* unmerge_menus) (FMDirectoryView *view); + + /* update_menus is a function pointer that subclasses can override to + * update the sensitivity or wording of menu items in the menu bar. + * It is called (at least) whenever the selection changes. If overridden, + * subclasses must call parent class's function. + */ + void (* update_menus) (FMDirectoryView *view); + + /* sort_files is a function pointer that subclasses can override + * to provide a sorting order to determine which files should be + * presented when only a partial list is provided. + */ + int (* compare_files) (FMDirectoryView *view, + CajaFile *a, + CajaFile *b); + + /* get_emblem_names_to_exclude is a function pointer that subclasses + * may override to specify a set of emblem names that should not + * be displayed with each file. By default, all emblems returned by + * CajaFile are displayed. + */ + char ** (* get_emblem_names_to_exclude) (FMDirectoryView *view); + + /* file_limit_reached is a function pointer that subclasses may + * override to control what happens when a directory is loaded + * that has more files than Caja can handle. The default + * implmentation puts up a dialog box that is appropriate to + * display when the user explicitly tried to visit a location that + * they would think of as a normal directory. + */ + void (* file_limit_reached) (FMDirectoryView *view); + + /* supports_properties is a function pointer that subclasses may + * override to control whether the "Show Properties" menu item + * should be enabled for selected items. The default implementation + * returns TRUE. + */ + gboolean (* supports_properties) (FMDirectoryView *view); + + /* supports_zooming is a function pointer that subclasses may + * override to control whether or not the zooming control and + * menu items should be enabled. The default implementation + * returns TRUE. + */ + gboolean (* supports_zooming) (FMDirectoryView *view); + + /* using_manual_layout is a function pointer that subclasses may + * override to control whether or not items can be freely positioned + * on the user-visible area. + * Note that this value is not guaranteed to be constant within the + * view's lifecycle. */ + gboolean (* using_manual_layout) (FMDirectoryView *view); + + /* is_read_only is a function pointer that subclasses may + * override to control whether or not the user is allowed to + * change the contents of the currently viewed directory. The + * default implementation checks the permissions of the + * directory. + */ + gboolean (* is_read_only) (FMDirectoryView *view); + + /* is_empty is a function pointer that subclasses must + * override to report whether the view contains any items. + */ + gboolean (* is_empty) (FMDirectoryView *view); + + /* supports_creating_files is a function pointer that subclasses may + * override to control whether or not new items can be created. + * be accepted. The default implementation checks whether the + * user has write permissions for the viewed directory, and whether + * the viewed directory is in the trash. + */ + gboolean (* supports_creating_files) (FMDirectoryView *view); + + /* accepts_dragged_files is a function pointer that subclasses may + * override to control whether or not files can be dropped in this + * location. The default implementation returns TRUE. + */ + gboolean (* accepts_dragged_files) (FMDirectoryView *view); + + gboolean (* can_rename_file) (FMDirectoryView *view, + CajaFile *file); + /* select_all specifies whether the whole filename should be selected + * or only its basename (i.e. everything except the extension) + * */ + void (* start_renaming_file) (FMDirectoryView *view, + CajaFile *file, + gboolean select_all); + + gboolean (* file_still_belongs) (FMDirectoryView *view, + CajaFile *file, + CajaDirectory *directory); + + /* convert *point from widget's coordinate system to a coordinate + * system used for specifying file operation positions, which is view-specific. + * + * This is used by the the icon view, which converts the screen position to a zoom + * level-independent coordinate system. + */ + void (* widget_to_file_operation_position) (FMDirectoryView *view, + GdkPoint *position); + + /* Preference change callbacks, overriden by icon and list views. + * Icon and list views respond by synchronizing to the new preference + * values and forcing an update if appropriate. + */ + void (* text_attribute_names_changed) (FMDirectoryView *view); + void (* embedded_text_policy_changed) (FMDirectoryView *view); + void (* image_display_policy_changed) (FMDirectoryView *view); + void (* click_policy_changed) (FMDirectoryView *view); + void (* sort_directories_first_changed) (FMDirectoryView *view); + + void (* emblems_changed) (FMDirectoryView *view); + + void (* set_is_active) (FMDirectoryView *view, + gboolean is_active); + + /* Signals used only for keybindings */ + gboolean (* trash) (FMDirectoryView *view); + gboolean (* delete) (FMDirectoryView *view); +}; + +/* GObject support */ +GType fm_directory_view_get_type (void); + +/* Functions callable from the user interface and elsewhere. */ +CajaWindowInfo *fm_directory_view_get_caja_window (FMDirectoryView *view); +CajaWindowSlotInfo *fm_directory_view_get_caja_window_slot (FMDirectoryView *view); +char * fm_directory_view_get_uri (FMDirectoryView *view); +char * fm_directory_view_get_backing_uri (FMDirectoryView *view); +gboolean fm_directory_view_can_accept_item (CajaFile *target_item, + const char *item_uri, + FMDirectoryView *view); +void fm_directory_view_display_selection_info (FMDirectoryView *view); +GList * fm_directory_view_get_selection (FMDirectoryView *view); +GList * fm_directory_view_get_selection_for_file_transfer (FMDirectoryView *view); +void fm_directory_view_invert_selection (FMDirectoryView *view); +void fm_directory_view_stop (FMDirectoryView *view); +guint fm_directory_view_get_item_count (FMDirectoryView *view); +gboolean fm_directory_view_can_zoom_in (FMDirectoryView *view); +gboolean fm_directory_view_can_zoom_out (FMDirectoryView *view); +GtkWidget * fm_directory_view_get_background_widget (FMDirectoryView *view); +void fm_directory_view_bump_zoom_level (FMDirectoryView *view, + int zoom_increment); +void fm_directory_view_zoom_to_level (FMDirectoryView *view, + CajaZoomLevel zoom_level); +CajaZoomLevel fm_directory_view_get_zoom_level (FMDirectoryView *view); +void fm_directory_view_restore_default_zoom_level (FMDirectoryView *view); +void fm_directory_view_reset_to_defaults (FMDirectoryView *view); +void fm_directory_view_select_all (FMDirectoryView *view); +void fm_directory_view_set_selection (FMDirectoryView *view, + GList *selection); +GArray * fm_directory_view_get_selected_icon_locations (FMDirectoryView *view); +void fm_directory_view_reveal_selection (FMDirectoryView *view); +gboolean fm_directory_view_is_empty (FMDirectoryView *view); +gboolean fm_directory_view_is_read_only (FMDirectoryView *view); +gboolean fm_directory_view_supports_creating_files (FMDirectoryView *view); +gboolean fm_directory_view_accepts_dragged_files (FMDirectoryView *view); +gboolean fm_directory_view_supports_properties (FMDirectoryView *view); +gboolean fm_directory_view_supports_zooming (FMDirectoryView *view); +gboolean fm_directory_view_using_manual_layout (FMDirectoryView *view); +void fm_directory_view_move_copy_items (const GList *item_uris, + GArray *relative_item_points, + const char *target_uri, + int copy_action, + int x, + int y, + FMDirectoryView *view); +GdkAtom fm_directory_view_get_copied_files_atom (FMDirectoryView *view); +gboolean fm_directory_view_get_active (FMDirectoryView *view); + +/* Wrappers for signal emitters. These are normally called + * only by FMDirectoryView itself. They have corresponding signals + * that observers might want to connect with. + */ +void fm_directory_view_clear (FMDirectoryView *view); +void fm_directory_view_begin_loading (FMDirectoryView *view); +void fm_directory_view_end_loading (FMDirectoryView *view, + gboolean all_files_seen); + +gboolean fm_directory_view_get_loading (FMDirectoryView *view); + +/* Hooks for subclasses to call. These are normally called only by + * FMDirectoryView and its subclasses + */ +void fm_directory_view_activate_files (FMDirectoryView *view, + GList *files, + CajaWindowOpenMode mode, + CajaWindowOpenFlags flags, + gboolean confirm_multiple); +void fm_directory_view_activate_file (FMDirectoryView *view, + CajaFile *file, + CajaWindowOpenMode mode, + CajaWindowOpenFlags flags); +void fm_directory_view_start_batching_selection_changes (FMDirectoryView *view); +void fm_directory_view_stop_batching_selection_changes (FMDirectoryView *view); +void fm_directory_view_queue_file_change (FMDirectoryView *view, + CajaFile *file); +void fm_directory_view_notify_selection_changed (FMDirectoryView *view); +GtkUIManager * fm_directory_view_get_ui_manager (FMDirectoryView *view); +char ** fm_directory_view_get_emblem_names_to_exclude (FMDirectoryView *view); +CajaDirectory *fm_directory_view_get_model (FMDirectoryView *view); +GtkWindow *fm_directory_view_get_containing_window (FMDirectoryView *view); +CajaFile *fm_directory_view_get_directory_as_file (FMDirectoryView *view); +EelBackground * fm_directory_view_get_background (FMDirectoryView *view); +gboolean fm_directory_view_get_allow_moves (FMDirectoryView *view); +void fm_directory_view_pop_up_background_context_menu (FMDirectoryView *view, + GdkEventButton *event); +void fm_directory_view_pop_up_selection_context_menu (FMDirectoryView *view, + GdkEventButton *event); +void fm_directory_view_pop_up_location_context_menu (FMDirectoryView *view, + GdkEventButton *event, + const char *location); +void fm_directory_view_send_selection_change (FMDirectoryView *view); +gboolean fm_directory_view_should_show_file (FMDirectoryView *view, + CajaFile *file); +gboolean fm_directory_view_should_sort_directories_first (FMDirectoryView *view); +void fm_directory_view_update_menus (FMDirectoryView *view); +void fm_directory_view_new_folder (FMDirectoryView *view); +void fm_directory_view_new_file (FMDirectoryView *view, + const char *parent_uri, + CajaFile *source); +void fm_directory_view_ignore_hidden_file_preferences (FMDirectoryView *view); +void fm_directory_view_set_show_foreign (FMDirectoryView *view, + gboolean show_foreign); +void fm_directory_view_init_view_iface (CajaViewIface *iface); +gboolean fm_directory_view_handle_scroll_event (FMDirectoryView *view, + GdkEventScroll *event); +void fm_directory_view_handle_netscape_url_drop (FMDirectoryView *view, + const char *encoded_url, + const char *target_uri, + GdkDragAction action, + int x, + int y); +void fm_directory_view_handle_uri_list_drop (FMDirectoryView *view, + const char *item_uris, + const char *target_uri, + GdkDragAction action, + int x, + int y); +void fm_directory_view_handle_text_drop (FMDirectoryView *view, + const char *text, + const char *target_uri, + GdkDragAction action, + int x, + int y); +void fm_directory_view_handle_raw_drop (FMDirectoryView *view, + const char *raw_data, + int length, + const char *target_uri, + const char *direct_save_uri, + GdkDragAction action, + int x, + int y); +void fm_directory_view_freeze_updates (FMDirectoryView *view); +void fm_directory_view_unfreeze_updates (FMDirectoryView *view); +void fm_directory_view_add_subdirectory (FMDirectoryView *view, + CajaDirectory*directory); +void fm_directory_view_remove_subdirectory (FMDirectoryView *view, + CajaDirectory*directory); + +gboolean fm_directory_view_is_editable (FMDirectoryView *view); +void fm_directory_view_set_initiated_unmount (FMDirectoryView *view, + gboolean inititated_unmount); + +/* operations affecting two directory views */ +void fm_directory_view_move_copy_items_between_views (FMDirectoryView *source, FMDirectoryView *target, gboolean copy); + +#endif /* FM_DIRECTORY_VIEW_H */ diff --git a/src/file-manager/fm-ditem-page.c b/src/file-manager/fm-ditem-page.c new file mode 100644 index 00000000..af88b753 --- /dev/null +++ b/src/file-manager/fm-ditem-page.c @@ -0,0 +1,563 @@ +/* + * fm-ditem-page.c: Desktop item editing support + * + * Copyright (C) 2004 James Willcox + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Authors: James Willcox <[email protected]> + * + */ + +#include <config.h> +#include "fm-ditem-page.h" + +#include <string.h> + +#include <eel/eel-glib-extensions.h> +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <libcaja-extension/caja-extension-types.h> +#include <libcaja-extension/caja-file-info.h> +#include <libcaja-private/caja-file.h> +#include <libcaja-private/caja-file-attributes.h> + +#define MAIN_GROUP "Desktop Entry" + +typedef struct ItemEntry +{ + const char *field; + const char *description; + char *current_value; + gboolean localized; + gboolean filename; +} ItemEntry; + +enum +{ + TARGET_URI_LIST +}; + +static const GtkTargetEntry target_table[] = +{ + { "text/uri-list", 0, TARGET_URI_LIST } +}; + +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_uri (GKeyFile *key_file, + const char *uri, + GError **error) +{ + GFile *file; + char *data; + gsize len; + + data = g_key_file_to_data (key_file, &len, error); + if (data == NULL) + { + return FALSE; + } + file = g_file_new_for_uri (uri); + if (!g_file_replace_contents (file, + data, len, + NULL, FALSE, + G_FILE_CREATE_NONE, + NULL, NULL, error)) + { + g_object_unref (file); + g_free (data); + return FALSE; + } + g_object_unref (file); + g_free (data); + return TRUE; +} + +static GKeyFile * +_g_key_file_new_from_file (GFile *file, + GKeyFileFlags flags, + GError **error) +{ + GKeyFile *key_file; + + 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; + } + return key_file; +} + +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_from_file (file, flags, error); + g_object_unref (file); + return key_file; +} + +static ItemEntry * +item_entry_new (const char *field, + const char *description, + gboolean localized, + gboolean filename) +{ + ItemEntry *entry; + + entry = g_new0 (ItemEntry, 1); + entry->field = field; + entry->description = description; + entry->localized = localized; + entry->filename = filename; + + return entry; +} + +static void +item_entry_free (ItemEntry *entry) +{ + g_free (entry->current_value); + g_free (entry); +} + +static void +fm_ditem_page_url_drag_data_received (GtkWidget *widget, GdkDragContext *context, + int x, int y, + GtkSelectionData *selection_data, + guint info, guint time, + GtkEntry *entry) +{ + char **uris; + gboolean exactly_one; + char *path; + + uris = g_strsplit (gtk_selection_data_get_data (selection_data), "\r\n", 0); + exactly_one = uris[0] != NULL && (uris[1] == NULL || uris[1][0] == '\0'); + + if (!exactly_one) + { + g_strfreev (uris); + return; + } + + path = g_filename_from_uri (uris[0], NULL, NULL); + if (path != NULL) + { + gtk_entry_set_text (entry, path); + g_free (path); + } + else + { + gtk_entry_set_text (entry, uris[0]); + } + + g_strfreev (uris); +} + +static void +fm_ditem_page_exec_drag_data_received (GtkWidget *widget, GdkDragContext *context, + int x, int y, + GtkSelectionData *selection_data, + guint info, guint time, + GtkEntry *entry) +{ + char **uris; + gboolean exactly_one; + CajaFile *file; + GKeyFile *key_file; + char *uri, *type, *exec; + + uris = g_strsplit (gtk_selection_data_get_data (selection_data), "\r\n", 0); + exactly_one = uris[0] != NULL && (uris[1] == NULL || uris[1][0] == '\0'); + + if (!exactly_one) + { + g_strfreev (uris); + return; + } + + file = caja_file_get_by_uri (uris[0]); + + g_return_if_fail (file != NULL); + + uri = caja_file_get_uri (file); + if (caja_file_is_mime_type (file, "application/x-desktop")) + { + key_file = _g_key_file_new_from_uri (uri, G_KEY_FILE_NONE, NULL); + if (key_file != NULL) + { + type = g_key_file_get_string (key_file, MAIN_GROUP, "Type", NULL); + if (type != NULL && strcmp (type, "Application") == 0) + { + exec = g_key_file_get_string (key_file, MAIN_GROUP, "Exec", NULL); + if (exec != NULL) + { + g_free (uri); + uri = exec; + } + } + g_free (type); + g_key_file_free (key_file); + } + } + gtk_entry_set_text (entry, + uri?uri:""); + gtk_widget_grab_focus (GTK_WIDGET (entry)); + + g_free (uri); + + caja_file_unref (file); + + g_strfreev (uris); +} + +static void +save_entry (GtkEntry *entry, GKeyFile *key_file, const char *uri) +{ + GError *error; + ItemEntry *item_entry; + const char *val; + gchar **languages; + + item_entry = g_object_get_data (G_OBJECT (entry), "item_entry"); + val = gtk_entry_get_text (entry); + + if (strcmp (val, item_entry->current_value) == 0) + { + return; /* No actual change, don't update file */ + } + + g_free (item_entry->current_value); + item_entry->current_value = g_strdup (val); + + if (item_entry->localized) + { + languages = (gchar **) g_get_language_names (); + g_key_file_set_locale_string (key_file, MAIN_GROUP, item_entry->field, languages[0], val); + } + else + { + g_key_file_set_string (key_file, MAIN_GROUP, item_entry->field, val); + } + + error = NULL; + + if (!_g_key_file_save_to_uri (key_file, uri, &error)) + { + g_warning ("%s", error->message); + g_error_free (error); + } +} + +static void +entry_activate_cb (GtkWidget *entry, + GtkWidget *container) +{ + const char *uri; + GKeyFile *key_file; + + uri = g_object_get_data (G_OBJECT (container), "uri"); + key_file = g_object_get_data (G_OBJECT (container), "keyfile"); + save_entry (GTK_ENTRY (entry), key_file, uri); +} + +static gboolean +entry_focus_out_cb (GtkWidget *entry, + GdkEventFocus *event, + GtkWidget *container) +{ + const char *uri; + GKeyFile *key_file; + + uri = g_object_get_data (G_OBJECT (container), "uri"); + key_file = g_object_get_data (G_OBJECT (container), "keyfile"); + save_entry (GTK_ENTRY (entry), key_file, uri); + return FALSE; +} + +static GtkWidget * +build_table (GtkWidget *container, + GKeyFile *key_file, + GtkSizeGroup *label_size_group, + GList *entries) +{ + GtkWidget *table; + GtkWidget *label; + GtkWidget *entry; + GList *l; + char *val; + int i; + + table = gtk_table_new (g_list_length (entries) + 1, 2, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_col_spacings (GTK_TABLE (table), 12); + i = 0; + + for (l = entries; l; l = l->next) + { + ItemEntry *item_entry = (ItemEntry *)l->data; + char *label_text; + + label_text = g_strdup_printf ("%s:", item_entry->description); + label = gtk_label_new (label_text); + gtk_label_set_use_markup (GTK_LABEL (label), TRUE); + g_free (label_text); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_size_group_add_widget (label_size_group, label); + + entry = gtk_entry_new (); + + if (item_entry->localized) + { + val = g_key_file_get_locale_string (key_file, + MAIN_GROUP, + item_entry->field, + NULL, NULL); + } + else + { + val = g_key_file_get_string (key_file, + MAIN_GROUP, + item_entry->field, + NULL); + } + + item_entry->current_value = g_strdup (val?val:""); + gtk_entry_set_text (GTK_ENTRY (entry), item_entry->current_value); + g_free (val); + + gtk_table_attach (GTK_TABLE (table), label, + 0, 1, i, i+1, GTK_FILL, GTK_FILL, + 0, 0); + gtk_table_attach (GTK_TABLE (table), entry, + 1, 2, i, i+1, GTK_EXPAND|GTK_FILL, GTK_EXPAND|GTK_FILL, + 0, 0); + g_signal_connect (entry, "activate", + G_CALLBACK (entry_activate_cb), + container); + g_signal_connect (entry, "focus_out_event", + G_CALLBACK (entry_focus_out_cb), + container); + + g_object_set_data_full (G_OBJECT (entry), "item_entry", item_entry, + (GDestroyNotify)item_entry_free); + + if (item_entry->filename) + { + gtk_drag_dest_set (GTK_WIDGET (entry), + GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP, + target_table, G_N_ELEMENTS (target_table), + GDK_ACTION_COPY | GDK_ACTION_MOVE); + + g_signal_connect (entry, "drag_data_received", + G_CALLBACK (fm_ditem_page_url_drag_data_received), + entry); + } + else if (strcmp (item_entry->field, "Exec") == 0) + { + gtk_drag_dest_set (GTK_WIDGET (entry), + GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP, + target_table, G_N_ELEMENTS (target_table), + GDK_ACTION_COPY | GDK_ACTION_MOVE); + + g_signal_connect (entry, "drag_data_received", + G_CALLBACK (fm_ditem_page_exec_drag_data_received), + entry); + } + + i++; + } + + /* append dummy row */ + label = gtk_label_new (""); + gtk_table_attach (GTK_TABLE (table), label, + 0, 1, i, i+1, GTK_FILL, GTK_FILL, + 0, 0); + gtk_size_group_add_widget (label_size_group, label); + + + gtk_widget_show_all (table); + return table; +} + +static void +create_page (GKeyFile *key_file, GtkWidget *box) +{ + GtkWidget *table; + GList *entries; + GtkSizeGroup *label_size_group; + char *type; + + entries = NULL; + + type = g_key_file_get_string (key_file, MAIN_GROUP, "Type", NULL); + + if (g_strcmp0 (type, "Link") == 0) + { + entries = g_list_prepend (entries, + item_entry_new ("Comment", + _("Comment"), TRUE, FALSE)); + entries = g_list_prepend (entries, + item_entry_new ("URL", + _("URL"), FALSE, TRUE)); + entries = g_list_prepend (entries, + item_entry_new ("GenericName", + _("Description"), TRUE, FALSE)); + } + else if (g_strcmp0 (type, "Application") == 0) + { + entries = g_list_prepend (entries, + item_entry_new ("Comment", + _("Comment"), TRUE, FALSE)); + entries = g_list_prepend (entries, + item_entry_new ("Exec", + _("Command"), FALSE, FALSE)); + entries = g_list_prepend (entries, + item_entry_new ("GenericName", + _("Description"), TRUE, FALSE)); + } + else + { + /* we only handle launchers and links */ + + /* ensure that we build an empty table with a dummy row at the end */ + goto build_table; + } + g_free (type); + +build_table: + label_size_group = g_object_get_data (G_OBJECT (box), "label-size-group"); + + table = build_table (box, key_file, label_size_group, entries); + g_list_free (entries); + + gtk_box_pack_start (GTK_BOX (box), table, FALSE, TRUE, 0); + gtk_widget_show_all (GTK_WIDGET (box)); +} + + +static void +ditem_read_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GKeyFile *key_file; + GtkWidget *box; + gsize file_size; + char *file_contents; + + box = GTK_WIDGET (user_data); + + if (g_file_load_contents_finish (G_FILE (source_object), + res, + &file_contents, &file_size, + NULL, NULL)) + { + key_file = g_key_file_new (); + g_object_set_data_full (G_OBJECT (box), "keyfile", key_file, (GDestroyNotify)g_key_file_free); + if (g_key_file_load_from_data (key_file, file_contents, file_size, 0, NULL)) + { + create_page (key_file, box); + } + g_free (file_contents); + + } + g_object_unref (box); +} + +static void +fm_ditem_page_create_begin (const char *uri, + GtkWidget *box) +{ + GFile *location; + + location = g_file_new_for_uri (uri); + g_object_set_data_full (G_OBJECT (box), "uri", g_strdup (uri), g_free); + g_file_load_contents_async (location, NULL, ditem_read_cb, g_object_ref (box)); + g_object_unref (location); +} + +GtkWidget * +fm_ditem_page_make_box (GtkSizeGroup *label_size_group, + GList *files) +{ + CajaFileInfo *info; + char *uri; + GtkWidget *box; + + g_assert (fm_ditem_page_should_show (files)); + + box = gtk_vbox_new (FALSE, 6); + g_object_set_data_full (G_OBJECT (box), "label-size-group", + label_size_group, (GDestroyNotify) g_object_unref); + + info = CAJA_FILE_INFO (files->data); + + uri = caja_file_info_get_uri (info); + fm_ditem_page_create_begin (uri, box); + g_free (uri); + + return box; +} + +gboolean +fm_ditem_page_should_show (GList *files) +{ + CajaFileInfo *info; + + if (!files || files->next) + { + return FALSE; + } + + info = CAJA_FILE_INFO (files->data); + + if (!caja_file_info_is_mime_type (info, "application/x-desktop")) + { + return FALSE; + } + + return TRUE; +} + diff --git a/src/file-manager/fm-ditem-page.h b/src/file-manager/fm-ditem-page.h new file mode 100644 index 00000000..bf55d002 --- /dev/null +++ b/src/file-manager/fm-ditem-page.h @@ -0,0 +1,51 @@ +/* + * fm-ditem-page.h - A property page for desktop items + * + * Copyright (C) 2004 James Willcox + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Authors: James Willcox <[email protected]> + * + */ + +#ifndef FM_DITEM_PAGE_H +#define FM_DITEM_PAGE_H + +#include <glib.h> +#include <glib-object.h> +#include <gtk/gtk.h> + +#ifdef __cplusplus +extern "C" { +#endif + + /* This is a mis-nomer. Launcher editables initially were displayed on separate + * a property notebook page, which implemented the CajaPropertyPageProvider + * interface. + * + * Nowadays, they are displayed on the "Basic" page, so just the setup + * routines are left. + */ + + GtkWidget *fm_ditem_page_make_box (GtkSizeGroup *label_size_group, + GList *files); + gboolean fm_ditem_page_should_show (GList *files); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/file-manager/fm-empty-view.c b/src/file-manager/fm-empty-view.c new file mode 100644 index 00000000..d85c702c --- /dev/null +++ b/src/file-manager/fm-empty-view.c @@ -0,0 +1,412 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-empty-view.c - implementation of empty view of directory. + + Copyright (C) 2006 Free Software Foundation, 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: Christian Neumair <[email protected]> +*/ + +#include <config.h> +#include "fm-empty-view.h" + +#include <string.h> +#include <libcaja-private/caja-file-utilities.h> +#include <libcaja-private/caja-view.h> +#include <libcaja-private/caja-view-factory.h> +#include <eel/eel-glib-extensions.h> +#include <eel/eel-gtk-macros.h> +#include <eel/eel-vfs-extensions.h> + +struct FMEmptyViewDetails +{ + int number_of_files; +}; + +static GList *fm_empty_view_get_selection (FMDirectoryView *view); +static GList *fm_empty_view_get_selection_for_file_transfer (FMDirectoryView *view); +static void fm_empty_view_scroll_to_file (CajaView *view, + const char *uri); +static void fm_empty_view_iface_init (CajaViewIface *iface); + +G_DEFINE_TYPE_WITH_CODE (FMEmptyView, fm_empty_view, FM_TYPE_DIRECTORY_VIEW, + G_IMPLEMENT_INTERFACE (CAJA_TYPE_VIEW, + fm_empty_view_iface_init)); + +/* for EEL_CALL_PARENT */ +#define parent_class fm_empty_view_parent_class + +static void +fm_empty_view_add_file (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory) +{ + static GTimer *timer = NULL; + static gdouble cumu = 0, elaps; + FM_EMPTY_VIEW (view)->details->number_of_files++; + GdkPixbuf *icon; + + if (!timer) timer = g_timer_new (); + + g_timer_start (timer); + icon = caja_file_get_icon_pixbuf (file, caja_get_icon_size_for_zoom_level (CAJA_ZOOM_LEVEL_STANDARD), TRUE, 0); + + elaps = g_timer_elapsed (timer, NULL); + g_timer_stop (timer); + + g_object_unref (icon); + + cumu += elaps; + g_message ("entire loading: %.3f, cumulative %.3f", elaps, cumu); +} + + +static void +fm_empty_view_begin_loading (FMDirectoryView *view) +{ +} + +static void +fm_empty_view_clear (FMDirectoryView *view) +{ +} + + +static void +fm_empty_view_file_changed (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory) +{ +} + +static GtkWidget * +fm_empty_view_get_background_widget (FMDirectoryView *view) +{ + return GTK_WIDGET (view); +} + +static GList * +fm_empty_view_get_selection (FMDirectoryView *view) +{ + return NULL; +} + + +static GList * +fm_empty_view_get_selection_for_file_transfer (FMDirectoryView *view) +{ + return NULL; +} + +static guint +fm_empty_view_get_item_count (FMDirectoryView *view) +{ + return FM_EMPTY_VIEW (view)->details->number_of_files; +} + +static gboolean +fm_empty_view_is_empty (FMDirectoryView *view) +{ + return FM_EMPTY_VIEW (view)->details->number_of_files == 0; +} + +static void +fm_empty_view_end_file_changes (FMDirectoryView *view) +{ +} + +static void +fm_empty_view_remove_file (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory) +{ + FM_EMPTY_VIEW (view)->details->number_of_files--; + g_assert (FM_EMPTY_VIEW (view)->details->number_of_files >= 0); +} + +static void +fm_empty_view_set_selection (FMDirectoryView *view, GList *selection) +{ + fm_directory_view_notify_selection_changed (view); +} + +static void +fm_empty_view_select_all (FMDirectoryView *view) +{ +} + +static void +fm_empty_view_reveal_selection (FMDirectoryView *view) +{ +} + +static void +fm_empty_view_merge_menus (FMDirectoryView *view) +{ + EEL_CALL_PARENT (FM_DIRECTORY_VIEW_CLASS, merge_menus, (view)); +} + +static void +fm_empty_view_update_menus (FMDirectoryView *view) +{ + EEL_CALL_PARENT (FM_DIRECTORY_VIEW_CLASS, update_menus, (view)); +} + +/* Reset sort criteria and zoom level to match defaults */ +static void +fm_empty_view_reset_to_defaults (FMDirectoryView *view) +{ +} + +static void +fm_empty_view_bump_zoom_level (FMDirectoryView *view, int zoom_increment) +{ +} + +static CajaZoomLevel +fm_empty_view_get_zoom_level (FMDirectoryView *view) +{ + return CAJA_ZOOM_LEVEL_STANDARD; +} + +static void +fm_empty_view_zoom_to_level (FMDirectoryView *view, + CajaZoomLevel zoom_level) +{ +} + +static void +fm_empty_view_restore_default_zoom_level (FMDirectoryView *view) +{ +} + +static gboolean +fm_empty_view_can_zoom_in (FMDirectoryView *view) +{ + return FALSE; +} + +static gboolean +fm_empty_view_can_zoom_out (FMDirectoryView *view) +{ + return FALSE; +} + +static void +fm_empty_view_start_renaming_file (FMDirectoryView *view, + CajaFile *file, + gboolean select_all) +{ +} + +static void +fm_empty_view_click_policy_changed (FMDirectoryView *directory_view) +{ +} + + +static int +fm_empty_view_compare_files (FMDirectoryView *view, CajaFile *file1, CajaFile *file2) +{ + if (file1 < file2) + { + return -1; + } + + if (file1 > file2) + { + return +1; + } + + return 0; +} + +static gboolean +fm_empty_view_using_manual_layout (FMDirectoryView *view) +{ + return FALSE; +} + +static void +fm_empty_view_end_loading (FMDirectoryView *view, + gboolean all_files_seen) +{ +} + +static void +fm_empty_view_finalize (GObject *object) +{ + FMEmptyView *empty_view; + + empty_view = FM_EMPTY_VIEW (object); + g_free (empty_view->details); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +fm_empty_view_emblems_changed (FMDirectoryView *directory_view) +{ +} + +static char * +fm_empty_view_get_first_visible_file (CajaView *view) +{ + return NULL; +} + +static void +fm_empty_view_scroll_to_file (CajaView *view, + const char *uri) +{ +} + +static void +fm_empty_view_grab_focus (CajaView *view) +{ + gtk_widget_grab_focus (GTK_WIDGET (view)); +} + +static void +fm_empty_view_sort_directories_first_changed (FMDirectoryView *view) +{ +} + +static void +fm_empty_view_class_init (FMEmptyViewClass *class) +{ + FMDirectoryViewClass *fm_directory_view_class; + + fm_directory_view_class = FM_DIRECTORY_VIEW_CLASS (class); + + G_OBJECT_CLASS (class)->finalize = fm_empty_view_finalize; + + fm_directory_view_class->add_file = fm_empty_view_add_file; + fm_directory_view_class->begin_loading = fm_empty_view_begin_loading; + fm_directory_view_class->bump_zoom_level = fm_empty_view_bump_zoom_level; + fm_directory_view_class->can_zoom_in = fm_empty_view_can_zoom_in; + fm_directory_view_class->can_zoom_out = fm_empty_view_can_zoom_out; + fm_directory_view_class->click_policy_changed = fm_empty_view_click_policy_changed; + fm_directory_view_class->clear = fm_empty_view_clear; + fm_directory_view_class->file_changed = fm_empty_view_file_changed; + fm_directory_view_class->get_background_widget = fm_empty_view_get_background_widget; + fm_directory_view_class->get_selection = fm_empty_view_get_selection; + fm_directory_view_class->get_selection_for_file_transfer = fm_empty_view_get_selection_for_file_transfer; + fm_directory_view_class->get_item_count = fm_empty_view_get_item_count; + fm_directory_view_class->is_empty = fm_empty_view_is_empty; + fm_directory_view_class->remove_file = fm_empty_view_remove_file; + fm_directory_view_class->merge_menus = fm_empty_view_merge_menus; + fm_directory_view_class->update_menus = fm_empty_view_update_menus; + fm_directory_view_class->reset_to_defaults = fm_empty_view_reset_to_defaults; + fm_directory_view_class->restore_default_zoom_level = fm_empty_view_restore_default_zoom_level; + fm_directory_view_class->reveal_selection = fm_empty_view_reveal_selection; + fm_directory_view_class->select_all = fm_empty_view_select_all; + fm_directory_view_class->set_selection = fm_empty_view_set_selection; + fm_directory_view_class->compare_files = fm_empty_view_compare_files; + fm_directory_view_class->sort_directories_first_changed = fm_empty_view_sort_directories_first_changed; + fm_directory_view_class->start_renaming_file = fm_empty_view_start_renaming_file; + fm_directory_view_class->get_zoom_level = fm_empty_view_get_zoom_level; + fm_directory_view_class->zoom_to_level = fm_empty_view_zoom_to_level; + fm_directory_view_class->emblems_changed = fm_empty_view_emblems_changed; + fm_directory_view_class->end_file_changes = fm_empty_view_end_file_changes; + fm_directory_view_class->using_manual_layout = fm_empty_view_using_manual_layout; + fm_directory_view_class->end_loading = fm_empty_view_end_loading; +} + +static const char * +fm_empty_view_get_id (CajaView *view) +{ + return FM_EMPTY_VIEW_ID; +} + + +static void +fm_empty_view_iface_init (CajaViewIface *iface) +{ + fm_directory_view_init_view_iface (iface); + + iface->get_view_id = fm_empty_view_get_id; + iface->get_first_visible_file = fm_empty_view_get_first_visible_file; + iface->scroll_to_file = fm_empty_view_scroll_to_file; + iface->get_title = NULL; + iface->grab_focus = fm_empty_view_grab_focus; +} + + +static void +fm_empty_view_init (FMEmptyView *empty_view) +{ + empty_view->details = g_new0 (FMEmptyViewDetails, 1); +} + +static CajaView * +fm_empty_view_create (CajaWindowSlotInfo *slot) +{ + FMEmptyView *view; + + g_assert (CAJA_IS_WINDOW_SLOT_INFO (slot)); + + view = g_object_new (FM_TYPE_EMPTY_VIEW, + "window-slot", slot, + NULL); + + return CAJA_VIEW (view); +} + +static gboolean +fm_empty_view_supports_uri (const char *uri, + GFileType file_type, + const char *mime_type) +{ + if (file_type == G_FILE_TYPE_DIRECTORY) + { + return TRUE; + } + if (strcmp (mime_type, CAJA_SAVED_SEARCH_MIMETYPE) == 0) + { + return TRUE; + } + if (g_str_has_prefix (uri, "trash:")) + { + return TRUE; + } + if (g_str_has_prefix (uri, EEL_SEARCH_URI)) + { + return TRUE; + } + + return FALSE; +} + +static CajaViewInfo fm_empty_view = +{ + FM_EMPTY_VIEW_ID, + "Empty", + "Empty View", + "_Empty View", + "The empty view encountered an error.", + "Display this location with the empty view.", + fm_empty_view_create, + fm_empty_view_supports_uri +}; + +void +fm_empty_view_register (void) +{ + fm_empty_view.id = fm_empty_view.id; + fm_empty_view.view_combo_label = fm_empty_view.view_combo_label; + fm_empty_view.view_menu_label_with_mnemonic = fm_empty_view.view_menu_label_with_mnemonic; + fm_empty_view.error_label = fm_empty_view.error_label; + fm_empty_view.display_location_label = fm_empty_view.display_location_label; + + caja_view_factory_register (&fm_empty_view); +} diff --git a/src/file-manager/fm-empty-view.h b/src/file-manager/fm-empty-view.h new file mode 100644 index 00000000..795dd27a --- /dev/null +++ b/src/file-manager/fm-empty-view.h @@ -0,0 +1,60 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-empty-view.h - interface for empty view of directory. + + Copyright (C) 2006 Free Software Foundation, 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: Christian Neumair <[email protected]> +*/ + +#ifndef FM_EMPTY_VIEW_H +#define FM_EMPTY_VIEW_H + +#include "fm-directory-view.h" + +#define FM_TYPE_EMPTY_VIEW fm_empty_view_get_type() +#define FM_EMPTY_VIEW(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_EMPTY_VIEW, FMEmptyView)) +#define FM_EMPTY_VIEW_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_EMPTY_VIEW, FMEmptyViewClass)) +#define FM_IS_EMPTY_VIEW(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_EMPTY_VIEW)) +#define FM_IS_EMPTY_VIEW_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_EMPTY_VIEW)) +#define FM_EMPTY_VIEW_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_EMPTY_VIEW, FMEmptyViewClass)) + +#define FM_EMPTY_VIEW_ID "OAFIID:Caja_File_Manager_Empty_View" + +typedef struct FMEmptyViewDetails FMEmptyViewDetails; + +typedef struct +{ + FMDirectoryView parent_instance; + FMEmptyViewDetails *details; +} FMEmptyView; + +typedef struct +{ + FMDirectoryViewClass parent_class; +} FMEmptyViewClass; + +GType fm_empty_view_get_type (void); +void fm_empty_view_register (void); + +#endif /* FM_EMPTY_VIEW_H */ diff --git a/src/file-manager/fm-error-reporting.c b/src/file-manager/fm-error-reporting.c new file mode 100644 index 00000000..f301e9a9 --- /dev/null +++ b/src/file-manager/fm-error-reporting.c @@ -0,0 +1,380 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-error-reporting.h - implementation of file manager functions that report + errors to the user. + + 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: John Sullivan <[email protected]> +*/ + +#include <config.h> +#include "fm-error-reporting.h" + +#include <string.h> +#include <glib/gi18n.h> +#include <libcaja-private/caja-debug-log.h> +#include <libcaja-private/caja-file.h> +#include <eel/eel-string.h> +#include <eel/eel-stock-dialogs.h> + +#define NEW_NAME_TAG "Caja: new name" +#define MAXIMUM_DISPLAYED_FILE_NAME_LENGTH 50 + +static void finish_rename (CajaFile *file, gboolean stop_timer, GError *error); + +void +fm_report_error_loading_directory (CajaFile *file, + GError *error, + GtkWindow *parent_window) +{ + char *file_name; + char *message; + + if (error == NULL || + error->message == NULL) + { + return; + } + + if (error->domain == G_IO_ERROR && + error->code == G_IO_ERROR_NOT_MOUNTED) + { + /* This case is retried automatically */ + return; + } + + file_name = caja_file_get_display_name (file); + + if (error->domain == G_IO_ERROR) + { + switch (error->code) + { + case G_IO_ERROR_PERMISSION_DENIED: + message = g_strdup_printf (_("You do not have the permissions necessary to view the contents of \"%s\"."), + file_name); + break; + case G_IO_ERROR_NOT_FOUND: + message = g_strdup_printf (_("\"%s\" could not be found. Perhaps it has recently been deleted."), + file_name); + break; + default: + message = g_strdup_printf (_("Sorry, could not display all the contents of \"%s\": %s"), file_name, + error->message); + } + } + else + { + message = g_strdup (error->message); + } + + eel_show_error_dialog (_("The folder contents could not be displayed."), message, parent_window); + + g_free (file_name); + g_free (message); +} + +void +fm_report_error_renaming_file (CajaFile *file, + const char *new_name, + GError *error, + GtkWindow *parent_window) +{ + char *original_name, *original_name_truncated; + char *new_name_truncated; + char *message; + + /* Truncate names for display since very long file names with no spaces + * in them won't get wrapped, and can create insanely wide dialog boxes. + */ + original_name = caja_file_get_display_name (file); + original_name_truncated = eel_str_middle_truncate (original_name, MAXIMUM_DISPLAYED_FILE_NAME_LENGTH); + g_free (original_name); + + new_name_truncated = eel_str_middle_truncate (new_name, MAXIMUM_DISPLAYED_FILE_NAME_LENGTH); + + message = NULL; + if (error->domain == G_IO_ERROR) + { + switch (error->code) + { + case G_IO_ERROR_EXISTS: + message = g_strdup_printf (_("The name \"%s\" is already used in this folder. " + "Please use a different name."), + new_name_truncated); + break; + case G_IO_ERROR_NOT_FOUND: + message = g_strdup_printf (_("There is no \"%s\" in this folder. " + "Perhaps it was just moved or deleted?"), + original_name_truncated); + break; + case G_IO_ERROR_PERMISSION_DENIED: + message = g_strdup_printf (_("You do not have the permissions necessary to rename \"%s\"."), + original_name_truncated); + break; + case G_IO_ERROR_INVALID_FILENAME: + if (strchr (new_name, '/') != NULL) + { + message = g_strdup_printf (_("The name \"%s\" is not valid because it contains the character \"/\". " + "Please use a different name."), + new_name_truncated); + } + else + { + message = g_strdup_printf (_("The name \"%s\" is not valid. " + "Please use a different name."), + new_name_truncated); + } + break; + default: + break; + } + } + + if (message == NULL) + { + /* We should invent decent error messages for every case we actually experience. */ + g_warning ("Hit unhandled case %s:%d in fm_report_error_renaming_file", + g_quark_to_string (error->domain), error->code); + /* fall through */ + message = g_strdup_printf (_("Sorry, could not rename \"%s\" to \"%s\": %s"), + original_name_truncated, new_name_truncated, + error->message); + } + + g_free (original_name_truncated); + g_free (new_name_truncated); + + eel_show_error_dialog (_("The item could not be renamed."), message, parent_window); + g_free (message); +} + +void +fm_report_error_setting_group (CajaFile *file, + GError *error, + GtkWindow *parent_window) +{ + char *file_name; + char *message; + + if (error == NULL) + { + return; + } + + file_name = caja_file_get_display_name (file); + + message = NULL; + if (error->domain == G_IO_ERROR) + { + switch (error->code) + { + case G_IO_ERROR_PERMISSION_DENIED: + message = g_strdup_printf (_("You do not have the permissions necessary to change the group of \"%s\"."), + file_name); + break; + default: + break; + } + } + + if (message == NULL) + { + /* We should invent decent error messages for every case we actually experience. */ + g_warning ("Hit unhandled case %s:%d in fm_report_error_setting_group", + g_quark_to_string (error->domain), error->code); + /* fall through */ + message = g_strdup_printf (_("Sorry, could not change the group of \"%s\": %s"), file_name, + error->message); + } + + + eel_show_error_dialog (_("The group could not be changed."), message, parent_window); + + g_free (file_name); + g_free (message); +} + +void +fm_report_error_setting_owner (CajaFile *file, + GError *error, + GtkWindow *parent_window) +{ + char *file_name; + char *message; + + if (error == NULL) + { + return; + } + + file_name = caja_file_get_display_name (file); + + message = g_strdup_printf (_("Sorry, could not change the owner of \"%s\": %s"), file_name, error->message); + + eel_show_error_dialog (_("The owner could not be changed."), message, parent_window); + + g_free (file_name); + g_free (message); +} + +void +fm_report_error_setting_permissions (CajaFile *file, + GError *error, + GtkWindow *parent_window) +{ + char *file_name; + char *message; + + if (error == NULL) + { + return; + } + + file_name = caja_file_get_display_name (file); + + message = g_strdup_printf (_("Sorry, could not change the permissions of \"%s\": %s"), file_name, error->message); + + eel_show_error_dialog (_("The permissions could not be changed."), message, parent_window); + + g_free (file_name); + g_free (message); +} + +typedef struct _FMRenameData +{ + char *name; + CajaFileOperationCallback callback; + gpointer callback_data; +} FMRenameData; + +static void +fm_rename_data_free (FMRenameData *data) +{ + g_free (data->name); + g_free (data); +} + +static void +rename_callback (CajaFile *file, GFile *result_location, + GError *error, gpointer callback_data) +{ + FMRenameData *data; + + g_assert (CAJA_IS_FILE (file)); + g_assert (callback_data == NULL); + + data = g_object_get_data (G_OBJECT (file), NEW_NAME_TAG); + g_assert (data != NULL); + + if (error && + !(error->domain == G_IO_ERROR && error->code == G_IO_ERROR_CANCELLED)) + { + /* If rename failed, notify the user. */ + fm_report_error_renaming_file (file, data->name, error, NULL); + } + + finish_rename (file, TRUE, error); +} + +static void +cancel_rename_callback (gpointer callback_data) +{ + GError *error; + + error = g_error_new (G_IO_ERROR, G_IO_ERROR_CANCELLED, "Cancelled"); + finish_rename (CAJA_FILE (callback_data), FALSE, error); + g_error_free (error); +} + +static void +finish_rename (CajaFile *file, gboolean stop_timer, GError *error) +{ + FMRenameData *data; + + data = g_object_get_data (G_OBJECT (file), NEW_NAME_TAG); + if (data == NULL) + { + return; + } + + /* Cancel both the rename and the timed wait. */ + caja_file_cancel (file, rename_callback, NULL); + if (stop_timer) + { + eel_timed_wait_stop (cancel_rename_callback, file); + } + + if (data->callback != NULL) + { + data->callback (file, NULL, error, data->callback_data); + } + + /* Let go of file name. */ + g_object_set_data (G_OBJECT (file), NEW_NAME_TAG, NULL); +} + +void +fm_rename_file (CajaFile *file, + const char *new_name, + CajaFileOperationCallback callback, + gpointer callback_data) +{ + char *old_name, *wait_message; + FMRenameData *data; + char *uri; + GError *error; + + g_return_if_fail (CAJA_IS_FILE (file)); + g_return_if_fail (new_name != NULL); + + /* Stop any earlier rename that's already in progress. */ + error = g_error_new (G_IO_ERROR, G_IO_ERROR_CANCELLED, "Cancelled"); + finish_rename (file, TRUE, error); + g_error_free (error); + + data = g_new0 (FMRenameData, 1); + data->name = g_strdup (new_name); + data->callback = callback; + data->callback_data = callback_data; + + /* Attach the new name to the file. */ + g_object_set_data_full (G_OBJECT (file), + NEW_NAME_TAG, + data, (GDestroyNotify)fm_rename_data_free); + + /* Start the timed wait to cancel the rename. */ + old_name = caja_file_get_display_name (file); + wait_message = g_strdup_printf (_("Renaming \"%s\" to \"%s\"."), + old_name, + new_name); + g_free (old_name); + eel_timed_wait_start (cancel_rename_callback, file, wait_message, + NULL); /* FIXME bugzilla.gnome.org 42395: Parent this? */ + g_free (wait_message); + + uri = caja_file_get_uri (file); + caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, + "rename file old=\"%s\", new=\"%s\"", + uri, new_name); + g_free (uri); + + /* Start the rename. */ + caja_file_rename (file, new_name, + rename_callback, NULL); +} diff --git a/src/file-manager/fm-error-reporting.h b/src/file-manager/fm-error-reporting.h new file mode 100644 index 00000000..556e5ae2 --- /dev/null +++ b/src/file-manager/fm-error-reporting.h @@ -0,0 +1,55 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-error-reporting.h - interface for file manager functions that report + errors to the user. + + 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: John Sullivan <[email protected]> +*/ + +#ifndef FM_ERROR_REPORTING_H +#define FM_ERROR_REPORTING_H + +#include <gtk/gtk.h> +#include <libcaja-private/caja-file.h> + +void fm_report_error_loading_directory (CajaFile *file, + GError *error, + GtkWindow *parent_window); +void fm_report_error_renaming_file (CajaFile *file, + const char *new_name, + GError *error, + GtkWindow *parent_window); +void fm_report_error_setting_permissions (CajaFile *file, + GError *error, + GtkWindow *parent_window); +void fm_report_error_setting_owner (CajaFile *file, + GError *error, + GtkWindow *parent_window); +void fm_report_error_setting_group (CajaFile *file, + GError *error, + GtkWindow *parent_window); + +/* FIXME bugzilla.gnome.org 42394: Should this file be renamed or should this function be moved? */ +void fm_rename_file (CajaFile *file, + const char *new_name, + CajaFileOperationCallback callback, + gpointer callback_data); + +#endif /* FM_ERROR_REPORTING_H */ diff --git a/src/file-manager/fm-icon-container.c b/src/file-manager/fm-icon-container.c new file mode 100644 index 00000000..86e54669 --- /dev/null +++ b/src/file-manager/fm-icon-container.c @@ -0,0 +1,625 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-icon-container.h - the container widget for file manager icons + + Copyright (C) 2002 Sun Microsystems, 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: Michael Meeks <[email protected]> +*/ +#include <config.h> + +#include <string.h> +#include <glib/gi18n.h> +#include <gio/gio.h> +#include <libcaja-private/caja-global-preferences.h> +#include <libcaja-private/caja-file-attributes.h> +#include <libcaja-private/caja-thumbnails.h> +#include <libcaja-private/caja-desktop-icon-file.h> + +#include "fm-icon-container.h" + +#define ICON_TEXT_ATTRIBUTES_NUM_ITEMS 3 +#define ICON_TEXT_ATTRIBUTES_DEFAULT_TOKENS "size,date_modified,type" + +G_DEFINE_TYPE (FMIconContainer, fm_icon_container, CAJA_TYPE_ICON_CONTAINER); + +static GQuark attribute_none_q; + +static FMIconView * +get_icon_view (CajaIconContainer *container) +{ + /* Type unsafe comparison for performance */ + return ((FMIconContainer *)container)->view; +} + +static CajaIconInfo * +fm_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_window_open) +{ + FMIconView *icon_view; + char **emblems_to_ignore; + CajaFile *file; + gboolean use_embedding; + CajaFileIconFlags flags; + guint emblem_size; + + file = (CajaFile *) data; + + g_assert (CAJA_IS_FILE (file)); + icon_view = get_icon_view (container); + g_return_val_if_fail (icon_view != NULL, NULL); + + use_embedding = FALSE; + if (embedded_text) + { + *embedded_text = caja_file_peek_top_left_text (file, need_large_embeddded_text, embedded_text_needs_loading); + use_embedding = *embedded_text != NULL; + } + + if (emblem_pixbufs != NULL) + { + emblem_size = caja_icon_get_emblem_size_for_icon_size (size); + /* don't return images larger than the actual icon size */ + emblem_size = MIN (emblem_size, size); + + if (emblem_size > 0) + { + emblems_to_ignore = fm_directory_view_get_emblem_names_to_exclude + (FM_DIRECTORY_VIEW (icon_view)); + *emblem_pixbufs = caja_file_get_emblem_pixbufs (file, + emblem_size, + FALSE, + emblems_to_ignore); + g_strfreev (emblems_to_ignore); + } + } + + *has_window_open = caja_file_has_open_window (file); + + flags = CAJA_FILE_ICON_FLAGS_USE_MOUNT_ICON_AS_EMBLEM; + if (!fm_icon_view_is_compact (icon_view) || + caja_icon_container_get_zoom_level (container) > CAJA_ZOOM_LEVEL_STANDARD) + { + flags |= CAJA_FILE_ICON_FLAGS_USE_THUMBNAILS; + if (fm_icon_view_is_compact (icon_view)) + { + flags |= CAJA_FILE_ICON_FLAGS_FORCE_THUMBNAIL_SIZE; + } + } + + if (use_embedding) + { + flags |= CAJA_FILE_ICON_FLAGS_EMBEDDING_TEXT; + } + if (for_drag_accept) + { + flags |= CAJA_FILE_ICON_FLAGS_FOR_DRAG_ACCEPT; + } + + return caja_file_get_icon (file, size, flags); +} + +static char * +fm_icon_container_get_icon_description (CajaIconContainer *container, + CajaIconData *data) +{ + CajaFile *file; + char *mime_type; + const char *description; + + file = CAJA_FILE (data); + g_assert (CAJA_IS_FILE (file)); + + if (CAJA_IS_DESKTOP_ICON_FILE (file)) + { + return NULL; + } + + mime_type = caja_file_get_mime_type (file); + description = g_content_type_get_description (mime_type); + g_free (mime_type); + return g_strdup (description); +} + +static void +fm_icon_container_start_monitor_top_left (CajaIconContainer *container, + CajaIconData *data, + gconstpointer client, + gboolean large_text) +{ + CajaFile *file; + CajaFileAttributes attributes; + + file = (CajaFile *) data; + + g_assert (CAJA_IS_FILE (file)); + + attributes = CAJA_FILE_ATTRIBUTE_TOP_LEFT_TEXT; + if (large_text) + { + attributes |= CAJA_FILE_ATTRIBUTE_LARGE_TOP_LEFT_TEXT; + } + caja_file_monitor_add (file, client, attributes); +} + +static void +fm_icon_container_stop_monitor_top_left (CajaIconContainer *container, + CajaIconData *data, + gconstpointer client) +{ + CajaFile *file; + + file = (CajaFile *) data; + + g_assert (CAJA_IS_FILE (file)); + + caja_file_monitor_remove (file, client); +} + +static void +fm_icon_container_prioritize_thumbnailing (CajaIconContainer *container, + CajaIconData *data) +{ + CajaFile *file; + char *uri; + + file = (CajaFile *) data; + + g_assert (CAJA_IS_FILE (file)); + + if (caja_file_is_thumbnailing (file)) + { + uri = caja_file_get_uri (file); + caja_thumbnail_prioritize (uri); + g_free (uri); + } +} + +/* + * Get the preference for which caption text should appear + * beneath icons. + */ +static GQuark * +fm_icon_container_get_icon_text_attributes_from_preferences (void) +{ + static GQuark *attributes = NULL; + + if (attributes == NULL) + { + eel_preferences_add_auto_string_array_as_quarks (CAJA_PREFERENCES_ICON_VIEW_CAPTIONS, + &attributes); + } + + /* We don't need to sanity check the attributes list even though it came + * from preferences. + * + * There are 2 ways that the values in the list could be bad. + * + * 1) The user picks "bad" values. "bad" values are those that result in + * there being duplicate attributes in the list. + * + * 2) Value stored in MateConf are tampered with. Its possible physically do + * this by pulling the rug underneath MateConf and manually editing its + * config files. Its also possible to use a third party MateConf key + * editor and store garbage for the keys in question. + * + * Thankfully, the Caja preferences machinery deals with both of + * these cases. + * + * In the first case, the preferences dialog widgetry prevents + * duplicate attributes by making "bad" choices insensitive. + * + * In the second case, the preferences getter (and also the auto storage) for + * string_array values are always valid members of the enumeration associated + * with the preference. + * + * So, no more error checking on attributes is needed here and we can return + * a the auto stored value. + */ + return attributes; +} + +static int +quarkv_length (GQuark *attributes) +{ + int i; + i = 0; + while (attributes[i] != 0) + { + i++; + } + return i; +} + +/** + * fm_icon_view_get_icon_text_attribute_names: + * + * Get a list representing which text attributes should be displayed + * beneath an icon. The result is dependent on zoom level and possibly + * user configuration. Don't free the result. + * @view: FMIconView to query. + * + **/ +static GQuark * +fm_icon_container_get_icon_text_attribute_names (CajaIconContainer *container, + int *len) +{ + GQuark *attributes; + int piece_count; + + const int pieces_by_level[] = + { + 0, /* CAJA_ZOOM_LEVEL_SMALLEST */ + 0, /* CAJA_ZOOM_LEVEL_SMALLER */ + 0, /* CAJA_ZOOM_LEVEL_SMALL */ + 1, /* CAJA_ZOOM_LEVEL_STANDARD */ + 2, /* CAJA_ZOOM_LEVEL_LARGE */ + 2, /* CAJA_ZOOM_LEVEL_LARGER */ + 3 /* CAJA_ZOOM_LEVEL_LARGEST */ + }; + + piece_count = pieces_by_level[caja_icon_container_get_zoom_level (container)]; + + attributes = fm_icon_container_get_icon_text_attributes_from_preferences (); + + *len = MIN (piece_count, quarkv_length (attributes)); + + return attributes; +} + +/* This callback returns the text, both the editable part, and the + * part below that is not editable. + */ +static void +fm_icon_container_get_icon_text (CajaIconContainer *container, + CajaIconData *data, + char **editable_text, + char **additional_text, + gboolean include_invisible) +{ + char *actual_uri; + gchar *description; + GQuark *attributes; + char *text_array[4]; + int i, j, num_attributes; + FMIconView *icon_view; + CajaFile *file; + gboolean use_additional; + + file = CAJA_FILE (data); + + g_assert (CAJA_IS_FILE (file)); + g_assert (editable_text != NULL); + icon_view = get_icon_view (container); + g_return_if_fail (icon_view != NULL); + + use_additional = (additional_text != NULL); + + /* In the smallest zoom mode, no text is drawn. */ + if (caja_icon_container_get_zoom_level (container) == CAJA_ZOOM_LEVEL_SMALLEST && + !include_invisible) + { + *editable_text = NULL; + } + else + { + /* Strip the suffix for caja object xml files. */ + *editable_text = caja_file_get_display_name (file); + } + + if (!use_additional) + { + return; + } + + if (fm_icon_view_is_compact (icon_view)) + { + *additional_text = NULL; + return; + } + + if (CAJA_IS_DESKTOP_ICON_FILE (file)) + { + /* Don't show the normal extra information for desktop icons, it doesn't + * make sense. */ + *additional_text = NULL; + return; + } + + /* Handle link files specially. */ + if (caja_file_is_caja_link (file)) + { + /* FIXME bugzilla.gnome.org 42531: Does sync. I/O and works only locally. */ + *additional_text = NULL; + if (caja_file_is_local (file)) + { + actual_uri = caja_file_get_uri (file); + description = caja_link_local_get_additional_text (actual_uri); + if (description) + *additional_text = g_strdup_printf (" \n%s\n ", description); + g_free (description); + g_free (actual_uri); + } + /* Don't show the normal extra information for desktop files, it doesn't + * make sense. */ + return; + } + + /* Find out what attributes go below each icon. */ + attributes = fm_icon_container_get_icon_text_attribute_names (container, + &num_attributes); + + /* Get the attributes. */ + j = 0; + for (i = 0; i < num_attributes; ++i) + { + if (attributes[i] == attribute_none_q) + { + continue; + } + + text_array[j++] = + caja_file_get_string_attribute_with_default_q (file, attributes[i]); + } + text_array[j] = NULL; + + /* Return them. */ + if (j == 0) + { + *additional_text = NULL; + } + else if (j == 1) + { + /* Only one item, avoid the strdup + free */ + *additional_text = text_array[0]; + } + else + { + *additional_text = g_strjoinv ("\n", text_array); + + for (i = 0; i < j; i++) + { + g_free (text_array[i]); + } + } +} + +/* Sort as follows: + * 0) computer link + * 1) home link + * 2) network link + * 3) mount links + * 4) other + * 5) trash link + */ +typedef enum +{ + SORT_COMPUTER_LINK, + SORT_HOME_LINK, + SORT_NETWORK_LINK, + SORT_MOUNT_LINK, + SORT_OTHER, + SORT_TRASH_LINK +} SortCategory; + +static SortCategory +get_sort_category (CajaFile *file) +{ + CajaDesktopLink *link; + SortCategory category; + + category = SORT_OTHER; + + if (CAJA_IS_DESKTOP_ICON_FILE (file)) + { + link = caja_desktop_icon_file_get_link (CAJA_DESKTOP_ICON_FILE (file)); + if (link != NULL) + { + switch (caja_desktop_link_get_link_type (link)) + { + case CAJA_DESKTOP_LINK_COMPUTER: + category = SORT_COMPUTER_LINK; + break; + case CAJA_DESKTOP_LINK_HOME: + category = SORT_HOME_LINK; + break; + case CAJA_DESKTOP_LINK_MOUNT: + category = SORT_MOUNT_LINK; + break; + case CAJA_DESKTOP_LINK_TRASH: + category = SORT_TRASH_LINK; + break; + case CAJA_DESKTOP_LINK_NETWORK: + category = SORT_NETWORK_LINK; + break; + default: + category = SORT_OTHER; + break; + } + g_object_unref (link); + } + } + + return category; +} + +static int +fm_desktop_icon_container_icons_compare (CajaIconContainer *container, + CajaIconData *data_a, + CajaIconData *data_b) +{ + CajaFile *file_a; + CajaFile *file_b; + FMDirectoryView *directory_view; + SortCategory category_a, category_b; + + file_a = (CajaFile *) data_a; + file_b = (CajaFile *) data_b; + + directory_view = FM_DIRECTORY_VIEW (FM_ICON_CONTAINER (container)->view); + g_return_val_if_fail (directory_view != NULL, 0); + + category_a = get_sort_category (file_a); + category_b = get_sort_category (file_b); + + if (category_a == category_b) + { + return caja_file_compare_for_sort + (file_a, file_b, CAJA_FILE_SORT_BY_DISPLAY_NAME, + fm_directory_view_should_sort_directories_first (directory_view), + FALSE); + } + + if (category_a < category_b) + { + return -1; + } + else + { + return +1; + } +} + +static int +fm_icon_container_compare_icons (CajaIconContainer *container, + CajaIconData *icon_a, + CajaIconData *icon_b) +{ + FMIconView *icon_view; + + icon_view = get_icon_view (container); + g_return_val_if_fail (icon_view != NULL, 0); + + if (FM_ICON_CONTAINER (container)->sort_for_desktop) + { + return fm_desktop_icon_container_icons_compare + (container, icon_a, icon_b); + } + + /* Type unsafe comparisons for performance */ + return fm_icon_view_compare_files (icon_view, + (CajaFile *)icon_a, + (CajaFile *)icon_b); +} + +static int +fm_icon_container_compare_icons_by_name (CajaIconContainer *container, + CajaIconData *icon_a, + CajaIconData *icon_b) +{ + return caja_file_compare_for_sort + (CAJA_FILE (icon_a), + CAJA_FILE (icon_b), + CAJA_FILE_SORT_BY_DISPLAY_NAME, + FALSE, FALSE); +} + +static void +fm_icon_container_freeze_updates (CajaIconContainer *container) +{ + FMIconView *icon_view; + icon_view = get_icon_view (container); + g_return_if_fail (icon_view != NULL); + fm_directory_view_freeze_updates (FM_DIRECTORY_VIEW (icon_view)); +} + +static void +fm_icon_container_unfreeze_updates (CajaIconContainer *container) +{ + FMIconView *icon_view; + icon_view = get_icon_view (container); + g_return_if_fail (icon_view != NULL); + fm_directory_view_unfreeze_updates (FM_DIRECTORY_VIEW (icon_view)); +} + +static void +fm_icon_container_dispose (GObject *object) +{ + FMIconContainer *icon_container; + + icon_container = FM_ICON_CONTAINER (object); + + icon_container->view = NULL; + + G_OBJECT_CLASS (fm_icon_container_parent_class)->dispose (object); +} + +static void +fm_icon_container_class_init (FMIconContainerClass *klass) +{ + CajaIconContainerClass *ic_class; + + ic_class = &klass->parent_class; + + attribute_none_q = g_quark_from_static_string ("none"); + + ic_class->get_icon_text = fm_icon_container_get_icon_text; + ic_class->get_icon_images = fm_icon_container_get_icon_images; + ic_class->get_icon_description = fm_icon_container_get_icon_description; + ic_class->start_monitor_top_left = fm_icon_container_start_monitor_top_left; + ic_class->stop_monitor_top_left = fm_icon_container_stop_monitor_top_left; + ic_class->prioritize_thumbnailing = fm_icon_container_prioritize_thumbnailing; + + ic_class->compare_icons = fm_icon_container_compare_icons; + ic_class->compare_icons_by_name = fm_icon_container_compare_icons_by_name; + ic_class->freeze_updates = fm_icon_container_freeze_updates; + ic_class->unfreeze_updates = fm_icon_container_unfreeze_updates; + + G_OBJECT_CLASS (klass)->dispose = fm_icon_container_dispose; +} + +static void +fm_icon_container_init (FMIconContainer *icon_container) +{ +} + +CajaIconContainer * +fm_icon_container_construct (FMIconContainer *icon_container, FMIconView *view) +{ + AtkObject *atk_obj; + + g_return_val_if_fail (FM_IS_ICON_VIEW (view), NULL); + + icon_container->view = view; + atk_obj = gtk_widget_get_accessible (GTK_WIDGET (icon_container)); + atk_object_set_name (atk_obj, _("Icon View")); + + return CAJA_ICON_CONTAINER (icon_container); +} + +CajaIconContainer * +fm_icon_container_new (FMIconView *view) +{ + return fm_icon_container_construct + (g_object_new (FM_TYPE_ICON_CONTAINER, NULL), + view); +} + +void +fm_icon_container_set_sort_desktop (FMIconContainer *container, + gboolean desktop) +{ + container->sort_for_desktop = desktop; +} diff --git a/src/file-manager/fm-icon-container.h b/src/file-manager/fm-icon-container.h new file mode 100644 index 00000000..a0527acf --- /dev/null +++ b/src/file-manager/fm-icon-container.h @@ -0,0 +1,68 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-icon-container.h - the container widget for file manager icons + + Copyright (C) 2002 Sun Microsystems, 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: Michael Meeks <[email protected]> +*/ + +#ifndef FM_ICON_CONTAINER_H +#define FM_ICON_CONTAINER_H + +#include <libcaja-private/caja-icon-container.h> +#include "fm-icon-view.h" + +typedef struct FMIconContainer FMIconContainer; +typedef struct FMIconContainerClass FMIconContainerClass; + +#define FM_TYPE_ICON_CONTAINER fm_icon_container_get_type() +#define FM_ICON_CONTAINER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_ICON_CONTAINER, FMIconContainer)) +#define FM_ICON_CONTAINER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_ICON_CONTAINER, FMIconContainerClass)) +#define FM_IS_ICON_CONTAINER(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_ICON_CONTAINER)) +#define FM_IS_ICON_CONTAINER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_ICON_CONTAINER)) +#define FM_ICON_CONTAINER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_ICON_CONTAINER, FMIconContainerClass)) + +typedef struct FMIconContainerDetails FMIconContainerDetails; + +struct FMIconContainer +{ + CajaIconContainer parent; + + FMIconView *view; + gboolean sort_for_desktop; +}; + +struct FMIconContainerClass +{ + CajaIconContainerClass parent_class; +}; + +GType fm_icon_container_get_type (void); +CajaIconContainer *fm_icon_container_construct (FMIconContainer *icon_container, + FMIconView *view); +CajaIconContainer *fm_icon_container_new (FMIconView *view); +void fm_icon_container_set_sort_desktop (FMIconContainer *container, + gboolean desktop); + +#endif /* FM_ICON_CONTAINER_H */ diff --git a/src/file-manager/fm-icon-view.c b/src/file-manager/fm-icon-view.c new file mode 100644 index 00000000..7d8687a2 --- /dev/null +++ b/src/file-manager/fm-icon-view.c @@ -0,0 +1,3439 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-icon-view.c - implementation of icon view of directory. + + 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: John Sullivan <[email protected]> +*/ + +#include <config.h> +#include "fm-icon-view.h" + +#include "fm-actions.h" +#include "fm-icon-container.h" +#include "fm-desktop-icon-view.h" +#include "fm-error-reporting.h" +#include <stdlib.h> +#include <eel/eel-background.h> +#include <eel/eel-glib-extensions.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 <errno.h> +#include <fcntl.h> +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <gio/gio.h> +#include <libcaja-private/caja-clipboard-monitor.h> +#include <libcaja-private/caja-directory-background.h> +#include <libcaja-private/caja-directory.h> +#include <libcaja-private/caja-dnd.h> +#include <libcaja-private/caja-file-utilities.h> +#include <libcaja-private/caja-ui-utilities.h> +#include <libcaja-private/caja-global-preferences.h> +#include <libcaja-private/caja-icon-container.h> +#include <libcaja-private/caja-icon-dnd.h> +#include <libcaja-private/caja-link.h> +#include <libcaja-private/caja-metadata.h> +#include <libcaja-private/caja-view-factory.h> +#include <libcaja-private/caja-clipboard.h> +#include <libcaja-private/caja-desktop-icon-file.h> +#include <locale.h> +#include <signal.h> +#include <stdio.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "caja-audio-mime-types.h" + +#define POPUP_PATH_ICON_APPEARANCE "/selection/Icon Appearance Items" + +enum +{ + PROP_0, + PROP_COMPACT +}; + +typedef struct +{ + const CajaFileSortType sort_type; + const char *metadata_text; + const char *action; + const char *menu_label; + const char *menu_hint; +} SortCriterion; + +typedef enum +{ + MENU_ITEM_TYPE_STANDARD, + MENU_ITEM_TYPE_CHECK, + MENU_ITEM_TYPE_RADIO, + MENU_ITEM_TYPE_TREE +} MenuItemType; + +struct FMIconViewDetails +{ + GList *icons_not_positioned; + + guint react_to_icon_change_idle_id; + + const SortCriterion *sort; + gboolean sort_reversed; + + GtkActionGroup *icon_action_group; + guint icon_merge_id; + + int audio_preview_timeout; + CajaFile *audio_preview_file; + int audio_preview_child_watch; + GPid audio_preview_child_pid; + + gboolean filter_by_screen; + int num_screens; + + gboolean compact; + + gulong clipboard_handler_id; +}; + + +/* Note that the first item in this list is the default sort, + * and that the items show up in the menu in the order they + * appear in this list. + */ +static const SortCriterion sort_criteria[] = +{ + { + CAJA_FILE_SORT_BY_DISPLAY_NAME, + "name", + "Sort by Name", + N_("by _Name"), + N_("Keep icons sorted by name in rows") + }, + { + CAJA_FILE_SORT_BY_SIZE, + "size", + "Sort by Size", + N_("by _Size"), + N_("Keep icons sorted by size in rows") + }, + { + CAJA_FILE_SORT_BY_TYPE, + "type", + "Sort by Type", + N_("by _Type"), + N_("Keep icons sorted by type in rows") + }, + { + CAJA_FILE_SORT_BY_MTIME, + "modification date", + "Sort by Modification Date", + N_("by Modification _Date"), + N_("Keep icons sorted by modification date in rows") + }, + { + CAJA_FILE_SORT_BY_EMBLEMS, + "emblems", + "Sort by Emblems", + N_("by _Emblems"), + N_("Keep icons sorted by emblems in rows") + }, + { + CAJA_FILE_SORT_BY_TRASHED_TIME, + "trashed", + "Sort by Trash Time", + N_("by T_rash Time"), + N_("Keep icons sorted by trash time in rows") + } +}; + +static gboolean default_sort_in_reverse_order = FALSE; +static int preview_sound_auto_value; + +static void fm_icon_view_set_directory_sort_by (FMIconView *icon_view, + CajaFile *file, + const char *sort_by); +static void fm_icon_view_set_zoom_level (FMIconView *view, + CajaZoomLevel new_level, + gboolean always_emit); +static void fm_icon_view_update_click_mode (FMIconView *icon_view); +static void fm_icon_view_set_directory_tighter_layout (FMIconView *icon_view, + CajaFile *file, + gboolean tighter_layout); +static gboolean fm_icon_view_supports_manual_layout (FMIconView *icon_view); +static gboolean fm_icon_view_supports_scaling (FMIconView *icon_view); +static void fm_icon_view_reveal_selection (FMDirectoryView *view); +static const SortCriterion *get_sort_criterion_by_sort_type (CajaFileSortType sort_type); +static void set_sort_criterion_by_sort_type (FMIconView *icon_view, + CajaFileSortType sort_type); +static gboolean set_sort_reversed (FMIconView *icon_view, + gboolean new_value); +static void switch_to_manual_layout (FMIconView *view); +static void preview_audio (FMIconView *icon_view, + CajaFile *file, + gboolean start_flag); +static void update_layout_menus (FMIconView *view); +static CajaFileSortType get_default_sort_order (CajaFile *file, + gboolean *reversed); + + +static void fm_icon_view_iface_init (CajaViewIface *iface); + +G_DEFINE_TYPE_WITH_CODE (FMIconView, fm_icon_view, FM_TYPE_DIRECTORY_VIEW, + G_IMPLEMENT_INTERFACE (CAJA_TYPE_VIEW, + fm_icon_view_iface_init)); + +static void +fm_icon_view_destroy (GtkObject *object) +{ + FMIconView *icon_view; + + icon_view = FM_ICON_VIEW (object); + + if (icon_view->details->react_to_icon_change_idle_id != 0) + { + g_source_remove (icon_view->details->react_to_icon_change_idle_id); + icon_view->details->react_to_icon_change_idle_id = 0; + } + + if (icon_view->details->clipboard_handler_id != 0) + { + g_signal_handler_disconnect (caja_clipboard_monitor_get (), + icon_view->details->clipboard_handler_id); + icon_view->details->clipboard_handler_id = 0; + } + + /* kill any sound preview process that is ongoing */ + preview_audio (icon_view, NULL, FALSE); + + if (icon_view->details->icons_not_positioned) + { + caja_file_list_free (icon_view->details->icons_not_positioned); + icon_view->details->icons_not_positioned = NULL; + } + + GTK_OBJECT_CLASS (fm_icon_view_parent_class)->destroy (object); +} + + +static void +fm_icon_view_finalize (GObject *object) +{ + FMIconView *icon_view; + + icon_view = FM_ICON_VIEW (object); + + g_free (icon_view->details); + + G_OBJECT_CLASS (fm_icon_view_parent_class)->finalize (object); +} + +static CajaIconContainer * +get_icon_container (FMIconView *icon_view) +{ + return CAJA_ICON_CONTAINER (gtk_bin_get_child (GTK_BIN (icon_view))); +} + +static gboolean +get_stored_icon_position_callback (CajaIconContainer *container, + CajaFile *file, + CajaIconPosition *position, + FMIconView *icon_view) +{ + char *position_string, *scale_string; + gboolean position_good; + char c; + + g_assert (CAJA_IS_ICON_CONTAINER (container)); + g_assert (CAJA_IS_FILE (file)); + g_assert (position != NULL); + g_assert (FM_IS_ICON_VIEW (icon_view)); + + if (!fm_icon_view_supports_manual_layout (icon_view)) + { + return FALSE; + } + + /* Get the current position of this icon from the metadata. */ + position_string = caja_file_get_metadata + (file, CAJA_METADATA_KEY_ICON_POSITION, ""); + position_good = sscanf + (position_string, " %d , %d %c", + &position->x, &position->y, &c) == 2; + g_free (position_string); + + /* If it is the desktop directory, maybe the mate-libs metadata has information about it */ + + /* Disable scaling if not on the desktop */ + if (fm_icon_view_supports_scaling (icon_view)) + { + /* Get the scale of the icon from the metadata. */ + scale_string = caja_file_get_metadata + (file, CAJA_METADATA_KEY_ICON_SCALE, "1"); + position->scale = g_ascii_strtod (scale_string, NULL); + if (errno != 0) + { + position->scale = 1.0; + } + + g_free (scale_string); + } + else + { + position->scale = 1.0; + } + + return position_good; +} + +static void +real_set_sort_criterion (FMIconView *icon_view, + const SortCriterion *sort, + gboolean clear) +{ + CajaFile *file; + + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (icon_view)); + + if (clear) + { + caja_file_set_metadata (file, + CAJA_METADATA_KEY_ICON_VIEW_SORT_BY, NULL, NULL); + caja_file_set_metadata (file, + CAJA_METADATA_KEY_ICON_VIEW_SORT_REVERSED, NULL, NULL); + icon_view->details->sort = + get_sort_criterion_by_sort_type (get_default_sort_order + (file, &icon_view->details->sort_reversed)); + } + else + { + /* Store the new sort setting. */ + fm_icon_view_set_directory_sort_by (icon_view, + file, + sort->metadata_text); + } + + /* Update the layout menus to match the new sort setting. */ + update_layout_menus (icon_view); +} + +static void +set_sort_criterion (FMIconView *icon_view, const SortCriterion *sort) +{ + if (sort == NULL || + icon_view->details->sort == sort) + { + return; + } + + icon_view->details->sort = sort; + + real_set_sort_criterion (icon_view, sort, FALSE); +} + +static void +clear_sort_criterion (FMIconView *icon_view) +{ + real_set_sort_criterion (icon_view, NULL, TRUE); +} + +static void +action_stretch_callback (GtkAction *action, + gpointer callback_data) +{ + g_assert (FM_IS_ICON_VIEW (callback_data)); + + caja_icon_container_show_stretch_handles + (get_icon_container (FM_ICON_VIEW (callback_data))); +} + +static void +action_unstretch_callback (GtkAction *action, + gpointer callback_data) +{ + g_assert (FM_IS_ICON_VIEW (callback_data)); + + caja_icon_container_unstretch + (get_icon_container (FM_ICON_VIEW (callback_data))); +} + +static void +fm_icon_view_clean_up (FMIconView *icon_view) +{ + EEL_CALL_METHOD (FM_ICON_VIEW_CLASS, icon_view, clean_up, (icon_view)); +} + +static void +fm_icon_view_real_clean_up (FMIconView *icon_view) +{ + CajaIconContainer *icon_container; + gboolean saved_sort_reversed; + + icon_container = get_icon_container (icon_view); + + /* Hardwire Clean Up to always be by name, in forward order */ + saved_sort_reversed = icon_view->details->sort_reversed; + + set_sort_reversed (icon_view, FALSE); + set_sort_criterion (icon_view, &sort_criteria[0]); + + caja_icon_container_sort (icon_container); + caja_icon_container_freeze_icon_positions (icon_container); + + set_sort_reversed (icon_view, saved_sort_reversed); +} + +static void +action_clean_up_callback (GtkAction *action, gpointer callback_data) +{ + fm_icon_view_clean_up (FM_ICON_VIEW (callback_data)); +} + +static void +set_tighter_layout (FMIconView *icon_view, gboolean new_value) +{ + fm_icon_view_set_directory_tighter_layout (icon_view, + fm_directory_view_get_directory_as_file + (FM_DIRECTORY_VIEW (icon_view)), + new_value); + caja_icon_container_set_tighter_layout (get_icon_container (icon_view), + new_value); +} + +static void +action_tighter_layout_callback (GtkAction *action, + gpointer user_data) +{ + g_assert (FM_IS_ICON_VIEW (user_data)); + + set_tighter_layout (FM_ICON_VIEW (user_data), + gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))); +} + + +static gboolean +fm_icon_view_using_auto_layout (FMIconView *icon_view) +{ + return caja_icon_container_is_auto_layout + (get_icon_container (icon_view)); +} + +static gboolean +fm_icon_view_using_tighter_layout (FMIconView *icon_view) +{ + return caja_icon_container_is_tighter_layout + (get_icon_container (icon_view)); +} + +static void +action_sort_radio_callback (GtkAction *action, + GtkRadioAction *current, + FMIconView *view) +{ + CajaFileSortType sort_type; + + sort_type = gtk_radio_action_get_current_value (current); + + /* Note that id might be a toggle item. + * Ignore non-sort ids so that they don't cause sorting. + */ + if (sort_type == CAJA_FILE_SORT_NONE) + { + switch_to_manual_layout (view); + } + else + { + set_sort_criterion_by_sort_type (view, sort_type); + } +} + +static void +list_covers (CajaIconData *data, gpointer callback_data) +{ + GSList **file_list; + + file_list = callback_data; + + *file_list = g_slist_prepend (*file_list, data); +} + +static void +unref_cover (CajaIconData *data, gpointer callback_data) +{ + caja_file_unref (CAJA_FILE (data)); +} + +static void +fm_icon_view_clear (FMDirectoryView *view) +{ + CajaIconContainer *icon_container; + GSList *file_list; + + g_return_if_fail (FM_IS_ICON_VIEW (view)); + + icon_container = get_icon_container (FM_ICON_VIEW (view)); + if (!icon_container) + return; + + /* Clear away the existing icons. */ + file_list = NULL; + caja_icon_container_for_each (icon_container, list_covers, &file_list); + caja_icon_container_clear (icon_container); + g_slist_foreach (file_list, (GFunc)unref_cover, NULL); + g_slist_free (file_list); +} + + +static gboolean +should_show_file_on_screen (FMDirectoryView *view, CajaFile *file) +{ + char *screen_string; + int screen_num; + FMIconView *icon_view; + GdkScreen *screen; + + icon_view = FM_ICON_VIEW (view); + + if (!fm_directory_view_should_show_file (view, file)) + { + return FALSE; + } + + /* Get the screen for this icon from the metadata. */ + screen_string = caja_file_get_metadata + (file, CAJA_METADATA_KEY_SCREEN, "0"); + screen_num = atoi (screen_string); + g_free (screen_string); + screen = gtk_widget_get_screen (GTK_WIDGET (view)); + + if (screen_num != gdk_screen_get_number (screen) && + (screen_num < icon_view->details->num_screens || + gdk_screen_get_number (screen) > 0)) + { + return FALSE; + } + + return TRUE; +} + +static void +fm_icon_view_remove_file (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory) +{ + FMIconView *icon_view; + + /* This used to assert that 'directory == fm_directory_view_get_model (view)', but that + * resulted in a lot of crash reports (bug #352592). I don't see how that trace happens. + * It seems that somehow we get a files_changed event sent to the view from a directory + * that isn't the model, but the code disables the monitor and signal callback handlers when + * changing directories. Maybe we can get some more information when this happens. + * Further discussion in bug #368178. + */ + if (directory != fm_directory_view_get_model (view)) + { + char *file_uri, *dir_uri, *model_uri; + file_uri = caja_file_get_uri (file); + dir_uri = caja_directory_get_uri (directory); + model_uri = caja_directory_get_uri (fm_directory_view_get_model (view)); + g_warning ("fm_icon_view_remove_file() - directory not icon view model, shouldn't happen.\n" + "file: %p:%s, dir: %p:%s, model: %p:%s, view loading: %d\n" + "If you see this, please add this info to http://bugzilla.gnome.org/show_bug.cgi?id=368178", + file, file_uri, directory, dir_uri, fm_directory_view_get_model (view), model_uri, fm_directory_view_get_loading (view)); + g_free (file_uri); + g_free (dir_uri); + g_free (model_uri); + } + + icon_view = FM_ICON_VIEW (view); + + if (caja_icon_container_remove (get_icon_container (icon_view), + CAJA_ICON_CONTAINER_ICON_DATA (file))) + { + if (file == icon_view->details->audio_preview_file) + { + preview_audio (icon_view, NULL, FALSE); + } + + caja_file_unref (file); + } +} + +static void +fm_icon_view_add_file (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory) +{ + FMIconView *icon_view; + CajaIconContainer *icon_container; + + g_assert (directory == fm_directory_view_get_model (view)); + + icon_view = FM_ICON_VIEW (view); + icon_container = get_icon_container (icon_view); + + if (icon_view->details->filter_by_screen && + !should_show_file_on_screen (view, file)) + { + return; + } + + /* Reset scroll region for the first icon added when loading a directory. */ + if (fm_directory_view_get_loading (view) && caja_icon_container_is_empty (icon_container)) + { + caja_icon_container_reset_scroll_region (icon_container); + } + + if (caja_icon_container_add (icon_container, + CAJA_ICON_CONTAINER_ICON_DATA (file))) + { + caja_file_ref (file); + } +} + +static void +fm_icon_view_flush_added_files (FMDirectoryView *view) +{ + caja_icon_container_layout_now (get_icon_container (FM_ICON_VIEW (view))); +} + +static void +fm_icon_view_file_changed (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory) +{ + FMIconView *icon_view; + + g_assert (directory == fm_directory_view_get_model (view)); + + g_return_if_fail (view != NULL); + icon_view = FM_ICON_VIEW (view); + + if (!icon_view->details->filter_by_screen) + { + caja_icon_container_request_update + (get_icon_container (icon_view), + CAJA_ICON_CONTAINER_ICON_DATA (file)); + return; + } + + if (!should_show_file_on_screen (view, file)) + { + fm_icon_view_remove_file (view, file, directory); + } + else + { + + caja_icon_container_request_update + (get_icon_container (icon_view), + CAJA_ICON_CONTAINER_ICON_DATA (file)); + } +} + +static gboolean +fm_icon_view_supports_auto_layout (FMIconView *view) +{ + g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_ICON_VIEW_CLASS, view, + supports_auto_layout, (view)); +} + +static gboolean +fm_icon_view_supports_scaling (FMIconView *view) +{ + g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_ICON_VIEW_CLASS, view, + supports_scaling, (view)); +} + +static gboolean +fm_icon_view_supports_manual_layout (FMIconView *view) +{ + g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_ICON_VIEW_CLASS, view, + supports_manual_layout, (view)); +} + +static gboolean +fm_icon_view_supports_keep_aligned (FMIconView *view) +{ + g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_ICON_VIEW_CLASS, view, + supports_keep_aligned, (view)); +} + +static gboolean +fm_icon_view_supports_labels_beside_icons (FMIconView *view) +{ + g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_ICON_VIEW_CLASS, view, + supports_labels_beside_icons, (view)); +} + +static gboolean +fm_icon_view_supports_tighter_layout (FMIconView *view) +{ + return !fm_icon_view_is_compact (view); +} + +static void +update_layout_menus (FMIconView *view) +{ + gboolean is_auto_layout; + GtkAction *action; + const char *action_name; + CajaFile *file; + + if (view->details->icon_action_group == NULL) + { + return; + } + + is_auto_layout = fm_icon_view_using_auto_layout (view); + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (view)); + + if (fm_icon_view_supports_auto_layout (view)) + { + /* Mark sort criterion. */ + action_name = is_auto_layout ? view->details->sort->action : FM_ACTION_MANUAL_LAYOUT; + action = gtk_action_group_get_action (view->details->icon_action_group, + action_name); + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE); + + action = gtk_action_group_get_action (view->details->icon_action_group, + FM_ACTION_TIGHTER_LAYOUT); + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), + fm_icon_view_using_tighter_layout (view)); + gtk_action_set_sensitive (action, fm_icon_view_supports_tighter_layout (view)); + gtk_action_set_visible (action, fm_icon_view_supports_tighter_layout (view)); + + action = gtk_action_group_get_action (view->details->icon_action_group, + FM_ACTION_REVERSED_ORDER); + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), + view->details->sort_reversed); + gtk_action_set_sensitive (action, is_auto_layout); + + action = gtk_action_group_get_action (view->details->icon_action_group, + FM_ACTION_SORT_TRASH_TIME); + + if (file != NULL && caja_file_is_in_trash (file)) + { + gtk_action_set_visible (action, TRUE); + } + else + { + gtk_action_set_visible (action, FALSE); + } + } + + action = gtk_action_group_get_action (view->details->icon_action_group, + FM_ACTION_MANUAL_LAYOUT); + gtk_action_set_visible (action, + fm_icon_view_supports_manual_layout (view)); + + /* Clean Up is only relevant for manual layout */ + action = gtk_action_group_get_action (view->details->icon_action_group, + FM_ACTION_CLEAN_UP); + gtk_action_set_sensitive (action, !is_auto_layout); + + if (FM_IS_DESKTOP_ICON_VIEW (view)) + { + gtk_action_set_label (action, _("_Organize Desktop by Name")); + } + + action = gtk_action_group_get_action (view->details->icon_action_group, + FM_ACTION_KEEP_ALIGNED); + gtk_action_set_visible (action, + fm_icon_view_supports_keep_aligned (view)); + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), + caja_icon_container_is_keep_aligned (get_icon_container (view))); + gtk_action_set_sensitive (action, !is_auto_layout); +} + + +static char * +fm_icon_view_get_directory_sort_by (FMIconView *icon_view, + CajaFile *file) +{ + if (!fm_icon_view_supports_auto_layout (icon_view)) + { + return g_strdup ("name"); + } + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_ICON_VIEW_CLASS, icon_view, + get_directory_sort_by, (icon_view, file)); +} + +static CajaFileSortType default_sort_order = CAJA_FILE_SORT_BY_DISPLAY_NAME; + +static CajaFileSortType +get_default_sort_order (CajaFile *file, gboolean *reversed) +{ + static gboolean auto_storaged_added = FALSE; + CajaFileSortType retval; + + if (auto_storaged_added == FALSE) + { + auto_storaged_added = TRUE; + eel_preferences_add_auto_enum (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_SORT_ORDER, + (int *) &default_sort_order); + eel_preferences_add_auto_boolean (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_SORT_IN_REVERSE_ORDER, + &default_sort_in_reverse_order); + + } + + retval = caja_file_get_default_sort_type (file, reversed); + + if (retval == CAJA_FILE_SORT_NONE) + { + + if (reversed != NULL) + { + *reversed = default_sort_in_reverse_order; + } + + retval = CLAMP (default_sort_order, CAJA_FILE_SORT_BY_DISPLAY_NAME, + CAJA_FILE_SORT_BY_EMBLEMS); + } + + return retval; +} + +static char * +fm_icon_view_real_get_directory_sort_by (FMIconView *icon_view, + CajaFile *file) +{ + const SortCriterion *default_sort_criterion; + default_sort_criterion = get_sort_criterion_by_sort_type (get_default_sort_order (file, NULL)); + g_return_val_if_fail (default_sort_criterion != NULL, NULL); + + return caja_file_get_metadata + (file, CAJA_METADATA_KEY_ICON_VIEW_SORT_BY, + default_sort_criterion->metadata_text); +} + +static void +fm_icon_view_set_directory_sort_by (FMIconView *icon_view, + CajaFile *file, + const char *sort_by) +{ + if (!fm_icon_view_supports_auto_layout (icon_view)) + { + return; + } + + EEL_CALL_METHOD (FM_ICON_VIEW_CLASS, icon_view, + set_directory_sort_by, (icon_view, file, sort_by)); +} + +static void +fm_icon_view_real_set_directory_sort_by (FMIconView *icon_view, + CajaFile *file, + const char *sort_by) +{ + const SortCriterion *default_sort_criterion; + default_sort_criterion = get_sort_criterion_by_sort_type (get_default_sort_order (file, NULL)); + g_return_if_fail (default_sort_criterion != NULL); + + caja_file_set_metadata + (file, CAJA_METADATA_KEY_ICON_VIEW_SORT_BY, + default_sort_criterion->metadata_text, + sort_by); +} + +static gboolean +fm_icon_view_get_directory_sort_reversed (FMIconView *icon_view, + CajaFile *file) +{ + if (!fm_icon_view_supports_auto_layout (icon_view)) + { + return FALSE; + } + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_ICON_VIEW_CLASS, icon_view, + get_directory_sort_reversed, (icon_view, file)); +} + +static gboolean +fm_icon_view_real_get_directory_sort_reversed (FMIconView *icon_view, + CajaFile *file) +{ + gboolean reversed; + + get_default_sort_order (file, &reversed); + return caja_file_get_boolean_metadata + (file, + CAJA_METADATA_KEY_ICON_VIEW_SORT_REVERSED, + reversed); +} + +static void +fm_icon_view_set_directory_sort_reversed (FMIconView *icon_view, + CajaFile *file, + gboolean sort_reversed) +{ + if (!fm_icon_view_supports_auto_layout (icon_view)) + { + return; + } + + EEL_CALL_METHOD (FM_ICON_VIEW_CLASS, icon_view, + set_directory_sort_reversed, + (icon_view, file, sort_reversed)); +} + +static void +fm_icon_view_real_set_directory_sort_reversed (FMIconView *icon_view, + CajaFile *file, + gboolean sort_reversed) +{ + gboolean reversed; + + get_default_sort_order (file, &reversed); + caja_file_set_boolean_metadata + (file, + CAJA_METADATA_KEY_ICON_VIEW_SORT_REVERSED, + reversed, sort_reversed); +} + +static gboolean +get_default_directory_keep_aligned (void) +{ + return TRUE; +} + +static gboolean +fm_icon_view_get_directory_keep_aligned (FMIconView *icon_view, + CajaFile *file) +{ + if (!fm_icon_view_supports_keep_aligned (icon_view)) + { + return FALSE; + } + + return caja_file_get_boolean_metadata + (file, + CAJA_METADATA_KEY_ICON_VIEW_KEEP_ALIGNED, + get_default_directory_keep_aligned ()); +} + +static void +fm_icon_view_set_directory_keep_aligned (FMIconView *icon_view, + CajaFile *file, + gboolean keep_aligned) +{ + if (!fm_icon_view_supports_keep_aligned (icon_view)) + { + return; + } + + caja_file_set_boolean_metadata + (file, CAJA_METADATA_KEY_ICON_VIEW_KEEP_ALIGNED, + get_default_directory_keep_aligned (), + keep_aligned); +} + +/* maintainence of auto layout boolean */ +static gboolean default_directory_manual_layout = FALSE; + +static gboolean +get_default_directory_manual_layout (void) +{ + static gboolean auto_storaged_added = FALSE; + + if (auto_storaged_added == FALSE) + { + auto_storaged_added = TRUE; + eel_preferences_add_auto_boolean (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_USE_MANUAL_LAYOUT, + &default_directory_manual_layout); + } + + return default_directory_manual_layout; +} + +static gboolean +fm_icon_view_get_directory_auto_layout (FMIconView *icon_view, + CajaFile *file) +{ + if (!fm_icon_view_supports_auto_layout (icon_view)) + { + return FALSE; + } + + if (!fm_icon_view_supports_manual_layout (icon_view)) + { + return TRUE; + } + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_ICON_VIEW_CLASS, icon_view, + get_directory_auto_layout, (icon_view, file)); +} + +static gboolean +fm_icon_view_real_get_directory_auto_layout (FMIconView *icon_view, + CajaFile *file) +{ + + + return caja_file_get_boolean_metadata + (file, CAJA_METADATA_KEY_ICON_VIEW_AUTO_LAYOUT, !get_default_directory_manual_layout ()); +} + +static void +fm_icon_view_set_directory_auto_layout (FMIconView *icon_view, + CajaFile *file, + gboolean auto_layout) +{ + if (!fm_icon_view_supports_auto_layout (icon_view) || + !fm_icon_view_supports_manual_layout (icon_view)) + { + return; + } + + EEL_CALL_METHOD (FM_ICON_VIEW_CLASS, icon_view, + set_directory_auto_layout, (icon_view, file, auto_layout)); +} + +static void +fm_icon_view_real_set_directory_auto_layout (FMIconView *icon_view, + CajaFile *file, + gboolean auto_layout) +{ + if (!fm_icon_view_supports_manual_layout (icon_view)) + { + return; + } + + caja_file_set_boolean_metadata + (file, CAJA_METADATA_KEY_ICON_VIEW_AUTO_LAYOUT, + !get_default_directory_manual_layout (), + auto_layout); +} +/* maintainence of tighter layout boolean */ + +static gboolean +fm_icon_view_get_directory_tighter_layout (FMIconView *icon_view, + CajaFile *file) +{ + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_ICON_VIEW_CLASS, icon_view, + get_directory_tighter_layout, (icon_view, file)); +} + +static gboolean default_directory_tighter_layout = FALSE; + +static gboolean +get_default_directory_tighter_layout (void) +{ + static gboolean auto_storaged_added = FALSE; + + if (auto_storaged_added == FALSE) + { + auto_storaged_added = TRUE; + eel_preferences_add_auto_boolean (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_USE_TIGHTER_LAYOUT, + &default_directory_tighter_layout); + } + + return default_directory_tighter_layout; +} + +static gboolean +fm_icon_view_real_get_directory_tighter_layout (FMIconView *icon_view, + CajaFile *file) +{ + if (!fm_icon_view_supports_tighter_layout (icon_view)) + { + return FALSE; + } + + return caja_file_get_boolean_metadata + (file, + CAJA_METADATA_KEY_ICON_VIEW_TIGHTER_LAYOUT, + get_default_directory_tighter_layout ()); +} + +static void +fm_icon_view_set_directory_tighter_layout (FMIconView *icon_view, + CajaFile *file, + gboolean tighter_layout) +{ + EEL_CALL_METHOD (FM_ICON_VIEW_CLASS, icon_view, + set_directory_tighter_layout, (icon_view, file, tighter_layout)); +} + +static void +fm_icon_view_real_set_directory_tighter_layout (FMIconView *icon_view, + CajaFile *file, + gboolean tighter_layout) +{ + if (!fm_icon_view_supports_tighter_layout (icon_view)) + { + return; + } + + caja_file_set_boolean_metadata + (file, CAJA_METADATA_KEY_ICON_VIEW_TIGHTER_LAYOUT, + get_default_directory_tighter_layout (), + tighter_layout); +} + +static gboolean +real_supports_auto_layout (FMIconView *view) +{ + g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE); + + return TRUE; +} + +static gboolean +real_supports_scaling (FMIconView *view) +{ + g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE); + + return FALSE; +} + +static gboolean +real_supports_manual_layout (FMIconView *view) +{ + g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE); + + return !fm_icon_view_is_compact (view); +} + +static gboolean +real_supports_keep_aligned (FMIconView *view) +{ + g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE); + + return FALSE; +} + +static gboolean +real_supports_labels_beside_icons (FMIconView *view) +{ + g_return_val_if_fail (FM_IS_ICON_VIEW (view), TRUE); + + return TRUE; +} + +static gboolean +set_sort_reversed (FMIconView *icon_view, gboolean new_value) +{ + if (icon_view->details->sort_reversed == new_value) + { + return FALSE; + } + icon_view->details->sort_reversed = new_value; + + /* Store the new sort setting. */ + fm_icon_view_set_directory_sort_reversed (icon_view, fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (icon_view)), new_value); + + /* Update the layout menus to match the new sort-order setting. */ + update_layout_menus (icon_view); + + return TRUE; +} + +static const SortCriterion * +get_sort_criterion_by_metadata_text (const char *metadata_text) +{ + guint i; + + /* Figure out what the new sort setting should be. */ + for (i = 0; i < G_N_ELEMENTS (sort_criteria); i++) + { + if (strcmp (sort_criteria[i].metadata_text, metadata_text) == 0) + { + return &sort_criteria[i]; + } + } + return NULL; +} + +static const SortCriterion * +get_sort_criterion_by_sort_type (CajaFileSortType sort_type) +{ + guint i; + + /* Figure out what the new sort setting should be. */ + for (i = 0; i < G_N_ELEMENTS (sort_criteria); i++) + { + if (sort_type == sort_criteria[i].sort_type) + { + return &sort_criteria[i]; + } + } + + return NULL; +} + +static CajaZoomLevel default_zoom_level = CAJA_ZOOM_LEVEL_STANDARD; +static CajaZoomLevel default_compact_zoom_level = CAJA_ZOOM_LEVEL_STANDARD; +#define DEFAULT_ZOOM_LEVEL(icon_view) icon_view->details->compact ? default_compact_zoom_level : default_zoom_level + +static CajaZoomLevel +get_default_zoom_level (FMIconView *icon_view) +{ + static gboolean auto_storage_added = FALSE; + + if (!auto_storage_added) + { + auto_storage_added = TRUE; + eel_preferences_add_auto_enum (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_ZOOM_LEVEL, + (int *) &default_zoom_level); + eel_preferences_add_auto_enum (CAJA_PREFERENCES_COMPACT_VIEW_DEFAULT_ZOOM_LEVEL, + (int *) &default_compact_zoom_level); + } + + return CLAMP (DEFAULT_ZOOM_LEVEL(icon_view), CAJA_ZOOM_LEVEL_SMALLEST, CAJA_ZOOM_LEVEL_LARGEST); +} + +static void +set_labels_beside_icons (FMIconView *icon_view) +{ + gboolean labels_beside; + + if (fm_icon_view_supports_labels_beside_icons (icon_view)) + { + labels_beside = fm_icon_view_is_compact (icon_view) || + eel_preferences_get_boolean (CAJA_PREFERENCES_ICON_VIEW_LABELS_BESIDE_ICONS); + + if (labels_beside) + { + caja_icon_container_set_label_position + (get_icon_container (icon_view), + CAJA_ICON_LABEL_POSITION_BESIDE); + } + else + { + caja_icon_container_set_label_position + (get_icon_container (icon_view), + CAJA_ICON_LABEL_POSITION_UNDER); + } + } +} + +static void +set_columns_same_width (FMIconView *icon_view) +{ + gboolean all_columns_same_width; + + if (fm_icon_view_is_compact (icon_view)) + { + all_columns_same_width = eel_preferences_get_boolean (CAJA_PREFERENCES_COMPACT_VIEW_ALL_COLUMNS_SAME_WIDTH); + caja_icon_container_set_all_columns_same_width (get_icon_container (icon_view), all_columns_same_width); + } +} + +static void +fm_icon_view_begin_loading (FMDirectoryView *view) +{ + FMIconView *icon_view; + GtkWidget *icon_container; + CajaFile *file; + int level; + char *sort_name; + + g_return_if_fail (FM_IS_ICON_VIEW (view)); + + icon_view = FM_ICON_VIEW (view); + file = fm_directory_view_get_directory_as_file (view); + icon_container = GTK_WIDGET (get_icon_container (icon_view)); + + caja_icon_container_begin_loading (CAJA_ICON_CONTAINER (icon_container)); + + caja_icon_container_set_allow_moves (CAJA_ICON_CONTAINER (icon_container), + fm_directory_view_get_allow_moves (view)); + + /* kill any sound preview process that is ongoing */ + preview_audio (icon_view, NULL, FALSE); + + /* FIXME bugzilla.gnome.org 45060: Should use methods instead + * of hardcoding desktop knowledge in here. + */ + if (FM_IS_DESKTOP_ICON_VIEW (view)) + { + caja_connect_desktop_background_to_file_metadata (CAJA_ICON_CONTAINER (icon_container), file); + } + else + { + GdkDragAction default_action; + + if (caja_window_info_get_window_type (fm_directory_view_get_caja_window (view)) == CAJA_WINDOW_NAVIGATION) + { + default_action = CAJA_DND_ACTION_SET_AS_GLOBAL_BACKGROUND; + } + else + { + default_action = CAJA_DND_ACTION_SET_AS_FOLDER_BACKGROUND; + } + + caja_connect_background_to_file_metadata + (icon_container, + file, + default_action); + } + + + /* Set up the zoom level from the metadata. */ + if (fm_directory_view_supports_zooming (FM_DIRECTORY_VIEW (icon_view))) + { + if (icon_view->details->compact) + { + level = caja_file_get_integer_metadata + (file, + CAJA_METADATA_KEY_COMPACT_VIEW_ZOOM_LEVEL, + get_default_zoom_level (icon_view)); + } + else + { + level = caja_file_get_integer_metadata + (file, + CAJA_METADATA_KEY_ICON_VIEW_ZOOM_LEVEL, + get_default_zoom_level (icon_view)); + } + + fm_icon_view_set_zoom_level (icon_view, level, TRUE); + } + + /* Set the sort mode. + * It's OK not to resort the icons because the + * container doesn't have any icons at this point. + */ + sort_name = fm_icon_view_get_directory_sort_by (icon_view, file); + set_sort_criterion (icon_view, get_sort_criterion_by_metadata_text (sort_name)); + g_free (sort_name); + + /* Set the sort direction from the metadata. */ + set_sort_reversed (icon_view, fm_icon_view_get_directory_sort_reversed (icon_view, file)); + + caja_icon_container_set_keep_aligned + (get_icon_container (icon_view), + fm_icon_view_get_directory_keep_aligned (icon_view, file)); + caja_icon_container_set_tighter_layout + (get_icon_container (icon_view), + fm_icon_view_get_directory_tighter_layout (icon_view, file)); + + set_labels_beside_icons (icon_view); + set_columns_same_width (icon_view); + + /* We must set auto-layout last, because it invokes the layout_changed + * callback, which works incorrectly if the other layout criteria are + * not already set up properly (see bug 6500, e.g.) + */ + caja_icon_container_set_auto_layout + (get_icon_container (icon_view), + fm_icon_view_get_directory_auto_layout (icon_view, file)); + + /* e.g. keep aligned may have changed */ + update_layout_menus (icon_view); +} + +static void +icon_view_notify_clipboard_info (CajaClipboardMonitor *monitor, + CajaClipboardInfo *info, + FMIconView *icon_view) +{ + GList *icon_data; + + icon_data = NULL; + if (info && info->cut) + { + icon_data = info->files; + } + + caja_icon_container_set_highlighted_for_clipboard ( + get_icon_container (icon_view), icon_data); +} + +static void +fm_icon_view_end_loading (FMDirectoryView *view, + gboolean all_files_seen) +{ + FMIconView *icon_view; + GtkWidget *icon_container; + CajaClipboardMonitor *monitor; + CajaClipboardInfo *info; + + icon_view = FM_ICON_VIEW (view); + + icon_container = GTK_WIDGET (get_icon_container (icon_view)); + caja_icon_container_end_loading (CAJA_ICON_CONTAINER (icon_container), all_files_seen); + + monitor = caja_clipboard_monitor_get (); + info = caja_clipboard_monitor_get_clipboard_info (monitor); + + icon_view_notify_clipboard_info (monitor, info, icon_view); +} + +static CajaZoomLevel +fm_icon_view_get_zoom_level (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_ICON_VIEW (view), CAJA_ZOOM_LEVEL_STANDARD); + + return caja_icon_container_get_zoom_level (get_icon_container (FM_ICON_VIEW (view))); +} + +static void +fm_icon_view_set_zoom_level (FMIconView *view, + CajaZoomLevel new_level, + gboolean always_emit) +{ + CajaIconContainer *icon_container; + + g_return_if_fail (FM_IS_ICON_VIEW (view)); + g_return_if_fail (new_level >= CAJA_ZOOM_LEVEL_SMALLEST && + new_level <= CAJA_ZOOM_LEVEL_LARGEST); + + icon_container = get_icon_container (view); + if (caja_icon_container_get_zoom_level (icon_container) == new_level) + { + if (always_emit) + { + g_signal_emit_by_name (view, "zoom_level_changed"); + } + return; + } + + if (view->details->compact) + { + caja_file_set_integer_metadata + (fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (view)), + CAJA_METADATA_KEY_COMPACT_VIEW_ZOOM_LEVEL, + get_default_zoom_level (view), + new_level); + } + else + { + caja_file_set_integer_metadata + (fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (view)), + CAJA_METADATA_KEY_ICON_VIEW_ZOOM_LEVEL, + get_default_zoom_level (view), + new_level); + } + + caja_icon_container_set_zoom_level (icon_container, new_level); + + g_signal_emit_by_name (view, "zoom_level_changed"); + + if (fm_directory_view_get_active (FM_DIRECTORY_VIEW (view))) + { + fm_directory_view_update_menus (FM_DIRECTORY_VIEW (view)); + } +} + +static void +fm_icon_view_bump_zoom_level (FMDirectoryView *view, int zoom_increment) +{ + FMIconView *icon_view; + CajaZoomLevel new_level; + + g_return_if_fail (FM_IS_ICON_VIEW (view)); + + icon_view = FM_ICON_VIEW (view); + new_level = fm_icon_view_get_zoom_level (view) + zoom_increment; + + if (new_level >= CAJA_ZOOM_LEVEL_SMALLEST && + new_level <= CAJA_ZOOM_LEVEL_LARGEST) + { + fm_directory_view_zoom_to_level (view, new_level); + } +} + +static void +fm_icon_view_zoom_to_level (FMDirectoryView *view, + CajaZoomLevel zoom_level) +{ + FMIconView *icon_view; + + g_assert (FM_IS_ICON_VIEW (view)); + + icon_view = FM_ICON_VIEW (view); + fm_icon_view_set_zoom_level (icon_view, zoom_level, FALSE); +} + +static void +fm_icon_view_restore_default_zoom_level (FMDirectoryView *view) +{ + FMIconView *icon_view; + + g_return_if_fail (FM_IS_ICON_VIEW (view)); + + icon_view = FM_ICON_VIEW (view); + fm_directory_view_zoom_to_level + (view, get_default_zoom_level (icon_view)); +} + +static gboolean +fm_icon_view_can_zoom_in (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE); + + return fm_icon_view_get_zoom_level (view) + < CAJA_ZOOM_LEVEL_LARGEST; +} + +static gboolean +fm_icon_view_can_zoom_out (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE); + + return fm_icon_view_get_zoom_level (view) + > CAJA_ZOOM_LEVEL_SMALLEST; +} + +static GtkWidget * +fm_icon_view_get_background_widget (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_ICON_VIEW (view), NULL); + + return GTK_WIDGET (get_icon_container (FM_ICON_VIEW (view))); +} + +static gboolean +fm_icon_view_is_empty (FMDirectoryView *view) +{ + g_assert (FM_IS_ICON_VIEW (view)); + + return caja_icon_container_is_empty + (get_icon_container (FM_ICON_VIEW (view))); +} + +static GList * +fm_icon_view_get_selection (FMDirectoryView *view) +{ + GList *list; + + g_return_val_if_fail (FM_IS_ICON_VIEW (view), NULL); + + list = caja_icon_container_get_selection + (get_icon_container (FM_ICON_VIEW (view))); + caja_file_list_ref (list); + return list; +} + +static void +count_item (CajaIconData *icon_data, + gpointer callback_data) +{ + guint *count; + + count = callback_data; + (*count)++; +} + +static guint +fm_icon_view_get_item_count (FMDirectoryView *view) +{ + guint count; + + g_return_val_if_fail (FM_IS_ICON_VIEW (view), 0); + + count = 0; + + caja_icon_container_for_each + (get_icon_container (FM_ICON_VIEW (view)), + count_item, &count); + + return count; +} + +static void +set_sort_criterion_by_sort_type (FMIconView *icon_view, + CajaFileSortType sort_type) +{ + const SortCriterion *sort; + + g_assert (FM_IS_ICON_VIEW (icon_view)); + + sort = get_sort_criterion_by_sort_type (sort_type); + g_return_if_fail (sort != NULL); + + if (sort == icon_view->details->sort + && fm_icon_view_using_auto_layout (icon_view)) + { + return; + } + + set_sort_criterion (icon_view, sort); + caja_icon_container_sort (get_icon_container (icon_view)); + fm_icon_view_reveal_selection (FM_DIRECTORY_VIEW (icon_view)); +} + + +static void +action_reversed_order_callback (GtkAction *action, + gpointer user_data) +{ + FMIconView *icon_view; + + icon_view = FM_ICON_VIEW (user_data); + + if (set_sort_reversed (icon_view, + gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))) + { + caja_icon_container_sort (get_icon_container (icon_view)); + fm_icon_view_reveal_selection (FM_DIRECTORY_VIEW (icon_view)); + } +} + +static void +action_keep_aligned_callback (GtkAction *action, + gpointer user_data) +{ + FMIconView *icon_view; + CajaFile *file; + gboolean keep_aligned; + + icon_view = FM_ICON_VIEW (user_data); + + keep_aligned = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)); + + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (icon_view)); + fm_icon_view_set_directory_keep_aligned (icon_view, + file, + keep_aligned); + + caja_icon_container_set_keep_aligned (get_icon_container (icon_view), + keep_aligned); +} + +static void +switch_to_manual_layout (FMIconView *icon_view) +{ + if (!fm_icon_view_using_auto_layout (icon_view)) + { + return; + } + + icon_view->details->sort = &sort_criteria[0]; + + caja_icon_container_set_auto_layout + (get_icon_container (icon_view), FALSE); +} + +static void +layout_changed_callback (CajaIconContainer *container, + FMIconView *icon_view) +{ + CajaFile *file; + + g_assert (FM_IS_ICON_VIEW (icon_view)); + g_assert (container == get_icon_container (icon_view)); + + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (icon_view)); + + if (file != NULL) + { + fm_icon_view_set_directory_auto_layout + (icon_view, + file, + fm_icon_view_using_auto_layout (icon_view)); + fm_icon_view_set_directory_tighter_layout + (icon_view, + file, + fm_icon_view_using_tighter_layout (icon_view)); + } + + update_layout_menus (icon_view); +} + +static gboolean +fm_icon_view_can_rename_file (FMDirectoryView *view, CajaFile *file) +{ + if (!(fm_icon_view_get_zoom_level (view) > CAJA_ZOOM_LEVEL_SMALLEST)) + { + return FALSE; + } + + return FM_DIRECTORY_VIEW_CLASS(fm_icon_view_parent_class)->can_rename_file (view, file); +} + +static void +fm_icon_view_start_renaming_file (FMDirectoryView *view, + CajaFile *file, + gboolean select_all) +{ + /* call parent class to make sure the right icon is selected */ + FM_DIRECTORY_VIEW_CLASS(fm_icon_view_parent_class)->start_renaming_file (view, file, select_all); + + /* start renaming */ + caja_icon_container_start_renaming_selected_item + (get_icon_container (FM_ICON_VIEW (view)), select_all); +} + +static const GtkActionEntry icon_view_entries[] = +{ + /* name, stock id, label */ { "Arrange Items", NULL, N_("Arran_ge Items") }, + /* name, stock id */ { "Stretch", NULL, + /* label, accelerator */ N_("Resize Icon..."), NULL, + /* tooltip */ N_("Make the selected icon resizable"), + G_CALLBACK (action_stretch_callback) + }, + /* name, stock id */ { "Unstretch", NULL, + /* label, accelerator */ N_("Restore Icons' Original Si_zes"), NULL, + /* tooltip */ N_("Restore each selected icon to its original size"), + G_CALLBACK (action_unstretch_callback) + }, + /* name, stock id */ { "Clean Up", NULL, + /* label, accelerator */ N_("_Organize by Name"), NULL, + /* tooltip */ N_("Reposition icons to better fit in the window and avoid overlapping"), + G_CALLBACK (action_clean_up_callback) + }, +}; + +static const GtkToggleActionEntry icon_view_toggle_entries[] = +{ + /* name, stock id */ { "Tighter Layout", NULL, + /* label, accelerator */ N_("Compact _Layout"), NULL, + /* tooltip */ N_("Toggle using a tighter layout scheme"), + G_CALLBACK (action_tighter_layout_callback), + 0 + }, + /* name, stock id */ { "Reversed Order", NULL, + /* label, accelerator */ N_("Re_versed Order"), NULL, + /* tooltip */ N_("Display icons in the opposite order"), + G_CALLBACK (action_reversed_order_callback), + 0 + }, + /* name, stock id */ { "Keep Aligned", NULL, + /* label, accelerator */ N_("_Keep Aligned"), NULL, + /* tooltip */ N_("Keep icons lined up on a grid"), + G_CALLBACK (action_keep_aligned_callback), + 0 + }, +}; + +static const GtkRadioActionEntry arrange_radio_entries[] = +{ + { + "Manual Layout", NULL, + N_("_Manually"), NULL, + N_("Leave icons wherever they are dropped"), + CAJA_FILE_SORT_NONE + }, + { + "Sort by Name", NULL, + N_("By _Name"), NULL, + N_("Keep icons sorted by name in rows"), + CAJA_FILE_SORT_BY_DISPLAY_NAME + }, + { + "Sort by Size", NULL, + N_("By _Size"), NULL, + N_("Keep icons sorted by size in rows"), + CAJA_FILE_SORT_BY_SIZE + }, + { + "Sort by Type", NULL, + N_("By _Type"), NULL, + N_("Keep icons sorted by type in rows"), + CAJA_FILE_SORT_BY_TYPE + }, + { + "Sort by Modification Date", NULL, + N_("By Modification _Date"), NULL, + N_("Keep icons sorted by modification date in rows"), + CAJA_FILE_SORT_BY_MTIME + }, + { + "Sort by Emblems", NULL, + N_("By _Emblems"), NULL, + N_("Keep icons sorted by emblems in rows"), + CAJA_FILE_SORT_BY_EMBLEMS + }, + { + "Sort by Trash Time", NULL, + N_("By T_rash Time"), NULL, + N_("Keep icons sorted by trash time in rows"), + CAJA_FILE_SORT_BY_TRASHED_TIME + }, +}; + +static void +fm_icon_view_merge_menus (FMDirectoryView *view) +{ + FMIconView *icon_view; + GtkUIManager *ui_manager; + GtkActionGroup *action_group; + GtkAction *action; + const char *ui; + + g_assert (FM_IS_ICON_VIEW (view)); + + FM_DIRECTORY_VIEW_CLASS (fm_icon_view_parent_class)->merge_menus (view); + + icon_view = FM_ICON_VIEW (view); + + ui_manager = fm_directory_view_get_ui_manager (FM_DIRECTORY_VIEW (icon_view)); + + action_group = gtk_action_group_new ("IconViewActions"); + gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE); + icon_view->details->icon_action_group = action_group; + gtk_action_group_add_actions (action_group, + icon_view_entries, G_N_ELEMENTS (icon_view_entries), + icon_view); + gtk_action_group_add_toggle_actions (action_group, + icon_view_toggle_entries, G_N_ELEMENTS (icon_view_toggle_entries), + icon_view); + gtk_action_group_add_radio_actions (action_group, + arrange_radio_entries, + G_N_ELEMENTS (arrange_radio_entries), + -1, + G_CALLBACK (action_sort_radio_callback), + icon_view); + + gtk_ui_manager_insert_action_group (ui_manager, action_group, 0); + g_object_unref (action_group); /* owned by ui manager */ + + ui = caja_ui_string_get ("caja-icon-view-ui.xml"); + icon_view->details->icon_merge_id = + gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, NULL); + + /* Do one-time state-setting here; context-dependent state-setting + * is done in update_menus. + */ + if (!fm_icon_view_supports_auto_layout (icon_view)) + { + action = gtk_action_group_get_action (action_group, + FM_ACTION_ARRANGE_ITEMS); + gtk_action_set_visible (action, FALSE); + } + + if (fm_icon_view_supports_scaling (icon_view)) + { + gtk_ui_manager_add_ui (ui_manager, + icon_view->details->icon_merge_id, + POPUP_PATH_ICON_APPEARANCE, + FM_ACTION_STRETCH, + FM_ACTION_STRETCH, + GTK_UI_MANAGER_MENUITEM, + FALSE); + gtk_ui_manager_add_ui (ui_manager, + icon_view->details->icon_merge_id, + POPUP_PATH_ICON_APPEARANCE, + FM_ACTION_UNSTRETCH, + FM_ACTION_UNSTRETCH, + GTK_UI_MANAGER_MENUITEM, + FALSE); + } + + update_layout_menus (icon_view); +} + +static void +fm_icon_view_unmerge_menus (FMDirectoryView *view) +{ + FMIconView *icon_view; + GtkUIManager *ui_manager; + + icon_view = FM_ICON_VIEW (view); + + FM_DIRECTORY_VIEW_CLASS (fm_icon_view_parent_class)->unmerge_menus (view); + + ui_manager = fm_directory_view_get_ui_manager (view); + if (ui_manager != NULL) + { + caja_ui_unmerge_ui (ui_manager, + &icon_view->details->icon_merge_id, + &icon_view->details->icon_action_group); + } +} + +static void +fm_icon_view_update_menus (FMDirectoryView *view) +{ + FMIconView *icon_view; + GList *selection; + int selection_count; + GtkAction *action; + CajaIconContainer *icon_container; + gboolean editable; + + icon_view = FM_ICON_VIEW (view); + + FM_DIRECTORY_VIEW_CLASS (fm_icon_view_parent_class)->update_menus(view); + + selection = fm_directory_view_get_selection (view); + selection_count = g_list_length (selection); + icon_container = get_icon_container (icon_view); + + action = gtk_action_group_get_action (icon_view->details->icon_action_group, + FM_ACTION_STRETCH); + gtk_action_set_sensitive (action, + selection_count == 1 + && icon_container != NULL + && !caja_icon_container_has_stretch_handles (icon_container)); + + gtk_action_set_visible (action, + fm_icon_view_supports_scaling (icon_view)); + + action = gtk_action_group_get_action (icon_view->details->icon_action_group, + FM_ACTION_UNSTRETCH); + g_object_set (action, "label", + eel_g_list_more_than_one_item (selection) + ? _("Restore Icons' Original Si_zes") + : _("Restore Icon's Original Si_ze"), + NULL); + gtk_action_set_sensitive (action, + icon_container != NULL + && caja_icon_container_is_stretched (icon_container)); + + gtk_action_set_visible (action, + fm_icon_view_supports_scaling (icon_view)); + + caja_file_list_free (selection); + + editable = fm_directory_view_is_editable (view); + action = gtk_action_group_get_action (icon_view->details->icon_action_group, + FM_ACTION_MANUAL_LAYOUT); + gtk_action_set_sensitive (action, editable); +} + +static void +fm_icon_view_reset_to_defaults (FMDirectoryView *view) +{ + CajaIconContainer *icon_container; + FMIconView *icon_view; + + icon_view = FM_ICON_VIEW (view); + icon_container = get_icon_container (icon_view); + + clear_sort_criterion (icon_view); + caja_icon_container_set_keep_aligned + (icon_container, get_default_directory_keep_aligned ()); + caja_icon_container_set_tighter_layout + (icon_container, get_default_directory_tighter_layout ()); + + caja_icon_container_sort (icon_container); + + /* Switch to manual layout of the default calls for it. + * This needs to happen last for the sort order menus + * to be in sync. + */ + if (get_default_directory_manual_layout ()) + { + switch_to_manual_layout (icon_view); + } + + update_layout_menus (icon_view); + + fm_icon_view_restore_default_zoom_level (view); +} + +static void +fm_icon_view_select_all (FMDirectoryView *view) +{ + CajaIconContainer *icon_container; + + g_return_if_fail (FM_IS_ICON_VIEW (view)); + + icon_container = get_icon_container (FM_ICON_VIEW (view)); + caja_icon_container_select_all (icon_container); +} + +static void +fm_icon_view_reveal_selection (FMDirectoryView *view) +{ + GList *selection; + + g_return_if_fail (FM_IS_ICON_VIEW (view)); + + selection = fm_directory_view_get_selection (view); + + /* Make sure at least one of the selected items is scrolled into view */ + if (selection != NULL) + { + caja_icon_container_reveal + (get_icon_container (FM_ICON_VIEW (view)), + selection->data); + } + + caja_file_list_free (selection); +} + +static GArray * +fm_icon_view_get_selected_icon_locations (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_ICON_VIEW (view), NULL); + + return caja_icon_container_get_selected_icon_locations + (get_icon_container (FM_ICON_VIEW (view))); +} + + +static void +fm_icon_view_set_selection (FMDirectoryView *view, GList *selection) +{ + g_return_if_fail (FM_IS_ICON_VIEW (view)); + + caja_icon_container_set_selection + (get_icon_container (FM_ICON_VIEW (view)), selection); +} + +static void +fm_icon_view_invert_selection (FMDirectoryView *view) +{ + g_return_if_fail (FM_IS_ICON_VIEW (view)); + + caja_icon_container_invert_selection + (get_icon_container (FM_ICON_VIEW (view))); +} + +static gboolean +fm_icon_view_using_manual_layout (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE); + + return !fm_icon_view_using_auto_layout (FM_ICON_VIEW (view)); +} + +static void +fm_icon_view_widget_to_file_operation_position (FMDirectoryView *view, + GdkPoint *position) +{ + g_assert (FM_IS_ICON_VIEW (view)); + + caja_icon_container_widget_to_file_operation_position + (get_icon_container (FM_ICON_VIEW (view)), position); +} + +static void +icon_container_activate_callback (CajaIconContainer *container, + GList *file_list, + FMIconView *icon_view) +{ + g_assert (FM_IS_ICON_VIEW (icon_view)); + g_assert (container == get_icon_container (icon_view)); + + fm_directory_view_activate_files (FM_DIRECTORY_VIEW (icon_view), + file_list, + CAJA_WINDOW_OPEN_ACCORDING_TO_MODE, 0, + TRUE); +} + +static void +icon_container_activate_alternate_callback (CajaIconContainer *container, + GList *file_list, + FMIconView *icon_view) +{ + GdkEvent *event; + GdkEventButton *button_event; + GdkEventKey *key_event; + gboolean open_in_tab; + CajaWindowInfo *window_info; + CajaWindowOpenFlags flags; + + g_assert (FM_IS_ICON_VIEW (icon_view)); + g_assert (container == get_icon_container (icon_view)); + + open_in_tab = FALSE; + + window_info = fm_directory_view_get_caja_window (FM_DIRECTORY_VIEW (icon_view)); + + if (caja_window_info_get_window_type (window_info) == CAJA_WINDOW_NAVIGATION) + { + event = gtk_get_current_event (); + if (event->type == GDK_BUTTON_PRESS || + event->type == GDK_BUTTON_RELEASE || + event->type == GDK_2BUTTON_PRESS || + event->type == GDK_3BUTTON_PRESS) + { + button_event = (GdkEventButton *) event; + open_in_tab = (button_event->state & GDK_SHIFT_MASK) == 0; + } + else if (event->type == GDK_KEY_PRESS || + event->type == GDK_KEY_RELEASE) + { + key_event = (GdkEventKey *) event; + open_in_tab = !((key_event->state & GDK_SHIFT_MASK) != 0 && + (key_event->state & GDK_CONTROL_MASK) != 0); + } + else + { + open_in_tab = TRUE; + } + } + + flags = CAJA_WINDOW_OPEN_FLAG_CLOSE_BEHIND; + if (open_in_tab) + { + flags |= CAJA_WINDOW_OPEN_FLAG_NEW_TAB; + } + else + { + flags |= CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW; + } + + fm_directory_view_activate_files (FM_DIRECTORY_VIEW (icon_view), + file_list, + CAJA_WINDOW_OPEN_ACCORDING_TO_MODE, + flags, + TRUE); +} + +static void +band_select_started_callback (CajaIconContainer *container, + FMIconView *icon_view) +{ + g_assert (FM_IS_ICON_VIEW (icon_view)); + g_assert (container == get_icon_container (icon_view)); + + fm_directory_view_start_batching_selection_changes (FM_DIRECTORY_VIEW (icon_view)); +} + +static void +band_select_ended_callback (CajaIconContainer *container, + FMIconView *icon_view) +{ + g_assert (FM_IS_ICON_VIEW (icon_view)); + g_assert (container == get_icon_container (icon_view)); + + fm_directory_view_stop_batching_selection_changes (FM_DIRECTORY_VIEW (icon_view)); +} + +/* handle the preview signal by inspecting the mime type. For now, we only preview local sound files. */ + +static char ** +get_preview_argv (char *uri) +{ + char *command; + char **argv; + int i; + + command = g_find_program_in_path ("totem-audio-preview"); + if (command) + { + argv = g_new (char *, 3); + argv[0] = command; + argv[1] = g_strdup (uri); + argv[2] = NULL; + + return argv; + } + + command = g_find_program_in_path ("gst-launch-0.10"); + if (command) + { + argv = g_new (char *, 10); + i = 0; + argv[i++] = command; + argv[i++] = g_strdup ("playbin"); + argv[i++] = g_strconcat ("uri=", uri, NULL); + /* do not display videos */ + argv[i++] = g_strdup ("current-video=-1"); + argv[i++] = NULL; + return argv; + } + + return NULL; +} + +static void +audio_child_died (GPid pid, + gint status, + gpointer data) +{ + FMIconView *icon_view; + + icon_view = FM_ICON_VIEW (data); + + icon_view->details->audio_preview_child_watch = 0; + icon_view->details->audio_preview_child_pid = 0; +} + +/* here's the timer task that actually plays the file using mpg123, ogg123 or play. */ +/* FIXME bugzilla.gnome.org 41258: we should get the application from our mime-type stuff */ +static gboolean +play_file (gpointer callback_data) +{ + CajaFile *file; + FMIconView *icon_view; + GPid child_pid; + char **argv; + GError *error; + char *uri; + + icon_view = FM_ICON_VIEW (callback_data); + + /* Stop timeout */ + icon_view->details->audio_preview_timeout = 0; + + file = icon_view->details->audio_preview_file; + uri = caja_file_get_uri (file); + argv = get_preview_argv (uri); + g_free (uri); + if (argv == NULL) + { + return FALSE; + } + + error = NULL; + if (!g_spawn_async_with_pipes (NULL, + argv, + NULL, + G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL, + NULL, + NULL /* user_data */, + &child_pid, + NULL, NULL, NULL, + &error)) + { + g_strfreev (argv); + g_warning ("Error spawning sound preview: %s\n", error->message); + g_error_free (error); + return FALSE; + } + g_strfreev (argv); + + icon_view->details->audio_preview_child_watch = + g_child_watch_add (child_pid, + audio_child_died, NULL); + icon_view->details->audio_preview_child_pid = child_pid; + + return FALSE; +} + +/* FIXME bugzilla.gnome.org 42530: Hardcoding this here sucks. We should be using components + * for open ended things like this. + */ + +/* this routine is invoked from the preview signal handler to preview a sound file. We + want to wait a suitable delay until we actually do it, so set up a timer task to actually + start playing. If we move out before the task files, we remove it. */ + +static void +preview_audio (FMIconView *icon_view, CajaFile *file, gboolean start_flag) +{ + /* Stop current audio playback */ + if (icon_view->details->audio_preview_child_pid != 0) + { + kill (icon_view->details->audio_preview_child_pid, SIGTERM); + g_source_remove (icon_view->details->audio_preview_child_watch); + waitpid (icon_view->details->audio_preview_child_pid, NULL, 0); + icon_view->details->audio_preview_child_pid = 0; + } + + if (icon_view->details->audio_preview_timeout != 0) + { + g_source_remove (icon_view->details->audio_preview_timeout); + icon_view->details->audio_preview_timeout = 0; + } + + if (start_flag) + { + icon_view->details->audio_preview_file = file; + icon_view->details->audio_preview_timeout = g_timeout_add_seconds (1, play_file, icon_view); + } +} + +static gboolean +sound_preview_type_supported (CajaFile *file) +{ + char *mime_type; + guint i; + + mime_type = caja_file_get_mime_type (file); + if (mime_type == NULL) + { + return FALSE; + } + for (i = 0; i < G_N_ELEMENTS (audio_mime_types); i++) + { + if (g_content_type_is_a (mime_type, audio_mime_types[i])) + { + g_free (mime_type); + return TRUE; + } + } + + g_free (mime_type); + return FALSE; +} + + +static gboolean +should_preview_sound (CajaFile *file) +{ + GFile *location; + GFilesystemPreviewType use_preview; + + use_preview = caja_file_get_filesystem_use_preview (file); + + location = caja_file_get_location (file); + if (g_file_has_uri_scheme (location, "burn")) + { + g_object_unref (location); + return FALSE; + } + g_object_unref (location); + + /* Check user performance preference */ + if (preview_sound_auto_value == CAJA_SPEED_TRADEOFF_NEVER) + { + return FALSE; + } + + if (preview_sound_auto_value == CAJA_SPEED_TRADEOFF_ALWAYS) + { + if (use_preview == G_FILESYSTEM_PREVIEW_TYPE_NEVER) + { + return FALSE; + } + else + { + return TRUE; + } + } + + 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); + } +} + +static int +icon_container_preview_callback (CajaIconContainer *container, + CajaFile *file, + gboolean start_flag, + FMIconView *icon_view) +{ + int result; + char *file_name, *message; + + result = 0; + + /* preview files based on the mime_type. */ + /* at first, we just handle sounds */ + if (should_preview_sound (file)) + { + if (sound_preview_type_supported (file)) + { + result = 1; + preview_audio (icon_view, file, start_flag); + } + } + + /* Display file name in status area at low zoom levels, since + * the name is not displayed or hard to read in the icon view. + */ + if (fm_icon_view_get_zoom_level (FM_DIRECTORY_VIEW (icon_view)) <= CAJA_ZOOM_LEVEL_SMALLER) + { + if (start_flag) + { + file_name = caja_file_get_display_name (file); + message = g_strdup_printf (_("pointing at \"%s\""), file_name); + g_free (file_name); + caja_window_slot_info_set_status + (fm_directory_view_get_caja_window_slot (FM_DIRECTORY_VIEW (icon_view)), + message); + g_free (message); + } + else + { + fm_directory_view_display_selection_info (FM_DIRECTORY_VIEW(icon_view)); + } + } + + return result; +} + +static void +renaming_icon_callback (CajaIconContainer *container, + GtkWidget *widget, + gpointer callback_data) +{ + FMDirectoryView *directory_view; + + directory_view = FM_DIRECTORY_VIEW (callback_data); + caja_clipboard_set_up_editable + (GTK_EDITABLE (widget), + fm_directory_view_get_ui_manager (directory_view), + FALSE); +} + +int +fm_icon_view_compare_files (FMIconView *icon_view, + CajaFile *a, + CajaFile *b) +{ + return caja_file_compare_for_sort + (a, b, icon_view->details->sort->sort_type, + /* Use type-unsafe cast for performance */ + fm_directory_view_should_sort_directories_first ((FMDirectoryView *)icon_view), + icon_view->details->sort_reversed); +} + +static int +compare_files (FMDirectoryView *icon_view, + CajaFile *a, + CajaFile *b) +{ + return fm_icon_view_compare_files ((FMIconView *)icon_view, a, b); +} + + +void +fm_icon_view_filter_by_screen (FMIconView *icon_view, + gboolean filter) +{ + icon_view->details->filter_by_screen = filter; + icon_view->details->num_screens = gdk_display_get_n_screens (gtk_widget_get_display (GTK_WIDGET (icon_view))); +} + +static void +fm_icon_view_screen_changed (GtkWidget *widget, + GdkScreen *previous_screen) +{ + FMDirectoryView *view; + GList *files, *l; + CajaFile *file; + CajaDirectory *directory; + CajaIconContainer *icon_container; + + if (GTK_WIDGET_CLASS (fm_icon_view_parent_class)->screen_changed) + { + GTK_WIDGET_CLASS (fm_icon_view_parent_class)->screen_changed (widget, previous_screen); + } + + view = FM_DIRECTORY_VIEW (widget); + if (FM_ICON_VIEW (view)->details->filter_by_screen) + { + icon_container = get_icon_container (FM_ICON_VIEW (view)); + + directory = fm_directory_view_get_model (view); + files = caja_directory_get_file_list (directory); + + for (l = files; l != NULL; l = l->next) + { + file = l->data; + + if (!should_show_file_on_screen (view, file)) + { + fm_icon_view_remove_file (view, file, directory); + } + else + { + if (caja_icon_container_add (icon_container, + CAJA_ICON_CONTAINER_ICON_DATA (file))) + { + caja_file_ref (file); + } + } + } + + caja_file_list_unref (files); + g_list_free (files); + } +} + +static gboolean +fm_icon_view_scroll_event (GtkWidget *widget, + GdkEventScroll *scroll_event) +{ + FMIconView *icon_view; + GdkEvent *event_copy; + GdkEventScroll *scroll_event_copy; + gboolean ret; + + icon_view = FM_ICON_VIEW (widget); + + if (icon_view->details->compact && + (scroll_event->direction == GDK_SCROLL_UP || + scroll_event->direction == GDK_SCROLL_DOWN)) + { + ret = fm_directory_view_handle_scroll_event (FM_DIRECTORY_VIEW (icon_view), scroll_event); + if (!ret) + { + /* in column-wise layout, re-emit vertical mouse scroll events as horizontal ones, + * if they don't bump zoom */ + event_copy = gdk_event_copy ((GdkEvent *) scroll_event); + + scroll_event_copy = (GdkEventScroll *) event_copy; + if (scroll_event_copy->direction == GDK_SCROLL_UP) + { + scroll_event_copy->direction = GDK_SCROLL_LEFT; + } + else + { + scroll_event_copy->direction = GDK_SCROLL_RIGHT; + } + + ret = gtk_widget_event (widget, event_copy); + gdk_event_free (event_copy); + } + + return ret; + } + + return GTK_WIDGET_CLASS (fm_icon_view_parent_class)->scroll_event (widget, scroll_event); +} + +static void +selection_changed_callback (CajaIconContainer *container, + FMIconView *icon_view) +{ + g_assert (FM_IS_ICON_VIEW (icon_view)); + g_assert (container == get_icon_container (icon_view)); + + fm_directory_view_notify_selection_changed (FM_DIRECTORY_VIEW (icon_view)); +} + +static void +icon_container_context_click_selection_callback (CajaIconContainer *container, + GdkEventButton *event, + FMIconView *icon_view) +{ + g_assert (CAJA_IS_ICON_CONTAINER (container)); + g_assert (FM_IS_ICON_VIEW (icon_view)); + + fm_directory_view_pop_up_selection_context_menu + (FM_DIRECTORY_VIEW (icon_view), event); +} + +static void +icon_container_context_click_background_callback (CajaIconContainer *container, + GdkEventButton *event, + FMIconView *icon_view) +{ + g_assert (CAJA_IS_ICON_CONTAINER (container)); + g_assert (FM_IS_ICON_VIEW (icon_view)); + + fm_directory_view_pop_up_background_context_menu + (FM_DIRECTORY_VIEW (icon_view), event); +} + +static gboolean +fm_icon_view_react_to_icon_change_idle_callback (gpointer data) +{ + FMIconView *icon_view; + + g_assert (FM_IS_ICON_VIEW (data)); + + icon_view = FM_ICON_VIEW (data); + icon_view->details->react_to_icon_change_idle_id = 0; + + /* Rebuild the menus since some of them (e.g. Restore Stretched Icons) + * may be different now. + */ + fm_directory_view_update_menus (FM_DIRECTORY_VIEW (icon_view)); + + /* Don't call this again (unless rescheduled) */ + return FALSE; +} + +static void +icon_position_changed_callback (CajaIconContainer *container, + CajaFile *file, + const CajaIconPosition *position, + FMIconView *icon_view) +{ + char *position_string; + char scale_string[G_ASCII_DTOSTR_BUF_SIZE]; + + g_assert (FM_IS_ICON_VIEW (icon_view)); + g_assert (container == get_icon_container (icon_view)); + g_assert (CAJA_IS_FILE (file)); + + /* Schedule updating menus for the next idle. Doing it directly here + * noticeably slows down icon stretching. The other work here to + * store the icon position and scale does not seem to noticeably + * slow down icon stretching. It would be trickier to move to an + * idle call, because we'd have to keep track of potentially multiple + * sets of file/geometry info. + */ + if (fm_directory_view_get_active (FM_DIRECTORY_VIEW (icon_view)) && + icon_view->details->react_to_icon_change_idle_id == 0) + { + icon_view->details->react_to_icon_change_idle_id + = g_idle_add (fm_icon_view_react_to_icon_change_idle_callback, + icon_view); + } + + /* Store the new position of the icon in the metadata. */ + if (!fm_icon_view_using_auto_layout (icon_view)) + { + position_string = g_strdup_printf + ("%d,%d", position->x, position->y); + caja_file_set_metadata + (file, CAJA_METADATA_KEY_ICON_POSITION, + NULL, position_string); + g_free (position_string); + } + + + g_ascii_dtostr (scale_string, sizeof (scale_string), position->scale); + caja_file_set_metadata + (file, CAJA_METADATA_KEY_ICON_SCALE, + "1.0", scale_string); +} + +/* Attempt to change the filename to the new text. Notify user if operation fails. */ +static void +fm_icon_view_icon_text_changed_callback (CajaIconContainer *container, + CajaFile *file, + char *new_name, + FMIconView *icon_view) +{ + g_assert (CAJA_IS_FILE (file)); + g_assert (new_name != NULL); + + /* Don't allow a rename with an empty string. Revert to original + * without notifying the user. + */ + if (new_name[0] == '\0') + { + return; + } + fm_rename_file (file, new_name, NULL, NULL); +} + +static char * +get_icon_uri_callback (CajaIconContainer *container, + CajaFile *file, + FMIconView *icon_view) +{ + g_assert (CAJA_IS_ICON_CONTAINER (container)); + g_assert (CAJA_IS_FILE (file)); + g_assert (FM_IS_ICON_VIEW (icon_view)); + + return caja_file_get_uri (file); +} + +static char * +get_icon_drop_target_uri_callback (CajaIconContainer *container, + CajaFile *file, + FMIconView *icon_view) +{ + g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (container), NULL); + g_return_val_if_fail (CAJA_IS_FILE (file), NULL); + g_return_val_if_fail (FM_IS_ICON_VIEW (icon_view), NULL); + + return caja_file_get_drop_target_uri (file); +} + +/* Preferences changed callbacks */ +static void +fm_icon_view_text_attribute_names_changed (FMDirectoryView *directory_view) +{ + g_assert (FM_IS_ICON_VIEW (directory_view)); + + caja_icon_container_request_update_all (get_icon_container (FM_ICON_VIEW (directory_view))); +} + +static void +fm_icon_view_embedded_text_policy_changed (FMDirectoryView *directory_view) +{ + g_assert (FM_IS_ICON_VIEW (directory_view)); + + caja_icon_container_request_update_all (get_icon_container (FM_ICON_VIEW (directory_view))); +} + +static void +fm_icon_view_image_display_policy_changed (FMDirectoryView *directory_view) +{ + g_assert (FM_IS_ICON_VIEW (directory_view)); + + caja_icon_container_request_update_all (get_icon_container (FM_ICON_VIEW (directory_view))); +} + +static void +fm_icon_view_click_policy_changed (FMDirectoryView *directory_view) +{ + g_assert (FM_IS_ICON_VIEW (directory_view)); + + fm_icon_view_update_click_mode (FM_ICON_VIEW (directory_view)); +} + +static void +fm_icon_view_emblems_changed (FMDirectoryView *directory_view) +{ + g_assert (FM_IS_ICON_VIEW (directory_view)); + + caja_icon_container_request_update_all (get_icon_container (FM_ICON_VIEW (directory_view))); +} + +static void +default_sort_order_changed_callback (gpointer callback_data) +{ + FMIconView *icon_view; + CajaFile *file; + char *sort_name; + CajaIconContainer *icon_container; + + g_return_if_fail (FM_IS_ICON_VIEW (callback_data)); + + icon_view = FM_ICON_VIEW (callback_data); + + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (icon_view)); + sort_name = fm_icon_view_get_directory_sort_by (icon_view, file); + set_sort_criterion (icon_view, get_sort_criterion_by_metadata_text (sort_name)); + g_free (sort_name); + + icon_container = get_icon_container (icon_view); + g_return_if_fail (CAJA_IS_ICON_CONTAINER (icon_container)); + + caja_icon_container_request_update_all (icon_container); +} + +static void +default_sort_in_reverse_order_changed_callback (gpointer callback_data) +{ + FMIconView *icon_view; + CajaFile *file; + CajaIconContainer *icon_container; + + g_return_if_fail (FM_IS_ICON_VIEW (callback_data)); + + icon_view = FM_ICON_VIEW (callback_data); + + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (icon_view)); + set_sort_reversed (icon_view, fm_icon_view_get_directory_sort_reversed (icon_view, file)); + icon_container = get_icon_container (icon_view); + g_return_if_fail (CAJA_IS_ICON_CONTAINER (icon_container)); + + caja_icon_container_request_update_all (icon_container); +} + +static void +default_use_tighter_layout_changed_callback (gpointer callback_data) +{ + FMIconView *icon_view; + CajaFile *file; + CajaIconContainer *icon_container; + + g_return_if_fail (FM_IS_ICON_VIEW (callback_data)); + + icon_view = FM_ICON_VIEW (callback_data); + + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (icon_view)); + icon_container = get_icon_container (icon_view); + g_return_if_fail (CAJA_IS_ICON_CONTAINER (icon_container)); + + caja_icon_container_set_tighter_layout ( + icon_container, + fm_icon_view_get_directory_tighter_layout (icon_view, file)); + + caja_icon_container_request_update_all (icon_container); +} + +static void +default_use_manual_layout_changed_callback (gpointer callback_data) +{ + FMIconView *icon_view; + CajaFile *file; + CajaIconContainer *icon_container; + + g_return_if_fail (FM_IS_ICON_VIEW (callback_data)); + + icon_view = FM_ICON_VIEW (callback_data); + + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (icon_view)); + icon_container = get_icon_container (icon_view); + g_return_if_fail (CAJA_IS_ICON_CONTAINER (icon_container)); + + caja_icon_container_set_auto_layout ( + icon_container, + fm_icon_view_get_directory_auto_layout (icon_view, file)); + + caja_icon_container_request_update_all (icon_container); +} + +static void +default_zoom_level_changed_callback (gpointer callback_data) +{ + FMIconView *icon_view; + CajaFile *file; + int level; + + g_return_if_fail (FM_IS_ICON_VIEW (callback_data)); + + icon_view = FM_ICON_VIEW (callback_data); + + if (fm_directory_view_supports_zooming (FM_DIRECTORY_VIEW (icon_view))) + { + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (icon_view)); + + if (fm_icon_view_is_compact (icon_view)) + { + level = caja_file_get_integer_metadata (file, + CAJA_METADATA_KEY_COMPACT_VIEW_ZOOM_LEVEL, + get_default_zoom_level (icon_view)); + } + else + { + level = caja_file_get_integer_metadata (file, + CAJA_METADATA_KEY_ICON_VIEW_ZOOM_LEVEL, + get_default_zoom_level (icon_view)); + } + fm_directory_view_zoom_to_level (FM_DIRECTORY_VIEW (icon_view), level); + } +} + +static void +labels_beside_icons_changed_callback (gpointer callback_data) +{ + FMIconView *icon_view; + + g_return_if_fail (FM_IS_ICON_VIEW (callback_data)); + + icon_view = FM_ICON_VIEW (callback_data); + + set_labels_beside_icons (icon_view); +} + +static void +all_columns_same_width_changed_callback (gpointer callback_data) +{ + FMIconView *icon_view; + + g_assert (FM_IS_ICON_VIEW (callback_data)); + + icon_view = FM_ICON_VIEW (callback_data); + + set_columns_same_width (icon_view); +} + + +static void +fm_icon_view_sort_directories_first_changed (FMDirectoryView *directory_view) +{ + FMIconView *icon_view; + + icon_view = FM_ICON_VIEW (directory_view); + + if (fm_icon_view_using_auto_layout (icon_view)) + { + caja_icon_container_sort + (get_icon_container (icon_view)); + } +} + +/* GtkObject methods. */ + +static gboolean +icon_view_can_accept_item (CajaIconContainer *container, + CajaFile *target_item, + const char *item_uri, + FMDirectoryView *view) +{ + return fm_directory_view_can_accept_item (target_item, item_uri, view); +} + +static char * +icon_view_get_container_uri (CajaIconContainer *container, + FMDirectoryView *view) +{ + return fm_directory_view_get_uri (view); +} + +static void +icon_view_move_copy_items (CajaIconContainer *container, + const GList *item_uris, + GArray *relative_item_points, + const char *target_dir, + int copy_action, + int x, int y, + FMDirectoryView *view) +{ + caja_clipboard_clear_if_colliding_uris (GTK_WIDGET (view), + item_uris, + fm_directory_view_get_copied_files_atom (view)); + fm_directory_view_move_copy_items (item_uris, relative_item_points, target_dir, + copy_action, x, y, view); +} + +static void +fm_icon_view_update_click_mode (FMIconView *icon_view) +{ + CajaIconContainer *icon_container; + int click_mode; + + icon_container = get_icon_container (icon_view); + g_assert (icon_container != NULL); + + click_mode = eel_preferences_get_enum (CAJA_PREFERENCES_CLICK_POLICY); + + caja_icon_container_set_single_click_mode (icon_container, + click_mode == CAJA_CLICK_POLICY_SINGLE); +} + +static gboolean +get_stored_layout_timestamp (CajaIconContainer *container, + CajaIconData *icon_data, + time_t *timestamp, + FMIconView *view) +{ + CajaFile *file; + CajaDirectory *directory; + + if (icon_data == NULL) + { + directory = fm_directory_view_get_model (FM_DIRECTORY_VIEW (view)); + if (directory == NULL) + { + return FALSE; + } + + file = caja_directory_get_corresponding_file (directory); + *timestamp = caja_file_get_time_metadata (file, + CAJA_METADATA_KEY_ICON_VIEW_LAYOUT_TIMESTAMP); + caja_file_unref (file); + } + else + { + *timestamp = caja_file_get_time_metadata (CAJA_FILE (icon_data), + CAJA_METADATA_KEY_ICON_POSITION_TIMESTAMP); + } + + return TRUE; +} + +static gboolean +store_layout_timestamp (CajaIconContainer *container, + CajaIconData *icon_data, + const time_t *timestamp, + FMIconView *view) +{ + CajaFile *file; + CajaDirectory *directory; + + if (icon_data == NULL) + { + directory = fm_directory_view_get_model (FM_DIRECTORY_VIEW (view)); + if (directory == NULL) + { + return FALSE; + } + + file = caja_directory_get_corresponding_file (directory); + caja_file_set_time_metadata (file, + CAJA_METADATA_KEY_ICON_VIEW_LAYOUT_TIMESTAMP, + (time_t) *timestamp); + caja_file_unref (file); + } + else + { + caja_file_set_time_metadata (CAJA_FILE (icon_data), + CAJA_METADATA_KEY_ICON_POSITION_TIMESTAMP, + (time_t) *timestamp); + } + + return TRUE; +} + +static gboolean +focus_in_event_callback (GtkWidget *widget, GdkEventFocus *event, gpointer user_data) +{ + CajaWindowSlotInfo *slot_info; + FMIconView *icon_view = FM_ICON_VIEW (user_data); + + /* make the corresponding slot (and the pane that contains it) active */ + slot_info = fm_directory_view_get_caja_window_slot (FM_DIRECTORY_VIEW (icon_view)); + caja_window_slot_info_make_hosting_pane_active (slot_info); + + return FALSE; +} + +static CajaIconContainer * +create_icon_container (FMIconView *icon_view) +{ + CajaIconContainer *icon_container; + + icon_container = fm_icon_container_new (icon_view); + + gtk_widget_set_can_focus (GTK_WIDGET (icon_container), TRUE); + + g_signal_connect_object (icon_container, "focus_in_event", + G_CALLBACK (focus_in_event_callback), icon_view, 0); + g_signal_connect_object (icon_container, "activate", + G_CALLBACK (icon_container_activate_callback), icon_view, 0); + g_signal_connect_object (icon_container, "activate_alternate", + G_CALLBACK (icon_container_activate_alternate_callback), icon_view, 0); + g_signal_connect_object (icon_container, "band_select_started", + G_CALLBACK (band_select_started_callback), icon_view, 0); + g_signal_connect_object (icon_container, "band_select_ended", + G_CALLBACK (band_select_ended_callback), icon_view, 0); + g_signal_connect_object (icon_container, "context_click_selection", + G_CALLBACK (icon_container_context_click_selection_callback), icon_view, 0); + g_signal_connect_object (icon_container, "context_click_background", + G_CALLBACK (icon_container_context_click_background_callback), icon_view, 0); + g_signal_connect_object (icon_container, "icon_position_changed", + G_CALLBACK (icon_position_changed_callback), icon_view, 0); + g_signal_connect_object (icon_container, "icon_text_changed", + G_CALLBACK (fm_icon_view_icon_text_changed_callback), icon_view, 0); + g_signal_connect_object (icon_container, "selection_changed", + G_CALLBACK (selection_changed_callback), icon_view, 0); + /* FIXME: many of these should move into fm-icon-container as virtual methods */ + g_signal_connect_object (icon_container, "get_icon_uri", + G_CALLBACK (get_icon_uri_callback), icon_view, 0); + g_signal_connect_object (icon_container, "get_icon_drop_target_uri", + G_CALLBACK (get_icon_drop_target_uri_callback), icon_view, 0); + g_signal_connect_object (icon_container, "move_copy_items", + G_CALLBACK (icon_view_move_copy_items), icon_view, 0); + g_signal_connect_object (icon_container, "get_container_uri", + G_CALLBACK (icon_view_get_container_uri), icon_view, 0); + g_signal_connect_object (icon_container, "can_accept_item", + G_CALLBACK (icon_view_can_accept_item), icon_view, 0); + g_signal_connect_object (icon_container, "get_stored_icon_position", + G_CALLBACK (get_stored_icon_position_callback), icon_view, 0); + g_signal_connect_object (icon_container, "layout_changed", + G_CALLBACK (layout_changed_callback), icon_view, 0); + g_signal_connect_object (icon_container, "preview", + G_CALLBACK (icon_container_preview_callback), icon_view, 0); + g_signal_connect_object (icon_container, "renaming_icon", + G_CALLBACK (renaming_icon_callback), icon_view, 0); + g_signal_connect_object (icon_container, "icon_stretch_started", + G_CALLBACK (fm_directory_view_update_menus), icon_view, + G_CONNECT_SWAPPED); + g_signal_connect_object (icon_container, "icon_stretch_ended", + G_CALLBACK (fm_directory_view_update_menus), icon_view, + G_CONNECT_SWAPPED); + + g_signal_connect_object (icon_container, "get_stored_layout_timestamp", + G_CALLBACK (get_stored_layout_timestamp), icon_view, 0); + g_signal_connect_object (icon_container, "store_layout_timestamp", + G_CALLBACK (store_layout_timestamp), icon_view, 0); + + gtk_container_add (GTK_CONTAINER (icon_view), + GTK_WIDGET (icon_container)); + + fm_icon_view_update_click_mode (icon_view); + + gtk_widget_show (GTK_WIDGET (icon_container)); + + return icon_container; +} + +/* Handles an URL received from Mozilla */ +static void +icon_view_handle_netscape_url (CajaIconContainer *container, const char *encoded_url, + const char *target_uri, + GdkDragAction action, int x, int y, FMIconView *view) +{ + fm_directory_view_handle_netscape_url_drop (FM_DIRECTORY_VIEW (view), + encoded_url, target_uri, action, x, y); +} + +static void +icon_view_handle_uri_list (CajaIconContainer *container, const char *item_uris, + const char *target_uri, + GdkDragAction action, int x, int y, FMIconView *view) +{ + fm_directory_view_handle_uri_list_drop (FM_DIRECTORY_VIEW (view), + item_uris, target_uri, action, x, y); +} + +static void +icon_view_handle_text (CajaIconContainer *container, const char *text, + const char *target_uri, + GdkDragAction action, int x, int y, FMIconView *view) +{ + fm_directory_view_handle_text_drop (FM_DIRECTORY_VIEW (view), + text, target_uri, action, x, y); +} + +static void +icon_view_handle_raw (CajaIconContainer *container, const char *raw_data, + int length, const char *target_uri, const char *direct_save_uri, + GdkDragAction action, int x, int y, FMIconView *view) +{ + fm_directory_view_handle_raw_drop (FM_DIRECTORY_VIEW (view), + raw_data, length, target_uri, direct_save_uri, action, x, y); +} + +static char * +icon_view_get_first_visible_file (CajaView *view) +{ + CajaFile *file; + FMIconView *icon_view; + + icon_view = FM_ICON_VIEW (view); + + file = CAJA_FILE (caja_icon_container_get_first_visible_icon (get_icon_container (icon_view))); + + if (file) + { + return caja_file_get_uri (file); + } + + return NULL; +} + +static void +icon_view_scroll_to_file (CajaView *view, + const char *uri) +{ + CajaFile *file; + FMIconView *icon_view; + + icon_view = FM_ICON_VIEW (view); + + if (uri != NULL) + { + /* Only if existing, since we don't want to add the file to + the directory if it has been removed since then */ + file = caja_file_get_existing_by_uri (uri); + if (file != NULL) + { + caja_icon_container_scroll_to_icon (get_icon_container (icon_view), + CAJA_ICON_CONTAINER_ICON_DATA (file)); + caja_file_unref (file); + } + } +} + +static void +fm_icon_view_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + FMIconView *icon_view; + + icon_view = FM_ICON_VIEW (object); + + switch (prop_id) + { + case PROP_COMPACT: + icon_view->details->compact = g_value_get_boolean (value); + if (icon_view->details->compact) + { + caja_icon_container_set_layout_mode (get_icon_container (icon_view), + gtk_widget_get_direction (GTK_WIDGET(icon_view)) == GTK_TEXT_DIR_RTL ? + CAJA_ICON_LAYOUT_T_B_R_L : + CAJA_ICON_LAYOUT_T_B_L_R); + caja_icon_container_set_forced_icon_size (get_icon_container (icon_view), + CAJA_ICON_SIZE_SMALLEST); + } + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + + +static void +fm_icon_view_class_init (FMIconViewClass *klass) +{ + FMDirectoryViewClass *fm_directory_view_class; + + fm_directory_view_class = FM_DIRECTORY_VIEW_CLASS (klass); + + G_OBJECT_CLASS (klass)->set_property = fm_icon_view_set_property; + G_OBJECT_CLASS (klass)->finalize = fm_icon_view_finalize; + + GTK_OBJECT_CLASS (klass)->destroy = fm_icon_view_destroy; + + GTK_WIDGET_CLASS (klass)->screen_changed = fm_icon_view_screen_changed; + GTK_WIDGET_CLASS (klass)->scroll_event = fm_icon_view_scroll_event; + + fm_directory_view_class->add_file = fm_icon_view_add_file; + fm_directory_view_class->flush_added_files = fm_icon_view_flush_added_files; + fm_directory_view_class->begin_loading = fm_icon_view_begin_loading; + fm_directory_view_class->bump_zoom_level = fm_icon_view_bump_zoom_level; + fm_directory_view_class->can_rename_file = fm_icon_view_can_rename_file; + fm_directory_view_class->can_zoom_in = fm_icon_view_can_zoom_in; + fm_directory_view_class->can_zoom_out = fm_icon_view_can_zoom_out; + fm_directory_view_class->clear = fm_icon_view_clear; + fm_directory_view_class->end_loading = fm_icon_view_end_loading; + fm_directory_view_class->file_changed = fm_icon_view_file_changed; + fm_directory_view_class->get_background_widget = fm_icon_view_get_background_widget; + fm_directory_view_class->get_selected_icon_locations = fm_icon_view_get_selected_icon_locations; + fm_directory_view_class->get_selection = fm_icon_view_get_selection; + fm_directory_view_class->get_selection_for_file_transfer = fm_icon_view_get_selection; + fm_directory_view_class->get_item_count = fm_icon_view_get_item_count; + fm_directory_view_class->is_empty = fm_icon_view_is_empty; + fm_directory_view_class->remove_file = fm_icon_view_remove_file; + fm_directory_view_class->reset_to_defaults = fm_icon_view_reset_to_defaults; + fm_directory_view_class->restore_default_zoom_level = fm_icon_view_restore_default_zoom_level; + fm_directory_view_class->reveal_selection = fm_icon_view_reveal_selection; + fm_directory_view_class->select_all = fm_icon_view_select_all; + fm_directory_view_class->set_selection = fm_icon_view_set_selection; + fm_directory_view_class->invert_selection = fm_icon_view_invert_selection; + fm_directory_view_class->compare_files = compare_files; + fm_directory_view_class->zoom_to_level = fm_icon_view_zoom_to_level; + fm_directory_view_class->get_zoom_level = fm_icon_view_get_zoom_level; + fm_directory_view_class->click_policy_changed = fm_icon_view_click_policy_changed; + fm_directory_view_class->embedded_text_policy_changed = fm_icon_view_embedded_text_policy_changed; + fm_directory_view_class->emblems_changed = fm_icon_view_emblems_changed; + fm_directory_view_class->image_display_policy_changed = fm_icon_view_image_display_policy_changed; + fm_directory_view_class->merge_menus = fm_icon_view_merge_menus; + fm_directory_view_class->unmerge_menus = fm_icon_view_unmerge_menus; + fm_directory_view_class->sort_directories_first_changed = fm_icon_view_sort_directories_first_changed; + fm_directory_view_class->start_renaming_file = fm_icon_view_start_renaming_file; + fm_directory_view_class->text_attribute_names_changed = fm_icon_view_text_attribute_names_changed; + fm_directory_view_class->update_menus = fm_icon_view_update_menus; + fm_directory_view_class->using_manual_layout = fm_icon_view_using_manual_layout; + fm_directory_view_class->widget_to_file_operation_position = fm_icon_view_widget_to_file_operation_position; + + klass->clean_up = fm_icon_view_real_clean_up; + klass->supports_auto_layout = real_supports_auto_layout; + klass->supports_scaling = real_supports_scaling; + klass->supports_manual_layout = real_supports_manual_layout; + klass->supports_keep_aligned = real_supports_keep_aligned; + klass->supports_labels_beside_icons = real_supports_labels_beside_icons; + klass->get_directory_auto_layout = fm_icon_view_real_get_directory_auto_layout; + klass->get_directory_sort_by = fm_icon_view_real_get_directory_sort_by; + klass->get_directory_sort_reversed = fm_icon_view_real_get_directory_sort_reversed; + klass->get_directory_tighter_layout = fm_icon_view_real_get_directory_tighter_layout; + klass->set_directory_auto_layout = fm_icon_view_real_set_directory_auto_layout; + klass->set_directory_sort_by = fm_icon_view_real_set_directory_sort_by; + klass->set_directory_sort_reversed = fm_icon_view_real_set_directory_sort_reversed; + klass->set_directory_tighter_layout = fm_icon_view_real_set_directory_tighter_layout; + + g_object_class_install_property (G_OBJECT_CLASS (klass), + PROP_COMPACT, + g_param_spec_boolean ("compact", + "Compact", + "Whether this view provides a compact listing", + FALSE, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); + +} + +static const char * +fm_icon_view_get_id (CajaView *view) +{ + if (FM_IS_DESKTOP_ICON_VIEW (view)) + { + return FM_DESKTOP_ICON_VIEW_ID; + } + + if (fm_icon_view_is_compact (FM_ICON_VIEW (view))) + { + return FM_COMPACT_VIEW_ID; + } + + return FM_ICON_VIEW_ID; +} + +static void +fm_icon_view_iface_init (CajaViewIface *iface) +{ + fm_directory_view_init_view_iface (iface); + + iface->get_view_id = fm_icon_view_get_id; + iface->get_first_visible_file = icon_view_get_first_visible_file; + iface->scroll_to_file = icon_view_scroll_to_file; + iface->get_title = NULL; +} + +static void +fm_icon_view_init (FMIconView *icon_view) +{ + static gboolean setup_sound_preview = FALSE; + CajaIconContainer *icon_container; + + g_return_if_fail (gtk_bin_get_child (GTK_BIN (icon_view)) == NULL); + + icon_view->details = g_new0 (FMIconViewDetails, 1); + icon_view->details->sort = &sort_criteria[0]; + icon_view->details->filter_by_screen = FALSE; + + icon_container = create_icon_container (icon_view); + + /* Set our default layout mode */ + caja_icon_container_set_layout_mode (icon_container, + gtk_widget_get_direction (GTK_WIDGET(icon_container)) == GTK_TEXT_DIR_RTL ? + CAJA_ICON_LAYOUT_R_L_T_B : + CAJA_ICON_LAYOUT_L_R_T_B); + + if (!setup_sound_preview) + { + eel_preferences_add_auto_enum (CAJA_PREFERENCES_PREVIEW_SOUND, + &preview_sound_auto_value); + + setup_sound_preview = TRUE; + } + + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_SORT_ORDER, + default_sort_order_changed_callback, + icon_view, G_OBJECT (icon_view)); + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_SORT_IN_REVERSE_ORDER, + default_sort_in_reverse_order_changed_callback, + icon_view, G_OBJECT (icon_view)); + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_USE_TIGHTER_LAYOUT, + default_use_tighter_layout_changed_callback, + icon_view, G_OBJECT (icon_view)); + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_USE_MANUAL_LAYOUT, + default_use_manual_layout_changed_callback, + icon_view, G_OBJECT (icon_view)); + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_ZOOM_LEVEL, + default_zoom_level_changed_callback, + icon_view, G_OBJECT (icon_view)); + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_ICON_VIEW_LABELS_BESIDE_ICONS, + labels_beside_icons_changed_callback, + icon_view, G_OBJECT (icon_view)); + + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_COMPACT_VIEW_DEFAULT_ZOOM_LEVEL, + default_zoom_level_changed_callback, + icon_view, G_OBJECT (icon_view)); + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_COMPACT_VIEW_ALL_COLUMNS_SAME_WIDTH, + all_columns_same_width_changed_callback, + icon_view, G_OBJECT (icon_view)); + + g_signal_connect_object (get_icon_container (icon_view), "handle_netscape_url", + G_CALLBACK (icon_view_handle_netscape_url), icon_view, 0); + g_signal_connect_object (get_icon_container (icon_view), "handle_uri_list", + G_CALLBACK (icon_view_handle_uri_list), icon_view, 0); + g_signal_connect_object (get_icon_container (icon_view), "handle_text", + G_CALLBACK (icon_view_handle_text), icon_view, 0); + g_signal_connect_object (get_icon_container (icon_view), "handle_raw", + G_CALLBACK (icon_view_handle_raw), icon_view, 0); + + icon_view->details->clipboard_handler_id = + g_signal_connect (caja_clipboard_monitor_get (), + "clipboard_info", + G_CALLBACK (icon_view_notify_clipboard_info), icon_view); +} + +static CajaView * +fm_icon_view_create (CajaWindowSlotInfo *slot) +{ + FMIconView *view; + + view = g_object_new (FM_TYPE_ICON_VIEW, + "window-slot", slot, + "compact", FALSE, + NULL); + return CAJA_VIEW (view); +} + +static CajaView * +fm_compact_view_create (CajaWindowSlotInfo *slot) +{ + FMIconView *view; + + view = g_object_new (FM_TYPE_ICON_VIEW, + "window-slot", slot, + "compact", TRUE, + NULL); + return CAJA_VIEW (view); +} + +static gboolean +fm_icon_view_supports_uri (const char *uri, + GFileType file_type, + const char *mime_type) +{ + if (file_type == G_FILE_TYPE_DIRECTORY) + { + return TRUE; + } + if (strcmp (mime_type, CAJA_SAVED_SEARCH_MIMETYPE) == 0) + { + return TRUE; + } + if (g_str_has_prefix (uri, "trash:")) + { + return TRUE; + } + if (g_str_has_prefix (uri, EEL_SEARCH_URI)) + { + return TRUE; + } + + return FALSE; +} + +#define TRANSLATE_VIEW_INFO(view_info) \ + view_info.view_combo_label = _(view_info.view_combo_label); \ + view_info.view_menu_label_with_mnemonic = _(view_info.view_menu_label_with_mnemonic); \ + view_info.error_label = _(view_info.error_label); \ + view_info.startup_error_label = _(view_info.startup_error_label); \ + view_info.display_location_label = _(view_info.display_location_label); \ + + +static CajaViewInfo fm_icon_view = +{ + FM_ICON_VIEW_ID, + /* translators: this is used in the view selection dropdown + * of navigation windows and in the preferences dialog */ + N_("Icon View"), + /* translators: this is used in the view menu */ + N_("_Icons"), + N_("The icon view encountered an error."), + N_("The icon view encountered an error while starting up."), + N_("Display this location with the icon view."), + fm_icon_view_create, + fm_icon_view_supports_uri +}; + +static CajaViewInfo fm_compact_view = +{ + FM_COMPACT_VIEW_ID, + /* translators: this is used in the view selection dropdown + * of navigation windows and in the preferences dialog */ + N_("Compact View"), + /* translators: this is used in the view menu */ + N_("_Compact"), + N_("The compact view encountered an error."), + N_("The compact view encountered an error while starting up."), + N_("Display this location with the compact view."), + fm_compact_view_create, + fm_icon_view_supports_uri +}; + +gboolean +fm_icon_view_is_compact (FMIconView *view) +{ + return view->details->compact; +} + +void +fm_icon_view_register (void) +{ + TRANSLATE_VIEW_INFO (fm_icon_view) + caja_view_factory_register (&fm_icon_view); +} + +void +fm_compact_view_register (void) +{ + TRANSLATE_VIEW_INFO (fm_compact_view) + caja_view_factory_register (&fm_compact_view); +} + diff --git a/src/file-manager/fm-icon-view.h b/src/file-manager/fm-icon-view.h new file mode 100644 index 00000000..2ef11a56 --- /dev/null +++ b/src/file-manager/fm-icon-view.h @@ -0,0 +1,135 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-icon-view.h - interface for icon view of directory. + + 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: John Sullivan <[email protected]> +*/ + +#ifndef FM_ICON_VIEW_H +#define FM_ICON_VIEW_H + +#include "fm-directory-view.h" + +typedef struct FMIconView FMIconView; +typedef struct FMIconViewClass FMIconViewClass; + +#define FM_TYPE_ICON_VIEW fm_icon_view_get_type() +#define FM_ICON_VIEW(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_ICON_VIEW, FMIconView)) +#define FM_ICON_VIEW_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_ICON_VIEW, FMIconViewClass)) +#define FM_IS_ICON_VIEW(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_ICON_VIEW)) +#define FM_IS_ICON_VIEW_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_ICON_VIEW)) +#define FM_ICON_VIEW_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_ICON_VIEW, FMIconViewClass)) + +#define FM_ICON_VIEW_ID "OAFIID:Caja_File_Manager_Icon_View" +#define FM_COMPACT_VIEW_ID "OAFIID:Caja_File_Manager_Compact_View" + +typedef struct FMIconViewDetails FMIconViewDetails; + +struct FMIconView +{ + FMDirectoryView parent; + FMIconViewDetails *details; +}; + +struct FMIconViewClass +{ + FMDirectoryViewClass parent_class; + + /* Methods that can be overriden for settings you don't want to come from metadata. + */ + + /* Note: get_directory_sort_by must return a string that can/will be g_freed. + */ + char * (* get_directory_sort_by) (FMIconView *icon_view, + CajaFile *file); + void (* set_directory_sort_by) (FMIconView *icon_view, + CajaFile *file, + const char* sort_by); + + gboolean (* get_directory_sort_reversed) (FMIconView *icon_view, + CajaFile *file); + void (* set_directory_sort_reversed) (FMIconView *icon_view, + CajaFile *file, + gboolean sort_reversed); + + gboolean (* get_directory_auto_layout) (FMIconView *icon_view, + CajaFile *file); + void (* set_directory_auto_layout) (FMIconView *icon_view, + CajaFile *file, + gboolean auto_layout); + + gboolean (* get_directory_tighter_layout) (FMIconView *icon_view, + CajaFile *file); + void (* set_directory_tighter_layout) (FMIconView *icon_view, + CajaFile *file, + gboolean tighter_layout); + + /* Override "clean_up" if your subclass has its own notion of where icons should be positioned */ + void (* clean_up) (FMIconView *icon_view); + + /* supports_auto_layout is a function pointer that subclasses may + * override to control whether or not the automatic layout options + * should be enabled. The default implementation returns TRUE. + */ + gboolean (* supports_auto_layout) (FMIconView *view); + + /* supports_manual_layout is a function pointer that subclasses may + * override to control whether or not the manual layout options + * should be enabled. The default implementation returns TRUE iff + * not in compact mode. + */ + gboolean (* supports_manual_layout) (FMIconView *view); + + /* supports_scaling is a function pointer that subclasses may + * override to control whether or not the manual layout supports + * scaling. The default implementation returns FALSE + */ + gboolean (* supports_scaling) (FMIconView *view); + + /* supports_auto_layout is a function pointer that subclasses may + * override to control whether snap-to-grid mode + * should be enabled. The default implementation returns FALSE. + */ + gboolean (* supports_keep_aligned) (FMIconView *view); + + /* supports_auto_layout is a function pointer that subclasses may + * override to control whether snap-to-grid mode + * should be enabled. The default implementation returns FALSE. + */ + gboolean (* supports_labels_beside_icons) (FMIconView *view); +}; + +/* GObject support */ +GType fm_icon_view_get_type (void); +int fm_icon_view_compare_files (FMIconView *icon_view, + CajaFile *a, + CajaFile *b); +void fm_icon_view_filter_by_screen (FMIconView *icon_view, gboolean filter); +gboolean fm_icon_view_is_compact (FMIconView *icon_view); + +void fm_icon_view_register (void); +void fm_compact_view_register (void); + +#endif /* FM_ICON_VIEW_H */ diff --git a/src/file-manager/fm-list-model.c b/src/file-manager/fm-list-model.c new file mode 100644 index 00000000..b5e12da2 --- /dev/null +++ b/src/file-manager/fm-list-model.c @@ -0,0 +1,1882 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-list-model.h - a GtkTreeModel for file lists. + + Copyright (C) 2001, 2002 Anders Carlsson + Copyright (C) 2003, Soeren Sandmann + 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: Anders Carlsson <[email protected]>, Soeren Sandmann ([email protected]), Dave Camp <[email protected]> +*/ + +#include <config.h> +#include "fm-list-model.h" +#include <libegg/eggtreemultidnd.h> + +#include <string.h> +#include <eel/eel-gtk-macros.h> +#include <eel/eel-glib-extensions.h> +#include <eel/eel-gdk-pixbuf-extensions.h> +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <libcaja-private/caja-dnd.h> +#include <glib.h> + + +enum +{ + SUBDIRECTORY_UNLOADED, + LAST_SIGNAL +}; + +static GQuark attribute_name_q, + attribute_modification_date_q, + attribute_date_modified_q; + +/* msec delay after Loading... dummy row turns into (empty) */ +#define LOADING_TO_EMPTY_DELAY 100 + +static guint list_model_signals[LAST_SIGNAL] = { 0 }; + +static int fm_list_model_file_entry_compare_func (gconstpointer a, + gconstpointer b, + gpointer user_data); +static void fm_list_model_tree_model_init (GtkTreeModelIface *iface); +static void fm_list_model_sortable_init (GtkTreeSortableIface *iface); +static void fm_list_model_multi_drag_source_init (EggTreeMultiDragSourceIface *iface); + +struct FMListModelDetails +{ + GSequence *files; + GHashTable *directory_reverse_map; /* map from directory to GSequenceIter's */ + GHashTable *top_reverse_map; /* map from files in top dir to GSequenceIter's */ + + int stamp; + + GQuark sort_attribute; + GtkSortType order; + + gboolean sort_directories_first; + + GtkTreeView *drag_view; + int drag_begin_x; + int drag_begin_y; + + GPtrArray *columns; + + GList *highlight_files; +}; + +typedef struct +{ + FMListModel *model; + + GList *path_list; +} DragDataGetInfo; + +typedef struct FileEntry FileEntry; + +struct FileEntry +{ + CajaFile *file; + GHashTable *reverse_map; /* map from files to GSequenceIter's */ + CajaDirectory *subdirectory; + FileEntry *parent; + GSequence *files; + GSequenceIter *ptr; + guint loaded : 1; +}; + +G_DEFINE_TYPE_WITH_CODE (FMListModel, fm_list_model, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL, + fm_list_model_tree_model_init) + G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_SORTABLE, + fm_list_model_sortable_init) + G_IMPLEMENT_INTERFACE (EGG_TYPE_TREE_MULTI_DRAG_SOURCE, + fm_list_model_multi_drag_source_init)); + +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 GtkTargetList *drag_target_list = NULL; + +static void +file_entry_free (FileEntry *file_entry) +{ + caja_file_unref (file_entry->file); + if (file_entry->reverse_map) + { + g_hash_table_destroy (file_entry->reverse_map); + file_entry->reverse_map = NULL; + } + if (file_entry->subdirectory != NULL) + { + caja_directory_unref (file_entry->subdirectory); + } + if (file_entry->files != NULL) + { + g_sequence_free (file_entry->files); + } + g_free (file_entry); +} + +static GtkTreeModelFlags +fm_list_model_get_flags (GtkTreeModel *tree_model) +{ + return GTK_TREE_MODEL_ITERS_PERSIST; +} + +static int +fm_list_model_get_n_columns (GtkTreeModel *tree_model) +{ + return FM_LIST_MODEL_NUM_COLUMNS + FM_LIST_MODEL (tree_model)->details->columns->len; +} + +static GType +fm_list_model_get_column_type (GtkTreeModel *tree_model, int index) +{ + switch (index) + { + case FM_LIST_MODEL_FILE_COLUMN: + return CAJA_TYPE_FILE; + case FM_LIST_MODEL_SUBDIRECTORY_COLUMN: + return CAJA_TYPE_DIRECTORY; + case FM_LIST_MODEL_SMALLEST_ICON_COLUMN: + case FM_LIST_MODEL_SMALLER_ICON_COLUMN: + case FM_LIST_MODEL_SMALL_ICON_COLUMN: + case FM_LIST_MODEL_STANDARD_ICON_COLUMN: + case FM_LIST_MODEL_LARGE_ICON_COLUMN: + case FM_LIST_MODEL_LARGER_ICON_COLUMN: + case FM_LIST_MODEL_LARGEST_ICON_COLUMN: + case FM_LIST_MODEL_SMALLEST_EMBLEM_COLUMN: + case FM_LIST_MODEL_SMALLER_EMBLEM_COLUMN: + case FM_LIST_MODEL_SMALL_EMBLEM_COLUMN: + case FM_LIST_MODEL_STANDARD_EMBLEM_COLUMN: + case FM_LIST_MODEL_LARGE_EMBLEM_COLUMN: + case FM_LIST_MODEL_LARGER_EMBLEM_COLUMN: + case FM_LIST_MODEL_LARGEST_EMBLEM_COLUMN: + return GDK_TYPE_PIXBUF; + case FM_LIST_MODEL_FILE_NAME_IS_EDITABLE_COLUMN: + return G_TYPE_BOOLEAN; + default: + if (index < FM_LIST_MODEL_NUM_COLUMNS + FM_LIST_MODEL (tree_model)->details->columns->len) + { + return G_TYPE_STRING; + } + else + { + return G_TYPE_INVALID; + } + } +} + +static void +fm_list_model_ptr_to_iter (FMListModel *model, GSequenceIter *ptr, GtkTreeIter *iter) +{ + g_assert (!g_sequence_iter_is_end (ptr)); + if (iter != NULL) + { + iter->stamp = model->details->stamp; + iter->user_data = ptr; + } +} + +static gboolean +fm_list_model_get_iter (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreePath *path) +{ + FMListModel *model; + GSequence *files; + GSequenceIter *ptr; + FileEntry *file_entry; + int i, d; + + model = (FMListModel *)tree_model; + ptr = NULL; + + files = model->details->files; + for (d = 0; d < gtk_tree_path_get_depth (path); d++) + { + i = gtk_tree_path_get_indices (path)[d]; + + if (files == NULL || i >= g_sequence_get_length (files)) + { + return FALSE; + } + + ptr = g_sequence_get_iter_at_pos (files, i); + file_entry = g_sequence_get (ptr); + files = file_entry->files; + } + + fm_list_model_ptr_to_iter (model, ptr, iter); + + return TRUE; +} + +static GtkTreePath * +fm_list_model_get_path (GtkTreeModel *tree_model, GtkTreeIter *iter) +{ + GtkTreePath *path; + FMListModel *model; + GSequenceIter *ptr; + FileEntry *file_entry; + + + model = (FMListModel *)tree_model; + + g_return_val_if_fail (iter->stamp == model->details->stamp, NULL); + + if (g_sequence_iter_is_end (iter->user_data)) + { + /* FIXME is this right? */ + return NULL; + } + + path = gtk_tree_path_new (); + ptr = iter->user_data; + while (ptr != NULL) + { + gtk_tree_path_prepend_index (path, g_sequence_iter_get_position (ptr)); + file_entry = g_sequence_get (ptr); + if (file_entry->parent != NULL) + { + ptr = file_entry->parent->ptr; + } + else + { + ptr = NULL; + } + } + + return path; +} + +static void +fm_list_model_get_value (GtkTreeModel *tree_model, GtkTreeIter *iter, int column, GValue *value) +{ + FMListModel *model; + FileEntry *file_entry; + CajaFile *file; + char *str; + GdkPixbuf *icon, *rendered_icon; + int icon_size; + guint emblem_size; + CajaZoomLevel zoom_level; + GList *emblem_pixbufs; + CajaFile *parent_file; + char *emblems_to_ignore[3]; + int i; + CajaFileIconFlags flags; + + model = (FMListModel *)tree_model; + + g_return_if_fail (model->details->stamp == iter->stamp); + g_return_if_fail (!g_sequence_iter_is_end (iter->user_data)); + + file_entry = g_sequence_get (iter->user_data); + file = file_entry->file; + + switch (column) + { + case FM_LIST_MODEL_FILE_COLUMN: + g_value_init (value, CAJA_TYPE_FILE); + + g_value_set_object (value, file); + break; + case FM_LIST_MODEL_SUBDIRECTORY_COLUMN: + g_value_init (value, CAJA_TYPE_DIRECTORY); + + g_value_set_object (value, file_entry->subdirectory); + break; + case FM_LIST_MODEL_SMALLEST_ICON_COLUMN: + case FM_LIST_MODEL_SMALLER_ICON_COLUMN: + case FM_LIST_MODEL_SMALL_ICON_COLUMN: + case FM_LIST_MODEL_STANDARD_ICON_COLUMN: + case FM_LIST_MODEL_LARGE_ICON_COLUMN: + case FM_LIST_MODEL_LARGER_ICON_COLUMN: + case FM_LIST_MODEL_LARGEST_ICON_COLUMN: + g_value_init (value, GDK_TYPE_PIXBUF); + + if (file != NULL) + { + zoom_level = fm_list_model_get_zoom_level_from_column_id (column); + icon_size = caja_get_icon_size_for_zoom_level (zoom_level); + + flags = CAJA_FILE_ICON_FLAGS_USE_THUMBNAILS | + CAJA_FILE_ICON_FLAGS_FORCE_THUMBNAIL_SIZE | + CAJA_FILE_ICON_FLAGS_USE_MOUNT_ICON_AS_EMBLEM; + if (model->details->drag_view != NULL) + { + GtkTreePath *path_a, *path_b; + + gtk_tree_view_get_drag_dest_row (model->details->drag_view, + &path_a, + NULL); + if (path_a != NULL) + { + path_b = gtk_tree_model_get_path (tree_model, iter); + + if (gtk_tree_path_compare (path_a, path_b) == 0) + { + flags |= CAJA_FILE_ICON_FLAGS_FOR_DRAG_ACCEPT; + } + + gtk_tree_path_free (path_a); + gtk_tree_path_free (path_b); + } + } + + icon = caja_file_get_icon_pixbuf (file, icon_size, TRUE, flags); + + if (model->details->highlight_files != NULL && + g_list_find_custom (model->details->highlight_files, + file, (GCompareFunc) caja_file_compare_location)) + { + rendered_icon = eel_gdk_pixbuf_render (icon, 1, 255, 255, 0, 0); + + if (rendered_icon != NULL) + { + g_object_unref (icon); + icon = rendered_icon; + } + } + + g_value_set_object (value, icon); + g_object_unref (icon); + } + break; + case FM_LIST_MODEL_SMALLEST_EMBLEM_COLUMN: + case FM_LIST_MODEL_SMALLER_EMBLEM_COLUMN: + case FM_LIST_MODEL_SMALL_EMBLEM_COLUMN: + case FM_LIST_MODEL_STANDARD_EMBLEM_COLUMN: + case FM_LIST_MODEL_LARGE_EMBLEM_COLUMN: + case FM_LIST_MODEL_LARGER_EMBLEM_COLUMN: + case FM_LIST_MODEL_LARGEST_EMBLEM_COLUMN: + g_value_init (value, GDK_TYPE_PIXBUF); + + if (file != NULL) + { + parent_file = caja_file_get_parent (file); + i = 0; + emblems_to_ignore[i++] = CAJA_FILE_EMBLEM_NAME_TRASH; + if (parent_file) + { + if (!caja_file_can_write (parent_file)) + { + emblems_to_ignore[i++] = CAJA_FILE_EMBLEM_NAME_CANT_WRITE; + } + caja_file_unref (parent_file); + } + emblems_to_ignore[i++] = NULL; + + zoom_level = fm_list_model_get_zoom_level_from_emblem_column_id (column); + icon_size = caja_get_icon_size_for_zoom_level (zoom_level); + emblem_size = caja_icon_get_emblem_size_for_icon_size (icon_size); + if (emblem_size != 0) + { + emblem_pixbufs = caja_file_get_emblem_pixbufs (file, + emblem_size, + TRUE, + emblems_to_ignore); + if (emblem_pixbufs != NULL) + { + icon = emblem_pixbufs->data; + g_value_set_object (value, icon); + } + eel_gdk_pixbuf_list_free (emblem_pixbufs); + } + } + break; + case FM_LIST_MODEL_FILE_NAME_IS_EDITABLE_COLUMN: + g_value_init (value, G_TYPE_BOOLEAN); + + g_value_set_boolean (value, file != NULL && caja_file_can_rename (file)); + break; + default: + if (column >= FM_LIST_MODEL_NUM_COLUMNS || column < FM_LIST_MODEL_NUM_COLUMNS + model->details->columns->len) + { + CajaColumn *caja_column; + GQuark attribute; + caja_column = model->details->columns->pdata[column - FM_LIST_MODEL_NUM_COLUMNS]; + + g_value_init (value, G_TYPE_STRING); + g_object_get (caja_column, + "attribute_q", &attribute, + NULL); + if (file != NULL) + { + str = caja_file_get_string_attribute_with_default_q (file, + attribute); + g_value_take_string (value, str); + } + else if (attribute == attribute_name_q) + { + if (file_entry->parent->loaded) + { + g_value_set_string (value, _("(Empty)")); + } + else + { + g_value_set_string (value, _("Loading...")); + } + } + } + else + { + g_assert_not_reached (); + } + } +} + +static gboolean +fm_list_model_iter_next (GtkTreeModel *tree_model, GtkTreeIter *iter) +{ + FMListModel *model; + + model = (FMListModel *)tree_model; + + g_return_val_if_fail (model->details->stamp == iter->stamp, FALSE); + + iter->user_data = g_sequence_iter_next (iter->user_data); + + return !g_sequence_iter_is_end (iter->user_data); +} + +static gboolean +fm_list_model_iter_children (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *parent) +{ + FMListModel *model; + GSequence *files; + FileEntry *file_entry; + + model = (FMListModel *)tree_model; + + if (parent == NULL) + { + files = model->details->files; + } + else + { + file_entry = g_sequence_get (parent->user_data); + files = file_entry->files; + } + + if (files == NULL || g_sequence_get_length (files) == 0) + { + return FALSE; + } + + iter->stamp = model->details->stamp; + iter->user_data = g_sequence_get_begin_iter (files); + + return TRUE; +} + +static gboolean +fm_list_model_iter_has_child (GtkTreeModel *tree_model, GtkTreeIter *iter) +{ + FileEntry *file_entry; + + if (iter == NULL) + { + return !fm_list_model_is_empty (FM_LIST_MODEL (tree_model)); + } + + file_entry = g_sequence_get (iter->user_data); + + return (file_entry->files != NULL && g_sequence_get_length (file_entry->files) > 0); +} + +static int +fm_list_model_iter_n_children (GtkTreeModel *tree_model, GtkTreeIter *iter) +{ + FMListModel *model; + GSequence *files; + FileEntry *file_entry; + + model = (FMListModel *)tree_model; + + if (iter == NULL) + { + files = model->details->files; + } + else + { + file_entry = g_sequence_get (iter->user_data); + files = file_entry->files; + } + + return g_sequence_get_length (files); +} + +static gboolean +fm_list_model_iter_nth_child (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *parent, int n) +{ + FMListModel *model; + GSequenceIter *child; + GSequence *files; + FileEntry *file_entry; + + model = (FMListModel *)tree_model; + + if (parent != NULL) + { + file_entry = g_sequence_get (parent->user_data); + files = file_entry->files; + } + else + { + files = model->details->files; + } + + child = g_sequence_get_iter_at_pos (files, n); + + if (g_sequence_iter_is_end (child)) + { + return FALSE; + } + + iter->stamp = model->details->stamp; + iter->user_data = child; + + return TRUE; +} + +static gboolean +fm_list_model_iter_parent (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *child) +{ + FMListModel *model; + FileEntry *file_entry; + + model = (FMListModel *)tree_model; + + file_entry = g_sequence_get (child->user_data); + + if (file_entry->parent == NULL) + { + return FALSE; + } + + iter->stamp = model->details->stamp; + iter->user_data = file_entry->parent->ptr; + + return TRUE; +} + +static GSequenceIter * +lookup_file (FMListModel *model, CajaFile *file, + CajaDirectory *directory) +{ + FileEntry *file_entry; + GSequenceIter *ptr, *parent_ptr; + + parent_ptr = NULL; + if (directory) + { + parent_ptr = g_hash_table_lookup (model->details->directory_reverse_map, + directory); + } + + if (parent_ptr) + { + file_entry = g_sequence_get (parent_ptr); + ptr = g_hash_table_lookup (file_entry->reverse_map, file); + } + else + { + ptr = g_hash_table_lookup (model->details->top_reverse_map, file); + } + + if (ptr) + { + g_assert (((FileEntry *)g_sequence_get (ptr))->file == file); + } + + return ptr; +} + + +struct GetIters +{ + FMListModel *model; + CajaFile *file; + GList *iters; +}; + +static void +dir_to_iters (struct GetIters *data, + GHashTable *reverse_map) +{ + GSequenceIter *ptr; + + ptr = g_hash_table_lookup (reverse_map, data->file); + if (ptr) + { + GtkTreeIter *iter; + iter = g_new0 (GtkTreeIter, 1); + fm_list_model_ptr_to_iter (data->model, ptr, iter); + data->iters = g_list_prepend (data->iters, iter); + } +} + +static void +file_to_iter_cb (gpointer key, + gpointer value, + gpointer user_data) +{ + struct GetIters *data; + FileEntry *dir_file_entry; + + data = user_data; + dir_file_entry = g_sequence_get ((GSequenceIter *)value); + dir_to_iters (data, dir_file_entry->reverse_map); +} + +GList * +fm_list_model_get_all_iters_for_file (FMListModel *model, CajaFile *file) +{ + struct GetIters data; + + data.file = file; + data.model = model; + data.iters = NULL; + + dir_to_iters (&data, model->details->top_reverse_map); + g_hash_table_foreach (model->details->directory_reverse_map, + file_to_iter_cb, &data); + + return g_list_reverse (data.iters); +} + +gboolean +fm_list_model_get_first_iter_for_file (FMListModel *model, + CajaFile *file, + GtkTreeIter *iter) +{ + GList *list; + gboolean res; + + res = FALSE; + + list = fm_list_model_get_all_iters_for_file (model, file); + if (list != NULL) + { + res = TRUE; + *iter = *(GtkTreeIter *)list->data; + } + eel_g_list_free_deep (list); + + return res; +} + + +gboolean +fm_list_model_get_tree_iter_from_file (FMListModel *model, CajaFile *file, + CajaDirectory *directory, + GtkTreeIter *iter) +{ + GSequenceIter *ptr; + + ptr = lookup_file (model, file, directory); + if (!ptr) + { + return FALSE; + } + + fm_list_model_ptr_to_iter (model, ptr, iter); + + return TRUE; +} + +static int +fm_list_model_file_entry_compare_func (gconstpointer a, + gconstpointer b, + gpointer user_data) +{ + FileEntry *file_entry1; + FileEntry *file_entry2; + FMListModel *model; + int result; + + model = (FMListModel *)user_data; + + file_entry1 = (FileEntry *)a; + file_entry2 = (FileEntry *)b; + + if (file_entry1->file != NULL && file_entry2->file != NULL) + { + result = caja_file_compare_for_sort_by_attribute_q (file_entry1->file, file_entry2->file, + model->details->sort_attribute, + model->details->sort_directories_first, + (model->details->order == GTK_SORT_DESCENDING)); + } + else if (file_entry1->file == NULL) + { + return -1; + } + else + { + return 1; + } + + return result; +} + +int +fm_list_model_compare_func (FMListModel *model, + CajaFile *file1, + CajaFile *file2) +{ + int result; + + result = caja_file_compare_for_sort_by_attribute_q (file1, file2, + model->details->sort_attribute, + model->details->sort_directories_first, + (model->details->order == GTK_SORT_DESCENDING)); + + return result; +} + +static void +fm_list_model_sort_file_entries (FMListModel *model, GSequence *files, GtkTreePath *path) +{ + GSequenceIter **old_order; + GtkTreeIter iter; + int *new_order; + int length; + int i; + FileEntry *file_entry; + gboolean has_iter; + + length = g_sequence_get_length (files); + + if (length <= 1) + { + return; + } + + /* generate old order of GSequenceIter's */ + old_order = g_new (GSequenceIter *, length); + for (i = 0; i < length; ++i) + { + GSequenceIter *ptr = g_sequence_get_iter_at_pos (files, i); + + file_entry = g_sequence_get (ptr); + if (file_entry->files != NULL) + { + gtk_tree_path_append_index (path, i); + fm_list_model_sort_file_entries (model, file_entry->files, path); + gtk_tree_path_up (path); + } + + old_order[i] = ptr; + } + + /* sort */ + g_sequence_sort (files, fm_list_model_file_entry_compare_func, model); + + /* generate new order */ + new_order = g_new (int, length); + /* Note: new_order[newpos] = oldpos */ + for (i = 0; i < length; ++i) + { + new_order[g_sequence_iter_get_position (old_order[i])] = i; + } + + /* Let the world know about our new order */ + + g_assert (new_order != NULL); + + has_iter = FALSE; + if (gtk_tree_path_get_depth (path) != 0) + { + gboolean get_iter_result; + has_iter = TRUE; + get_iter_result = gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path); + g_assert (get_iter_result); + } + + gtk_tree_model_rows_reordered (GTK_TREE_MODEL (model), + path, has_iter ? &iter : NULL, new_order); + + g_free (old_order); + g_free (new_order); +} + +static void +fm_list_model_sort (FMListModel *model) +{ + GtkTreePath *path; + + path = gtk_tree_path_new (); + + fm_list_model_sort_file_entries (model, model->details->files, path); + + gtk_tree_path_free (path); +} + +static gboolean +fm_list_model_get_sort_column_id (GtkTreeSortable *sortable, + gint *sort_column_id, + GtkSortType *order) +{ + FMListModel *model; + int id; + + model = (FMListModel *)sortable; + + id = fm_list_model_get_sort_column_id_from_attribute + (model, model->details->sort_attribute); + + if (id == -1) + { + return FALSE; + } + + if (sort_column_id != NULL) + { + *sort_column_id = id; + } + + if (order != NULL) + { + *order = model->details->order; + } + + return TRUE; +} + +static void +fm_list_model_set_sort_column_id (GtkTreeSortable *sortable, gint sort_column_id, GtkSortType order) +{ + FMListModel *model; + + model = (FMListModel *)sortable; + + model->details->sort_attribute = fm_list_model_get_attribute_from_sort_column_id (model, sort_column_id); + + model->details->order = order; + + fm_list_model_sort (model); + gtk_tree_sortable_sort_column_changed (sortable); +} + +static gboolean +fm_list_model_has_default_sort_func (GtkTreeSortable *sortable) +{ + return FALSE; +} + +static gboolean +fm_list_model_multi_row_draggable (EggTreeMultiDragSource *drag_source, GList *path_list) +{ + return TRUE; +} + +static void +each_path_get_data_binder (CajaDragEachSelectedItemDataGet data_get, + gpointer context, + gpointer data) +{ + DragDataGetInfo *info; + GList *l; + CajaFile *file; + GtkTreeRowReference *row; + GtkTreePath *path; + char *uri; + GdkRectangle cell_area; + GtkTreeViewColumn *column; + + info = context; + + g_return_if_fail (info->model->details->drag_view); + + column = gtk_tree_view_get_column (info->model->details->drag_view, 0); + + for (l = info->path_list; l != NULL; l = l->next) + { + row = l->data; + + path = gtk_tree_row_reference_get_path (row); + file = fm_list_model_file_for_path (info->model, path); + if (file) + { + gtk_tree_view_get_cell_area + (info->model->details->drag_view, + path, + column, + &cell_area); + + uri = caja_file_get_uri (file); + + (*data_get) (uri, + 0, + cell_area.y - info->model->details->drag_begin_y, + cell_area.width, cell_area.height, + data); + + g_free (uri); + + caja_file_unref (file); + } + + gtk_tree_path_free (path); + } +} + +static gboolean +fm_list_model_multi_drag_data_get (EggTreeMultiDragSource *drag_source, + GList *path_list, + GtkSelectionData *selection_data) +{ + FMListModel *model; + DragDataGetInfo context; + guint target_info; + + model = FM_LIST_MODEL (drag_source); + + context.model = model; + context.path_list = path_list; + + if (!drag_target_list) + { + drag_target_list = fm_list_model_get_drag_target_list (); + } + + if (gtk_target_list_find (drag_target_list, + gtk_selection_data_get_target (selection_data), + &target_info)) + { + caja_drag_drag_data_get (NULL, + NULL, + selection_data, + target_info, + GDK_CURRENT_TIME, + &context, + each_path_get_data_binder); + return TRUE; + } + else + { + return FALSE; + } +} + +static gboolean +fm_list_model_multi_drag_data_delete (EggTreeMultiDragSource *drag_source, GList *path_list) +{ + return TRUE; +} + +static void +add_dummy_row (FMListModel *model, FileEntry *parent_entry) +{ + FileEntry *dummy_file_entry; + GtkTreeIter iter; + GtkTreePath *path; + + dummy_file_entry = g_new0 (FileEntry, 1); + dummy_file_entry->parent = parent_entry; + dummy_file_entry->ptr = g_sequence_insert_sorted (parent_entry->files, dummy_file_entry, + fm_list_model_file_entry_compare_func, model); + iter.stamp = model->details->stamp; + iter.user_data = dummy_file_entry->ptr; + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); + gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter); + gtk_tree_path_free (path); +} + +gboolean +fm_list_model_add_file (FMListModel *model, CajaFile *file, + CajaDirectory *directory) +{ + GtkTreeIter iter; + GtkTreePath *path; + FileEntry *file_entry; + GSequenceIter *ptr, *parent_ptr; + GSequence *files; + gboolean replace_dummy; + GHashTable *parent_hash; + + parent_ptr = g_hash_table_lookup (model->details->directory_reverse_map, + directory); + if (parent_ptr) + { + file_entry = g_sequence_get (parent_ptr); + ptr = g_hash_table_lookup (file_entry->reverse_map, file); + } + else + { + file_entry = NULL; + ptr = g_hash_table_lookup (model->details->top_reverse_map, file); + } + + if (ptr != NULL) + { + g_warning ("file already in tree (parent_ptr: %p)!!!\n", parent_ptr); + return FALSE; + } + + file_entry = g_new0 (FileEntry, 1); + file_entry->file = caja_file_ref (file); + file_entry->parent = NULL; + file_entry->subdirectory = NULL; + file_entry->files = NULL; + + files = model->details->files; + parent_hash = model->details->top_reverse_map; + + replace_dummy = FALSE; + + if (parent_ptr != NULL) + { + file_entry->parent = g_sequence_get (parent_ptr); + /* At this point we set loaded. Either we saw + * "done" and ignored it waiting for this, or we do this + * earlier, but then we replace the dummy row anyway, + * so it doesn't matter */ + file_entry->parent->loaded = 1; + parent_hash = file_entry->parent->reverse_map; + files = file_entry->parent->files; + if (g_sequence_get_length (files) == 1) + { + GSequenceIter *dummy_ptr = g_sequence_get_iter_at_pos (files, 0); + FileEntry *dummy_entry = g_sequence_get (dummy_ptr); + if (dummy_entry->file == NULL) + { + /* replace the dummy loading entry */ + model->details->stamp++; + g_sequence_remove (dummy_ptr); + + replace_dummy = TRUE; + } + } + } + + + file_entry->ptr = g_sequence_insert_sorted (files, file_entry, + fm_list_model_file_entry_compare_func, model); + + g_hash_table_insert (parent_hash, file, file_entry->ptr); + + iter.stamp = model->details->stamp; + iter.user_data = file_entry->ptr; + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); + if (replace_dummy) + { + gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter); + } + else + { + gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter); + } + + if (caja_file_is_directory (file)) + { + file_entry->files = g_sequence_new ((GDestroyNotify)file_entry_free); + + add_dummy_row (model, file_entry); + + gtk_tree_model_row_has_child_toggled (GTK_TREE_MODEL (model), + path, &iter); + } + gtk_tree_path_free (path); + + return TRUE; +} + +void +fm_list_model_file_changed (FMListModel *model, CajaFile *file, + CajaDirectory *directory) +{ + FileEntry *parent_file_entry; + GtkTreeIter iter; + GtkTreePath *path, *parent_path; + GSequenceIter *ptr; + int pos_before, pos_after, length, i, old; + int *new_order; + gboolean has_iter; + GSequence *files; + + ptr = lookup_file (model, file, directory); + if (!ptr) + { + return; + } + + + pos_before = g_sequence_iter_get_position (ptr); + + g_sequence_sort_changed (ptr, fm_list_model_file_entry_compare_func, model); + + pos_after = g_sequence_iter_get_position (ptr); + + if (pos_before != pos_after) + { + /* The file moved, we need to send rows_reordered */ + + parent_file_entry = ((FileEntry *)g_sequence_get (ptr))->parent; + + if (parent_file_entry == NULL) + { + has_iter = FALSE; + parent_path = gtk_tree_path_new (); + files = model->details->files; + } + else + { + has_iter = TRUE; + fm_list_model_ptr_to_iter (model, parent_file_entry->ptr, &iter); + parent_path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); + files = parent_file_entry->files; + } + + length = g_sequence_get_length (files); + new_order = g_new (int, length); + /* Note: new_order[newpos] = oldpos */ + for (i = 0, old = 0; i < length; ++i) + { + if (i == pos_after) + { + new_order[i] = pos_before; + } + else + { + if (old == pos_before) + old++; + new_order[i] = old++; + } + } + + gtk_tree_model_rows_reordered (GTK_TREE_MODEL (model), + parent_path, has_iter ? &iter : NULL, new_order); + + gtk_tree_path_free (parent_path); + g_free (new_order); + } + + fm_list_model_ptr_to_iter (model, ptr, &iter); + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); + gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter); + gtk_tree_path_free (path); +} + +gboolean +fm_list_model_is_empty (FMListModel *model) +{ + return (g_sequence_get_length (model->details->files) == 0); +} + +guint +fm_list_model_get_length (FMListModel *model) +{ + return g_sequence_get_length (model->details->files); +} + +static void +fm_list_model_remove (FMListModel *model, GtkTreeIter *iter) +{ + GSequenceIter *ptr, *child_ptr; + FileEntry *file_entry, *child_file_entry, *parent_file_entry; + GtkTreePath *path; + GtkTreeIter parent_iter; + + ptr = iter->user_data; + file_entry = g_sequence_get (ptr); + + if (file_entry->files != NULL) + { + while (g_sequence_get_length (file_entry->files) > 0) + { + child_ptr = g_sequence_get_begin_iter (file_entry->files); + child_file_entry = g_sequence_get (child_ptr); + if (child_file_entry->file != NULL) + { + fm_list_model_remove_file (model, + child_file_entry->file, + file_entry->subdirectory); + } + else + { + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter); + gtk_tree_path_append_index (path, 0); + model->details->stamp++; + g_sequence_remove (child_ptr); + gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path); + gtk_tree_path_free (path); + } + + /* the parent iter didn't actually change */ + iter->stamp = model->details->stamp; + } + + } + + if (file_entry->file != NULL) /* Don't try to remove dummy row */ + { + if (file_entry->parent != NULL) + { + g_hash_table_remove (file_entry->parent->reverse_map, file_entry->file); + } + else + { + g_hash_table_remove (model->details->top_reverse_map, file_entry->file); + } + } + + parent_file_entry = file_entry->parent; + if (parent_file_entry && g_sequence_get_length (parent_file_entry->files) == 1 && + file_entry->file != NULL) + { + /* this is the last non-dummy child, add a dummy node */ + /* We need to do this before removing the last file to avoid + * collapsing the row. + */ + add_dummy_row (model, parent_file_entry); + } + + if (file_entry->subdirectory != NULL) + { + g_signal_emit (model, + list_model_signals[SUBDIRECTORY_UNLOADED], 0, + file_entry->subdirectory); + g_hash_table_remove (model->details->directory_reverse_map, + file_entry->subdirectory); + } + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter); + + g_sequence_remove (ptr); + model->details->stamp++; + gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path); + + gtk_tree_path_free (path); + + if (parent_file_entry && g_sequence_get_length (parent_file_entry->files) == 0) + { + parent_iter.stamp = model->details->stamp; + parent_iter.user_data = parent_file_entry->ptr; + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &parent_iter); + gtk_tree_model_row_has_child_toggled (GTK_TREE_MODEL (model), + path, &parent_iter); + gtk_tree_path_free (path); + } +} + +void +fm_list_model_remove_file (FMListModel *model, CajaFile *file, + CajaDirectory *directory) +{ + GtkTreeIter iter; + + if (fm_list_model_get_tree_iter_from_file (model, file, directory, &iter)) + { + fm_list_model_remove (model, &iter); + } +} + +static void +fm_list_model_clear_directory (FMListModel *model, GSequence *files) +{ + GtkTreeIter iter; + FileEntry *file_entry; + + while (g_sequence_get_length (files) > 0) + { + iter.user_data = g_sequence_get_begin_iter (files); + + file_entry = g_sequence_get (iter.user_data); + if (file_entry->files != NULL) + { + fm_list_model_clear_directory (model, file_entry->files); + } + + iter.stamp = model->details->stamp; + fm_list_model_remove (model, &iter); + } +} + +void +fm_list_model_clear (FMListModel *model) +{ + g_return_if_fail (model != NULL); + + fm_list_model_clear_directory (model, model->details->files); +} + +CajaFile * +fm_list_model_file_for_path (FMListModel *model, GtkTreePath *path) +{ + CajaFile *file; + GtkTreeIter iter; + + file = NULL; + if (gtk_tree_model_get_iter (GTK_TREE_MODEL (model), + &iter, path)) + { + gtk_tree_model_get (GTK_TREE_MODEL (model), + &iter, + FM_LIST_MODEL_FILE_COLUMN, &file, + -1); + } + return file; +} + +gboolean +fm_list_model_load_subdirectory (FMListModel *model, GtkTreePath *path, CajaDirectory **directory) +{ + GtkTreeIter iter; + FileEntry *file_entry; + CajaDirectory *subdirectory; + + if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path)) + { + return FALSE; + } + + file_entry = g_sequence_get (iter.user_data); + if (file_entry->file == NULL || + file_entry->subdirectory != NULL) + { + return FALSE; + } + + subdirectory = caja_directory_get_for_file (file_entry->file); + + if (g_hash_table_lookup (model->details->directory_reverse_map, + subdirectory) != NULL) + { + caja_directory_unref (subdirectory); + g_warning ("Already in directory_reverse_map, failing\n"); + return FALSE; + } + + file_entry->subdirectory = subdirectory, + g_hash_table_insert (model->details->directory_reverse_map, + subdirectory, file_entry->ptr); + file_entry->reverse_map = g_hash_table_new (g_direct_hash, g_direct_equal); + + /* Return a ref too */ + caja_directory_ref (subdirectory); + *directory = subdirectory; + + return TRUE; +} + +/* removes all children of the subfolder and unloads the subdirectory */ +void +fm_list_model_unload_subdirectory (FMListModel *model, GtkTreeIter *iter) +{ + GSequenceIter *child_ptr; + FileEntry *file_entry, *child_file_entry; + GtkTreeIter child_iter; + + file_entry = g_sequence_get (iter->user_data); + if (file_entry->file == NULL || + file_entry->subdirectory == NULL) + { + return; + } + + file_entry->loaded = 0; + + /* Remove all children */ + while (g_sequence_get_length (file_entry->files) > 0) + { + child_ptr = g_sequence_get_begin_iter (file_entry->files); + child_file_entry = g_sequence_get (child_ptr); + if (child_file_entry->file == NULL) + { + /* Don't delete the dummy node */ + break; + } + else + { + fm_list_model_ptr_to_iter (model, child_ptr, &child_iter); + fm_list_model_remove (model, &child_iter); + } + } + + /* Emit unload signal */ + g_signal_emit (model, + list_model_signals[SUBDIRECTORY_UNLOADED], 0, + file_entry->subdirectory); + + /* actually unload */ + g_hash_table_remove (model->details->directory_reverse_map, + file_entry->subdirectory); + caja_directory_unref (file_entry->subdirectory); + file_entry->subdirectory = NULL; + + g_assert (g_hash_table_size (file_entry->reverse_map) == 0); + g_hash_table_destroy (file_entry->reverse_map); + file_entry->reverse_map = NULL; +} + + + +void +fm_list_model_set_should_sort_directories_first (FMListModel *model, gboolean sort_directories_first) +{ + if (model->details->sort_directories_first == sort_directories_first) + { + return; + } + + model->details->sort_directories_first = sort_directories_first; + fm_list_model_sort (model); +} + +int +fm_list_model_get_sort_column_id_from_attribute (FMListModel *model, + GQuark attribute) +{ + guint i; + + if (attribute == 0) + { + return -1; + } + + /* Hack - the preferences dialog sets modification_date for some + * rather than date_modified for some reason. Make sure that + * works. */ + if (attribute == attribute_modification_date_q) + { + attribute = attribute_date_modified_q; + } + + for (i = 0; i < model->details->columns->len; i++) + { + CajaColumn *column; + GQuark column_attribute; + + column = + CAJA_COLUMN (model->details->columns->pdata[i]); + g_object_get (G_OBJECT (column), + "attribute_q", &column_attribute, + NULL); + if (column_attribute == attribute) + { + return FM_LIST_MODEL_NUM_COLUMNS + i; + } + } + + return -1; +} + +GQuark +fm_list_model_get_attribute_from_sort_column_id (FMListModel *model, + int sort_column_id) +{ + CajaColumn *column; + int index; + GQuark attribute; + + index = sort_column_id - FM_LIST_MODEL_NUM_COLUMNS; + + if (index < 0 || index >= model->details->columns->len) + { + g_warning ("unknown sort column id: %d", sort_column_id); + return 0; + } + + column = CAJA_COLUMN (model->details->columns->pdata[index]); + g_object_get (G_OBJECT (column), "attribute_q", &attribute, NULL); + + return attribute; +} + +CajaZoomLevel +fm_list_model_get_zoom_level_from_column_id (int column) +{ + switch (column) + { + case FM_LIST_MODEL_SMALLEST_ICON_COLUMN: + return CAJA_ZOOM_LEVEL_SMALLEST; + case FM_LIST_MODEL_SMALLER_ICON_COLUMN: + return CAJA_ZOOM_LEVEL_SMALLER; + case FM_LIST_MODEL_SMALL_ICON_COLUMN: + return CAJA_ZOOM_LEVEL_SMALL; + case FM_LIST_MODEL_STANDARD_ICON_COLUMN: + return CAJA_ZOOM_LEVEL_STANDARD; + case FM_LIST_MODEL_LARGE_ICON_COLUMN: + return CAJA_ZOOM_LEVEL_LARGE; + case FM_LIST_MODEL_LARGER_ICON_COLUMN: + return CAJA_ZOOM_LEVEL_LARGER; + case FM_LIST_MODEL_LARGEST_ICON_COLUMN: + return CAJA_ZOOM_LEVEL_LARGEST; + } + + g_return_val_if_reached (CAJA_ZOOM_LEVEL_STANDARD); +} + +int +fm_list_model_get_column_id_from_zoom_level (CajaZoomLevel zoom_level) +{ + switch (zoom_level) + { + case CAJA_ZOOM_LEVEL_SMALLEST: + return FM_LIST_MODEL_SMALLEST_ICON_COLUMN; + case CAJA_ZOOM_LEVEL_SMALLER: + return FM_LIST_MODEL_SMALLER_ICON_COLUMN; + case CAJA_ZOOM_LEVEL_SMALL: + return FM_LIST_MODEL_SMALL_ICON_COLUMN; + case CAJA_ZOOM_LEVEL_STANDARD: + return FM_LIST_MODEL_STANDARD_ICON_COLUMN; + case CAJA_ZOOM_LEVEL_LARGE: + return FM_LIST_MODEL_LARGE_ICON_COLUMN; + case CAJA_ZOOM_LEVEL_LARGER: + return FM_LIST_MODEL_LARGER_ICON_COLUMN; + case CAJA_ZOOM_LEVEL_LARGEST: + return FM_LIST_MODEL_LARGEST_ICON_COLUMN; + } + + g_return_val_if_reached (FM_LIST_MODEL_STANDARD_ICON_COLUMN); +} + +CajaZoomLevel +fm_list_model_get_zoom_level_from_emblem_column_id (int column) +{ + switch (column) + { + case FM_LIST_MODEL_SMALLEST_EMBLEM_COLUMN: + return CAJA_ZOOM_LEVEL_SMALLEST; + case FM_LIST_MODEL_SMALLER_EMBLEM_COLUMN: + return CAJA_ZOOM_LEVEL_SMALLER; + case FM_LIST_MODEL_SMALL_EMBLEM_COLUMN: + return CAJA_ZOOM_LEVEL_SMALL; + case FM_LIST_MODEL_STANDARD_EMBLEM_COLUMN: + return CAJA_ZOOM_LEVEL_STANDARD; + case FM_LIST_MODEL_LARGE_EMBLEM_COLUMN: + return CAJA_ZOOM_LEVEL_LARGE; + case FM_LIST_MODEL_LARGER_EMBLEM_COLUMN: + return CAJA_ZOOM_LEVEL_LARGER; + case FM_LIST_MODEL_LARGEST_EMBLEM_COLUMN: + return CAJA_ZOOM_LEVEL_LARGEST; + } + + g_return_val_if_reached (CAJA_ZOOM_LEVEL_STANDARD); +} + +int +fm_list_model_get_emblem_column_id_from_zoom_level (CajaZoomLevel zoom_level) +{ + switch (zoom_level) + { + case CAJA_ZOOM_LEVEL_SMALLEST: + return FM_LIST_MODEL_SMALLEST_EMBLEM_COLUMN; + case CAJA_ZOOM_LEVEL_SMALLER: + return FM_LIST_MODEL_SMALLER_EMBLEM_COLUMN; + case CAJA_ZOOM_LEVEL_SMALL: + return FM_LIST_MODEL_SMALL_EMBLEM_COLUMN; + case CAJA_ZOOM_LEVEL_STANDARD: + return FM_LIST_MODEL_STANDARD_EMBLEM_COLUMN; + case CAJA_ZOOM_LEVEL_LARGE: + return FM_LIST_MODEL_LARGE_EMBLEM_COLUMN; + case CAJA_ZOOM_LEVEL_LARGER: + return FM_LIST_MODEL_LARGER_EMBLEM_COLUMN; + case CAJA_ZOOM_LEVEL_LARGEST: + return FM_LIST_MODEL_LARGEST_EMBLEM_COLUMN; + } + + g_return_val_if_reached (FM_LIST_MODEL_STANDARD_EMBLEM_COLUMN); +} + +void +fm_list_model_set_drag_view (FMListModel *model, + GtkTreeView *view, + int drag_begin_x, + int drag_begin_y) +{ + g_return_if_fail (model != NULL); + g_return_if_fail (FM_IS_LIST_MODEL (model)); + g_return_if_fail (!view || GTK_IS_TREE_VIEW (view)); + + model->details->drag_view = view; + model->details->drag_begin_x = drag_begin_x; + model->details->drag_begin_y = drag_begin_y; +} + +GtkTargetList * +fm_list_model_get_drag_target_list () +{ + GtkTargetList *target_list; + + target_list = gtk_target_list_new (drag_types, G_N_ELEMENTS (drag_types)); + gtk_target_list_add_text_targets (target_list, CAJA_ICON_DND_TEXT); + + return target_list; +} + +int +fm_list_model_add_column (FMListModel *model, + CajaColumn *column) +{ + g_ptr_array_add (model->details->columns, column); + g_object_ref (column); + + return FM_LIST_MODEL_NUM_COLUMNS + (model->details->columns->len - 1); +} + +int +fm_list_model_get_column_number (FMListModel *model, + const char *column_name) +{ + int i; + + for (i = 0; i < model->details->columns->len; i++) + { + CajaColumn *column; + char *name; + + column = model->details->columns->pdata[i]; + + g_object_get (G_OBJECT (column), "name", &name, NULL); + + if (!strcmp (name, column_name)) + { + g_free (name); + return FM_LIST_MODEL_NUM_COLUMNS + i; + } + g_free (name); + } + + return -1; +} + +static void +fm_list_model_dispose (GObject *object) +{ + FMListModel *model; + int i; + + model = FM_LIST_MODEL (object); + + if (model->details->columns) + { + for (i = 0; i < model->details->columns->len; i++) + { + g_object_unref (model->details->columns->pdata[i]); + } + g_ptr_array_free (model->details->columns, TRUE); + model->details->columns = NULL; + } + + if (model->details->files) + { + g_sequence_free (model->details->files); + model->details->files = NULL; + } + + if (model->details->top_reverse_map) + { + g_hash_table_destroy (model->details->top_reverse_map); + model->details->top_reverse_map = NULL; + } + if (model->details->directory_reverse_map) + { + g_hash_table_destroy (model->details->directory_reverse_map); + model->details->directory_reverse_map = NULL; + } + + G_OBJECT_CLASS (fm_list_model_parent_class)->dispose (object); +} + +static void +fm_list_model_finalize (GObject *object) +{ + FMListModel *model; + + model = FM_LIST_MODEL (object); + + if (model->details->highlight_files != NULL) + { + caja_file_list_free (model->details->highlight_files); + model->details->highlight_files = NULL; + } + + g_free (model->details); + + G_OBJECT_CLASS (fm_list_model_parent_class)->finalize (object); +} + +static void +fm_list_model_init (FMListModel *model) +{ + model->details = g_new0 (FMListModelDetails, 1); + model->details->files = g_sequence_new ((GDestroyNotify)file_entry_free); + model->details->top_reverse_map = g_hash_table_new (g_direct_hash, g_direct_equal); + model->details->directory_reverse_map = g_hash_table_new (g_direct_hash, g_direct_equal); + model->details->stamp = g_random_int (); + model->details->sort_attribute = 0; + model->details->columns = g_ptr_array_new (); +} + +static void +fm_list_model_class_init (FMListModelClass *klass) +{ + GObjectClass *object_class; + + attribute_name_q = g_quark_from_static_string ("name"); + attribute_modification_date_q = g_quark_from_static_string ("modification_date"); + attribute_date_modified_q = g_quark_from_static_string ("date_modified"); + + object_class = (GObjectClass *)klass; + object_class->finalize = fm_list_model_finalize; + object_class->dispose = fm_list_model_dispose; + + list_model_signals[SUBDIRECTORY_UNLOADED] = + g_signal_new ("subdirectory_unloaded", + FM_TYPE_LIST_MODEL, + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (FMListModelClass, subdirectory_unloaded), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + CAJA_TYPE_DIRECTORY); +} + +static void +fm_list_model_tree_model_init (GtkTreeModelIface *iface) +{ + iface->get_flags = fm_list_model_get_flags; + iface->get_n_columns = fm_list_model_get_n_columns; + iface->get_column_type = fm_list_model_get_column_type; + iface->get_iter = fm_list_model_get_iter; + iface->get_path = fm_list_model_get_path; + iface->get_value = fm_list_model_get_value; + iface->iter_next = fm_list_model_iter_next; + iface->iter_children = fm_list_model_iter_children; + iface->iter_has_child = fm_list_model_iter_has_child; + iface->iter_n_children = fm_list_model_iter_n_children; + iface->iter_nth_child = fm_list_model_iter_nth_child; + iface->iter_parent = fm_list_model_iter_parent; +} + +static void +fm_list_model_sortable_init (GtkTreeSortableIface *iface) +{ + iface->get_sort_column_id = fm_list_model_get_sort_column_id; + iface->set_sort_column_id = fm_list_model_set_sort_column_id; + iface->has_default_sort_func = fm_list_model_has_default_sort_func; +} + +static void +fm_list_model_multi_drag_source_init (EggTreeMultiDragSourceIface *iface) +{ + iface->row_draggable = fm_list_model_multi_row_draggable; + iface->drag_data_get = fm_list_model_multi_drag_data_get; + iface->drag_data_delete = fm_list_model_multi_drag_data_delete; +} + +void +fm_list_model_subdirectory_done_loading (FMListModel *model, CajaDirectory *directory) +{ + GtkTreeIter iter; + GtkTreePath *path; + FileEntry *file_entry, *dummy_entry; + GSequenceIter *parent_ptr, *dummy_ptr; + GSequence *files; + + if (model == NULL || model->details->directory_reverse_map == NULL) + { + return; + } + parent_ptr = g_hash_table_lookup (model->details->directory_reverse_map, + directory); + if (parent_ptr == NULL) + { + return; + } + + file_entry = g_sequence_get (parent_ptr); + files = file_entry->files; + + /* Only swap loading -> empty if we saw no files yet at "done", + * otherwise, toggle loading at first added file to the model. + */ + if (!caja_directory_is_not_empty (directory) && + g_sequence_get_length (files) == 1) + { + dummy_ptr = g_sequence_get_iter_at_pos (file_entry->files, 0); + dummy_entry = g_sequence_get (dummy_ptr); + if (dummy_entry->file == NULL) + { + /* was the dummy file */ + file_entry->loaded = 1; + + iter.stamp = model->details->stamp; + iter.user_data = dummy_ptr; + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); + gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter); + gtk_tree_path_free (path); + } + } +} + +static void +refresh_row (gpointer data, + gpointer user_data) +{ + CajaFile *file; + FMListModel *model; + GList *iters, *l; + GtkTreePath *path; + + model = user_data; + file = data; + + iters = fm_list_model_get_all_iters_for_file (model, file); + for (l = iters; l != NULL; l = l->next) + { + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), l->data); + gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, l->data); + + gtk_tree_path_free (path); + } + + eel_g_list_free_deep (iters); +} + +void +fm_list_model_set_highlight_for_files (FMListModel *model, + GList *files) +{ + if (model->details->highlight_files != NULL) + { + g_list_foreach (model->details->highlight_files, + refresh_row, model); + caja_file_list_free (model->details->highlight_files); + model->details->highlight_files = NULL; + } + + if (files != NULL) + { + model->details->highlight_files = caja_file_list_copy (files); + g_list_foreach (model->details->highlight_files, + refresh_row, model); + + } +} diff --git a/src/file-manager/fm-list-model.h b/src/file-manager/fm-list-model.h new file mode 100644 index 00000000..49f39078 --- /dev/null +++ b/src/file-manager/fm-list-model.h @@ -0,0 +1,148 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-list-model.h - a GtkTreeModel for file lists. + + Copyright (C) 2001, 2002 Anders Carlsson + + 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: Anders Carlsson <[email protected]> +*/ + +#include <gtk/gtk.h> +#include <gdk/gdk.h> +#include <libcaja-private/caja-file.h> +#include <libcaja-private/caja-directory.h> +#include <libcaja-extension/caja-column.h> + +#ifndef FM_LIST_MODEL_H +#define FM_LIST_MODEL_H + +#define FM_TYPE_LIST_MODEL fm_list_model_get_type() +#define FM_LIST_MODEL(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_LIST_MODEL, FMListModel)) +#define FM_LIST_MODEL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_LIST_MODEL, FMListModelClass)) +#define FM_IS_LIST_MODEL(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_LIST_MODEL)) +#define FM_IS_LIST_MODEL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_LIST_MODEL)) +#define FM_LIST_MODEL_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_LIST_MODEL, FMListModelClass)) + +enum +{ + FM_LIST_MODEL_FILE_COLUMN, + FM_LIST_MODEL_SUBDIRECTORY_COLUMN, + FM_LIST_MODEL_SMALLEST_ICON_COLUMN, + FM_LIST_MODEL_SMALLER_ICON_COLUMN, + FM_LIST_MODEL_SMALL_ICON_COLUMN, + FM_LIST_MODEL_STANDARD_ICON_COLUMN, + FM_LIST_MODEL_LARGE_ICON_COLUMN, + FM_LIST_MODEL_LARGER_ICON_COLUMN, + FM_LIST_MODEL_LARGEST_ICON_COLUMN, + FM_LIST_MODEL_SMALLEST_EMBLEM_COLUMN, + FM_LIST_MODEL_SMALLER_EMBLEM_COLUMN, + FM_LIST_MODEL_SMALL_EMBLEM_COLUMN, + FM_LIST_MODEL_STANDARD_EMBLEM_COLUMN, + FM_LIST_MODEL_LARGE_EMBLEM_COLUMN, + FM_LIST_MODEL_LARGER_EMBLEM_COLUMN, + FM_LIST_MODEL_LARGEST_EMBLEM_COLUMN, + FM_LIST_MODEL_FILE_NAME_IS_EDITABLE_COLUMN, + FM_LIST_MODEL_NUM_COLUMNS +}; + +typedef struct FMListModelDetails FMListModelDetails; + +typedef struct FMListModel +{ + GObject parent_instance; + FMListModelDetails *details; +} FMListModel; + +typedef struct +{ + GObjectClass parent_class; + + void (* subdirectory_unloaded)(FMListModel *model, + CajaDirectory *subdirectory); +} FMListModelClass; + +GType fm_list_model_get_type (void); +gboolean fm_list_model_add_file (FMListModel *model, + CajaFile *file, + CajaDirectory *directory); +void fm_list_model_file_changed (FMListModel *model, + CajaFile *file, + CajaDirectory *directory); +gboolean fm_list_model_is_empty (FMListModel *model); +guint fm_list_model_get_length (FMListModel *model); +void fm_list_model_remove_file (FMListModel *model, + CajaFile *file, + CajaDirectory *directory); +void fm_list_model_clear (FMListModel *model); +gboolean fm_list_model_get_tree_iter_from_file (FMListModel *model, + CajaFile *file, + CajaDirectory *directory, + GtkTreeIter *iter); +GList * fm_list_model_get_all_iters_for_file (FMListModel *model, + CajaFile *file); +gboolean fm_list_model_get_first_iter_for_file (FMListModel *model, + CajaFile *file, + GtkTreeIter *iter); +void fm_list_model_set_should_sort_directories_first (FMListModel *model, + gboolean sort_directories_first); + +int fm_list_model_get_sort_column_id_from_attribute (FMListModel *model, + GQuark attribute); +GQuark fm_list_model_get_attribute_from_sort_column_id (FMListModel *model, + int sort_column_id); +void fm_list_model_sort_files (FMListModel *model, + GList **files); + +CajaZoomLevel fm_list_model_get_zoom_level_from_column_id (int column); +int fm_list_model_get_column_id_from_zoom_level (CajaZoomLevel zoom_level); +CajaZoomLevel fm_list_model_get_zoom_level_from_emblem_column_id (int column); +int fm_list_model_get_emblem_column_id_from_zoom_level (CajaZoomLevel zoom_level); + +CajaFile * fm_list_model_file_for_path (FMListModel *model, GtkTreePath *path); +gboolean fm_list_model_load_subdirectory (FMListModel *model, GtkTreePath *path, CajaDirectory **directory); +void fm_list_model_unload_subdirectory (FMListModel *model, GtkTreeIter *iter); + +void fm_list_model_set_drag_view (FMListModel *model, + GtkTreeView *view, + int begin_x, + int begin_y); + +GtkTargetList * fm_list_model_get_drag_target_list (void); + +int fm_list_model_compare_func (FMListModel *model, + CajaFile *file1, + CajaFile *file2); + + +int fm_list_model_add_column (FMListModel *model, + CajaColumn *column); +int fm_list_model_get_column_number (FMListModel *model, + const char *column_name); + +void fm_list_model_subdirectory_done_loading (FMListModel *model, + CajaDirectory *directory); + +void fm_list_model_set_highlight_for_files (FMListModel *model, + GList *files); + +#endif /* FM_LIST_MODEL_H */ diff --git a/src/file-manager/fm-list-view-private.h b/src/file-manager/fm-list-view-private.h new file mode 100644 index 00000000..2225cfca --- /dev/null +++ b/src/file-manager/fm-list-view-private.h @@ -0,0 +1,43 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-list-view-private.h - Private functions for both the list and search list + view to share + + 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: Rebecca Schulman <[email protected]> + +*/ + +struct FMListViewColumn +{ + const char *attribute; + const char *title; + CajaFileSortType sort_criterion; + int minimum_width, default_width, maximum_width; + gboolean right_justified; +}; + +void fm_list_view_column_set (FMListViewColumn *column, + const char *attribute, + const char *title, + CajaFileSortType sort_criterion, + int minimum_width, + int default_width, + int maximum_width, + gboolean right_justified); diff --git a/src/file-manager/fm-list-view.c b/src/file-manager/fm-list-view.c new file mode 100644 index 00000000..1c82af9b --- /dev/null +++ b/src/file-manager/fm-list-view.c @@ -0,0 +1,3415 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-list-view.c - implementation of list view of directory. + + Copyright (C) 2000 Eazel, Inc. + Copyright (C) 2001, 2002 Anders Carlsson <[email protected]> + + 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]> + Anders Carlsson <[email protected]> + David Emory Watson <[email protected]> +*/ + +#include <config.h> +#include "fm-list-view.h" + +#include <string.h> +#include "fm-error-reporting.h" +#include "fm-list-model.h" +#include <string.h> +#include <eel/eel-vfs-extensions.h> +#include <eel/eel-gdk-extensions.h> +#include <eel/eel-glib-extensions.h> +#include <eel/eel-gtk-macros.h> +#include <gdk/gdk.h> +#include <gdk/gdkkeysyms.h> +#include <gtk/gtk.h> +#include <libegg/eggtreemultidnd.h> +#include <glib/gi18n.h> +#include <glib-object.h> +#include <libcaja-extension/caja-column-provider.h> +#include <libcaja-private/caja-clipboard-monitor.h> +#include <libcaja-private/caja-column-chooser.h> +#include <libcaja-private/caja-column-utilities.h> +#include <libcaja-private/caja-debug-log.h> +#include <libcaja-private/caja-directory-background.h> +#include <libcaja-private/caja-dnd.h> +#include <libcaja-private/caja-file-dnd.h> +#include <libcaja-private/caja-file-utilities.h> +#include <libcaja-private/caja-ui-utilities.h> +#include <libcaja-private/caja-global-preferences.h> +#include <libcaja-private/caja-icon-dnd.h> +#include <libcaja-private/caja-metadata.h> +#include <libcaja-private/caja-module.h> +#include <libcaja-private/caja-tree-view-drag-dest.h> +#include <libcaja-private/caja-view-factory.h> +#include <libcaja-private/caja-clipboard.h> +#include <libcaja-private/caja-cell-renderer-pixbuf-emblem.h> +#include <libcaja-private/caja-cell-renderer-text-ellipsized.h> + +struct FMListViewDetails +{ + GtkTreeView *tree_view; + FMListModel *model; + GtkActionGroup *list_action_group; + guint list_merge_id; + + GtkTreeViewColumn *file_name_column; + int file_name_column_num; + + GtkCellRendererPixbuf *pixbuf_cell; + GtkCellRendererText *file_name_cell; + GList *cells; + GtkCellEditable *editable_widget; + + CajaZoomLevel zoom_level; + + CajaTreeViewDragDest *drag_dest; + + GtkTreePath *double_click_path[2]; /* Both clicks in a double click need to be on the same row */ + + GtkTreePath *new_selection_path; /* Path of the new selection after removing a file */ + + GtkTreePath *hover_path; + + guint drag_button; + int drag_x; + int drag_y; + + gboolean drag_started; + + gboolean ignore_button_release; + + gboolean row_selected_on_button_down; + + gboolean menus_ready; + + GHashTable *columns; + GtkWidget *column_editor; + + char *original_name; + + CajaFile *renaming_file; + gboolean rename_done; + guint renaming_file_activate_timeout; + + gulong clipboard_handler_id; + + GQuark last_sort_attr; +}; + +struct SelectionForeachData +{ + GList *list; + GtkTreeSelection *selection; +}; + +/* + * The row height should be large enough to not clip emblems. + * Computing this would be costly, so we just choose a number + * that works well with the set of emblems we've designed. + */ +#define LIST_VIEW_MINIMUM_ROW_HEIGHT 28 + +/* We wait two seconds after row is collapsed to unload the subdirectory */ +#define COLLAPSE_TO_UNLOAD_DELAY 2 + +/* Wait for the rename to end when activating a file being renamed */ +#define WAIT_FOR_RENAME_ON_ACTIVATE 200 + +static int click_policy_auto_value; +static char * default_sort_order_auto_value; +static gboolean default_sort_reversed_auto_value; +static CajaZoomLevel default_zoom_level_auto_value; +static char ** default_visible_columns_auto_value; +static char ** default_column_order_auto_value; +static GdkCursor * hand_cursor = NULL; + +static GtkTargetList * source_target_list = NULL; + +static GList *fm_list_view_get_selection (FMDirectoryView *view); +static GList *fm_list_view_get_selection_for_file_transfer (FMDirectoryView *view); +static void fm_list_view_set_zoom_level (FMListView *view, + CajaZoomLevel new_level, + gboolean always_set_level); +static void fm_list_view_scale_font_size (FMListView *view, + CajaZoomLevel new_level); +static void fm_list_view_scroll_to_file (FMListView *view, + CajaFile *file); +static void fm_list_view_iface_init (CajaViewIface *iface); +static void fm_list_view_rename_callback (CajaFile *file, + GFile *result_location, + GError *error, + gpointer callback_data); + + +G_DEFINE_TYPE_WITH_CODE (FMListView, fm_list_view, FM_TYPE_DIRECTORY_VIEW, + G_IMPLEMENT_INTERFACE (CAJA_TYPE_VIEW, + fm_list_view_iface_init)); + +static const char * default_trash_visible_columns[] = +{ + "name", "size", "type", "trashed_on", "trash_orig_path", NULL +}; + +static const char * default_trash_columns_order[] = +{ + "name", "size", "type", "trashed_on", "trash_orig_path", NULL +}; + +/* for EEL_CALL_PARENT */ +#define parent_class fm_list_view_parent_class + +static const gchar* +get_default_sort_order (CajaFile *file, gboolean *reversed) +{ + const gchar *retval; + + retval = caja_file_get_default_sort_attribute (file, reversed); + + if (retval == NULL) + { + retval = default_sort_order_auto_value; + *reversed = default_sort_reversed_auto_value; + } + + return retval; +} + +static void +list_selection_changed_callback (GtkTreeSelection *selection, gpointer user_data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (user_data); + + fm_directory_view_notify_selection_changed (view); +} + +/* Move these to eel? */ + +static void +tree_selection_foreach_set_boolean (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer callback_data) +{ + * (gboolean *) callback_data = TRUE; +} + +static gboolean +tree_selection_not_empty (GtkTreeSelection *selection) +{ + gboolean not_empty; + + not_empty = FALSE; + gtk_tree_selection_selected_foreach (selection, + tree_selection_foreach_set_boolean, + ¬_empty); + return not_empty; +} + +static gboolean +tree_view_has_selection (GtkTreeView *view) +{ + return tree_selection_not_empty (gtk_tree_view_get_selection (view)); +} + +static void +activate_selected_items (FMListView *view) +{ + GList *file_list; + + file_list = fm_list_view_get_selection (FM_DIRECTORY_VIEW (view)); + + + if (view->details->renaming_file) + { + /* We're currently renaming a file, wait until the rename is + finished, or the activation uri will be wrong */ + if (view->details->renaming_file_activate_timeout == 0) + { + view->details->renaming_file_activate_timeout = + g_timeout_add (WAIT_FOR_RENAME_ON_ACTIVATE, (GSourceFunc) activate_selected_items, view); + } + return; + } + + if (view->details->renaming_file_activate_timeout != 0) + { + g_source_remove (view->details->renaming_file_activate_timeout); + view->details->renaming_file_activate_timeout = 0; + } + + fm_directory_view_activate_files (FM_DIRECTORY_VIEW (view), + file_list, + CAJA_WINDOW_OPEN_ACCORDING_TO_MODE, + 0, + TRUE); + caja_file_list_free (file_list); + +} + +static void +activate_selected_items_alternate (FMListView *view, + CajaFile *file, + gboolean open_in_tab) +{ + GList *file_list; + CajaWindowOpenFlags flags; + + flags = CAJA_WINDOW_OPEN_FLAG_CLOSE_BEHIND; + + if (open_in_tab) + { + flags |= CAJA_WINDOW_OPEN_FLAG_NEW_TAB; + } + else + { + flags |= CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW; + } + + if (file != NULL) + { + caja_file_ref (file); + file_list = g_list_prepend (NULL, file); + } + else + { + file_list = fm_list_view_get_selection (FM_DIRECTORY_VIEW (view)); + } + fm_directory_view_activate_files (FM_DIRECTORY_VIEW (view), + file_list, + CAJA_WINDOW_OPEN_ACCORDING_TO_MODE, + flags, + TRUE); + caja_file_list_free (file_list); + +} + +static gboolean +button_event_modifies_selection (GdkEventButton *event) +{ + return (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) != 0; +} + +static void +fm_list_view_did_not_drag (FMListView *view, + GdkEventButton *event) +{ + GtkTreeView *tree_view; + GtkTreeSelection *selection; + GtkTreePath *path; + + tree_view = view->details->tree_view; + selection = gtk_tree_view_get_selection (tree_view); + + if (gtk_tree_view_get_path_at_pos (tree_view, event->x, event->y, + &path, NULL, NULL, NULL)) + { + if ((event->button == 1 || event->button == 2) + && ((event->state & GDK_CONTROL_MASK) != 0 || + (event->state & GDK_SHIFT_MASK) == 0) + && view->details->row_selected_on_button_down) + { + if (!button_event_modifies_selection (event)) + { + gtk_tree_selection_unselect_all (selection); + gtk_tree_selection_select_path (selection, path); + } + else + { + gtk_tree_selection_unselect_path (selection, path); + } + } + + if ((click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE) + && !button_event_modifies_selection(event)) + { + if (event->button == 1) + { + activate_selected_items (view); + } + else if (event->button == 2) + { + activate_selected_items_alternate (view, NULL, TRUE); + } + } + gtk_tree_path_free (path); + } + +} + +static void +drag_data_get_callback (GtkWidget *widget, + GdkDragContext *context, + GtkSelectionData *selection_data, + guint info, + guint time) +{ + GtkTreeView *tree_view; + GtkTreeModel *model; + GList *ref_list; + + tree_view = GTK_TREE_VIEW (widget); + + model = gtk_tree_view_get_model (tree_view); + + if (model == NULL) + { + return; + } + + ref_list = g_object_get_data (G_OBJECT (context), "drag-info"); + + if (ref_list == NULL) + { + return; + } + + if (EGG_IS_TREE_MULTI_DRAG_SOURCE (model)) + { + egg_tree_multi_drag_source_drag_data_get (EGG_TREE_MULTI_DRAG_SOURCE (model), + ref_list, + selection_data); + } +} + +static void +filtered_selection_foreach (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + struct SelectionForeachData *selection_data; + GtkTreeIter parent; + GtkTreeIter child; + + selection_data = data; + + /* If the parent folder is also selected, don't include this file in the + * file operation, since that would copy it to the toplevel target instead + * of keeping it as a child of the copied folder + */ + child = *iter; + while (gtk_tree_model_iter_parent (model, &parent, &child)) + { + if (gtk_tree_selection_iter_is_selected (selection_data->selection, + &parent)) + { + return; + } + child = parent; + } + + selection_data->list = g_list_prepend (selection_data->list, + gtk_tree_row_reference_new (model, path)); +} + +static GList * +get_filtered_selection_refs (GtkTreeView *tree_view) +{ + struct SelectionForeachData selection_data; + + selection_data.list = NULL; + selection_data.selection = gtk_tree_view_get_selection (tree_view); + + gtk_tree_selection_selected_foreach (selection_data.selection, + filtered_selection_foreach, + &selection_data); + return g_list_reverse (selection_data.list); +} + +static void +ref_list_free (GList *ref_list) +{ + g_list_foreach (ref_list, (GFunc) gtk_tree_row_reference_free, NULL); + g_list_free (ref_list); +} + +static void +stop_drag_check (FMListView *view) +{ + view->details->drag_button = 0; +} + +static GdkPixbuf * +get_drag_pixbuf (FMListView *view) +{ + GtkTreeModel *model; + GtkTreePath *path; + GtkTreeIter iter; + GdkPixbuf *ret; + GdkRectangle cell_area; + + ret = NULL; + + if (gtk_tree_view_get_path_at_pos (view->details->tree_view, + view->details->drag_x, + view->details->drag_y, + &path, NULL, NULL, NULL)) + { + model = gtk_tree_view_get_model (view->details->tree_view); + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_model_get (model, &iter, + fm_list_model_get_column_id_from_zoom_level (view->details->zoom_level), + &ret, + -1); + + gtk_tree_view_get_cell_area (view->details->tree_view, + path, + view->details->file_name_column, + &cell_area); + + gtk_tree_path_free (path); + } + + return ret; +} + +static void +drag_begin_callback (GtkWidget *widget, + GdkDragContext *context, + FMListView *view) +{ + GList *ref_list; + GdkPixbuf *pixbuf; + + pixbuf = get_drag_pixbuf (view); + if (pixbuf) + { + gtk_drag_set_icon_pixbuf (context, + pixbuf, + 0, 0); + g_object_unref (pixbuf); + } + else + { + gtk_drag_set_icon_default (context); + } + + stop_drag_check (view); + view->details->drag_started = TRUE; + + ref_list = get_filtered_selection_refs (GTK_TREE_VIEW (widget)); + g_object_set_data_full (G_OBJECT (context), + "drag-info", + ref_list, + (GDestroyNotify)ref_list_free); +} + +static gboolean +motion_notify_callback (GtkWidget *widget, + GdkEventMotion *event, + gpointer callback_data) +{ + FMListView *view; + GdkDragContext *context; + + view = FM_LIST_VIEW (callback_data); + + if (event->window != gtk_tree_view_get_bin_window (GTK_TREE_VIEW (widget))) + { + return FALSE; + } + + if (click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE) + { + GtkTreePath *old_hover_path; + + old_hover_path = view->details->hover_path; + gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget), + event->x, event->y, + &view->details->hover_path, + NULL, NULL, NULL); + + if ((old_hover_path != NULL) != (view->details->hover_path != NULL)) + { + if (view->details->hover_path != NULL) + { + gdk_window_set_cursor (gtk_widget_get_window (widget), hand_cursor); + } + else + { + gdk_window_set_cursor (gtk_widget_get_window (widget), NULL); + } + } + + if (old_hover_path != NULL) + { + gtk_tree_path_free (old_hover_path); + } + } + + if (view->details->drag_button != 0) + { + if (!source_target_list) + { + source_target_list = fm_list_model_get_drag_target_list (); + } + + if (gtk_drag_check_threshold (widget, + view->details->drag_x, + view->details->drag_y, + event->x, + event->y)) + { + context = gtk_drag_begin + (widget, + source_target_list, + GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK | GDK_ACTION_ASK, + view->details->drag_button, + (GdkEvent*)event); + } + return TRUE; + } + + return FALSE; +} + +static gboolean +leave_notify_callback (GtkWidget *widget, + GdkEventCrossing *event, + gpointer callback_data) +{ + FMListView *view; + + view = FM_LIST_VIEW (callback_data); + + if (click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE && + view->details->hover_path != NULL) + { + gtk_tree_path_free (view->details->hover_path); + view->details->hover_path = NULL; + } + + return FALSE; +} + +static gboolean +enter_notify_callback (GtkWidget *widget, + GdkEventCrossing *event, + gpointer callback_data) +{ + FMListView *view; + + view = FM_LIST_VIEW (callback_data); + + if (click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE) + { + if (view->details->hover_path != NULL) + { + gtk_tree_path_free (view->details->hover_path); + } + + gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget), + event->x, event->y, + &view->details->hover_path, + NULL, NULL, NULL); + + if (view->details->hover_path != NULL) + { + gdk_window_set_cursor (gtk_widget_get_window (widget), hand_cursor); + } + } + + return FALSE; +} + +static void +do_popup_menu (GtkWidget *widget, FMListView *view, GdkEventButton *event) +{ + if (tree_view_has_selection (GTK_TREE_VIEW (widget))) + { + fm_directory_view_pop_up_selection_context_menu (FM_DIRECTORY_VIEW (view), event); + } + else + { + fm_directory_view_pop_up_background_context_menu (FM_DIRECTORY_VIEW (view), event); + } +} + +static gboolean +button_press_callback (GtkWidget *widget, GdkEventButton *event, gpointer callback_data) +{ + FMListView *view; + GtkTreeView *tree_view; + GtkTreePath *path; + gboolean call_parent; + gboolean allow_drag; + GtkTreeSelection *selection; + GtkWidgetClass *tree_view_class; + gint64 current_time; + static gint64 last_click_time = 0; + static int click_count = 0; + int double_click_time; + int expander_size, horizontal_separator; + gboolean on_expander; + + view = FM_LIST_VIEW (callback_data); + tree_view = GTK_TREE_VIEW (widget); + tree_view_class = GTK_WIDGET_GET_CLASS (tree_view); + selection = gtk_tree_view_get_selection (tree_view); + + if (event->window != gtk_tree_view_get_bin_window (tree_view)) + { + return FALSE; + } + + fm_list_model_set_drag_view + (FM_LIST_MODEL (gtk_tree_view_get_model (tree_view)), + tree_view, + event->x, event->y); + + g_object_get (G_OBJECT (gtk_widget_get_settings (widget)), + "gtk-double-click-time", &double_click_time, + NULL); + + /* Determine click count */ + 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; + + /* Ignore double click if we are in single click mode */ + if (click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE && click_count >= 2) + { + return TRUE; + } + + view->details->ignore_button_release = FALSE; + + call_parent = TRUE; + allow_drag = FALSE; + if (gtk_tree_view_get_path_at_pos (tree_view, event->x, event->y, + &path, NULL, NULL, NULL)) + { + gtk_widget_style_get (widget, + "expander-size", &expander_size, + "horizontal-separator", &horizontal_separator, + NULL); + /* TODO we should not hardcode this extra padding. It is + * EXPANDER_EXTRA_PADDING from GtkTreeView. + */ + expander_size += 4; + on_expander = (event->x <= horizontal_separator / 2 + + gtk_tree_path_get_depth (path) * expander_size); + + /* Keep track of path of last click so double clicks only happen + * on the same item */ + if ((event->button == 1 || event->button == 2) && + event->type == GDK_BUTTON_PRESS) + { + if (view->details->double_click_path[1]) + { + gtk_tree_path_free (view->details->double_click_path[1]); + } + view->details->double_click_path[1] = view->details->double_click_path[0]; + view->details->double_click_path[0] = gtk_tree_path_copy (path); + } + if (event->type == GDK_2BUTTON_PRESS) + { + /* Double clicking does not trigger a D&D action. */ + view->details->drag_button = 0; + if (view->details->double_click_path[1] && + gtk_tree_path_compare (view->details->double_click_path[0], view->details->double_click_path[1]) == 0 && + !on_expander) + { + /* NOTE: Activation can actually destroy the view if we're switching */ + if (!button_event_modifies_selection (event)) + { + if ((event->button == 1 || event->button == 3)) + { + activate_selected_items (view); + } + else if (event->button == 2) + { + activate_selected_items_alternate (view, NULL, TRUE); + } + } + else if (event->button == 1 && + (event->state & GDK_SHIFT_MASK) != 0) + { + CajaFile *file; + file = fm_list_model_file_for_path (view->details->model, path); + if (file != NULL) + { + activate_selected_items_alternate (view, file, TRUE); + caja_file_unref (file); + } + } + } + else + { + tree_view_class->button_press_event (widget, event); + } + } + else + { + + /* We're going to filter out some situations where + * we can't let the default code run because all + * but one row would be would be deselected. We don't + * want that; we want the right click menu or single + * click to apply to everything that's currently selected. */ + + if (event->button == 3 && gtk_tree_selection_path_is_selected (selection, path)) + { + call_parent = FALSE; + } + + if ((event->button == 1 || event->button == 2) && + ((event->state & GDK_CONTROL_MASK) != 0 || + (event->state & GDK_SHIFT_MASK) == 0)) + { + view->details->row_selected_on_button_down = gtk_tree_selection_path_is_selected (selection, path); + if (view->details->row_selected_on_button_down) + { + call_parent = on_expander; + view->details->ignore_button_release = call_parent; + } + else if ((event->state & GDK_CONTROL_MASK) != 0) + { + GList *selected_rows; + GList *l; + + call_parent = FALSE; + if ((event->state & GDK_SHIFT_MASK) != 0) + { + GtkTreePath *cursor; + gtk_tree_view_get_cursor (tree_view, &cursor, NULL); + if (cursor != NULL) + { + gtk_tree_selection_select_range (selection, cursor, path); + } + else + { + gtk_tree_selection_select_path (selection, path); + } + } + else + { + gtk_tree_selection_select_path (selection, path); + } + selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL); + + /* This unselects everything */ + gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE); + + /* So select it again */ + l = selected_rows; + while (l != NULL) + { + GtkTreePath *p = l->data; + l = l->next; + gtk_tree_selection_select_path (selection, p); + gtk_tree_path_free (p); + } + g_list_free (selected_rows); + } + else + { + view->details->ignore_button_release = on_expander; + } + } + + if (call_parent) + { + tree_view_class->button_press_event (widget, event); + } + else if (gtk_tree_selection_path_is_selected (selection, path)) + { + gtk_widget_grab_focus (widget); + } + + if ((event->button == 1 || event->button == 2) && + event->type == GDK_BUTTON_PRESS) + { + view->details->drag_started = FALSE; + view->details->drag_button = event->button; + view->details->drag_x = event->x; + view->details->drag_y = event->y; + } + + if (event->button == 3) + { + do_popup_menu (widget, view, event); + } + } + + gtk_tree_path_free (path); + } + else + { + if ((event->button == 1 || event->button == 2) && + event->type == GDK_BUTTON_PRESS) + { + if (view->details->double_click_path[1]) + { + gtk_tree_path_free (view->details->double_click_path[1]); + } + view->details->double_click_path[1] = view->details->double_click_path[0]; + view->details->double_click_path[0] = NULL; + } + /* Deselect if people click outside any row. It's OK to + let default code run; it won't reselect anything. */ + gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (tree_view)); + tree_view_class->button_press_event (widget, event); + + if (event->button == 3) + { + do_popup_menu (widget, view, event); + } + } + + /* We chained to the default handler in this method, so never + * let the default handler run */ + return TRUE; +} + +static gboolean +button_release_callback (GtkWidget *widget, + GdkEventButton *event, + gpointer callback_data) +{ + FMListView *view; + + view = FM_LIST_VIEW (callback_data); + + if (event->button == view->details->drag_button) + { + stop_drag_check (view); + if (!view->details->drag_started && + !view->details->ignore_button_release) + { + fm_list_view_did_not_drag (view, event); + } + } + return FALSE; +} + +static gboolean +popup_menu_callback (GtkWidget *widget, gpointer callback_data) +{ + FMListView *view; + + view = FM_LIST_VIEW (callback_data); + + do_popup_menu (widget, view, NULL); + + return TRUE; +} + +static void +subdirectory_done_loading_callback (CajaDirectory *directory, FMListView *view) +{ + fm_list_model_subdirectory_done_loading (view->details->model, directory); +} + +static void +row_expanded_callback (GtkTreeView *treeview, GtkTreeIter *iter, GtkTreePath *path, gpointer callback_data) +{ + FMListView *view; + CajaDirectory *directory; + + view = FM_LIST_VIEW (callback_data); + + if (fm_list_model_load_subdirectory (view->details->model, path, &directory)) + { + char *uri; + + uri = caja_directory_get_uri (directory); + caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, + "list view row expanded window=%p: %s", + fm_directory_view_get_containing_window (FM_DIRECTORY_VIEW (view)), + uri); + g_free (uri); + + fm_directory_view_add_subdirectory (FM_DIRECTORY_VIEW (view), directory); + + if (caja_directory_are_all_files_seen (directory)) + { + fm_list_model_subdirectory_done_loading (view->details->model, + directory); + } + else + { + g_signal_connect_object (directory, "done_loading", + G_CALLBACK (subdirectory_done_loading_callback), + view, 0); + } + + caja_directory_unref (directory); + } +} + +struct UnloadDelayData +{ + CajaFile *file; + CajaDirectory *directory; + FMListView *view; +}; + +static gboolean +unload_file_timeout (gpointer data) +{ + struct UnloadDelayData *unload_data = data; + GtkTreeIter iter; + FMListModel *model; + GtkTreePath *path; + + if (unload_data->view != NULL) + { + model = unload_data->view->details->model; + if (fm_list_model_get_tree_iter_from_file (model, + unload_data->file, + unload_data->directory, + &iter)) + { + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); + if (!gtk_tree_view_row_expanded (unload_data->view->details->tree_view, + path)) + { + fm_list_model_unload_subdirectory (model, &iter); + } + gtk_tree_path_free (path); + } + } + + eel_remove_weak_pointer (&unload_data->view); + + if (unload_data->directory) + { + caja_directory_unref (unload_data->directory); + } + caja_file_unref (unload_data->file); + g_free (unload_data); + return FALSE; +} + +static void +row_collapsed_callback (GtkTreeView *treeview, GtkTreeIter *iter, GtkTreePath *path, gpointer callback_data) +{ + FMListView *view; + CajaFile *file; + CajaDirectory *directory; + GtkTreeIter parent; + struct UnloadDelayData *unload_data; + GtkTreeModel *model; + char *uri; + + view = FM_LIST_VIEW (callback_data); + model = GTK_TREE_MODEL (view->details->model); + + gtk_tree_model_get (model, iter, + FM_LIST_MODEL_FILE_COLUMN, &file, + -1); + + directory = NULL; + if (gtk_tree_model_iter_parent (model, &parent, iter)) + { + gtk_tree_model_get (model, &parent, + FM_LIST_MODEL_SUBDIRECTORY_COLUMN, &directory, + -1); + } + + + uri = caja_file_get_uri (file); + caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, + "list view row collapsed window=%p: %s", + fm_directory_view_get_containing_window (FM_DIRECTORY_VIEW (view)), + uri); + g_free (uri); + + unload_data = g_new (struct UnloadDelayData, 1); + unload_data->view = view; + unload_data->file = file; + unload_data->directory = directory; + + eel_add_weak_pointer (&unload_data->view); + + g_timeout_add_seconds (COLLAPSE_TO_UNLOAD_DELAY, + unload_file_timeout, + unload_data); +} + +static void +row_activated_callback (GtkTreeView *treeview, GtkTreePath *path, + GtkTreeViewColumn *column, FMListView *view) +{ + activate_selected_items (view); +} + +static void +subdirectory_unloaded_callback (FMListModel *model, + CajaDirectory *directory, + gpointer callback_data) +{ + FMListView *view; + + g_return_if_fail (FM_IS_LIST_MODEL (model)); + g_return_if_fail (CAJA_IS_DIRECTORY (directory)); + + view = FM_LIST_VIEW(callback_data); + + g_signal_handlers_disconnect_by_func (directory, + G_CALLBACK (subdirectory_done_loading_callback), + view); + fm_directory_view_remove_subdirectory (FM_DIRECTORY_VIEW (view), directory); +} + +static gboolean +key_press_callback (GtkWidget *widget, GdkEventKey *event, gpointer callback_data) +{ + FMDirectoryView *view; + GdkEventButton button_event = { 0 }; + gboolean handled; + GtkTreeView *tree_view; + GtkTreePath *path; + + tree_view = GTK_TREE_VIEW (widget); + + view = FM_DIRECTORY_VIEW (callback_data); + handled = FALSE; + + switch (event->keyval) + { + case GDK_F10: + if (event->state & GDK_CONTROL_MASK) + { + fm_directory_view_pop_up_background_context_menu (view, &button_event); + handled = TRUE; + } + break; + case GDK_Right: + gtk_tree_view_get_cursor (tree_view, &path, NULL); + if (path) + { + gtk_tree_view_expand_row (tree_view, path, FALSE); + gtk_tree_path_free (path); + } + handled = TRUE; + break; + case GDK_Left: + gtk_tree_view_get_cursor (tree_view, &path, NULL); + if (path) + { + gtk_tree_view_collapse_row (tree_view, path); + gtk_tree_path_free (path); + } + handled = TRUE; + break; + case GDK_space: + if (event->state & GDK_CONTROL_MASK) + { + handled = FALSE; + break; + } + if (!gtk_widget_has_focus (GTK_WIDGET (FM_LIST_VIEW (view)->details->tree_view))) + { + handled = FALSE; + break; + } + if ((event->state & GDK_SHIFT_MASK) != 0) + { + activate_selected_items_alternate (FM_LIST_VIEW (view), NULL, TRUE); + } + else + { + activate_selected_items (FM_LIST_VIEW (view)); + } + handled = TRUE; + break; + case GDK_Return: + case GDK_KP_Enter: + if ((event->state & GDK_SHIFT_MASK) != 0) + { + activate_selected_items_alternate (FM_LIST_VIEW (view), NULL, TRUE); + } + else + { + activate_selected_items (FM_LIST_VIEW (view)); + } + handled = TRUE; + break; + case GDK_v: + /* Eat Control + v to not enable type ahead */ + if ((event->state & GDK_CONTROL_MASK) != 0) + { + handled = TRUE; + } + break; + + default: + handled = FALSE; + } + + return handled; +} + +static void +fm_list_view_reveal_selection (FMDirectoryView *view) +{ + GList *selection; + + g_return_if_fail (FM_IS_LIST_VIEW (view)); + + selection = fm_directory_view_get_selection (view); + + /* Make sure at least one of the selected items is scrolled into view */ + if (selection != NULL) + { + FMListView *list_view; + CajaFile *file; + GtkTreeIter iter; + GtkTreePath *path; + + list_view = FM_LIST_VIEW (view); + file = selection->data; + if (fm_list_model_get_first_iter_for_file (list_view->details->model, file, &iter)) + { + path = gtk_tree_model_get_path (GTK_TREE_MODEL (list_view->details->model), &iter); + + gtk_tree_view_scroll_to_cell (list_view->details->tree_view, path, NULL, FALSE, 0.0, 0.0); + + gtk_tree_path_free (path); + } + } + + caja_file_list_free (selection); +} + +static gboolean +sort_criterion_changes_due_to_user (GtkTreeView *tree_view) +{ + GList *columns, *p; + GtkTreeViewColumn *column; + GSignalInvocationHint *ihint; + unsigned int sort_signal_id; + gboolean ret; + + sort_signal_id = g_signal_lookup ("clicked", gtk_tree_view_column_get_type ()); + + ret = FALSE; + + columns = gtk_tree_view_get_columns (tree_view); + for (p = columns; p != NULL; p = p->next) + { + column = p->data; + ihint = g_signal_get_invocation_hint (column); + if (ihint != NULL) + { + ret = TRUE; + break; + } + } + g_list_free (columns); + + return ret; +} + +static void +sort_column_changed_callback (GtkTreeSortable *sortable, + FMListView *view) +{ + CajaFile *file; + gint sort_column_id, default_sort_column_id; + GtkSortType reversed; + GQuark sort_attr, default_sort_attr; + char *reversed_attr, *default_reversed_attr; + gboolean default_sort_reversed; + + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (view)); + + gtk_tree_sortable_get_sort_column_id (sortable, &sort_column_id, &reversed); + sort_attr = fm_list_model_get_attribute_from_sort_column_id (view->details->model, sort_column_id); + + default_sort_column_id = fm_list_model_get_sort_column_id_from_attribute (view->details->model, + g_quark_from_string (get_default_sort_order (file, &default_sort_reversed))); + default_sort_attr = fm_list_model_get_attribute_from_sort_column_id (view->details->model, default_sort_column_id); + caja_file_set_metadata (file, CAJA_METADATA_KEY_LIST_VIEW_SORT_COLUMN, + g_quark_to_string (default_sort_attr), g_quark_to_string (sort_attr)); + + default_reversed_attr = (default_sort_reversed ? "true" : "false"); + + if (view->details->last_sort_attr != sort_attr && + sort_criterion_changes_due_to_user (view->details->tree_view)) + { + /* at this point, the sort order is always GTK_SORT_ASCENDING, if the sort column ID + * switched. Invert the sort order, if it's the default criterion with a reversed preference, + * or if it makes sense for the attribute (i.e. date). */ + if (sort_attr == default_sort_attr) + { + /* use value from preferences */ + reversed = eel_preferences_get_boolean (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_SORT_IN_REVERSE_ORDER); + } + else + { + reversed = caja_file_is_date_sort_attribute_q (sort_attr); + } + + if (reversed) + { + g_signal_handlers_block_by_func (sortable, sort_column_changed_callback, view); + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (view->details->model), + sort_column_id, + GTK_SORT_DESCENDING); + g_signal_handlers_unblock_by_func (sortable, sort_column_changed_callback, view); + } + } + + + reversed_attr = (reversed ? "true" : "false"); + caja_file_set_metadata (file, CAJA_METADATA_KEY_LIST_VIEW_SORT_REVERSED, + default_reversed_attr, reversed_attr); + + /* Make sure selected item(s) is visible after sort */ + fm_list_view_reveal_selection (FM_DIRECTORY_VIEW (view)); + + view->details->last_sort_attr = sort_attr; +} + +static void +cell_renderer_editing_started_cb (GtkCellRenderer *renderer, + GtkCellEditable *editable, + const gchar *path_str, + FMListView *list_view) +{ + GtkEntry *entry; + gint start_offset, end_offset; + + entry = GTK_ENTRY (editable); + list_view->details->editable_widget = editable; + + /* Free a previously allocated original_name */ + g_free (list_view->details->original_name); + + list_view->details->original_name = g_strdup (gtk_entry_get_text (entry)); + eel_filename_get_rename_region (list_view->details->original_name, + &start_offset, &end_offset); + gtk_editable_select_region (GTK_EDITABLE (entry), start_offset, end_offset); + + caja_clipboard_set_up_editable + (GTK_EDITABLE (entry), + fm_directory_view_get_ui_manager (FM_DIRECTORY_VIEW (list_view)), + FALSE); +} + +static void +cell_renderer_editing_canceled (GtkCellRendererText *cell, + FMListView *view) +{ + view->details->editable_widget = NULL; + + fm_directory_view_unfreeze_updates (FM_DIRECTORY_VIEW (view)); +} + +static void +cell_renderer_edited (GtkCellRendererText *cell, + const char *path_str, + const char *new_text, + FMListView *view) +{ + GtkTreePath *path; + CajaFile *file; + GtkTreeIter iter; + + view->details->editable_widget = NULL; + + /* Don't allow a rename with an empty string. Revert to original + * without notifying the user. + */ + if (new_text[0] == '\0') + { + g_object_set (G_OBJECT (view->details->file_name_cell), + "editable", FALSE, + NULL); + fm_directory_view_unfreeze_updates (FM_DIRECTORY_VIEW (view)); + return; + } + + path = gtk_tree_path_new_from_string (path_str); + + gtk_tree_model_get_iter (GTK_TREE_MODEL (view->details->model), + &iter, path); + + gtk_tree_path_free (path); + + gtk_tree_model_get (GTK_TREE_MODEL (view->details->model), + &iter, + FM_LIST_MODEL_FILE_COLUMN, &file, + -1); + + /* Only rename if name actually changed */ + if (strcmp (new_text, view->details->original_name) != 0) + { + view->details->renaming_file = caja_file_ref (file); + view->details->rename_done = FALSE; + fm_rename_file (file, new_text, fm_list_view_rename_callback, g_object_ref (view)); + g_free (view->details->original_name); + view->details->original_name = g_strdup (new_text); + } + + caja_file_unref (file); + + /*We're done editing - make the filename-cells readonly again.*/ + g_object_set (G_OBJECT (view->details->file_name_cell), + "editable", FALSE, + NULL); + + fm_directory_view_unfreeze_updates (FM_DIRECTORY_VIEW (view)); +} + +static char * +get_root_uri_callback (CajaTreeViewDragDest *dest, + gpointer user_data) +{ + FMListView *view; + + view = FM_LIST_VIEW (user_data); + + return fm_directory_view_get_uri (FM_DIRECTORY_VIEW (view)); +} + +static CajaFile * +get_file_for_path_callback (CajaTreeViewDragDest *dest, + GtkTreePath *path, + gpointer user_data) +{ + FMListView *view; + + view = FM_LIST_VIEW (user_data); + + return fm_list_model_file_for_path (view->details->model, path); +} + +/* Handles an URL received from Mozilla */ +static void +list_view_handle_netscape_url (CajaTreeViewDragDest *dest, const char *encoded_url, + const char *target_uri, GdkDragAction action, int x, int y, FMListView *view) +{ + fm_directory_view_handle_netscape_url_drop (FM_DIRECTORY_VIEW (view), + encoded_url, target_uri, action, x, y); +} + +static void +list_view_handle_uri_list (CajaTreeViewDragDest *dest, const char *item_uris, + const char *target_uri, + GdkDragAction action, int x, int y, FMListView *view) +{ + fm_directory_view_handle_uri_list_drop (FM_DIRECTORY_VIEW (view), + item_uris, target_uri, action, x, y); +} + +static void +list_view_handle_text (CajaTreeViewDragDest *dest, const char *text, + const char *target_uri, + GdkDragAction action, int x, int y, FMListView *view) +{ + fm_directory_view_handle_text_drop (FM_DIRECTORY_VIEW (view), + text, target_uri, action, x, y); +} + +static void +list_view_handle_raw (CajaTreeViewDragDest *dest, const char *raw_data, + int length, const char *target_uri, const char *direct_save_uri, + GdkDragAction action, int x, int y, FMListView *view) +{ + fm_directory_view_handle_raw_drop (FM_DIRECTORY_VIEW (view), + raw_data, length, target_uri, direct_save_uri, + action, x, y); +} + +static void +move_copy_items_callback (CajaTreeViewDragDest *dest, + const GList *item_uris, + const char *target_uri, + guint action, + int x, + int y, + gpointer user_data) + +{ + FMDirectoryView *view = user_data; + + caja_clipboard_clear_if_colliding_uris (GTK_WIDGET (view), + item_uris, + fm_directory_view_get_copied_files_atom (view)); + fm_directory_view_move_copy_items (item_uris, + NULL, + target_uri, + action, + x, y, + view); +} + +static void +apply_columns_settings (FMListView *list_view, + char **column_order, + char **visible_columns) +{ + GList *all_columns; + CajaFile *file; + GList *old_view_columns, *view_columns; + GHashTable *visible_columns_hash; + GtkTreeViewColumn *prev_view_column; + GList *l; + int i; + + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (list_view)); + + /* prepare ordered list of view columns using column_order and visible_columns */ + view_columns = NULL; + + all_columns = caja_get_columns_for_file (file); + all_columns = caja_sort_columns (all_columns, column_order); + + /* hash table to lookup if a given column should be visible */ + visible_columns_hash = g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_free); + for (i = 0; visible_columns[i] != NULL; ++i) + { + g_hash_table_insert (visible_columns_hash, + g_ascii_strdown (visible_columns[i], -1), + g_ascii_strdown (visible_columns[i], -1)); + } + + for (l = all_columns; l != NULL; l = l->next) + { + char *name; + char *lowercase; + + g_object_get (G_OBJECT (l->data), "name", &name, NULL); + lowercase = g_ascii_strdown (name, -1); + + if (g_hash_table_lookup (visible_columns_hash, lowercase) != NULL) + { + GtkTreeViewColumn *view_column; + + view_column = g_hash_table_lookup (list_view->details->columns, name); + if (view_column != NULL) + { + view_columns = g_list_prepend (view_columns, view_column); + } + } + + g_free (name); + g_free (lowercase); + } + + g_hash_table_destroy (visible_columns_hash); + caja_column_list_free (all_columns); + + view_columns = g_list_reverse (view_columns); + + /* remove columns that are not present in the configuration */ + old_view_columns = gtk_tree_view_get_columns (list_view->details->tree_view); + for (l = old_view_columns; l != NULL; l = l->next) + { + if (g_list_find (view_columns, l->data) == NULL) + { + gtk_tree_view_remove_column (list_view->details->tree_view, l->data); + } + } + g_list_free (old_view_columns); + + /* append new columns from the configuration */ + old_view_columns = gtk_tree_view_get_columns (list_view->details->tree_view); + for (l = view_columns; l != NULL; l = l->next) + { + if (g_list_find (old_view_columns, l->data) == NULL) + { + gtk_tree_view_append_column (list_view->details->tree_view, l->data); + } + } + g_list_free (old_view_columns); + + /* place columns in the correct order */ + prev_view_column = NULL; + for (l = view_columns; l != NULL; l = l->next) + { + gtk_tree_view_move_column_after (list_view->details->tree_view, l->data, prev_view_column); + prev_view_column = l->data; + } + g_list_free (view_columns); +} + +static void +filename_cell_data_func (GtkTreeViewColumn *column, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + FMListView *view) +{ + char *text; + GtkTreePath *path; + PangoUnderline underline; + + gtk_tree_model_get (model, iter, + view->details->file_name_column_num, &text, + -1); + + if (click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE) + { + path = gtk_tree_model_get_path (model, iter); + + if (view->details->hover_path == NULL || + gtk_tree_path_compare (path, view->details->hover_path)) + { + underline = PANGO_UNDERLINE_NONE; + } + else + { + underline = PANGO_UNDERLINE_SINGLE; + } + + gtk_tree_path_free (path); + } + else + { + underline = PANGO_UNDERLINE_NONE; + } + + g_object_set (G_OBJECT (renderer), + "text", text, + "underline", underline, + NULL); + g_free (text); +} + +static gboolean +focus_in_event_callback (GtkWidget *widget, GdkEventFocus *event, gpointer user_data) +{ + CajaWindowSlotInfo *slot_info; + FMListView *list_view = FM_LIST_VIEW (user_data); + + /* make the corresponding slot (and the pane that contains it) active */ + slot_info = fm_directory_view_get_caja_window_slot (FM_DIRECTORY_VIEW (list_view)); + caja_window_slot_info_make_hosting_pane_active (slot_info); + + return FALSE; +} + +static void +create_and_set_up_tree_view (FMListView *view) +{ + GtkCellRenderer *cell; + GtkTreeViewColumn *column; + GtkBindingSet *binding_set; + AtkObject *atk_obj; + GList *caja_columns; + GList *l; + + view->details->tree_view = GTK_TREE_VIEW (gtk_tree_view_new ()); + view->details->columns = g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify)g_free, + (GDestroyNotify) g_object_unref); + gtk_tree_view_set_enable_search (view->details->tree_view, TRUE); + + /* Don't handle backspace key. It's used to open the parent folder. */ + binding_set = gtk_binding_set_by_class (GTK_WIDGET_GET_CLASS (view->details->tree_view)); + gtk_binding_entry_remove (binding_set, GDK_BackSpace, 0); + + view->details->drag_dest = + caja_tree_view_drag_dest_new (view->details->tree_view); + + g_signal_connect_object (view->details->drag_dest, + "get_root_uri", + G_CALLBACK (get_root_uri_callback), + view, 0); + g_signal_connect_object (view->details->drag_dest, + "get_file_for_path", + G_CALLBACK (get_file_for_path_callback), + view, 0); + g_signal_connect_object (view->details->drag_dest, + "move_copy_items", + G_CALLBACK (move_copy_items_callback), + view, 0); + g_signal_connect_object (view->details->drag_dest, "handle_netscape_url", + G_CALLBACK (list_view_handle_netscape_url), view, 0); + g_signal_connect_object (view->details->drag_dest, "handle_uri_list", + G_CALLBACK (list_view_handle_uri_list), view, 0); + g_signal_connect_object (view->details->drag_dest, "handle_text", + G_CALLBACK (list_view_handle_text), view, 0); + g_signal_connect_object (view->details->drag_dest, "handle_raw", + G_CALLBACK (list_view_handle_raw), view, 0); + + g_signal_connect_object (gtk_tree_view_get_selection (view->details->tree_view), + "changed", + G_CALLBACK (list_selection_changed_callback), view, 0); + + g_signal_connect_object (view->details->tree_view, "drag_begin", + G_CALLBACK (drag_begin_callback), view, 0); + g_signal_connect_object (view->details->tree_view, "drag_data_get", + G_CALLBACK (drag_data_get_callback), view, 0); + g_signal_connect_object (view->details->tree_view, "motion_notify_event", + G_CALLBACK (motion_notify_callback), view, 0); + g_signal_connect_object (view->details->tree_view, "enter_notify_event", + G_CALLBACK (enter_notify_callback), view, 0); + g_signal_connect_object (view->details->tree_view, "leave_notify_event", + G_CALLBACK (leave_notify_callback), view, 0); + g_signal_connect_object (view->details->tree_view, "button_press_event", + G_CALLBACK (button_press_callback), view, 0); + g_signal_connect_object (view->details->tree_view, "button_release_event", + G_CALLBACK (button_release_callback), view, 0); + g_signal_connect_object (view->details->tree_view, "key_press_event", + G_CALLBACK (key_press_callback), view, 0); + g_signal_connect_object (view->details->tree_view, "popup_menu", + G_CALLBACK (popup_menu_callback), view, 0); + g_signal_connect_object (view->details->tree_view, "row_expanded", + G_CALLBACK (row_expanded_callback), view, 0); + g_signal_connect_object (view->details->tree_view, "row_collapsed", + G_CALLBACK (row_collapsed_callback), view, 0); + g_signal_connect_object (view->details->tree_view, "row-activated", + G_CALLBACK (row_activated_callback), view, 0); + + g_signal_connect_object (view->details->tree_view, "focus_in_event", + G_CALLBACK(focus_in_event_callback), view, 0); + + view->details->model = g_object_new (FM_TYPE_LIST_MODEL, NULL); + gtk_tree_view_set_model (view->details->tree_view, GTK_TREE_MODEL (view->details->model)); + /* Need the model for the dnd drop icon "accept" change */ + fm_list_model_set_drag_view (FM_LIST_MODEL (view->details->model), + view->details->tree_view, 0, 0); + + g_signal_connect_object (view->details->model, "sort_column_changed", + G_CALLBACK (sort_column_changed_callback), view, 0); + + g_signal_connect_object (view->details->model, "subdirectory_unloaded", + G_CALLBACK (subdirectory_unloaded_callback), view, 0); + + gtk_tree_selection_set_mode (gtk_tree_view_get_selection (view->details->tree_view), GTK_SELECTION_MULTIPLE); + gtk_tree_view_set_rules_hint (view->details->tree_view, TRUE); + + caja_columns = caja_get_all_columns (); + + for (l = caja_columns; l != NULL; l = l->next) + { + CajaColumn *caja_column; + int column_num; + char *name; + char *label; + float xalign; + + caja_column = CAJA_COLUMN (l->data); + + g_object_get (caja_column, + "name", &name, + "label", &label, + "xalign", &xalign, NULL); + + column_num = fm_list_model_add_column (view->details->model, + caja_column); + + /* Created the name column specially, because it + * has the icon in it.*/ + if (!strcmp (name, "name")) + { + /* Create the file name column */ + cell = caja_cell_renderer_pixbuf_emblem_new (); + view->details->pixbuf_cell = (GtkCellRendererPixbuf *)cell; + + view->details->file_name_column = gtk_tree_view_column_new (); + g_object_ref_sink (view->details->file_name_column); + view->details->file_name_column_num = column_num; + + g_hash_table_insert (view->details->columns, + g_strdup ("name"), + view->details->file_name_column); + + gtk_tree_view_set_search_column (view->details->tree_view, column_num); + + gtk_tree_view_column_set_sort_column_id (view->details->file_name_column, column_num); + gtk_tree_view_column_set_title (view->details->file_name_column, _("Name")); + gtk_tree_view_column_set_resizable (view->details->file_name_column, TRUE); + + gtk_tree_view_column_pack_start (view->details->file_name_column, cell, FALSE); + gtk_tree_view_column_set_attributes (view->details->file_name_column, + cell, + "pixbuf", FM_LIST_MODEL_SMALLEST_ICON_COLUMN, + "pixbuf_emblem", FM_LIST_MODEL_SMALLEST_EMBLEM_COLUMN, + NULL); + + cell = caja_cell_renderer_text_ellipsized_new (); + view->details->file_name_cell = (GtkCellRendererText *)cell; + g_signal_connect (cell, "edited", G_CALLBACK (cell_renderer_edited), view); + g_signal_connect (cell, "editing-canceled", G_CALLBACK (cell_renderer_editing_canceled), view); + g_signal_connect (cell, "editing-started", G_CALLBACK (cell_renderer_editing_started_cb), view); + + gtk_tree_view_column_pack_start (view->details->file_name_column, cell, TRUE); + gtk_tree_view_column_set_cell_data_func (view->details->file_name_column, cell, + (GtkTreeCellDataFunc) filename_cell_data_func, + view, NULL); + } + else + { + cell = gtk_cell_renderer_text_new (); + g_object_set (cell, "xalign", xalign, NULL); + view->details->cells = g_list_append (view->details->cells, + cell); + column = gtk_tree_view_column_new_with_attributes (label, + cell, + "text", column_num, + NULL); + g_object_ref_sink (column); + gtk_tree_view_column_set_sort_column_id (column, column_num); + g_hash_table_insert (view->details->columns, + g_strdup (name), + column); + + gtk_tree_view_column_set_resizable (column, TRUE); + gtk_tree_view_column_set_visible (column, TRUE); + } + g_free (name); + g_free (label); + } + caja_column_list_free (caja_columns); + + /* Apply the default column order and visible columns, to get it + * right most of the time. The metadata will be checked when a + * folder is loaded */ + apply_columns_settings (view, + default_column_order_auto_value, + default_visible_columns_auto_value); + + gtk_widget_show (GTK_WIDGET (view->details->tree_view)); + gtk_container_add (GTK_CONTAINER (view), GTK_WIDGET (view->details->tree_view)); + + + atk_obj = gtk_widget_get_accessible (GTK_WIDGET (view->details->tree_view)); + atk_object_set_name (atk_obj, _("List View")); +} + +static void +fm_list_view_add_file (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory) +{ + FMListModel *model; + + model = FM_LIST_VIEW (view)->details->model; + fm_list_model_add_file (model, file, directory); +} + +static char ** +get_visible_columns (FMListView *list_view) +{ + CajaFile *file; + GList *visible_columns; + char **ret; + + ret = NULL; + + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (list_view)); + + visible_columns = caja_file_get_metadata_list + (file, + CAJA_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS); + + if (visible_columns) + { + GPtrArray *res; + GList *l; + + res = g_ptr_array_new (); + for (l = visible_columns; l != NULL; l = l->next) + { + g_ptr_array_add (res, l->data); + } + g_ptr_array_add (res, NULL); + + ret = (char **) g_ptr_array_free (res, FALSE); + g_list_free (visible_columns); + } + + if (ret != NULL) + { + return ret; + } + + return caja_file_is_in_trash (file) ? + g_strdupv ((gchar **) default_trash_visible_columns) : + g_strdupv (default_visible_columns_auto_value); +} + +static char ** +get_column_order (FMListView *list_view) +{ + CajaFile *file; + GList *column_order; + char **ret; + + ret = NULL; + + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (list_view)); + + column_order = caja_file_get_metadata_list + (file, + CAJA_METADATA_KEY_LIST_VIEW_COLUMN_ORDER); + + if (column_order) + { + GPtrArray *res; + GList *l; + + res = g_ptr_array_new (); + for (l = column_order; l != NULL; l = l->next) + { + g_ptr_array_add (res, l->data); + } + g_ptr_array_add (res, NULL); + + ret = (char **) g_ptr_array_free (res, FALSE); + g_list_free (column_order); + } + + if (ret != NULL) + { + return ret; + } + + return caja_file_is_in_trash (file) ? + g_strdupv ((gchar **) default_trash_columns_order) : + g_strdupv (default_column_order_auto_value); +} + +static void +set_columns_settings_from_metadata_and_preferences (FMListView *list_view) +{ + char **column_order; + char **visible_columns; + + column_order = get_column_order (list_view); + visible_columns = get_visible_columns (list_view); + + apply_columns_settings (list_view, column_order, visible_columns); + + g_strfreev (column_order); + g_strfreev (visible_columns); +} + +static void +set_sort_order_from_metadata_and_preferences (FMListView *list_view) +{ + char *sort_attribute; + int sort_column_id; + CajaFile *file; + gboolean sort_reversed, default_sort_reversed; + const gchar *default_sort_order; + + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (list_view)); + sort_attribute = caja_file_get_metadata (file, + CAJA_METADATA_KEY_LIST_VIEW_SORT_COLUMN, + NULL); + sort_column_id = fm_list_model_get_sort_column_id_from_attribute (list_view->details->model, + g_quark_from_string (sort_attribute)); + g_free (sort_attribute); + + default_sort_order = get_default_sort_order (file, &default_sort_reversed); + + if (sort_column_id == -1) + { + sort_column_id = + fm_list_model_get_sort_column_id_from_attribute (list_view->details->model, + g_quark_from_string (default_sort_order)); + } + + sort_reversed = caja_file_get_boolean_metadata (file, + CAJA_METADATA_KEY_LIST_VIEW_SORT_REVERSED, + default_sort_reversed); + + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (list_view->details->model), + sort_column_id, + sort_reversed ? GTK_SORT_DESCENDING : GTK_SORT_ASCENDING); +} + +static gboolean +list_view_changed_foreach (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + gtk_tree_model_row_changed (model, path, iter); + return FALSE; +} + +static CajaZoomLevel +get_default_zoom_level (void) +{ + CajaZoomLevel default_zoom_level; + + default_zoom_level = default_zoom_level_auto_value; + + if (default_zoom_level < CAJA_ZOOM_LEVEL_SMALLEST + || CAJA_ZOOM_LEVEL_LARGEST < default_zoom_level) + { + default_zoom_level = CAJA_ZOOM_LEVEL_SMALL; + } + + return default_zoom_level; +} + +static void +set_zoom_level_from_metadata_and_preferences (FMListView *list_view) +{ + CajaFile *file; + int level; + + if (fm_directory_view_supports_zooming (FM_DIRECTORY_VIEW (list_view))) + { + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (list_view)); + level = caja_file_get_integer_metadata (file, + CAJA_METADATA_KEY_LIST_VIEW_ZOOM_LEVEL, + get_default_zoom_level ()); + fm_list_view_set_zoom_level (list_view, level, TRUE); + + /* updated the rows after updating the font size */ + gtk_tree_model_foreach (GTK_TREE_MODEL (list_view->details->model), + list_view_changed_foreach, NULL); + } +} + +static void +fm_list_view_begin_loading (FMDirectoryView *view) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (view); + + set_sort_order_from_metadata_and_preferences (list_view); + set_zoom_level_from_metadata_and_preferences (list_view); + set_columns_settings_from_metadata_and_preferences (list_view); +} + +static void +stop_cell_editing (FMListView *list_view) +{ + GtkTreeViewColumn *column; + + /* Stop an ongoing rename to commit the name changes when the user + * changes directories without exiting cell edit mode. It also prevents + * the edited handler from being called on the cleared list model. + */ + column = list_view->details->file_name_column; + if (column != NULL && list_view->details->editable_widget != NULL && + GTK_IS_CELL_EDITABLE (list_view->details->editable_widget)) + { + gtk_cell_editable_editing_done (list_view->details->editable_widget); + } +} + +static void +fm_list_view_clear (FMDirectoryView *view) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (view); + + if (list_view->details->model != NULL) + { + stop_cell_editing (list_view); + fm_list_model_clear (list_view->details->model); + } +} + +static void +fm_list_view_rename_callback (CajaFile *file, + GFile *result_location, + GError *error, + gpointer callback_data) +{ + FMListView *view; + + view = FM_LIST_VIEW (callback_data); + + if (view->details->renaming_file) + { + view->details->rename_done = TRUE; + + if (error != NULL) + { + /* If the rename failed (or was cancelled), kill renaming_file. + * We won't get a change event for the rename, so otherwise + * it would stay around forever. + */ + caja_file_unref (view->details->renaming_file); + view->details->renaming_file = NULL; + } + } + + g_object_unref (view); +} + + +static void +fm_list_view_file_changed (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory) +{ + FMListView *listview; + GtkTreeIter iter; + GtkTreePath *file_path; + + listview = FM_LIST_VIEW (view); + + fm_list_model_file_changed (listview->details->model, file, directory); + + if (listview->details->renaming_file != NULL && + file == listview->details->renaming_file && + listview->details->rename_done) + { + /* This is (probably) the result of the rename operation, and + * the tree-view changes above could have resorted the list, so + * scroll to the new position + */ + if (fm_list_model_get_tree_iter_from_file (listview->details->model, file, directory, &iter)) + { + file_path = gtk_tree_model_get_path (GTK_TREE_MODEL (listview->details->model), &iter); + gtk_tree_view_scroll_to_cell (listview->details->tree_view, + file_path, NULL, + FALSE, 0.0, 0.0); + gtk_tree_path_free (file_path); + } + + caja_file_unref (listview->details->renaming_file); + listview->details->renaming_file = NULL; + } +} + +static GtkWidget * +fm_list_view_get_background_widget (FMDirectoryView *view) +{ + return GTK_WIDGET (view); +} + +static void +fm_list_view_get_selection_foreach_func (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) +{ + GList **list; + CajaFile *file; + + list = data; + + gtk_tree_model_get (model, iter, + FM_LIST_MODEL_FILE_COLUMN, &file, + -1); + + if (file != NULL) + { + (* list) = g_list_prepend ((* list), file); + } +} + +static GList * +fm_list_view_get_selection (FMDirectoryView *view) +{ + GList *list; + + list = NULL; + + gtk_tree_selection_selected_foreach (gtk_tree_view_get_selection (FM_LIST_VIEW (view)->details->tree_view), + fm_list_view_get_selection_foreach_func, &list); + + return g_list_reverse (list); +} + +static void +fm_list_view_get_selection_for_file_transfer_foreach_func (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) +{ + CajaFile *file; + struct SelectionForeachData *selection_data; + GtkTreeIter parent, child; + + selection_data = data; + + gtk_tree_model_get (model, iter, + FM_LIST_MODEL_FILE_COLUMN, &file, + -1); + + if (file != NULL) + { + /* If the parent folder is also selected, don't include this file in the + * file operation, since that would copy it to the toplevel target instead + * of keeping it as a child of the copied folder + */ + child = *iter; + while (gtk_tree_model_iter_parent (model, &parent, &child)) + { + if (gtk_tree_selection_iter_is_selected (selection_data->selection, + &parent)) + { + return; + } + child = parent; + } + + caja_file_ref (file); + selection_data->list = g_list_prepend (selection_data->list, file); + } +} + + +static GList * +fm_list_view_get_selection_for_file_transfer (FMDirectoryView *view) +{ + struct SelectionForeachData selection_data; + + selection_data.list = NULL; + selection_data.selection = gtk_tree_view_get_selection (FM_LIST_VIEW (view)->details->tree_view); + + gtk_tree_selection_selected_foreach (selection_data.selection, + fm_list_view_get_selection_for_file_transfer_foreach_func, &selection_data); + + return g_list_reverse (selection_data.list); +} + + + + +static guint +fm_list_view_get_item_count (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_LIST_VIEW (view), 0); + + return fm_list_model_get_length (FM_LIST_VIEW (view)->details->model); +} + +static gboolean +fm_list_view_is_empty (FMDirectoryView *view) +{ + return fm_list_model_is_empty (FM_LIST_VIEW (view)->details->model); +} + +static void +fm_list_view_end_file_changes (FMDirectoryView *view) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (view); + + if (list_view->details->new_selection_path) + { + gtk_tree_view_set_cursor (list_view->details->tree_view, + list_view->details->new_selection_path, + NULL, FALSE); + gtk_tree_path_free (list_view->details->new_selection_path); + list_view->details->new_selection_path = NULL; + } +} + +static void +fm_list_view_remove_file (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory) +{ + GtkTreePath *path; + GtkTreePath *file_path; + GtkTreeIter iter; + GtkTreeIter temp_iter; + GtkTreeRowReference* row_reference; + FMListView *list_view; + GtkTreeModel* tree_model; + GtkTreeSelection *selection; + + path = NULL; + row_reference = NULL; + list_view = FM_LIST_VIEW (view); + tree_model = GTK_TREE_MODEL(list_view->details->model); + + if (fm_list_model_get_tree_iter_from_file (list_view->details->model, file, directory, &iter)) + { + selection = gtk_tree_view_get_selection (list_view->details->tree_view); + file_path = gtk_tree_model_get_path (tree_model, &iter); + + if (gtk_tree_selection_path_is_selected (selection, file_path)) + { + /* get reference for next element in the list view. If the element to be deleted is the + * last one, get reference to previous element. If there is only one element in view + * no need to select anything. + */ + temp_iter = iter; + + if (gtk_tree_model_iter_next (tree_model, &iter)) + { + path = gtk_tree_model_get_path (tree_model, &iter); + row_reference = gtk_tree_row_reference_new (tree_model, path); + } + else + { + path = gtk_tree_model_get_path (tree_model, &temp_iter); + if (gtk_tree_path_prev (path)) + { + row_reference = gtk_tree_row_reference_new (tree_model, path); + } + } + gtk_tree_path_free (path); + } + + gtk_tree_path_free (file_path); + + fm_list_model_remove_file (list_view->details->model, file, directory); + + if (gtk_tree_row_reference_valid (row_reference)) + { + if (list_view->details->new_selection_path) + { + gtk_tree_path_free (list_view->details->new_selection_path); + } + list_view->details->new_selection_path = gtk_tree_row_reference_get_path (row_reference); + } + + if (row_reference) + { + gtk_tree_row_reference_free (row_reference); + } + } + + +} + +static void +fm_list_view_set_selection (FMDirectoryView *view, GList *selection) +{ + FMListView *list_view; + GtkTreeSelection *tree_selection; + GList *node; + GList *iters, *l; + CajaFile *file; + + list_view = FM_LIST_VIEW (view); + tree_selection = gtk_tree_view_get_selection (list_view->details->tree_view); + + g_signal_handlers_block_by_func (tree_selection, list_selection_changed_callback, view); + + gtk_tree_selection_unselect_all (tree_selection); + for (node = selection; node != NULL; node = node->next) + { + file = node->data; + iters = fm_list_model_get_all_iters_for_file (list_view->details->model, file); + + for (l = iters; l != NULL; l = l->next) + { + gtk_tree_selection_select_iter (tree_selection, + (GtkTreeIter *)l->data); + } + eel_g_list_free_deep (iters); + } + + g_signal_handlers_unblock_by_func (tree_selection, list_selection_changed_callback, view); + fm_directory_view_notify_selection_changed (view); +} + +static void +fm_list_view_invert_selection (FMDirectoryView *view) +{ + FMListView *list_view; + GtkTreeSelection *tree_selection; + GList *node; + GList *iters, *l; + CajaFile *file; + GList *selection = NULL; + + list_view = FM_LIST_VIEW (view); + tree_selection = gtk_tree_view_get_selection (list_view->details->tree_view); + + g_signal_handlers_block_by_func (tree_selection, list_selection_changed_callback, view); + + gtk_tree_selection_selected_foreach (tree_selection, + fm_list_view_get_selection_foreach_func, &selection); + + gtk_tree_selection_select_all (tree_selection); + + for (node = selection; node != NULL; node = node->next) + { + file = node->data; + iters = fm_list_model_get_all_iters_for_file (list_view->details->model, file); + + for (l = iters; l != NULL; l = l->next) + { + gtk_tree_selection_unselect_iter (tree_selection, + (GtkTreeIter *)l->data); + } + eel_g_list_free_deep (iters); + } + + g_list_free (selection); + + g_signal_handlers_unblock_by_func (tree_selection, list_selection_changed_callback, view); + fm_directory_view_notify_selection_changed (view); +} + +static void +fm_list_view_select_all (FMDirectoryView *view) +{ + gtk_tree_selection_select_all (gtk_tree_view_get_selection (FM_LIST_VIEW (view)->details->tree_view)); +} + +static void +column_editor_response_callback (GtkWidget *dialog, + int response_id, + gpointer user_data) +{ + gtk_widget_destroy (GTK_WIDGET (dialog)); +} + +static void +column_chooser_changed_callback (CajaColumnChooser *chooser, + FMListView *view) +{ + CajaFile *file; + char **visible_columns; + char **column_order; + GList *list; + int i; + + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (view)); + + caja_column_chooser_get_settings (chooser, + &visible_columns, + &column_order); + + list = NULL; + for (i = 0; visible_columns[i] != NULL; ++i) + { + list = g_list_prepend (list, visible_columns[i]); + } + list = g_list_reverse (list); + caja_file_set_metadata_list (file, + CAJA_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS, + list); + g_list_free (list); + + list = NULL; + for (i = 0; column_order[i] != NULL; ++i) + { + list = g_list_prepend (list, column_order[i]); + } + list = g_list_reverse (list); + caja_file_set_metadata_list (file, + CAJA_METADATA_KEY_LIST_VIEW_COLUMN_ORDER, + list); + g_list_free (list); + + apply_columns_settings (view, column_order, visible_columns); + + g_strfreev (visible_columns); + g_strfreev (column_order); +} + +static void +column_chooser_set_from_arrays (CajaColumnChooser *chooser, + FMListView *view, + char **visible_columns, + char **column_order) +{ + g_signal_handlers_block_by_func + (chooser, G_CALLBACK (column_chooser_changed_callback), view); + + caja_column_chooser_set_settings (chooser, + visible_columns, + column_order); + + g_signal_handlers_unblock_by_func + (chooser, G_CALLBACK (column_chooser_changed_callback), view); +} + +static void +column_chooser_set_from_settings (CajaColumnChooser *chooser, + FMListView *view) +{ + char **visible_columns; + char **column_order; + + visible_columns = get_visible_columns (view); + column_order = get_column_order (view); + + column_chooser_set_from_arrays (chooser, view, + visible_columns, column_order); + + g_strfreev (visible_columns); + g_strfreev (column_order); +} + +static void +column_chooser_use_default_callback (CajaColumnChooser *chooser, + FMListView *view) +{ + CajaFile *file; + char **default_columns; + char **default_order; + + file = fm_directory_view_get_directory_as_file + (FM_DIRECTORY_VIEW (view)); + + caja_file_set_metadata_list (file, CAJA_METADATA_KEY_LIST_VIEW_COLUMN_ORDER, NULL); + caja_file_set_metadata_list (file, CAJA_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS, NULL); + + /* set view values ourselves, as new metadata could not have been + * updated yet. + */ + default_columns = caja_file_is_in_trash (file) ? + g_strdupv ((gchar **) default_trash_visible_columns) : + g_strdupv (default_visible_columns_auto_value); + + default_order = caja_file_is_in_trash (file) ? + g_strdupv ((gchar **) default_trash_columns_order) : + g_strdupv (default_column_order_auto_value); + + apply_columns_settings (view, default_order, default_columns); + column_chooser_set_from_arrays (chooser, view, + default_columns, default_order); + + g_strfreev (default_columns); + g_strfreev (default_order); +} + +static GtkWidget * +create_column_editor (FMListView *view) +{ + GtkWidget *window; + GtkWidget *label; + GtkWidget *box; + GtkWidget *column_chooser; + GtkWidget *alignment; + CajaFile *file; + char *str; + char *name; + const char *label_text; + + file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (view)); + name = caja_file_get_display_name (file); + str = g_strdup_printf (_("%s Visible Columns"), name); + g_free (name); + + window = gtk_dialog_new_with_buttons (str, + GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (view))), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, + NULL); + g_free (str); + g_signal_connect (window, "response", + G_CALLBACK (column_editor_response_callback), NULL); + + gtk_window_set_default_size (GTK_WINDOW (window), 300, 400); + + box = gtk_vbox_new (FALSE, 12); + gtk_container_set_border_width (GTK_CONTAINER (box), 12); + gtk_widget_show (box); + gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (window))), box); + + label_text = _("Choose the order of information to appear in this folder:"); + str = g_strconcat ("<b>", label_text, "</b>", NULL); + label = gtk_label_new (NULL); + gtk_label_set_markup (GTK_LABEL (label), str); + gtk_label_set_line_wrap (GTK_LABEL (label), FALSE); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0); + + g_free (str); + + alignment = gtk_alignment_new (0.5, 0.5, 1, 1); + gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), + 0, 0, 12, 0); + gtk_widget_show (alignment); + gtk_box_pack_start (GTK_BOX (box), alignment, TRUE, TRUE, 0); + + column_chooser = caja_column_chooser_new (file); + gtk_widget_show (column_chooser); + gtk_container_add (GTK_CONTAINER (alignment), column_chooser); + + g_signal_connect (column_chooser, "changed", + G_CALLBACK (column_chooser_changed_callback), + view); + g_signal_connect (column_chooser, "use_default", + G_CALLBACK (column_chooser_use_default_callback), + view); + + column_chooser_set_from_settings + (CAJA_COLUMN_CHOOSER (column_chooser), view); + + return window; +} + +static void +action_visible_columns_callback (GtkAction *action, + gpointer callback_data) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (callback_data); + + if (list_view->details->column_editor) + { + gtk_window_present (GTK_WINDOW (list_view->details->column_editor)); + } + else + { + list_view->details->column_editor = create_column_editor (list_view); + eel_add_weak_pointer (&list_view->details->column_editor); + + gtk_widget_show (list_view->details->column_editor); + } +} + +static const GtkActionEntry list_view_entries[] = +{ + /* name, stock id */ { "Visible Columns", NULL, + /* label, accelerator */ N_("Visible _Columns..."), NULL, + /* tooltip */ N_("Select the columns visible in this folder"), + G_CALLBACK (action_visible_columns_callback) + }, +}; + +static void +fm_list_view_merge_menus (FMDirectoryView *view) +{ + FMListView *list_view; + GtkUIManager *ui_manager; + GtkActionGroup *action_group; + const char *ui; + + EEL_CALL_PARENT (FM_DIRECTORY_VIEW_CLASS, merge_menus, (view)); + + list_view = FM_LIST_VIEW (view); + + ui_manager = fm_directory_view_get_ui_manager (view); + + action_group = gtk_action_group_new ("ListViewActions"); + gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE); + list_view->details->list_action_group = action_group; + gtk_action_group_add_actions (action_group, + list_view_entries, G_N_ELEMENTS (list_view_entries), + list_view); + + gtk_ui_manager_insert_action_group (ui_manager, action_group, 0); + g_object_unref (action_group); /* owned by ui manager */ + + ui = caja_ui_string_get ("caja-list-view-ui.xml"); + list_view->details->list_merge_id = gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, NULL); + + list_view->details->menus_ready = TRUE; +} + +static void +fm_list_view_unmerge_menus (FMDirectoryView *view) +{ + FMListView *list_view; + GtkUIManager *ui_manager; + + list_view = FM_LIST_VIEW (view); + + FM_DIRECTORY_VIEW_CLASS (fm_list_view_parent_class)->unmerge_menus (view); + + ui_manager = fm_directory_view_get_ui_manager (view); + if (ui_manager != NULL) + { + caja_ui_unmerge_ui (ui_manager, + &list_view->details->list_merge_id, + &list_view->details->list_action_group); + } +} + +static void +fm_list_view_update_menus (FMDirectoryView *view) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (view); + + /* don't update if the menus aren't ready */ + if (!list_view->details->menus_ready) + { + return; + } + + EEL_CALL_PARENT (FM_DIRECTORY_VIEW_CLASS, update_menus, (view)); +} + +/* Reset sort criteria and zoom level to match defaults */ +static void +fm_list_view_reset_to_defaults (FMDirectoryView *view) +{ + CajaFile *file; + const gchar *default_sort_order; + gboolean default_sort_reversed; + + file = fm_directory_view_get_directory_as_file (view); + + caja_file_set_metadata (file, CAJA_METADATA_KEY_LIST_VIEW_SORT_COLUMN, NULL, NULL); + caja_file_set_metadata (file, CAJA_METADATA_KEY_LIST_VIEW_SORT_REVERSED, NULL, NULL); + caja_file_set_metadata (file, CAJA_METADATA_KEY_LIST_VIEW_ZOOM_LEVEL, NULL, NULL); + caja_file_set_metadata_list (file, CAJA_METADATA_KEY_LIST_VIEW_COLUMN_ORDER, NULL); + caja_file_set_metadata_list (file, CAJA_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS, NULL); + + default_sort_order = get_default_sort_order (file, &default_sort_reversed); + + gtk_tree_sortable_set_sort_column_id + (GTK_TREE_SORTABLE (FM_LIST_VIEW (view)->details->model), + fm_list_model_get_sort_column_id_from_attribute (FM_LIST_VIEW (view)->details->model, + g_quark_from_string (default_sort_order)), + default_sort_reversed ? GTK_SORT_DESCENDING : GTK_SORT_ASCENDING); + + fm_list_view_set_zoom_level (FM_LIST_VIEW (view), get_default_zoom_level (), FALSE); + set_columns_settings_from_metadata_and_preferences (FM_LIST_VIEW (view)); +} + +static void +fm_list_view_scale_font_size (FMListView *view, + CajaZoomLevel new_level) +{ + GList *l; + static gboolean first_time = TRUE; + static double pango_scale[7]; + int medium; + int i; + + g_return_if_fail (new_level >= CAJA_ZOOM_LEVEL_SMALLEST && + new_level <= CAJA_ZOOM_LEVEL_LARGEST); + + if (first_time) + { + first_time = FALSE; + medium = CAJA_ZOOM_LEVEL_SMALLER; + pango_scale[medium] = PANGO_SCALE_MEDIUM; + for (i = medium; i > CAJA_ZOOM_LEVEL_SMALLEST; i--) + { + pango_scale[i - 1] = (1 / 1.2) * pango_scale[i]; + } + for (i = medium; i < CAJA_ZOOM_LEVEL_LARGEST; i++) + { + pango_scale[i + 1] = 1.2 * pango_scale[i]; + } + } + + g_object_set (G_OBJECT (view->details->file_name_cell), + "scale", pango_scale[new_level], + NULL); + for (l = view->details->cells; l != NULL; l = l->next) + { + g_object_set (G_OBJECT (l->data), + "scale", pango_scale[new_level], + NULL); + } +} + +static void +fm_list_view_set_zoom_level (FMListView *view, + CajaZoomLevel new_level, + gboolean always_emit) +{ + int icon_size; + int column, emblem_column; + + g_return_if_fail (FM_IS_LIST_VIEW (view)); + g_return_if_fail (new_level >= CAJA_ZOOM_LEVEL_SMALLEST && + new_level <= CAJA_ZOOM_LEVEL_LARGEST); + + if (view->details->zoom_level == new_level) + { + if (always_emit) + { + g_signal_emit_by_name (FM_DIRECTORY_VIEW(view), "zoom_level_changed"); + } + return; + } + + view->details->zoom_level = new_level; + g_signal_emit_by_name (FM_DIRECTORY_VIEW(view), "zoom_level_changed"); + + caja_file_set_integer_metadata + (fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (view)), + CAJA_METADATA_KEY_LIST_VIEW_ZOOM_LEVEL, + get_default_zoom_level (), + new_level); + + /* Select correctly scaled icons. */ + column = fm_list_model_get_column_id_from_zoom_level (new_level); + emblem_column = fm_list_model_get_emblem_column_id_from_zoom_level (new_level); + gtk_tree_view_column_set_attributes (view->details->file_name_column, + GTK_CELL_RENDERER (view->details->pixbuf_cell), + "pixbuf", column, + "pixbuf_emblem", emblem_column, + NULL); + + /* Scale text. */ + fm_list_view_scale_font_size (view, new_level); + + /* Make all rows the same size. */ + icon_size = caja_get_icon_size_for_zoom_level (new_level); + gtk_cell_renderer_set_fixed_size (GTK_CELL_RENDERER (view->details->pixbuf_cell), + -1, icon_size); + + fm_directory_view_update_menus (FM_DIRECTORY_VIEW (view)); +} + +static void +fm_list_view_bump_zoom_level (FMDirectoryView *view, int zoom_increment) +{ + FMListView *list_view; + gint new_level; + + g_return_if_fail (FM_IS_LIST_VIEW (view)); + + list_view = FM_LIST_VIEW (view); + new_level = list_view->details->zoom_level + zoom_increment; + + if (new_level >= CAJA_ZOOM_LEVEL_SMALLEST && + new_level <= CAJA_ZOOM_LEVEL_LARGEST) + { + fm_list_view_set_zoom_level (list_view, new_level, FALSE); + } +} + +static CajaZoomLevel +fm_list_view_get_zoom_level (FMDirectoryView *view) +{ + FMListView *list_view; + + g_return_val_if_fail (FM_IS_LIST_VIEW (view), CAJA_ZOOM_LEVEL_STANDARD); + + list_view = FM_LIST_VIEW (view); + + return list_view->details->zoom_level; +} + +static void +fm_list_view_zoom_to_level (FMDirectoryView *view, + CajaZoomLevel zoom_level) +{ + FMListView *list_view; + + g_return_if_fail (FM_IS_LIST_VIEW (view)); + + list_view = FM_LIST_VIEW (view); + + fm_list_view_set_zoom_level (list_view, zoom_level, FALSE); +} + +static void +fm_list_view_restore_default_zoom_level (FMDirectoryView *view) +{ + FMListView *list_view; + + g_return_if_fail (FM_IS_LIST_VIEW (view)); + + list_view = FM_LIST_VIEW (view); + + fm_list_view_set_zoom_level (list_view, get_default_zoom_level (), FALSE); +} + +static gboolean +fm_list_view_can_zoom_in (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_LIST_VIEW (view), FALSE); + + return FM_LIST_VIEW (view)->details->zoom_level < CAJA_ZOOM_LEVEL_LARGEST; +} + +static gboolean +fm_list_view_can_zoom_out (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_LIST_VIEW (view), FALSE); + + return FM_LIST_VIEW (view)->details->zoom_level > CAJA_ZOOM_LEVEL_SMALLEST; +} + +static void +fm_list_view_start_renaming_file (FMDirectoryView *view, + CajaFile *file, + gboolean select_all) +{ + FMListView *list_view; + GtkTreeIter iter; + GtkTreePath *path; + + list_view = FM_LIST_VIEW (view); + + /* Select all if we are in renaming mode already */ + if (list_view->details->file_name_column && list_view->details->editable_widget) + { + gtk_editable_select_region ( + GTK_EDITABLE (list_view->details->editable_widget), + 0, + -1); + return; + } + + if (!fm_list_model_get_first_iter_for_file (list_view->details->model, file, &iter)) + { + return; + } + + /* Freeze updates to the view to prevent losing rename focus when the tree view updates */ + fm_directory_view_freeze_updates (FM_DIRECTORY_VIEW (view)); + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (list_view->details->model), &iter); + + /* Make filename-cells editable. */ + g_object_set (G_OBJECT (list_view->details->file_name_cell), + "editable", TRUE, + NULL); + + gtk_tree_view_scroll_to_cell (list_view->details->tree_view, + NULL, + list_view->details->file_name_column, + TRUE, 0.0, 0.0); + gtk_tree_view_set_cursor (list_view->details->tree_view, + path, + list_view->details->file_name_column, + TRUE); + + gtk_tree_path_free (path); +} + +static void +fm_list_view_click_policy_changed (FMDirectoryView *directory_view) +{ + GdkWindow *win; + GdkDisplay *display; + FMListView *view; + GtkTreeIter iter; + GtkTreeView *tree; + + view = FM_LIST_VIEW (directory_view); + + /* ensure that we unset the hand cursor and refresh underlined rows */ + if (click_policy_auto_value == CAJA_CLICK_POLICY_DOUBLE) + { + if (view->details->hover_path != NULL) + { + if (gtk_tree_model_get_iter (GTK_TREE_MODEL (view->details->model), + &iter, view->details->hover_path)) + { + gtk_tree_model_row_changed (GTK_TREE_MODEL (view->details->model), + view->details->hover_path, &iter); + } + + gtk_tree_path_free (view->details->hover_path); + view->details->hover_path = NULL; + } + + tree = view->details->tree_view; + if (gtk_widget_get_realized (GTK_WIDGET (tree))) + { + win = gtk_widget_get_window (GTK_WIDGET (tree)); + gdk_window_set_cursor (win, NULL); + + display = gtk_widget_get_display (GTK_WIDGET (view)); + if (display != NULL) + { + gdk_display_flush (display); + } + } + + if (hand_cursor != NULL) + { + gdk_cursor_unref (hand_cursor); + hand_cursor = NULL; + } + } + else if (click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE) + { + if (hand_cursor == NULL) + { + hand_cursor = gdk_cursor_new(GDK_HAND2); + } + } +} + +static void +default_sort_order_changed_callback (gpointer callback_data) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (callback_data); + + set_sort_order_from_metadata_and_preferences (list_view); +} + +static void +default_zoom_level_changed_callback (gpointer callback_data) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (callback_data); + + set_zoom_level_from_metadata_and_preferences (list_view); +} + +static void +default_visible_columns_changed_callback (gpointer callback_data) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (callback_data); + + set_columns_settings_from_metadata_and_preferences (list_view); +} + +static void +default_column_order_changed_callback (gpointer callback_data) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (callback_data); + + set_columns_settings_from_metadata_and_preferences (list_view); +} + +static void +fm_list_view_sort_directories_first_changed (FMDirectoryView *view) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (view); + + fm_list_model_set_should_sort_directories_first (list_view->details->model, + fm_directory_view_should_sort_directories_first (view)); +} + +static int +fm_list_view_compare_files (FMDirectoryView *view, CajaFile *file1, CajaFile *file2) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (view); + return fm_list_model_compare_func (list_view->details->model, file1, file2); +} + +static gboolean +fm_list_view_using_manual_layout (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_LIST_VIEW (view), FALSE); + + return FALSE; +} + +static void +fm_list_view_dispose (GObject *object) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (object); + + if (list_view->details->model) + { + stop_cell_editing (list_view); + g_object_unref (list_view->details->model); + list_view->details->model = NULL; + } + + if (list_view->details->drag_dest) + { + g_object_unref (list_view->details->drag_dest); + list_view->details->drag_dest = NULL; + } + + if (list_view->details->renaming_file_activate_timeout != 0) + { + g_source_remove (list_view->details->renaming_file_activate_timeout); + list_view->details->renaming_file_activate_timeout = 0; + } + + if (list_view->details->clipboard_handler_id != 0) + { + g_signal_handler_disconnect (caja_clipboard_monitor_get (), + list_view->details->clipboard_handler_id); + list_view->details->clipboard_handler_id = 0; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +fm_list_view_finalize (GObject *object) +{ + FMListView *list_view; + + list_view = FM_LIST_VIEW (object); + + g_free (list_view->details->original_name); + list_view->details->original_name = NULL; + + if (list_view->details->double_click_path[0]) + { + gtk_tree_path_free (list_view->details->double_click_path[0]); + } + if (list_view->details->double_click_path[1]) + { + gtk_tree_path_free (list_view->details->double_click_path[1]); + } + if (list_view->details->new_selection_path) + { + gtk_tree_path_free (list_view->details->new_selection_path); + } + + g_list_free (list_view->details->cells); + g_hash_table_destroy (list_view->details->columns); + + if (list_view->details->hover_path != NULL) + { + gtk_tree_path_free (list_view->details->hover_path); + } + + if (list_view->details->column_editor != NULL) + { + gtk_widget_destroy (list_view->details->column_editor); + } + + g_free (list_view->details); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +fm_list_view_emblems_changed (FMDirectoryView *directory_view) +{ + g_assert (FM_IS_LIST_VIEW (directory_view)); + + /* FIXME: This needs to update the emblems of the icons, since + * relative emblems may have changed. + */ +} + +static char * +fm_list_view_get_first_visible_file (CajaView *view) +{ + CajaFile *file; + GtkTreePath *path; + GtkTreeIter iter; + FMListView *list_view; + + list_view = FM_LIST_VIEW (view); + + if (gtk_tree_view_get_path_at_pos (list_view->details->tree_view, + 0, 0, + &path, NULL, NULL, NULL)) + { + gtk_tree_model_get_iter (GTK_TREE_MODEL (list_view->details->model), + &iter, path); + + gtk_tree_path_free (path); + + gtk_tree_model_get (GTK_TREE_MODEL (list_view->details->model), + &iter, + FM_LIST_MODEL_FILE_COLUMN, &file, + -1); + if (file) + { + char *uri; + + uri = caja_file_get_uri (file); + + caja_file_unref (file); + + return uri; + } + } + + return NULL; +} + +static void +fm_list_view_scroll_to_file (FMListView *view, + CajaFile *file) +{ + GtkTreePath *path; + GtkTreeIter iter; + + if (!fm_list_model_get_first_iter_for_file (view->details->model, file, &iter)) + { + return; + } + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (view->details->model), &iter); + + gtk_tree_view_scroll_to_cell (view->details->tree_view, + path, NULL, + TRUE, 0.0, 0.0); + + gtk_tree_path_free (path); +} + +static void +list_view_scroll_to_file (CajaView *view, + const char *uri) +{ + CajaFile *file; + + if (uri != NULL) + { + /* Only if existing, since we don't want to add the file to + the directory if it has been removed since then */ + file = caja_file_get_existing_by_uri (uri); + if (file != NULL) + { + fm_list_view_scroll_to_file (FM_LIST_VIEW (view), file); + caja_file_unref (file); + } + } +} + +static void +list_view_notify_clipboard_info (CajaClipboardMonitor *monitor, + CajaClipboardInfo *info, + FMListView *view) +{ + /* this could be called as a result of _end_loading() being + * called after _dispose(), where the model is cleared. + */ + if (view->details->model == NULL) + { + return; + } + + if (info != NULL && info->cut) + { + fm_list_model_set_highlight_for_files (view->details->model, info->files); + } + else + { + fm_list_model_set_highlight_for_files (view->details->model, NULL); + } +} + +static void +fm_list_view_end_loading (FMDirectoryView *view, + gboolean all_files_seen) +{ + CajaClipboardMonitor *monitor; + CajaClipboardInfo *info; + + monitor = caja_clipboard_monitor_get (); + info = caja_clipboard_monitor_get_clipboard_info (monitor); + + list_view_notify_clipboard_info (monitor, info, FM_LIST_VIEW (view)); +} + +static void +real_set_is_active (FMDirectoryView *view, + gboolean is_active) +{ + GtkWidget *tree_view; + GtkStyle *style; + GdkColor color; + + tree_view = GTK_WIDGET (fm_list_view_get_tree_view (FM_LIST_VIEW (view))); + + if (is_active) + { + gtk_widget_modify_base (tree_view, GTK_STATE_NORMAL, NULL); + } + else + { + style = gtk_widget_get_style (tree_view); + color = style->base[GTK_STATE_INSENSITIVE]; + gtk_widget_modify_base (tree_view, GTK_STATE_NORMAL, &color); + } + + EEL_CALL_PARENT (FM_DIRECTORY_VIEW_CLASS, + set_is_active, (view, is_active)); +} + +static void +fm_list_view_class_init (FMListViewClass *class) +{ + FMDirectoryViewClass *fm_directory_view_class; + + fm_directory_view_class = FM_DIRECTORY_VIEW_CLASS (class); + + G_OBJECT_CLASS (class)->dispose = fm_list_view_dispose; + G_OBJECT_CLASS (class)->finalize = fm_list_view_finalize; + + fm_directory_view_class->add_file = fm_list_view_add_file; + fm_directory_view_class->begin_loading = fm_list_view_begin_loading; + fm_directory_view_class->end_loading = fm_list_view_end_loading; + fm_directory_view_class->bump_zoom_level = fm_list_view_bump_zoom_level; + fm_directory_view_class->can_zoom_in = fm_list_view_can_zoom_in; + fm_directory_view_class->can_zoom_out = fm_list_view_can_zoom_out; + fm_directory_view_class->click_policy_changed = fm_list_view_click_policy_changed; + fm_directory_view_class->clear = fm_list_view_clear; + fm_directory_view_class->file_changed = fm_list_view_file_changed; + fm_directory_view_class->get_background_widget = fm_list_view_get_background_widget; + fm_directory_view_class->get_selection = fm_list_view_get_selection; + fm_directory_view_class->get_selection_for_file_transfer = fm_list_view_get_selection_for_file_transfer; + fm_directory_view_class->get_item_count = fm_list_view_get_item_count; + fm_directory_view_class->is_empty = fm_list_view_is_empty; + fm_directory_view_class->remove_file = fm_list_view_remove_file; + fm_directory_view_class->merge_menus = fm_list_view_merge_menus; + fm_directory_view_class->unmerge_menus = fm_list_view_unmerge_menus; + fm_directory_view_class->update_menus = fm_list_view_update_menus; + fm_directory_view_class->reset_to_defaults = fm_list_view_reset_to_defaults; + fm_directory_view_class->restore_default_zoom_level = fm_list_view_restore_default_zoom_level; + fm_directory_view_class->reveal_selection = fm_list_view_reveal_selection; + fm_directory_view_class->select_all = fm_list_view_select_all; + fm_directory_view_class->set_selection = fm_list_view_set_selection; + fm_directory_view_class->invert_selection = fm_list_view_invert_selection; + fm_directory_view_class->compare_files = fm_list_view_compare_files; + fm_directory_view_class->sort_directories_first_changed = fm_list_view_sort_directories_first_changed; + fm_directory_view_class->start_renaming_file = fm_list_view_start_renaming_file; + fm_directory_view_class->get_zoom_level = fm_list_view_get_zoom_level; + fm_directory_view_class->zoom_to_level = fm_list_view_zoom_to_level; + fm_directory_view_class->emblems_changed = fm_list_view_emblems_changed; + fm_directory_view_class->end_file_changes = fm_list_view_end_file_changes; + fm_directory_view_class->using_manual_layout = fm_list_view_using_manual_layout; + fm_directory_view_class->set_is_active = real_set_is_active; + + eel_preferences_add_auto_enum (CAJA_PREFERENCES_CLICK_POLICY, + &click_policy_auto_value); + eel_preferences_add_auto_string (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_SORT_ORDER, + (const char **) &default_sort_order_auto_value); + eel_preferences_add_auto_boolean (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_SORT_IN_REVERSE_ORDER, + &default_sort_reversed_auto_value); + eel_preferences_add_auto_enum (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_ZOOM_LEVEL, + (int *) &default_zoom_level_auto_value); + eel_preferences_add_auto_string_array (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS, + &default_visible_columns_auto_value); + eel_preferences_add_auto_string_array (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_COLUMN_ORDER, + &default_column_order_auto_value); +} + +static const char * +fm_list_view_get_id (CajaView *view) +{ + return FM_LIST_VIEW_ID; +} + + +static void +fm_list_view_iface_init (CajaViewIface *iface) +{ + fm_directory_view_init_view_iface (iface); + + iface->get_view_id = fm_list_view_get_id; + iface->get_first_visible_file = fm_list_view_get_first_visible_file; + iface->scroll_to_file = list_view_scroll_to_file; + iface->get_title = NULL; +} + + +static void +fm_list_view_init (FMListView *list_view) +{ + list_view->details = g_new0 (FMListViewDetails, 1); + + create_and_set_up_tree_view (list_view); + + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_SORT_ORDER, + default_sort_order_changed_callback, + list_view, G_OBJECT (list_view)); + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_SORT_IN_REVERSE_ORDER, + default_sort_order_changed_callback, + list_view, G_OBJECT (list_view)); + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_ZOOM_LEVEL, + default_zoom_level_changed_callback, + list_view, G_OBJECT (list_view)); + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS, + default_visible_columns_changed_callback, + list_view, G_OBJECT (list_view)); + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_COLUMN_ORDER, + default_column_order_changed_callback, + list_view, G_OBJECT (list_view)); + + fm_list_view_click_policy_changed (FM_DIRECTORY_VIEW (list_view)); + + fm_list_view_sort_directories_first_changed (FM_DIRECTORY_VIEW (list_view)); + + /* ensure that the zoom level is always set in begin_loading */ + list_view->details->zoom_level = CAJA_ZOOM_LEVEL_SMALLEST - 1; + + list_view->details->hover_path = NULL; + list_view->details->clipboard_handler_id = + g_signal_connect (caja_clipboard_monitor_get (), + "clipboard_info", + G_CALLBACK (list_view_notify_clipboard_info), list_view); +} + +static CajaView * +fm_list_view_create (CajaWindowSlotInfo *slot) +{ + FMListView *view; + + view = g_object_new (FM_TYPE_LIST_VIEW, + "window-slot", slot, + NULL); + return CAJA_VIEW (view); +} + +static gboolean +fm_list_view_supports_uri (const char *uri, + GFileType file_type, + const char *mime_type) +{ + if (file_type == G_FILE_TYPE_DIRECTORY) + { + return TRUE; + } + if (strcmp (mime_type, CAJA_SAVED_SEARCH_MIMETYPE) == 0) + { + return TRUE; + } + if (g_str_has_prefix (uri, "trash:")) + { + return TRUE; + } + if (g_str_has_prefix (uri, EEL_SEARCH_URI)) + { + return TRUE; + } + + return FALSE; +} + +static CajaViewInfo fm_list_view = +{ + FM_LIST_VIEW_ID, + /* translators: this is used in the view selection dropdown + * of navigation windows and in the preferences dialog */ + N_("List View"), + /* translators: this is used in the view menu */ + N_("_List"), + N_("The list view encountered an error."), + N_("The list view encountered an error while starting up."), + N_("Display this location with the list view."), + fm_list_view_create, + fm_list_view_supports_uri +}; + +void +fm_list_view_register (void) +{ + fm_list_view.view_combo_label = _(fm_list_view.view_combo_label); + fm_list_view.view_menu_label_with_mnemonic = _(fm_list_view.view_menu_label_with_mnemonic); + fm_list_view.error_label = _(fm_list_view.error_label); + fm_list_view.startup_error_label = _(fm_list_view.startup_error_label); + fm_list_view.display_location_label = _(fm_list_view.display_location_label); + + caja_view_factory_register (&fm_list_view); +} + +GtkTreeView* +fm_list_view_get_tree_view (FMListView *list_view) +{ + return list_view->details->tree_view; +} diff --git a/src/file-manager/fm-list-view.h b/src/file-manager/fm-list-view.h new file mode 100644 index 00000000..2defc6ca --- /dev/null +++ b/src/file-manager/fm-list-view.h @@ -0,0 +1,63 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-list-view.h - interface for list view of directory. + + Copyright (C) 2000 Eazel, Inc. + Copyright (C) 2001 Anders Carlsson <[email protected]> + + 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]> + Anders Carlsson <[email protected]> +*/ + +#ifndef FM_LIST_VIEW_H +#define FM_LIST_VIEW_H + +#include "fm-directory-view.h" + +#define FM_TYPE_LIST_VIEW fm_list_view_get_type() +#define FM_LIST_VIEW(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_LIST_VIEW, FMListView)) +#define FM_LIST_VIEW_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_LIST_VIEW, FMListViewClass)) +#define FM_IS_LIST_VIEW(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_LIST_VIEW)) +#define FM_IS_LIST_VIEW_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_LIST_VIEW)) +#define FM_LIST_VIEW_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_LIST_VIEW, FMListViewClass)) + +#define FM_LIST_VIEW_ID "OAFIID:Caja_File_Manager_List_View" + +typedef struct FMListViewDetails FMListViewDetails; + +typedef struct +{ + FMDirectoryView parent_instance; + FMListViewDetails *details; +} FMListView; + +typedef struct +{ + FMDirectoryViewClass parent_class; +} FMListViewClass; + +GType fm_list_view_get_type (void); +void fm_list_view_register (void); +GtkTreeView* fm_list_view_get_tree_view (FMListView *list_view); + +#endif /* FM_LIST_VIEW_H */ diff --git a/src/file-manager/fm-properties-window.c b/src/file-manager/fm-properties-window.c new file mode 100644 index 00000000..6310b407 --- /dev/null +++ b/src/file-manager/fm-properties-window.c @@ -0,0 +1,5835 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-properties-window.c - window that lets user modify file properties + + 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: Darin Adler <[email protected]> +*/ + +#include <config.h> +#include "fm-properties-window.h" +#include "fm-ditem-page.h" + +#define MATE_DESKTOP_USE_UNSTABLE_API + +#include "fm-error-reporting.h" +#include "libcaja-private/caja-mime-application-chooser.h" +#include <eel/eel-accessibility.h> +#include <eel/eel-gdk-pixbuf-extensions.h> +#include <eel/eel-glib-extensions.h> +#include <eel/eel-mate-extensions.h> +#include <eel/eel-gtk-extensions.h> +#include <eel/eel-labeled-image.h> +#include <eel/eel-stock-dialogs.h> +#include <eel/eel-string.h> +#include <eel/eel-vfs-extensions.h> +#include <eel/eel-wrap-table.h> +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> +#include <glib/gi18n.h> +#include <libmateui/mate-desktop-thumbnail.h> +#include <libcaja-extension/caja-property-page-provider.h> +#include <libcaja-private/caja-customization-data.h> +#include <libcaja-private/caja-entry.h> +#include <libcaja-private/caja-file-attributes.h> +#include <libcaja-private/caja-file-operations.h> +#include <libcaja-private/caja-desktop-icon-file.h> +#include <libcaja-private/caja-global-preferences.h> +#include <libcaja-private/caja-emblem-utils.h> +#include <libcaja-private/caja-link.h> +#include <libcaja-private/caja-metadata.h> +#include <libcaja-private/caja-module.h> +#include <libcaja-private/caja-undo-signal-handlers.h> +#include <libcaja-private/caja-mime-actions.h> +#include <libcaja-private/caja-undo.h> +#include <string.h> +#include <sys/stat.h> +#include <cairo.h> + +#if HAVE_SYS_VFS_H +#include <sys/vfs.h> +#elif HAVE_SYS_MOUNT_H +#if HAVE_SYS_PARAM_H +#include <sys/param.h> +#endif +#include <sys/mount.h> +#endif + +#define USED_FILL_R (0.988235294 * 65535) +#define USED_FILL_G (0.91372549 * 65535) +#define USED_FILL_B (0.309803922 * 65535) + +#define FREE_FILL_R (0.447058824 * 65535) +#define FREE_FILL_G (0.623529412 * 65535) +#define FREE_FILL_B (0.811764706 * 65535) + + +#define PREVIEW_IMAGE_WIDTH 96 + +#define ROW_PAD 6 + +static GHashTable *windows; +static GHashTable *pending_lists; + +struct FMPropertiesWindowDetails { + GList *original_files; + GList *target_files; + + GtkNotebook *notebook; + + GtkTable *basic_table; + GtkTable *permissions_table; + gboolean advanced_permissions; + + GtkWidget *icon_button; + GtkWidget *icon_image; + GtkWidget *icon_chooser; + + GtkLabel *name_label; + GtkWidget *name_field; + unsigned int name_row; + char *pending_name; + + GtkLabel *directory_contents_title_field; + GtkLabel *directory_contents_value_field; + guint update_directory_contents_timeout_id; + guint update_files_timeout_id; + + GList *emblem_buttons; + GHashTable *initial_emblems; + + CajaFile *group_change_file; + char *group_change_group; + unsigned int group_change_timeout; + CajaFile *owner_change_file; + char *owner_change_owner; + unsigned int owner_change_timeout; + + GList *permission_buttons; + GList *permission_combos; + GHashTable *initial_permissions; + gboolean has_recursive_apply; + + GList *value_fields; + + GList *mime_list; + + gboolean deep_count_finished; + + guint total_count; + goffset total_size; + + guint long_operation_underway; + + GList *changed_files; + + guint64 volume_capacity; + guint64 volume_free; + + GdkColor used_color; + GdkColor free_color; + GdkColor used_stroke_color; + GdkColor free_stroke_color; +}; + +enum { + PERMISSIONS_CHECKBOXES_OWNER_ROW, + PERMISSIONS_CHECKBOXES_GROUP_ROW, + PERMISSIONS_CHECKBOXES_OTHERS_ROW, + PERMISSIONS_CHECKBOXES_ROW_COUNT +}; + +enum { + PERMISSIONS_CHECKBOXES_READ_COLUMN, + PERMISSIONS_CHECKBOXES_WRITE_COLUMN, + PERMISSIONS_CHECKBOXES_EXECUTE_COLUMN, + PERMISSIONS_CHECKBOXES_COLUMN_COUNT +}; + +enum { + TITLE_COLUMN, + VALUE_COLUMN, + COLUMN_COUNT +}; + +typedef struct { + GList *original_files; + GList *target_files; + GtkWidget *parent_widget; + char *pending_key; + GHashTable *pending_files; +} StartupData; + +/* drag and drop definitions */ + +enum { + TARGET_URI_LIST, + TARGET_MATE_URI_LIST, + TARGET_RESET_BACKGROUND +}; + +static const GtkTargetEntry target_table[] = { + { "text/uri-list", 0, TARGET_URI_LIST }, + { "x-special/mate-icon-list", 0, TARGET_MATE_URI_LIST }, + { "x-special/mate-reset-background", 0, TARGET_RESET_BACKGROUND } +}; + +#define DIRECTORY_CONTENTS_UPDATE_INTERVAL 200 /* milliseconds */ +#define FILES_UPDATE_INTERVAL 200 /* milliseconds */ +#define STANDARD_EMBLEM_HEIGHT 52 +#define EMBLEM_LABEL_SPACING 2 + +/* + * A timeout before changes through the user/group combo box will be applied. + * When quickly changing owner/groups (i.e. by keyboard or scroll wheel), + * this ensures that the GUI doesn't end up unresponsive. + * + * Both combos react on changes by scheduling a new change and unscheduling + * or cancelling old pending changes. + */ +#define CHOWN_CHGRP_TIMEOUT 300 /* milliseconds */ + +static void directory_contents_value_field_update (FMPropertiesWindow *window); +static void file_changed_callback (CajaFile *file, + gpointer user_data); +static void permission_button_update (FMPropertiesWindow *window, + GtkToggleButton *button); +static void permission_combo_update (FMPropertiesWindow *window, + GtkComboBox *combo); +static void value_field_update (FMPropertiesWindow *window, + GtkLabel *field); +static void properties_window_update (FMPropertiesWindow *window, + GList *files); +static void is_directory_ready_callback (CajaFile *file, + gpointer data); +static void cancel_group_change_callback (FMPropertiesWindow *window); +static void cancel_owner_change_callback (FMPropertiesWindow *window); +static void parent_widget_destroyed_callback (GtkWidget *widget, + gpointer callback_data); +static void select_image_button_callback (GtkWidget *widget, + FMPropertiesWindow *properties_window); +static void set_icon (const char *icon_path, + FMPropertiesWindow *properties_window); +static void remove_pending (StartupData *data, + gboolean cancel_call_when_ready, + gboolean cancel_timed_wait, + gboolean cancel_destroy_handler); +static void append_extension_pages (FMPropertiesWindow *window); + +static gboolean name_field_focus_out (CajaEntry *name_field, + GdkEventFocus *event, + gpointer callback_data); +static void name_field_activate (CajaEntry *name_field, + gpointer callback_data); +static GtkLabel *attach_ellipsizing_value_label (GtkTable *table, + int row, + int column, + const char *initial_text); + +static GtkWidget* create_pie_widget (FMPropertiesWindow *window); + +G_DEFINE_TYPE (FMPropertiesWindow, fm_properties_window, GTK_TYPE_DIALOG); +#define parent_class fm_properties_window_parent_class + +static gboolean +is_multi_file_window (FMPropertiesWindow *window) +{ + GList *l; + int count; + + count = 0; + + for (l = window->details->original_files; l != NULL; l = l->next) { + if (!caja_file_is_gone (CAJA_FILE (l->data))) { + count++; + if (count > 1) { + return TRUE; + } + } + } + + return FALSE; +} + +static int +get_not_gone_original_file_count (FMPropertiesWindow *window) +{ + GList *l; + int count; + + count = 0; + + for (l = window->details->original_files; l != NULL; l = l->next) { + if (!caja_file_is_gone (CAJA_FILE (l->data))) { + count++; + } + } + + return count; +} + +static CajaFile * +get_original_file (FMPropertiesWindow *window) +{ + g_return_val_if_fail (!is_multi_file_window (window), NULL); + + if (window->details->original_files == NULL) { + return NULL; + } + + return CAJA_FILE (window->details->original_files->data); +} + +static CajaFile * +get_target_file_for_original_file (CajaFile *file) +{ + CajaFile *target_file; + GFile *location; + char *uri_to_display; + CajaDesktopLink *link; + + target_file = NULL; + if (CAJA_IS_DESKTOP_ICON_FILE (file)) { + link = caja_desktop_icon_file_get_link (CAJA_DESKTOP_ICON_FILE (file)); + + if (link != NULL) { + /* map to linked URI for these types of links */ + location = caja_desktop_link_get_activation_location (link); + if (location) { + target_file = caja_file_get (location); + g_object_unref (location); + } + + g_object_unref (link); + } + } else { + uri_to_display = caja_file_get_activation_uri (file); + if (uri_to_display != NULL) { + target_file = caja_file_get_by_uri (uri_to_display); + g_free (uri_to_display); + } + } + + if (target_file != NULL) { + return target_file; + } + + /* Ref passed-in file here since we've decided to use it. */ + caja_file_ref (file); + return file; +} + +static CajaFile * +get_target_file (FMPropertiesWindow *window) +{ + return CAJA_FILE (window->details->target_files->data); +} + +static void +add_prompt (GtkVBox *vbox, const char *prompt_text, gboolean pack_at_start) +{ + GtkWidget *prompt; + + prompt = gtk_label_new (prompt_text); + gtk_label_set_justify (GTK_LABEL (prompt), GTK_JUSTIFY_LEFT); + gtk_label_set_line_wrap (GTK_LABEL (prompt), TRUE); + gtk_widget_show (prompt); + if (pack_at_start) { + gtk_box_pack_start (GTK_BOX (vbox), prompt, FALSE, FALSE, 0); + } else { + gtk_box_pack_end (GTK_BOX (vbox), prompt, FALSE, FALSE, 0); + } +} + +static void +add_prompt_and_separator (GtkVBox *vbox, const char *prompt_text) +{ + GtkWidget *separator_line; + + add_prompt (vbox, prompt_text, FALSE); + + separator_line = gtk_hseparator_new (); + gtk_widget_show (separator_line); + gtk_box_pack_end (GTK_BOX (vbox), separator_line, TRUE, TRUE, 2*ROW_PAD); +} + +static void +get_image_for_properties_window (FMPropertiesWindow *window, + char **icon_name, + GdkPixbuf **icon_pixbuf) +{ + CajaIconInfo *icon, *new_icon; + GList *l; + + icon = NULL; + for (l = window->details->original_files; l != NULL; l = l->next) { + CajaFile *file; + + file = CAJA_FILE (l->data); + + if (!icon) { + icon = caja_file_get_icon (file, CAJA_ICON_SIZE_STANDARD, CAJA_FILE_ICON_FLAGS_USE_THUMBNAILS | CAJA_FILE_ICON_FLAGS_IGNORE_VISITING); + } else { + new_icon = caja_file_get_icon (file, CAJA_ICON_SIZE_STANDARD, CAJA_FILE_ICON_FLAGS_USE_THUMBNAILS | CAJA_FILE_ICON_FLAGS_IGNORE_VISITING); + if (!new_icon || new_icon != icon) { + g_object_unref (icon); + g_object_unref (new_icon); + icon = NULL; + break; + } + g_object_unref (new_icon); + } + } + + if (!icon) { + icon = caja_icon_info_lookup_from_name ("text-x-generic", CAJA_ICON_SIZE_STANDARD); + } + + if (icon_name != NULL) { + *icon_name = g_strdup (caja_icon_info_get_used_name (icon)); + } + + if (icon_pixbuf != NULL) { + *icon_pixbuf = caja_icon_info_get_pixbuf_at_size (icon, CAJA_ICON_SIZE_STANDARD); + } + + g_object_unref (icon); +} + + +static void +update_properties_window_icon (GtkImage *image) +{ + FMPropertiesWindow *window; + GdkPixbuf *pixbuf; + char *name; + + window = g_object_get_data (G_OBJECT (image), "properties_window"); + + get_image_for_properties_window (window, &name, &pixbuf); + + if (name != NULL) { + gtk_window_set_icon_name (GTK_WINDOW (window), name); + } else { + gtk_window_set_icon (GTK_WINDOW (window), pixbuf); + } + + gtk_image_set_from_pixbuf (image, pixbuf); + + g_free (name); + g_object_unref (pixbuf); +} + +/* utility to test if a uri refers to a local image */ +static gboolean +uri_is_local_image (const char *uri) +{ + GdkPixbuf *pixbuf; + char *image_path; + + image_path = g_filename_from_uri (uri, NULL, NULL); + if (image_path == NULL) { + return FALSE; + } + + pixbuf = gdk_pixbuf_new_from_file (image_path, NULL); + g_free (image_path); + + if (pixbuf == NULL) { + return FALSE; + } + g_object_unref (pixbuf); + return TRUE; +} + + +static void +reset_icon (FMPropertiesWindow *properties_window) +{ + GList *l; + + for (l = properties_window->details->original_files; l != NULL; l = l->next) { + CajaFile *file; + + file = CAJA_FILE (l->data); + + caja_file_set_metadata (file, + CAJA_METADATA_KEY_ICON_SCALE, + NULL, NULL); + caja_file_set_metadata (file, + CAJA_METADATA_KEY_CUSTOM_ICON, + NULL, NULL); + } +} + + +static void +fm_properties_window_drag_data_received (GtkWidget *widget, GdkDragContext *context, + int x, int y, + GtkSelectionData *selection_data, + guint info, guint time) +{ + char **uris; + gboolean exactly_one; + GtkImage *image; + GtkWindow *window; + + image = GTK_IMAGE (widget); + window = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (image))); + + if (info == TARGET_RESET_BACKGROUND) { + reset_icon (FM_PROPERTIES_WINDOW (window)); + + return; + } + + uris = g_strsplit (gtk_selection_data_get_data (selection_data), "\r\n", 0); + exactly_one = uris[0] != NULL && (uris[1] == NULL || uris[1][0] == '\0'); + + + if (!exactly_one) { + eel_show_error_dialog + (_("You cannot assign more than one custom icon at a time!"), + _("Please drag just one image to set a custom icon."), + window); + } else { + if (uri_is_local_image (uris[0])) { + set_icon (uris[0], FM_PROPERTIES_WINDOW (window)); + } else { + GFile *f; + + f = g_file_new_for_uri (uris[0]); + if (!g_file_is_native (f)) { + eel_show_error_dialog + (_("The file that you dropped is not local."), + _("You can only use local images as custom icons."), + window); + + } else { + eel_show_error_dialog + (_("The file that you dropped is not an image."), + _("You can only use local images as custom icons."), + window); + } + g_object_unref (f); + } + } + g_strfreev (uris); +} + +static GtkWidget * +create_image_widget (FMPropertiesWindow *window, + gboolean is_customizable) +{ + GtkWidget *button; + GtkWidget *image; + GdkPixbuf *pixbuf; + + get_image_for_properties_window (window, NULL, &pixbuf); + + image = gtk_image_new (); + gtk_widget_show (image); + + button = NULL; + if (is_customizable) { + button = gtk_button_new (); + gtk_container_add (GTK_CONTAINER (button), image); + + /* prepare the image to receive dropped objects to assign custom images */ + gtk_drag_dest_set (GTK_WIDGET (image), + GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP, + target_table, G_N_ELEMENTS (target_table), + GDK_ACTION_COPY | GDK_ACTION_MOVE); + + g_signal_connect (image, "drag_data_received", + G_CALLBACK (fm_properties_window_drag_data_received), NULL); + g_signal_connect (button, "clicked", + G_CALLBACK (select_image_button_callback), window); + } + + gtk_image_set_from_pixbuf (GTK_IMAGE (image), pixbuf); + + g_object_unref (pixbuf); + + g_object_set_data (G_OBJECT (image), "properties_window", window); + + window->details->icon_image = image; + window->details->icon_button = button; + + return button != NULL ? button : image; +} + +static void +set_name_field (FMPropertiesWindow *window, const gchar *original_name, + const gchar *name) +{ + gboolean new_widget; + gboolean use_label; + + /* There are four cases here: + * 1) Changing the text of a label + * 2) Changing the text of an entry + * 3) Creating label (potentially replacing entry) + * 4) Creating entry (potentially replacing label) + */ + use_label = is_multi_file_window (window) || !caja_file_can_rename (get_original_file (window)); + new_widget = !window->details->name_field || (use_label ? CAJA_IS_ENTRY (window->details->name_field) : GTK_IS_LABEL (window->details->name_field)); + + if (new_widget) { + if (window->details->name_field) { + gtk_widget_destroy (window->details->name_field); + } + + if (use_label) { + window->details->name_field = + GTK_WIDGET (attach_ellipsizing_value_label + (window->details->basic_table, + window->details->name_row, + VALUE_COLUMN, name)); + } else { + window->details->name_field = caja_entry_new (); + gtk_entry_set_text (GTK_ENTRY (window->details->name_field), name); + gtk_widget_show (window->details->name_field); + gtk_table_attach (window->details->basic_table, + window->details->name_field, + VALUE_COLUMN, + VALUE_COLUMN + 1, + window->details->name_row, + window->details->name_row + 1, + GTK_FILL, 0, + 0, 0); + gtk_label_set_mnemonic_widget (GTK_LABEL (window->details->name_label), window->details->name_field); + + /* FIXME bugzilla.gnome.org 42151: + * With this (and one place elsewhere in this file, not sure which is the + * trouble-causer) code in place, bug 2151 happens (crash on quit). Since + * we've removed Undo from Caja for now, I'm just ifdeffing out this + * code rather than trying to fix 2151 now. Note that it might be possible + * to fix 2151 without making Undo actually work, it's just not worth the + * trouble. + */ +#ifdef UNDO_ENABLED + /* Set up name field for undo */ + caja_undo_set_up_caja_entry_for_undo ( CAJA_ENTRY (window->details->name_field)); + caja_undo_editable_set_undo_key (GTK_EDITABLE (window->details->name_field), TRUE); +#endif + + g_signal_connect_object (window->details->name_field, "focus_out_event", + G_CALLBACK (name_field_focus_out), window, 0); + g_signal_connect_object (window->details->name_field, "activate", + G_CALLBACK (name_field_activate), window, 0); + } + + gtk_widget_show (window->details->name_field); + } + /* Only replace text if the file's name has changed. */ + else if (original_name == NULL || strcmp (original_name, name) != 0) { + + if (use_label) { + gtk_label_set_text (GTK_LABEL (window->details->name_field), name); + } else { + /* Only reset the text if it's different from what is + * currently showing. This causes minimal ripples (e.g. + * selection change). + */ + gchar *displayed_name = gtk_editable_get_chars (GTK_EDITABLE (window->details->name_field), 0, -1); + if (strcmp (displayed_name, name) != 0) { + gtk_entry_set_text (GTK_ENTRY (window->details->name_field), name); + } + g_free (displayed_name); + } + } +} + +static void +update_name_field (FMPropertiesWindow *window) +{ + CajaFile *file; + + gtk_label_set_text_with_mnemonic (window->details->name_label, + ngettext ("_Name:", "_Names:", + get_not_gone_original_file_count (window))); + + if (is_multi_file_window (window)) { + /* Multifile property dialog, show all names */ + GString *str; + char *name; + gboolean first; + GList *l; + + str = g_string_new (""); + + first = TRUE; + + for (l = window->details->target_files; l != NULL; l = l->next) { + file = CAJA_FILE (l->data); + + if (!caja_file_is_gone (file)) { + if (!first) { + g_string_append (str, ", "); + } + first = FALSE; + + name = caja_file_get_display_name (file); + g_string_append (str, name); + g_free (name); + } + } + set_name_field (window, NULL, str->str); + g_string_free (str, TRUE); + } else { + const char *original_name = NULL; + char *current_name; + + file = get_original_file (window); + + if (file == NULL || caja_file_is_gone (file)) { + current_name = g_strdup (""); + } else { + current_name = caja_file_get_display_name (file); + } + + /* If the file name has changed since the original name was stored, + * update the text in the text field, possibly (deliberately) clobbering + * an edit in progress. If the name hasn't changed (but some other + * aspect of the file might have), then don't clobber changes. + */ + if (window->details->name_field) { + original_name = (const char *) g_object_get_data (G_OBJECT (window->details->name_field), "original_name"); + } + + set_name_field (window, original_name, current_name); + + if (original_name == NULL || + eel_strcmp (original_name, current_name) != 0) { + g_object_set_data_full (G_OBJECT (window->details->name_field), + "original_name", + current_name, + g_free); + } else { + g_free (current_name); + } + } +} + +static void +name_field_restore_original_name (CajaEntry *name_field) +{ + const char *original_name; + char *displayed_name; + + original_name = (const char *) g_object_get_data (G_OBJECT (name_field), + "original_name"); + + if (!original_name) { + return; + } + + displayed_name = gtk_editable_get_chars (GTK_EDITABLE (name_field), 0, -1); + + if (strcmp (original_name, displayed_name) != 0) { + gtk_entry_set_text (GTK_ENTRY (name_field), original_name); + } + caja_entry_select_all (name_field); + + g_free (displayed_name); +} + +static void +rename_callback (CajaFile *file, GFile *res_loc, GError *error, gpointer callback_data) +{ + FMPropertiesWindow *window; + char *new_name; + + window = FM_PROPERTIES_WINDOW (callback_data); + + /* Complain to user if rename failed. */ + if (error != NULL) { + new_name = window->details->pending_name; + fm_report_error_renaming_file (file, + window->details->pending_name, + error, + GTK_WINDOW (window)); + if (window->details->name_field != NULL) { + name_field_restore_original_name (CAJA_ENTRY (window->details->name_field)); + } + } + + g_object_unref (window); +} + +static void +set_pending_name (FMPropertiesWindow *window, const char *name) +{ + g_free (window->details->pending_name); + window->details->pending_name = g_strdup (name); +} + +static void +name_field_done_editing (CajaEntry *name_field, FMPropertiesWindow *window) +{ + CajaFile *file; + char *new_name; + const char *original_name; + + g_return_if_fail (CAJA_IS_ENTRY (name_field)); + + /* Don't apply if the dialog has more than one file */ + if (is_multi_file_window (window)) { + return; + } + + file = get_original_file (window); + + /* This gets called when the window is closed, which might be + * caused by the file having been deleted. + */ + if (file == NULL || caja_file_is_gone (file)) { + return; + } + + new_name = gtk_editable_get_chars (GTK_EDITABLE (name_field), 0, -1); + + /* Special case: silently revert text if new text is empty. */ + if (strlen (new_name) == 0) { + name_field_restore_original_name (CAJA_ENTRY (name_field)); + } else { + original_name = (const char *) g_object_get_data (G_OBJECT (window->details->name_field), + "original_name"); + /* Don't rename if not changed since we read the display name. + This is needed so that we don't save the display name to the + file when nothing is changed */ + if (strcmp (new_name, original_name) != 0) { + set_pending_name (window, new_name); + g_object_ref (window); + caja_file_rename (file, new_name, + rename_callback, window); + } + } + + g_free (new_name); +} + +static gboolean +name_field_focus_out (CajaEntry *name_field, + GdkEventFocus *event, + gpointer callback_data) +{ + g_assert (FM_IS_PROPERTIES_WINDOW (callback_data)); + + if (gtk_widget_get_sensitive (GTK_WIDGET (name_field))) { + name_field_done_editing (name_field, FM_PROPERTIES_WINDOW (callback_data)); + } + + return FALSE; +} + +static void +name_field_activate (CajaEntry *name_field, gpointer callback_data) +{ + g_assert (CAJA_IS_ENTRY (name_field)); + g_assert (FM_IS_PROPERTIES_WINDOW (callback_data)); + + /* Accept changes. */ + name_field_done_editing (name_field, FM_PROPERTIES_WINDOW (callback_data)); + + caja_entry_select_all_at_idle (name_field); +} + +static gboolean +file_has_keyword (CajaFile *file, const char *keyword) +{ + GList *keywords, *word; + + keywords = caja_file_get_keywords (file); + word = g_list_find_custom (keywords, keyword, (GCompareFunc) strcmp); + eel_g_list_free_deep (keywords); + + return (word != NULL); +} + +static void +get_initial_emblem_state (FMPropertiesWindow *window, + const char *name, + GList **on, + GList **off) +{ + GList *l; + + *on = NULL; + *off = NULL; + + for (l = window->details->original_files; l != NULL; l = l->next) { + GList *initial_emblems; + + initial_emblems = g_hash_table_lookup (window->details->initial_emblems, + l->data); + + if (g_list_find_custom (initial_emblems, name, (GCompareFunc) strcmp)) { + *on = g_list_prepend (*on, l->data); + } else { + *off = g_list_prepend (*off, l->data); + } + } +} + +static void +emblem_button_toggled (GtkToggleButton *button, + FMPropertiesWindow *window) +{ + GList *l; + GList *keywords; + GList *word; + char *name; + GList *files_on; + GList *files_off; + + name = g_object_get_data (G_OBJECT (button), "caja_emblem_name"); + + files_on = NULL; + files_off = NULL; + if (gtk_toggle_button_get_active (button) + && !gtk_toggle_button_get_inconsistent (button)) { + /* Go to the initial state unless the initial state was + consistent */ + get_initial_emblem_state (window, name, + &files_on, &files_off); + + if (!(files_on && files_off)) { + g_list_free (files_on); + g_list_free (files_off); + files_on = g_list_copy (window->details->original_files); + files_off = NULL; + } + } else if (gtk_toggle_button_get_inconsistent (button) + && !gtk_toggle_button_get_active (button)) { + files_on = g_list_copy (window->details->original_files); + files_off = NULL; + } else { + files_off = g_list_copy (window->details->original_files); + files_on = NULL; + } + + g_signal_handlers_block_by_func (G_OBJECT (button), + G_CALLBACK (emblem_button_toggled), + window); + + gtk_toggle_button_set_active (button, files_on != NULL); + gtk_toggle_button_set_inconsistent (button, files_on && files_off); + + g_signal_handlers_unblock_by_func (G_OBJECT (button), + G_CALLBACK (emblem_button_toggled), + window); + + for (l = files_on; l != NULL; l = l->next) { + CajaFile *file; + + file = CAJA_FILE (l->data); + + keywords = caja_file_get_keywords (file); + + word = g_list_find_custom (keywords, name, (GCompareFunc)strcmp); + if (!word) { + keywords = g_list_prepend (keywords, g_strdup (name)); + } + caja_file_set_keywords (file, keywords); + eel_g_list_free_deep (keywords); + } + + for (l = files_off; l != NULL; l = l->next) { + CajaFile *file; + + file = CAJA_FILE (l->data); + + keywords = caja_file_get_keywords (file); + + word = g_list_find_custom (keywords, name, (GCompareFunc)strcmp); + if (word) { + keywords = g_list_remove_link (keywords, word); + eel_g_list_free_deep (word); + } + caja_file_set_keywords (file, keywords); + eel_g_list_free_deep (keywords); + } + + g_list_free (files_on); + g_list_free (files_off); +} + +static void +emblem_button_update (FMPropertiesWindow *window, + GtkToggleButton *button) +{ + GList *l; + char *name; + gboolean all_set; + gboolean all_unset; + + name = g_object_get_data (G_OBJECT (button), "caja_emblem_name"); + + all_set = TRUE; + all_unset = TRUE; + for (l = window->details->original_files; l != NULL; l = l->next) { + gboolean has_keyword; + CajaFile *file; + + file = CAJA_FILE (l->data); + + has_keyword = file_has_keyword (file, name); + + if (has_keyword) { + all_unset = FALSE; + } else { + all_set = FALSE; + } + } + + g_signal_handlers_block_by_func (G_OBJECT (button), + G_CALLBACK (emblem_button_toggled), + window); + + gtk_toggle_button_set_active (button, !all_unset); + gtk_toggle_button_set_inconsistent (button, !all_unset && !all_set); + + g_signal_handlers_unblock_by_func (G_OBJECT (button), + G_CALLBACK (emblem_button_toggled), + window); + +} + +static void +update_properties_window_title (FMPropertiesWindow *window) +{ + char *name, *title; + CajaFile *file; + + g_return_if_fail (GTK_IS_WINDOW (window)); + + title = g_strdup_printf (_("Properties")); + + if (!is_multi_file_window (window)) { + file = get_original_file (window); + + if (file != NULL) { + g_free (title); + name = caja_file_get_display_name (file); + title = g_strdup_printf (_("%s Properties"), name); + g_free (name); + } + } + + gtk_window_set_title (GTK_WINDOW (window), title); + + g_free (title); +} + +static void +clear_extension_pages (FMPropertiesWindow *window) +{ + int i; + int num_pages; + GtkWidget *page; + + num_pages = gtk_notebook_get_n_pages + (GTK_NOTEBOOK (window->details->notebook)); + + for (i = 0; i < num_pages; i++) { + page = gtk_notebook_get_nth_page + (GTK_NOTEBOOK (window->details->notebook), i); + + if (g_object_get_data (G_OBJECT (page), "is-extension-page")) { + gtk_notebook_remove_page + (GTK_NOTEBOOK (window->details->notebook), i); + num_pages--; + i--; + } + } +} + +static void +refresh_extension_pages (FMPropertiesWindow *window) +{ + clear_extension_pages (window); + append_extension_pages (window); +} + +static void +remove_from_dialog (FMPropertiesWindow *window, + CajaFile *file) +{ + int index; + GList *original_link; + GList *target_link; + CajaFile *original_file; + CajaFile *target_file; + + index = g_list_index (window->details->target_files, file); + if (index == -1) { + index = g_list_index (window->details->original_files, file); + g_return_if_fail (index != -1); + } + + original_link = g_list_nth (window->details->original_files, index); + target_link = g_list_nth (window->details->target_files, index); + + g_return_if_fail (original_link && target_link); + + original_file = CAJA_FILE (original_link->data); + target_file = CAJA_FILE (target_link->data); + + window->details->original_files = g_list_remove_link (window->details->original_files, original_link); + g_list_free (original_link); + + window->details->target_files = g_list_remove_link (window->details->target_files, target_link); + g_list_free (target_link); + + g_hash_table_remove (window->details->initial_emblems, original_file); + g_hash_table_remove (window->details->initial_permissions, target_file); + + g_signal_handlers_disconnect_by_func (original_file, + G_CALLBACK (file_changed_callback), + window); + g_signal_handlers_disconnect_by_func (target_file, + G_CALLBACK (file_changed_callback), + window); + + caja_file_monitor_remove (original_file, &window->details->original_files); + caja_file_monitor_remove (target_file, &window->details->target_files); + + caja_file_unref (original_file); + caja_file_unref (target_file); + +} + +static gboolean +mime_list_equal (GList *a, GList *b) +{ + while (a && b) { + if (strcmp (a->data, b->data)) { + return FALSE; + } + a = a->next; + b = b->next; + } + + return (a == b); +} + +static GList * +get_mime_list (FMPropertiesWindow *window) +{ + GList *ret; + GList *l; + + ret = NULL; + for (l = window->details->target_files; l != NULL; l = l->next) { + ret = g_list_append (ret, caja_file_get_mime_type (CAJA_FILE (l->data))); + } + ret = g_list_reverse (ret); + return ret; +} + +static void +properties_window_update (FMPropertiesWindow *window, + GList *files) +{ + GList *l; + GList *mime_list; + GList *tmp; + CajaFile *changed_file; + gboolean dirty_original = FALSE; + gboolean dirty_target = FALSE; + + if (files == NULL) { + dirty_original = TRUE; + dirty_target = TRUE; + } + + for (tmp = files; tmp != NULL; tmp = tmp->next) { + changed_file = CAJA_FILE (tmp->data); + + if (changed_file && caja_file_is_gone (changed_file)) { + /* Remove the file from the property dialog */ + remove_from_dialog (window, changed_file); + changed_file = NULL; + + if (window->details->original_files == NULL) { + return; + } + } + if (changed_file == NULL || + g_list_find (window->details->original_files, changed_file)) { + dirty_original = TRUE; + } + if (changed_file == NULL || + g_list_find (window->details->target_files, changed_file)) { + dirty_target = TRUE; + } + + } + + if (dirty_original) { + update_properties_window_title (window); + update_properties_window_icon (GTK_IMAGE (window->details->icon_image)); + + update_name_field (window); + + for (l = window->details->emblem_buttons; l != NULL; l = l->next) { + emblem_button_update (window, GTK_TOGGLE_BUTTON (l->data)); + } + + /* If any of the value fields start to depend on the original + * value, value_field_updates should be added here */ + } + + if (dirty_target) { + for (l = window->details->permission_buttons; l != NULL; l = l->next) { + permission_button_update (window, GTK_TOGGLE_BUTTON (l->data)); + } + + for (l = window->details->permission_combos; l != NULL; l = l->next) { + permission_combo_update (window, GTK_COMBO_BOX (l->data)); + } + + for (l = window->details->value_fields; l != NULL; l = l->next) { + value_field_update (window, GTK_LABEL (l->data)); + } + } + + mime_list = get_mime_list (window); + + if (!window->details->mime_list) { + window->details->mime_list = mime_list; + } else { + if (!mime_list_equal (window->details->mime_list, mime_list)) { + refresh_extension_pages (window); + } + + eel_g_list_free_deep (window->details->mime_list); + window->details->mime_list = mime_list; + } +} + +static gboolean +update_files_callback (gpointer data) +{ + FMPropertiesWindow *window; + + window = FM_PROPERTIES_WINDOW (data); + + window->details->update_files_timeout_id = 0; + + properties_window_update (window, window->details->changed_files); + + if (window->details->original_files == NULL) { + /* Close the window if no files are left */ + gtk_widget_destroy (GTK_WIDGET (window)); + } else { + caja_file_list_free (window->details->changed_files); + window->details->changed_files = NULL; + } + + return FALSE; + } + +static void +schedule_files_update (FMPropertiesWindow *window) + { + g_assert (FM_IS_PROPERTIES_WINDOW (window)); + + if (window->details->update_files_timeout_id == 0) { + window->details->update_files_timeout_id + = g_timeout_add (FILES_UPDATE_INTERVAL, + update_files_callback, + window); + } + } + +static gboolean +file_list_attributes_identical (GList *file_list, const char *attribute_name) +{ + gboolean identical; + char *first_attr; + GList *l; + + first_attr = NULL; + identical = TRUE; + + for (l = file_list; l != NULL; l = l->next) { + CajaFile *file; + + file = CAJA_FILE (l->data); + + if (caja_file_is_gone (file)) { + continue; + } + + if (first_attr == NULL) { + first_attr = caja_file_get_string_attribute_with_default (file, attribute_name); + } else { + char *attr; + attr = caja_file_get_string_attribute_with_default (file, attribute_name); + if (strcmp (attr, first_attr)) { + identical = FALSE; + g_free (attr); + break; + } + g_free (attr); + } + } + + g_free (first_attr); + return identical; +} + +static char * +file_list_get_string_attribute (GList *file_list, + const char *attribute_name, + const char *inconsistent_value) +{ + if (file_list_attributes_identical (file_list, attribute_name)) { + GList *l; + + for (l = file_list; l != NULL; l = l->next) { + CajaFile *file; + + file = CAJA_FILE (l->data); + if (!caja_file_is_gone (file)) { + return caja_file_get_string_attribute_with_default + (file, + attribute_name); + } + } + return g_strdup (_("unknown")); + } else { + return g_strdup (inconsistent_value); + } +} + + +static gboolean +file_list_all_directories (GList *file_list) +{ + GList *l; + for (l = file_list; l != NULL; l = l->next) { + if (!caja_file_is_directory (CAJA_FILE (l->data))) { + return FALSE; + } + } + return TRUE; +} + +static void +value_field_update_internal (GtkLabel *label, + GList *file_list) +{ + const char *attribute_name; + char *attribute_value; + char *inconsistent_string; + char *mime_type, *tmp; + + g_assert (GTK_IS_LABEL (label)); + + attribute_name = g_object_get_data (G_OBJECT (label), "file_attribute"); + inconsistent_string = g_object_get_data (G_OBJECT (label), "inconsistent_string"); + attribute_value = file_list_get_string_attribute (file_list, + attribute_name, + inconsistent_string); + if (!strcmp (attribute_name, "type") && strcmp (attribute_value, inconsistent_string)) { + mime_type = file_list_get_string_attribute (file_list, + "mime_type", + inconsistent_string); + if (strcmp (mime_type, inconsistent_string)) { + tmp = attribute_value; + attribute_value = g_strdup_printf (C_("MIME type description (MIME type)", "%s (%s)"), attribute_value, mime_type); + g_free (tmp); + } + g_free (mime_type); + } + + gtk_label_set_text (label, attribute_value); + g_free (attribute_value); +} + +static void +value_field_update (FMPropertiesWindow *window, GtkLabel *label) +{ + gboolean use_original; + + use_original = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (label), "show_original")); + + value_field_update_internal (label, + (use_original ? + window->details->original_files : + window->details->target_files)); +} + +static GtkLabel * +attach_label (GtkTable *table, + int row, + int column, + const char *initial_text, + gboolean right_aligned, + gboolean bold, + gboolean ellipsize_text, + gboolean selectable, + gboolean mnemonic) +{ + GtkWidget *label_field; + + if (ellipsize_text) { + label_field = gtk_label_new (initial_text); + gtk_label_set_ellipsize (GTK_LABEL (label_field), + right_aligned ? PANGO_ELLIPSIZE_START : + PANGO_ELLIPSIZE_END); + } else if (mnemonic) { + label_field = gtk_label_new_with_mnemonic (initial_text); + } else { + label_field = gtk_label_new (initial_text); + } + + if (selectable) { + gtk_label_set_selectable (GTK_LABEL (label_field), TRUE); + } + + if (bold) { + eel_gtk_label_make_bold (GTK_LABEL (label_field)); + } + gtk_misc_set_alignment (GTK_MISC (label_field), right_aligned ? 1 : 0, 0.5); + gtk_widget_show (label_field); + gtk_table_attach (table, label_field, + column, column + 1, + row, row + 1, + ellipsize_text + ? GTK_FILL | GTK_EXPAND + : GTK_FILL, + 0, + 0, 0); + + return GTK_LABEL (label_field); +} + +static GtkLabel * +attach_value_label (GtkTable *table, + int row, + int column, + const char *initial_text) +{ + return attach_label (table, row, column, initial_text, FALSE, FALSE, FALSE, TRUE, FALSE); +} + +static GtkLabel * +attach_ellipsizing_value_label (GtkTable *table, + int row, + int column, + const char *initial_text) +{ + return attach_label (table, row, column, initial_text, FALSE, FALSE, TRUE, TRUE, FALSE); +} + +static GtkWidget* +attach_value_field_internal (FMPropertiesWindow *window, + GtkTable *table, + int row, + int column, + const char *file_attribute_name, + const char *inconsistent_string, + gboolean show_original, + gboolean ellipsize_text) +{ + GtkLabel *value_field; + + if (ellipsize_text) { + value_field = attach_ellipsizing_value_label (table, row, column, ""); + } else { + value_field = attach_value_label (table, row, column, ""); + } + + /* Stash a copy of the file attribute name in this field for the callback's sake. */ + g_object_set_data_full (G_OBJECT (value_field), "file_attribute", + g_strdup (file_attribute_name), g_free); + + g_object_set_data_full (G_OBJECT (value_field), "inconsistent_string", + g_strdup (inconsistent_string), g_free); + + g_object_set_data (G_OBJECT (value_field), "show_original", GINT_TO_POINTER (show_original)); + + window->details->value_fields = g_list_prepend (window->details->value_fields, + value_field); + return GTK_WIDGET(value_field); +} + +static GtkWidget* +attach_value_field (FMPropertiesWindow *window, + GtkTable *table, + int row, + int column, + const char *file_attribute_name, + const char *inconsistent_string, + gboolean show_original) +{ + return attach_value_field_internal (window, + table, row, column, + file_attribute_name, + inconsistent_string, + show_original, + FALSE); +} + +static GtkWidget* +attach_ellipsizing_value_field (FMPropertiesWindow *window, + GtkTable *table, + int row, + int column, + const char *file_attribute_name, + const char *inconsistent_string, + gboolean show_original) +{ + return attach_value_field_internal (window, + table, row, column, + file_attribute_name, + inconsistent_string, + show_original, + TRUE); +} + +static void +group_change_callback (CajaFile *file, + GFile *res_loc, + GError *error, + FMPropertiesWindow *window) +{ + char *group; + + g_assert (FM_IS_PROPERTIES_WINDOW (window)); + g_assert (window->details->group_change_file == file); + + group = window->details->group_change_group; + g_assert (group != NULL); + + /* Report the error if it's an error. */ + eel_timed_wait_stop ((EelCancelCallback) cancel_group_change_callback, window); + fm_report_error_setting_group (file, error, GTK_WINDOW (window)); + + caja_file_unref (file); + g_free (group); + + window->details->group_change_file = NULL; + window->details->group_change_group = NULL; + g_object_unref (G_OBJECT (window)); +} + +static void +cancel_group_change_callback (FMPropertiesWindow *window) +{ + CajaFile *file; + char *group; + + file = window->details->group_change_file; + g_assert (CAJA_IS_FILE (file)); + + group = window->details->group_change_group; + g_assert (group != NULL); + + caja_file_cancel (file, (CajaFileOperationCallback) group_change_callback, window); + + g_free (group); + caja_file_unref (file); + + window->details->group_change_file = NULL; + window->details->group_change_group = NULL; + g_object_unref (window); +} + +static gboolean +schedule_group_change_timeout (FMPropertiesWindow *window) +{ + CajaFile *file; + char *group; + + g_assert (FM_IS_PROPERTIES_WINDOW (window)); + + file = window->details->group_change_file; + g_assert (CAJA_IS_FILE (file)); + + group = window->details->group_change_group; + g_assert (group != NULL); + + eel_timed_wait_start + ((EelCancelCallback) cancel_group_change_callback, + window, + _("Cancel Group Change?"), + GTK_WINDOW (window)); + + caja_file_set_group + (file, group, + (CajaFileOperationCallback) group_change_callback, window); + + window->details->group_change_timeout = 0; + return FALSE; +} + +static void +schedule_group_change (FMPropertiesWindow *window, + CajaFile *file, + const char *group) +{ + g_assert (FM_IS_PROPERTIES_WINDOW (window)); + g_assert (window->details->group_change_group == NULL); + g_assert (window->details->group_change_file == NULL); + g_assert (CAJA_IS_FILE (file)); + + window->details->group_change_file = caja_file_ref (file); + window->details->group_change_group = g_strdup (group); + g_object_ref (G_OBJECT (window)); + window->details->group_change_timeout = + g_timeout_add (CHOWN_CHGRP_TIMEOUT, + (GSourceFunc) schedule_group_change_timeout, + window); +} + +static void +unschedule_or_cancel_group_change (FMPropertiesWindow *window) +{ + CajaFile *file; + char *group; + + g_assert (FM_IS_PROPERTIES_WINDOW (window)); + + file = window->details->group_change_file; + group = window->details->group_change_group; + + g_assert ((file == NULL && group == NULL) || + (file != NULL && group != NULL)); + + if (file != NULL) { + g_assert (CAJA_IS_FILE (file)); + + if (window->details->group_change_timeout == 0) { + caja_file_cancel (file, + (CajaFileOperationCallback) group_change_callback, window); + eel_timed_wait_stop ((EelCancelCallback) cancel_group_change_callback, window); + } + + caja_file_unref (file); + g_free (group); + + window->details->group_change_file = NULL; + window->details->group_change_group = NULL; + g_object_unref (G_OBJECT (window)); + } + + if (window->details->group_change_timeout > 0) { + g_assert (file != NULL); + g_source_remove (window->details->group_change_timeout); + window->details->group_change_timeout = 0; + } +} + +static void +changed_group_callback (GtkComboBox *combo_box, CajaFile *file) +{ + FMPropertiesWindow *window; + char *group; + char *cur_group; + + g_assert (GTK_IS_COMBO_BOX (combo_box)); + g_assert (CAJA_IS_FILE (file)); + + group = gtk_combo_box_get_active_text (combo_box); + cur_group = caja_file_get_group_name (file); + + if (group != NULL && strcmp (group, cur_group) != 0) { + /* Try to change file group. If this fails, complain to user. */ + window = FM_PROPERTIES_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (combo_box), GTK_TYPE_WINDOW)); + + unschedule_or_cancel_group_change (window); + schedule_group_change (window, file, group); + } + g_free (group); + g_free (cur_group); +} + +/* checks whether the given column at the first level + * of model has the specified entries in the given order. */ +static gboolean +tree_model_entries_equal (GtkTreeModel *model, + unsigned int column, + GList *entries) +{ + GtkTreeIter iter; + gboolean empty_model; + + g_assert (GTK_IS_TREE_MODEL (model)); + g_assert (gtk_tree_model_get_column_type (model, column) == G_TYPE_STRING); + + empty_model = !gtk_tree_model_get_iter_first (model, &iter); + + if (!empty_model && entries != NULL) { + GList *l; + + l = entries; + + do { + char *val; + + gtk_tree_model_get (model, &iter, + column, &val, + -1); + if ((val == NULL && l->data != NULL) || + (val != NULL && l->data == NULL) || + (val != NULL && strcmp (val, l->data))) { + g_free (val); + return FALSE; + } + + g_free (val); + l = l->next; + } while (gtk_tree_model_iter_next (model, &iter)); + + return l == NULL; + } else { + return (empty_model && entries == NULL) || + (!empty_model && entries != NULL); + } +} + +static char * +combo_box_get_active_entry (GtkComboBox *combo_box, + unsigned int column) +{ + GtkTreeModel *model; + GtkTreeIter iter; + char *val; + + g_assert (GTK_IS_COMBO_BOX (combo_box)); + + if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo_box), &iter)) { + model = gtk_combo_box_get_model (combo_box); + g_assert (GTK_IS_TREE_MODEL (model)); + + gtk_tree_model_get (model, &iter, + column, &val, + -1); + return val; + } + + return NULL; +} + +/* returns the index of the given entry in the the given column + * at the first level of model. Returns -1 if entry can't be found + * or entry is NULL. + * */ +static int +tree_model_get_entry_index (GtkTreeModel *model, + unsigned int column, + const char *entry) +{ + GtkTreeIter iter; + int index; + gboolean empty_model; + + g_assert (GTK_IS_TREE_MODEL (model)); + g_assert (gtk_tree_model_get_column_type (model, column) == G_TYPE_STRING); + + empty_model = !gtk_tree_model_get_iter_first (model, &iter); + if (!empty_model && entry != NULL) { + index = 0; + + do { + char *val; + + gtk_tree_model_get (model, &iter, + column, &val, + -1); + if (val != NULL && !strcmp (val, entry)) { + g_free (val); + return index; + } + + g_free (val); + index++; + } while (gtk_tree_model_iter_next (model, &iter)); + } + + return -1; +} + + +static void +synch_groups_combo_box (GtkComboBox *combo_box, CajaFile *file) +{ + GList *groups; + GList *node; + GtkTreeModel *model; + GtkListStore *store; + const char *group_name; + char *current_group_name; + int group_index; + int current_group_index; + + g_assert (GTK_IS_COMBO_BOX (combo_box)); + g_assert (CAJA_IS_FILE (file)); + + if (caja_file_is_gone (file)) { + return; + } + + groups = caja_file_get_settable_group_names (file); + + model = gtk_combo_box_get_model (combo_box); + store = GTK_LIST_STORE (model); + g_assert (GTK_IS_LIST_STORE (model)); + + if (!tree_model_entries_equal (model, 0, groups)) { + /* Clear the contents of ComboBox in a wacky way because there + * is no function to clear all items and also no function to obtain + * the number of items in a combobox. + */ + gtk_list_store_clear (store); + + for (node = groups, group_index = 0; node != NULL; node = node->next, ++group_index) { + group_name = (const char *)node->data; + gtk_combo_box_append_text (combo_box, group_name); + } + } + + current_group_name = caja_file_get_group_name (file); + current_group_index = tree_model_get_entry_index (model, 0, current_group_name); + + /* If current group wasn't in list, we prepend it (with a separator). + * This can happen if the current group is an id with no matching + * group in the groups file. + */ + if (current_group_index < 0 && current_group_name != NULL) { + if (groups != NULL) { + /* add separator */ + gtk_combo_box_prepend_text (combo_box, "-"); + } + + gtk_combo_box_prepend_text (combo_box, current_group_name); + current_group_index = 0; + } + gtk_combo_box_set_active (combo_box, current_group_index); + + g_free (current_group_name); + eel_g_list_free_deep (groups); +} + +static gboolean +combo_box_row_separator_func (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + gchar *text; + gboolean ret; + + gtk_tree_model_get (model, iter, 0, &text, -1); + + if (text == NULL) { + return FALSE; + } + + if (strcmp (text, "-") == 0) { + ret = TRUE; + } else { + ret = FALSE; + } + + g_free (text); + return ret; +} + +static GtkComboBox * +attach_combo_box (GtkTable *table, + int row, + gboolean two_columns) +{ + GtkWidget *combo_box; + GtkWidget *aligner; + + if (!two_columns) { + combo_box = gtk_combo_box_new_text (); + } else { + GtkTreeModel *model; + GtkCellRenderer *renderer; + + model = GTK_TREE_MODEL (gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING)); + combo_box = gtk_combo_box_new_with_model (model); + g_object_unref (G_OBJECT (model)); + + renderer = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box), renderer, TRUE); + gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (combo_box), renderer, + "text", 0); + + } + gtk_widget_show (combo_box); + + gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (combo_box), + combo_box_row_separator_func, + NULL, + NULL); + + /* Put combo box in alignment to make it left-justified + * but minimally sized. + */ + aligner = gtk_alignment_new (0, 0.5, 0, 0); + gtk_widget_show (aligner); + + gtk_container_add (GTK_CONTAINER (aligner), combo_box); + gtk_table_attach (table, aligner, + VALUE_COLUMN, VALUE_COLUMN + 1, + row, row + 1, + GTK_FILL, 0, + 0, 0); + + return GTK_COMBO_BOX (combo_box); +} + +static GtkComboBox* +attach_group_combo_box (GtkTable *table, + int row, + CajaFile *file) +{ + GtkComboBox *combo_box; + + combo_box = attach_combo_box (table, row, FALSE); + + synch_groups_combo_box (combo_box, file); + + /* Connect to signal to update menu when file changes. */ + g_signal_connect_object (file, "changed", + G_CALLBACK (synch_groups_combo_box), + combo_box, G_CONNECT_SWAPPED); + g_signal_connect_data (combo_box, "changed", + G_CALLBACK (changed_group_callback), + caja_file_ref (file), + (GClosureNotify)caja_file_unref, 0); + + return combo_box; +} + +static void +owner_change_callback (CajaFile *file, + GFile *result_location, + GError *error, + FMPropertiesWindow *window) +{ + char *owner; + + g_assert (FM_IS_PROPERTIES_WINDOW (window)); + g_assert (window->details->owner_change_file == file); + + owner = window->details->owner_change_owner; + g_assert (owner != NULL); + + /* Report the error if it's an error. */ + eel_timed_wait_stop ((EelCancelCallback) cancel_owner_change_callback, window); + fm_report_error_setting_owner (file, error, GTK_WINDOW (window)); + + caja_file_unref (file); + g_free (owner); + + window->details->owner_change_file = NULL; + window->details->owner_change_owner = NULL; + g_object_unref (G_OBJECT (window)); +} + +static void +cancel_owner_change_callback (FMPropertiesWindow *window) +{ + CajaFile *file; + char *owner; + + file = window->details->owner_change_file; + g_assert (CAJA_IS_FILE (file)); + + owner = window->details->owner_change_owner; + g_assert (owner != NULL); + + caja_file_cancel (file, (CajaFileOperationCallback) owner_change_callback, window); + + caja_file_unref (file); + g_free (owner); + + window->details->owner_change_file = NULL; + window->details->owner_change_owner = NULL; + g_object_unref (window); +} + +static gboolean +schedule_owner_change_timeout (FMPropertiesWindow *window) +{ + CajaFile *file; + char *owner; + + g_assert (FM_IS_PROPERTIES_WINDOW (window)); + + file = window->details->owner_change_file; + g_assert (CAJA_IS_FILE (file)); + + owner = window->details->owner_change_owner; + g_assert (owner != NULL); + + eel_timed_wait_start + ((EelCancelCallback) cancel_owner_change_callback, + window, + _("Cancel Owner Change?"), + GTK_WINDOW (window)); + + caja_file_set_owner + (file, owner, + (CajaFileOperationCallback) owner_change_callback, window); + + window->details->owner_change_timeout = 0; + return FALSE; +} + +static void +schedule_owner_change (FMPropertiesWindow *window, + CajaFile *file, + const char *owner) +{ + g_assert (FM_IS_PROPERTIES_WINDOW (window)); + g_assert (window->details->owner_change_owner == NULL); + g_assert (window->details->owner_change_file == NULL); + g_assert (CAJA_IS_FILE (file)); + + window->details->owner_change_file = caja_file_ref (file); + window->details->owner_change_owner = g_strdup (owner); + g_object_ref (G_OBJECT (window)); + window->details->owner_change_timeout = + g_timeout_add (CHOWN_CHGRP_TIMEOUT, + (GSourceFunc) schedule_owner_change_timeout, + window); +} + +static void +unschedule_or_cancel_owner_change (FMPropertiesWindow *window) +{ + CajaFile *file; + char *owner; + + g_assert (FM_IS_PROPERTIES_WINDOW (window)); + + file = window->details->owner_change_file; + owner = window->details->owner_change_owner; + + g_assert ((file == NULL && owner == NULL) || + (file != NULL && owner != NULL)); + + if (file != NULL) { + g_assert (CAJA_IS_FILE (file)); + + if (window->details->owner_change_timeout == 0) { + caja_file_cancel (file, + (CajaFileOperationCallback) owner_change_callback, window); + eel_timed_wait_stop ((EelCancelCallback) cancel_owner_change_callback, window); + } + + caja_file_unref (file); + g_free (owner); + + window->details->owner_change_file = NULL; + window->details->owner_change_owner = NULL; + g_object_unref (G_OBJECT (window)); + } + + if (window->details->owner_change_timeout > 0) { + g_assert (file != NULL); + g_source_remove (window->details->owner_change_timeout); + window->details->owner_change_timeout = 0; + } +} + +static void +changed_owner_callback (GtkComboBox *combo_box, CajaFile* file) +{ + FMPropertiesWindow *window; + char *owner_text; + char **name_array; + char *new_owner; + char *cur_owner; + + g_assert (GTK_IS_COMBO_BOX (combo_box)); + g_assert (CAJA_IS_FILE (file)); + + owner_text = combo_box_get_active_entry (combo_box, 0); + if (! owner_text) + return; + name_array = g_strsplit (owner_text, " - ", 2); + new_owner = name_array[0]; + g_free (owner_text); + cur_owner = caja_file_get_owner_name (file); + + if (strcmp (new_owner, cur_owner) != 0) { + /* Try to change file owner. If this fails, complain to user. */ + window = FM_PROPERTIES_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (combo_box), GTK_TYPE_WINDOW)); + + unschedule_or_cancel_owner_change (window); + schedule_owner_change (window, file, new_owner); + } + g_strfreev (name_array); + g_free (cur_owner); +} + +static void +synch_user_menu (GtkComboBox *combo_box, CajaFile *file) +{ + GList *users; + GList *node; + GtkTreeModel *model; + GtkListStore *store; + GtkTreeIter iter; + char *user_name; + char *owner_name; + int user_index; + int owner_index; + char **name_array; + char *combo_text; + + g_assert (GTK_IS_COMBO_BOX (combo_box)); + g_assert (CAJA_IS_FILE (file)); + + if (caja_file_is_gone (file)) { + return; + } + + users = caja_get_user_names (); + + model = gtk_combo_box_get_model (combo_box); + store = GTK_LIST_STORE (model); + g_assert (GTK_IS_LIST_STORE (model)); + + if (!tree_model_entries_equal (model, 1, users)) { + /* Clear the contents of ComboBox in a wacky way because there + * is no function to clear all items and also no function to obtain + * the number of items in a combobox. + */ + gtk_list_store_clear (store); + + for (node = users, user_index = 0; node != NULL; node = node->next, ++user_index) { + user_name = (char *)node->data; + + name_array = g_strsplit (user_name, "\n", 2); + if (name_array[1] != NULL) { + combo_text = g_strdup_printf ("%s - %s", name_array[0], name_array[1]); + } else { + combo_text = g_strdup (name_array[0]); + } + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + 0, combo_text, + 1, user_name, + -1); + + g_strfreev (name_array); + g_free (combo_text); + } + } + + owner_name = caja_file_get_string_attribute (file, "owner"); + owner_index = tree_model_get_entry_index (model, 0, owner_name); + + /* If owner wasn't in list, we prepend it (with a separator). + * This can happen if the owner is an id with no matching + * identifier in the passwords file. + */ + if (owner_index < 0 && owner_name != NULL) { + if (users != NULL) { + /* add separator */ + gtk_list_store_prepend (store, &iter); + gtk_list_store_set (store, &iter, + 0, "-", + 1, NULL, + -1); + } + + name_array = g_strsplit (owner_name, " - ", 2); + if (name_array[1] != NULL) { + user_name = g_strdup_printf ("%s\n%s", name_array[0], name_array[1]); + } else { + user_name = g_strdup (name_array[0]); + } + owner_index = 0; + + gtk_list_store_prepend (store, &iter); + gtk_list_store_set (store, &iter, + 0, owner_name, + 1, user_name, + -1); + + g_free (user_name); + g_strfreev (name_array); + } + + gtk_combo_box_set_active (combo_box, owner_index); + + g_free (owner_name); + eel_g_list_free_deep (users); +} + +static GtkComboBox* +attach_owner_combo_box (GtkTable *table, + int row, + CajaFile *file) +{ + GtkComboBox *combo_box; + + combo_box = attach_combo_box (table, row, TRUE); + + synch_user_menu (combo_box, file); + + /* Connect to signal to update menu when file changes. */ + g_signal_connect_object (file, "changed", + G_CALLBACK (synch_user_menu), + combo_box, G_CONNECT_SWAPPED); + g_signal_connect_data (combo_box, "changed", + G_CALLBACK (changed_owner_callback), + caja_file_ref (file), + (GClosureNotify)caja_file_unref, 0); + + return combo_box; +} + +static guint +append_row (GtkTable *table) +{ + guint new_row_count; + gint nrows, ncols; + + g_object_get (table, "n-rows", &nrows, "n-columns", &ncols, NULL); + + new_row_count = nrows + 1; + + gtk_table_resize (table, new_row_count, ncols); + gtk_table_set_row_spacing (table, new_row_count - 1, ROW_PAD); + + return new_row_count - 1; +} + +static gboolean +file_has_prefix (CajaFile *file, + GList *prefix_candidates) +{ + GList *p; + GFile *location, *candidate_location; + + location = caja_file_get_location (file); + + for (p = prefix_candidates; p != NULL; p = p->next) { + if (file == p->data) { + continue; + } + + candidate_location = caja_file_get_location (CAJA_FILE (p->data)); + if (g_file_has_prefix (location, candidate_location)) { + g_object_unref (location); + g_object_unref (candidate_location); + return TRUE; + } + g_object_unref (candidate_location); + } + + g_object_unref (location); + + return FALSE; +} + +static void +directory_contents_value_field_update (FMPropertiesWindow *window) +{ + CajaRequestStatus file_status, status; + char *text, *temp; + guint directory_count; + guint file_count; + guint total_count; + guint unreadable_directory_count; + goffset total_size; + gboolean used_two_lines; + CajaFile *file; + GList *l; + guint file_unreadable; + goffset file_size; + + g_assert (FM_IS_PROPERTIES_WINDOW (window)); + + status = CAJA_REQUEST_DONE; + file_status = CAJA_REQUEST_NOT_STARTED; + total_count = window->details->total_count; + total_size = window->details->total_size; + unreadable_directory_count = FALSE; + + for (l = window->details->target_files; l; l = l->next) { + file = CAJA_FILE (l->data); + + if (file_has_prefix (file, window->details->target_files)) { + /* don't count nested files twice */ + continue; + } + + if (caja_file_is_directory (file)) { + file_status = caja_file_get_deep_counts (file, + &directory_count, + &file_count, + &file_unreadable, + &file_size, + TRUE); + total_count += (file_count + directory_count); + total_size += file_size; + + if (file_unreadable) { + unreadable_directory_count = TRUE; + } + + if (file_status != CAJA_REQUEST_DONE) { + status = file_status; + } + } else { + ++total_count; + total_size += caja_file_get_size (file); + } + } + + /* If we've already displayed the total once, don't do another visible + * count-up if the deep_count happens to get invalidated. + * But still display the new total, since it might have changed. + */ + if (window->details->deep_count_finished && + status != CAJA_REQUEST_DONE) { + return; + } + + text = NULL; + used_two_lines = FALSE; + + if (total_count == 0) { + switch (status) { + case CAJA_REQUEST_DONE: + if (unreadable_directory_count == 0) { + text = g_strdup (_("nothing")); + } else { + text = g_strdup (_("unreadable")); + } + + break; + default: + text = g_strdup ("..."); + } + } else { + char *size_str; + + #if GLIB_CHECK_VERSION(2, 30, 0) + size_str = g_format_size(total_size); + #else + size_str = g_format_size_for_display(total_size); + #endif + + text = g_strdup_printf (ngettext("%'d item, with size %s", + "%'d items, totalling %s", + total_count), + total_count, size_str); + g_free (size_str); + + if (unreadable_directory_count != 0) { + temp = text; + text = g_strconcat (temp, "\n", + _("(some contents unreadable)"), + NULL); + g_free (temp); + used_two_lines = TRUE; + } + } + + gtk_label_set_text (window->details->directory_contents_value_field, + text); + g_free (text); + + /* Also set the title field here, with a trailing carriage return & + * space if the value field has two lines. This is a hack to get the + * "Contents:" title to line up with the first line of the + * 2-line value. Maybe there's a better way to do this, but I + * couldn't think of one. + */ + text = g_strdup (_("Contents:")); + if (used_two_lines) { + temp = text; + text = g_strconcat (temp, "\n ", NULL); + g_free (temp); + } + gtk_label_set_text (window->details->directory_contents_title_field, + text); + g_free (text); + + if (status == CAJA_REQUEST_DONE) { + window->details->deep_count_finished = TRUE; + } +} + +static gboolean +update_directory_contents_callback (gpointer data) +{ + FMPropertiesWindow *window; + + window = FM_PROPERTIES_WINDOW (data); + + window->details->update_directory_contents_timeout_id = 0; + directory_contents_value_field_update (window); + + return FALSE; +} + +static void +schedule_directory_contents_update (FMPropertiesWindow *window) +{ + g_assert (FM_IS_PROPERTIES_WINDOW (window)); + + if (window->details->update_directory_contents_timeout_id == 0) { + window->details->update_directory_contents_timeout_id + = g_timeout_add (DIRECTORY_CONTENTS_UPDATE_INTERVAL, + update_directory_contents_callback, + window); + } +} + +static GtkLabel * +attach_directory_contents_value_field (FMPropertiesWindow *window, + GtkTable *table, + int row) +{ + GtkLabel *value_field; + GList *l; + CajaFile *file; + + value_field = attach_value_label (table, row, VALUE_COLUMN, ""); + + g_assert (window->details->directory_contents_value_field == NULL); + window->details->directory_contents_value_field = value_field; + + gtk_label_set_line_wrap (value_field, TRUE); + + /* Fill in the initial value. */ + directory_contents_value_field_update (window); + + for (l = window->details->target_files; l; l = l->next) { + file = CAJA_FILE (l->data); + caja_file_recompute_deep_counts (file); + + g_signal_connect_object (file, + "updated_deep_count_in_progress", + G_CALLBACK (schedule_directory_contents_update), + window, G_CONNECT_SWAPPED); + } + + return value_field; +} + +static GtkLabel * +attach_title_field (GtkTable *table, + int row, + const char *title) +{ + return attach_label (table, row, TITLE_COLUMN, title, FALSE, FALSE, FALSE, FALSE, TRUE); +} + +static guint +append_title_field (GtkTable *table, const char *title, GtkLabel **label) +{ + guint last_row; + GtkLabel *title_label; + + last_row = append_row (table); + title_label = attach_title_field (table, last_row, title); + + if (label) { + *label = title_label; + } + + return last_row; +} + +#define INCONSISTENT_STATE_STRING \ + "\xE2\x80\x92" + +static guint +append_title_value_pair (FMPropertiesWindow *window, + GtkTable *table, + const char *title, + const char *file_attribute_name, + const char *inconsistent_state, + gboolean show_original) +{ + guint last_row; + GtkLabel *title_label; + GtkWidget *value; + + last_row = append_title_field (table, title, &title_label); + value = attach_value_field (window, table, last_row, VALUE_COLUMN, + file_attribute_name, + inconsistent_state, + show_original); + gtk_label_set_mnemonic_widget (title_label, value); + return last_row; +} + +static guint +append_title_and_ellipsizing_value (FMPropertiesWindow *window, + GtkTable *table, + const char *title, + const char *file_attribute_name, + const char *inconsistent_state, + gboolean show_original) +{ + GtkLabel *title_label; + GtkWidget *value; + guint last_row; + + last_row = append_title_field (table, title, &title_label); + value = attach_ellipsizing_value_field (window, table, last_row, VALUE_COLUMN, + file_attribute_name, + inconsistent_state, + show_original); + gtk_label_set_mnemonic_widget (title_label, value); + + return last_row; +} + +static guint +append_directory_contents_fields (FMPropertiesWindow *window, + GtkTable *table) +{ + GtkLabel *title_field, *value_field; + guint last_row; + + last_row = append_row (table); + + title_field = attach_title_field (table, last_row, ""); + window->details->directory_contents_title_field = title_field; + gtk_label_set_line_wrap (title_field, TRUE); + + value_field = attach_directory_contents_value_field + (window, table, last_row); + + gtk_label_set_mnemonic_widget(title_field, GTK_WIDGET(value_field)); + return last_row; +} + +static GtkWidget * +create_page_with_hbox (GtkNotebook *notebook, + const char *title) +{ + GtkWidget *hbox; + + g_assert (GTK_IS_NOTEBOOK (notebook)); + g_assert (title != NULL); + + hbox = gtk_hbox_new (FALSE, 0); + gtk_widget_show (hbox); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 12); + gtk_box_set_spacing (GTK_BOX (hbox), 12); + gtk_notebook_append_page (notebook, hbox, gtk_label_new (title)); + + return hbox; +} + +static GtkWidget * +create_page_with_vbox (GtkNotebook *notebook, + const char *title) +{ + GtkWidget *vbox; + + g_assert (GTK_IS_NOTEBOOK (notebook)); + g_assert (title != NULL); + + vbox = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 12); + gtk_notebook_append_page (notebook, vbox, gtk_label_new (title)); + + return vbox; +} + +static GtkWidget * +append_blank_row (GtkTable *table) +{ + GtkWidget *separator; + + append_title_field (table, "", (GtkLabel **) &separator); + + return separator; +} + +static void +apply_standard_table_padding (GtkTable *table) +{ + gtk_table_set_row_spacings (table, ROW_PAD); + gtk_table_set_col_spacings (table, 12); +} + +static GtkWidget * +create_attribute_value_table (GtkVBox *vbox, int row_count) +{ + GtkWidget *table; + + table = gtk_table_new (row_count, COLUMN_COUNT, FALSE); + apply_standard_table_padding (GTK_TABLE (table)); + gtk_widget_show (table); + gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0); + + return table; +} + +static gboolean +is_merged_trash_directory (CajaFile *file) +{ + char *file_uri; + gboolean result; + + file_uri = caja_file_get_uri (file); + result = strcmp (file_uri, "trash:///") == 0; + g_free (file_uri); + + return result; +} + +static gboolean +is_computer_directory (CajaFile *file) +{ + char *file_uri; + gboolean result; + + file_uri = caja_file_get_uri (file); + result = strcmp (file_uri, "computer:///") == 0; + g_free (file_uri); + + return result; +} + +static gboolean +is_network_directory (CajaFile *file) +{ + char *file_uri; + gboolean result; + + file_uri = caja_file_get_uri (file); + result = strcmp (file_uri, "network:///") == 0; + g_free (file_uri); + + return result; +} + +static gboolean +is_burn_directory (CajaFile *file) +{ + char *file_uri; + gboolean result; + + file_uri = caja_file_get_uri (file); + result = strcmp (file_uri, "burn:///") == 0; + g_free (file_uri); + + return result; +} + +static gboolean +should_show_custom_icon_buttons (FMPropertiesWindow *window) +{ + if (is_multi_file_window (window)) { + return FALSE; + } + + return TRUE; +} + +static gboolean +should_show_file_type (FMPropertiesWindow *window) +{ + if (!is_multi_file_window (window) + && (is_merged_trash_directory (get_target_file (window)) || + is_computer_directory (get_target_file (window)) || + is_network_directory (get_target_file (window)) || + is_burn_directory (get_target_file (window)))) { + return FALSE; + } + + + return TRUE; +} + +static gboolean +should_show_location_info (FMPropertiesWindow *window) +{ + if (!is_multi_file_window (window) + && (is_merged_trash_directory (get_target_file (window)) || + is_computer_directory (get_target_file (window)) || + is_network_directory (get_target_file (window)) || + is_burn_directory (get_target_file (window)))) { + return FALSE; + } + + return TRUE; +} + +static gboolean +should_show_accessed_date (FMPropertiesWindow *window) +{ + /* Accessed date for directory seems useless. If we some + * day decide that it is useful, we should separately + * consider whether it's useful for "trash:". + */ + if (file_list_all_directories (window->details->target_files)) { + return FALSE; + } + + return TRUE; +} + +static gboolean +should_show_link_target (FMPropertiesWindow *window) +{ + if (!is_multi_file_window (window) + && caja_file_is_symbolic_link (get_target_file (window))) { + return TRUE; + } + + return FALSE; +} + +static gboolean +should_show_free_space (FMPropertiesWindow *window) +{ + + if (!is_multi_file_window (window) + && (is_merged_trash_directory (get_target_file (window)) || + is_computer_directory (get_target_file (window)) || + is_network_directory (get_target_file (window)) || + is_burn_directory (get_target_file (window)))) { + return FALSE; + } + + if (file_list_all_directories (window->details->target_files)) { + return TRUE; + } + + return FALSE; +} + +static gboolean +should_show_volume_usage (FMPropertiesWindow *window) +{ + CajaFile *file; + gboolean success = FALSE; + + if (is_multi_file_window (window)) { + return FALSE; + } + + file = get_original_file (window); + + if (file == NULL) { + return FALSE; + } + + if (caja_file_can_unmount (file)) { + return TRUE; + } + +#ifdef TODO_GIO + /* Look at is_mountpoint for activation uri */ +#endif + return success; +} + +static void +paint_used_legend (GtkWidget *widget, GdkEventExpose *eev, gpointer data) +{ + FMPropertiesWindow *window; + cairo_t *cr; + gint width, height; + GtkAllocation allocation; + + gtk_widget_get_allocation (widget, &allocation); + + width = allocation.width; + height = allocation.height; + + window = FM_PROPERTIES_WINDOW (data); + + cr = gdk_cairo_create (gtk_widget_get_window (widget)); + + cairo_rectangle (cr, + 2, + 2, + width - 4, + height - 4); + + cairo_set_source_rgb (cr, (double) window->details->used_color.red / 65535, (double) window->details->used_color.green / 65535, (double) window->details->used_color.blue / 65535); + cairo_fill_preserve (cr); + + cairo_set_source_rgb (cr, (double) window->details->used_stroke_color.red / 65535, (double) window->details->used_stroke_color.green / 65535, (double) window->details->used_stroke_color.blue / 65535); + cairo_stroke (cr); + + cairo_destroy (cr); +} + +static void +paint_free_legend (GtkWidget *widget, GdkEventExpose *eev, gpointer data) +{ + FMPropertiesWindow *window; + cairo_t *cr; + gint width, height; + GtkAllocation allocation; + + window = FM_PROPERTIES_WINDOW (data); + gtk_widget_get_allocation (widget, &allocation); + + width = allocation.width; + height = allocation.height; + cr = gdk_cairo_create (gtk_widget_get_window (widget)); + + cairo_rectangle (cr, + 2, + 2, + width - 4, + height - 4); + + cairo_set_source_rgb (cr, (double) window->details->free_color.red / 65535, (double) window->details->free_color.green / 65535, (double) window->details->free_color.blue / 65535); + cairo_fill_preserve(cr); + + cairo_set_source_rgb (cr, (double) window->details->free_stroke_color.red / 65535, (double) window->details->free_stroke_color.green / 65535, (double) window->details->free_stroke_color.blue / 65535); + cairo_stroke (cr); + + cairo_destroy (cr); +} + +static void +paint_pie_chart (GtkWidget *widget, GdkEventExpose *eev, gpointer data) +{ + + FMPropertiesWindow *window; + cairo_t *cr; + gint width, height; + double free, used; + double angle1, angle2, split, xc, yc, radius; + GtkAllocation allocation; + + window = FM_PROPERTIES_WINDOW (data); + gtk_widget_get_allocation (widget, &allocation); + + width = allocation.width; + height = allocation.height; + + + free = (double)window->details->volume_free / (double)window->details->volume_capacity; + used = 1.0 - free; + + angle1 = free * 2 * G_PI; + angle2 = used * 2 * G_PI; + split = (2 * G_PI - angle1) * .5; + xc = width / 2; + yc = height / 2; + + cr = gdk_cairo_create (gtk_widget_get_window (widget)); + + if (width < height) { + radius = width / 2 - 8; + } else { + radius = height / 2 - 8; + } + + if (angle1 != 2 * G_PI && angle1 != 0) { + angle1 = angle1 + split; + } + + if (angle2 != 2 * G_PI && angle2 != 0) { + angle2 = angle2 - split; + } + + if (used > 0) { + if (free != 0) { + cairo_move_to (cr,xc,yc); + } + + cairo_arc (cr, xc, yc, radius, angle1, angle2); + + if (free != 0) { + cairo_line_to (cr,xc,yc); + } + + cairo_set_source_rgb (cr, (double) window->details->used_color.red / 65535, (double) window->details->used_color.green / 65535, (double) window->details->used_color.blue / 65535); + cairo_fill_preserve (cr); + + cairo_set_source_rgb (cr, (double) window->details->used_stroke_color.red / 65535, (double) window->details->used_stroke_color.green / 65535, (double) window->details->used_stroke_color.blue / 65535); + cairo_stroke (cr); + } + + if (free > 0) { + if (used != 0) { + cairo_move_to (cr,xc,yc); + } + + cairo_arc_negative (cr, xc, yc, radius, angle1, angle2); + + if (used != 0) { + cairo_line_to (cr,xc,yc); + } + + cairo_set_source_rgb (cr, (double) window->details->free_color.red / 65535, (double) window->details->free_color.green / 65535,(double) window->details->free_color.blue / 65535); + cairo_fill_preserve(cr); + + cairo_set_source_rgb (cr, (double) window->details->free_stroke_color.red / 65535, (double) window->details->free_stroke_color.green / 65535, (double) window->details->free_stroke_color.blue / 65535); + cairo_stroke (cr); + } + + cairo_destroy (cr); +} + + +/* Copied from gtk/gtkstyle.c */ + +static void +rgb_to_hls (gdouble *r, + gdouble *g, + gdouble *b) +{ + gdouble min; + gdouble max; + gdouble red; + gdouble green; + gdouble blue; + gdouble h, l, s; + gdouble delta; + + red = *r; + green = *g; + blue = *b; + + if (red > green) + { + if (red > blue) + max = red; + else + max = blue; + + if (green < blue) + min = green; + else + min = blue; + } + else + { + if (green > blue) + max = green; + else + max = blue; + + if (red < blue) + min = red; + else + min = blue; + } + + l = (max + min) / 2; + s = 0; + h = 0; + + if (max != min) + { + if (l <= 0.5) + s = (max - min) / (max + min); + else + s = (max - min) / (2 - max - min); + + delta = max -min; + if (red == max) + h = (green - blue) / delta; + else if (green == max) + h = 2 + (blue - red) / delta; + else if (blue == max) + h = 4 + (red - green) / delta; + + h *= 60; + if (h < 0.0) + h += 360; + } + + *r = h; + *g = l; + *b = s; +} + +static void +hls_to_rgb (gdouble *h, + gdouble *l, + gdouble *s) +{ + gdouble hue; + gdouble lightness; + gdouble saturation; + gdouble m1, m2; + gdouble r, g, b; + + lightness = *l; + saturation = *s; + + if (lightness <= 0.5) + m2 = lightness * (1 + saturation); + else + m2 = lightness + saturation - lightness * saturation; + m1 = 2 * lightness - m2; + + if (saturation == 0) + { + *h = lightness; + *l = lightness; + *s = lightness; + } + else + { + hue = *h + 120; + while (hue > 360) + hue -= 360; + while (hue < 0) + hue += 360; + + if (hue < 60) + r = m1 + (m2 - m1) * hue / 60; + else if (hue < 180) + r = m2; + else if (hue < 240) + r = m1 + (m2 - m1) * (240 - hue) / 60; + else + r = m1; + + hue = *h; + while (hue > 360) + hue -= 360; + while (hue < 0) + hue += 360; + + if (hue < 60) + g = m1 + (m2 - m1) * hue / 60; + else if (hue < 180) + g = m2; + else if (hue < 240) + g = m1 + (m2 - m1) * (240 - hue) / 60; + else + g = m1; + + hue = *h - 120; + while (hue > 360) + hue -= 360; + while (hue < 0) + hue += 360; + + if (hue < 60) + b = m1 + (m2 - m1) * hue / 60; + else if (hue < 180) + b = m2; + else if (hue < 240) + b = m1 + (m2 - m1) * (240 - hue) / 60; + else + b = m1; + + *h = r; + *l = g; + *s = b; + } +} +static void +_pie_style_shade (GdkColor *a, + GdkColor *b, + gdouble k) +{ + gdouble red; + gdouble green; + gdouble blue; + + red = (gdouble) a->red / 65535.0; + green = (gdouble) a->green / 65535.0; + blue = (gdouble) a->blue / 65535.0; + + rgb_to_hls (&red, &green, &blue); + + green *= k; + if (green > 1.0) + green = 1.0; + else if (green < 0.0) + green = 0.0; + + blue *= k; + if (blue > 1.0) + blue = 1.0; + else if (blue < 0.0) + blue = 0.0; + + hls_to_rgb (&red, &green, &blue); + + b->red = red * 65535.0; + b->green = green * 65535.0; + b->blue = blue * 65535.0; +} + + +static GtkWidget* +create_pie_widget (FMPropertiesWindow *window) +{ + CajaFile *file; + GtkTable *table; + GtkStyle *style; + GtkWidget *pie_canvas; + GtkWidget *used_canvas; + GtkWidget *used_label; + GtkWidget *free_canvas; + GtkWidget *free_label; + GtkWidget *capacity_label; + GtkWidget *fstype_label; + gchar *capacity; + gchar *used; + gchar *free; + const char *fs_type; + gchar *uri; + GFile *location; + GFileInfo *info; + + #if GLIB_CHECK_VERSION(2, 30, 0) + capacity = g_format_size(window->details->volume_capacity); + free = g_format_size(window->details->volume_free); + used = g_format_size(window->details->volume_capacity - window->details->volume_free); + #else + capacity = g_format_size_for_display(window->details->volume_capacity); + free = g_format_size_for_display(window->details->volume_free); + used = g_format_size_for_display(window->details->volume_capacity - window->details->volume_free); + #endif + + file = get_original_file (window); + + uri = caja_file_get_activation_uri (file); + + table = GTK_TABLE (gtk_table_new (4, 3, FALSE)); + + style = gtk_rc_get_style (GTK_WIDGET(table)); + + if (!gtk_style_lookup_color (style, "chart_color_1", &window->details->used_color)) { + window->details->used_color.red = USED_FILL_R; + window->details->used_color.green = USED_FILL_G; + window->details->used_color.blue = USED_FILL_B; + } + + if (!gtk_style_lookup_color (style, "chart_color_2", &window->details->free_color)) { + window->details->free_color.red = FREE_FILL_R; + window->details->free_color.green = FREE_FILL_G; + window->details->free_color.blue = FREE_FILL_B; + } + + _pie_style_shade (&window->details->used_color, &window->details->used_stroke_color, 0.7); + _pie_style_shade (&window->details->free_color, &window->details->free_stroke_color, 0.7); + + pie_canvas = gtk_drawing_area_new (); + gtk_widget_set_size_request (pie_canvas, 200, 200); + + used_canvas = gtk_drawing_area_new (); + gtk_widget_set_size_request (used_canvas, 20, 20); + /* Translators: "used" refers to the capacity of the filesystem */ + used_label = gtk_label_new (g_strconcat (used, " ", _("used"), NULL)); + + free_canvas = gtk_drawing_area_new (); + gtk_widget_set_size_request (free_canvas,20,20); + /* Translators: "free" refers to the capacity of the filesystem */ + free_label = gtk_label_new (g_strconcat (free, " ", _("free"), NULL)); + + capacity_label = gtk_label_new (g_strconcat (_("Total capacity:"), " ", capacity, NULL)); + fstype_label = gtk_label_new (NULL); + + location = g_file_new_for_uri (uri); + info = g_file_query_filesystem_info (location, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, + NULL, NULL); + if (info) { + fs_type = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE); + if (fs_type != NULL) { + gtk_label_set_text (GTK_LABEL (fstype_label), g_strconcat (_("Filesystem type:"), " ", fs_type, NULL)); + } + + g_object_unref (info); + } + g_object_unref (location); + + g_free (uri); + g_free (capacity); + g_free (used); + g_free (free); + + gtk_table_attach (table, pie_canvas , 0, 1, 0, 4, GTK_FILL, GTK_SHRINK, 5, 5); + + gtk_table_attach (table, used_canvas, 1, 2, 0, 1, 0, 0, 5, 5); + gtk_table_attach (table, used_label , 2, 3, 0, 1, GTK_FILL, 0, 5, 5); + + gtk_table_attach (table, free_canvas, 1, 2, 1, 2, 0, 0, 5, 5); + gtk_table_attach (table, free_label , 2, 3, 1, 2, GTK_FILL, 0, 5, 5); + + gtk_table_attach (table, capacity_label , 1, 3, 2, 3, GTK_FILL, 0, 5, 5); + gtk_table_attach (table, fstype_label , 1, 3, 3, 4, GTK_FILL, 0, 5, 5); + + g_signal_connect (G_OBJECT (pie_canvas), "expose-event", G_CALLBACK (paint_pie_chart), window); + g_signal_connect (G_OBJECT (used_canvas), "expose-event", G_CALLBACK (paint_used_legend), window); + g_signal_connect (G_OBJECT (free_canvas), "expose-event", G_CALLBACK (paint_free_legend), window); + + return GTK_WIDGET (table); +} + +static GtkWidget* +create_volume_usage_widget (FMPropertiesWindow *window) +{ + GtkWidget *piewidget; + gchar *uri; + CajaFile *file; + GFile *location; + GFileInfo *info; + + file = get_original_file (window); + + uri = caja_file_get_activation_uri (file); + + location = g_file_new_for_uri (uri); + info = g_file_query_filesystem_info (location, "filesystem::*", NULL, NULL); + + if (info) { + window->details->volume_capacity = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE); + window->details->volume_free = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE); + + g_object_unref (info); + } else { + window->details->volume_capacity = 0; + window->details->volume_free = 0; + } + + g_object_unref (location); + + piewidget = create_pie_widget (window); + + gtk_widget_show_all (piewidget); + + return piewidget; +} + +static void +create_basic_page (FMPropertiesWindow *window) +{ + GtkTable *table; + GtkWidget *icon_aligner; + GtkWidget *icon_pixmap_widget; + GtkWidget *volume_usage; + GtkWidget *hbox, *vbox; + + guint last_row, row; + + hbox = create_page_with_hbox (window->details->notebook, _("Basic")); + + /* Icon pixmap */ + + icon_pixmap_widget = create_image_widget ( + window, should_show_custom_icon_buttons (window)); + gtk_widget_show (icon_pixmap_widget); + + icon_aligner = gtk_alignment_new (1, 0, 0, 0); + gtk_widget_show (icon_aligner); + + gtk_container_add (GTK_CONTAINER (icon_aligner), icon_pixmap_widget); + gtk_box_pack_start (GTK_BOX (hbox), icon_aligner, FALSE, FALSE, 0); + + window->details->icon_chooser = NULL; + + /* Table */ + + vbox = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox); + gtk_container_add (GTK_CONTAINER (hbox), vbox); + + table = GTK_TABLE (create_attribute_value_table (GTK_VBOX (vbox), 0)); + window->details->basic_table = table; + + /* Name label. The text will be determined in update_name_field */ + row = append_title_field (table, NULL, &window->details->name_label); + window->details->name_row = row; + + /* Name field */ + window->details->name_field = NULL; + update_name_field (window); + + /* Start with name field selected, if it's an entry. */ + if (CAJA_IS_ENTRY (window->details->name_field)) { + caja_entry_select_all (CAJA_ENTRY (window->details->name_field)); + gtk_widget_grab_focus (GTK_WIDGET (window->details->name_field)); + } + + if (fm_ditem_page_should_show (window->details->target_files)) { + GtkSizeGroup *label_size_group; + GtkWidget *box; + + row = append_row (table); + + label_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + gtk_size_group_add_widget (label_size_group, + GTK_WIDGET (window->details->name_label)); + box = fm_ditem_page_make_box (label_size_group, + window->details->target_files); + + gtk_table_attach (window->details->basic_table, box, + TITLE_COLUMN, VALUE_COLUMN + 1, + row, row + 1, + GTK_FILL, 0, + 0, 0); + } + + if (should_show_file_type (window)) { + append_title_value_pair (window, + table, _("Type:"), + "type", + INCONSISTENT_STATE_STRING, + FALSE); + } + + if (should_show_link_target (window)) { + append_title_and_ellipsizing_value (window, table, + _("Link target:"), + "link_target", + INCONSISTENT_STATE_STRING, + FALSE); + } + + if (is_multi_file_window (window) || + caja_file_is_directory (get_target_file (window))) { + append_directory_contents_fields (window, table); + } else { + append_title_value_pair (window, table, _("Size:"), + "size_detail", + INCONSISTENT_STATE_STRING, + FALSE); + } + + append_blank_row (table); + + if (should_show_location_info (window)) { + append_title_and_ellipsizing_value (window, table, _("Location:"), + "where", + INCONSISTENT_STATE_STRING, + TRUE); + + append_title_and_ellipsizing_value (window, table, + _("Volume:"), + "volume", + INCONSISTENT_STATE_STRING, + FALSE); + } + + if (should_show_accessed_date (window)) { + append_blank_row (table); + + append_title_value_pair (window, table, _("Accessed:"), + "date_accessed", + INCONSISTENT_STATE_STRING, + FALSE); + append_title_value_pair (window, table, _("Modified:"), + "date_modified", + INCONSISTENT_STATE_STRING, + FALSE); + } + + if (should_show_free_space (window)) { + append_blank_row (table); + + append_title_value_pair (window, table, _("Free space:"), + "free_space", + INCONSISTENT_STATE_STRING, + FALSE); + } + + if (should_show_volume_usage (window)) { + last_row = append_row (table); + volume_usage = create_volume_usage_widget (window); + gtk_table_attach_defaults (GTK_TABLE(table), volume_usage, 0, 2, last_row, last_row+1); + } +} + +static GHashTable * +get_initial_emblems (GList *files) +{ + GHashTable *ret; + GList *l; + + ret = g_hash_table_new_full (g_direct_hash, + g_direct_equal, + NULL, + (GDestroyNotify)eel_g_list_free_deep); + + for (l = files; l != NULL; l = l->next) { + CajaFile *file; + GList *keywords; + + file = CAJA_FILE (l->data); + + keywords = caja_file_get_keywords (file); + g_hash_table_insert (ret, file, keywords); + } + + return ret; +} + +static gboolean +files_has_directory (FMPropertiesWindow *window) +{ + GList *l; + + for (l = window->details->target_files; l != NULL; l = l->next) { + CajaFile *file; + file = CAJA_FILE (l->data); + if (caja_file_is_directory (file)) { + return TRUE; + } + + } + + return FALSE; +} + +static gboolean +files_has_changable_permissions_directory (FMPropertiesWindow *window) +{ + GList *l; + + for (l = window->details->target_files; l != NULL; l = l->next) { + CajaFile *file; + file = CAJA_FILE (l->data); + if (caja_file_is_directory (file) && + caja_file_can_get_permissions (file) && + caja_file_can_set_permissions (file)) { + return TRUE; + } + + } + + return FALSE; +} + + +static gboolean +files_has_file (FMPropertiesWindow *window) +{ + GList *l; + + for (l = window->details->target_files; l != NULL; l = l->next) { + CajaFile *file; + file = CAJA_FILE (l->data); + if (!caja_file_is_directory (file)) { + return TRUE; + } + + } + + return FALSE; +} + + +static void +create_emblems_page (FMPropertiesWindow *window) +{ + GtkWidget *emblems_table, *button, *scroller; + char *emblem_name; + GdkPixbuf *pixbuf; + char *label; + GList *icons, *l; + CajaIconInfo *info; + + /* The emblems wrapped table */ + scroller = eel_scrolled_wrap_table_new (TRUE, GTK_SHADOW_NONE, &emblems_table); + + gtk_container_set_border_width (GTK_CONTAINER (emblems_table), 12); + + gtk_widget_show (scroller); + + gtk_notebook_append_page (window->details->notebook, + scroller, gtk_label_new (_("Emblems"))); + + icons = caja_emblem_list_available (); + + window->details->initial_emblems = get_initial_emblems (window->details->original_files); + + l = icons; + while (l != NULL) { + emblem_name = l->data; + l = l->next; + + if (!caja_emblem_should_show_in_list (emblem_name)) { + continue; + } + + info = caja_icon_info_lookup_from_name (emblem_name, CAJA_ICON_SIZE_SMALL); + pixbuf = caja_icon_info_get_pixbuf_nodefault_at_size (info, CAJA_ICON_SIZE_SMALL); + + if (pixbuf == NULL) { + continue; + } + + label = g_strdup (caja_icon_info_get_display_name (info)); + g_object_unref (info); + + if (label == NULL) { + label = caja_emblem_get_keyword_from_icon_name (emblem_name); + } + + button = eel_labeled_image_check_button_new (label, pixbuf); + eel_labeled_image_set_fixed_image_height (EEL_LABELED_IMAGE (gtk_bin_get_child (GTK_BIN (button))), STANDARD_EMBLEM_HEIGHT); + eel_labeled_image_set_spacing (EEL_LABELED_IMAGE (gtk_bin_get_child (GTK_BIN (button))), EMBLEM_LABEL_SPACING); + + g_free (label); + g_object_unref (pixbuf); + + /* Attach parameters and signal handler. */ + g_object_set_data_full (G_OBJECT (button), "caja_emblem_name", + caja_emblem_get_keyword_from_icon_name (emblem_name), g_free); + + window->details->emblem_buttons = + g_list_append (window->details->emblem_buttons, + button); + + g_signal_connect_object (button, "toggled", + G_CALLBACK (emblem_button_toggled), + G_OBJECT (window), + 0); + + gtk_container_add (GTK_CONTAINER (emblems_table), button); + } + eel_g_list_free_deep (icons); + gtk_widget_show_all (emblems_table); +} + +static void +start_long_operation (FMPropertiesWindow *window) +{ + if (window->details->long_operation_underway == 0) { + /* start long operation */ + GdkCursor * cursor; + + cursor = gdk_cursor_new (GDK_WATCH); + gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (window)), cursor); + gdk_cursor_unref (cursor); + } + window->details->long_operation_underway ++; +} + +static void +end_long_operation (FMPropertiesWindow *window) +{ + if (gtk_widget_get_window (GTK_WIDGET (window)) != NULL && + window->details->long_operation_underway == 1) { + /* finished !! */ + gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (window)), NULL); + } + window->details->long_operation_underway--; +} + +static void +permission_change_callback (CajaFile *file, + GFile *res_loc, + GError *error, + gpointer callback_data) +{ + FMPropertiesWindow *window; + g_assert (callback_data != NULL); + + window = FM_PROPERTIES_WINDOW (callback_data); + end_long_operation (window); + + /* Report the error if it's an error. */ + fm_report_error_setting_permissions (file, error, NULL); + + g_object_unref (window); +} + +static void +update_permissions (FMPropertiesWindow *window, + guint32 vfs_new_perm, + guint32 vfs_mask, + gboolean is_folder, + gboolean apply_to_both_folder_and_dir, + gboolean use_original) +{ + GList *l; + + for (l = window->details->target_files; l != NULL; l = l->next) { + CajaFile *file; + guint32 permissions; + + file = CAJA_FILE (l->data); + + if (!caja_file_can_get_permissions (file)) { + continue; + } + + if (!apply_to_both_folder_and_dir && + ((caja_file_is_directory (file) && !is_folder) || + (!caja_file_is_directory (file) && is_folder))) { + continue; + } + + permissions = caja_file_get_permissions (file); + if (use_original) { + gpointer ptr; + if (g_hash_table_lookup_extended (window->details->initial_permissions, + file, NULL, &ptr)) { + permissions = (permissions & ~vfs_mask) | (GPOINTER_TO_INT (ptr) & vfs_mask); + } + } else { + permissions = (permissions & ~vfs_mask) | vfs_new_perm; + } + + start_long_operation (window); + g_object_ref (window); + caja_file_set_permissions + (file, permissions, + permission_change_callback, + window); + } +} + +static gboolean +initial_permission_state_consistent (FMPropertiesWindow *window, + guint32 mask, + gboolean is_folder, + gboolean both_folder_and_dir) +{ + GList *l; + gboolean first; + guint32 first_permissions; + + first = TRUE; + first_permissions = 0; + for (l = window->details->target_files; l != NULL; l = l->next) { + CajaFile *file; + guint32 permissions; + + file = l->data; + + if (!both_folder_and_dir && + ((caja_file_is_directory (file) && !is_folder) || + (!caja_file_is_directory (file) && is_folder))) { + continue; + } + + permissions = GPOINTER_TO_INT (g_hash_table_lookup (window->details->initial_permissions, + file)); + + if (first) { + if ((permissions & mask) != mask && + (permissions & mask) != 0) { + /* Not fully on or off -> inconsistent */ + return FALSE; + } + + first_permissions = permissions; + first = FALSE; + + } else if ((permissions & mask) != first_permissions) { + /* Not same permissions as first -> inconsistent */ + return FALSE; + } + } + return TRUE; +} + +static void +permission_button_toggled (GtkToggleButton *button, + FMPropertiesWindow *window) +{ + gboolean is_folder, is_special; + guint32 permission_mask; + gboolean inconsistent; + gboolean on; + + permission_mask = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), + "permission")); + is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), + "is-folder")); + is_special = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), + "is-special")); + + if (gtk_toggle_button_get_active (button) + && !gtk_toggle_button_get_inconsistent (button)) { + /* Go to the initial state unless the initial state was + consistent, or we support recursive apply */ + inconsistent = TRUE; + on = TRUE; + + if (!window->details->has_recursive_apply && + initial_permission_state_consistent (window, permission_mask, is_folder, is_special)) { + inconsistent = FALSE; + on = TRUE; + } + } else if (gtk_toggle_button_get_inconsistent (button) + && !gtk_toggle_button_get_active (button)) { + inconsistent = FALSE; + on = TRUE; + } else { + inconsistent = FALSE; + on = FALSE; + } + + g_signal_handlers_block_by_func (G_OBJECT (button), + G_CALLBACK (permission_button_toggled), + window); + + gtk_toggle_button_set_active (button, on); + gtk_toggle_button_set_inconsistent (button, inconsistent); + + g_signal_handlers_unblock_by_func (G_OBJECT (button), + G_CALLBACK (permission_button_toggled), + window); + + update_permissions (window, + on?permission_mask:0, + permission_mask, + is_folder, + is_special, + inconsistent); +} + +static void +permission_button_update (FMPropertiesWindow *window, + GtkToggleButton *button) +{ + GList *l; + gboolean all_set; + gboolean all_unset; + gboolean all_cannot_set; + gboolean is_folder, is_special; + gboolean no_match; + gboolean sensitive; + guint32 button_permission; + + if (gtk_toggle_button_get_inconsistent (button) && + window->details->has_recursive_apply) { + /* Never change from an inconsistent state if we have dirs, even + * if the current state is now consistent, because its a useful + * state for recursive apply. + */ + return; + } + + button_permission = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), + "permission")); + is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), + "is-folder")); + is_special = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), + "is-special")); + + all_set = TRUE; + all_unset = TRUE; + all_cannot_set = TRUE; + no_match = TRUE; + for (l = window->details->target_files; l != NULL; l = l->next) { + CajaFile *file; + guint32 file_permissions; + + file = CAJA_FILE (l->data); + + if (!caja_file_can_get_permissions (file)) { + continue; + } + + if (!is_special && + ((caja_file_is_directory (file) && !is_folder) || + (!caja_file_is_directory (file) && is_folder))) { + continue; + } + + no_match = FALSE; + + file_permissions = caja_file_get_permissions (file); + + if ((file_permissions & button_permission) == button_permission) { + all_unset = FALSE; + } else if ((file_permissions & button_permission) == 0) { + all_set = FALSE; + } else { + all_unset = FALSE; + all_set = FALSE; + } + + if (caja_file_can_set_permissions (file)) { + all_cannot_set = FALSE; + } + } + + sensitive = !all_cannot_set; + if (!is_folder) { + /* Don't insitive files when we have recursive apply */ + sensitive |= window->details->has_recursive_apply; + } + + + g_signal_handlers_block_by_func (G_OBJECT (button), + G_CALLBACK (permission_button_toggled), + window); + + gtk_toggle_button_set_active (button, !all_unset); + /* if actually inconsistent, or default value for file buttons + if no files are selected. (useful for recursive apply) */ + gtk_toggle_button_set_inconsistent (button, + (!all_unset && !all_set) || + (!is_folder && no_match)); + gtk_widget_set_sensitive (GTK_WIDGET (button), sensitive); + + g_signal_handlers_unblock_by_func (G_OBJECT (button), + G_CALLBACK (permission_button_toggled), + window); +} + +static void +set_up_permissions_checkbox (FMPropertiesWindow *window, + GtkWidget *check_button, + guint32 permission, + gboolean is_folder) +{ + /* Load up the check_button with data we'll need when updating its state. */ + g_object_set_data (G_OBJECT (check_button), "permission", + GINT_TO_POINTER (permission)); + g_object_set_data (G_OBJECT (check_button), "properties_window", + window); + g_object_set_data (G_OBJECT (check_button), "is-folder", + GINT_TO_POINTER (is_folder)); + + window->details->permission_buttons = + g_list_prepend (window->details->permission_buttons, + check_button); + + g_signal_connect_object (check_button, "toggled", + G_CALLBACK (permission_button_toggled), + window, + 0); +} + +static void +add_permissions_checkbox_with_label (FMPropertiesWindow *window, + GtkTable *table, + int row, int column, + const char *label, + guint32 permission_to_check, + GtkLabel *label_for, + gboolean is_folder) +{ + GtkWidget *check_button; + gboolean a11y_enabled; + + check_button = gtk_check_button_new_with_mnemonic (label); + gtk_widget_show (check_button); + gtk_table_attach (table, check_button, + column, column + 1, + row, row + 1, + GTK_FILL, 0, + 0, 0); + + set_up_permissions_checkbox (window, + check_button, + permission_to_check, + is_folder); + + a11y_enabled = GTK_IS_ACCESSIBLE (gtk_widget_get_accessible (check_button)); + if (a11y_enabled && label_for != NULL) { + eel_accessibility_set_up_label_widget_relation (GTK_WIDGET (label_for), + check_button); + } +} + +static void +add_permissions_checkbox (FMPropertiesWindow *window, + GtkTable *table, + int row, int column, + guint32 permission_to_check, + GtkLabel *label_for, + gboolean is_folder) +{ + gchar *label; + + if (column == PERMISSIONS_CHECKBOXES_READ_COLUMN) { + label = _("_Read"); + } else if (column == PERMISSIONS_CHECKBOXES_WRITE_COLUMN) { + label = _("_Write"); + } else { + label = _("E_xecute"); + } + + add_permissions_checkbox_with_label (window, table, + row, column, + label, + permission_to_check, + label_for, + is_folder); +} + +enum { + UNIX_PERM_SUID = S_ISUID, + UNIX_PERM_SGID = S_ISGID, + UNIX_PERM_STICKY = 01000, /* S_ISVTX not defined on all systems */ + UNIX_PERM_USER_READ = S_IRUSR, + UNIX_PERM_USER_WRITE = S_IWUSR, + UNIX_PERM_USER_EXEC = S_IXUSR, + UNIX_PERM_USER_ALL = S_IRUSR | S_IWUSR | S_IXUSR, + UNIX_PERM_GROUP_READ = S_IRGRP, + UNIX_PERM_GROUP_WRITE = S_IWGRP, + UNIX_PERM_GROUP_EXEC = S_IXGRP, + UNIX_PERM_GROUP_ALL = S_IRGRP | S_IWGRP | S_IXGRP, + UNIX_PERM_OTHER_READ = S_IROTH, + UNIX_PERM_OTHER_WRITE = S_IWOTH, + UNIX_PERM_OTHER_EXEC = S_IXOTH, + UNIX_PERM_OTHER_ALL = S_IROTH | S_IWOTH | S_IXOTH +}; + +typedef enum { + PERMISSION_READ = (1<<0), + PERMISSION_WRITE = (1<<1), + PERMISSION_EXEC = (1<<2) +} PermissionValue; + +typedef enum { + PERMISSION_USER, + PERMISSION_GROUP, + PERMISSION_OTHER +} PermissionType; + +static guint32 vfs_perms[3][3] = { + {UNIX_PERM_USER_READ, UNIX_PERM_USER_WRITE, UNIX_PERM_USER_EXEC}, + {UNIX_PERM_GROUP_READ, UNIX_PERM_GROUP_WRITE, UNIX_PERM_GROUP_EXEC}, + {UNIX_PERM_OTHER_READ, UNIX_PERM_OTHER_WRITE, UNIX_PERM_OTHER_EXEC}, +}; + +static guint32 +permission_to_vfs (PermissionType type, PermissionValue perm) +{ + guint32 vfs_perm; + g_assert (type >= 0 && type < 3); + + vfs_perm = 0; + if (perm & PERMISSION_READ) { + vfs_perm |= vfs_perms[type][0]; + } + if (perm & PERMISSION_WRITE) { + vfs_perm |= vfs_perms[type][1]; + } + if (perm & PERMISSION_EXEC) { + vfs_perm |= vfs_perms[type][2]; + } + + return vfs_perm; +} + + +static PermissionValue +permission_from_vfs (PermissionType type, guint32 vfs_perm) +{ + PermissionValue perm; + g_assert (type >= 0 && type < 3); + + perm = 0; + if (vfs_perm & vfs_perms[type][0]) { + perm |= PERMISSION_READ; + } + if (vfs_perm & vfs_perms[type][1]) { + perm |= PERMISSION_WRITE; + } + if (vfs_perm & vfs_perms[type][2]) { + perm |= PERMISSION_EXEC; + } + + return perm; +} + +static void +permission_combo_changed (GtkWidget *combo, FMPropertiesWindow *window) +{ + GtkTreeIter iter; + GtkTreeModel *model; + gboolean is_folder, use_original; + PermissionType type; + int new_perm, mask; + guint32 vfs_new_perm, vfs_mask; + + is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "is-folder")); + type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "permission-type")); + + if (is_folder) { + mask = PERMISSION_READ|PERMISSION_WRITE|PERMISSION_EXEC; + } else { + mask = PERMISSION_READ|PERMISSION_WRITE; + } + + vfs_mask = permission_to_vfs (type, mask); + + model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo)); + + if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter)) { + return; + } + gtk_tree_model_get (model, &iter, 1, &new_perm, 2, &use_original, -1); + vfs_new_perm = permission_to_vfs (type, new_perm); + + update_permissions (window, vfs_new_perm, vfs_mask, + is_folder, FALSE, use_original); +} + +static void +permission_combo_add_multiple_choice (GtkComboBox *combo, GtkTreeIter *iter) +{ + GtkTreeModel *model; + GtkListStore *store; + gboolean found; + + model = gtk_combo_box_get_model (combo); + store = GTK_LIST_STORE (model); + + found = FALSE; + gtk_tree_model_get_iter_first (model, iter); + do { + gboolean multi; + gtk_tree_model_get (model, iter, 2, &multi, -1); + + if (multi) { + found = TRUE; + break; + } + } while (gtk_tree_model_iter_next (model, iter)); + + if (!found) { + gtk_list_store_append (store, iter); + gtk_list_store_set (store, iter, 0, "---", 1, 0, 2, TRUE, -1); + } +} + +static void +permission_combo_update (FMPropertiesWindow *window, + GtkComboBox *combo) +{ + PermissionType type; + PermissionValue perm, all_dir_perm, all_file_perm, all_perm; + gboolean is_folder, no_files, no_dirs, all_file_same, all_dir_same, all_same; + gboolean all_dir_cannot_set, all_file_cannot_set, sensitive; + GtkTreeIter iter; + int mask; + GtkTreeModel *model; + GtkListStore *store; + GList *l; + gboolean is_multi; + + model = gtk_combo_box_get_model (combo); + + is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "is-folder")); + type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "permission-type")); + + is_multi = FALSE; + if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter)) { + gtk_tree_model_get (model, &iter, 2, &is_multi, -1); + } + + if (is_multi && window->details->has_recursive_apply) { + /* Never change from an inconsistent state if we have dirs, even + * if the current state is now consistent, because its a useful + * state for recursive apply. + */ + return; + } + + no_files = TRUE; + no_dirs = TRUE; + all_dir_same = TRUE; + all_file_same = TRUE; + all_dir_perm = 0; + all_file_perm = 0; + all_dir_cannot_set = TRUE; + all_file_cannot_set = TRUE; + + for (l = window->details->target_files; l != NULL; l = l->next) { + CajaFile *file; + guint32 file_permissions; + + file = CAJA_FILE (l->data); + + if (!caja_file_can_get_permissions (file)) { + continue; + } + + if (caja_file_is_directory (file)) { + mask = PERMISSION_READ|PERMISSION_WRITE|PERMISSION_EXEC; + } else { + mask = PERMISSION_READ|PERMISSION_WRITE; + } + + file_permissions = caja_file_get_permissions (file); + + perm = permission_from_vfs (type, file_permissions) & mask; + + if (caja_file_is_directory (file)) { + if (no_dirs) { + all_dir_perm = perm; + no_dirs = FALSE; + } else if (perm != all_dir_perm) { + all_dir_same = FALSE; + } + + if (caja_file_can_set_permissions (file)) { + all_dir_cannot_set = FALSE; + } + } else { + if (no_files) { + all_file_perm = perm; + no_files = FALSE; + } else if (perm != all_file_perm) { + all_file_same = FALSE; + } + + if (caja_file_can_set_permissions (file)) { + all_file_cannot_set = FALSE; + } + } + } + + if (is_folder) { + all_same = all_dir_same; + all_perm = all_dir_perm; + } else { + all_same = all_file_same && !no_files; + all_perm = all_file_perm; + } + + store = GTK_LIST_STORE (model); + if (all_same) { + gboolean found; + + found = FALSE; + gtk_tree_model_get_iter_first (model, &iter); + do { + int current_perm; + gtk_tree_model_get (model, &iter, 1, ¤t_perm, -1); + + if (current_perm == all_perm) { + found = TRUE; + break; + } + } while (gtk_tree_model_iter_next (model, &iter)); + + if (!found) { + GString *str; + str = g_string_new (""); + + if (!(all_perm & PERMISSION_READ)) { + /* translators: this gets concatenated to "no read", + * "no access", etc. (see following strings) + */ + g_string_append (str, _("no ")); + } + if (is_folder) { + g_string_append (str, _("list")); + } else { + g_string_append (str, _("read")); + } + + g_string_append (str, ", "); + + if (!(all_perm & PERMISSION_WRITE)) { + g_string_append (str, _("no ")); + } + if (is_folder) { + g_string_append (str, _("create/delete")); + } else { + g_string_append (str, _("write")); + } + + if (is_folder) { + g_string_append (str, ", "); + + if (!(all_perm & PERMISSION_EXEC)) { + g_string_append (str, _("no ")); + } + g_string_append (str, _("access")); + } + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + 0, str->str, + 1, all_perm, -1); + + g_string_free (str, TRUE); + } + } else { + permission_combo_add_multiple_choice (combo, &iter); + } + + g_signal_handlers_block_by_func (G_OBJECT (combo), + G_CALLBACK (permission_combo_changed), + window); + + gtk_combo_box_set_active_iter (combo, &iter); + + /* Also enable if no files found (for recursive + file changes when only selecting folders) */ + if (is_folder) { + sensitive = !all_dir_cannot_set; + } else { + sensitive = !all_file_cannot_set || + window->details->has_recursive_apply; + } + gtk_widget_set_sensitive (GTK_WIDGET (combo), sensitive); + + g_signal_handlers_unblock_by_func (G_OBJECT (combo), + G_CALLBACK (permission_combo_changed), + window); + +} + +static void +add_permissions_combo_box (FMPropertiesWindow *window, GtkTable *table, + PermissionType type, gboolean is_folder, + gboolean short_label) +{ + GtkWidget *combo; + GtkLabel *label; + GtkListStore *store; + GtkCellRenderer *cell; + GtkTreeIter iter; + int row; + + if (short_label) { + row = append_title_field (table, _("Access:"), &label); + } else if (is_folder) { + row = append_title_field (table, _("Folder access:"), &label); + } else { + row = append_title_field (table, _("File access:"), &label); + } + + store = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN); + combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL (store)); + + g_object_set_data (G_OBJECT (combo), "is-folder", GINT_TO_POINTER (is_folder)); + g_object_set_data (G_OBJECT (combo), "permission-type", GINT_TO_POINTER (type)); + + if (is_folder) { + if (type != PERMISSION_USER) { + gtk_list_store_append (store, &iter); + /* Translators: this is referred to the permissions + * the user has in a directory. + */ + gtk_list_store_set (store, &iter, 0, _("None"), 1, 0, -1); + } + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, _("List files only"), 1, PERMISSION_READ, -1); + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, _("Access files"), 1, PERMISSION_READ|PERMISSION_EXEC, -1); + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, _("Create and delete files"), 1, PERMISSION_READ|PERMISSION_EXEC|PERMISSION_WRITE, -1); + } else { + if (type != PERMISSION_USER) { + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, _("None"), 1, 0, -1); + } + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, _("Read-only"), 1, PERMISSION_READ, -1); + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, _("Read and write"), 1, PERMISSION_READ|PERMISSION_WRITE, -1); + } + if (window->details->has_recursive_apply) { + permission_combo_add_multiple_choice (GTK_COMBO_BOX (combo), &iter); + } + + g_object_unref (store); + + window->details->permission_combos = + g_list_prepend (window->details->permission_combos, + combo); + + g_signal_connect (combo, "changed", G_CALLBACK (permission_combo_changed), window); + + cell = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), cell, TRUE); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), cell, + "text", 0, + NULL); + + gtk_label_set_mnemonic_widget (label, combo); + gtk_widget_show (combo); + + gtk_table_attach (table, combo, + VALUE_COLUMN, VALUE_COLUMN + 1, + row, row + 1, + GTK_FILL, 0, + 0, 0); +} + + +static GtkWidget * +append_special_execution_checkbox (FMPropertiesWindow *window, + GtkTable *table, + const char *label_text, + guint32 permission_to_check) +{ + GtkWidget *check_button; + guint last_row; + + last_row = append_row (table); + + check_button = gtk_check_button_new_with_mnemonic (label_text); + gtk_widget_show (check_button); + + gtk_table_attach (table, check_button, + VALUE_COLUMN, VALUE_COLUMN + 1, + last_row, last_row + 1, + GTK_FILL, 0, + 0, 0); + + set_up_permissions_checkbox (window, + check_button, + permission_to_check, + FALSE); + g_object_set_data (G_OBJECT (check_button), "is-special", + GINT_TO_POINTER (TRUE)); + + return check_button; +} + +static void +append_special_execution_flags (FMPropertiesWindow *window, GtkTable *table) +{ + gint nrows; + + append_special_execution_checkbox + (window, table, _("Set _user ID"), UNIX_PERM_SUID); + + g_object_get (table, "n-rows", &nrows, NULL); + attach_title_field (table, nrows - 1, _("Special flags:")); + + append_special_execution_checkbox (window, table, _("Set gro_up ID"), UNIX_PERM_SGID); + append_special_execution_checkbox (window, table, _("_Sticky"), UNIX_PERM_STICKY); + + g_object_get (table, "n-rows", &nrows, NULL); + gtk_table_set_row_spacing (table, nrows - 1, 18); +} + +static gboolean +all_can_get_permissions (GList *file_list) +{ + GList *l; + for (l = file_list; l != NULL; l = l->next) { + CajaFile *file; + + file = CAJA_FILE (l->data); + + if (!caja_file_can_get_permissions (file)) { + return FALSE; + } + } + + return TRUE; +} + +static gboolean +all_can_set_permissions (GList *file_list) +{ + GList *l; + for (l = file_list; l != NULL; l = l->next) { + CajaFile *file; + + file = CAJA_FILE (l->data); + + if (!caja_file_can_set_permissions (file)) { + return FALSE; + } + } + + return TRUE; +} + +static GHashTable * +get_initial_permissions (GList *file_list) +{ + GHashTable *ret; + GList *l; + + ret = g_hash_table_new (g_direct_hash, + g_direct_equal); + + for (l = file_list; l != NULL; l = l->next) { + guint32 permissions; + CajaFile *file; + + file = CAJA_FILE (l->data); + + permissions = caja_file_get_permissions (file); + g_hash_table_insert (ret, file, + GINT_TO_POINTER (permissions)); + } + + return ret; +} + +static void +create_simple_permissions (FMPropertiesWindow *window, GtkTable *page_table) +{ + gboolean has_file, has_directory; + GtkLabel *group_label; + GtkLabel *owner_label; + GtkLabel *execute_label; + GtkWidget *value; + GtkComboBox *group_combo_box; + GtkComboBox *owner_combo_box; + guint last_row; + gint nrows; + + last_row = 0; + + has_file = files_has_file (window); + has_directory = files_has_directory (window); + + if (!is_multi_file_window (window) && caja_file_can_set_owner (get_target_file (window))) { + owner_label = attach_title_field (page_table, last_row, _("_Owner:")); + /* Combo box in this case. */ + owner_combo_box = attach_owner_combo_box (page_table, last_row, get_target_file (window)); + gtk_label_set_mnemonic_widget (owner_label, + GTK_WIDGET (owner_combo_box)); + } else { + owner_label = attach_title_field (page_table, last_row, _("Owner:")); + /* Static text in this case. */ + value = attach_value_field (window, + page_table, last_row, VALUE_COLUMN, + "owner", + INCONSISTENT_STATE_STRING, + FALSE); + gtk_label_set_mnemonic_widget (owner_label, value); + } + + if (has_directory) { + add_permissions_combo_box (window, page_table, + PERMISSION_USER, TRUE, FALSE); + } + if (has_file || window->details->has_recursive_apply) { + add_permissions_combo_box (window, page_table, + PERMISSION_USER, FALSE, !has_directory); + } + + g_object_get (page_table, "n-rows", &nrows, NULL); + gtk_table_set_row_spacing (page_table, nrows - 1, 18); + + if (!is_multi_file_window (window) && caja_file_can_set_group (get_target_file (window))) { + last_row = append_title_field (page_table, + _("_Group:"), + &group_label); + /* Combo box in this case. */ + group_combo_box = attach_group_combo_box (page_table, last_row, + get_target_file (window)); + gtk_label_set_mnemonic_widget (group_label, + GTK_WIDGET (group_combo_box)); + } else { + last_row = append_title_field (page_table, + _("Group:"), + &group_label); + /* Static text in this case. */ + value = attach_value_field (window, page_table, last_row, + VALUE_COLUMN, + "group", + INCONSISTENT_STATE_STRING, + FALSE); + gtk_label_set_mnemonic_widget (group_label, value); + } + + if (has_directory) { + add_permissions_combo_box (window, page_table, + PERMISSION_GROUP, TRUE, + FALSE); + } + if (has_file || window->details->has_recursive_apply) { + add_permissions_combo_box (window, page_table, + PERMISSION_GROUP, FALSE, + !has_directory); + } + + g_object_get (page_table, "n-rows", &nrows, NULL); + gtk_table_set_row_spacing (page_table, nrows - 1, 18); + + append_title_field (page_table, + _("Others"), + &group_label); + + if (has_directory) { + add_permissions_combo_box (window, page_table, + PERMISSION_OTHER, TRUE, + FALSE); + } + if (has_file || window->details->has_recursive_apply) { + add_permissions_combo_box (window, page_table, + PERMISSION_OTHER, FALSE, + !has_directory); + } + + g_object_get (page_table, "n-rows", &nrows, NULL); + gtk_table_set_row_spacing (page_table, nrows - 1, 18); + + last_row = append_title_field (page_table, + _("Execute:"), + &execute_label); + add_permissions_checkbox_with_label (window, page_table, + last_row, 1, + _("Allow _executing file as program"), + UNIX_PERM_USER_EXEC|UNIX_PERM_GROUP_EXEC|UNIX_PERM_OTHER_EXEC, + execute_label, FALSE); + +} + +static void +create_permission_checkboxes (FMPropertiesWindow *window, + GtkTable *page_table, + gboolean is_folder) +{ + guint checkbox_titles_row; + GtkLabel *owner_perm_label; + GtkLabel *group_perm_label; + GtkLabel *other_perm_label; + GtkTable *check_button_table; + + checkbox_titles_row = append_title_field (page_table, _("Owner:"), &owner_perm_label); + append_title_field (page_table, _("Group:"), &group_perm_label); + append_title_field (page_table, _("Others:"), &other_perm_label); + + check_button_table = GTK_TABLE (gtk_table_new + (PERMISSIONS_CHECKBOXES_ROW_COUNT, + PERMISSIONS_CHECKBOXES_COLUMN_COUNT, + FALSE)); + apply_standard_table_padding (check_button_table); + gtk_widget_show (GTK_WIDGET (check_button_table)); + gtk_table_attach (page_table, GTK_WIDGET (check_button_table), + VALUE_COLUMN, VALUE_COLUMN + 1, + checkbox_titles_row, checkbox_titles_row + PERMISSIONS_CHECKBOXES_ROW_COUNT, + 0, 0, + 0, 0); + + add_permissions_checkbox (window, + check_button_table, + PERMISSIONS_CHECKBOXES_OWNER_ROW, + PERMISSIONS_CHECKBOXES_READ_COLUMN, + UNIX_PERM_USER_READ, + owner_perm_label, + is_folder); + + add_permissions_checkbox (window, + check_button_table, + PERMISSIONS_CHECKBOXES_OWNER_ROW, + PERMISSIONS_CHECKBOXES_WRITE_COLUMN, + UNIX_PERM_USER_WRITE, + owner_perm_label, + is_folder); + + add_permissions_checkbox (window, + check_button_table, + PERMISSIONS_CHECKBOXES_OWNER_ROW, + PERMISSIONS_CHECKBOXES_EXECUTE_COLUMN, + UNIX_PERM_USER_EXEC, + owner_perm_label, + is_folder); + + add_permissions_checkbox (window, + check_button_table, + PERMISSIONS_CHECKBOXES_GROUP_ROW, + PERMISSIONS_CHECKBOXES_READ_COLUMN, + UNIX_PERM_GROUP_READ, + group_perm_label, + is_folder); + + add_permissions_checkbox (window, + check_button_table, + PERMISSIONS_CHECKBOXES_GROUP_ROW, + PERMISSIONS_CHECKBOXES_WRITE_COLUMN, + UNIX_PERM_GROUP_WRITE, + group_perm_label, + is_folder); + + add_permissions_checkbox (window, + check_button_table, + PERMISSIONS_CHECKBOXES_GROUP_ROW, + PERMISSIONS_CHECKBOXES_EXECUTE_COLUMN, + UNIX_PERM_GROUP_EXEC, + group_perm_label, + is_folder); + + add_permissions_checkbox (window, + check_button_table, + PERMISSIONS_CHECKBOXES_OTHERS_ROW, + PERMISSIONS_CHECKBOXES_READ_COLUMN, + UNIX_PERM_OTHER_READ, + other_perm_label, + is_folder); + + add_permissions_checkbox (window, + check_button_table, + PERMISSIONS_CHECKBOXES_OTHERS_ROW, + PERMISSIONS_CHECKBOXES_WRITE_COLUMN, + UNIX_PERM_OTHER_WRITE, + other_perm_label, + is_folder); + + add_permissions_checkbox (window, + check_button_table, + PERMISSIONS_CHECKBOXES_OTHERS_ROW, + PERMISSIONS_CHECKBOXES_EXECUTE_COLUMN, + UNIX_PERM_OTHER_EXEC, + other_perm_label, + is_folder); +} + +static void +create_advanced_permissions (FMPropertiesWindow *window, GtkTable *page_table) +{ + guint last_row; + GtkLabel *group_label; + GtkLabel *owner_label; + GtkComboBox *group_combo_box; + GtkComboBox *owner_combo_box; + gboolean has_directory, has_file; + gint nrows; + + last_row = 0; + + if (!is_multi_file_window (window) && caja_file_can_set_owner (get_target_file (window))) { + + owner_label = attach_title_field (page_table, last_row, _("_Owner:")); + /* Combo box in this case. */ + owner_combo_box = attach_owner_combo_box (page_table, last_row, get_target_file (window)); + gtk_label_set_mnemonic_widget (owner_label, + GTK_WIDGET (owner_combo_box)); + } else { + GtkWidget *value; + + owner_label = attach_title_field (page_table, last_row, _("Owner:")); + /* Static text in this case. */ + value = attach_value_field (window, + page_table, last_row, VALUE_COLUMN, + "owner", + INCONSISTENT_STATE_STRING, + FALSE); + gtk_label_set_mnemonic_widget (owner_label, value); + } + + if (!is_multi_file_window (window) && caja_file_can_set_group (get_target_file (window))) { + last_row = append_title_field (page_table, + _("_Group:"), + &group_label); + /* Combo box in this case. */ + group_combo_box = attach_group_combo_box (page_table, last_row, + get_target_file (window)); + gtk_label_set_mnemonic_widget (group_label, + GTK_WIDGET (group_combo_box)); + } else { + last_row = append_title_field (page_table, + _("Group:"), + NULL); + /* Static text in this case. */ + attach_value_field (window, page_table, last_row, + VALUE_COLUMN, + "group", + INCONSISTENT_STATE_STRING, + FALSE); + } + + g_object_get (page_table, "n-rows", &nrows, NULL); + gtk_table_set_row_spacing (page_table, nrows - 1, 18); + + has_directory = files_has_directory (window); + has_file = files_has_file (window); + + if (has_directory) { + if (has_file || window->details->has_recursive_apply) { + append_title_field (page_table, + _("Folder Permissions:"), + NULL); + } + create_permission_checkboxes (window, page_table, TRUE); + g_object_get (page_table, "n-rows", &nrows, NULL); + gtk_table_set_row_spacing (page_table, nrows - 1, 18); + + } + + + if (has_file || window->details->has_recursive_apply) { + if (has_directory) { + append_title_field (page_table, + _("File Permissions:"), + NULL); + } + create_permission_checkboxes (window, page_table, FALSE); + g_object_get (page_table, "n-rows", &nrows, NULL); + gtk_table_set_row_spacing (page_table, nrows - 1, 18); + } + + append_special_execution_flags (window, page_table); + + append_title_value_pair + (window, page_table, _("Text view:"), + "permissions", INCONSISTENT_STATE_STRING, + FALSE); +} + +static void +set_recursive_permissions_done (gpointer callback_data) +{ + FMPropertiesWindow *window; + + window = FM_PROPERTIES_WINDOW (callback_data); + end_long_operation (window); + + g_object_unref (window); +} + + +static void +apply_recursive_clicked (GtkWidget *recursive_button, + FMPropertiesWindow *window) +{ + guint32 file_permission, file_permission_mask; + guint32 dir_permission, dir_permission_mask; + guint32 vfs_mask, vfs_new_perm, p; + GtkWidget *button, *combo; + gboolean active, is_folder, is_special, use_original; + GList *l; + GtkTreeModel *model; + GtkTreeIter iter; + PermissionType type; + int new_perm, mask; + + file_permission = 0; + file_permission_mask = 0; + dir_permission = 0; + dir_permission_mask = 0; + + /* Advanced mode and execute checkbox: */ + for (l = window->details->permission_buttons; l != NULL; l = l->next) { + button = l->data; + + if (gtk_toggle_button_get_inconsistent (GTK_TOGGLE_BUTTON (button))) { + continue; + } + + active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)); + p = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), + "permission")); + is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), + "is-folder")); + is_special = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), + "is-special")); + + if (is_folder || is_special) { + dir_permission_mask |= p; + if (active) { + dir_permission |= p; + } + } + if (!is_folder || is_special) { + file_permission_mask |= p; + if (active) { + file_permission |= p; + } + } + } + /* Simple mode, minus exec checkbox */ + for (l = window->details->permission_combos; l != NULL; l = l->next) { + combo = l->data; + + if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter)) { + continue; + } + + type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "permission-type")); + is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), + "is-folder")); + + model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo)); + gtk_tree_model_get (model, &iter, 1, &new_perm, 2, &use_original, -1); + if (use_original) { + continue; + } + vfs_new_perm = permission_to_vfs (type, new_perm); + + if (is_folder) { + mask = PERMISSION_READ|PERMISSION_WRITE|PERMISSION_EXEC; + } else { + mask = PERMISSION_READ|PERMISSION_WRITE; + } + vfs_mask = permission_to_vfs (type, mask); + + if (is_folder) { + dir_permission_mask |= vfs_mask; + dir_permission |= vfs_new_perm; + } else { + file_permission_mask |= vfs_mask; + file_permission |= vfs_new_perm; + } + } + + for (l = window->details->target_files; l != NULL; l = l->next) { + CajaFile *file; + char *uri; + + file = CAJA_FILE (l->data); + + if (caja_file_is_directory (file) && + caja_file_can_set_permissions (file)) { + uri = caja_file_get_uri (file); + start_long_operation (window); + g_object_ref (window); + caja_file_set_permissions_recursive (uri, + file_permission, + file_permission_mask, + dir_permission, + dir_permission_mask, + set_recursive_permissions_done, + window); + g_free (uri); + } + } +} + +static void +create_permissions_page (FMPropertiesWindow *window) +{ + GtkWidget *vbox, *button, *hbox; + GtkTable *page_table; + char *file_name, *prompt_text; + GList *file_list; + guint last_row; + gint nrows; + + vbox = create_page_with_vbox (window->details->notebook, + _("Permissions")); + + file_list = window->details->original_files; + + window->details->initial_permissions = NULL; + + if (all_can_get_permissions (file_list) && all_can_get_permissions (window->details->target_files)) { + window->details->initial_permissions = get_initial_permissions (window->details->target_files); + window->details->has_recursive_apply = files_has_changable_permissions_directory (window); + + if (!all_can_set_permissions (file_list)) { + add_prompt_and_separator ( + GTK_VBOX (vbox), + _("You are not the owner, so you cannot change these permissions.")); + } + + page_table = GTK_TABLE (gtk_table_new (1, COLUMN_COUNT, FALSE)); + window->details->permissions_table = page_table; + + apply_standard_table_padding (page_table); + gtk_widget_show (GTK_WIDGET (page_table)); + gtk_box_pack_start (GTK_BOX (vbox), + GTK_WIDGET (page_table), + TRUE, TRUE, 0); + + if (eel_preferences_get_boolean (CAJA_PREFERENCES_SHOW_ADVANCED_PERMISSIONS)) { + window->details->advanced_permissions = TRUE; + create_advanced_permissions (window, page_table); + } else { + window->details->advanced_permissions = FALSE; + create_simple_permissions (window, page_table); + } + + g_object_get (page_table, "n-rows", &nrows, NULL); + gtk_table_set_row_spacing (page_table, nrows - 1, 18); + +#ifdef HAVE_SELINUX + append_title_value_pair + (window, page_table, _("SELinux context:"), + "selinux_context", INCONSISTENT_STATE_STRING, + FALSE); +#endif + append_title_value_pair + (window, page_table, _("Last changed:"), + "date_permissions", INCONSISTENT_STATE_STRING, + FALSE); + + if (window->details->has_recursive_apply) { + last_row = append_row (page_table); + hbox = gtk_hbox_new (FALSE, 0); + gtk_widget_show (hbox); + gtk_table_attach (page_table, hbox, + 0, 2, + last_row, last_row+1, + GTK_FILL, 0, + 0, 0); + + button = gtk_button_new_with_mnemonic (_("Apply Permissions to Enclosed Files")); + gtk_widget_show (button); + gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0); + g_signal_connect (button, "clicked", + G_CALLBACK (apply_recursive_clicked), + window); + } + } else { + if (!is_multi_file_window (window)) { + file_name = caja_file_get_display_name (get_target_file (window)); + prompt_text = g_strdup_printf (_("The permissions of \"%s\" could not be determined."), file_name); + g_free (file_name); + } else { + prompt_text = g_strdup (_("The permissions of the selected file could not be determined.")); + } + + add_prompt (GTK_VBOX (vbox), prompt_text, TRUE); + g_free (prompt_text); + } +} + +static void +append_extension_pages (FMPropertiesWindow *window) +{ + GList *providers; + GList *p; + + providers = caja_module_get_extensions_for_type (CAJA_TYPE_PROPERTY_PAGE_PROVIDER); + + for (p = providers; p != NULL; p = p->next) { + CajaPropertyPageProvider *provider; + GList *pages; + GList *l; + + provider = CAJA_PROPERTY_PAGE_PROVIDER (p->data); + + pages = caja_property_page_provider_get_pages + (provider, window->details->original_files); + + for (l = pages; l != NULL; l = l->next) { + CajaPropertyPage *page; + GtkWidget *page_widget; + GtkWidget *label; + + page = CAJA_PROPERTY_PAGE (l->data); + + g_object_get (G_OBJECT (page), + "page", &page_widget, "label", &label, + NULL); + + gtk_notebook_append_page (window->details->notebook, + page_widget, label); + + g_object_set_data (G_OBJECT (page_widget), + "is-extension-page", + page); + + g_object_unref (page_widget); + g_object_unref (label); + + g_object_unref (page); + } + + g_list_free (pages); + } + + caja_module_extension_list_free (providers); +} + +static gboolean +should_show_emblems (FMPropertiesWindow *window) +{ + /* FIXME bugzilla.gnome.org 45643: + * Emblems aren't displayed on the the desktop Trash icon, so + * we shouldn't pretend that they work by showing them here. + * When bug 5643 is fixed we can remove this case. + */ + if (!is_multi_file_window (window) + && is_merged_trash_directory (get_target_file (window))) { + return FALSE; + } + + return TRUE; +} + +static gboolean +should_show_permissions (FMPropertiesWindow *window) +{ + CajaFile *file; + + file = get_target_file (window); + + /* Don't show permissions for Trash and Computer since they're not + * really file system objects. + */ + if (!is_multi_file_window (window) + && (is_merged_trash_directory (file) || + is_computer_directory (file))) { + return FALSE; + } + + return TRUE; +} + +static char * +get_pending_key (GList *file_list) +{ + GList *l; + GList *uris; + GString *key; + char *ret; + + uris = NULL; + for (l = file_list; l != NULL; l = l->next) { + uris = g_list_prepend (uris, caja_file_get_uri (CAJA_FILE (l->data))); + } + uris = g_list_sort (uris, (GCompareFunc)strcmp); + + key = g_string_new (""); + for (l = uris; l != NULL; l = l->next) { + g_string_append (key, l->data); + g_string_append (key, ";"); + } + + eel_g_list_free_deep (uris); + + ret = key->str; + g_string_free (key, FALSE); + + return ret; +} + +static StartupData * +startup_data_new (GList *original_files, + GList *target_files, + const char *pending_key, + GtkWidget *parent_widget) +{ + StartupData *data; + GList *l; + + data = g_new0 (StartupData, 1); + data->original_files = caja_file_list_copy (original_files); + data->target_files = caja_file_list_copy (target_files); + data->parent_widget = parent_widget; + data->pending_key = g_strdup (pending_key); + data->pending_files = g_hash_table_new (g_direct_hash, + g_direct_equal); + + for (l = data->target_files; l != NULL; l = l->next) { + g_hash_table_insert (data->pending_files, l->data, l->data); + } + + return data; +} + +static void +startup_data_free (StartupData *data) +{ + caja_file_list_free (data->original_files); + caja_file_list_free (data->target_files); + g_hash_table_destroy (data->pending_files); + g_free (data->pending_key); + g_free (data); +} + +static void +file_changed_callback (CajaFile *file, gpointer user_data) +{ + FMPropertiesWindow *window = FM_PROPERTIES_WINDOW (user_data); + + if (!g_list_find (window->details->changed_files, file)) { + caja_file_ref (file); + window->details->changed_files = g_list_prepend (window->details->changed_files, file); + + schedule_files_update (window); + } +} + +static gboolean +is_a_special_file (CajaFile *file) +{ + if (file == NULL || + CAJA_IS_DESKTOP_ICON_FILE (file) || + caja_file_is_caja_link (file) || + is_merged_trash_directory (file) || + is_computer_directory (file)) { + return TRUE; + } + return FALSE; +} + +static gboolean +should_show_open_with (FMPropertiesWindow *window) +{ + CajaFile *file; + + /* Don't show open with tab for desktop special icons (trash, etc) + * or desktop files. We don't get the open-with menu for these anyway. + * + * Also don't show it for folders. Changing the default app for folders + * leads to all sort of hard to understand errors. + */ + + if (is_multi_file_window (window)) { + if (!file_list_attributes_identical (window->details->original_files, + "mime_type")) { + return FALSE; + } else { + + GList *l; + + for (l = window->details->original_files; l; l = l->next) { + file = CAJA_FILE (l->data); + if (caja_file_is_directory (file) || + is_a_special_file (file)) { + return FALSE; + } + } + } + } else { + file = get_original_file (window); + if (caja_file_is_directory (file) || + is_a_special_file (file)) { + return FALSE; + } + } + return TRUE; +} + +static void +create_open_with_page (FMPropertiesWindow *window) +{ + GtkWidget *vbox; + char *mime_type; + char *uri; + + mime_type = caja_file_get_mime_type (get_target_file (window)); + + if (!is_multi_file_window (window)) { + uri = caja_file_get_uri (get_target_file (window)); + if (uri == NULL) { + return; + } + vbox = caja_mime_application_chooser_new (uri, mime_type); + + g_free (uri); + } else { + GList *uris; + + uris = window->details->original_files; + if (uris == NULL) { + return; + } + vbox = caja_mime_application_chooser_new_for_multiple_files (uris, mime_type); + } + + gtk_widget_show (vbox); + g_free (mime_type); + + gtk_notebook_append_page (window->details->notebook, + vbox, gtk_label_new (_("Open With"))); +} + + +static FMPropertiesWindow * +create_properties_window (StartupData *startup_data) +{ + FMPropertiesWindow *window; + GList *l; + + window = FM_PROPERTIES_WINDOW (gtk_widget_new (fm_properties_window_get_type (), NULL)); + + window->details->original_files = caja_file_list_copy (startup_data->original_files); + + window->details->target_files = caja_file_list_copy (startup_data->target_files); + + gtk_window_set_wmclass (GTK_WINDOW (window), "file_properties", "Caja"); + gtk_window_set_screen (GTK_WINDOW (window), + gtk_widget_get_screen (startup_data->parent_widget)); + + gtk_window_set_type_hint (GTK_WINDOW (window), GDK_WINDOW_TYPE_HINT_DIALOG); + + /* Set initial window title */ + update_properties_window_title (window); + + /* Start monitoring the file attributes we display. Note that some + * of the attributes are for the original file, and some for the + * target files. + */ + + for (l = window->details->original_files; l != NULL; l = l->next) { + CajaFile *file; + CajaFileAttributes attributes; + + file = CAJA_FILE (l->data); + + attributes = + CAJA_FILE_ATTRIBUTES_FOR_ICON | + CAJA_FILE_ATTRIBUTE_INFO | + CAJA_FILE_ATTRIBUTE_LINK_INFO; + + caja_file_monitor_add (CAJA_FILE (l->data), + &window->details->original_files, + attributes); + } + + for (l = window->details->target_files; l != NULL; l = l->next) { + CajaFile *file; + CajaFileAttributes attributes; + + file = CAJA_FILE (l->data); + + attributes = 0; + if (caja_file_is_directory (file)) { + attributes |= CAJA_FILE_ATTRIBUTE_DEEP_COUNTS; + } + + attributes |= CAJA_FILE_ATTRIBUTE_INFO; + caja_file_monitor_add (file, &window->details->target_files, attributes); + } + + for (l = window->details->target_files; l != NULL; l = l->next) { + g_signal_connect_object (CAJA_FILE (l->data), + "changed", + G_CALLBACK (file_changed_callback), + G_OBJECT (window), + 0); + } + + for (l = window->details->original_files; l != NULL; l = l->next) { + g_signal_connect_object (CAJA_FILE (l->data), + "changed", + G_CALLBACK (file_changed_callback), + G_OBJECT (window), + 0); + } + + /* Create the notebook tabs. */ + window->details->notebook = GTK_NOTEBOOK (gtk_notebook_new ()); + gtk_widget_show (GTK_WIDGET (window->details->notebook)); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (window))), + GTK_WIDGET (window->details->notebook), + TRUE, TRUE, 0); + + /* Create the pages. */ + create_basic_page (window); + + if (should_show_emblems (window)) { + create_emblems_page (window); + } + + if (should_show_permissions (window)) { + create_permissions_page (window); + } + + if (should_show_open_with (window)) { + create_open_with_page (window); + } + + /* append pages from available views */ + append_extension_pages (window); + + gtk_dialog_add_buttons (GTK_DIALOG (window), + GTK_STOCK_HELP, GTK_RESPONSE_HELP, + GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, + NULL); + + /* FIXME - HIGificiation, should be done inside GTK+ */ + gtk_widget_ensure_style (GTK_WIDGET (window)); + gtk_container_set_border_width (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (window))), 12); + gtk_container_set_border_width (GTK_CONTAINER (gtk_dialog_get_action_area (GTK_DIALOG (window))), 0); + gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (window))), 12); + gtk_dialog_set_has_separator (GTK_DIALOG (window), FALSE); + + /* Update from initial state */ + properties_window_update (window, NULL); + + return window; +} + +static GList * +get_target_file_list (GList *original_files) +{ + GList *ret; + GList *l; + + ret = NULL; + + for (l = original_files; l != NULL; l = l->next) { + CajaFile *target; + + target = get_target_file_for_original_file (CAJA_FILE (l->data)); + + ret = g_list_prepend (ret, target); + } + + ret = g_list_reverse (ret); + + return ret; +} + +static void +add_window (FMPropertiesWindow *window) +{ + if (!is_multi_file_window (window)) { + g_hash_table_insert (windows, + get_original_file (window), + window); + g_object_set_data (G_OBJECT (window), "window_key", + get_original_file (window)); + } +} + +static void +remove_window (FMPropertiesWindow *window) +{ + gpointer key; + + key = g_object_get_data (G_OBJECT (window), "window_key"); + if (key) { + g_hash_table_remove (windows, key); + } +} + +static GtkWindow * +get_existing_window (GList *file_list) +{ + if (!file_list->next) { + return g_hash_table_lookup (windows, file_list->data); + } + + return NULL; +} + +static void +cancel_create_properties_window_callback (gpointer callback_data) +{ + remove_pending ((StartupData *)callback_data, TRUE, FALSE, TRUE); +} + +static void +parent_widget_destroyed_callback (GtkWidget *widget, gpointer callback_data) +{ + g_assert (widget == ((StartupData *)callback_data)->parent_widget); + + remove_pending ((StartupData *)callback_data, TRUE, TRUE, FALSE); +} + +static void +cancel_call_when_ready_callback (gpointer key, + gpointer value, + gpointer user_data) +{ + caja_file_cancel_call_when_ready + (CAJA_FILE (key), + is_directory_ready_callback, + user_data); +} + +static void +remove_pending (StartupData *startup_data, + gboolean cancel_call_when_ready, + gboolean cancel_timed_wait, + gboolean cancel_destroy_handler) +{ + if (cancel_call_when_ready) { + g_hash_table_foreach (startup_data->pending_files, + cancel_call_when_ready_callback, + startup_data); + + } + if (cancel_timed_wait) { + eel_timed_wait_stop + (cancel_create_properties_window_callback, startup_data); + } + if (cancel_destroy_handler) { + g_signal_handlers_disconnect_by_func (startup_data->parent_widget, + G_CALLBACK (parent_widget_destroyed_callback), + startup_data); + } + + g_hash_table_remove (pending_lists, startup_data->pending_key); + + startup_data_free (startup_data); +} + +static void +is_directory_ready_callback (CajaFile *file, + gpointer data) +{ + StartupData *startup_data; + + startup_data = data; + + g_hash_table_remove (startup_data->pending_files, file); + + if (g_hash_table_size (startup_data->pending_files) == 0) { + FMPropertiesWindow *new_window; + + new_window = create_properties_window (startup_data); + + add_window (new_window); + + remove_pending (startup_data, FALSE, TRUE, TRUE); + +/* FIXME bugzilla.gnome.org 42151: + * See comment elsewhere in this file about bug 2151. + */ +#ifdef UNDO_ENABLED + caja_undo_share_undo_manager (GTK_OBJECT (new_window), + GTK_OBJECT (callback_data)); +#endif + gtk_window_present (GTK_WINDOW (new_window)); + } +} + + +void +fm_properties_window_present (GList *original_files, + GtkWidget *parent_widget) +{ + GList *l, *next; + GtkWidget *parent_window; + StartupData *startup_data; + GList *target_files; + GtkWindow *existing_window; + char *pending_key; + + g_return_if_fail (original_files != NULL); + g_return_if_fail (GTK_IS_WIDGET (parent_widget)); + + /* Create the hash tables first time through. */ + if (windows == NULL) { + windows = eel_g_hash_table_new_free_at_exit + (NULL, NULL, "property windows"); + } + + if (pending_lists == NULL) { + pending_lists = eel_g_hash_table_new_free_at_exit + (g_str_hash, g_str_equal, "pending property window files"); + } + + /* Look to see if there's already a window for this file. */ + existing_window = get_existing_window (original_files); + if (existing_window != NULL) { + gtk_window_set_screen (existing_window, + gtk_widget_get_screen (parent_widget)); + gtk_window_present (existing_window); + return; + } + + + pending_key = get_pending_key (original_files); + + /* Look to see if we're already waiting for a window for this file. */ + if (g_hash_table_lookup (pending_lists, pending_key) != NULL) { + return; + } + + target_files = get_target_file_list (original_files); + + startup_data = startup_data_new (original_files, + target_files, + pending_key, + parent_widget); + + caja_file_list_free (target_files); + g_free(pending_key); + + /* Wait until we can tell whether it's a directory before showing, since + * some one-time layout decisions depend on that info. + */ + + g_hash_table_insert (pending_lists, startup_data->pending_key, startup_data->pending_key); + g_signal_connect (parent_widget, "destroy", + G_CALLBACK (parent_widget_destroyed_callback), startup_data); + + parent_window = gtk_widget_get_ancestor (parent_widget, GTK_TYPE_WINDOW); + + eel_timed_wait_start + (cancel_create_properties_window_callback, + startup_data, + _("Creating Properties window."), + parent_window == NULL ? NULL : GTK_WINDOW (parent_window)); + + + for (l = startup_data->target_files; l != NULL; l = next) { + next = l->next; + caja_file_call_when_ready + (CAJA_FILE (l->data), + CAJA_FILE_ATTRIBUTE_INFO, + is_directory_ready_callback, + startup_data); + } +} + +static void +real_response (GtkDialog *dialog, + int response) +{ + GError *error = NULL; + + switch (response) { + case GTK_RESPONSE_HELP: + gtk_show_uri (gtk_window_get_screen (GTK_WINDOW (dialog)), + "ghelp:user-guide#goscaja-51", + gtk_get_current_event_time (), + &error); + if (error != NULL) { + eel_show_error_dialog (_("There was an error displaying help."), error->message, + GTK_WINDOW (dialog)); + g_error_free (error); + } + break; + + case GTK_RESPONSE_NONE: + case GTK_RESPONSE_CLOSE: + case GTK_RESPONSE_DELETE_EVENT: + gtk_widget_destroy (GTK_WIDGET (dialog)); + break; + + default: + g_assert_not_reached (); + break; + } +} + +static void +real_destroy (GtkObject *object) +{ + FMPropertiesWindow *window; + GList *l; + + window = FM_PROPERTIES_WINDOW (object); + + remove_window (window); + + for (l = window->details->original_files; l != NULL; l = l->next) { + caja_file_monitor_remove (CAJA_FILE (l->data), &window->details->original_files); + } + caja_file_list_free (window->details->original_files); + window->details->original_files = NULL; + + for (l = window->details->target_files; l != NULL; l = l->next) { + caja_file_monitor_remove (CAJA_FILE (l->data), &window->details->target_files); + } + caja_file_list_free (window->details->target_files); + window->details->target_files = NULL; + + caja_file_list_free (window->details->changed_files); + window->details->changed_files = NULL; + + window->details->name_field = NULL; + + g_list_free (window->details->emblem_buttons); + window->details->emblem_buttons = NULL; + + if (window->details->initial_emblems) { + g_hash_table_destroy (window->details->initial_emblems); + window->details->initial_emblems = NULL; + } + + g_list_free (window->details->permission_buttons); + window->details->permission_buttons = NULL; + + g_list_free (window->details->permission_combos); + window->details->permission_combos = NULL; + + if (window->details->initial_permissions) { + g_hash_table_destroy (window->details->initial_permissions); + window->details->initial_permissions = NULL; + } + + g_list_free (window->details->value_fields); + window->details->value_fields = NULL; + + if (window->details->update_directory_contents_timeout_id != 0) { + g_source_remove (window->details->update_directory_contents_timeout_id); + window->details->update_directory_contents_timeout_id = 0; + } + + if (window->details->update_files_timeout_id != 0) { + g_source_remove (window->details->update_files_timeout_id); + window->details->update_files_timeout_id = 0; + } + + GTK_OBJECT_CLASS (parent_class)->destroy (object); +} + +static void +real_finalize (GObject *object) +{ + FMPropertiesWindow *window; + + window = FM_PROPERTIES_WINDOW (object); + + eel_g_list_free_deep (window->details->mime_list); + + g_free (window->details->pending_name); + g_free (window->details); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +/* converts + * file://foo/foobar/foofoo/bar + * to + * foofoo/bar + * if + * file://foo/foobar + * is the parent + * + * It does not resolve any symlinks. + * */ +static char * +make_relative_uri_from_full (const char *uri, + const char *base_uri) +{ + g_assert (uri != NULL); + g_assert (base_uri != NULL); + + if (g_str_has_prefix (uri, base_uri)) { + uri += strlen (base_uri); + if (*uri != '/') { + return NULL; + } + + while (*uri == '/') { + uri++; + } + + if (*uri != '\0') { + return g_strdup (uri); + } + } + + return NULL; +} + +/* icon selection callback to set the image of the file object to the selected file */ +static void +set_icon (const char* icon_uri, FMPropertiesWindow *properties_window) +{ + CajaFile *file; + char *file_uri; + char *icon_path; + char *real_icon_uri; + + g_assert (icon_uri != NULL); + g_assert (FM_IS_PROPERTIES_WINDOW (properties_window)); + + icon_path = g_filename_from_uri (icon_uri, NULL, NULL); + /* we don't allow remote URIs */ + if (icon_path != NULL) { + GList *l; + + for (l = properties_window->details->original_files; l != NULL; l = l->next) { + file = CAJA_FILE (l->data); + + file_uri = caja_file_get_uri (file); + + if (caja_file_is_mime_type (file, "application/x-desktop")) { + if (caja_link_local_set_icon (file_uri, icon_path)) { + caja_file_invalidate_attributes (file, + CAJA_FILE_ATTRIBUTE_INFO | + CAJA_FILE_ATTRIBUTE_LINK_INFO); + } + } else { + real_icon_uri = make_relative_uri_from_full (icon_uri, file_uri); + if (real_icon_uri == NULL) { + real_icon_uri = g_strdup (icon_uri); + } + + caja_file_set_metadata (file, CAJA_METADATA_KEY_CUSTOM_ICON, NULL, real_icon_uri); + caja_file_set_metadata (file, CAJA_METADATA_KEY_ICON_SCALE, NULL, NULL); + + g_free (real_icon_uri); + } + + g_free (file_uri); + } + + g_free (icon_path); + } +} + +static void +update_preview_callback (GtkFileChooser *icon_chooser, + FMPropertiesWindow *window) +{ + GtkWidget *preview_widget; + GdkPixbuf *pixbuf, *scaled_pixbuf; + char *filename; + double scale; + + pixbuf = NULL; + + filename = gtk_file_chooser_get_filename (icon_chooser); + if (filename != NULL) { + pixbuf = gdk_pixbuf_new_from_file (filename, NULL); + } + + if (pixbuf != NULL) { + preview_widget = gtk_file_chooser_get_preview_widget (icon_chooser); + gtk_file_chooser_set_preview_widget_active (icon_chooser, TRUE); + + if (gdk_pixbuf_get_width (pixbuf) > PREVIEW_IMAGE_WIDTH) { + scale = (double)gdk_pixbuf_get_height (pixbuf) / + gdk_pixbuf_get_width (pixbuf); + + scaled_pixbuf = mate_desktop_thumbnail_scale_down_pixbuf + (pixbuf, + PREVIEW_IMAGE_WIDTH, + scale * PREVIEW_IMAGE_WIDTH); + g_object_unref (pixbuf); + pixbuf = scaled_pixbuf; + } + + gtk_image_set_from_pixbuf (GTK_IMAGE (preview_widget), pixbuf); + } else { + gtk_file_chooser_set_preview_widget_active (icon_chooser, FALSE); + } + + g_free (filename); + + if (pixbuf != NULL) { + g_object_unref (pixbuf); + } +} + +static void +custom_icon_file_chooser_response_cb (GtkDialog *dialog, + gint response, + FMPropertiesWindow *window) +{ + char *uri; + + switch (response) { + case GTK_RESPONSE_NO: + reset_icon (window); + break; + + case GTK_RESPONSE_OK: + uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog)); + set_icon (uri, window); + g_free (uri); + break; + + default: + break; + } + + gtk_widget_hide (GTK_WIDGET (dialog)); +} + +static void +select_image_button_callback (GtkWidget *widget, + FMPropertiesWindow *window) +{ + GtkWidget *dialog, *preview; + GtkFileFilter *filter; + GList *l; + CajaFile *file; + char *uri; + char *image_path; + gboolean revert_is_sensitive; + + g_assert (FM_IS_PROPERTIES_WINDOW (window)); + + dialog = window->details->icon_chooser; + + if (dialog == NULL) { + dialog = gtk_file_chooser_dialog_new (_("Select Custom Icon"), GTK_WINDOW (window), + GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_REVERT_TO_SAVED, GTK_RESPONSE_NO, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_OK, + NULL); + gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (dialog), "/usr/share/pixmaps", NULL); + gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE); + + filter = gtk_file_filter_new (); + gtk_file_filter_add_pixbuf_formats (filter); + gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), filter); + + preview = gtk_image_new (); + gtk_widget_set_size_request (preview, PREVIEW_IMAGE_WIDTH, -1); + gtk_file_chooser_set_preview_widget (GTK_FILE_CHOOSER (dialog), preview); + gtk_file_chooser_set_use_preview_label (GTK_FILE_CHOOSER (dialog), FALSE); + gtk_file_chooser_set_preview_widget_active (GTK_FILE_CHOOSER (dialog), FALSE); + + g_signal_connect (dialog, "update-preview", + G_CALLBACK (update_preview_callback), window); + + window->details->icon_chooser = dialog; + + g_object_add_weak_pointer (G_OBJECT (dialog), + (gpointer *) &window->details->icon_chooser); + } + + /* it's likely that the user wants to pick an icon that is inside a local directory */ + if (g_list_length (window->details->original_files) == 1) { + file = CAJA_FILE (window->details->original_files->data); + + if (caja_file_is_directory (file)) { + uri = caja_file_get_uri (file); + + image_path = g_filename_from_uri (uri, NULL, NULL); + if (image_path != NULL) { + gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), image_path); + g_free (image_path); + } + + g_free (uri); + } + } + + revert_is_sensitive = FALSE; + for (l = window->details->original_files; l != NULL; l = l->next) { + file = CAJA_FILE (l->data); + image_path = caja_file_get_metadata (file, CAJA_METADATA_KEY_CUSTOM_ICON, NULL); + revert_is_sensitive = (image_path != NULL); + g_free (image_path); + + if (revert_is_sensitive) { + break; + } + } + gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_NO, revert_is_sensitive); + + g_signal_connect (dialog, "response", + G_CALLBACK (custom_icon_file_chooser_response_cb), window); + gtk_widget_show (dialog); +} + +static void +fm_properties_window_class_init (FMPropertiesWindowClass *class) +{ + GtkBindingSet *binding_set; + + G_OBJECT_CLASS (class)->finalize = real_finalize; + GTK_OBJECT_CLASS (class)->destroy = real_destroy; + GTK_DIALOG_CLASS (class)->response = real_response; + + binding_set = gtk_binding_set_by_class (class); + gtk_binding_entry_add_signal (binding_set, GDK_Escape, 0, + "close", 0); +} + +static void +fm_properties_window_init (FMPropertiesWindow *window) +{ + window->details = g_new0 (FMPropertiesWindowDetails, 1); +} diff --git a/src/file-manager/fm-properties-window.h b/src/file-manager/fm-properties-window.h new file mode 100644 index 00000000..6044e8b9 --- /dev/null +++ b/src/file-manager/fm-properties-window.h @@ -0,0 +1,69 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-properties-window.h - interface for window that lets user modify + icon properties + + 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: Darin Adler <[email protected]> +*/ + +#ifndef FM_PROPERTIES_WINDOW_H +#define FM_PROPERTIES_WINDOW_H + +#include <gtk/gtk.h> +#include <libcaja-private/caja-file.h> + +typedef struct FMPropertiesWindow FMPropertiesWindow; + +#define FM_TYPE_PROPERTIES_WINDOW fm_properties_window_get_type() +#define FM_PROPERTIES_WINDOW(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_PROPERTIES_WINDOW, FMPropertiesWindow)) +#define FM_PROPERTIES_WINDOW_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_PROPERTIES_WINDOW, FMPropertiesWindowClass)) +#define FM_IS_PROPERTIES_WINDOW(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_PROPERTIES_WINDOW)) +#define FM_IS_PROPERTIES_WINDOW_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_PROPERTIES_WINDOW)) +#define FM_PROPERTIES_WINDOW_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_PROPERTIES_WINDOW, FMPropertiesWindowClass)) + +typedef struct FMPropertiesWindowDetails FMPropertiesWindowDetails; + +struct FMPropertiesWindow +{ + GtkDialog window; + FMPropertiesWindowDetails *details; +}; + +struct FMPropertiesWindowClass +{ + GtkDialogClass parent_class; + + /* Keybinding signals */ + void (* close) (FMPropertiesWindow *window); +}; + +typedef struct FMPropertiesWindowClass FMPropertiesWindowClass; + +GType fm_properties_window_get_type (void); + +void fm_properties_window_present (GList *files, + GtkWidget *parent_widget); + +#endif /* FM_PROPERTIES_WINDOW_H */ diff --git a/src/file-manager/fm-tree-model.c b/src/file-manager/fm-tree-model.c new file mode 100644 index 00000000..6356db90 --- /dev/null +++ b/src/file-manager/fm-tree-model.c @@ -0,0 +1,2152 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* + * Copyright C) 2000, 2001 Eazel, Inc + * Copyright (C) 2002 Anders Carlsson + * Copyright (C) 2002 Bent Spoon Software + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * 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: Anders Carlsson <[email protected]> + * Darin Adler <[email protected]> + */ + +/* fm-tree-model.c - model for the tree view */ + +#include <config.h> +#include "fm-tree-model.h" + +#include <eel/eel-glib-extensions.h> +#include <eel/eel-gdk-pixbuf-extensions.h> +#include <glib/gi18n.h> +#include <libcaja-private/caja-directory.h> +#include <libcaja-private/caja-file-attributes.h> +#include <libcaja-private/caja-file.h> +#include <gtk/gtk.h> +#include <string.h> + +enum +{ + ROW_LOADED, + LAST_SIGNAL +}; + +static guint tree_model_signals[LAST_SIGNAL] = { 0 }; + +typedef gboolean (* FilePredicate) (CajaFile *); + +/* The user_data of the GtkTreeIter is the TreeNode pointer. + * It's NULL for the dummy node. If it's NULL, then user_data2 + * is the TreeNode pointer to the parent. + */ + +typedef struct TreeNode TreeNode; +typedef struct FMTreeModelRoot FMTreeModelRoot; + +struct TreeNode +{ + /* part of this node for the file itself */ + int ref_count; + + CajaFile *file; + char *display_name; + GIcon *icon; + GMount *mount; + GdkPixbuf *closed_pixbuf; + GdkPixbuf *open_pixbuf; + GdkPixbuf *emblem_pixbuf; + + FMTreeModelRoot *root; + + TreeNode *parent; + TreeNode *next; + TreeNode *prev; + + /* part of the node used only for directories */ + int dummy_child_ref_count; + int all_children_ref_count; + + CajaDirectory *directory; + guint done_loading_id; + guint files_added_id; + guint files_changed_id; + + TreeNode *first_child; + + /* misc. flags */ + guint done_loading : 1; + guint force_has_dummy : 1; + guint inserted : 1; +}; + +struct FMTreeModelDetails +{ + int stamp; + + TreeNode *root_node; + + guint monitoring_update_idle_id; + + gboolean show_hidden_files; + gboolean show_backup_files; + gboolean show_only_directories; + + GList *highlighted_files; +}; + +struct FMTreeModelRoot +{ + FMTreeModel *model; + + /* separate hash table for each root node needed */ + GHashTable *file_to_node_map; + + TreeNode *root_node; +}; + +typedef struct +{ + CajaDirectory *directory; + FMTreeModel *model; +} DoneLoadingParameters; + +static void fm_tree_model_tree_model_init (GtkTreeModelIface *iface); +static void schedule_monitoring_update (FMTreeModel *model); +static void destroy_node_without_reporting (FMTreeModel *model, + TreeNode *node); +static void report_node_contents_changed (FMTreeModel *model, + TreeNode *node); + +G_DEFINE_TYPE_WITH_CODE (FMTreeModel, fm_tree_model, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL, + fm_tree_model_tree_model_init)); + +static GtkTreeModelFlags +fm_tree_model_get_flags (GtkTreeModel *tree_model) +{ + return GTK_TREE_MODEL_ITERS_PERSIST; +} + +static void +object_unref_if_not_NULL (gpointer object) +{ + if (object == NULL) + { + return; + } + g_object_unref (object); +} + +static FMTreeModelRoot * +tree_model_root_new (FMTreeModel *model) +{ + FMTreeModelRoot *root; + + root = g_new0 (FMTreeModelRoot, 1); + root->model = model; + root->file_to_node_map = g_hash_table_new (NULL, NULL); + + return root; +} + +static TreeNode * +tree_node_new (CajaFile *file, FMTreeModelRoot *root) +{ + TreeNode *node; + + node = g_new0 (TreeNode, 1); + node->file = caja_file_ref (file); + node->root = root; + return node; +} + +static void +tree_node_unparent (FMTreeModel *model, TreeNode *node) +{ + TreeNode *parent, *next, *prev; + + parent = node->parent; + next = node->next; + prev = node->prev; + + if (parent == NULL && + node == model->details->root_node) + { + /* it's the first root node -> if there is a next then let it be the first root node */ + model->details->root_node = next; + } + + if (next != NULL) + { + next->prev = prev; + } + if (prev == NULL && parent != NULL) + { + g_assert (parent->first_child == node); + parent->first_child = next; + } + else if (prev != NULL) + { + prev->next = next; + } + + node->parent = NULL; + node->next = NULL; + node->prev = NULL; + node->root = NULL; +} + +static void +tree_node_destroy (FMTreeModel *model, TreeNode *node) +{ + g_assert (node->first_child == NULL); + g_assert (node->ref_count == 0); + + tree_node_unparent (model, node); + + g_object_unref (node->file); + g_free (node->display_name); + object_unref_if_not_NULL (node->icon); + object_unref_if_not_NULL (node->closed_pixbuf); + object_unref_if_not_NULL (node->open_pixbuf); + object_unref_if_not_NULL (node->emblem_pixbuf); + + g_assert (node->done_loading_id == 0); + g_assert (node->files_added_id == 0); + g_assert (node->files_changed_id == 0); + caja_directory_unref (node->directory); + + g_free (node); +} + +static void +tree_node_parent (TreeNode *node, TreeNode *parent) +{ + TreeNode *first_child; + + g_assert (parent != NULL); + g_assert (node->parent == NULL); + g_assert (node->prev == NULL); + g_assert (node->next == NULL); + + first_child = parent->first_child; + + node->parent = parent; + node->root = parent->root; + node->next = first_child; + + if (first_child != NULL) + { + g_assert (first_child->prev == NULL); + first_child->prev = node; + } + + parent->first_child = node; +} + +static GdkPixbuf * +get_menu_icon (GIcon *icon) +{ + CajaIconInfo *info; + GdkPixbuf *pixbuf; + int size; + + size = caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU); + + info = caja_icon_info_lookup (icon, size); + pixbuf = caja_icon_info_get_pixbuf_nodefault_at_size (info, size); + g_object_unref (info); + + return pixbuf; +} + +static GdkPixbuf * +get_menu_icon_for_file (TreeNode *node, + CajaFile *file, + CajaFileIconFlags flags) +{ + CajaIconInfo *info; + GdkPixbuf *pixbuf, *retval; + gboolean highlight; + int size; + FMTreeModel *model; + + size = caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU); + + info = caja_file_get_icon (file, size, flags); + retval = caja_icon_info_get_pixbuf_nodefault_at_size (info, size); + model = node->root->model; + + highlight = (g_list_find_custom (model->details->highlighted_files, + file, (GCompareFunc) caja_file_compare_location) != NULL); + + if (highlight) + { + pixbuf = eel_gdk_pixbuf_render (retval, 1, 255, 255, 0, 0); + + if (pixbuf != NULL) + { + g_object_unref (retval); + retval = pixbuf; + } + } + + g_object_unref (info); + + return retval; +} + +static GdkPixbuf * +tree_node_get_pixbuf (TreeNode *node, + CajaFileIconFlags flags) +{ + if (node->parent == NULL) + { + return get_menu_icon (node->icon); + } + return get_menu_icon_for_file (node, node->file, flags); +} + +static gboolean +tree_node_update_pixbuf (TreeNode *node, + GdkPixbuf **pixbuf_storage, + CajaFileIconFlags flags) +{ + GdkPixbuf *pixbuf; + + if (*pixbuf_storage == NULL) + { + return FALSE; + } + pixbuf = tree_node_get_pixbuf (node, flags); + if (pixbuf == *pixbuf_storage) + { + g_object_unref (pixbuf); + return FALSE; + } + g_object_unref (*pixbuf_storage); + *pixbuf_storage = pixbuf; + return TRUE; +} + +static gboolean +tree_node_update_closed_pixbuf (TreeNode *node) +{ + return tree_node_update_pixbuf (node, &node->closed_pixbuf, 0); +} + +static gboolean +tree_node_update_open_pixbuf (TreeNode *node) +{ + return tree_node_update_pixbuf (node, &node->open_pixbuf, CAJA_FILE_ICON_FLAGS_FOR_OPEN_FOLDER); +} + +static GdkPixbuf * +tree_node_get_emblem_pixbuf_internal (TreeNode *node) +{ + GdkPixbuf *pixbuf; + GList *emblem_pixbufs; + char *emblems_to_ignore[3]; + int i; + + i = 0; + emblems_to_ignore[i++] = CAJA_FILE_EMBLEM_NAME_TRASH; + + if (node->parent && node->parent->file) + { + if (!caja_file_can_write (node->parent->file)) + { + emblems_to_ignore[i++] = CAJA_FILE_EMBLEM_NAME_CANT_WRITE; + } + } + + emblems_to_ignore[i++] = NULL; + + emblem_pixbufs = caja_file_get_emblem_pixbufs (node->file, + caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU), + TRUE, + emblems_to_ignore); + + + if (emblem_pixbufs != NULL) + { + pixbuf = g_object_ref (emblem_pixbufs->data); + } + else + { + pixbuf = NULL; + } + + eel_gdk_pixbuf_list_free (emblem_pixbufs); + + return pixbuf; +} + +static gboolean +tree_node_update_emblem_pixbuf (TreeNode *node) +{ + GdkPixbuf *pixbuf; + + if (node->emblem_pixbuf == NULL) + { + return FALSE; + } + pixbuf = tree_node_get_emblem_pixbuf_internal (node); + if (pixbuf == node->emblem_pixbuf) + { + g_object_unref (pixbuf); + return FALSE; + } + g_object_unref (node->emblem_pixbuf); + node->emblem_pixbuf = pixbuf; + return TRUE; +} + +static gboolean +tree_node_update_display_name (TreeNode *node) +{ + char *display_name; + + if (node->display_name == NULL) + { + return FALSE; + } + /* don't update root node display names */ + if (node->parent == NULL) + { + return FALSE; + } + display_name = caja_file_get_display_name (node->file); + if (strcmp (display_name, node->display_name) == 0) + { + g_free (display_name); + return FALSE; + } + g_free (node->display_name); + node->display_name = NULL; + return TRUE; +} + +static GdkPixbuf * +tree_node_get_closed_pixbuf (TreeNode *node) +{ + if (node->closed_pixbuf == NULL) + { + node->closed_pixbuf = tree_node_get_pixbuf (node, 0); + } + return node->closed_pixbuf; +} + +static GdkPixbuf * +tree_node_get_open_pixbuf (TreeNode *node) +{ + if (node->open_pixbuf == NULL) + { + node->open_pixbuf = tree_node_get_pixbuf (node, CAJA_FILE_ICON_FLAGS_FOR_OPEN_FOLDER); + } + return node->open_pixbuf; +} + +static GdkPixbuf * +tree_node_get_emblem_pixbuf (TreeNode *node) +{ + if (node->emblem_pixbuf == NULL) + { + node->emblem_pixbuf = tree_node_get_emblem_pixbuf_internal (node); + } + return node->emblem_pixbuf; +} + +static const char * +tree_node_get_display_name (TreeNode *node) +{ + if (node->display_name == NULL) + { + node->display_name = caja_file_get_display_name (node->file); + } + return node->display_name; +} + +static gboolean +tree_node_has_dummy_child (TreeNode *node) +{ + return (node->directory != NULL + && (!node->done_loading + || node->first_child == NULL + || node->force_has_dummy)) || + /* Roots always have dummy nodes if directory isn't loaded yet */ + (node->directory == NULL && node->parent == NULL); +} + +static int +tree_node_get_child_index (TreeNode *parent, TreeNode *child) +{ + int i; + TreeNode *node; + + if (child == NULL) + { + g_assert (tree_node_has_dummy_child (parent)); + return 0; + } + + i = tree_node_has_dummy_child (parent) ? 1 : 0; + for (node = parent->first_child; node != NULL; node = node->next, i++) + { + if (child == node) + { + return i; + } + } + + g_assert_not_reached (); + return 0; +} + +static gboolean +make_iter_invalid (GtkTreeIter *iter) +{ + iter->stamp = 0; + iter->user_data = NULL; + iter->user_data2 = NULL; + iter->user_data3 = NULL; + return FALSE; +} + +static gboolean +make_iter_for_node (TreeNode *node, GtkTreeIter *iter, int stamp) +{ + if (node == NULL) + { + return make_iter_invalid (iter); + } + iter->stamp = stamp; + iter->user_data = node; + iter->user_data2 = NULL; + iter->user_data3 = NULL; + return TRUE; +} + +static gboolean +make_iter_for_dummy_row (TreeNode *parent, GtkTreeIter *iter, int stamp) +{ + g_assert (tree_node_has_dummy_child (parent)); + g_assert (parent != NULL); + iter->stamp = stamp; + iter->user_data = NULL; + iter->user_data2 = parent; + iter->user_data3 = NULL; + return TRUE; +} + +static TreeNode * +get_node_from_file (FMTreeModelRoot *root, CajaFile *file) +{ + return g_hash_table_lookup (root->file_to_node_map, file); +} + +static TreeNode * +get_parent_node_from_file (FMTreeModelRoot *root, CajaFile *file) +{ + CajaFile *parent_file; + TreeNode *parent_node; + + parent_file = caja_file_get_parent (file); + parent_node = get_node_from_file (root, parent_file); + caja_file_unref (parent_file); + return parent_node; +} + +static TreeNode * +create_node_for_file (FMTreeModelRoot *root, CajaFile *file) +{ + TreeNode *node; + + g_assert (get_node_from_file (root, file) == NULL); + node = tree_node_new (file, root); + g_hash_table_insert (root->file_to_node_map, node->file, node); + return node; +} + +#ifdef LOG_REF_COUNTS + +static char * +get_node_uri (GtkTreeIter *iter) +{ + TreeNode *node, *parent; + char *parent_uri, *node_uri; + + node = iter->user_data; + if (node != NULL) + { + return caja_file_get_uri (node->file); + } + + parent = iter->user_data2; + parent_uri = caja_file_get_uri (parent->file); + node_uri = g_strconcat (parent_uri, " -- DUMMY", NULL); + g_free (parent_uri); + return node_uri; +} + +#endif + +static void +decrement_ref_count (FMTreeModel *model, TreeNode *node, int count) +{ + node->all_children_ref_count -= count; + if (node->all_children_ref_count == 0) + { + schedule_monitoring_update (model); + } +} + +static void +abandon_node_ref_count (FMTreeModel *model, TreeNode *node) +{ + if (node->parent != NULL) + { + decrement_ref_count (model, node->parent, node->ref_count); +#ifdef LOG_REF_COUNTS + if (node->ref_count != 0) + { + char *uri; + + uri = caja_file_get_uri (node->file); + g_message ("abandoning %d ref of %s, count is now %d", + node->ref_count, uri, node->parent->all_children_ref_count); + g_free (uri); + } +#endif + } + node->ref_count = 0; +} + +static void +abandon_dummy_row_ref_count (FMTreeModel *model, TreeNode *node) +{ + decrement_ref_count (model, node, node->dummy_child_ref_count); + if (node->dummy_child_ref_count != 0) + { +#ifdef LOG_REF_COUNTS + char *uri; + + uri = caja_file_get_uri (node->file); + g_message ("abandoning %d ref of %s -- DUMMY, count is now %d", + node->dummy_child_ref_count, uri, node->all_children_ref_count); + g_free (uri); +#endif + } + node->dummy_child_ref_count = 0; +} + +static void +report_row_inserted (FMTreeModel *model, GtkTreeIter *iter) +{ + GtkTreePath *path; + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter); + gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, iter); + gtk_tree_path_free (path); +} + +static void +report_row_contents_changed (FMTreeModel *model, GtkTreeIter *iter) +{ + GtkTreePath *path; + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter); + gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, iter); + gtk_tree_path_free (path); +} + +static void +report_row_has_child_toggled (FMTreeModel *model, GtkTreeIter *iter) +{ + GtkTreePath *path; + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter); + gtk_tree_model_row_has_child_toggled (GTK_TREE_MODEL (model), path, iter); + gtk_tree_path_free (path); +} + +static GtkTreePath * +get_node_path (FMTreeModel *model, TreeNode *node) +{ + GtkTreeIter iter; + + make_iter_for_node (node, &iter, model->details->stamp); + return gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); +} + +static void +report_dummy_row_inserted (FMTreeModel *model, TreeNode *parent) +{ + GtkTreeIter iter; + + if (!parent->inserted) + { + return; + } + make_iter_for_dummy_row (parent, &iter, model->details->stamp); + report_row_inserted (model, &iter); +} + +static void +report_dummy_row_deleted (FMTreeModel *model, TreeNode *parent) +{ + GtkTreeIter iter; + GtkTreePath *path; + + if (parent->inserted) + { + make_iter_for_node (parent, &iter, model->details->stamp); + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); + gtk_tree_path_append_index (path, 0); + gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path); + gtk_tree_path_free (path); + } + abandon_dummy_row_ref_count (model, parent); +} + +static void +report_node_inserted (FMTreeModel *model, TreeNode *node) +{ + GtkTreeIter iter; + + make_iter_for_node (node, &iter, model->details->stamp); + report_row_inserted (model, &iter); + node->inserted = TRUE; + + if (tree_node_has_dummy_child (node)) + { + report_dummy_row_inserted (model, node); + } + + if (node->directory != NULL || + node->parent == NULL) + { + report_row_has_child_toggled (model, &iter); + } +} + +static void +report_node_contents_changed (FMTreeModel *model, TreeNode *node) +{ + GtkTreeIter iter; + + if (!node->inserted) + { + return; + } + make_iter_for_node (node, &iter, model->details->stamp); + report_row_contents_changed (model, &iter); +} + +static void +report_node_has_child_toggled (FMTreeModel *model, TreeNode *node) +{ + GtkTreeIter iter; + + if (!node->inserted) + { + return; + } + make_iter_for_node (node, &iter, model->details->stamp); + report_row_has_child_toggled (model, &iter); +} + +static void +report_dummy_row_contents_changed (FMTreeModel *model, TreeNode *parent) +{ + GtkTreeIter iter; + + if (!parent->inserted) + { + return; + } + make_iter_for_dummy_row (parent, &iter, model->details->stamp); + report_row_contents_changed (model, &iter); +} + +static void +stop_monitoring_directory (FMTreeModel *model, TreeNode *node) +{ + CajaDirectory *directory; + + if (node->done_loading_id == 0) + { + g_assert (node->files_added_id == 0); + g_assert (node->files_changed_id == 0); + return; + } + + directory = node->directory; + + g_signal_handler_disconnect (node->directory, node->done_loading_id); + g_signal_handler_disconnect (node->directory, node->files_added_id); + g_signal_handler_disconnect (node->directory, node->files_changed_id); + + node->done_loading_id = 0; + node->files_added_id = 0; + node->files_changed_id = 0; + + caja_directory_file_monitor_remove (node->directory, model); +} + +static void +destroy_children_without_reporting (FMTreeModel *model, TreeNode *parent) +{ + while (parent->first_child != NULL) + { + destroy_node_without_reporting (model, parent->first_child); + } +} + +static void +destroy_node_without_reporting (FMTreeModel *model, TreeNode *node) +{ + abandon_node_ref_count (model, node); + stop_monitoring_directory (model, node); + node->inserted = FALSE; + destroy_children_without_reporting (model, node); + g_hash_table_remove (node->root->file_to_node_map, node->file); + tree_node_destroy (model, node); +} + +static void +destroy_node (FMTreeModel *model, TreeNode *node) +{ + TreeNode *parent; + gboolean parent_had_dummy_child; + GtkTreePath *path; + + parent = node->parent; + parent_had_dummy_child = tree_node_has_dummy_child (parent); + + path = get_node_path (model, node); + + /* Report row_deleted before actually deleting */ + gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path); + gtk_tree_path_free (path); + + destroy_node_without_reporting (model, node); + + if (tree_node_has_dummy_child (parent)) + { + if (!parent_had_dummy_child) + { + report_dummy_row_inserted (model, parent); + } + } + else + { + g_assert (!parent_had_dummy_child); + } +} + +static void +destroy_children (FMTreeModel *model, TreeNode *parent) +{ + while (parent->first_child != NULL) + { + destroy_node (model, parent->first_child); + } +} + +static void +destroy_children_by_function (FMTreeModel *model, TreeNode *parent, FilePredicate f) +{ + TreeNode *child, *next; + + for (child = parent->first_child; child != NULL; child = next) + { + next = child->next; + if (f (child->file)) + { + destroy_node (model, child); + } + else + { + destroy_children_by_function (model, child, f); + } + } +} + +static void +destroy_by_function (FMTreeModel *model, FilePredicate f) +{ + TreeNode *node; + for (node = model->details->root_node; node != NULL; node = node->next) + { + destroy_children_by_function (model, node, f); + } +} + +static gboolean +update_node_without_reporting (FMTreeModel *model, TreeNode *node) +{ + gboolean changed; + + changed = FALSE; + + if (node->directory == NULL && + (caja_file_is_directory (node->file) || node->parent == NULL)) + { + node->directory = caja_directory_get_for_file (node->file); + } + else if (node->directory != NULL && + !(caja_file_is_directory (node->file) || node->parent == NULL)) + { + stop_monitoring_directory (model, node); + destroy_children (model, node); + caja_directory_unref (node->directory); + node->directory = NULL; + } + + changed |= tree_node_update_display_name (node); + changed |= tree_node_update_closed_pixbuf (node); + changed |= tree_node_update_open_pixbuf (node); + changed |= tree_node_update_emblem_pixbuf (node); + + return changed; +} + +static void +insert_node (FMTreeModel *model, TreeNode *parent, TreeNode *node) +{ + gboolean parent_empty; + + parent_empty = parent->first_child == NULL; + if (parent_empty) + { + /* Make sure the dummy lives as we insert the new row */ + parent->force_has_dummy = TRUE; + } + + tree_node_parent (node, parent); + + update_node_without_reporting (model, node); + report_node_inserted (model, node); + + if (parent_empty) + { + parent->force_has_dummy = FALSE; + if (!tree_node_has_dummy_child (parent)) + { + /* Temporarily set this back so that row_deleted is + * sent before actually removing the dummy child */ + parent->force_has_dummy = TRUE; + report_dummy_row_deleted (model, parent); + parent->force_has_dummy = FALSE; + } + } +} + +static void +reparent_node (FMTreeModel *model, TreeNode *node) +{ + GtkTreePath *path; + TreeNode *new_parent; + + new_parent = get_parent_node_from_file (node->root, node->file); + if (new_parent == NULL || new_parent->directory == NULL) + { + destroy_node (model, node); + return; + } + + path = get_node_path (model, node); + + /* Report row_deleted before actually deleting */ + gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path); + gtk_tree_path_free (path); + + abandon_node_ref_count (model, node); + tree_node_unparent (model, node); + + insert_node (model, new_parent, node); +} + +static gboolean +should_show_file (FMTreeModel *model, CajaFile *file) +{ + gboolean should; + TreeNode *node; + + should = caja_file_should_show (file, + model->details->show_hidden_files, + model->details->show_backup_files, + TRUE); + + if (should + && model->details->show_only_directories + &&! caja_file_is_directory (file)) + { + should = FALSE; + } + + if (should && caja_file_is_gone (file)) + { + should = FALSE; + } + + for (node = model->details->root_node; node != NULL; node = node->next) + { + if (!should && node != NULL && file == node->file) + { + should = TRUE; + } + } + + return should; +} + +static void +update_node (FMTreeModel *model, TreeNode *node) +{ + gboolean had_dummy_child, has_dummy_child; + gboolean had_directory, has_directory; + gboolean changed; + + if (!should_show_file (model, node->file)) + { + destroy_node (model, node); + return; + } + + if (node->parent != NULL && node->parent->directory != NULL + && !caja_directory_contains_file (node->parent->directory, node->file)) + { + reparent_node (model, node); + return; + } + + had_dummy_child = tree_node_has_dummy_child (node); + had_directory = node->directory != NULL; + + changed = update_node_without_reporting (model, node); + + has_dummy_child = tree_node_has_dummy_child (node); + has_directory = node->directory != NULL; + + if (had_dummy_child != has_dummy_child) + { + if (has_dummy_child) + { + report_dummy_row_inserted (model, node); + } + else + { + /* Temporarily set this back so that row_deleted is + * sent before actually removing the dummy child */ + node->force_has_dummy = TRUE; + report_dummy_row_deleted (model, node); + node->force_has_dummy = FALSE; + } + } + if (had_directory != has_directory) + { + report_node_has_child_toggled (model, node); + } + + if (changed) + { + report_node_contents_changed (model, node); + } +} + +static void +process_file_change (FMTreeModelRoot *root, + CajaFile *file) +{ + TreeNode *node, *parent; + + node = get_node_from_file (root, file); + if (node != NULL) + { + update_node (root->model, node); + return; + } + + if (!should_show_file (root->model, file)) + { + return; + } + + parent = get_parent_node_from_file (root, file); + if (parent == NULL) + { + return; + } + + insert_node (root->model, parent, create_node_for_file (root, file)); +} + +static void +files_changed_callback (CajaDirectory *directory, + GList *changed_files, + gpointer callback_data) +{ + FMTreeModelRoot *root; + GList *node; + + root = (FMTreeModelRoot *) (callback_data); + + for (node = changed_files; node != NULL; node = node->next) + { + process_file_change (root, CAJA_FILE (node->data)); + } +} + +static void +set_done_loading (FMTreeModel *model, TreeNode *node, gboolean done_loading) +{ + gboolean had_dummy; + + if (node == NULL || node->done_loading == done_loading) + { + return; + } + + had_dummy = tree_node_has_dummy_child (node); + + node->done_loading = done_loading; + + if (tree_node_has_dummy_child (node)) + { + if (had_dummy) + { + report_dummy_row_contents_changed (model, node); + } + else + { + report_dummy_row_inserted (model, node); + } + } + else + { + if (had_dummy) + { + /* Temporarily set this back so that row_deleted is + * sent before actually removing the dummy child */ + node->force_has_dummy = TRUE; + report_dummy_row_deleted (model, node); + node->force_has_dummy = FALSE; + } + else + { + g_assert_not_reached (); + } + } +} + +static void +done_loading_callback (CajaDirectory *directory, + FMTreeModelRoot *root) +{ + CajaFile *file; + TreeNode *node; + GtkTreeIter iter; + + file = caja_directory_get_corresponding_file (directory); + node = get_node_from_file (root, file); + if (node == NULL) + { + /* This can happen for non-existing files as tree roots, + * since the directory <-> file object relation gets + * broken due to caja_directory_remove_file() + * getting called when i/o fails. + */ + return; + } + set_done_loading (root->model, node, TRUE); + caja_file_unref (file); + + make_iter_for_node (node, &iter, root->model->details->stamp); + g_signal_emit (root->model, + tree_model_signals[ROW_LOADED], 0, + &iter); +} + +static CajaFileAttributes +get_tree_monitor_attributes (void) +{ + CajaFileAttributes attributes; + + attributes = + CAJA_FILE_ATTRIBUTES_FOR_ICON | + CAJA_FILE_ATTRIBUTE_INFO | + CAJA_FILE_ATTRIBUTE_LINK_INFO; + + return attributes; +} + +static void +start_monitoring_directory (FMTreeModel *model, TreeNode *node) +{ + CajaDirectory *directory; + CajaFileAttributes attributes; + + if (node->done_loading_id != 0) + { + return; + } + + g_assert (node->files_added_id == 0); + g_assert (node->files_changed_id == 0); + + directory = node->directory; + + node->done_loading_id = g_signal_connect + (directory, "done_loading", + G_CALLBACK (done_loading_callback), node->root); + node->files_added_id = g_signal_connect + (directory, "files_added", + G_CALLBACK (files_changed_callback), node->root); + node->files_changed_id = g_signal_connect + (directory, "files_changed", + G_CALLBACK (files_changed_callback), node->root); + + set_done_loading (model, node, caja_directory_are_all_files_seen (directory)); + + attributes = get_tree_monitor_attributes (); + caja_directory_file_monitor_add (directory, model, + model->details->show_hidden_files, + model->details->show_backup_files, + attributes, files_changed_callback, node->root); +} + +static int +fm_tree_model_get_n_columns (GtkTreeModel *model) +{ + return FM_TREE_MODEL_NUM_COLUMNS; +} + +static GType +fm_tree_model_get_column_type (GtkTreeModel *model, int index) +{ + switch (index) + { + case FM_TREE_MODEL_DISPLAY_NAME_COLUMN: + return G_TYPE_STRING; + case FM_TREE_MODEL_CLOSED_PIXBUF_COLUMN: + return GDK_TYPE_PIXBUF; + case FM_TREE_MODEL_OPEN_PIXBUF_COLUMN: + return GDK_TYPE_PIXBUF; + case FM_TREE_MODEL_EMBLEM_PIXBUF_COLUMN: + return GDK_TYPE_PIXBUF; + case FM_TREE_MODEL_FONT_STYLE_COLUMN: + return PANGO_TYPE_STYLE; + default: + g_assert_not_reached (); + } + + return G_TYPE_INVALID; +} + +static gboolean +iter_is_valid (FMTreeModel *model, const GtkTreeIter *iter) +{ + TreeNode *node, *parent; + + if (iter->stamp != model->details->stamp) + { + return FALSE; + } + + node = iter->user_data; + parent = iter->user_data2; + if (node == NULL) + { + if (parent != NULL) + { + if (!CAJA_IS_FILE (parent->file)) + { + return FALSE; + } + if (!tree_node_has_dummy_child (parent)) + { + return FALSE; + } + } + } + else + { + if (!CAJA_IS_FILE (node->file)) + { + return FALSE; + } + if (parent != NULL) + { + return FALSE; + } + } + if (iter->user_data3 != NULL) + { + return FALSE; + } + return TRUE; +} + +static gboolean +fm_tree_model_get_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreePath *path) +{ + int *indices; + GtkTreeIter parent; + int depth, i; + + indices = gtk_tree_path_get_indices (path); + depth = gtk_tree_path_get_depth (path); + + if (! gtk_tree_model_iter_nth_child (model, iter, NULL, indices[0])) + { + return FALSE; + } + + for (i = 1; i < depth; i++) + { + parent = *iter; + + if (! gtk_tree_model_iter_nth_child (model, iter, &parent, indices[i])) + { + return FALSE; + } + } + + return TRUE; +} + +static GtkTreePath * +fm_tree_model_get_path (GtkTreeModel *model, GtkTreeIter *iter) +{ + FMTreeModel *tree_model; + TreeNode *node, *parent, *cnode; + GtkTreePath *path; + GtkTreeIter parent_iter; + int i; + + g_return_val_if_fail (FM_IS_TREE_MODEL (model), NULL); + tree_model = FM_TREE_MODEL (model); + g_return_val_if_fail (iter_is_valid (tree_model, iter), NULL); + + node = iter->user_data; + if (node == NULL) + { + parent = iter->user_data2; + if (parent == NULL) + { + return gtk_tree_path_new (); + } + } + else + { + parent = node->parent; + if (parent == NULL) + { + i = 0; + for (cnode = tree_model->details->root_node; cnode != node; cnode = cnode->next) + { + i++; + } + path = gtk_tree_path_new (); + gtk_tree_path_append_index (path, i); + return path; + } + } + + parent_iter.stamp = iter->stamp; + parent_iter.user_data = parent; + parent_iter.user_data2 = NULL; + parent_iter.user_data3 = NULL; + + path = fm_tree_model_get_path (model, &parent_iter); + + gtk_tree_path_append_index (path, tree_node_get_child_index (parent, node)); + + return path; +} + +static void +fm_tree_model_get_value (GtkTreeModel *model, GtkTreeIter *iter, int column, GValue *value) +{ + TreeNode *node, *parent; + FMTreeModel *fm_model; + + g_return_if_fail (FM_IS_TREE_MODEL (model)); + g_return_if_fail (iter_is_valid (FM_TREE_MODEL (model), iter)); + + fm_model = FM_TREE_MODEL (model); + node = iter->user_data; + + switch (column) + { + case FM_TREE_MODEL_DISPLAY_NAME_COLUMN: + g_value_init (value, G_TYPE_STRING); + if (node == NULL) + { + parent = iter->user_data2; + g_value_set_static_string (value, parent->done_loading + ? _("(Empty)") : _("Loading...")); + } + else + { + g_value_set_string (value, tree_node_get_display_name (node)); + } + break; + case FM_TREE_MODEL_CLOSED_PIXBUF_COLUMN: + g_value_init (value, GDK_TYPE_PIXBUF); + g_value_set_object (value, node == NULL ? NULL : tree_node_get_closed_pixbuf (node)); + break; + case FM_TREE_MODEL_OPEN_PIXBUF_COLUMN: + g_value_init (value, GDK_TYPE_PIXBUF); + g_value_set_object (value, node == NULL ? NULL : tree_node_get_open_pixbuf (node)); + break; + case FM_TREE_MODEL_EMBLEM_PIXBUF_COLUMN: + g_value_init (value, GDK_TYPE_PIXBUF); + g_value_set_object (value, node == NULL ? NULL : tree_node_get_emblem_pixbuf (node)); + break; + case FM_TREE_MODEL_FONT_STYLE_COLUMN: + g_value_init (value, PANGO_TYPE_STYLE); + if (node == NULL) + { + g_value_set_enum (value, PANGO_STYLE_ITALIC); + } + else + { + g_value_set_enum (value, PANGO_STYLE_NORMAL); + } + break; + default: + g_assert_not_reached (); + } +} + +static gboolean +fm_tree_model_iter_next (GtkTreeModel *model, GtkTreeIter *iter) +{ + TreeNode *node, *parent, *next; + + g_return_val_if_fail (FM_IS_TREE_MODEL (model), FALSE); + g_return_val_if_fail (iter_is_valid (FM_TREE_MODEL (model), iter), FALSE); + + node = iter->user_data; + + if (node == NULL) + { + parent = iter->user_data2; + next = parent->first_child; + } + else + { + next = node->next; + } + + return make_iter_for_node (next, iter, iter->stamp); +} + +static gboolean +fm_tree_model_iter_children (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *parent_iter) +{ + TreeNode *parent; + + g_return_val_if_fail (FM_IS_TREE_MODEL (model), FALSE); + g_return_val_if_fail (iter_is_valid (FM_TREE_MODEL (model), parent_iter), FALSE); + + parent = parent_iter->user_data; + if (parent == NULL) + { + return make_iter_invalid (iter); + } + + if (tree_node_has_dummy_child (parent)) + { + return make_iter_for_dummy_row (parent, iter, parent_iter->stamp); + } + return make_iter_for_node (parent->first_child, iter, parent_iter->stamp); +} + +static gboolean +fm_tree_model_iter_parent (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *child_iter) +{ + TreeNode *child, *parent; + + g_return_val_if_fail (FM_IS_TREE_MODEL (model), FALSE); + g_return_val_if_fail (iter_is_valid (FM_TREE_MODEL (model), child_iter), FALSE); + + child = child_iter->user_data; + + if (child == NULL) + { + parent = child_iter->user_data2; + } + else + { + parent = child->parent; + } + + return make_iter_for_node (parent, iter, child_iter->stamp); +} + +static gboolean +fm_tree_model_iter_has_child (GtkTreeModel *model, GtkTreeIter *iter) +{ + gboolean has_child; + TreeNode *node; + + g_return_val_if_fail (FM_IS_TREE_MODEL (model), FALSE); + g_return_val_if_fail (iter_is_valid (FM_TREE_MODEL (model), iter), FALSE); + + node = iter->user_data; + + has_child = node != NULL && (node->directory != NULL || node->parent == NULL); + +#if 0 + g_warning ("Node '%s' %s", + node && node->file ? caja_file_get_uri (node->file) : "no name", + has_child ? "has child" : "no child"); +#endif + + return has_child; +} + +static int +fm_tree_model_iter_n_children (GtkTreeModel *model, GtkTreeIter *iter) +{ + FMTreeModel *tree_model; + TreeNode *parent, *node; + int n; + + g_return_val_if_fail (FM_IS_TREE_MODEL (model), FALSE); + g_return_val_if_fail (iter == NULL || iter_is_valid (FM_TREE_MODEL (model), iter), FALSE); + + tree_model = FM_TREE_MODEL (model); + + if (iter == NULL) + { + return 1; + } + + parent = iter->user_data; + if (parent == NULL) + { + return 0; + } + + n = tree_node_has_dummy_child (parent) ? 1 : 0; + for (node = parent->first_child; node != NULL; node = node->next) + { + n++; + } + + return n; +} + +static gboolean +fm_tree_model_iter_nth_child (GtkTreeModel *model, GtkTreeIter *iter, + GtkTreeIter *parent_iter, int n) +{ + FMTreeModel *tree_model; + TreeNode *parent, *node; + int i; + + g_return_val_if_fail (FM_IS_TREE_MODEL (model), FALSE); + g_return_val_if_fail (parent_iter == NULL + || iter_is_valid (FM_TREE_MODEL (model), parent_iter), FALSE); + + tree_model = FM_TREE_MODEL (model); + + if (parent_iter == NULL) + { + node = tree_model->details->root_node; + for (i = 0; i < n && node != NULL; i++, node = node->next); + return make_iter_for_node (node, iter, + tree_model->details->stamp); + } + + parent = parent_iter->user_data; + if (parent == NULL) + { + return make_iter_invalid (iter); + } + + i = tree_node_has_dummy_child (parent) ? 1 : 0; + if (n == 0 && i == 1) + { + return make_iter_for_dummy_row (parent, iter, parent_iter->stamp); + } + for (node = parent->first_child; i != n; i++, node = node->next) + { + if (node == NULL) + { + return make_iter_invalid (iter); + } + } + + return make_iter_for_node (node, iter, parent_iter->stamp); +} + +static void +update_monitoring (FMTreeModel *model, TreeNode *node) +{ + TreeNode *child; + + if (node->all_children_ref_count == 0) + { + stop_monitoring_directory (model, node); + destroy_children (model, node); + } + else + { + for (child = node->first_child; child != NULL; child = child->next) + { + update_monitoring (model, child); + } + start_monitoring_directory (model, node); + } +} + +static gboolean +update_monitoring_idle_callback (gpointer callback_data) +{ + FMTreeModel *model; + TreeNode *node; + + model = FM_TREE_MODEL (callback_data); + model->details->monitoring_update_idle_id = 0; + for (node = model->details->root_node; node != NULL; node = node->next) + { + update_monitoring (model, node); + } + return FALSE; +} + +static void +schedule_monitoring_update (FMTreeModel *model) +{ + if (model->details->monitoring_update_idle_id == 0) + { + model->details->monitoring_update_idle_id = + g_idle_add (update_monitoring_idle_callback, model); + } +} + +static void +stop_monitoring_directory_and_children (FMTreeModel *model, TreeNode *node) +{ + TreeNode *child; + + stop_monitoring_directory (model, node); + for (child = node->first_child; child != NULL; child = child->next) + { + stop_monitoring_directory_and_children (model, child); + } +} + +static void +stop_monitoring (FMTreeModel *model) +{ + TreeNode *node; + + for (node = model->details->root_node; node != NULL; node = node->next) + { + stop_monitoring_directory_and_children (model, node); + } +} + +static void +fm_tree_model_ref_node (GtkTreeModel *model, GtkTreeIter *iter) +{ + TreeNode *node, *parent; +#ifdef LOG_REF_COUNTS + char *uri; +#endif + + g_return_if_fail (FM_IS_TREE_MODEL (model)); + g_return_if_fail (iter_is_valid (FM_TREE_MODEL (model), iter)); + + node = iter->user_data; + if (node == NULL) + { + parent = iter->user_data2; + g_assert (parent->dummy_child_ref_count >= 0); + ++parent->dummy_child_ref_count; + } + else + { + parent = node->parent; + g_assert (node->ref_count >= 0); + ++node->ref_count; + } + + if (parent != NULL) + { + g_assert (parent->all_children_ref_count >= 0); + if (++parent->all_children_ref_count == 1) + { + if (parent->first_child == NULL) + { + parent->done_loading = FALSE; + } + schedule_monitoring_update (FM_TREE_MODEL (model)); + } +#ifdef LOG_REF_COUNTS + uri = get_node_uri (iter); + g_message ("ref of %s, count is now %d", + uri, parent->all_children_ref_count); + g_free (uri); +#endif + } +} + +static void +fm_tree_model_unref_node (GtkTreeModel *model, GtkTreeIter *iter) +{ + TreeNode *node, *parent; +#ifdef LOG_REF_COUNTS + char *uri; +#endif + + g_return_if_fail (FM_IS_TREE_MODEL (model)); + g_return_if_fail (iter_is_valid (FM_TREE_MODEL (model), iter)); + + node = iter->user_data; + if (node == NULL) + { + parent = iter->user_data2; + g_assert (parent->dummy_child_ref_count > 0); + --parent->dummy_child_ref_count; + } + else + { + parent = node->parent; + g_assert (node->ref_count > 0); + --node->ref_count; + } + + if (parent != NULL) + { + g_assert (parent->all_children_ref_count > 0); +#ifdef LOG_REF_COUNTS + uri = get_node_uri (iter); + g_message ("unref of %s, count is now %d", + uri, parent->all_children_ref_count - 1); + g_free (uri); +#endif + if (--parent->all_children_ref_count == 0) + { + schedule_monitoring_update (FM_TREE_MODEL (model)); + } + } +} + +void +fm_tree_model_add_root_uri (FMTreeModel *model, const char *root_uri, const char *display_name, GIcon *icon, GMount *mount) +{ + CajaFile *file; + TreeNode *node, *cnode; + FMTreeModelRoot *newroot; + + file = caja_file_get_by_uri (root_uri); + + newroot = tree_model_root_new (model); + node = create_node_for_file (newroot, file); + node->display_name = g_strdup (display_name); + node->icon = g_object_ref (icon); + if (mount) + { + node->mount = g_object_ref (mount); + } + newroot->root_node = node; + node->parent = NULL; + if (model->details->root_node == NULL) + { + model->details->root_node = node; + } + else + { + /* append it */ + for (cnode = model->details->root_node; cnode->next != NULL; cnode = cnode->next); + cnode->next = node; + node->prev = cnode; + } + + caja_file_unref (file); + + update_node_without_reporting (model, node); + report_node_inserted (model, node); +} + +GMount * +fm_tree_model_get_mount_for_root_node_file (FMTreeModel *model, CajaFile *file) +{ + TreeNode *node; + + for (node = model->details->root_node; node != NULL; node = node->next) + { + if (file == node->file) + { + break; + } + } + + if (node) + { + return node->mount; + } + + return NULL; +} + +void +fm_tree_model_remove_root_uri (FMTreeModel *model, const char *uri) +{ + TreeNode *node; + GtkTreePath *path; + FMTreeModelRoot *root; + CajaFile *file; + + file = caja_file_get_by_uri (uri); + for (node = model->details->root_node; node != NULL; node = node->next) + { + if (file == node->file) + { + break; + } + } + caja_file_unref (file); + + if (node) + { + /* remove the node */ + + if (node->mount) + { + g_object_unref (node->mount); + node->mount = NULL; + } + + caja_file_monitor_remove (node->file, model); + path = get_node_path (model, node); + + /* Report row_deleted before actually deleting */ + gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path); + gtk_tree_path_free (path); + + if (node->prev) + { + node->prev->next = node->next; + } + if (node->next) + { + node->next->prev = node->prev; + } + if (node == model->details->root_node) + { + model->details->root_node = node->next; + } + + /* destroy the root identifier */ + root = node->root; + destroy_node_without_reporting (model, node); + g_hash_table_destroy (root->file_to_node_map); + g_free (root); + } +} + +FMTreeModel * +fm_tree_model_new (void) +{ + FMTreeModel *model; + + model = g_object_new (FM_TYPE_TREE_MODEL, NULL); + + return model; +} + +void +fm_tree_model_set_show_hidden_files (FMTreeModel *model, + gboolean show_hidden_files) +{ + g_return_if_fail (FM_IS_TREE_MODEL (model)); + g_return_if_fail (show_hidden_files == FALSE || show_hidden_files == TRUE); + + show_hidden_files = show_hidden_files != FALSE; + if (model->details->show_hidden_files == show_hidden_files) + { + return; + } + model->details->show_hidden_files = show_hidden_files; + model->details->show_backup_files = show_hidden_files; + stop_monitoring (model); + if (!show_hidden_files) + { + destroy_by_function (model, caja_file_is_hidden_file); + } + schedule_monitoring_update (model); +} + +static gboolean +file_is_not_directory (CajaFile *file) +{ + return !caja_file_is_directory (file); +} + +void +fm_tree_model_set_show_only_directories (FMTreeModel *model, + gboolean show_only_directories) +{ + g_return_if_fail (FM_IS_TREE_MODEL (model)); + g_return_if_fail (show_only_directories == FALSE || show_only_directories == TRUE); + + show_only_directories = show_only_directories != FALSE; + if (model->details->show_only_directories == show_only_directories) + { + return; + } + model->details->show_only_directories = show_only_directories; + stop_monitoring (model); + if (show_only_directories) + { + destroy_by_function (model, file_is_not_directory); + } + schedule_monitoring_update (model); +} + +CajaFile * +fm_tree_model_iter_get_file (FMTreeModel *model, GtkTreeIter *iter) +{ + TreeNode *node; + + g_return_val_if_fail (FM_IS_TREE_MODEL (model), NULL); + g_return_val_if_fail (iter_is_valid (FM_TREE_MODEL (model), iter), NULL); + + node = iter->user_data; + return node == NULL ? NULL : caja_file_ref (node->file); +} + +/* This is used to work around some sort order stability problems + with gtktreemodelsort */ +int +fm_tree_model_iter_compare_roots (FMTreeModel *model, + GtkTreeIter *iter_a, + GtkTreeIter *iter_b) +{ + TreeNode *a, *b, *n; + + g_return_val_if_fail (FM_IS_TREE_MODEL (model), 0); + g_return_val_if_fail (iter_is_valid (model, iter_a), 0); + g_return_val_if_fail (iter_is_valid (model, iter_b), 0); + + a = iter_a->user_data; + b = iter_b->user_data; + + g_assert (a != NULL && a->parent == NULL); + g_assert (b != NULL && b->parent == NULL); + + if (a == b) + { + return 0; + } + + for (n = model->details->root_node; n != NULL; n = n->next) + { + if (n == a) + { + return -1; + } + if (n == b) + { + return 1; + } + } + g_assert_not_reached (); +} + +gboolean +fm_tree_model_iter_is_root (FMTreeModel *model, GtkTreeIter *iter) +{ + TreeNode *node; + + g_return_val_if_fail (FM_IS_TREE_MODEL (model), 0); + g_return_val_if_fail (iter_is_valid (model, iter), 0); + node = iter->user_data; + if (node == NULL) + { + return FALSE; + } + else + { + return (node->parent == NULL); + } +} + +gboolean +fm_tree_model_file_get_iter (FMTreeModel *model, + GtkTreeIter *iter, + CajaFile *file, + GtkTreeIter *current_iter) +{ + TreeNode *node, *root_node; + + if (current_iter != NULL && current_iter->user_data != NULL) + { + node = get_node_from_file (((TreeNode *) current_iter->user_data)->root, file); + return make_iter_for_node (node, iter, model->details->stamp); + } + + for (root_node = model->details->root_node; root_node != NULL; root_node = root_node->next) + { + node = get_node_from_file (root_node->root, file); + if (node != NULL) + { + return make_iter_for_node (node, iter, model->details->stamp); + } + } + return FALSE; +} + +static void +do_update_node (CajaFile *file, + FMTreeModel *model) +{ + TreeNode *root, *node = NULL; + + for (root = model->details->root_node; root != NULL; root = root->next) + { + node = get_node_from_file (root->root, file); + + if (node != NULL) + { + break; + } + } + + if (node == NULL) + { + return; + } + + update_node (model, node); +} + +void +fm_tree_model_set_highlight_for_files (FMTreeModel *model, + GList *files) +{ + GList *old_files; + + if (model->details->highlighted_files != NULL) + { + old_files = model->details->highlighted_files; + model->details->highlighted_files = NULL; + + g_list_foreach (old_files, + (GFunc) do_update_node, model); + + caja_file_list_free (old_files); + } + + if (files != NULL) + { + model->details->highlighted_files = + caja_file_list_copy (files); + g_list_foreach (model->details->highlighted_files, + (GFunc) do_update_node, model); + } +} + +static void +fm_tree_model_init (FMTreeModel *model) +{ + model->details = g_new0 (FMTreeModelDetails, 1); + + do + { + model->details->stamp = g_random_int (); + } + while (model->details->stamp == 0); +} + +static void +fm_tree_model_finalize (GObject *object) +{ + FMTreeModel *model; + TreeNode *root_node, *next_root; + FMTreeModelRoot *root; + + model = FM_TREE_MODEL (object); + + for (root_node = model->details->root_node; root_node != NULL; root_node = next_root) + { + next_root = root_node->next; + root = root_node->root; + destroy_node_without_reporting (model, root_node); + g_hash_table_destroy (root->file_to_node_map); + g_free (root); + } + + if (model->details->monitoring_update_idle_id != 0) + { + g_source_remove (model->details->monitoring_update_idle_id); + } + + if (model->details->highlighted_files != NULL) + { + caja_file_list_free (model->details->highlighted_files); + } + + g_free (model->details); + + G_OBJECT_CLASS (fm_tree_model_parent_class)->finalize (object); +} + +static void +fm_tree_model_class_init (FMTreeModelClass *class) +{ + G_OBJECT_CLASS (class)->finalize = fm_tree_model_finalize; + + tree_model_signals[ROW_LOADED] = + g_signal_new ("row_loaded", + FM_TYPE_TREE_MODEL, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (FMTreeModelClass, row_loaded), + NULL, NULL, + g_cclosure_marshal_VOID__BOXED, + G_TYPE_NONE, 1, + GTK_TYPE_TREE_ITER); +} + +static void +fm_tree_model_tree_model_init (GtkTreeModelIface *iface) +{ + iface->get_flags = fm_tree_model_get_flags; + iface->get_n_columns = fm_tree_model_get_n_columns; + iface->get_column_type = fm_tree_model_get_column_type; + iface->get_iter = fm_tree_model_get_iter; + iface->get_path = fm_tree_model_get_path; + iface->get_value = fm_tree_model_get_value; + iface->iter_next = fm_tree_model_iter_next; + iface->iter_children = fm_tree_model_iter_children; + iface->iter_has_child = fm_tree_model_iter_has_child; + iface->iter_n_children = fm_tree_model_iter_n_children; + iface->iter_nth_child = fm_tree_model_iter_nth_child; + iface->iter_parent = fm_tree_model_iter_parent; + iface->ref_node = fm_tree_model_ref_node; + iface->unref_node = fm_tree_model_unref_node; +} + + diff --git a/src/file-manager/fm-tree-model.h b/src/file-manager/fm-tree-model.h new file mode 100644 index 00000000..f74ce06c --- /dev/null +++ b/src/file-manager/fm-tree-model.h @@ -0,0 +1,104 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* + * Copyright (C) 2002 Anders Carlsson + * Copyright (C) 2002 Bent Spoon Software + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * 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]> + */ + +/* fm-tree-model.h - Model for the tree view */ + +#ifndef FM_TREE_MODEL_H +#define FM_TREE_MODEL_H + +#include <glib-object.h> +#include <gtk/gtk.h> +#include <gio/gio.h> +#include <libcaja-private/caja-file.h> + +#define FM_TYPE_TREE_MODEL fm_tree_model_get_type() +#define FM_TREE_MODEL(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_TREE_MODEL, FMTreeModel)) +#define FM_TREE_MODEL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_TREE_MODEL, FMTreeModelClass)) +#define FM_IS_TREE_MODEL(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_TREE_MODEL)) +#define FM_IS_TREE_MODEL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_TREE_MODEL)) +#define FM_TREE_MODEL_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_TREE_MODEL, FMTreeModelClass)) + +enum +{ + FM_TREE_MODEL_DISPLAY_NAME_COLUMN, + FM_TREE_MODEL_CLOSED_PIXBUF_COLUMN, + FM_TREE_MODEL_OPEN_PIXBUF_COLUMN, + FM_TREE_MODEL_EMBLEM_PIXBUF_COLUMN, + FM_TREE_MODEL_FONT_STYLE_COLUMN, + FM_TREE_MODEL_NUM_COLUMNS +}; + +typedef struct FMTreeModelDetails FMTreeModelDetails; + +typedef struct +{ + GObject parent; + FMTreeModelDetails *details; +} FMTreeModel; + +typedef struct +{ + GObjectClass parent_class; + + void (* row_loaded) (FMTreeModel *tree_model, + GtkTreeIter *iter); +} FMTreeModelClass; + +GType fm_tree_model_get_type (void); +FMTreeModel *fm_tree_model_new (void); +void fm_tree_model_set_show_hidden_files (FMTreeModel *model, + gboolean show_hidden_files); +void fm_tree_model_set_show_only_directories (FMTreeModel *model, + gboolean show_only_directories); +CajaFile * fm_tree_model_iter_get_file (FMTreeModel *model, + GtkTreeIter *iter); +void fm_tree_model_add_root_uri (FMTreeModel *model, + const char *root_uri, + const char *display_name, + GIcon *icon, + GMount *mount); +void fm_tree_model_remove_root_uri (FMTreeModel *model, + const char *root_uri); +gboolean fm_tree_model_iter_is_root (FMTreeModel *model, + GtkTreeIter *iter); +int fm_tree_model_iter_compare_roots (FMTreeModel *model, + GtkTreeIter *iter_a, + GtkTreeIter *iter_b); +gboolean fm_tree_model_file_get_iter (FMTreeModel *model, + GtkTreeIter *iter, + CajaFile *file, + GtkTreeIter *currentIter); + +GMount * fm_tree_model_get_mount_for_root_node_file +(FMTreeModel *model, + CajaFile *file); +void fm_tree_model_set_highlight_for_files (FMTreeModel *model, + GList *files); + +#endif /* FM_TREE_MODEL_H */ diff --git a/src/file-manager/fm-tree-view.c b/src/file-manager/fm-tree-view.c new file mode 100644 index 00000000..2a6cbd60 --- /dev/null +++ b/src/file-manager/fm-tree-view.c @@ -0,0 +1,1814 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* + * Copyright (C) 2000, 2001 Eazel, Inc + * Copyright (C) 2002 Anders Carlsson + * Copyright (C) 2002 Darin Adler + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * 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: + * Maciej Stachowiak <[email protected]> + * Anders Carlsson <[email protected]> + * Darin Adler <[email protected]> + */ + +/* fm-tree-view.c - tree sidebar panel + */ + +#include <config.h> +#include "fm-tree-view.h" + +#include "fm-tree-model.h" +#include "fm-properties-window.h" +#include <string.h> +#include <eel/eel-alert-dialog.h> +#include <eel/eel-glib-extensions.h> +#include <eel/eel-gtk-extensions.h> +#include <eel/eel-preferences.h> +#include <eel/eel-stock-dialogs.h> +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <gio/gio.h> +#include <libcaja-private/caja-clipboard.h> +#include <libcaja-private/caja-clipboard-monitor.h> +#include <libcaja-private/caja-desktop-icon-file.h> +#include <libcaja-private/caja-debug-log.h> +#include <libcaja-private/caja-file-attributes.h> +#include <libcaja-private/caja-file-operations.h> +#include <libcaja-private/caja-file-utilities.h> +#include <libcaja-private/caja-global-preferences.h> +#include <libcaja-private/caja-icon-names.h> +#include <libcaja-private/caja-program-choosing.h> +#include <libcaja-private/caja-tree-view-drag-dest.h> +#include <libcaja-private/caja-cell-renderer-pixbuf-emblem.h> +#include <libcaja-private/caja-sidebar-provider.h> +#include <libcaja-private/caja-module.h> +#include <libcaja-private/caja-window-info.h> +#include <libcaja-private/caja-window-slot-info.h> + +typedef struct +{ + GObject parent; +} FMTreeViewProvider; + +typedef struct +{ + GObjectClass parent; +} FMTreeViewProviderClass; + + +struct FMTreeViewDetails +{ + CajaWindowInfo *window; + GtkTreeView *tree_widget; + GtkTreeModelSort *sort_model; + FMTreeModel *child_model; + + GVolumeMonitor *volume_monitor; + + CajaFile *activation_file; + CajaWindowOpenFlags activation_flags; + + CajaTreeViewDragDest *drag_dest; + + char *selection_location; + gboolean selecting; + + guint show_selection_idle_id; + gulong clipboard_handler_id; + + GtkWidget *popup; + GtkWidget *popup_open; + GtkWidget *popup_open_in_new_window; + GtkWidget *popup_create_folder; + GtkWidget *popup_cut; + GtkWidget *popup_copy; + GtkWidget *popup_paste; + GtkWidget *popup_rename; + GtkWidget *popup_trash; + GtkWidget *popup_delete; + GtkWidget *popup_properties; + GtkWidget *popup_unmount_separator; + GtkWidget *popup_unmount; + GtkWidget *popup_eject; + CajaFile *popup_file; + guint popup_file_idle_handler; + + guint selection_changed_timer; +}; + +typedef struct +{ + GList *uris; + FMTreeView *view; +} PrependURIParameters; + +static GdkAtom copied_files_atom; +static gboolean show_delete_command_auto_value; + +static void fm_tree_view_iface_init (CajaSidebarIface *iface); +static void sidebar_provider_iface_init (CajaSidebarProviderIface *iface); +static void fm_tree_view_activate_file (FMTreeView *view, + CajaFile *file, + CajaWindowOpenFlags flags); +static GType fm_tree_view_provider_get_type (void); + +static void create_popup_menu (FMTreeView *view); + +G_DEFINE_TYPE_WITH_CODE (FMTreeView, fm_tree_view, GTK_TYPE_SCROLLED_WINDOW, + G_IMPLEMENT_INTERFACE (CAJA_TYPE_SIDEBAR, + fm_tree_view_iface_init)); +#define parent_class fm_tree_view_parent_class + +G_DEFINE_TYPE_WITH_CODE (FMTreeViewProvider, fm_tree_view_provider, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (CAJA_TYPE_SIDEBAR_PROVIDER, + sidebar_provider_iface_init)); + +static void +notify_clipboard_info (CajaClipboardMonitor *monitor, + CajaClipboardInfo *info, + FMTreeView *view) +{ + if (info != NULL && info->cut) + { + fm_tree_model_set_highlight_for_files (view->details->child_model, info->files); + } + else + { + fm_tree_model_set_highlight_for_files (view->details->child_model, NULL); + } +} + + +static gboolean +show_iter_for_file (FMTreeView *view, CajaFile *file, GtkTreeIter *iter) +{ + GtkTreeModel *model; + CajaFile *parent_file; + GtkTreeIter parent_iter; + GtkTreePath *path, *sort_path; + GtkTreeIter cur_iter; + + if (view->details->child_model == NULL) + { + return FALSE; + } + model = GTK_TREE_MODEL (view->details->child_model); + + /* check if file is visible in the same root as the currently selected folder is */ + gtk_tree_view_get_cursor (view->details->tree_widget, &path, NULL); + if (path != NULL) + { + if (gtk_tree_model_get_iter (model, &cur_iter, path) && + fm_tree_model_file_get_iter (view->details->child_model, iter, + file, &cur_iter)) + { + gtk_tree_path_free (path); + return TRUE; + } + gtk_tree_path_free (path); + } + /* check if file is visible at all */ + if (fm_tree_model_file_get_iter (view->details->child_model, + iter, file, NULL)) + { + return TRUE; + } + + parent_file = caja_file_get_parent (file); + + if (parent_file == NULL) + { + return FALSE; + } + if (!show_iter_for_file (view, parent_file, &parent_iter)) + { + caja_file_unref (parent_file); + return FALSE; + } + caja_file_unref (parent_file); + + if (parent_iter.user_data == NULL || parent_iter.stamp == 0) + { + return FALSE; + } + path = gtk_tree_model_get_path (model, &parent_iter); + sort_path = gtk_tree_model_sort_convert_child_path_to_path + (view->details->sort_model, path); + gtk_tree_path_free (path); + gtk_tree_view_expand_row (view->details->tree_widget, sort_path, FALSE); + gtk_tree_path_free (sort_path); + + return FALSE; +} + +static void +refresh_highlight (FMTreeView *view) +{ + CajaClipboardMonitor *monitor; + CajaClipboardInfo *info; + + monitor = caja_clipboard_monitor_get (); + info = caja_clipboard_monitor_get_clipboard_info (monitor); + + notify_clipboard_info (monitor, info, view); +} + +static gboolean +show_selection_idle_callback (gpointer callback_data) +{ + FMTreeView *view; + CajaFile *file, *old_file; + GtkTreeIter iter; + GtkTreePath *path, *sort_path; + + view = FM_TREE_VIEW (callback_data); + + view->details->show_selection_idle_id = 0; + + file = caja_file_get_by_uri (view->details->selection_location); + if (file == NULL) + { + return FALSE; + } + + if (!caja_file_is_directory (file)) + { + old_file = file; + file = caja_file_get_parent (file); + caja_file_unref (old_file); + if (file == NULL) + { + return FALSE; + } + } + + view->details->selecting = TRUE; + if (!show_iter_for_file (view, file, &iter)) + { + caja_file_unref (file); + return FALSE; + } + view->details->selecting = FALSE; + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (view->details->child_model), &iter); + sort_path = gtk_tree_model_sort_convert_child_path_to_path + (view->details->sort_model, path); + gtk_tree_path_free (path); + gtk_tree_view_set_cursor (view->details->tree_widget, sort_path, NULL, FALSE); + if (gtk_widget_get_realized (GTK_WIDGET (view->details->tree_widget))) + { + gtk_tree_view_scroll_to_cell (view->details->tree_widget, sort_path, NULL, FALSE, 0, 0); + } + gtk_tree_path_free (sort_path); + + caja_file_unref (file); + refresh_highlight (view); + + return FALSE; +} + +static void +schedule_show_selection (FMTreeView *view) +{ + if (view->details->show_selection_idle_id == 0) + { + view->details->show_selection_idle_id = g_idle_add (show_selection_idle_callback, view); + } +} + +static void +schedule_select_and_show_location (FMTreeView *view, char *location) +{ + if (view->details->selection_location != NULL) + { + g_free (view->details->selection_location); + } + view->details->selection_location = g_strdup (location); + schedule_show_selection (view); +} + +static void +row_loaded_callback (GtkTreeModel *tree_model, + GtkTreeIter *iter, + FMTreeView *view) +{ + CajaFile *file, *tmp_file, *selection_file; + + if (view->details->selection_location == NULL + || !view->details->selecting + || iter->user_data == NULL || iter->stamp == 0) + { + return; + } + + file = fm_tree_model_iter_get_file (view->details->child_model, iter); + if (file == NULL) + { + return; + } + if (!caja_file_is_directory (file)) + { + caja_file_unref(file); + return; + } + + /* if iter is ancestor of wanted selection_location then update selection */ + selection_file = caja_file_get_by_uri (view->details->selection_location); + while (selection_file != NULL) + { + if (file == selection_file) + { + caja_file_unref (file); + caja_file_unref (selection_file); + + schedule_show_selection (view); + return; + } + tmp_file = caja_file_get_parent (selection_file); + caja_file_unref (selection_file); + selection_file = tmp_file; + } + caja_file_unref (file); +} + +static CajaFile * +sort_model_iter_to_file (FMTreeView *view, GtkTreeIter *iter) +{ + GtkTreeIter child_iter; + + gtk_tree_model_sort_convert_iter_to_child_iter (view->details->sort_model, &child_iter, iter); + return fm_tree_model_iter_get_file (view->details->child_model, &child_iter); +} + +static CajaFile * +sort_model_path_to_file (FMTreeView *view, GtkTreePath *path) +{ + GtkTreeIter iter; + + if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (view->details->sort_model), &iter, path)) + { + return NULL; + } + return sort_model_iter_to_file (view, &iter); +} + +static void +got_activation_uri_callback (CajaFile *file, gpointer callback_data) +{ + char *uri, *file_uri; + FMTreeView *view; + GdkScreen *screen; + GFile *location; + CajaWindowSlotInfo *slot; + gboolean open_in_same_slot; + + view = FM_TREE_VIEW (callback_data); + + screen = gtk_widget_get_screen (GTK_WIDGET (view->details->tree_widget)); + + g_assert (file == view->details->activation_file); + + open_in_same_slot = + (view->details->activation_flags & + (CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW | + CAJA_WINDOW_OPEN_FLAG_NEW_TAB)) == 0; + + slot = caja_window_info_get_active_slot (view->details->window); + + uri = caja_file_get_activation_uri (file); + if (caja_file_is_launcher (file)) + { + file_uri = caja_file_get_uri (file); + caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, + "tree view launch_desktop_file window=%p: %s", + view->details->window, file_uri); + caja_launch_desktop_file (screen, file_uri, NULL, NULL); + g_free (file_uri); + } + else if (uri != NULL + && caja_file_is_executable (file) + && caja_file_can_execute (file) + && !caja_file_is_directory (file)) + { + + file_uri = g_filename_from_uri (uri, NULL, NULL); + + /* Non-local executables don't get launched. They act like non-executables. */ + if (file_uri == NULL) + { + caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, + "tree view window_info_open_location window=%p: %s", + view->details->window, uri); + location = g_file_new_for_uri (uri); + caja_window_slot_info_open_location + (slot, + location, + CAJA_WINDOW_OPEN_ACCORDING_TO_MODE, + view->details->activation_flags, + NULL); + g_object_unref (location); + } + else + { + caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, + "tree view launch_application_from_command window=%p: %s", + view->details->window, file_uri); + caja_launch_application_from_command (screen, NULL, file_uri, FALSE, NULL); + g_free (file_uri); + } + + } + else if (uri != NULL) + { + if (!open_in_same_slot || + view->details->selection_location == NULL || + strcmp (uri, view->details->selection_location) != 0) + { + if (open_in_same_slot) + { + if (view->details->selection_location != NULL) + { + g_free (view->details->selection_location); + } + view->details->selection_location = g_strdup (uri); + } + + caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, + "tree view window_info_open_location window=%p: %s", + view->details->window, uri); + location = g_file_new_for_uri (uri); + caja_window_slot_info_open_location + (slot, + location, + CAJA_WINDOW_OPEN_ACCORDING_TO_MODE, + view->details->activation_flags, + NULL); + g_object_unref (location); + } + } + + g_free (uri); + caja_file_unref (view->details->activation_file); + view->details->activation_file = NULL; +} + +static void +cancel_activation (FMTreeView *view) +{ + if (view->details->activation_file == NULL) + { + return; + } + + caja_file_cancel_call_when_ready + (view->details->activation_file, + got_activation_uri_callback, view); + caja_file_unref (view->details->activation_file); + view->details->activation_file = NULL; +} + +static void +row_activated_callback (GtkTreeView *treeview, GtkTreePath *path, + GtkTreeViewColumn *column, FMTreeView *view) +{ + if (gtk_tree_view_row_expanded (view->details->tree_widget, path)) + { + gtk_tree_view_collapse_row (view->details->tree_widget, path); + } + else + { + gtk_tree_view_expand_row (view->details->tree_widget, + path, FALSE); + } +} + +static gboolean +selection_changed_timer_callback(FMTreeView *view) +{ + CajaFileAttributes attributes; + GtkTreeIter iter; + GtkTreeSelection *selection; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view->details->tree_widget)); + + /* no activation if popup menu is open */ + if (view->details->popup_file != NULL) + { + return FALSE; + } + + cancel_activation (view); + + if (!gtk_tree_selection_get_selected (selection, NULL, &iter)) + { + return FALSE; + } + + view->details->activation_file = sort_model_iter_to_file (view, &iter); + if (view->details->activation_file == NULL) + { + return FALSE; + } + view->details->activation_flags = 0; + + attributes = CAJA_FILE_ATTRIBUTE_INFO | CAJA_FILE_ATTRIBUTE_LINK_INFO; + caja_file_call_when_ready (view->details->activation_file, attributes, + got_activation_uri_callback, view); + return FALSE; /* remove timeout */ +} + +static void +selection_changed_callback (GtkTreeSelection *selection, + FMTreeView *view) +{ + GdkEvent *event; + gboolean is_keyboard; + + if (view->details->selection_changed_timer) + { + g_source_remove (view->details->selection_changed_timer); + view->details->selection_changed_timer = 0; + } + + event = gtk_get_current_event (); + if (event) + { + is_keyboard = (event->type == GDK_KEY_PRESS || event->type == GDK_KEY_RELEASE); + gdk_event_free (event); + + if (is_keyboard) + { + /* on keyboard event: delay the change */ + /* TODO: make dependent on keyboard repeat rate as per Markus Bertheau ? */ + view->details->selection_changed_timer = g_timeout_add (300, (GSourceFunc) selection_changed_timer_callback, view); + } + else + { + /* on mouse event: show the change immediately */ + selection_changed_timer_callback (view); + } + } +} + +static int +compare_rows (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer callback_data) +{ + CajaFile *file_a, *file_b; + int result; + + /* Dummy rows are always first */ + if (a->user_data == NULL) + { + return -1; + } + else if (b->user_data == NULL) + { + return 1; + } + + /* don't sort root nodes */ + if (fm_tree_model_iter_is_root (FM_TREE_MODEL (model), a) && + fm_tree_model_iter_is_root (FM_TREE_MODEL (model), b)) + { + return fm_tree_model_iter_compare_roots (FM_TREE_MODEL (model), a, b); + } + + file_a = fm_tree_model_iter_get_file (FM_TREE_MODEL (model), a); + file_b = fm_tree_model_iter_get_file (FM_TREE_MODEL (model), b); + + if (file_a == file_b) + { + result = 0; + } + else if (file_a == NULL) + { + result = -1; + } + else if (file_b == NULL) + { + result = +1; + } + else + { + result = caja_file_compare_for_sort (file_a, file_b, + CAJA_FILE_SORT_BY_DISPLAY_NAME, + FALSE, FALSE); + } + + caja_file_unref (file_a); + caja_file_unref (file_b); + + return result; +} + + +static char * +get_root_uri_callback (CajaTreeViewDragDest *dest, + gpointer user_data) +{ + FMTreeView *view; + + view = FM_TREE_VIEW (user_data); + + /* Don't allow drops on background */ + return NULL; +} + +static CajaFile * +get_file_for_path_callback (CajaTreeViewDragDest *dest, + GtkTreePath *path, + gpointer user_data) +{ + FMTreeView *view; + + view = FM_TREE_VIEW (user_data); + + return sort_model_path_to_file (view, path); +} + +static void +move_copy_items_callback (CajaTreeViewDragDest *dest, + const GList *item_uris, + const char *target_uri, + GdkDragAction action, + int x, + int y, + gpointer user_data) +{ + FMTreeView *view; + + view = FM_TREE_VIEW (user_data); + + caja_clipboard_clear_if_colliding_uris (GTK_WIDGET (view), + item_uris, + copied_files_atom); + caja_file_operations_copy_move + (item_uris, + NULL, + target_uri, + action, + GTK_WIDGET (view->details->tree_widget), + NULL, NULL); +} + +static void +add_root_for_mount (FMTreeView *view, + GMount *mount) +{ + char *mount_uri, *name; + GFile *root; + GIcon *icon; + + if (g_mount_is_shadowed (mount)) + return; + + icon = g_mount_get_icon (mount); + root = g_mount_get_root (mount); + mount_uri = g_file_get_uri (root); + g_object_unref (root); + name = g_mount_get_name (mount); + + fm_tree_model_add_root_uri(view->details->child_model, + mount_uri, name, icon, mount); + + g_object_unref (icon); + g_free (name); + g_free (mount_uri); + +} + +static void +mount_added_callback (GVolumeMonitor *volume_monitor, + GMount *mount, + FMTreeView *view) +{ + add_root_for_mount (view, mount); +} + +static void +mount_removed_callback (GVolumeMonitor *volume_monitor, + GMount *mount, + FMTreeView *view) +{ + GFile *root; + char *mount_uri; + + root = g_mount_get_root (mount); + mount_uri = g_file_get_uri (root); + g_object_unref (root); + fm_tree_model_remove_root_uri (view->details->child_model, + mount_uri); + g_free (mount_uri); +} + +static void +clipboard_contents_received_callback (GtkClipboard *clipboard, + GtkSelectionData *selection_data, + gpointer data) +{ + FMTreeView *view; + + view = FM_TREE_VIEW (data); + + if (gtk_selection_data_get_data_type (selection_data) == copied_files_atom + && gtk_selection_data_get_length (selection_data) > 0 && + view->details->popup != NULL) + { + gtk_widget_set_sensitive (view->details->popup_paste, TRUE); + } + + g_object_unref (view); +} + +static gboolean +is_parent_writable (CajaFile *file) +{ + CajaFile *parent; + gboolean result; + + parent = caja_file_get_parent (file); + + /* No parent directory, return FALSE */ + if (parent == NULL) + { + return FALSE; + } + + result = caja_file_can_write (parent); + caja_file_unref (parent); + + return result; +} + +static gboolean +button_pressed_callback (GtkTreeView *treeview, GdkEventButton *event, + FMTreeView *view) +{ + GtkTreePath *path, *cursor_path; + gboolean parent_file_is_writable; + gboolean file_is_home_or_desktop; + gboolean file_is_special_link; + gboolean can_move_file_to_trash; + gboolean can_delete_file; + + if (event->button == 3) + { + gboolean show_unmount = FALSE; + gboolean show_eject = FALSE; + GMount *mount = NULL; + + if (view->details->popup_file != NULL) + { + return FALSE; /* Already up, ignore */ + } + + if (!gtk_tree_view_get_path_at_pos (treeview, event->x, event->y, + &path, NULL, NULL, NULL)) + { + return FALSE; + } + + view->details->popup_file = sort_model_path_to_file (view, path); + if (view->details->popup_file == NULL) + { + gtk_tree_path_free (path); + return FALSE; + } + gtk_tree_view_get_cursor (view->details->tree_widget, &cursor_path, NULL); + gtk_tree_view_set_cursor (view->details->tree_widget, path, NULL, FALSE); + gtk_tree_path_free (path); + + create_popup_menu (view); + + gtk_widget_set_sensitive (view->details->popup_open_in_new_window, + caja_file_is_directory (view->details->popup_file)); + gtk_widget_set_sensitive (view->details->popup_create_folder, + caja_file_is_directory (view->details->popup_file) && + caja_file_can_write (view->details->popup_file)); + gtk_widget_set_sensitive (view->details->popup_paste, FALSE); + if (caja_file_is_directory (view->details->popup_file) && + caja_file_can_write (view->details->popup_file)) + { + gtk_clipboard_request_contents (caja_clipboard_get (GTK_WIDGET (view->details->tree_widget)), + copied_files_atom, + clipboard_contents_received_callback, g_object_ref (view)); + } + can_move_file_to_trash = caja_file_can_trash (view->details->popup_file); + gtk_widget_set_sensitive (view->details->popup_trash, can_move_file_to_trash); + + if (show_delete_command_auto_value) + { + parent_file_is_writable = is_parent_writable (view->details->popup_file); + file_is_home_or_desktop = caja_file_is_home (view->details->popup_file) + || caja_file_is_desktop_directory (view->details->popup_file); + file_is_special_link = CAJA_IS_DESKTOP_ICON_FILE (view->details->popup_file); + + can_delete_file = parent_file_is_writable + && !file_is_home_or_desktop + && !file_is_special_link; + + gtk_widget_show (view->details->popup_delete); + gtk_widget_set_sensitive (view->details->popup_delete, can_delete_file); + } + else + { + gtk_widget_hide (view->details->popup_delete); + } + + mount = fm_tree_model_get_mount_for_root_node_file (view->details->child_model, view->details->popup_file); + if (mount) + { + show_unmount = g_mount_can_unmount (mount); + show_eject = g_mount_can_eject (mount); + } + + if (show_unmount) + { + gtk_widget_show (view->details->popup_unmount); + } + else + { + gtk_widget_hide (view->details->popup_unmount); + } + + if (show_eject) + { + gtk_widget_show (view->details->popup_eject); + } + else + { + gtk_widget_hide (view->details->popup_eject); + } + + if (show_unmount || show_eject) + { + gtk_widget_show (view->details->popup_unmount_separator); + } + else + { + gtk_widget_hide (view->details->popup_unmount_separator); + } + + gtk_menu_popup (GTK_MENU (view->details->popup), + NULL, NULL, NULL, NULL, + event->button, event->time); + + gtk_tree_view_set_cursor (view->details->tree_widget, cursor_path, NULL, FALSE); + gtk_tree_path_free (cursor_path); + + return TRUE; + } + else if (event->button == 2 && event->type == GDK_BUTTON_PRESS) + { + CajaFile *file; + + if (!gtk_tree_view_get_path_at_pos (treeview, event->x, event->y, + &path, NULL, NULL, NULL)) + { + return FALSE; + } + + file = sort_model_path_to_file (view, path); + if (file) + { + fm_tree_view_activate_file (view, file, + (event->state & GDK_CONTROL_MASK) != 0 ? + CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW : + CAJA_WINDOW_OPEN_FLAG_NEW_TAB); + caja_file_unref (file); + } + + gtk_tree_path_free (path); + + return TRUE; + } + + return FALSE; +} + +static void +fm_tree_view_activate_file (FMTreeView *view, + CajaFile *file, + CajaWindowOpenFlags flags) +{ + CajaFileAttributes attributes; + + cancel_activation (view); + + view->details->activation_file = caja_file_ref (file); + view->details->activation_flags = flags; + + attributes = CAJA_FILE_ATTRIBUTE_INFO | CAJA_FILE_ATTRIBUTE_LINK_INFO; + caja_file_call_when_ready (view->details->activation_file, attributes, + got_activation_uri_callback, view); +} + +static void +fm_tree_view_open_cb (GtkWidget *menu_item, + FMTreeView *view) +{ + fm_tree_view_activate_file (view, view->details->popup_file, 0); +} + +static void +fm_tree_view_open_in_new_tab_cb (GtkWidget *menu_item, + FMTreeView *view) +{ + fm_tree_view_activate_file (view, view->details->popup_file, CAJA_WINDOW_OPEN_FLAG_NEW_TAB); +} + +static void +fm_tree_view_open_in_new_window_cb (GtkWidget *menu_item, + FMTreeView *view) +{ + fm_tree_view_activate_file (view, view->details->popup_file, CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW); +} + +static void +new_folder_done (GFile *new_folder, gpointer data) +{ + GList *list; + + /* show the properties window for the newly created + * folder so the user can change its name + */ + list = g_list_prepend (NULL, caja_file_get (new_folder)); + + fm_properties_window_present (list, GTK_WIDGET (data)); + + caja_file_list_free (list); +} + +static void +fm_tree_view_create_folder_cb (GtkWidget *menu_item, + FMTreeView *view) +{ + char *parent_uri; + + parent_uri = caja_file_get_uri (view->details->popup_file); + caja_file_operations_new_folder (GTK_WIDGET (view->details->tree_widget), + NULL, + parent_uri, + new_folder_done, view->details->tree_widget); + + g_free (parent_uri); +} + +static void +copy_or_cut_files (FMTreeView *view, + gboolean cut) +{ + char *status_string, *name; + CajaClipboardInfo info; + GtkTargetList *target_list; + GtkTargetEntry *targets; + int n_targets; + + info.cut = cut; + info.files = g_list_prepend (NULL, view->details->popup_file); + + target_list = gtk_target_list_new (NULL, 0); + gtk_target_list_add (target_list, copied_files_atom, 0, 0); + gtk_target_list_add_uri_targets (target_list, 0); + gtk_target_list_add_text_targets (target_list, 0); + + targets = gtk_target_table_new_from_list (target_list, &n_targets); + gtk_target_list_unref (target_list); + + gtk_clipboard_set_with_data (caja_clipboard_get (GTK_WIDGET (view->details->tree_widget)), + targets, n_targets, + caja_get_clipboard_callback, caja_clear_clipboard_callback, + NULL); + gtk_target_table_free (targets, n_targets); + + caja_clipboard_monitor_set_clipboard_info (caja_clipboard_monitor_get (), + &info); + g_list_free (info.files); + + name = caja_file_get_display_name (view->details->popup_file); + if (cut) + { + status_string = g_strdup_printf (_("\"%s\" will be moved " + "if you select the Paste command"), + name); + } + else + { + status_string = g_strdup_printf (_("\"%s\" will be copied " + "if you select the Paste command"), + name); + } + g_free (name); + + caja_window_info_push_status (view->details->window, + status_string); + g_free (status_string); +} + +static void +fm_tree_view_cut_cb (GtkWidget *menu_item, + FMTreeView *view) +{ + copy_or_cut_files (view, TRUE); +} + +static void +fm_tree_view_copy_cb (GtkWidget *menu_item, + FMTreeView *view) +{ + copy_or_cut_files (view, FALSE); +} + +static void +paste_clipboard_data (FMTreeView *view, + GtkSelectionData *selection_data, + char *destination_uri) +{ + gboolean cut; + GList *item_uris; + + cut = FALSE; + item_uris = caja_clipboard_get_uri_list_from_selection_data (selection_data, &cut, + copied_files_atom); + + if (item_uris == NULL|| destination_uri == NULL) + { + caja_window_info_push_status (view->details->window, + _("There is nothing on the clipboard to paste.")); + } + else + { + caja_file_operations_copy_move + (item_uris, NULL, destination_uri, + cut ? GDK_ACTION_MOVE : GDK_ACTION_COPY, + GTK_WIDGET (view->details->tree_widget), + NULL, NULL); + + /* If items are cut then remove from clipboard */ + if (cut) + { + gtk_clipboard_clear (caja_clipboard_get (GTK_WIDGET (view))); + } + + eel_g_list_free_deep (item_uris); + } +} + +static void +paste_into_clipboard_received_callback (GtkClipboard *clipboard, + GtkSelectionData *selection_data, + gpointer data) +{ + FMTreeView *view; + char *directory_uri; + + view = FM_TREE_VIEW (data); + + directory_uri = caja_file_get_uri (view->details->popup_file); + + paste_clipboard_data (view, selection_data, directory_uri); + + g_free (directory_uri); +} + +static void +fm_tree_view_paste_cb (GtkWidget *menu_item, + FMTreeView *view) +{ + gtk_clipboard_request_contents (caja_clipboard_get (GTK_WIDGET (view->details->tree_widget)), + copied_files_atom, + paste_into_clipboard_received_callback, view); +} + +static GtkWindow * +fm_tree_view_get_containing_window (FMTreeView *view) +{ + GtkWidget *window; + + g_assert (FM_IS_TREE_VIEW (view)); + + window = gtk_widget_get_ancestor (GTK_WIDGET (view), GTK_TYPE_WINDOW); + if (window == NULL) + { + return NULL; + } + + return GTK_WINDOW (window); +} + +static void +fm_tree_view_trash_cb (GtkWidget *menu_item, + FMTreeView *view) +{ + GList *list; + + if (!caja_file_can_trash (view->details->popup_file)) + { + return; + } + + list = g_list_prepend (NULL, + caja_file_get_location (view->details->popup_file)); + + caja_file_operations_trash_or_delete (list, + fm_tree_view_get_containing_window (view), + NULL, NULL); + eel_g_object_list_free (list); +} + +static void +fm_tree_view_delete_cb (GtkWidget *menu_item, + FMTreeView *view) +{ + GList *location_list; + + if (!show_delete_command_auto_value) + { + return; + } + + location_list = g_list_prepend (NULL, + caja_file_get_location (view->details->popup_file)); + + caja_file_operations_delete (location_list, fm_tree_view_get_containing_window (view), NULL, NULL); + eel_g_object_list_free (location_list); +} + +static void +fm_tree_view_properties_cb (GtkWidget *menu_item, + FMTreeView *view) +{ + GList *list; + + list = g_list_prepend (NULL, caja_file_ref (view->details->popup_file)); + + fm_properties_window_present (list, GTK_WIDGET (view->details->tree_widget)); + + caja_file_list_free (list); +} + +static void +fm_tree_view_unmount_cb (GtkWidget *menu_item, + FMTreeView *view) +{ + CajaFile *file = view->details->popup_file; + GMount *mount; + + if (file == NULL) + { + return; + } + + mount = fm_tree_model_get_mount_for_root_node_file (view->details->child_model, file); + + if (mount != NULL) + { + caja_file_operations_unmount_mount (fm_tree_view_get_containing_window (view), + mount, FALSE, TRUE); + } +} + +static void +fm_tree_view_eject_cb (GtkWidget *menu_item, + FMTreeView *view) +{ + CajaFile *file = view->details->popup_file; + GMount *mount; + + if (file == NULL) + { + return; + } + + mount = fm_tree_model_get_mount_for_root_node_file (view->details->child_model, file); + + if (mount != NULL) + { + caja_file_operations_unmount_mount (fm_tree_view_get_containing_window (view), + mount, TRUE, TRUE); + } +} + +static gboolean +free_popup_file_in_idle_cb (gpointer data) +{ + FMTreeView *view; + + view = FM_TREE_VIEW (data); + + if (view->details->popup_file != NULL) + { + caja_file_unref (view->details->popup_file); + view->details->popup_file = NULL; + } + view->details->popup_file_idle_handler = 0; + return FALSE; +} + +static void +popup_menu_deactivated (GtkMenuShell *menu_shell, gpointer data) +{ + FMTreeView *view; + + view = FM_TREE_VIEW (data); + + /* The popup menu is deactivated. (I.E. hidden) + We want to free popup_file, but can't right away as it might immediately get + used if we're deactivation due to activating a menu item. So, we free it in + idle */ + + if (view->details->popup_file != NULL && + view->details->popup_file_idle_handler == 0) + { + view->details->popup_file_idle_handler = g_idle_add (free_popup_file_in_idle_cb, view); + } +} + +static void +create_popup_menu (FMTreeView *view) +{ + GtkWidget *popup, *menu_item, *menu_image; + + if (view->details->popup != NULL) + { + /* already created */ + return; + } + + popup = gtk_menu_new (); + + g_signal_connect (popup, "deactivate", + G_CALLBACK (popup_menu_deactivated), + view); + + + /* add the "open" menu item */ + menu_image = gtk_image_new_from_stock (GTK_STOCK_OPEN, + GTK_ICON_SIZE_MENU); + gtk_widget_show (menu_image); + menu_item = gtk_image_menu_item_new_with_mnemonic (_("_Open")); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item), + menu_image); + g_signal_connect (menu_item, "activate", + G_CALLBACK (fm_tree_view_open_cb), + view); + gtk_widget_show (menu_item); + gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); + view->details->popup_open = menu_item; + + /* add the "open in new tab" menu item */ + menu_item = gtk_menu_item_new_with_mnemonic (_("Open in New _Tab")); + g_signal_connect (menu_item, "activate", + G_CALLBACK (fm_tree_view_open_in_new_tab_cb), + view); + gtk_widget_show (menu_item); + gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); + view->details->popup_open_in_new_window = menu_item; + + /* add the "open in new window" menu item */ + menu_item = gtk_menu_item_new_with_mnemonic (_("Open in New _Window")); + g_signal_connect (menu_item, "activate", + G_CALLBACK (fm_tree_view_open_in_new_window_cb), + view); + gtk_widget_show (menu_item); + gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); + view->details->popup_open_in_new_window = menu_item; + + eel_gtk_menu_append_separator (GTK_MENU (popup)); + + /* add the "create folder" menu item */ + menu_item = gtk_image_menu_item_new_with_mnemonic (_("Create _Folder")); + g_signal_connect (menu_item, "activate", + G_CALLBACK (fm_tree_view_create_folder_cb), + view); + gtk_widget_show (menu_item); + gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); + view->details->popup_create_folder = menu_item; + + eel_gtk_menu_append_separator (GTK_MENU (popup)); + + /* add the "cut folder" menu item */ + menu_item = gtk_image_menu_item_new_from_stock (GTK_STOCK_CUT, NULL); + g_signal_connect (menu_item, "activate", + G_CALLBACK (fm_tree_view_cut_cb), + view); + gtk_widget_show (menu_item); + gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); + view->details->popup_cut = menu_item; + + /* add the "copy folder" menu item */ + menu_item = gtk_image_menu_item_new_from_stock (GTK_STOCK_COPY, NULL); + g_signal_connect (menu_item, "activate", + G_CALLBACK (fm_tree_view_copy_cb), + view); + gtk_widget_show (menu_item); + gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); + view->details->popup_copy = menu_item; + + /* add the "paste files into folder" menu item */ + menu_image = gtk_image_new_from_stock (GTK_STOCK_PASTE, + GTK_ICON_SIZE_MENU); + gtk_widget_show (menu_image); + menu_item = gtk_image_menu_item_new_with_mnemonic (_("_Paste Into Folder")); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item), + menu_image); + g_signal_connect (menu_item, "activate", + G_CALLBACK (fm_tree_view_paste_cb), + view); + gtk_widget_show (menu_item); + gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); + view->details->popup_paste = menu_item; + + eel_gtk_menu_append_separator (GTK_MENU (popup)); + + /* add the "move to trash" menu item */ + menu_image = gtk_image_new_from_icon_name (CAJA_ICON_TRASH_FULL, + GTK_ICON_SIZE_MENU); + gtk_widget_show (menu_image); + menu_item = gtk_image_menu_item_new_with_mnemonic (_("Mo_ve to Trash")); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item), + menu_image); + g_signal_connect (menu_item, "activate", + G_CALLBACK (fm_tree_view_trash_cb), + view); + gtk_widget_show (menu_item); + gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); + view->details->popup_trash = menu_item; + + /* add the "delete" menu item */ + menu_image = gtk_image_new_from_icon_name (CAJA_ICON_DELETE, + GTK_ICON_SIZE_MENU); + gtk_widget_show (menu_image); + menu_item = gtk_image_menu_item_new_with_mnemonic (_("_Delete")); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item), + menu_image); + g_signal_connect (menu_item, "activate", + G_CALLBACK (fm_tree_view_delete_cb), + view); + gtk_widget_show (menu_item); + gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); + view->details->popup_delete = menu_item; + + eel_gtk_menu_append_separator (GTK_MENU (popup)); + + /* add the "Unmount" menu item */ + menu_item = gtk_image_menu_item_new_with_mnemonic (_("_Unmount")); + g_signal_connect (menu_item, "activate", + G_CALLBACK (fm_tree_view_unmount_cb), + view); + gtk_widget_show (menu_item); + gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); + view->details->popup_unmount = menu_item; + + /* add the "Eject" menu item */ + menu_item = gtk_image_menu_item_new_with_mnemonic (_("_Eject")); + g_signal_connect (menu_item, "activate", + G_CALLBACK (fm_tree_view_eject_cb), + view); + gtk_widget_show (menu_item); + gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); + view->details->popup_eject = menu_item; + + /* add the unmount separator menu item */ + view->details->popup_unmount_separator = + GTK_WIDGET (eel_gtk_menu_append_separator (GTK_MENU (popup))); + + /* add the "properties" menu item */ + menu_item = gtk_image_menu_item_new_from_stock (GTK_STOCK_PROPERTIES, NULL); + g_signal_connect (menu_item, "activate", + G_CALLBACK (fm_tree_view_properties_cb), + view); + gtk_widget_show (menu_item); + gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); + view->details->popup_properties = menu_item; + + view->details->popup = popup; +} + +static void +create_tree (FMTreeView *view) +{ + GtkCellRenderer *cell; + GtkTreeViewColumn *column; + GVolumeMonitor *volume_monitor; + char *home_uri; + GList *mounts, *l; + char *location; + GIcon *icon; + CajaWindowSlotInfo *slot; + + view->details->child_model = fm_tree_model_new (); + view->details->sort_model = GTK_TREE_MODEL_SORT + (gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (view->details->child_model))); + view->details->tree_widget = GTK_TREE_VIEW + (gtk_tree_view_new_with_model (GTK_TREE_MODEL (view->details->sort_model))); + g_object_unref (view->details->sort_model); + + gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (view->details->sort_model), + compare_rows, view, NULL); + + g_signal_connect_object + (view->details->child_model, "row_loaded", + G_CALLBACK (row_loaded_callback), + view, G_CONNECT_AFTER); + home_uri = caja_get_home_directory_uri (); + icon = g_themed_icon_new (CAJA_ICON_HOME); + fm_tree_model_add_root_uri (view->details->child_model, home_uri, _("Home Folder"), icon, NULL); + g_object_unref (icon); + g_free (home_uri); + icon = g_themed_icon_new (CAJA_ICON_FILESYSTEM); + fm_tree_model_add_root_uri (view->details->child_model, "file:///", _("File System"), icon, NULL); + g_object_unref (icon); +#ifdef NOT_YET_USABLE /* Do we really want this? */ + icon = g_themed_icon_new (CAJA_ICON_NETWORK); + fm_tree_model_add_root_uri (view->details->child_model, "network:///", _("Network Neighbourhood"), icon, NULL); + g_object_unref (icon); +#endif + + volume_monitor = g_volume_monitor_get (); + view->details->volume_monitor = volume_monitor; + mounts = g_volume_monitor_get_mounts (volume_monitor); + for (l = mounts; l != NULL; l = l->next) + { + add_root_for_mount (view, l->data); + g_object_unref (l->data); + } + g_list_free (mounts); + + g_signal_connect_object (volume_monitor, "mount_added", + G_CALLBACK (mount_added_callback), view, 0); + g_signal_connect_object (volume_monitor, "mount_removed", + G_CALLBACK (mount_removed_callback), view, 0); + + g_object_unref (view->details->child_model); + + gtk_tree_view_set_headers_visible (view->details->tree_widget, FALSE); + + view->details->drag_dest = + caja_tree_view_drag_dest_new (view->details->tree_widget); + g_signal_connect_object (view->details->drag_dest, + "get_root_uri", + G_CALLBACK (get_root_uri_callback), + view, 0); + g_signal_connect_object (view->details->drag_dest, + "get_file_for_path", + G_CALLBACK (get_file_for_path_callback), + view, 0); + g_signal_connect_object (view->details->drag_dest, + "move_copy_items", + G_CALLBACK (move_copy_items_callback), + view, 0); + + /* Create column */ + column = gtk_tree_view_column_new (); + + cell = caja_cell_renderer_pixbuf_emblem_new (); + gtk_tree_view_column_pack_start (column, cell, FALSE); + gtk_tree_view_column_set_attributes (column, cell, + "pixbuf", FM_TREE_MODEL_CLOSED_PIXBUF_COLUMN, + "pixbuf_expander_closed", FM_TREE_MODEL_CLOSED_PIXBUF_COLUMN, + "pixbuf_expander_open", FM_TREE_MODEL_OPEN_PIXBUF_COLUMN, + "pixbuf_emblem", FM_TREE_MODEL_EMBLEM_PIXBUF_COLUMN, + NULL); + + cell = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (column, cell, TRUE); + gtk_tree_view_column_set_attributes (column, cell, + "text", FM_TREE_MODEL_DISPLAY_NAME_COLUMN, + "style", FM_TREE_MODEL_FONT_STYLE_COLUMN, + NULL); + + gtk_tree_view_append_column (view->details->tree_widget, column); + + gtk_widget_show (GTK_WIDGET (view->details->tree_widget)); + + gtk_container_add (GTK_CONTAINER (view), + GTK_WIDGET (view->details->tree_widget)); + + g_signal_connect_object (gtk_tree_view_get_selection (GTK_TREE_VIEW (view->details->tree_widget)), "changed", + G_CALLBACK (selection_changed_callback), view, 0); + + g_signal_connect (G_OBJECT (view->details->tree_widget), + "row-activated", G_CALLBACK (row_activated_callback), + view); + + g_signal_connect (G_OBJECT (view->details->tree_widget), + "button_press_event", G_CALLBACK (button_pressed_callback), + view); + + slot = caja_window_info_get_active_slot (view->details->window); + location = caja_window_slot_info_get_current_location (slot); + schedule_select_and_show_location (view, location); + g_free (location); +} + +static void +update_filtering_from_preferences (FMTreeView *view) +{ + CajaWindowShowHiddenFilesMode mode; + + if (view->details->child_model == NULL) + { + return; + } + + mode = caja_window_info_get_hidden_files_mode (view->details->window); + + if (mode == CAJA_WINDOW_SHOW_HIDDEN_FILES_DEFAULT) + { + fm_tree_model_set_show_hidden_files + (view->details->child_model, + eel_preferences_get_boolean (CAJA_PREFERENCES_SHOW_HIDDEN_FILES)); + } + else + { + fm_tree_model_set_show_hidden_files + (view->details->child_model, + mode == CAJA_WINDOW_SHOW_HIDDEN_FILES_ENABLE); + } + fm_tree_model_set_show_only_directories + (view->details->child_model, + eel_preferences_get_boolean (CAJA_PREFERENCES_TREE_SHOW_ONLY_DIRECTORIES)); +} + +static void +parent_set_callback (GtkWidget *widget, + GtkWidget *previous_parent, + gpointer callback_data) +{ + FMTreeView *view; + + view = FM_TREE_VIEW (callback_data); + + if (gtk_widget_get_parent (widget) != NULL && view->details->tree_widget == NULL) + { + create_tree (view); + update_filtering_from_preferences (view); + } +} + +static void +filtering_changed_callback (gpointer callback_data) +{ + update_filtering_from_preferences (FM_TREE_VIEW (callback_data)); +} + +static void +loading_uri_callback (CajaWindowInfo *window, + char *location, + gpointer callback_data) +{ + FMTreeView *view; + + view = FM_TREE_VIEW (callback_data); + schedule_select_and_show_location (view, location); +} + +static void +fm_tree_view_init (FMTreeView *view) +{ + view->details = g_new0 (FMTreeViewDetails, 1); + + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (view), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_hadjustment (GTK_SCROLLED_WINDOW (view), NULL); + gtk_scrolled_window_set_vadjustment (GTK_SCROLLED_WINDOW (view), NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (view), GTK_SHADOW_IN); + + gtk_widget_show (GTK_WIDGET (view)); + + g_signal_connect_object (view, "parent_set", + G_CALLBACK (parent_set_callback), view, 0); + + view->details->selection_location = NULL; + + view->details->selecting = FALSE; + + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_SHOW_HIDDEN_FILES, + filtering_changed_callback, view, G_OBJECT (view)); + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_SHOW_BACKUP_FILES, + filtering_changed_callback, view, G_OBJECT (view)); + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_TREE_SHOW_ONLY_DIRECTORIES, + filtering_changed_callback, view, G_OBJECT (view)); + + view->details->popup_file = NULL; + + view->details->clipboard_handler_id = + g_signal_connect (caja_clipboard_monitor_get (), + "clipboard_info", + G_CALLBACK (notify_clipboard_info), view); +} + +static void +fm_tree_view_dispose (GObject *object) +{ + FMTreeView *view; + + view = FM_TREE_VIEW (object); + + if (view->details->selection_changed_timer) + { + g_source_remove (view->details->selection_changed_timer); + view->details->selection_changed_timer = 0; + } + + if (view->details->drag_dest) + { + g_object_unref (view->details->drag_dest); + view->details->drag_dest = NULL; + } + + if (view->details->show_selection_idle_id) + { + g_source_remove (view->details->show_selection_idle_id); + view->details->show_selection_idle_id = 0; + } + + if (view->details->clipboard_handler_id != 0) + { + g_signal_handler_disconnect (caja_clipboard_monitor_get (), + view->details->clipboard_handler_id); + view->details->clipboard_handler_id = 0; + } + + cancel_activation (view); + + if (view->details->popup != NULL) + { + gtk_widget_destroy (view->details->popup); + view->details->popup = NULL; + } + + if (view->details->popup_file_idle_handler != 0) + { + g_source_remove (view->details->popup_file_idle_handler); + view->details->popup_file_idle_handler = 0; + } + + if (view->details->popup_file != NULL) + { + caja_file_unref (view->details->popup_file); + view->details->popup_file = NULL; + } + + if (view->details->selection_location != NULL) + { + g_free (view->details->selection_location); + view->details->selection_location = NULL; + } + + if (view->details->volume_monitor != NULL) + { + g_object_unref (view->details->volume_monitor); + view->details->volume_monitor = NULL; + } + + view->details->window = NULL; + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +fm_tree_view_finalize (GObject *object) +{ + FMTreeView *view; + + view = FM_TREE_VIEW (object); + + g_free (view->details); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +fm_tree_view_class_init (FMTreeViewClass *class) +{ + G_OBJECT_CLASS (class)->dispose = fm_tree_view_dispose; + G_OBJECT_CLASS (class)->finalize = fm_tree_view_finalize; + + copied_files_atom = gdk_atom_intern ("x-special/mate-copied-files", FALSE); + + eel_preferences_add_auto_boolean (CAJA_PREFERENCES_ENABLE_DELETE, + &show_delete_command_auto_value); +} + +static const char * +fm_tree_view_get_sidebar_id (CajaSidebar *sidebar) +{ + return TREE_SIDEBAR_ID; +} + +static char * +fm_tree_view_get_tab_label (CajaSidebar *sidebar) +{ + return g_strdup (_("Tree")); +} + +static char * +fm_tree_view_get_tab_tooltip (CajaSidebar *sidebar) +{ + return g_strdup (_("Show Tree")); +} + +static GdkPixbuf * +fm_tree_view_get_tab_icon (CajaSidebar *sidebar) +{ + return NULL; +} + +static void +fm_tree_view_is_visible_changed (CajaSidebar *sidebar, + gboolean is_visible) +{ + /* Do nothing */ +} + +static void +hidden_files_mode_changed_callback (CajaWindowInfo *window, + FMTreeView *view) +{ + update_filtering_from_preferences (view); +} + +static void +fm_tree_view_iface_init (CajaSidebarIface *iface) +{ + iface->get_sidebar_id = fm_tree_view_get_sidebar_id; + iface->get_tab_label = fm_tree_view_get_tab_label; + iface->get_tab_tooltip = fm_tree_view_get_tab_tooltip; + iface->get_tab_icon = fm_tree_view_get_tab_icon; + iface->is_visible_changed = fm_tree_view_is_visible_changed; +} + +static void +fm_tree_view_set_parent_window (FMTreeView *sidebar, + CajaWindowInfo *window) +{ + char *location; + CajaWindowSlotInfo *slot; + + sidebar->details->window = window; + + slot = caja_window_info_get_active_slot (window); + + g_signal_connect_object (window, "loading_uri", + G_CALLBACK (loading_uri_callback), sidebar, 0); + location = caja_window_slot_info_get_current_location (slot); + loading_uri_callback (window, location, sidebar); + g_free (location); + + g_signal_connect_object (window, "hidden_files_mode_changed", + G_CALLBACK (hidden_files_mode_changed_callback), sidebar, 0); + +} + +static CajaSidebar * +fm_tree_view_create (CajaSidebarProvider *provider, + CajaWindowInfo *window) +{ + FMTreeView *sidebar; + + sidebar = g_object_new (fm_tree_view_get_type (), NULL); + fm_tree_view_set_parent_window (sidebar, window); + g_object_ref_sink (sidebar); + + return CAJA_SIDEBAR (sidebar); +} + +static void +sidebar_provider_iface_init (CajaSidebarProviderIface *iface) +{ + iface->create = fm_tree_view_create; +} + +static void +fm_tree_view_provider_init (FMTreeViewProvider *sidebar) +{ +} + +static void +fm_tree_view_provider_class_init (FMTreeViewProviderClass *class) +{ +} + +void +fm_tree_view_register (void) +{ + caja_module_add_type (fm_tree_view_provider_get_type ()); +} diff --git a/src/file-manager/fm-tree-view.h b/src/file-manager/fm-tree-view.h new file mode 100644 index 00000000..a1bbe458 --- /dev/null +++ b/src/file-manager/fm-tree-view.h @@ -0,0 +1,65 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* + * Copyright (C) 2000, 2001 Eazel, Inc + * Copyright (C) 2002 Anders Carlsson + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * 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: Maciej Stachowiak <[email protected]> + * Anders Carlsson <[email protected]> + */ + +/* fm-tree-view.h - tree view. */ + + +#ifndef FM_TREE_VIEW_H +#define FM_TREE_VIEW_H + +#include <gtk/gtk.h> + +#define FM_TYPE_TREE_VIEW fm_tree_view_get_type() +#define FM_TREE_VIEW(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_TREE_VIEW, FMTreeView)) +#define FM_TREE_VIEW_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_TREE_VIEW, FMTreeViewClass)) +#define FM_IS_TREE_VIEW(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_TREE_VIEW)) +#define FM_IS_TREE_VIEW_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_TREE_VIEW)) +#define FM_TREE_VIEW_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_TREE_VIEW, FMTreeViewClass)) + +#define TREE_SIDEBAR_ID "CajaTreeSidebar" + +typedef struct FMTreeViewDetails FMTreeViewDetails; + +typedef struct +{ + GtkScrolledWindow parent; + + FMTreeViewDetails *details; +} FMTreeView; + +typedef struct +{ + GtkScrolledWindowClass parent_class; +} FMTreeViewClass; + +GType fm_tree_view_get_type (void); +void fm_tree_view_register (void); + +#endif /* FM_TREE_VIEW_H */ |