summaryrefslogtreecommitdiff
path: root/src/file-manager
diff options
context:
space:
mode:
Diffstat (limited to 'src/file-manager')
-rw-r--r--src/file-manager/Makefile.am61
-rw-r--r--src/file-manager/caja-audio-mime-types.h46
-rw-r--r--src/file-manager/caja-desktop-icon-view-ui.xml21
-rw-r--r--src/file-manager/caja-directory-view-ui.xml231
-rw-r--r--src/file-manager/caja-icon-view-ui.xml56
-rw-r--r--src/file-manager/caja-list-view-ui.xml9
-rw-r--r--src/file-manager/fm-actions.h110
-rw-r--r--src/file-manager/fm-desktop-icon-view.c881
-rw-r--r--src/file-manager/fm-desktop-icon-view.h60
-rw-r--r--src/file-manager/fm-directory-view.c10936
-rw-r--r--src/file-manager/fm-directory-view.h495
-rw-r--r--src/file-manager/fm-ditem-page.c563
-rw-r--r--src/file-manager/fm-ditem-page.h51
-rw-r--r--src/file-manager/fm-empty-view.c412
-rw-r--r--src/file-manager/fm-empty-view.h60
-rw-r--r--src/file-manager/fm-error-reporting.c380
-rw-r--r--src/file-manager/fm-error-reporting.h55
-rw-r--r--src/file-manager/fm-icon-container.c625
-rw-r--r--src/file-manager/fm-icon-container.h68
-rw-r--r--src/file-manager/fm-icon-view.c3439
-rw-r--r--src/file-manager/fm-icon-view.h135
-rw-r--r--src/file-manager/fm-list-model.c1882
-rw-r--r--src/file-manager/fm-list-model.h148
-rw-r--r--src/file-manager/fm-list-view-private.h43
-rw-r--r--src/file-manager/fm-list-view.c3415
-rw-r--r--src/file-manager/fm-list-view.h63
-rw-r--r--src/file-manager/fm-properties-window.c5835
-rw-r--r--src/file-manager/fm-properties-window.h69
-rw-r--r--src/file-manager/fm-tree-model.c2152
-rw-r--r--src/file-manager/fm-tree-model.h104
-rw-r--r--src/file-manager/fm-tree-view.c1814
-rw-r--r--src/file-manager/fm-tree-view.h65
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 (&copy_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,
+ &copy_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,
+ &not_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, &current_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 */