diff options
Diffstat (limited to 'src/file-manager')
32 files changed, 34284 insertions, 0 deletions
diff --git a/src/file-manager/Makefile.am b/src/file-manager/Makefile.am new file mode 100644 index 00000000..4090fc91 --- /dev/null +++ b/src/file-manager/Makefile.am @@ -0,0 +1,61 @@ +include $(top_srcdir)/Makefile.shared + +noinst_LTLIBRARIES=libcaja-file-manager.la + +INCLUDES = \ +	-I$(top_srcdir) \ +	-I$(top_srcdir)/cut-n-paste-code \ +	$(CORE_CFLAGS) \ +	$(WARNING_CFLAGS) \ +	-DCAJA_DATADIR=\""$(datadir)/caja"\" \ +	-DDATADIR=\""$(datadir)"\" \ +	$(DISABLE_DEPRECATED_CFLAGS) \ +	$(NULL) + + + +libcaja_file_manager_la_SOURCES = \ +	fm-actions.h \ +	fm-desktop-icon-view.c \ +	fm-desktop-icon-view.h \ +	fm-directory-view.c \ +	fm-directory-view.h \ +	fm-ditem-page.c \ +	fm-ditem-page.h \ +	fm-error-reporting.c \ +	fm-error-reporting.h \ +	fm-icon-container.c \ +	fm-icon-container.h \ +	fm-icon-view.c \ +	fm-icon-view.h \ +	fm-list-model.c \ +	fm-list-model.h \ +	fm-list-view-private.h \ +	fm-list-view.c \ +	fm-list-view.h \ +	fm-properties-window.c \ +	fm-properties-window.h \ +	fm-tree-model.c \ +	fm-tree-model.h \ +	fm-tree-view.c \ +	fm-tree-view.h \ +	caja-audio-mime-types.h \ +	$(NULL) + +EMPTY_VIEW_SOURCES = \ +	fm-empty-view.c \ +	fm-empty-view.h + +if ENABLE_EMPTY_VIEW +libcaja_file_manager_la_SOURCES += $(EMPTY_VIEW_SOURCES) +endif + +uidir = $(datadir)/caja/ui +ui_DATA = \ +	caja-desktop-icon-view-ui.xml \ +	caja-directory-view-ui.xml \ +	caja-icon-view-ui.xml \ +	caja-list-view-ui.xml \ +	$(NULL) + +EXTRA_DIST = $(ui_DATA) diff --git a/src/file-manager/caja-audio-mime-types.h b/src/file-manager/caja-audio-mime-types.h new file mode 100644 index 00000000..74194cc8 --- /dev/null +++ b/src/file-manager/caja-audio-mime-types.h @@ -0,0 +1,46 @@ +/* generated with mime-types-include.sh in the totem module, don't edit or +   commit in the caja module without filing a bug against totem */ +static const char* audio_mime_types[] = +{ +    "audio/3gpp", +    "audio/ac3", +    "audio/AMR", +    "audio/AMR-WB", +    "audio/basic", +    "audio/midi", +    "audio/mp4", +    "audio/mpeg", +    "audio/ogg", +    "audio/prs.sid", +    "audio/vnd.rn-realaudio", +    "audio/x-aiff", +    "audio/x-ape", +    "audio/x-flac", +    "audio/x-gsm", +    "audio/x-it", +    "audio/x-m4a", +    "audio/x-matroska", +    "audio/x-mod", +    "audio/x-mp3", +    "audio/x-mpeg", +    "audio/x-ms-asf", +    "audio/x-ms-asx", +    "audio/x-ms-wax", +    "audio/x-ms-wma", +    "audio/x-musepack", +    "audio/x-pn-aiff", +    "audio/x-pn-au", +    "audio/x-pn-wav", +    "audio/x-pn-windows-acm", +    "audio/x-realaudio", +    "audio/x-real-audio", +    "audio/x-sbc", +    "audio/x-speex", +    "audio/x-tta", +    "audio/x-wav", +    "audio/x-wavpack", +    "audio/x-vorbis", +    "audio/x-vorbis+ogg", +    "audio/x-xm", +    "application/x-flac", +}; diff --git a/src/file-manager/caja-desktop-icon-view-ui.xml b/src/file-manager/caja-desktop-icon-view-ui.xml new file mode 100644 index 00000000..d437801d --- /dev/null +++ b/src/file-manager/caja-desktop-icon-view-ui.xml @@ -0,0 +1,21 @@ +<ui> +<popup name="background"> +        <placeholder name="Before Zoom Items"> +                <placeholder name="New Window Items"> +                </placeholder> +                <placeholder name="New Object Items"> +                        <menuitem name="New Launcher" action="New Launcher Desktop"/> +                </placeholder> +        </placeholder> +        <placeholder name="After Zoom Items"> +                <placeholder name="Background Items"> +                        <menuitem name="Change Background" action="Change Background"/> +                </placeholder> +        </placeholder> +</popup> +<popup name="selection"> +        <placeholder name="Empty Trash Holder"> +                <menuitem name="Empty Trash" action="Empty Trash Conditional"/> +        </placeholder> +</popup> +</ui> diff --git a/src/file-manager/caja-directory-view-ui.xml b/src/file-manager/caja-directory-view-ui.xml new file mode 100644 index 00000000..80b9e881 --- /dev/null +++ b/src/file-manager/caja-directory-view-ui.xml @@ -0,0 +1,231 @@ +<ui> +<accelerator action="OpenAccel"/> +<accelerator action="OpenCloseParent"/> +<accelerator action="PropertiesAccel"/> +<accelerator action="RenameSelectAll"/> +<menubar name="MenuBar"> +	<menu action="File"> +		<placeholder name="New Items Placeholder"> +			<menuitem name="New Folder" action="New Folder"/> +			<menu action="New Documents"> +			         <menuitem name="No Templates" action="No Templates"/> +				 <placeholder name="New Documents Placeholder"/> +				 <separator name="After New Documents"/> +				 <menuitem name="New Empty File" action="New Empty File"/> +			</menu> +			<menuitem name="New Launcher" action="New Launcher"/> +		</placeholder> +		<placeholder name="Open Placeholder"> +			<menuitem name="Open" action="Open"/> +			<menuitem name="OpenInNewTab" action="OpenInNewTab"/> +			<menuitem name="OpenAlternate" action="OpenAlternate"/> +			<placeholder name="Applications Placeholder"> +                        </placeholder> +			<menu action="Open With"> +				<placeholder name="Applications Placeholder"/> +				<separator name="Open With Separator"/> +				<menuitem name="OtherApplication" action="OtherApplication1"/> +			</menu> +			<placeholder name="OtherApplicationPlaceholder"> +				<menuitem name="OtherApplication" action="OtherApplication2"/> +			</placeholder> +			<menu action="Scripts"> +				 <placeholder name="Scripts Placeholder"/> +				 <separator name="After Scripts"/> +				 <menuitem name="Open Scripts Folder" action="Open Scripts Folder"/> +			</menu> +		</placeholder> +		<placeholder name="File Items Placeholder"> +			<menuitem name="Self Mount Volume" action="Self Mount Volume"/> +			<menuitem name="Self Unmount Volume" action="Self Unmount Volume"/> +			<menuitem name="Self Eject Volume" action="Self Eject Volume"/> +			<menuitem name="Self Format Volume" action="Self Format Volume"/> +			<menuitem name="Self Start Volume" action="Self Start Volume"/> +			<menuitem name="Self Stop Volume" action="Self Stop Volume"/> +			<menuitem name="Self Poll" action="Self Poll"/> +			<separator name="Properties Separator"/> +			<menuitem name="Properties" action="Properties"/> +		</placeholder> +		<placeholder name="Global File Items Placeholder"> +			<menuitem name="Empty Trash" action="Empty Trash"/> +			<menuitem name="Save Search" action="Save Search"/> +			<menuitem name="Save Search As" action="Save Search As"/> +		</placeholder> +	</menu> +	<menu action="Edit"> +		<placeholder name="Clipboard Actions"> +			<menuitem name="Cut" action="Cut"/> +			<menuitem name="Copy" action="Copy"/> +			<menuitem name="Paste" action="Paste"/> +		</placeholder> +		<placeholder name="Select Items"> +		<menuitem name="Select All" action="Select All"/> +		<menuitem name="Select Pattern" action="Select Pattern"/> +		<menuitem name="Invert Selection" action="Invert Selection"/> +		</placeholder> +		<placeholder name="File Items Placeholder"> +			<menuitem name="Duplicate" action="Duplicate"/> +			<menuitem name="Create Link" action="Create Link"/> +			<menuitem name="Rename" action="Rename"/> +			<menu action="CopyToMenu"> +				<menuitem name="Copy to next pane" action="Copy to next pane"/> +				<menuitem name="Copy to Home" action="Copy to Home"/> +				<menuitem name="Copy to Desktop" action="Copy to Desktop"/> +			</menu> +			<menu action="MoveToMenu"> +				<menuitem name="Move to next pane" action="Move to next pane"/> +				<menuitem name="Copy to Home" action="Move to Home"/> +				<menuitem name="Copy to Desktop" action="Move to Desktop"/> +			</menu> +		</placeholder> +		<placeholder name="Dangerous File Items Placeholder"> +			<menuitem name="Trash" action="Trash"/> +			<menuitem name="Delete" action="Delete"/> +			<menuitem name="Restore From Trash" action="Restore From Trash"/> +		</placeholder> +		<placeholder name="Extension Actions"/> +	</menu> +	<menu action="View"> +		<placeholder name="View Preferences Placeholder"> +			<menuitem name="Reset to Defaults" action="Reset to Defaults"/> +			<menuitem name="Show Hidden Files" action="Show Hidden Files"/> +		</placeholder> +	</menu> +</menubar> +<popup name="background"> +	<placeholder name="Before Zoom Items"> +		<placeholder name="New Object Items"> +			<menuitem name="New Folder" action="New Folder"/> +			<menuitem name="New Launcher" action="New Launcher"/> +			<menu action="New Documents"> +		         	<menuitem name="No Templates" action="No Templates"/> +			 	<placeholder name="New Documents Placeholder"/> +			 	<separator name="After New Documents"/> +			 	<menuitem name="New Empty File" action="New Empty File"/> +			</menu> +                	<menu action="Scripts"> +			 	<placeholder name="Scripts Placeholder"/> +			 	<separator name="After Scripts"/> +			 	<menuitem name="Open Scripts Folder" action="Open Scripts Folder"/> +			</menu> +		</placeholder> +		<separator name="View items separator"/> +		<placeholder name="View Items"/> +		<separator name="Clipboard separator"/> +		<placeholder name="File Clipboard Actions"> +			<menuitem name="Paste" action="Paste"/> +		</placeholder> +	</placeholder> + +	<separator name="Folder Items separator"/> +	<placeholder name="Folder Items Placeholder"> +		<menuitem name="Self Mount Volume" action="Self Mount Volume"/> +		<menuitem name="Self Unmount Volume" action="Self Unmount Volume"/> +		<menuitem name="Self Eject Volume" action="Self Eject Volume"/> +		<menuitem name="Self Format Volume" action="Self Format Volume"/> +		<menuitem name="Self Start Volume" action="Self Start Volume"/> +		<menuitem name="Self Stop Volume" action="Self Stop Volume"/> +		<menuitem name="Self Poll" action="Self Poll"/> +		<separator name="Properties separator"/> +		<menuitem name="Properties" action="Properties"/> +	</placeholder> + +</popup> +<popup name="selection"> +	<placeholder name="Open Placeholder"> +		<menuitem name="Open" action="Open"/> +		<menuitem name="OpenInNewTab" action="OpenInNewTab"/> +		<menuitem name="OpenAlternate" action="OpenAlternate"/> +		<menuitem name="OpenFolderWindow" action="OpenFolderWindow"/> +		<separator name="applications separator"/> +		<placeholder name="Applications Placeholder"/> +		<menu action="Open With"> +			<placeholder name="Applications Placeholder"/> +			<separator name="open with separator"/> +			<menuitem name="OtherApplication" action="OtherApplication1"/> +		</menu> +		<placeholder name="OtherApplicationPlaceholder"> +		        <menuitem name="OtherApplication2" action="OtherApplication2"/> +		</placeholder> +		<menu action="Scripts"> +			 <placeholder name="Scripts Placeholder"/> +			 <separator name="After Scripts"/> +			 <menuitem name="Open Scripts Folder" action="Open Scripts Folder"/> +		</menu> +	</placeholder> +	<separator name="Clipboard separator"/> +	<placeholder name="File Clipboard Actions"> +		<menuitem name="Cut" action="Cut"/> +		<menuitem name="Copy" action="Copy"/> +		<menuitem name="Paste Files Into" action="Paste Files Into"/> +	</placeholder> +	<separator name="File actions separator"/> +	<placeholder name="File Actions"> +		<menuitem name="Create Link" action="Create Link"/> +		<menuitem name="Rename" action="Rename"/> +		<menu action="CopyToMenu"> +			<menuitem name="Copy to next pane" action="Copy to next pane"/> +			<menuitem name="Copy to Home" action="Copy to Home"/> +			<menuitem name="Copy to Desktop" action="Copy to Desktop"/> +		</menu> +		<menu action="MoveToMenu"> +			<menuitem name="Move to next pane" action="Move to next pane"/> +			<menuitem name="Copy to Home" action="Move to Home"/> +			<menuitem name="Copy to Desktop" action="Move to Desktop"/> +		</menu> +	</placeholder> +	<separator name="Dangerous separator"/> +	<placeholder name="Dangerous File Actions"> +		<menuitem name="Trash" action="Trash"/> +		<menuitem name="Delete" action="Delete"/> +		<menuitem name="Restore From Trash" action="Restore From Trash"/> +	</placeholder> +	<separator name="Appearance separator"/> +	<placeholder name="Icon Appearance Items"> +	</placeholder> +	<separator name="Extension actions separator"/> +	<placeholder name="Extension Actions"/> +	<separator name="Removable separator"/> +	<placeholder name="Removable Media Placeholder"> +		 <menuitem name="Mount Volume" action="Mount Volume"/> +		 <menuitem name="Unmount Volume" action="Unmount Volume"/> +		 <menuitem name="Eject Volume" action="Eject Volume"/> +		 <menuitem name="Format Volume" action="Format Volume"/> +		 <menuitem name="Start Volume" action="Start Volume"/> +		 <menuitem name="Stop Volume" action="Stop Volume"/> +		 <menuitem name="Poll" action="Poll"/> +        </placeholder> +        <menuitem name="Connect To Server Link" action="Connect To Server Link"/> +        <separator name="Properties Separator"/> +        <menuitem name="Properties" action="Properties"/> +</popup> +<popup name="location"> +	<placeholder name="Open Placeholder"> +		<menuitem name="LocationOpenInNewTab" action="LocationOpenInNewTab"/> +		<menuitem name="LocationOpenAlternate" action="LocationOpenAlternate"/> +		<menuitem name="LocationOpenFolderWindow" action="LocationOpenFolderWindow"/> +	</placeholder> +	<separator name="Location After Open Separator"/> +	<placeholder name="Clipboard Actions"> +		<menuitem name="Cut" action="LocationCut"/> +		<menuitem name="Copy" action="LocationCopy"/> +		<menuitem name="LocationPasteFilesInto" action="LocationPasteFilesInto"/> +	</placeholder> +	<separator name="Location After Clipboard Separator"/> +	<placeholder name="Dangerous File Actions"> +		<menuitem name="Trash" action="LocationTrash"/> +		<menuitem name="Delete" action="LocationDelete"/> +		<menuitem name="Restore From Trash" action="LocationRestoreFromTrash"/> +	</placeholder> +	<separator name="Location After Dangerous Separator"/> +	<menuitem name="Location Mount Volume" action="Location Mount Volume"/> +	<menuitem name="Location Unmount Volume" action="Location Unmount Volume"/> +	<menuitem name="Location Eject Volume" action="Location Eject Volume"/> +	<menuitem name="Location Format Volume" action="Location Format Volume"/> +	<menuitem name="Location Start Volume" action="Location Start Volume"/> +	<menuitem name="Location Stop Volume" action="Location Stop Volume"/> +	<menuitem name="Location Poll" action="Location Poll"/> +	<separator name="Properties Separator"/> +	<menuitem name="LocationProperties" action="LocationProperties"/> +</popup> +</ui> diff --git a/src/file-manager/caja-icon-view-ui.xml b/src/file-manager/caja-icon-view-ui.xml new file mode 100644 index 00000000..89c4cb6e --- /dev/null +++ b/src/file-manager/caja-icon-view-ui.xml @@ -0,0 +1,56 @@ +<ui> +<menubar name="MenuBar"> +	<menu action="Edit"> +		<placeholder name="Edit Items Placeholder"> +			<menuitem name="Stretch" action="Stretch"/> +			<menuitem name="Unstretch" action="Unstretch"/> +		</placeholder> +	</menu> +	<menu action="View"> +		<placeholder name="View Items Placeholder"> +			<menu action="Arrange Items"> +				<menuitem name="Manual Layout" action="Manual Layout"/> +				<placeholder name="Auto Layout"> +					<menuitem name="Sort by Name" action="Sort by Name"/> +					<menuitem name="Sort by Size" action="Sort by Size"/> +					<menuitem name="Sort by Type" action="Sort by Type"/> +					<menuitem name="Sort by Modification Date" action="Sort by Modification Date"/> +					<menuitem name="Sort by Emblems" action="Sort by Emblems"/> +					<menuitem name="Sort by Trash Time" action="Sort by Trash Time"/> +				</placeholder> +				<separator name="Layout separator"/> +				<menuitem name="Tighter Layout" action="Tighter Layout"/> +				<menuitem name="Reversed Order" action="Reversed Order"/> +			</menu> +			<menuitem name="Clean Up" action="Clean Up"/> +			<menuitem name="Keep Aligned" action="Keep Aligned"/> +		</placeholder> + +	</menu> +</menubar> +<popup name="background"> +	<placeholder name="Before Zoom Items"> +		<placeholder name="View Items"> +			<menu action="Arrange Items"> +				<menuitem name="Manual Layout" action="Manual Layout"/> +				<placeholder name="Auto Layout"> +					<menuitem name="Sort by Name" action="Sort by Name"/> +					<menuitem name="Sort by Size" action="Sort by Size"/> +					<menuitem name="Sort by Type" action="Sort by Type"/> +					<menuitem name="Sort by Modification Date" action="Sort by Modification Date"/> +					<menuitem name="Sort by Emblems" action="Sort by Emblems"/> +					<menuitem name="Sort by Trash Time" action="Sort by Trash Time"/> +				</placeholder> +				<separator name="Layout separator"/> +				<menuitem name="Tighter Layout" action="Tighter Layout"/> +				<menuitem name="Reversed Order" action="Reversed Order"/> +			</menu> +			<menuitem name="Clean Up" action="Clean Up"/> +			<menuitem name="Keep Aligned" action="Keep Aligned"/> +		</placeholder> +	</placeholder> +</popup> +<popup name="selection"> +	<placeholder name="Icon Appearance Items"/> +</popup> +</ui> diff --git a/src/file-manager/caja-list-view-ui.xml b/src/file-manager/caja-list-view-ui.xml new file mode 100644 index 00000000..ad9e6255 --- /dev/null +++ b/src/file-manager/caja-list-view-ui.xml @@ -0,0 +1,9 @@ +<ui> +<menubar name="MenuBar"> +	<menu action="View"> +		<placeholder name="View Items Placeholder"> +			<menuitem name="Visible Columns" action="Visible Columns"/> +		</placeholder> +	</menu> +</menubar> +</ui> diff --git a/src/file-manager/fm-actions.h b/src/file-manager/fm-actions.h new file mode 100644 index 00000000..077d82c1 --- /dev/null +++ b/src/file-manager/fm-actions.h @@ -0,0 +1,110 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-actions.h + * + * Copyright (C) 2004  Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Alexander Larsson < [email protected]> + */ + +#ifndef FM_ACTIONS_H +#define FM_ACTIONS_H + +#define FM_ACTION_OPEN "Open" +#define FM_ACTION_OPEN_ALTERNATE "OpenAlternate" +#define FM_ACTION_OPEN_IN_NEW_TAB "OpenInNewTab" +#define FM_ACTION_OPEN_FOLDER_WINDOW "OpenFolderWindow" +#define FM_ACTION_LOCATION_OPEN_ALTERNATE "LocationOpenAlternate" +#define FM_ACTION_LOCATION_OPEN_IN_NEW_TAB "LocationOpenInNewTab" +#define FM_ACTION_LOCATION_OPEN_FOLDER_WINDOW "LocationOpenFolderWindow" +#define FM_ACTION_OTHER_APPLICATION1 "OtherApplication1" +#define FM_ACTION_OTHER_APPLICATION2 "OtherApplication2" +#define FM_ACTION_NEW_FOLDER "New Folder" +#define FM_ACTION_PROPERTIES "Properties" +#define FM_ACTION_PROPERTIES_ACCEL "PropertiesAccel" +#define FM_ACTION_LOCATION_PROPERTIES "LocationProperties" +#define FM_ACTION_NO_TEMPLATES "No Templates" +#define FM_ACTION_EMPTY_TRASH "Empty Trash" +#define FM_ACTION_SAVE_SEARCH "Save Search" +#define FM_ACTION_SAVE_SEARCH_AS "Save Search As" +#define FM_ACTION_CUT "Cut" +#define FM_ACTION_LOCATION_CUT "LocationCut" +#define FM_ACTION_COPY "Copy" +#define FM_ACTION_LOCATION_COPY "LocationCopy" +#define FM_ACTION_PASTE "Paste" +#define FM_ACTION_PASTE_FILES_INTO "Paste Files Into" +#define FM_ACTION_COPY_TO_NEXT_PANE "Copy to next pane" +#define FM_ACTION_MOVE_TO_NEXT_PANE "Move to next pane" +#define FM_ACTION_COPY_TO_HOME "Copy to Home" +#define FM_ACTION_MOVE_TO_HOME "Move to Home" +#define FM_ACTION_COPY_TO_DESKTOP "Copy to Desktop" +#define FM_ACTION_MOVE_TO_DESKTOP "Move to Desktop" +#define FM_ACTION_LOCATION_PASTE_FILES_INTO "LocationPasteFilesInto" +#define FM_ACTION_NEW_LAUNCHER "New Launcher" +#define FM_ACTION_NEW_LAUNCHER_DESKTOP "New Launcher Desktop" +#define FM_ACTION_RENAME "Rename" +#define FM_ACTION_DUPLICATE "Duplicate" +#define FM_ACTION_CREATE_LINK "Create Link" +#define FM_ACTION_SELECT_ALL "Select All" +#define FM_ACTION_INVERT_SELECTION "Invert Selection" +#define FM_ACTION_SELECT_PATTERN "Select Pattern" +#define FM_ACTION_TRASH "Trash" +#define FM_ACTION_LOCATION_TRASH "LocationTrash" +#define FM_ACTION_DELETE "Delete" +#define FM_ACTION_LOCATION_DELETE "LocationDelete" +#define FM_ACTION_RESTORE_FROM_TRASH "Restore From Trash" +#define FM_ACTION_LOCATION_RESTORE_FROM_TRASH "LocationRestoreFromTrash" +#define FM_ACTION_SHOW_HIDDEN_FILES "Show Hidden Files" +#define FM_ACTION_CONNECT_TO_SERVER_LINK "Connect To Server Link" +#define FM_ACTION_MOUNT_VOLUME "Mount Volume" +#define FM_ACTION_UNMOUNT_VOLUME "Unmount Volume" +#define FM_ACTION_EJECT_VOLUME "Eject Volume" +#define FM_ACTION_FORMAT_VOLUME "Format Volume" +#define FM_ACTION_START_VOLUME "Start Volume" +#define FM_ACTION_STOP_VOLUME "Stop Volume" +#define FM_ACTION_POLL "Poll" +#define FM_ACTION_SELF_MOUNT_VOLUME "Self Mount Volume" +#define FM_ACTION_SELF_UNMOUNT_VOLUME "Self Unmount Volume" +#define FM_ACTION_SELF_EJECT_VOLUME "Self Eject Volume" +#define FM_ACTION_SELF_FORMAT_VOLUME "Self Format Volume" +#define FM_ACTION_SELF_START_VOLUME "Self Start Volume" +#define FM_ACTION_SELF_STOP_VOLUME "Self Stop Volume" +#define FM_ACTION_SELF_POLL "Self Poll" +#define FM_ACTION_LOCATION_MOUNT_VOLUME "Location Mount Volume" +#define FM_ACTION_LOCATION_UNMOUNT_VOLUME "Location Unmount Volume" +#define FM_ACTION_LOCATION_EJECT_VOLUME "Location Eject Volume" +#define FM_ACTION_LOCATION_FORMAT_VOLUME "Location Format Volume" +#define FM_ACTION_LOCATION_START_VOLUME "Location Start Volume" +#define FM_ACTION_LOCATION_STOP_VOLUME "Location Stop Volume" +#define FM_ACTION_LOCATION_POLL "Location Poll" +#define FM_ACTION_SCRIPTS "Scripts" +#define FM_ACTION_NEW_DOCUMENTS "New Documents" +#define FM_ACTION_NEW_EMPTY_FILE "New Empty File" +#define FM_ACTION_EMPTY_TRASH_CONDITIONAL "Empty Trash Conditional" +#define FM_ACTION_MANUAL_LAYOUT "Manual Layout" +#define FM_ACTION_TIGHTER_LAYOUT "Tighter Layout" +#define FM_ACTION_REVERSED_ORDER "Reversed Order" +#define FM_ACTION_CLEAN_UP "Clean Up" +#define FM_ACTION_KEEP_ALIGNED "Keep Aligned" +#define FM_ACTION_ARRANGE_ITEMS "Arrange Items" +#define FM_ACTION_STRETCH "Stretch" +#define FM_ACTION_UNSTRETCH "Unstretch" +#define FM_ACTION_ZOOM_ITEMS "Zoom Items" +#define FM_ACTION_SORT_TRASH_TIME "Sort by Trash Time" + +#endif /* FM_ACTIONS_H */ diff --git a/src/file-manager/fm-desktop-icon-view.c b/src/file-manager/fm-desktop-icon-view.c new file mode 100644 index 00000000..4a1bab93 --- /dev/null +++ b/src/file-manager/fm-desktop-icon-view.c @@ -0,0 +1,881 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-desktop-icon-view.c - implementation of icon view for managing the desktop. + +   Copyright (C) 2000, 2001 Eazel, Inc.mou + +   The Mate Library is free software; you can redistribute it and/or +   modify it under the terms of the GNU Library General Public License as +   published by the Free Software Foundation; either version 2 of the +   License, or (at your option) any later version. + +   The Mate Library is distributed in the hope that it will be useful, +   but WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +   Library General Public License for more details. + +   You should have received a copy of the GNU Library General Public +   License along with the Mate Library; see the file COPYING.LIB.  If not, +   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +   Boston, MA 02111-1307, USA. + +   Authors: Mike Engber <[email protected]> +   	    Gene Z. Ragan <[email protected]> +	    Miguel de Icaza <[email protected]> +*/ + +#include <config.h> +#include "fm-icon-container.h" +#include "fm-desktop-icon-view.h" +#include "fm-actions.h" + +#include <X11/Xatom.h> +#include <gtk/gtk.h> +#include <dirent.h> +#include <eel/eel-glib-extensions.h> +#include <eel/eel-mate-extensions.h> +#include <eel/eel-gtk-extensions.h> +#include <eel/eel-gtk-macros.h> +#include <eel/eel-stock-dialogs.h> +#include <eel/eel-string.h> +#include <eel/eel-vfs-extensions.h> +#include <fcntl.h> +#include <gdk/gdkx.h> +#include <glib/gi18n.h> +#include <libcaja-private/caja-desktop-icon-file.h> +#include <libcaja-private/caja-directory-background.h> +#include <libcaja-private/caja-directory-notify.h> +#include <libcaja-private/caja-file-changes-queue.h> +#include <libcaja-private/caja-file-operations.h> +#include <libcaja-private/caja-file-utilities.h> +#include <libcaja-private/caja-ui-utilities.h> +#include <libcaja-private/caja-global-preferences.h> +#include <libcaja-private/caja-view-factory.h> +#include <libcaja-private/caja-link.h> +#include <libcaja-private/caja-metadata.h> +#include <libcaja-private/caja-monitor.h> +#include <libcaja-private/caja-program-choosing.h> +#include <libcaja-private/caja-trash-monitor.h> +#include <limits.h> +#include <stddef.h> +#include <stdio.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +/* Timeout to check the desktop directory for updates */ +#define RESCAN_TIMEOUT 4 + +struct FMDesktopIconViewDetails +{ +    GdkWindow *root_window; +    GtkActionGroup *desktop_action_group; +    guint desktop_merge_id; + +    /* For the desktop rescanning +     */ +    gulong delayed_init_signal; +    guint reload_desktop_timeout; +    gboolean pending_rescan; +}; + +static void     fm_desktop_icon_view_init                   (FMDesktopIconView      *desktop_icon_view); +static void     fm_desktop_icon_view_class_init             (FMDesktopIconViewClass *klass); +static void     default_zoom_level_changed                        (gpointer                user_data); +static gboolean real_supports_auto_layout                         (FMIconView             *view); +static gboolean real_supports_scaling	                          (FMIconView             *view); +static gboolean real_supports_keep_aligned                        (FMIconView             *view); +static gboolean real_supports_labels_beside_icons                 (FMIconView             *view); +static void     real_merge_menus                                  (FMDirectoryView        *view); +static void     real_update_menus                                 (FMDirectoryView        *view); +static gboolean real_supports_zooming                             (FMDirectoryView        *view); +static void     fm_desktop_icon_view_update_icon_container_fonts  (FMDesktopIconView      *view); + +EEL_CLASS_BOILERPLATE (FMDesktopIconView, +                       fm_desktop_icon_view, +                       FM_TYPE_ICON_VIEW) + +static char *desktop_directory; +static time_t desktop_dir_modify_time; + +static void +desktop_directory_changed_callback (gpointer callback_data) +{ +    g_free (desktop_directory); +    desktop_directory = caja_get_desktop_directory (); +} + +static void +lockdown_disable_command_line_changed_callback (gpointer callback_data) +{ +    fm_directory_view_update_menus (FM_DIRECTORY_VIEW (callback_data)); +} + +static CajaIconContainer * +get_icon_container (FMDesktopIconView *icon_view) +{ +    g_return_val_if_fail (FM_IS_DESKTOP_ICON_VIEW (icon_view), NULL); +    g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (gtk_bin_get_child (GTK_BIN (icon_view))), NULL); + +    return CAJA_ICON_CONTAINER (gtk_bin_get_child (GTK_BIN (icon_view))); +} + +static void +icon_container_set_workarea (CajaIconContainer *icon_container, +                             GdkScreen             *screen, +                             long                  *workareas, +                             int                    n_items) +{ +    int left, right, top, bottom; +    int screen_width, screen_height; +    int i; + +    left = right = top = bottom = 0; + +    screen_width  = gdk_screen_get_width (screen); +    screen_height = gdk_screen_get_height (screen); + +    for (i = 0; i < n_items; i += 4) +    { +        int x      = workareas [i]; +        int y      = workareas [i + 1]; +        int width  = workareas [i + 2]; +        int height = workareas [i + 3]; + +        if ((x + width) > screen_width || (y + height) > screen_height) +            continue; + +        left   = MAX (left, x); +        right  = MAX (right, screen_width - width - x); +        top    = MAX (top, y); +        bottom = MAX (bottom, screen_height - height - y); +    } + +    caja_icon_container_set_margins (icon_container, +                                     left, right, top, bottom); +} + +static void +net_workarea_changed (FMDesktopIconView *icon_view, +                      GdkWindow         *window) +{ +    long *nworkareas = NULL; +    long *workareas = NULL; +    GdkAtom type_returned; +    int format_returned; +    int length_returned; +    CajaIconContainer *icon_container; +    GdkScreen *screen; + +    g_return_if_fail (FM_IS_DESKTOP_ICON_VIEW (icon_view)); + +    icon_container = get_icon_container (icon_view); + +    /* Find the number of desktops so we know how long the +     * workareas array is going to be (each desktop will have four +     * elements in the workareas array describing +     * x,y,width,height) */ +    gdk_error_trap_push (); +    if (!gdk_property_get (window, +                           gdk_atom_intern ("_NET_NUMBER_OF_DESKTOPS", FALSE), +                           gdk_x11_xatom_to_atom (XA_CARDINAL), +                           0, 4, FALSE, +                           &type_returned, +                           &format_returned, +                           &length_returned, +                           (guchar **) &nworkareas)) +    { +        g_warning("Can not calculate _NET_NUMBER_OF_DESKTOPS"); +    } +    if (gdk_error_trap_pop() +            || nworkareas == NULL +            || type_returned != gdk_x11_xatom_to_atom (XA_CARDINAL) +            || format_returned != 32) +        g_warning("Can not calculate _NET_NUMBER_OF_DESKTOPS"); + +    /* Note : gdk_property_get() is broken (API documents admit +     * this).  As a length argument, it expects the number of +     * _bytes_ of data you require.  Internally, gdk_property_get +     * converts that value to a count of 32 bit (4 byte) elements. +     * However, the length returned is in bytes, but is calculated +     * via the count of returned elements * sizeof(long).  This +     * means on a 64 bit system, the number of bytes you have to +     * request does not correspond to the number of bytes you get +     * back, and is the reason for the workaround below. +     */ +    gdk_error_trap_push (); +    if (nworkareas == NULL || (*nworkareas < 1) +            || !gdk_property_get (window, +                                  gdk_atom_intern ("_NET_WORKAREA", FALSE), +                                  gdk_x11_xatom_to_atom (XA_CARDINAL), +                                  0, ((*nworkareas) * 4 * 4), FALSE, +                                  &type_returned, +                                  &format_returned, +                                  &length_returned, +                                  (guchar **) &workareas)) +    { +        g_warning("Can not get _NET_WORKAREA"); +        workareas = NULL; +    } + +    if (gdk_error_trap_pop () +            || workareas == NULL +            || type_returned != gdk_x11_xatom_to_atom (XA_CARDINAL) +            || ((*nworkareas) * 4 * sizeof(long)) != length_returned +            || format_returned != 32) +    { +        g_warning("Can not determine workarea, guessing at layout"); +        caja_icon_container_set_margins (icon_container, +                                         0, 0, 0, 0); +    } +    else +    { +        screen = gdk_drawable_get_screen (GDK_DRAWABLE (window)); + +        icon_container_set_workarea ( +            icon_container, screen, workareas, length_returned / sizeof (long)); +    } + +    if (nworkareas != NULL) +        g_free (nworkareas); + +    if (workareas != NULL) +        g_free (workareas); +} + +static GdkFilterReturn +desktop_icon_view_property_filter (GdkXEvent *gdk_xevent, +                                   GdkEvent *event, +                                   gpointer data) +{ +    XEvent *xevent = gdk_xevent; +    FMDesktopIconView *icon_view; + +    icon_view = FM_DESKTOP_ICON_VIEW (data); + +    switch (xevent->type) +    { +    case PropertyNotify: +        if (xevent->xproperty.atom == gdk_x11_get_xatom_by_name ("_NET_WORKAREA")) +            net_workarea_changed (icon_view, event->any.window); +        break; +    default: +        break; +    } + +    return GDK_FILTER_CONTINUE; +} + +static void +fm_desktop_icon_view_destroy (GtkObject *object) +{ +    FMDesktopIconView *icon_view; +    GtkUIManager *ui_manager; + +    icon_view = FM_DESKTOP_ICON_VIEW (object); + +    /* Remove desktop rescan timeout. */ +    if (icon_view->details->reload_desktop_timeout != 0) +    { +        g_source_remove (icon_view->details->reload_desktop_timeout); +        icon_view->details->reload_desktop_timeout = 0; +    } + +    ui_manager = fm_directory_view_get_ui_manager (FM_DIRECTORY_VIEW (icon_view)); +    if (ui_manager != NULL) +    { +        caja_ui_unmerge_ui (ui_manager, +                            &icon_view->details->desktop_merge_id, +                            &icon_view->details->desktop_action_group); +    } + +    GTK_OBJECT_CLASS (parent_class)->destroy (object); +} + +static void +fm_desktop_icon_view_finalize (GObject *object) +{ +    FMDesktopIconView *icon_view; + +    icon_view = FM_DESKTOP_ICON_VIEW (object); + +    eel_preferences_remove_callback (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_ZOOM_LEVEL, +                                     default_zoom_level_changed, +                                     icon_view); + +    eel_preferences_remove_callback (CAJA_PREFERENCES_LOCKDOWN_COMMAND_LINE, +                                     lockdown_disable_command_line_changed_callback, +                                     icon_view); + +    g_free (icon_view->details); + +    G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +fm_desktop_icon_view_class_init (FMDesktopIconViewClass *class) +{ +    G_OBJECT_CLASS (class)->finalize = fm_desktop_icon_view_finalize; + +    GTK_OBJECT_CLASS (class)->destroy = fm_desktop_icon_view_destroy; + +    FM_DIRECTORY_VIEW_CLASS (class)->merge_menus = real_merge_menus; +    FM_DIRECTORY_VIEW_CLASS (class)->update_menus = real_update_menus; +    FM_DIRECTORY_VIEW_CLASS (class)->supports_zooming = real_supports_zooming; + +    FM_ICON_VIEW_CLASS (class)->supports_auto_layout = real_supports_auto_layout; +    FM_ICON_VIEW_CLASS (class)->supports_scaling = real_supports_scaling; +    FM_ICON_VIEW_CLASS (class)->supports_keep_aligned = real_supports_keep_aligned; +    FM_ICON_VIEW_CLASS (class)->supports_labels_beside_icons = real_supports_labels_beside_icons; +} + +static void +fm_desktop_icon_view_handle_middle_click (CajaIconContainer *icon_container, +        GdkEventButton *event, +        FMDesktopIconView *desktop_icon_view) +{ +    XButtonEvent x_event; + +    /* During a mouse click we have the pointer and keyboard grab. +     * We will send a fake event to the root window which will cause it +     * to try to get the grab so we need to let go ourselves. +     */ +    gdk_pointer_ungrab (GDK_CURRENT_TIME); +    gdk_keyboard_ungrab (GDK_CURRENT_TIME); + +    /* Stop the event because we don't want anyone else dealing with it. */ +    gdk_flush (); +    g_signal_stop_emission_by_name (icon_container, "middle_click"); + +    /* build an X event to represent the middle click. */ +    x_event.type = ButtonPress; +    x_event.send_event = True; +    x_event.display = GDK_DISPLAY (); +    x_event.window = GDK_ROOT_WINDOW (); +    x_event.root = GDK_ROOT_WINDOW (); +    x_event.subwindow = 0; +    x_event.time = event->time; +    x_event.x = event->x; +    x_event.y = event->y; +    x_event.x_root = event->x_root; +    x_event.y_root = event->y_root; +    x_event.state = event->state; +    x_event.button = event->button; +    x_event.same_screen = True; + +    /* Send it to the root window, the window manager will handle it. */ +    XSendEvent (GDK_DISPLAY (), GDK_ROOT_WINDOW (), True, +                ButtonPressMask, (XEvent *) &x_event); +} + +static void +unrealized_callback (GtkWidget *widget, FMDesktopIconView *desktop_icon_view) +{ +    g_return_if_fail (desktop_icon_view->details->root_window != NULL); + +    /* Remove the property filter */ +    gdk_window_remove_filter (desktop_icon_view->details->root_window, +                              desktop_icon_view_property_filter, +                              desktop_icon_view); +    desktop_icon_view->details->root_window = NULL; +} + +static void +realized_callback (GtkWidget *widget, FMDesktopIconView *desktop_icon_view) +{ +    GdkWindow *root_window; +    GdkScreen *screen; +    GtkAllocation allocation; + +    g_return_if_fail (desktop_icon_view->details->root_window == NULL); + +    screen = gtk_widget_get_screen (widget); + +    /* Ugly HACK for the problem that the views realize at the +     * wrong size and then get resized. (This is a problem with +     * MateComponentPlug.) This was leading to problems where initial +     * layout was done at 60x60 stacking all desktop icons in +     * the top left corner. +     */ +    allocation.x = 0; +    allocation.y = 0; +    allocation.width = gdk_screen_get_width (screen); +    allocation.height = gdk_screen_get_height (screen); +    gtk_widget_size_allocate (GTK_WIDGET(get_icon_container(desktop_icon_view)), +                              &allocation); + +    root_window = gdk_screen_get_root_window (screen); + +    desktop_icon_view->details->root_window = root_window; + +    /* Read out the workarea geometry and update the icon container accordingly */ +    net_workarea_changed (desktop_icon_view, root_window); + +    /* Setup the property filter */ +    gdk_window_set_events (root_window, GDK_PROPERTY_CHANGE_MASK); +    gdk_window_add_filter (root_window, +                           desktop_icon_view_property_filter, +                           desktop_icon_view); +} + +static CajaZoomLevel +get_default_zoom_level (void) +{ +    static gboolean auto_storage_added = FALSE; +    static CajaZoomLevel default_zoom_level = CAJA_ZOOM_LEVEL_STANDARD; + +    if (!auto_storage_added) +    { +        auto_storage_added = TRUE; +        eel_preferences_add_auto_enum (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_ZOOM_LEVEL, +                                       (int *) &default_zoom_level); +    } + +    return CLAMP (default_zoom_level, CAJA_ZOOM_LEVEL_SMALLEST, CAJA_ZOOM_LEVEL_LARGEST); +} + +static void +default_zoom_level_changed (gpointer user_data) +{ +    CajaZoomLevel new_level; +    FMDesktopIconView *desktop_icon_view; + +    desktop_icon_view = FM_DESKTOP_ICON_VIEW (user_data); +    new_level = get_default_zoom_level (); + +    caja_icon_container_set_zoom_level (get_icon_container (desktop_icon_view), +                                        new_level); +} + +static gboolean +do_desktop_rescan (gpointer data) +{ +    FMDesktopIconView *desktop_icon_view; +    struct stat buf; + +    desktop_icon_view = FM_DESKTOP_ICON_VIEW (data); +    if (desktop_icon_view->details->pending_rescan) +    { +        return TRUE; +    } + +    if (stat (desktop_directory, &buf) == -1) +    { +        return TRUE; +    } + +    if (buf.st_ctime == desktop_dir_modify_time) +    { +        return TRUE; +    } + +    desktop_icon_view->details->pending_rescan = TRUE; + +    caja_directory_force_reload ( +        fm_directory_view_get_model ( +            FM_DIRECTORY_VIEW (desktop_icon_view))); +    return TRUE; +} + +static void +done_loading (GtkObject *DirectoryView, FMDesktopIconView *desktop_icon_view) +{ +    struct stat buf; + +    desktop_icon_view->details->pending_rescan = FALSE; +    if (stat (desktop_directory, &buf) == -1) +    { +        return; +    } + +    desktop_dir_modify_time = buf.st_ctime; +} + +/* This function is used because the CajaDirectory model does not + * exist always in the desktop_icon_view, so we wait until it has been + * instantiated. + */ +static void +delayed_init (FMDesktopIconView *desktop_icon_view) +{ +    /* Keep track of the load time. */ +    g_signal_connect_object (fm_directory_view_get_model (FM_DIRECTORY_VIEW (desktop_icon_view)), +                             "done_loading", +                             G_CALLBACK (done_loading), desktop_icon_view, 0); + +    /* Monitor desktop directory. */ +    desktop_icon_view->details->reload_desktop_timeout = +        g_timeout_add_seconds (RESCAN_TIMEOUT, do_desktop_rescan, desktop_icon_view); + +    g_signal_handler_disconnect (desktop_icon_view, +                                 desktop_icon_view->details->delayed_init_signal); + +    desktop_icon_view->details->delayed_init_signal = 0; +} + +static void +font_changed_callback (gpointer callback_data) +{ +    g_return_if_fail (FM_IS_DESKTOP_ICON_VIEW (callback_data)); + +    fm_desktop_icon_view_update_icon_container_fonts (FM_DESKTOP_ICON_VIEW (callback_data)); +} + +static void +fm_desktop_icon_view_update_icon_container_fonts (FMDesktopIconView *icon_view) +{ +    CajaIconContainer *icon_container; +    char *font; + +    icon_container = get_icon_container (icon_view); +    g_assert (icon_container != NULL); + +    font = eel_preferences_get (CAJA_PREFERENCES_DESKTOP_FONT); + +    caja_icon_container_set_font (icon_container, font); + +    g_free (font); +} + +static void +fm_desktop_icon_view_init (FMDesktopIconView *desktop_icon_view) +{ +    CajaIconContainer *icon_container; +    GtkAllocation allocation; +    GtkAdjustment *hadj, *vadj; + +    if (desktop_directory == NULL) +    { +        eel_preferences_add_callback (CAJA_PREFERENCES_DESKTOP_IS_HOME_DIR, +                                      desktop_directory_changed_callback, +                                      NULL); +        desktop_directory_changed_callback (NULL); +    } + +    fm_icon_view_filter_by_screen (FM_ICON_VIEW (desktop_icon_view), TRUE); +    icon_container = get_icon_container (desktop_icon_view); +    caja_icon_container_set_use_drop_shadows (icon_container, TRUE); +    fm_icon_container_set_sort_desktop (FM_ICON_CONTAINER (icon_container), TRUE); + +    /* Set up details */ +    desktop_icon_view->details = g_new0 (FMDesktopIconViewDetails, 1); + +    /* Do a reload on the desktop if we don't have FAM, a smarter +     * way to keep track of the items on the desktop. +     */ +    if (!caja_monitor_active ()) +    { +        desktop_icon_view->details->delayed_init_signal = g_signal_connect_object +                (desktop_icon_view, "begin_loading", +                 G_CALLBACK (delayed_init), desktop_icon_view, 0); +    } + +    caja_icon_container_set_is_fixed_size (icon_container, TRUE); +    caja_icon_container_set_is_desktop (icon_container, TRUE); +    caja_icon_container_set_store_layout_timestamps (icon_container, TRUE); + +    /* Set allocation to be at 0, 0 */ +    gtk_widget_get_allocation (GTK_WIDGET (icon_container), &allocation); +    allocation.x = 0; +    allocation.y = 0; +    gtk_widget_set_allocation (GTK_WIDGET (icon_container), &allocation); + +    gtk_widget_queue_resize (GTK_WIDGET (icon_container)); + +    hadj = gtk_layout_get_hadjustment (GTK_LAYOUT (icon_container)); +    vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (icon_container)); + +    eel_gtk_adjustment_set_value (hadj, 0); +    eel_gtk_adjustment_set_value (vadj, 0); + +    gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (desktop_icon_view), +                                         GTK_SHADOW_NONE); + +    fm_directory_view_ignore_hidden_file_preferences +    (FM_DIRECTORY_VIEW (desktop_icon_view)); + +    fm_directory_view_set_show_foreign (FM_DIRECTORY_VIEW (desktop_icon_view), +                                        FALSE); + +    /* Set our default layout mode */ +    caja_icon_container_set_layout_mode (icon_container, +                                         gtk_widget_get_direction (GTK_WIDGET(icon_container)) == GTK_TEXT_DIR_RTL ? +                                         CAJA_ICON_LAYOUT_T_B_R_L : +                                         CAJA_ICON_LAYOUT_T_B_L_R); + +    g_signal_connect_object (icon_container, "middle_click", +                             G_CALLBACK (fm_desktop_icon_view_handle_middle_click), desktop_icon_view, 0); +    g_signal_connect_object (desktop_icon_view, "realize", +                             G_CALLBACK (realized_callback), desktop_icon_view, 0); +    g_signal_connect_object (desktop_icon_view, "unrealize", +                             G_CALLBACK (unrealized_callback), desktop_icon_view, 0); + +    eel_preferences_add_callback (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_ZOOM_LEVEL, +                                  default_zoom_level_changed, +                                  desktop_icon_view); + +    eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_DESKTOP_FONT, +            font_changed_callback, +            desktop_icon_view, G_OBJECT (desktop_icon_view)); + +    default_zoom_level_changed (desktop_icon_view); +    fm_desktop_icon_view_update_icon_container_fonts (desktop_icon_view); + +    eel_preferences_add_callback (CAJA_PREFERENCES_LOCKDOWN_COMMAND_LINE, +                                  lockdown_disable_command_line_changed_callback, +                                  desktop_icon_view); + +} + +static void +action_new_launcher_callback (GtkAction *action, gpointer data) +{ +    char *desktop_directory; + +    g_assert (FM_DIRECTORY_VIEW (data)); + +    desktop_directory = caja_get_desktop_directory (); + +    caja_launch_application_from_command (gtk_widget_get_screen (GTK_WIDGET (data)), +                                          "mate-desktop-item-edit", +                                          "mate-desktop-item-edit", +                                          FALSE, +                                          "--create-new", desktop_directory, NULL); +    g_free (desktop_directory); + +} + +static void +action_change_background_callback (GtkAction *action, +                                   gpointer data) +{ +    g_assert (FM_DIRECTORY_VIEW (data)); + +    caja_launch_application_from_command (gtk_widget_get_screen (GTK_WIDGET (data)), +                                          _("Background"), +                                          "mate-appearance-properties", +                                          FALSE, +                                          "--show-page=background", NULL); +} + +static void +action_empty_trash_conditional_callback (GtkAction *action, +        gpointer data) +{ +    g_assert (FM_IS_DIRECTORY_VIEW (data)); + +    caja_file_operations_empty_trash (GTK_WIDGET (data)); +} + +static gboolean +trash_link_is_selection (FMDirectoryView *view) +{ +    GList *selection; +    CajaDesktopLink *link; +    gboolean result; + +    result = FALSE; + +    selection = fm_directory_view_get_selection (view); + +    if (eel_g_list_exactly_one_item (selection) && +            CAJA_IS_DESKTOP_ICON_FILE (selection->data)) +    { +        link = caja_desktop_icon_file_get_link (CAJA_DESKTOP_ICON_FILE (selection->data)); +        /* link may be NULL if the link was recently removed (unmounted) */ +        if (link != NULL && +                caja_desktop_link_get_link_type (link) == CAJA_DESKTOP_LINK_TRASH) +        { +            result = TRUE; +        } +        if (link) +        { +            g_object_unref (link); +        } +    } + +    caja_file_list_free (selection); + +    return result; +} + +static void +real_update_menus (FMDirectoryView *view) +{ +    FMDesktopIconView *desktop_view; +    char *label; +    gboolean disable_command_line; +    gboolean include_empty_trash; +    GtkAction *action; + +    g_assert (FM_IS_DESKTOP_ICON_VIEW (view)); + +    EEL_CALL_PARENT (FM_DIRECTORY_VIEW_CLASS, update_menus, (view)); + +    desktop_view = FM_DESKTOP_ICON_VIEW (view); + +    /* New Launcher */ +    disable_command_line = eel_preferences_get_boolean (CAJA_PREFERENCES_LOCKDOWN_COMMAND_LINE); +    action = gtk_action_group_get_action (desktop_view->details->desktop_action_group, +                                          FM_ACTION_NEW_LAUNCHER_DESKTOP); +    gtk_action_set_visible (action, +                            !disable_command_line); + +    /* Empty Trash */ +    include_empty_trash = trash_link_is_selection (view); +    action = gtk_action_group_get_action (desktop_view->details->desktop_action_group, +                                          FM_ACTION_EMPTY_TRASH_CONDITIONAL); +    gtk_action_set_visible (action, +                            include_empty_trash); +    if (include_empty_trash) +    { +        label = g_strdup (_("E_mpty Trash")); +        g_object_set (action , "label", label, NULL); +        gtk_action_set_sensitive (action, +                                  !caja_trash_monitor_is_empty ()); +        g_free (label); +    } +} + +static const GtkActionEntry desktop_view_entries[] = +{ +    /* name, stock id */ +    { +        "New Launcher Desktop", NULL, +        /* label, accelerator */ +        N_("Create L_auncher..."), NULL, +        /* tooltip */ +        N_("Create a new launcher"), +        G_CALLBACK (action_new_launcher_callback) +    }, +    /* name, stock id */ +    { +        "Change Background", NULL, +        /* label, accelerator */ +        N_("Change Desktop _Background"), NULL, +        /* tooltip */ +        N_("Show a window that lets you set your desktop background's pattern or color"), +        G_CALLBACK (action_change_background_callback) +    }, +    /* name, stock id */ +    { +        "Empty Trash Conditional", NULL, +        /* label, accelerator */ +        N_("Empty Trash"), NULL, +        /* tooltip */ +        N_("Delete all items in the Trash"), +        G_CALLBACK (action_empty_trash_conditional_callback) +    }, +}; + +static void +real_merge_menus (FMDirectoryView *view) +{ +    FMDesktopIconView *desktop_view; +    GtkUIManager *ui_manager; +    GtkActionGroup *action_group; +    const char *ui; + +    EEL_CALL_PARENT (FM_DIRECTORY_VIEW_CLASS, merge_menus, (view)); + +    desktop_view = FM_DESKTOP_ICON_VIEW (view); + +    ui_manager = fm_directory_view_get_ui_manager (view); + +    action_group = gtk_action_group_new ("DesktopViewActions"); +    gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE); +    desktop_view->details->desktop_action_group = action_group; +    gtk_action_group_add_actions (action_group, +                                  desktop_view_entries, G_N_ELEMENTS (desktop_view_entries), +                                  view); + +    gtk_ui_manager_insert_action_group (ui_manager, action_group, 0); +    g_object_unref (action_group); /* owned by ui manager */ + +    ui = caja_ui_string_get ("caja-desktop-icon-view-ui.xml"); +    desktop_view->details->desktop_merge_id = +        gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, NULL); +} + +static gboolean +real_supports_auto_layout (FMIconView *view) +{ +    /* Can't use auto-layout on the desktop, because doing so +     * would cause all sorts of complications involving the +     * fixed-size window. +     */ +    return FALSE; +} + +static gboolean +real_supports_scaling (FMIconView *view) +{ +    return TRUE; +} + +static gboolean +real_supports_keep_aligned (FMIconView *view) +{ +    return TRUE; +} + +static gboolean +real_supports_labels_beside_icons (FMIconView *view) +{ +    return FALSE; +} + +static gboolean +real_supports_zooming (FMDirectoryView *view) +{ +    /* Can't zoom on the desktop, because doing so would cause all +     * sorts of complications involving the fixed-size window. +     */ +    return FALSE; +} + +static CajaView * +fm_desktop_icon_view_create (CajaWindowSlotInfo *slot) +{ +    FMIconView *view; + +    view = g_object_new (FM_TYPE_DESKTOP_ICON_VIEW, +                         "window-slot", slot, +                         NULL); +    return CAJA_VIEW (view); +} + +static gboolean +fm_desktop_icon_view_supports_uri (const char *uri, +                                   GFileType file_type, +                                   const char *mime_type) +{ +    if (g_str_has_prefix (uri, EEL_DESKTOP_URI)) +    { +        return TRUE; +    } + +    return FALSE; +} + +static CajaViewInfo fm_desktop_icon_view = +{ +    FM_DESKTOP_ICON_VIEW_ID, +    "Desktop View", +    "_Desktop", +    N_("The desktop view encountered an error."), +    N_("The desktop view encountered an error while starting up."), +    "Display this location with the desktop view.", +    fm_desktop_icon_view_create, +    fm_desktop_icon_view_supports_uri +}; + +void +fm_desktop_icon_view_register (void) +{ +    fm_desktop_icon_view.error_label = _(fm_desktop_icon_view.error_label); +    fm_desktop_icon_view.startup_error_label = _(fm_desktop_icon_view.startup_error_label); + +    caja_view_factory_register (&fm_desktop_icon_view); +} diff --git a/src/file-manager/fm-desktop-icon-view.h b/src/file-manager/fm-desktop-icon-view.h new file mode 100644 index 00000000..f5702296 --- /dev/null +++ b/src/file-manager/fm-desktop-icon-view.h @@ -0,0 +1,60 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-icon-view.h - interface for icon view of directory. + +   Copyright (C) 2000 Eazel, Inc. + +   The Mate Library is free software; you can redistribute it and/or +   modify it under the terms of the GNU Library General Public License as +   published by the Free Software Foundation; either version 2 of the +   License, or (at your option) any later version. + +   The Mate Library is distributed in the hope that it will be useful, +   but WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +   Library General Public License for more details. + +   You should have received a copy of the GNU Library General Public +   License along with the Mate Library; see the file COPYING.LIB.  If not, +   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +   Boston, MA 02111-1307, USA. + +   Authors: Mike Engber <[email protected]> +*/ + +#ifndef FM_DESKTOP_ICON_VIEW_H +#define FM_DESKTOP_ICON_VIEW_H + +#include "fm-icon-view.h" + +#define FM_TYPE_DESKTOP_ICON_VIEW fm_desktop_icon_view_get_type() +#define FM_DESKTOP_ICON_VIEW(obj) \ +  (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_DESKTOP_ICON_VIEW, FMDesktopIconView)) +#define FM_DESKTOP_ICON_VIEW_CLASS(klass) \ +  (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_DESKTOP_ICON_VIEW, FMDesktopIconViewClass)) +#define FM_IS_DESKTOP_ICON_VIEW(obj) \ +  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_DESKTOP_ICON_VIEW)) +#define FM_IS_DESKTOP_ICON_VIEW_CLASS(klass) \ +  (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_DESKTOP_ICON_VIEW)) +#define FM_DESKTOP_ICON_VIEW_GET_CLASS(obj) \ +  (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_DESKTOP_ICON_VIEW, FMDesktopIconViewClass)) + +#define FM_DESKTOP_ICON_VIEW_ID "OAFIID:Caja_File_Manager_Desktop_Icon_View" + +typedef struct FMDesktopIconViewDetails FMDesktopIconViewDetails; +typedef struct +{ +    FMIconView parent; +    FMDesktopIconViewDetails *details; +} FMDesktopIconView; + +typedef struct +{ +    FMIconViewClass parent_class; +} FMDesktopIconViewClass; + +/* GObject support */ +GType   fm_desktop_icon_view_get_type (void); +void fm_desktop_icon_view_register (void); + +#endif /* FM_DESKTOP_ICON_VIEW_H */ diff --git a/src/file-manager/fm-directory-view.c b/src/file-manager/fm-directory-view.c new file mode 100644 index 00000000..a192aa58 --- /dev/null +++ b/src/file-manager/fm-directory-view.c @@ -0,0 +1,10936 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-directory-view.c + * + * Copyright (C) 1999, 2000  Free Software Foundation + * Copyright (C) 2000, 2001  Eazel, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Ettore Perazzoli, + *          John Sullivan <[email protected]>, + *          Darin Adler <[email protected]>, + *          Pavel Cisler <[email protected]>, + *          David Emory Watson <[email protected]> + */ + +#include <config.h> +#include <math.h> +#include "fm-directory-view.h" +#include "fm-list-view.h" +#include "fm-desktop-icon-view.h" + +#include "fm-actions.h" +#include "fm-error-reporting.h" +#include "fm-properties-window.h" +#include "libcaja-private/caja-open-with-dialog.h" + +#include <eel/eel-background.h> +#include <eel/eel-glib-extensions.h> +#include <eel/eel-mate-extensions.h> +#include <eel/eel-gtk-extensions.h> +#include <eel/eel-gtk-macros.h> +#include <eel/eel-stock-dialogs.h> +#include <eel/eel-string.h> +#include <eel/eel-vfs-extensions.h> +#include <eel/eel-marshal.h> +#include <gdk/gdkkeysyms.h> +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> +#include <gio/gio.h> +#include <libcaja-private/caja-recent.h> +#include <libcaja-extension/caja-menu-provider.h> +#include <libcaja-private/caja-clipboard.h> +#include <libcaja-private/caja-clipboard-monitor.h> +#include <libcaja-private/caja-debug-log.h> +#include <libcaja-private/caja-desktop-icon-file.h> +#include <libcaja-private/caja-desktop-directory.h> +#include <libcaja-private/caja-search-directory.h> +#include <libcaja-private/caja-directory-background.h> +#include <libcaja-private/caja-directory.h> +#include <libcaja-private/caja-dnd.h> +#include <libcaja-private/caja-file-attributes.h> +#include <libcaja-private/caja-file-changes-queue.h> +#include <libcaja-private/caja-file-dnd.h> +#include <libcaja-private/caja-file-operations.h> +#include <libcaja-private/caja-file-utilities.h> +#include <libcaja-private/caja-file-private.h> /* for caja_file_get_existing_by_uri */ +#include <libcaja-private/caja-global-preferences.h> +#include <libcaja-private/caja-link.h> +#include <libcaja-private/caja-marshal.h> +#include <libcaja-private/caja-metadata.h> +#include <libcaja-private/caja-mime-actions.h> +#include <libcaja-private/caja-module.h> +#include <libcaja-private/caja-program-choosing.h> +#include <libcaja-private/caja-trash-monitor.h> +#include <libcaja-private/caja-ui-utilities.h> +#include <libcaja-private/caja-signaller.h> +#include <libcaja-private/caja-autorun.h> +#include <libcaja-private/caja-icon-names.h> + +/* Minimum starting update inverval */ +#define UPDATE_INTERVAL_MIN 100 +/* Maximum update interval */ +#define UPDATE_INTERVAL_MAX 2000 +/* Amount of miliseconds the update interval is increased */ +#define UPDATE_INTERVAL_INC 250 +/* Interval at which the update interval is increased */ +#define UPDATE_INTERVAL_TIMEOUT_INTERVAL 250 +/* Milliseconds that have to pass without a change to reset the update interval */ +#define UPDATE_INTERVAL_RESET 1000 + +#define SILENT_WINDOW_OPEN_LIMIT 5 + +#define DUPLICATE_HORIZONTAL_ICON_OFFSET 70 +#define DUPLICATE_VERTICAL_ICON_OFFSET   30 + +#define MAX_QUEUED_UPDATES 500 + +#define FM_DIRECTORY_VIEW_MENU_PATH_APPLICATIONS_SUBMENU_PLACEHOLDER   	"/MenuBar/File/Open Placeholder/Open With/Applications Placeholder" +#define FM_DIRECTORY_VIEW_MENU_PATH_APPLICATIONS_PLACEHOLDER    	"/MenuBar/File/Open Placeholder/Applications Placeholder" +#define FM_DIRECTORY_VIEW_MENU_PATH_SCRIPTS_PLACEHOLDER    		"/MenuBar/File/Open Placeholder/Scripts/Scripts Placeholder" +#define FM_DIRECTORY_VIEW_MENU_PATH_EXTENSION_ACTIONS_PLACEHOLDER       "/MenuBar/Edit/Extension Actions" +#define FM_DIRECTORY_VIEW_MENU_PATH_NEW_DOCUMENTS_PLACEHOLDER  		"/MenuBar/File/New Items Placeholder/New Documents/New Documents Placeholder" +#define FM_DIRECTORY_VIEW_MENU_PATH_OPEN				"/MenuBar/File/Open Placeholder/Open" + +#define FM_DIRECTORY_VIEW_POPUP_PATH_SELECTION				"/selection" +#define FM_DIRECTORY_VIEW_POPUP_PATH_APPLICATIONS_SUBMENU_PLACEHOLDER  	"/selection/Open Placeholder/Open With/Applications Placeholder" +#define FM_DIRECTORY_VIEW_POPUP_PATH_APPLICATIONS_PLACEHOLDER    	"/selection/Open Placeholder/Applications Placeholder" +#define FM_DIRECTORY_VIEW_POPUP_PATH_SCRIPTS_PLACEHOLDER    		"/selection/Open Placeholder/Scripts/Scripts Placeholder" +#define FM_DIRECTORY_VIEW_POPUP_PATH_EXTENSION_ACTIONS			"/selection/Extension Actions" +#define FM_DIRECTORY_VIEW_POPUP_PATH_OPEN				"/selection/Open Placeholder/Open" + +#define FM_DIRECTORY_VIEW_POPUP_PATH_BACKGROUND				"/background" +#define FM_DIRECTORY_VIEW_POPUP_PATH_BACKGROUND_SCRIPTS_PLACEHOLDER	"/background/Before Zoom Items/New Object Items/Scripts/Scripts Placeholder" +#define FM_DIRECTORY_VIEW_POPUP_PATH_BACKGROUND_NEW_DOCUMENTS_PLACEHOLDER "/background/Before Zoom Items/New Object Items/New Documents/New Documents Placeholder" + +#define FM_DIRECTORY_VIEW_POPUP_PATH_LOCATION				"/location" + +#define MAX_MENU_LEVELS 5 +#define TEMPLATE_LIMIT 30 + +enum { +	ADD_FILE, +	BEGIN_FILE_CHANGES, +	BEGIN_LOADING, +	CLEAR, +	END_FILE_CHANGES, +	FLUSH_ADDED_FILES, +	END_LOADING, +	FILE_CHANGED, +	LOAD_ERROR, +	MOVE_COPY_ITEMS, +	REMOVE_FILE, +	TRASH, +	DELETE, +	LAST_SIGNAL +}; + +enum +{ +  PROP_0, +  PROP_WINDOW_SLOT +}; + + +static guint signals[LAST_SIGNAL]; + +static GdkAtom copied_files_atom; + +static gboolean show_delete_command_auto_value; +static gboolean confirm_trash_auto_value; + +static char *scripts_directory_uri; +static int scripts_directory_uri_length; + +struct FMDirectoryViewDetails +{ +	CajaWindowInfo *window; +	CajaWindowSlotInfo *slot; +	CajaDirectory *model; +	CajaFile *directory_as_file; +	CajaFile *location_popup_directory_as_file; +	GdkEventButton *location_popup_event; +	GtkActionGroup *dir_action_group; +	guint dir_merge_id; + +	GList *scripts_directory_list; +	GtkActionGroup *scripts_action_group; +	guint scripts_merge_id; + +	GList *templates_directory_list; +	GtkActionGroup *templates_action_group; +	guint templates_merge_id; + +	GtkActionGroup *extensions_menu_action_group; +	guint extensions_menu_merge_id; + +	guint display_selection_idle_id; +	guint update_menus_timeout_id; +	guint update_status_idle_id; +	guint reveal_selection_idle_id; + +	guint display_pending_source_id; +	guint changes_timeout_id; + +	guint update_interval; + 	guint64 last_queued; + +	guint files_added_handler_id; +	guint files_changed_handler_id; +	guint load_error_handler_id; +	guint done_loading_handler_id; +	guint file_changed_handler_id; + +	guint delayed_rename_file_id; + +	GList *new_added_files; +	GList *new_changed_files; + +	GHashTable *non_ready_files; + +	GList *old_added_files; +	GList *old_changed_files; + +	GList *pending_locations_selected; + +	/* whether we are in the active slot */ +	gboolean active; + +	/* loading indicates whether this view has begun loading a directory. +	 * This flag should need not be set inside subclasses. FMDirectoryView automatically +	 * sets 'loading' to TRUE before it begins loading a directory's contents and to FALSE +	 * after it finishes loading the directory and its view. +	 */ +	gboolean loading; +	gboolean menu_states_untrustworthy; +	gboolean scripts_invalid; +	gboolean templates_invalid; +	gboolean reported_load_error; + +	/* flag to indicate that no file updates should be dispatched to subclasses. +	 * This is a workaround for bug #87701 that prevents the list view from +	 * losing focus when the underlying GtkTreeView is updated. +	 */ +	gboolean updates_frozen; +	guint	 updates_queued; +	gboolean needs_reload; + +	gboolean sort_directories_first; + +	gboolean show_foreign_files; +	gboolean show_hidden_files; +	gboolean show_backup_files; +	gboolean ignore_hidden_file_preferences; + +	gboolean batching_selection_level; +	gboolean selection_changed_while_batched; + +	gboolean selection_was_removed; + +	gboolean metadata_for_directory_as_file_pending; +	gboolean metadata_for_files_in_directory_pending; + +	gboolean selection_change_is_due_to_shell; +	gboolean send_selection_change_to_shell; + +	GtkActionGroup *open_with_action_group; +	guint open_with_merge_id; + +	GList *subdirectory_list; + +	gboolean allow_moves; + +	GdkPoint context_menu_position; +}; + +typedef struct { +	CajaFile *file; +	CajaDirectory *directory; +} FileAndDirectory; + +/* forward declarations */ + +static gboolean display_selection_info_idle_callback           (gpointer              data); +static void     fm_directory_view_class_init                   (FMDirectoryViewClass *klass); +static void     fm_directory_view_init                         (FMDirectoryView      *view); +static void     fm_directory_view_duplicate_selection          (FMDirectoryView      *view, +								GList                *files, +								GArray               *item_locations); +static void     fm_directory_view_create_links_for_files       (FMDirectoryView      *view, +								GList                *files, +								GArray               *item_locations); +static void     trash_or_delete_files                          (GtkWindow            *parent_window, +								const GList          *files, +								gboolean              delete_if_all_already_in_trash, +								FMDirectoryView      *view); +static void     load_directory                                 (FMDirectoryView      *view, +								CajaDirectory    *directory); +static void     fm_directory_view_merge_menus                  (FMDirectoryView      *view); +static void     fm_directory_view_unmerge_menus                (FMDirectoryView      *view); +static void     fm_directory_view_init_show_hidden_files       (FMDirectoryView      *view); +static void     fm_directory_view_load_location                (CajaView         *caja_view, +								const char           *location); +static void     fm_directory_view_stop_loading                 (CajaView         *caja_view); +static void     fm_directory_view_drop_proxy_received_uris     (FMDirectoryView *view, +								const GList *source_uri_list, +								const char *target_uri, +								GdkDragAction action); +static void     fm_directory_view_drop_proxy_received_netscape_url (FMDirectoryView *view, +								    const char *netscape_url, +								    const char *target_uri, +								    GdkDragAction action); +static void     clipboard_changed_callback                     (CajaClipboardMonitor *monitor, +								FMDirectoryView      *view); +static void     open_one_in_new_window                         (gpointer              data, +								gpointer              callback_data); +static void     open_one_in_folder_window                      (gpointer              data, +								gpointer              callback_data); +static void     schedule_update_menus                          (FMDirectoryView      *view); +static void     schedule_update_menus_callback                 (gpointer              callback_data); +static void     remove_update_menus_timeout_callback           (FMDirectoryView      *view); +static void     schedule_update_status                          (FMDirectoryView      *view); +static void     remove_update_status_idle_callback             (FMDirectoryView *view); +static void     reset_update_interval                          (FMDirectoryView      *view); +static void     schedule_idle_display_of_pending_files         (FMDirectoryView      *view); +static void     unschedule_display_of_pending_files            (FMDirectoryView      *view); +static void     disconnect_model_handlers                      (FMDirectoryView      *view); +static void     metadata_for_directory_as_file_ready_callback  (CajaFile         *file, +								gpointer              callback_data); +static void     metadata_for_files_in_directory_ready_callback (CajaDirectory    *directory, +								GList                *files, +								gpointer              callback_data); +static void     fm_directory_view_trash_state_changed_callback (CajaTrashMonitor *trash, +								gboolean              state, +								gpointer              callback_data); +static void     fm_directory_view_select_file                  (FMDirectoryView      *view, +								CajaFile         *file); + +static GdkDragAction ask_link_action                           (FMDirectoryView      *view); +static void     update_templates_directory                     (FMDirectoryView *view); +static void     user_dirs_changed                              (FMDirectoryView *view); +static void     fm_directory_view_set_is_active                (FMDirectoryView *view, +								gboolean         is_active); + +static gboolean file_list_all_are_folders                      (GList *file_list); + +static void action_open_scripts_folder_callback    (GtkAction *action, +						    gpointer   callback_data); +static void action_cut_files_callback              (GtkAction *action, +						    gpointer   callback_data); +static void action_copy_files_callback             (GtkAction *action, +						    gpointer   callback_data); +static void action_paste_files_callback            (GtkAction *action, +						    gpointer   callback_data); +static void action_copy_to_next_pane_callback      (GtkAction *action, +						    gpointer   callback_data); +static void action_move_to_next_pane_callback      (GtkAction *action, +						    gpointer   callback_data); +static void action_rename_callback                 (GtkAction *action, +						    gpointer   callback_data); +static void action_rename_select_all_callback      (GtkAction *action, +						    gpointer   callback_data); +static void action_paste_files_into_callback       (GtkAction *action, +						    gpointer   callback_data); +static void action_connect_to_server_link_callback (GtkAction *action, +						    gpointer   data); +static void action_mount_volume_callback           (GtkAction *action, +						    gpointer   data); +static void action_unmount_volume_callback         (GtkAction *action, +						    gpointer   data); +static void action_format_volume_callback          (GtkAction *action, +						    gpointer   data); +static void action_start_volume_callback           (GtkAction *action, +						    gpointer   data); +static void action_stop_volume_callback            (GtkAction *action, +						    gpointer   data); +static void action_detect_media_callback           (GtkAction *action, +						    gpointer   data); + +/* location popup-related actions */ + +static void action_location_open_alternate_callback (GtkAction *action, +						     gpointer   callback_data); +static void action_location_open_folder_window_callback (GtkAction *action, +							 gpointer   callback_data); + +static void action_location_cut_callback            (GtkAction *action, +						     gpointer   callback_data); +static void action_location_copy_callback           (GtkAction *action, +						     gpointer   callback_data); +static void action_location_trash_callback          (GtkAction *action, +						     gpointer   callback_data); +static void action_location_delete_callback         (GtkAction *action, +						     gpointer   callback_data); +static void action_location_properties_callback     (GtkAction *action, +						     gpointer   callback_data); + +static void unschedule_pop_up_location_context_menu (FMDirectoryView *view); + +static inline void fm_directory_view_widget_to_file_operation_position (FMDirectoryView *view, +									GdkPoint *position); +static void        fm_directory_view_widget_to_file_operation_position_xy (FMDirectoryView *view, +									   int *x, int *y); + +EEL_CLASS_BOILERPLATE (FMDirectoryView, fm_directory_view, GTK_TYPE_SCROLLED_WINDOW) + +EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, add_file) +EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, bump_zoom_level) +EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, can_zoom_in) +EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, can_zoom_out) +EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, clear) +EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, file_changed) +EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, get_background_widget) +EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, get_selection) +EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, get_selection_for_file_transfer) +EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, get_item_count) +EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, is_empty) +EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, reset_to_defaults) +EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, restore_default_zoom_level) +EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, select_all) +EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, set_selection) +EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, zoom_to_level) +EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, get_zoom_level) +EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, invert_selection) + +typedef struct { +	GAppInfo *application; +	GList *files; +	FMDirectoryView *directory_view; +} ApplicationLaunchParameters; + +typedef struct { +	CajaFile *file; +	FMDirectoryView *directory_view; +} ScriptLaunchParameters; + +typedef struct { +	CajaFile *file; +	FMDirectoryView *directory_view; +} CreateTemplateParameters; + +static ApplicationLaunchParameters * +application_launch_parameters_new (GAppInfo *application, +			      	   GList *files, +			           FMDirectoryView *directory_view) +{ +	ApplicationLaunchParameters *result; + +	result = g_new0 (ApplicationLaunchParameters, 1); +	result->application = g_object_ref (application); +	result->files = caja_file_list_copy (files); + +	if (directory_view != NULL) { +		g_object_ref (directory_view); +		result->directory_view = directory_view; +	} + +	return result; +} + +static void +application_launch_parameters_free (ApplicationLaunchParameters *parameters) +{ +	g_object_unref (parameters->application); +	caja_file_list_free (parameters->files); + +	if (parameters->directory_view != NULL) { +		g_object_unref (parameters->directory_view); +	} + +	g_free (parameters); +} + +static GList * +file_and_directory_list_to_files (GList *fad_list) +{ +	GList *res, *l; +	FileAndDirectory *fad; + +	res = NULL; +	for (l = fad_list; l != NULL; l = l->next) { +		fad = l->data; +		res = g_list_prepend (res, caja_file_ref (fad->file)); +	} +	return g_list_reverse (res); +} + + +static GList * +file_and_directory_list_from_files (CajaDirectory *directory, GList *files) +{ +	GList *res, *l; +	FileAndDirectory *fad; + +	res = NULL; +	for (l = files; l != NULL; l = l->next) { +		fad = g_new0 (FileAndDirectory, 1); +		fad->directory = caja_directory_ref (directory); +		fad->file = caja_file_ref (l->data); +		res = g_list_prepend (res, fad); +	} +	return g_list_reverse (res); +} + +static void +file_and_directory_free (FileAndDirectory *fad) +{ +	caja_directory_unref (fad->directory); +	caja_file_unref (fad->file); +	g_free (fad); +} + + +static void +file_and_directory_list_free (GList *list) +{ +	GList *l; + +	for (l = list; l != NULL; l = l->next) { +		file_and_directory_free (l->data); +	} + +	g_list_free (list); +} + +static gboolean +file_and_directory_equal (gconstpointer  v1, +			  gconstpointer  v2) +{ +	const FileAndDirectory *fad1, *fad2; +	fad1 = v1; +	fad2 = v2; + +	return (fad1->file == fad2->file && +		fad1->directory == fad2->directory); +} + +static guint +file_and_directory_hash  (gconstpointer  v) +{ +	const FileAndDirectory *fad; + +	fad = v; +	return GPOINTER_TO_UINT (fad->file) ^ GPOINTER_TO_UINT (fad->directory); +} + + + + +static ScriptLaunchParameters * +script_launch_parameters_new (CajaFile *file, +			      FMDirectoryView *directory_view) +{ +	ScriptLaunchParameters *result; + +	result = g_new0 (ScriptLaunchParameters, 1); +	g_object_ref (directory_view); +	result->directory_view = directory_view; +	caja_file_ref (file); +	result->file = file; + +	return result; +} + +static void +script_launch_parameters_free (ScriptLaunchParameters *parameters) +{ +	g_object_unref (parameters->directory_view); +	caja_file_unref (parameters->file); +	g_free (parameters); +} + +static CreateTemplateParameters * +create_template_parameters_new (CajaFile *file, +				FMDirectoryView *directory_view) +{ +	CreateTemplateParameters *result; + +	result = g_new0 (CreateTemplateParameters, 1); +	g_object_ref (directory_view); +	result->directory_view = directory_view; +	caja_file_ref (file); +	result->file = file; + +	return result; +} + +static void +create_templates_parameters_free (CreateTemplateParameters *parameters) +{ +	g_object_unref (parameters->directory_view); +	caja_file_unref (parameters->file); +	g_free (parameters); +} + +CajaWindowInfo * +fm_directory_view_get_caja_window (FMDirectoryView  *view) +{ +	g_assert (view->details->window != NULL); + +	return view->details->window; +} + +CajaWindowSlotInfo * +fm_directory_view_get_caja_window_slot (FMDirectoryView  *view) +{ +	g_assert (view->details->slot != NULL); + +	return view->details->slot; +} + +/* Returns the GtkWindow that this directory view occupies, or NULL + * if at the moment this directory view is not in a GtkWindow or the + * GtkWindow cannot be determined. Primarily used for parenting dialogs. + */ +GtkWindow * +fm_directory_view_get_containing_window (FMDirectoryView *view) +{ +	GtkWidget *window; + +	g_assert (FM_IS_DIRECTORY_VIEW (view)); + +	window = gtk_widget_get_ancestor (GTK_WIDGET (view), GTK_TYPE_WINDOW); +	if (window == NULL) { +		return NULL; +	} + +	return GTK_WINDOW (window); +} + +static gboolean +fm_directory_view_confirm_multiple (GtkWindow *parent_window, +				    int count, +				    gboolean tabs) +{ +	GtkDialog *dialog; +	char *prompt; +	char *detail; +	int response; + +	if (count <= SILENT_WINDOW_OPEN_LIMIT) { +		return TRUE; +	} + +	prompt = _("Are you sure you want to open all files?"); +	if (tabs) { +		detail = g_strdup_printf (ngettext("This will open %'d separate tab.", +						   "This will open %'d separate tabs.", count), count); +	} else { +		detail = g_strdup_printf (ngettext("This will open %'d separate window.", +						   "This will open %'d separate windows.", count), count); +	} +	dialog = eel_show_yes_no_dialog (prompt, detail, +					 GTK_STOCK_OK, GTK_STOCK_CANCEL, +					 parent_window); +	g_free (detail); + +	response = gtk_dialog_run (dialog); +	gtk_object_destroy (GTK_OBJECT (dialog)); + +	return response == GTK_RESPONSE_YES; +} + +static gboolean +selection_contains_one_item_in_menu_callback (FMDirectoryView *view, GList *selection) +{ +	if (eel_g_list_exactly_one_item (selection)) { +		return TRUE; +	} + +	/* If we've requested a menu update that hasn't yet occurred, then +	 * the mismatch here doesn't surprise us, and we won't complain. +	 * Otherwise, we will complain. +	 */ +	if (!view->details->menu_states_untrustworthy) { +		g_warning ("Expected one selected item, found %'d. No action will be performed.", +			   g_list_length (selection)); +	} + +	return FALSE; +} + +static gboolean +selection_not_empty_in_menu_callback (FMDirectoryView *view, GList *selection) +{ +	if (selection != NULL) { +		return TRUE; +	} + +	/* If we've requested a menu update that hasn't yet occurred, then +	 * the mismatch here doesn't surprise us, and we won't complain. +	 * Otherwise, we will complain. +	 */ +	if (!view->details->menu_states_untrustworthy) { +		g_warning ("Empty selection found when selection was expected. No action will be performed."); +	} + +	return FALSE; +} + +static char * +get_view_directory (FMDirectoryView *view) +{ +	char *uri, *path; +	GFile *f; + +	uri = caja_directory_get_uri (view->details->model); +	if (eel_uri_is_desktop (uri)) { +		g_free (uri); +		uri = caja_get_desktop_directory_uri (); + +	} +	f = g_file_new_for_uri (uri); +	path = g_file_get_path (f); +	g_object_unref (f); +	g_free (uri); + +	return path; +} + +void +fm_directory_view_activate_files (FMDirectoryView *view, +				  GList *files, +				  CajaWindowOpenMode mode, +				  CajaWindowOpenFlags flags, +				  gboolean confirm_multiple) +{ +	char *path; + +	path = get_view_directory (view); +	caja_mime_activate_files (fm_directory_view_get_containing_window (view), +				      view->details->slot, +				      files, +				      path, +				      mode, +				      flags, +				      confirm_multiple); + +	g_free (path); +} + +void +fm_directory_view_activate_file (FMDirectoryView *view, +				 CajaFile *file, +				 CajaWindowOpenMode mode, +				 CajaWindowOpenFlags flags) +{ +	char *path; + +	path = get_view_directory (view); +	caja_mime_activate_file (fm_directory_view_get_containing_window (view), +				     view->details->slot, +				     file, +				     path, +				     mode, +				     flags); + +	g_free (path); +} + +static void +action_open_callback (GtkAction *action, +		      gpointer callback_data) +{ +	GList *selection; +	FMDirectoryView *view; + +	view = FM_DIRECTORY_VIEW (callback_data); + +	selection = fm_directory_view_get_selection (view); +	fm_directory_view_activate_files (view, +					  selection, +					  CAJA_WINDOW_OPEN_ACCORDING_TO_MODE, +					  0, +					  TRUE); +	caja_file_list_free (selection); +} + +static void +action_open_close_parent_callback (GtkAction *action, +				   gpointer callback_data) +{ +	GList *selection; +	FMDirectoryView *view; + +	view = FM_DIRECTORY_VIEW (callback_data); + +	selection = fm_directory_view_get_selection (view); +	fm_directory_view_activate_files (view, +					  selection, +					  CAJA_WINDOW_OPEN_ACCORDING_TO_MODE, +					  CAJA_WINDOW_OPEN_FLAG_CLOSE_BEHIND, +					  TRUE); +	caja_file_list_free (selection); +} + + +static void +action_open_alternate_callback (GtkAction *action, +				gpointer callback_data) +{ +	FMDirectoryView *view; +	GList *selection; +	GtkWindow *window; + +	view = FM_DIRECTORY_VIEW (callback_data); +	selection = fm_directory_view_get_selection (view); + +	window = fm_directory_view_get_containing_window (view); + +	if (fm_directory_view_confirm_multiple (window, g_list_length (selection), FALSE)) { +		g_list_foreach (selection, open_one_in_new_window, view); +	} + +	caja_file_list_free (selection); +} + +static void +action_open_new_tab_callback (GtkAction *action, +			      gpointer callback_data) +{ +	FMDirectoryView *view; +	GList *selection; +	GtkWindow *window; + +	view = FM_DIRECTORY_VIEW (callback_data); +	selection = fm_directory_view_get_selection (view); + +	window = fm_directory_view_get_containing_window (view); + +	if (fm_directory_view_confirm_multiple (window, g_list_length (selection), TRUE)) { +		fm_directory_view_activate_files (view, +						  selection, +						  CAJA_WINDOW_OPEN_ACCORDING_TO_MODE, +						  CAJA_WINDOW_OPEN_FLAG_NEW_TAB, +						  FALSE); +	} + +	caja_file_list_free (selection); +} + +static void +action_open_folder_window_callback (GtkAction *action, +				gpointer callback_data) +{ +	FMDirectoryView *view; +	GList *selection; +	GtkWindow *window; + +	view = FM_DIRECTORY_VIEW (callback_data); +	selection = fm_directory_view_get_selection (view); + +	window = fm_directory_view_get_containing_window (view); + +	if (fm_directory_view_confirm_multiple (window, g_list_length (selection), FALSE)) { +		g_list_foreach (selection, open_one_in_folder_window, view); +	} + +	caja_file_list_free (selection); +} + +static void +open_location (FMDirectoryView *directory_view, +	       const char *new_uri, +	       CajaWindowOpenMode mode, +	       CajaWindowOpenFlags flags) +{ +	GtkWindow *window; +	GFile *location; + +	g_assert (FM_IS_DIRECTORY_VIEW (directory_view)); +	g_assert (new_uri != NULL); + +	window = fm_directory_view_get_containing_window (directory_view); +	caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, +			    "directory view open_location window=%p: %s", window, new_uri); +	location = g_file_new_for_uri (new_uri); +	caja_window_slot_info_open_location (directory_view->details->slot, +						 location, mode, flags, NULL); +	g_object_unref (location); +} + +static void +application_selected_cb (CajaOpenWithDialog *dialog, +			 GAppInfo *app, +			 gpointer user_data) +{ +	GtkWindow *parent_window; +	CajaFile *file; +	GList files; + +	parent_window = GTK_WINDOW (user_data); + +	file = g_object_get_data (G_OBJECT (dialog), "directory-view:file"); + +	files.next = NULL; +	files.prev = NULL; +	files.data = file; +	caja_launch_application (app, &files, parent_window); +} + +static void +choose_program (FMDirectoryView *view, +		CajaFile *file) +{ +	GtkWidget *dialog; +	char *uri; +	char *mime_type; + +	g_assert (FM_IS_DIRECTORY_VIEW (view)); +	g_assert (CAJA_IS_FILE (file)); + +	caja_file_ref (file); +	uri = caja_file_get_uri (file); +	mime_type = caja_file_get_mime_type (file); + +	dialog = caja_open_with_dialog_new (uri, mime_type, NULL); +	g_object_set_data_full (G_OBJECT (dialog), +				"directory-view:file", +				g_object_ref (file), +				(GDestroyNotify)g_object_unref); + +	gtk_window_set_screen (GTK_WINDOW (dialog), +			       gtk_widget_get_screen (GTK_WIDGET (view))); +	gtk_widget_show (dialog); + +	g_signal_connect_object (dialog, +				 "application_selected", +				 G_CALLBACK (application_selected_cb), +				 fm_directory_view_get_containing_window (view), +				 0); + + 	g_free (uri); +	g_free (mime_type); +	caja_file_unref (file); +} + +static void +open_with_other_program (FMDirectoryView *view) +{ +        GList *selection; + +	g_assert (FM_IS_DIRECTORY_VIEW (view)); + +       	selection = fm_directory_view_get_selection (view); + +	if (selection_contains_one_item_in_menu_callback (view, selection)) { +		choose_program (view, CAJA_FILE (selection->data)); +	} + +	caja_file_list_free (selection); +} + +static void +action_other_application_callback (GtkAction *action, +				   gpointer callback_data) +{ +	g_assert (FM_IS_DIRECTORY_VIEW (callback_data)); + +	open_with_other_program (FM_DIRECTORY_VIEW (callback_data)); +} + +static void +trash_or_delete_selected_files (FMDirectoryView *view) +{ +        GList *selection; + +	/* This might be rapidly called multiple times for the same selection +	 * when using keybindings. So we remember if the current selection +	 * was already removed (but the view doesn't know about it yet). +	 */ +	if (!view->details->selection_was_removed) { +		selection = fm_directory_view_get_selection_for_file_transfer (view); +		trash_or_delete_files (fm_directory_view_get_containing_window (view), +				       selection, TRUE, +				       view); +		caja_file_list_free (selection); +		view->details->selection_was_removed = TRUE; +	} +} + +static gboolean +real_trash (FMDirectoryView *view) +{ +	GtkAction *action; + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_TRASH); +	if (gtk_action_get_sensitive (action) && +	    gtk_action_get_visible (action)) { +		trash_or_delete_selected_files (view); +		return TRUE; +	} +	return FALSE; +} + +static void +action_trash_callback (GtkAction *action, +		       gpointer callback_data) +{ +        trash_or_delete_selected_files (FM_DIRECTORY_VIEW (callback_data)); +} + +static void +delete_selected_files (FMDirectoryView *view) +{ +        GList *selection; +	GList *node; +	GList *locations; + +	selection = fm_directory_view_get_selection_for_file_transfer (view); +	if (selection == NULL) { +		return; +	} + +	locations = NULL; +	for (node = selection; node != NULL; node = node->next) { +		locations = g_list_prepend (locations, +					    caja_file_get_location ((CajaFile *) node->data)); +	} +	locations = g_list_reverse (locations); + +	caja_file_operations_delete (locations, fm_directory_view_get_containing_window (view), NULL, NULL); + +	eel_g_object_list_free (locations); +        caja_file_list_free (selection); +} + +static void +action_delete_callback (GtkAction *action, +			gpointer callback_data) +{ +        delete_selected_files (FM_DIRECTORY_VIEW (callback_data)); +} + +static void +action_restore_from_trash_callback (GtkAction *action, +				    gpointer callback_data) +{ +	FMDirectoryView *view; +	GList *selection; + +	view = FM_DIRECTORY_VIEW (callback_data); + +	selection = fm_directory_view_get_selection_for_file_transfer (view); +	caja_restore_files_from_trash (selection, +					   fm_directory_view_get_containing_window (view)); + +	caja_file_list_free (selection); + +} + +static gboolean +real_delete (FMDirectoryView *view) +{ +	GtkAction *action; + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_DELETE); +	if (gtk_action_get_sensitive (action) && +	    gtk_action_get_visible (action)) { +		delete_selected_files (view); +		return TRUE; +	} +	return FALSE; +} + +static void +action_duplicate_callback (GtkAction *action, +			   gpointer callback_data) +{ +        FMDirectoryView *view; +        GList *selection; +        GArray *selected_item_locations; + +        view = FM_DIRECTORY_VIEW (callback_data); +	selection = fm_directory_view_get_selection_for_file_transfer (view); +	if (selection_not_empty_in_menu_callback (view, selection)) { +		/* FIXME bugzilla.gnome.org 45061: +		 * should change things here so that we use a get_icon_locations (view, selection). +		 * Not a problem in this case but in other places the selection may change by +		 * the time we go and retrieve the icon positions, relying on the selection +		 * staying intact to ensure the right sequence and count of positions is fragile. +		 */ +		selected_item_locations = fm_directory_view_get_selected_icon_locations (view); +	        fm_directory_view_duplicate_selection (view, selection, selected_item_locations); +	        g_array_free (selected_item_locations, TRUE); +	} + +        caja_file_list_free (selection); +} + +static void +action_create_link_callback (GtkAction *action, +			     gpointer callback_data) +{ +        FMDirectoryView *view; +        GList *selection; +        GArray *selected_item_locations; + +        g_assert (FM_IS_DIRECTORY_VIEW (callback_data)); + +        view = FM_DIRECTORY_VIEW (callback_data); +	selection = fm_directory_view_get_selection (view); +	if (selection_not_empty_in_menu_callback (view, selection)) { +		selected_item_locations = fm_directory_view_get_selected_icon_locations (view); +	        fm_directory_view_create_links_for_files (view, selection, selected_item_locations); +	        g_array_free (selected_item_locations, TRUE); +	} + +        caja_file_list_free (selection); +} + +static void +action_select_all_callback (GtkAction *action, +			    gpointer callback_data) +{ +	g_assert (FM_IS_DIRECTORY_VIEW (callback_data)); + +	fm_directory_view_select_all (callback_data); +} + +static void +action_invert_selection_callback (GtkAction *action, +				  gpointer callback_data) +{ +	g_assert (FM_IS_DIRECTORY_VIEW (callback_data)); + +	fm_directory_view_invert_selection (callback_data); +} + + +static void +pattern_select_response_cb (GtkWidget *dialog, int response, gpointer user_data) +{ +	FMDirectoryView *view; +	CajaDirectory *directory; +	GtkWidget *entry; +	GList *selection; +	GError *error; + +	view = FM_DIRECTORY_VIEW (user_data); + +	switch (response) { +	case GTK_RESPONSE_OK : +		entry = g_object_get_data (G_OBJECT (dialog), "entry"); +		directory = fm_directory_view_get_model (view); +		selection = caja_directory_match_pattern (directory, +					gtk_entry_get_text (GTK_ENTRY (entry))); + +		if (selection) { +			fm_directory_view_set_selection (view, selection); +			caja_file_list_free (selection); + +			fm_directory_view_reveal_selection(view); +		} +		/* fall through */ +	case GTK_RESPONSE_NONE : +	case GTK_RESPONSE_DELETE_EVENT : +	case GTK_RESPONSE_CANCEL : +		gtk_widget_destroy (GTK_WIDGET (dialog)); +		break; +	case GTK_RESPONSE_HELP : +		error = NULL; +		gtk_show_uri (gtk_window_get_screen (GTK_WINDOW (dialog)), +			      "ghelp:user-guide#caja-select-pattern", +			      gtk_get_current_event_time (), &error); +		if (error) { +			eel_show_error_dialog (_("There was an error displaying help."), error->message, +					       GTK_WINDOW (dialog)); +			g_error_free (error); +		} +		break; +	default : +		g_assert_not_reached (); +	} +} + +static void +select_pattern (FMDirectoryView *view) +{ +	GtkWidget *dialog; +	GtkWidget *label; +	GtkWidget *example; +	GtkWidget *table; +	GtkWidget *entry; +	GList *ret; +	char *example_pattern; + +	ret = NULL; +	dialog = gtk_dialog_new_with_buttons (_("Select Items Matching"), +			fm_directory_view_get_containing_window (view), +			GTK_DIALOG_DESTROY_WITH_PARENT, +			GTK_STOCK_HELP, +			GTK_RESPONSE_HELP, +			GTK_STOCK_CANCEL, +			GTK_RESPONSE_CANCEL, +			GTK_STOCK_OK, +			GTK_RESPONSE_OK, +			NULL); +	gtk_dialog_set_default_response (GTK_DIALOG (dialog), +					 GTK_RESPONSE_OK); +	gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE); +	gtk_container_set_border_width (GTK_CONTAINER (dialog), 5); +	gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), 2); + +	label = gtk_label_new_with_mnemonic (_("_Pattern:")); +	example = gtk_label_new (NULL); +	example_pattern = g_strdup_printf ("<b>%s</b><i>%s</i>", +					   _("Examples: "), +					   "*.png, file\?\?.txt, pict*.\?\?\?"); +	gtk_label_set_markup (GTK_LABEL (example), example_pattern); +	g_free (example_pattern); +	gtk_misc_set_alignment (GTK_MISC (example), 0.0, 0.5); +	entry = gtk_entry_new (); +	gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE); + +	table = gtk_table_new (2, 2, FALSE); + +	gtk_table_attach (GTK_TABLE (table), label, +			  0, 1, +			  0, 1, +			  GTK_FILL, GTK_FILL, +			  5, 5); + +	gtk_table_attach (GTK_TABLE (table), entry, +			  1, 2, +			  0, 1, +			  GTK_EXPAND | GTK_FILL, GTK_FILL, +			  5, 5); + +	gtk_table_attach (GTK_TABLE (table), example, +			  1, 2, +			  1, 2, +			  GTK_FILL, GTK_FILL, +			  5, 0); + +	gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry); +	gtk_widget_show_all (table); +	gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), table); +	g_object_set_data (G_OBJECT (dialog), "entry", entry); +	g_signal_connect (dialog, "response", +			  G_CALLBACK (pattern_select_response_cb), +			  view); +	gtk_widget_show_all (dialog); +} + +static void +action_select_pattern_callback (GtkAction *action, +				gpointer callback_data) +{ +	g_assert (FM_IS_DIRECTORY_VIEW (callback_data)); + +	select_pattern(callback_data); +} + +static void +action_reset_to_defaults_callback (GtkAction *action, +				   gpointer callback_data) +{ +	g_assert (FM_IS_DIRECTORY_VIEW (callback_data)); + +	fm_directory_view_reset_to_defaults (callback_data); +} + + +static void +hidden_files_mode_changed (CajaWindow *window, +			   gpointer callback_data) +{ +	FMDirectoryView *directory_view; + +	directory_view = FM_DIRECTORY_VIEW (callback_data); + +	fm_directory_view_init_show_hidden_files (directory_view); +} + +static void +action_save_search_callback (GtkAction *action, +			     gpointer callback_data) +{ +	CajaSearchDirectory *search; +	FMDirectoryView	*directory_view; + +        directory_view = FM_DIRECTORY_VIEW (callback_data); + +	if (directory_view->details->model && +	    CAJA_IS_SEARCH_DIRECTORY (directory_view->details->model)) { +		search = CAJA_SEARCH_DIRECTORY (directory_view->details->model); +		caja_search_directory_save_search (search); + +		/* Save search is disabled */ +		schedule_update_menus (directory_view); +	} +} + +static void +query_name_entry_changed_cb  (GtkWidget *entry, GtkWidget *button) +{ +	const char *text; +	gboolean sensitive; + +	text = gtk_entry_get_text (GTK_ENTRY (entry)); + +	sensitive = (text != NULL) && (*text != 0); + +	gtk_widget_set_sensitive (button, sensitive); +} + + +static void +action_save_search_as_callback (GtkAction *action, +				gpointer callback_data) +{ +	FMDirectoryView	*directory_view; +	CajaSearchDirectory *search; +	CajaQuery *query; +	GtkWidget *dialog, *table, *label, *entry, *chooser, *save_button; +	const char *entry_text; +	char *filename, *filename_utf8, *dirname, *path, *uri; +	GFile *location; + +        directory_view = FM_DIRECTORY_VIEW (callback_data); + +	if (directory_view->details->model && +	    CAJA_IS_SEARCH_DIRECTORY (directory_view->details->model)) { +		search = CAJA_SEARCH_DIRECTORY (directory_view->details->model); + +		query = caja_search_directory_get_query (search); + +		dialog = gtk_dialog_new_with_buttons (_("Save Search as"), +						      fm_directory_view_get_containing_window (directory_view), +						      GTK_DIALOG_NO_SEPARATOR, +						      GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, +						      NULL); +		save_button = gtk_dialog_add_button (GTK_DIALOG (dialog), +						     GTK_STOCK_SAVE, GTK_RESPONSE_OK); +		gtk_dialog_set_default_response (GTK_DIALOG (dialog), +						 GTK_RESPONSE_OK); +		gtk_container_set_border_width (GTK_CONTAINER (dialog), 5); +		gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), 2); +		gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); + +		table = gtk_table_new (2, 2, FALSE); +		gtk_container_set_border_width (GTK_CONTAINER (table), 5); +		gtk_table_set_row_spacings (GTK_TABLE (table), 6); +		gtk_table_set_col_spacings (GTK_TABLE (table), 12); +		gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), table, TRUE, TRUE, 0); +		gtk_widget_show (table); + +		label = gtk_label_new_with_mnemonic (_("Search _name:")); +		gtk_misc_set_alignment (GTK_MISC(label), 0.0, 0.5); +		gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0); +		gtk_widget_show (label); +		entry = gtk_entry_new (); +		gtk_table_attach (GTK_TABLE (table), entry, 1, 2, 0, 1, GTK_FILL | GTK_EXPAND, 0, 0, 0); +		gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE); +		gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry); + +		gtk_widget_set_sensitive (save_button, FALSE); +		g_signal_connect (entry, "changed", +				  G_CALLBACK (query_name_entry_changed_cb), save_button); + +		gtk_widget_show (entry); +		label = gtk_label_new_with_mnemonic (_("_Folder:")); +		gtk_misc_set_alignment (GTK_MISC(label), 0.0, 0.5); +		gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2, GTK_FILL, 0, 0, 0); +		gtk_widget_show (label); + +		chooser = gtk_file_chooser_button_new (_("Select Folder to Save Search In"), +						      GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER); +		gtk_table_attach (GTK_TABLE (table), chooser, 1, 2, 1, 2, GTK_FILL | GTK_EXPAND, 0, 0, 0); +		gtk_label_set_mnemonic_widget (GTK_LABEL (label), chooser); +		gtk_widget_show (chooser); + +		gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (chooser), TRUE); + +		gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (chooser), +						     g_get_home_dir ()); + +		if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) { +			entry_text = gtk_entry_get_text (GTK_ENTRY (entry)); +			if (g_str_has_suffix (entry_text, CAJA_SAVED_SEARCH_EXTENSION)) { +				filename_utf8 = g_strdup (entry_text); +			} else { +				filename_utf8 = g_strconcat (entry_text, CAJA_SAVED_SEARCH_EXTENSION, NULL); +			} + +			filename = g_filename_from_utf8 (filename_utf8, -1, NULL, NULL, NULL); +			g_free (filename_utf8); + +			dirname = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (chooser)); + +			path = g_build_filename (dirname, filename, NULL); +			g_free (filename); +			g_free (dirname); + +			uri = g_filename_to_uri (path, NULL, NULL); +			g_free (path); + +			caja_search_directory_save_to_file (search, uri); +			location = g_file_new_for_uri (uri); +			caja_file_changes_queue_file_added (location); +			g_object_unref (location); +			caja_file_changes_consume_changes (TRUE); +			g_free (uri); +		} + +		gtk_widget_destroy (dialog); +	} +} + + +static void +action_empty_trash_callback (GtkAction *action, +			     gpointer callback_data) +{ +        g_assert (FM_IS_DIRECTORY_VIEW (callback_data)); + +	caja_file_operations_empty_trash (GTK_WIDGET (callback_data)); +} + +static void +action_new_folder_callback (GtkAction *action, +			    gpointer callback_data) +{ +        g_assert (FM_IS_DIRECTORY_VIEW (callback_data)); + +	fm_directory_view_new_folder (FM_DIRECTORY_VIEW (callback_data)); +} + +static void +action_new_empty_file_callback (GtkAction *action, +				gpointer callback_data) +{ +        g_assert (FM_IS_DIRECTORY_VIEW (callback_data)); + +	fm_directory_view_new_file (FM_DIRECTORY_VIEW (callback_data), NULL, NULL); +} + +static void +action_new_launcher_callback (GtkAction *action, +			      gpointer callback_data) +{ +	char *parent_uri; +	FMDirectoryView *view; +	GtkWindow *window; + +	g_assert (FM_IS_DIRECTORY_VIEW (callback_data)); + +	view = FM_DIRECTORY_VIEW (callback_data); + +	parent_uri = fm_directory_view_get_backing_uri (view); + +	window = fm_directory_view_get_containing_window (view); +	caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, +			    "directory view create new launcher in window=%p: %s", window, parent_uri); +	caja_launch_application_from_command (gtk_widget_get_screen (GTK_WIDGET (view)), +						  "mate-desktop-item-edit", +						  "mate-desktop-item-edit", +						  FALSE, +						  "--create-new", parent_uri, NULL); + +	g_free (parent_uri); +} + +static void +action_properties_callback (GtkAction *action, +			    gpointer callback_data) +{ +        FMDirectoryView *view; +        GList *selection; +	GList *files; + +        g_assert (FM_IS_DIRECTORY_VIEW (callback_data)); + +        view = FM_DIRECTORY_VIEW (callback_data); +	selection = fm_directory_view_get_selection (view); +	if (g_list_length (selection) == 0) { +		if (view->details->directory_as_file != NULL) { +			files = g_list_append (NULL, caja_file_ref (view->details->directory_as_file)); + +			fm_properties_window_present (files, GTK_WIDGET (view)); + +			caja_file_list_free (files); +		} +	} else { +		fm_properties_window_present (selection, GTK_WIDGET (view)); +	} +        caja_file_list_free (selection); +} + +static void +action_location_properties_callback (GtkAction *action, +				     gpointer   callback_data) +{ +	FMDirectoryView *view; +	GList           *files; + +	g_assert (FM_IS_DIRECTORY_VIEW (callback_data)); + +	view = FM_DIRECTORY_VIEW (callback_data); +	g_assert (CAJA_IS_FILE (view->details->location_popup_directory_as_file)); + +	files = g_list_append (NULL, caja_file_ref (view->details->location_popup_directory_as_file)); + +	fm_properties_window_present (files, GTK_WIDGET (view)); + +	caja_file_list_free (files); +} + +static gboolean +all_files_in_trash (GList *files) +{ +	GList *node; + +	/* Result is ambiguous if called on NULL, so disallow. */ +	g_return_val_if_fail (files != NULL, FALSE); + +	for (node = files; node != NULL; node = node->next) { +		if (!caja_file_is_in_trash (CAJA_FILE (node->data))) { +			return FALSE; +		} +	} + +	return TRUE; +} + +static gboolean +all_selected_items_in_trash (FMDirectoryView *view) +{ +	GList *selection; +	gboolean result; + +	/* If the contents share a parent directory, we need only +	 * check that parent directory. Otherwise we have to inspect +	 * each selected item. +	 */ +	selection = fm_directory_view_get_selection (view); +	result = (selection == NULL) ? FALSE : all_files_in_trash (selection); +	caja_file_list_free (selection); + +	return result; +} + +static gboolean +we_are_in_vfolder_desktop_dir (FMDirectoryView *view) +{ +	CajaFile *file; +	char *mime_type; + +	g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + +	if (view->details->model == NULL) { +		return FALSE; +	} + +	file = caja_directory_get_corresponding_file (view->details->model); +	mime_type = caja_file_get_mime_type (file); +	caja_file_unref (file); + +	if (mime_type != NULL +	    && strcmp (mime_type, "x-directory/vfolder-desktop") == 0) { +		g_free (mime_type); +		return TRUE; +	} else { +		g_free (mime_type); +		return FALSE; +	} +} + +/* Preferences changed callbacks */ +static void +text_attribute_names_changed_callback (gpointer callback_data) +{ +	FMDirectoryView *view; + +	view = FM_DIRECTORY_VIEW (callback_data); + +	EEL_CALL_METHOD +		(FM_DIRECTORY_VIEW_CLASS, view, +		 text_attribute_names_changed, (view)); +} + +static void +image_display_policy_changed_callback (gpointer callback_data) +{ +	FMDirectoryView *view; + +	view = FM_DIRECTORY_VIEW (callback_data); + +	EEL_CALL_METHOD +		(FM_DIRECTORY_VIEW_CLASS, view, +		 image_display_policy_changed, (view)); +} + +static void +click_policy_changed_callback (gpointer callback_data) +{ +	FMDirectoryView *view; + +	view = FM_DIRECTORY_VIEW (callback_data); + +	EEL_CALL_METHOD +		(FM_DIRECTORY_VIEW_CLASS, view, +		 click_policy_changed, (view)); +} + +gboolean +fm_directory_view_should_sort_directories_first (FMDirectoryView *view) +{ +	return view->details->sort_directories_first; +} + +static void +sort_directories_first_changed_callback (gpointer callback_data) +{ +	FMDirectoryView *view; +	gboolean preference_value; + +	view = FM_DIRECTORY_VIEW (callback_data); + +	preference_value = +		eel_preferences_get_boolean (CAJA_PREFERENCES_SORT_DIRECTORIES_FIRST); + +	if (preference_value != view->details->sort_directories_first) { +		view->details->sort_directories_first = preference_value; +		EEL_CALL_METHOD +			(FM_DIRECTORY_VIEW_CLASS, view, +			 sort_directories_first_changed, (view)); +	} +} + +static void +lockdown_disable_command_line_changed_callback (gpointer callback_data) +{ +	FMDirectoryView *view; + +	view = FM_DIRECTORY_VIEW (callback_data); +	schedule_update_menus (view); +} + +static void set_up_scripts_directory_global(void) +{ +	if (scripts_directory_uri != NULL) +	{ +		return; +	} + +	char* scripts_directory_path; +	const char* override = g_getenv ("MATE22_USER_DIR"); //TODO: quitar? + +	if (override) +	{ +		scripts_directory_path = g_build_filename(override, "caja", "scripts", NULL); +	} +	else +	{ +		scripts_directory_path = g_build_filename(g_get_home_dir(), ".config", "caja", "scripts", NULL); +	} + +	if (g_mkdir_with_parents(scripts_directory_path, 0755) == 0) +	{ +		scripts_directory_uri = g_filename_to_uri(scripts_directory_path, NULL, NULL); +		scripts_directory_uri_length = strlen(scripts_directory_uri); + +		/* Emulación de GNOME Nautilus scripts +		 */ +		char* nautilus_scripts_path = g_build_filename(g_get_home_dir(), ".gnome2", "nautilus-scripts", NULL); + +		if (g_file_test(nautilus_scripts_path, G_FILE_TEST_IS_DIR) == TRUE) +		{ +			char* nautilus_syslink = g_build_filename(g_get_home_dir(), ".config", "caja", "scripts", "nautilus", NULL); +			// G_FILE_TEST_IS_REGULAR +			/* En caso de que exista el enlace, o algún otro tipo de archivo con +			 * el mismo nombre, ignoramos. Incluso si es una carpeta. */ +			if (g_file_test(nautilus_syslink, G_FILE_TEST_IS_SYMLINK) == FALSE && +				g_file_test(nautilus_syslink, G_FILE_TEST_EXISTS) == FALSE && +				g_file_test(nautilus_syslink, G_FILE_TEST_IS_DIR) == FALSE) +			{ +				/* Nos fijamos si es necesario crear un enlace */ +				GDir* dir = g_dir_open(nautilus_scripts_path, 0, NULL); + +				if (dir) +				{ +					/* Con tener más de un elemento en la carpeta, podemos hacer +					 * el enlace */ +					int count = 0; + +					while (g_dir_read_name(dir) != NULL) +					{ +						count++; +					} + +					if (count > 0) +					{ +						/* creamos un enlace a la carpeta de nautilus */ +						symlink(nautilus_scripts_path, nautilus_syslink); +					} + +					g_dir_close(dir); +				} +			} + +			g_free(nautilus_syslink); +		} + +		g_free(nautilus_scripts_path); +	} + +	g_free(scripts_directory_path); +} + +static void +scripts_added_or_changed_callback (CajaDirectory *directory, +				   GList *files, +				   gpointer callback_data) +{ +	FMDirectoryView *view; + +	view = FM_DIRECTORY_VIEW (callback_data); + +	view->details->scripts_invalid = TRUE; +	if (view->details->active) { +		schedule_update_menus (view); +	} +} + +static void +templates_added_or_changed_callback (CajaDirectory *directory, +				     GList *files, +				     gpointer callback_data) +{ +	FMDirectoryView *view; + +	view = FM_DIRECTORY_VIEW (callback_data); + +	view->details->templates_invalid = TRUE; +	if (view->details->active) { +		schedule_update_menus (view); +	} +} + +static void +add_directory_to_directory_list (FMDirectoryView *view, +				 CajaDirectory *directory, +				 GList **directory_list, +				 GCallback changed_callback) +{ +	CajaFileAttributes attributes; + +	if (g_list_find (*directory_list, directory) == NULL) { +		caja_directory_ref (directory); + +		attributes = +			CAJA_FILE_ATTRIBUTES_FOR_ICON | +			CAJA_FILE_ATTRIBUTE_INFO | +			CAJA_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT; + +		caja_directory_file_monitor_add (directory, directory_list, +						     FALSE, FALSE, attributes, +						     (CajaDirectoryCallback)changed_callback, view); + +		g_signal_connect_object (directory, "files_added", +					 G_CALLBACK (changed_callback), view, 0); +		g_signal_connect_object (directory, "files_changed", +					 G_CALLBACK (changed_callback), view, 0); + +		*directory_list = g_list_append	(*directory_list, directory); +	} +} + +static void +remove_directory_from_directory_list (FMDirectoryView *view, +				      CajaDirectory *directory, +				      GList **directory_list, +				      GCallback changed_callback) +{ +	*directory_list = g_list_remove	(*directory_list, directory); + +	g_signal_handlers_disconnect_by_func (directory, +					      G_CALLBACK (changed_callback), +					      view); + +	caja_directory_file_monitor_remove (directory, directory_list); + +	caja_directory_unref (directory); +} + + +static void +add_directory_to_scripts_directory_list (FMDirectoryView *view, +					 CajaDirectory *directory) +{ +	add_directory_to_directory_list (view, directory, +					 &view->details->scripts_directory_list, +					 G_CALLBACK (scripts_added_or_changed_callback)); +} + +static void +remove_directory_from_scripts_directory_list (FMDirectoryView *view, +					      CajaDirectory *directory) +{ +	remove_directory_from_directory_list (view, directory, +					      &view->details->scripts_directory_list, +					      G_CALLBACK (scripts_added_or_changed_callback)); +} + +static void +add_directory_to_templates_directory_list (FMDirectoryView *view, +					   CajaDirectory *directory) +{ +	add_directory_to_directory_list (view, directory, +					 &view->details->templates_directory_list, +					 G_CALLBACK (templates_added_or_changed_callback)); +} + +static void +remove_directory_from_templates_directory_list (FMDirectoryView *view, +						CajaDirectory *directory) +{ +	remove_directory_from_directory_list (view, directory, +					      &view->details->templates_directory_list, +					      G_CALLBACK (templates_added_or_changed_callback)); +} + +static void +slot_active (CajaWindowSlot *slot, +	     FMDirectoryView *view) +{ +	g_assert (!view->details->active); +	view->details->active = TRUE; + +	fm_directory_view_merge_menus (view); +	schedule_update_menus (view); +} + +static void +slot_inactive (CajaWindowSlot *slot, +	       FMDirectoryView *view) +{ +	g_assert (view->details->active || +		  gtk_widget_get_parent (GTK_WIDGET (view)) == NULL); +	view->details->active = FALSE; + +	fm_directory_view_unmerge_menus (view); +	remove_update_menus_timeout_callback (view); +} + +static void +fm_directory_view_grab_focus (CajaView *view) +{ +	/* focus the child of the scrolled window if it exists */ +	GtkWidget *child; +	child = gtk_bin_get_child (GTK_BIN (view)); +	if (child) { +		gtk_widget_grab_focus (GTK_WIDGET (child)); +	} +} + +static void +view_iface_update_menus (CajaView *view) +{ +	fm_directory_view_update_menus (FM_DIRECTORY_VIEW (view)); +} + +static GtkWidget * +fm_directory_view_get_widget (CajaView *view) +{ +	return GTK_WIDGET (view); +} + +static int +fm_directory_view_get_selection_count (CajaView *view) +{ +	/* FIXME: This could be faster if we special cased it in subclasses */ +	GList *files; +	int len; + +	files = fm_directory_view_get_selection (FM_DIRECTORY_VIEW (view)); +	len = g_list_length (files); +	caja_file_list_free (files); + +	return len; +} + +static GList * +fm_directory_view_get_selection_locations (CajaView *view) +{ +	GList *files; +	GList *locations; +	GFile *location; +	GList *l; + +	files = fm_directory_view_get_selection (FM_DIRECTORY_VIEW (view)); +	locations = NULL; +	for (l = files; l != NULL; l = l->next) { +		location = caja_file_get_location (CAJA_FILE (l->data)); +		locations = g_list_prepend (locations, location); +	} +	caja_file_list_free (files); + +	return g_list_reverse (locations); +} + +static GList * +file_list_from_location_list (const GList *uri_list) +{ +	GList *file_list; +	const GList *node; + +	file_list = NULL; +	for (node = uri_list; node != NULL; node = node->next) { +		file_list = g_list_prepend +			(file_list, +			 caja_file_get (node->data)); +	} +	return g_list_reverse (file_list); +} + +static void +fm_directory_view_set_selection_locations (CajaView *caja_view, +					   GList *selection_locations) +{ +	GList *selection; +	FMDirectoryView *view; + +	view = FM_DIRECTORY_VIEW (caja_view); + +	if (!view->details->loading) { +		/* If we aren't still loading, set the selection right now, +		 * and reveal the new selection. +		 */ +		selection = file_list_from_location_list (selection_locations); +		view->details->selection_change_is_due_to_shell = TRUE; +		fm_directory_view_set_selection (view, selection); +		view->details->selection_change_is_due_to_shell = FALSE; +		fm_directory_view_reveal_selection (view); +		caja_file_list_free (selection); +	} else { +		/* If we are still loading, set the list of pending URIs instead. +		 * done_loading() will eventually select the pending URIs and reveal them. +		 */ +		eel_g_object_list_free (view->details->pending_locations_selected); +		view->details->pending_locations_selected = +			eel_g_object_list_copy (selection_locations); +	} +} + + +void +fm_directory_view_init_view_iface (CajaViewIface *iface) +{ +	iface->grab_focus = fm_directory_view_grab_focus; +	iface->update_menus = view_iface_update_menus; + +	iface->get_widget = fm_directory_view_get_widget; +  	iface->load_location = fm_directory_view_load_location; +	iface->stop_loading = fm_directory_view_stop_loading; + +	iface->get_selection_count = fm_directory_view_get_selection_count; +	iface->get_selection = fm_directory_view_get_selection_locations; +	iface->set_selection = fm_directory_view_set_selection_locations; +	iface->set_is_active = (gpointer)fm_directory_view_set_is_active; + +	iface->supports_zooming = (gpointer)fm_directory_view_supports_zooming; +	iface->bump_zoom_level = (gpointer)fm_directory_view_bump_zoom_level; +        iface->zoom_to_level = (gpointer)fm_directory_view_zoom_to_level; +        iface->restore_default_zoom_level = (gpointer)fm_directory_view_restore_default_zoom_level; +        iface->can_zoom_in = (gpointer)fm_directory_view_can_zoom_in; +        iface->can_zoom_out = (gpointer)fm_directory_view_can_zoom_out; +	iface->get_zoom_level = (gpointer)fm_directory_view_get_zoom_level; + +	iface->pop_up_location_context_menu = (gpointer)fm_directory_view_pop_up_location_context_menu; +	iface->drop_proxy_received_uris = (gpointer)fm_directory_view_drop_proxy_received_uris; +	iface->drop_proxy_received_netscape_url = (gpointer)fm_directory_view_drop_proxy_received_netscape_url; +} + +static void +fm_directory_view_init (FMDirectoryView *view) +{ +	static gboolean setup_autos = FALSE; +	CajaDirectory *scripts_directory; +	CajaDirectory *templates_directory; +	char *templates_uri; + +	if (!setup_autos) { +		setup_autos = TRUE; +		eel_preferences_add_auto_boolean (CAJA_PREFERENCES_CONFIRM_TRASH, +						  &confirm_trash_auto_value); +		eel_preferences_add_auto_boolean (CAJA_PREFERENCES_ENABLE_DELETE, +						  &show_delete_command_auto_value); +	} + +	view->details = g_new0 (FMDirectoryViewDetails, 1); + +	/* Default to true; desktop-icon-view sets to false */ +	view->details->show_foreign_files = TRUE; + +	view->details->non_ready_files = +		g_hash_table_new_full (file_and_directory_hash, +				       file_and_directory_equal, +				       (GDestroyNotify)file_and_directory_free, +				       NULL); + +	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (view), +					GTK_POLICY_AUTOMATIC, +					GTK_POLICY_AUTOMATIC); +	gtk_scrolled_window_set_hadjustment (GTK_SCROLLED_WINDOW (view), NULL); +	gtk_scrolled_window_set_vadjustment (GTK_SCROLLED_WINDOW (view), NULL); +	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (view), GTK_SHADOW_ETCHED_IN); + +	set_up_scripts_directory_global (); +	scripts_directory = caja_directory_get_by_uri (scripts_directory_uri); +	add_directory_to_scripts_directory_list (view, scripts_directory); +	caja_directory_unref (scripts_directory); + +	if (caja_should_use_templates_directory ()) { +		templates_uri = caja_get_templates_directory_uri (); +		templates_directory = caja_directory_get_by_uri (templates_uri); +		g_free (templates_uri); +		add_directory_to_templates_directory_list (view, templates_directory); +		caja_directory_unref (templates_directory); +	} +	update_templates_directory (view); +	g_signal_connect_object (caja_signaller_get_current (), +				 "user_dirs_changed", +				 G_CALLBACK (user_dirs_changed), +				 view, G_CONNECT_SWAPPED); + +	view->details->sort_directories_first = +		eel_preferences_get_boolean (CAJA_PREFERENCES_SORT_DIRECTORIES_FIRST); + +	g_signal_connect_object (caja_trash_monitor_get (), "trash_state_changed", +				 G_CALLBACK (fm_directory_view_trash_state_changed_callback), view, 0); + +	/* React to clipboard changes */ +	g_signal_connect_object (caja_clipboard_monitor_get (), "clipboard_changed", +				 G_CALLBACK (clipboard_changed_callback), view, 0); + +        /* Register to menu provider extension signal managing menu updates */ +        g_signal_connect_object (caja_signaller_get_current (), "popup_menu_changed", +                         G_CALLBACK (fm_directory_view_update_menus), view, G_CONNECT_SWAPPED); + +	gtk_widget_show (GTK_WIDGET (view)); + +	eel_preferences_add_callback (CAJA_PREFERENCES_CONFIRM_TRASH, +				      schedule_update_menus_callback, view); +	eel_preferences_add_callback (CAJA_PREFERENCES_ENABLE_DELETE, +				      schedule_update_menus_callback, view); +	eel_preferences_add_callback (CAJA_PREFERENCES_ICON_VIEW_CAPTIONS, +				      text_attribute_names_changed_callback, view); +	eel_preferences_add_callback (CAJA_PREFERENCES_SHOW_IMAGE_FILE_THUMBNAILS, +				      image_display_policy_changed_callback, view); +	eel_preferences_add_callback (CAJA_PREFERENCES_CLICK_POLICY, +				      click_policy_changed_callback, view); +	eel_preferences_add_callback (CAJA_PREFERENCES_SORT_DIRECTORIES_FIRST, +				      sort_directories_first_changed_callback, view); +	eel_preferences_add_callback (CAJA_PREFERENCES_LOCKDOWN_COMMAND_LINE, +				      lockdown_disable_command_line_changed_callback, view); +} + +static void +real_unmerge_menus (FMDirectoryView *view) +{ +	GtkUIManager *ui_manager; + +	if (view->details->window == NULL) { +		return; +	} + +	ui_manager = caja_window_info_get_ui_manager (view->details->window); + +	caja_ui_unmerge_ui (ui_manager, +				&view->details->dir_merge_id, +				&view->details->dir_action_group); +	caja_ui_unmerge_ui (ui_manager, +				&view->details->extensions_menu_merge_id, +				&view->details->extensions_menu_action_group); +	caja_ui_unmerge_ui (ui_manager, +				&view->details->open_with_merge_id, +				&view->details->open_with_action_group); +	caja_ui_unmerge_ui (ui_manager, +				&view->details->scripts_merge_id, +				&view->details->scripts_action_group); +	caja_ui_unmerge_ui (ui_manager, +				&view->details->templates_merge_id, +				&view->details->templates_action_group); +} + +static void +fm_directory_view_destroy (GtkObject *object) +{ +	FMDirectoryView *view; +	GList *node, *next; + +	view = FM_DIRECTORY_VIEW (object); + +	disconnect_model_handlers (view); + +	fm_directory_view_unmerge_menus (view); + +	/* We don't own the window, so no unref */ +	view->details->slot = NULL; +	view->details->window = NULL; + +	fm_directory_view_stop (view); +	fm_directory_view_clear (view); + +	for (node = view->details->scripts_directory_list; node != NULL; node = next) { +		next = node->next; +		remove_directory_from_scripts_directory_list (view, node->data); +	} + +	for (node = view->details->templates_directory_list; node != NULL; node = next) { +		next = node->next; +		remove_directory_from_templates_directory_list (view, node->data); +	} + +	while (view->details->subdirectory_list != NULL) { +		fm_directory_view_remove_subdirectory (view, +				view->details->subdirectory_list->data); +	} + +	remove_update_menus_timeout_callback (view); +	remove_update_status_idle_callback (view); + +	if (view->details->display_selection_idle_id != 0) { +		g_source_remove (view->details->display_selection_idle_id); +		view->details->display_selection_idle_id = 0; +	} + +	if (view->details->reveal_selection_idle_id != 0) { +		g_source_remove (view->details->reveal_selection_idle_id); +		view->details->reveal_selection_idle_id = 0; +	} + +	if (view->details->delayed_rename_file_id != 0) { +		g_source_remove (view->details->delayed_rename_file_id); +		view->details->delayed_rename_file_id = 0; +	} + +	if (view->details->model) { +		caja_directory_unref (view->details->model); +		view->details->model = NULL; +	} + +	if (view->details->directory_as_file) { +		caja_file_unref (view->details->directory_as_file); +		view->details->directory_as_file = NULL; +	} + +	EEL_CALL_PARENT (GTK_OBJECT_CLASS, destroy, (object)); +} + +static void +fm_directory_view_finalize (GObject *object) +{ +	FMDirectoryView *view; + +	view = FM_DIRECTORY_VIEW (object); + +	eel_preferences_remove_callback (CAJA_PREFERENCES_CONFIRM_TRASH, +					 schedule_update_menus_callback, view); +	eel_preferences_remove_callback (CAJA_PREFERENCES_ENABLE_DELETE, +					 schedule_update_menus_callback, view); +	eel_preferences_remove_callback (CAJA_PREFERENCES_ICON_VIEW_CAPTIONS, +					 text_attribute_names_changed_callback, view); +	eel_preferences_remove_callback (CAJA_PREFERENCES_SHOW_IMAGE_FILE_THUMBNAILS, +					 image_display_policy_changed_callback, view); +	eel_preferences_remove_callback (CAJA_PREFERENCES_CLICK_POLICY, +					 click_policy_changed_callback, view); +	eel_preferences_remove_callback (CAJA_PREFERENCES_SORT_DIRECTORIES_FIRST, +					 sort_directories_first_changed_callback, view); +	eel_preferences_remove_callback (CAJA_PREFERENCES_LOCKDOWN_COMMAND_LINE, +					 lockdown_disable_command_line_changed_callback, view); + +	unschedule_pop_up_location_context_menu (view); +	if (view->details->location_popup_event != NULL) { +		gdk_event_free ((GdkEvent *) view->details->location_popup_event); +	} + +	g_hash_table_destroy (view->details->non_ready_files); + +	g_free (view->details); + +	EEL_CALL_PARENT (G_OBJECT_CLASS, finalize, (object)); +} + +/** + * fm_directory_view_display_selection_info: + * + * Display information about the current selection, and notify the view frame of the changed selection. + * @view: FMDirectoryView for which to display selection info. + * + **/ +void +fm_directory_view_display_selection_info (FMDirectoryView *view) +{ +	GList *selection; +	goffset non_folder_size; +	gboolean non_folder_size_known; +	guint non_folder_count, folder_count, folder_item_count; +	gboolean folder_item_count_known; +	guint file_item_count; +	GList *p; +	char *first_item_name; +	char *non_folder_str; +	char *folder_count_str; +	char *folder_item_count_str; +	char *status_string; +	char *free_space_str; +	char *obj_selected_free_space_str; +	CajaFile *file; + +	g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + +	selection = fm_directory_view_get_selection (view); + +	folder_item_count_known = TRUE; +	folder_count = 0; +	folder_item_count = 0; +	non_folder_count = 0; +	non_folder_size_known = FALSE; +	non_folder_size = 0; +	first_item_name = NULL; +	folder_count_str = NULL; +	non_folder_str = NULL; +	folder_item_count_str = NULL; +	free_space_str = NULL; +	obj_selected_free_space_str = NULL; + +	for (p = selection; p != NULL; p = p->next) { +		file = p->data; +		if (caja_file_is_directory (file)) { +			folder_count++; +			if (caja_file_get_directory_item_count (file, &file_item_count, NULL)) { +				folder_item_count += file_item_count; +			} else { +				folder_item_count_known = FALSE; +			} +		} else { +			non_folder_count++; +			if (!caja_file_can_get_size (file)) { +				non_folder_size_known = TRUE; +				non_folder_size += caja_file_get_size (file); +			} +		} + +		if (first_item_name == NULL) { +			first_item_name = caja_file_get_display_name (file); +		} +	} + +	caja_file_list_free (selection); + +	/* Break out cases for localization's sake. But note that there are still pieces +	 * being assembled in a particular order, which may be a problem for some localizers. +	 */ + +	if (folder_count != 0) { +		if (folder_count == 1 && non_folder_count == 0) { +			folder_count_str = g_strdup_printf (_("\"%s\" selected"), first_item_name); +		} else { +			folder_count_str = g_strdup_printf (ngettext("%'d folder selected", +								     "%'d folders selected", +								     folder_count), +							    folder_count); +		} + +		if (folder_count == 1) { +			if (!folder_item_count_known) { +				folder_item_count_str = g_strdup (""); +			} else { +				folder_item_count_str = g_strdup_printf (ngettext(" (containing %'d item)", +										  " (containing %'d items)", +										  folder_item_count), +									 folder_item_count); +			} +		} +		else { +			if (!folder_item_count_known) { +				folder_item_count_str = g_strdup (""); +			} else { +				/* translators: this is preceded with a string of form 'N folders' (N more than 1) */ +				folder_item_count_str = g_strdup_printf (ngettext(" (containing a total of %'d item)", +										  " (containing a total of %'d items)", +										  folder_item_count), +									 folder_item_count); +			} + +		} +	} + +	if (non_folder_count != 0) { +		char *items_string; + +		if (folder_count == 0) { +			if (non_folder_count == 1) { +				items_string = g_strdup_printf (_("\"%s\" selected"), +								  first_item_name); +			} else { +				items_string = g_strdup_printf (ngettext("%'d item selected", +									   "%'d items selected", +									   non_folder_count), +								  non_folder_count); +			} +		} else { +			/* Folders selected also, use "other" terminology */ +			items_string = g_strdup_printf (ngettext("%'d other item selected", +								   "%'d other items selected", +								   non_folder_count), +							  non_folder_count); +		} + +		if (non_folder_size_known) { +			char *size_string; + +			#if GLIB_CHECK_VERSION(2, 30, 0) +				size_string = g_format_size(non_folder_size); +			#else +				size_string = g_format_size_for_display(non_folder_size); +			#endif + +			/* This is marked for translation in case a localiser +			 * needs to use something other than parentheses. The +			 * first message gives the number of items selected; +			 * the message in parentheses the size of those items. +			 */ +			non_folder_str = g_strdup_printf (_("%s (%s)"), +							  items_string, +							  size_string); + +			g_free (size_string); +			g_free (items_string); +		} else { +			non_folder_str = items_string; +		} +	} + +	free_space_str = caja_file_get_volume_free_space (view->details->directory_as_file); +	if (free_space_str != NULL) { +		obj_selected_free_space_str = g_strdup_printf (_("Free space: %s"), free_space_str); +	} +	if (folder_count == 0 && non_folder_count == 0)	{ +		char *item_count_str; +		guint item_count; + +		item_count = fm_directory_view_get_item_count (view); + +		item_count_str = g_strdup_printf (ngettext ("%'u item", "%'u items", item_count), item_count); + +		if (free_space_str != NULL) { +			status_string = g_strdup_printf (_("%s, Free space: %s"), item_count_str, free_space_str); +			g_free (item_count_str); +		} else { +			status_string = item_count_str; +		} + +	} else if (folder_count == 0) { +		if (free_space_str == NULL) { +			status_string = g_strdup (non_folder_str); +		} else { +			/* Marking this for translation, since you +			 * might want to change "," to something else. +			 * After the comma the amount of free space will +			 * be shown. +			 */ +			status_string = g_strdup_printf (_("%s, %s"), +							 non_folder_str, +							 obj_selected_free_space_str); +		} +	} else if (non_folder_count == 0) { +		if (free_space_str == NULL) { +		        /* No use marking this for translation, since you +		         * can't reorder the strings, which is the main thing +		         * you'd want to do. +		         */ +			status_string = g_strdup_printf ("%s%s", +							 folder_count_str, +							 folder_item_count_str); +		} else { +			/* Marking this for translation, since you +			 * might want to change "," to something else. +			 * After the comma the amount of free space will +			 * be shown. +			 */ +			status_string = g_strdup_printf (_("%s%s, %s"), +							 folder_count_str, +							 folder_item_count_str, +							 obj_selected_free_space_str); +		} +	} else { +		if (obj_selected_free_space_str == NULL) { +			/* This is marked for translation in case a localizer +			 * needs to change ", " to something else. The comma +			 * is between the message about the number of folders +			 * and the number of items in those folders and the +			 * message about the number of other items and the +			 * total size of those items. +			 */ +			status_string = g_strdup_printf (_("%s%s, %s"), +							 folder_count_str, +							 folder_item_count_str, +							 non_folder_str); +		} else { +			/* This is marked for translation in case a localizer +			 * needs to change ", " to something else. The first comma +			 * is between the message about the number of folders +			 * and the number of items in those folders and the +			 * message about the number of other items and the +			 * total size of those items. After the second comma +			 * the free space is written. +			 */ +			status_string = g_strdup_printf (_("%s%s, %s, %s"), +							 folder_count_str, +							 folder_item_count_str, +							 non_folder_str, +							 obj_selected_free_space_str); +		} +	} + +	g_free (free_space_str); +	g_free (obj_selected_free_space_str); +	g_free (first_item_name); +	g_free (folder_count_str); +	g_free (folder_item_count_str); +	g_free (non_folder_str); + +	caja_window_slot_info_set_status (view->details->slot, +					      status_string); +	g_free (status_string); +} + +void +fm_directory_view_send_selection_change (FMDirectoryView *view) +{ +	caja_window_info_report_selection_changed (view->details->window); + +	view->details->send_selection_change_to_shell = FALSE; +} + +gboolean +fm_directory_view_get_allow_moves (FMDirectoryView *view) +{ +	return view->details->allow_moves; +} + +static void +fm_directory_view_load_location (CajaView *caja_view, +				 const char *location) +{ +	CajaDirectory *directory; +	FMDirectoryView *directory_view; + +	directory_view = FM_DIRECTORY_VIEW (caja_view); + +	if (eel_uri_is_search (location)) { +		directory_view->details->allow_moves = FALSE; +	} else { +		directory_view->details->allow_moves = TRUE; +	} + +	directory = caja_directory_get_by_uri (location); +	load_directory (directory_view, directory); +	caja_directory_unref (directory); +} + +static void +fm_directory_view_stop_loading (CajaView *caja_view) +{ +	fm_directory_view_stop (FM_DIRECTORY_VIEW (caja_view)); +} + +static void +fm_directory_view_file_limit_reached (FMDirectoryView *view) +{ +	g_assert (FM_IS_DIRECTORY_VIEW (view)); + +	EEL_CALL_METHOD (FM_DIRECTORY_VIEW_CLASS, view, +		 	       file_limit_reached, (view)); +} + +static void +real_file_limit_reached (FMDirectoryView *view) +{ +	CajaFile *file; +	GtkDialog *dialog; +	char *directory_name; +	char *message; + +	g_assert (FM_IS_DIRECTORY_VIEW (view)); + +	file = fm_directory_view_get_directory_as_file (view); +	directory_name = caja_file_get_display_name (file); + +	/* Note that the number of items actually displayed varies somewhat due +	 * to the way files are collected in batches. So you can't assume that +	 * no more than the constant limit are displayed. +	 */ +	message = g_strdup_printf (_("The folder \"%s\" contains more files than " +			             "Caja can handle."), +			           directory_name); +	g_free (directory_name); + +	dialog = eel_show_warning_dialog (message, +					  _("Some files will not be displayed."), +					  fm_directory_view_get_containing_window (view)); +	g_free (message); +} + +static void +check_for_directory_hard_limit (FMDirectoryView *view) +{ +	if (caja_directory_file_list_length_reached (view->details->model)) { +		fm_directory_view_file_limit_reached (view); +	} +} + +static gboolean +reveal_selection_idle_callback (gpointer data) +{ +	FMDirectoryView *view; + +	view = FM_DIRECTORY_VIEW (data); + +	view->details->reveal_selection_idle_id = 0; +	fm_directory_view_reveal_selection (view); + +	return FALSE; +} + +static void +done_loading (FMDirectoryView *view, +	      gboolean all_files_seen) +{ +	GList *locations_selected, *selection; + +	if (!view->details->loading) { +		return; +	} + +	/* This can be called during destruction, in which case there +	 * is no CajaWindowInfo any more. +	 */ +	if (view->details->window != NULL) { +		if (all_files_seen) { +			caja_window_info_report_load_complete (view->details->window, CAJA_VIEW (view)); +		} + +		schedule_update_menus (view); +		schedule_update_status (view); +		check_for_directory_hard_limit (view); +		reset_update_interval (view); + +		locations_selected = view->details->pending_locations_selected; +		if (locations_selected != NULL && all_files_seen) { +			view->details->pending_locations_selected = NULL; + +			selection = file_list_from_location_list (locations_selected); + +			view->details->selection_change_is_due_to_shell = TRUE; +			fm_directory_view_set_selection (view, selection); +			view->details->selection_change_is_due_to_shell = FALSE; +			caja_file_list_free (selection); + +			if (FM_IS_LIST_VIEW (view)) { +				/* HACK: We should be able to directly call reveal_selection here, +				 * but at this point the GtkTreeView hasn't allocated the new nodes +				 * yet, and it has a bug in the scroll calculation dealing with this +				 * special case. It would always make the selection the top row, even +				 * if no scrolling would be neccessary to reveal it. So we let it +				 * allocate before revealing. +				 */ +				if (view->details->reveal_selection_idle_id != 0) { +					g_source_remove (view->details->reveal_selection_idle_id); +				} +				view->details->reveal_selection_idle_id = +					g_idle_add (reveal_selection_idle_callback, view); +			} else { +				fm_directory_view_reveal_selection (view); +			} +		} +		eel_g_object_list_free (locations_selected); +		fm_directory_view_display_selection_info (view); +	} + +	fm_directory_view_end_loading (view, all_files_seen); + +	view->details->loading = FALSE; +} + + +typedef struct { +	GHashTable *debuting_files; +	GList	   *added_files; +} DebutingFilesData; + +static void +debuting_files_data_free (DebutingFilesData *data) +{ +	g_hash_table_unref (data->debuting_files); +	caja_file_list_free (data->added_files); +	g_free (data); +} + +/* This signal handler watch for the arrival of the icons created + * as the result of a file operation. Once the last one is detected + * it selects and reveals them all. + */ +static void +debuting_files_add_file_callback (FMDirectoryView *view, +				  CajaFile *new_file, +				  CajaDirectory *directory, +				  DebutingFilesData *data) +{ +	GFile *location; + +	location = caja_file_get_location (new_file); + +	if (g_hash_table_remove (data->debuting_files, location)) { +		caja_file_ref (new_file); +		data->added_files = g_list_prepend (data->added_files, new_file); + +		if (g_hash_table_size (data->debuting_files) == 0) { +			fm_directory_view_set_selection (view, data->added_files); +			fm_directory_view_reveal_selection (view); +			g_signal_handlers_disconnect_by_func (view, +							      G_CALLBACK (debuting_files_add_file_callback), +							      data); +		} +	} + +	g_object_unref (location); +} + +typedef struct { +	GList		*added_files; +	FMDirectoryView *directory_view; +} CopyMoveDoneData; + +static void +copy_move_done_data_free (CopyMoveDoneData *data) +{ +	g_assert (data != NULL); + +	eel_remove_weak_pointer (&data->directory_view); +	caja_file_list_free (data->added_files); +	g_free (data); +} + +static void +pre_copy_move_add_file_callback (FMDirectoryView *view, +				 CajaFile *new_file, +				 CajaDirectory *directory, +				 CopyMoveDoneData *data) +{ +	caja_file_ref (new_file); +	data->added_files = g_list_prepend (data->added_files, new_file); +} + +/* This needs to be called prior to caja_file_operations_copy_move. + * It hooks up a signal handler to catch any icons that get added before + * the copy_done_callback is invoked. The return value should  be passed + * as the data for uri_copy_move_done_callback. + */ +static CopyMoveDoneData * +pre_copy_move (FMDirectoryView *directory_view) +{ +	CopyMoveDoneData *copy_move_done_data; + +	copy_move_done_data = g_new0 (CopyMoveDoneData, 1); +	copy_move_done_data->directory_view = directory_view; + +	eel_add_weak_pointer (©_move_done_data->directory_view); + +	/* We need to run after the default handler adds the folder we want to +	 * operate on. The ADD_FILE signal is registered as G_SIGNAL_RUN_LAST, so we +	 * must use connect_after. +	 */ +	g_signal_connect (directory_view, "add_file", +			  G_CALLBACK (pre_copy_move_add_file_callback), copy_move_done_data); + +	return copy_move_done_data; +} + +/* This function is used to pull out any debuting uris that were added + * and (as a side effect) remove them from the debuting uri hash table. + */ +static gboolean +copy_move_done_partition_func (gpointer data, gpointer callback_data) +{ + 	GFile *location; + 	gboolean result; + +	location = caja_file_get_location (CAJA_FILE (data)); +	result = g_hash_table_remove ((GHashTable *) callback_data, location); +	g_object_unref (location); + +	return result; +} + +static gboolean +remove_not_really_moved_files (gpointer key, +			       gpointer value, +			       gpointer callback_data) +{ +	GList **added_files; +	GFile *loc; + +	loc = key; + +	if (GPOINTER_TO_INT (value)) { +		return FALSE; +	} + +	added_files = callback_data; +	*added_files = g_list_prepend (*added_files, +				       caja_file_get (loc)); +	return TRUE; +} + + +/* When this function is invoked, the file operation is over, but all + * the icons may not have been added to the directory view yet, so + * we can't select them yet. + * + * We're passed a hash table of the uri's to look out for, we hook + * up a signal handler to await their arrival. + */ +static void +copy_move_done_callback (GHashTable *debuting_files, gpointer data) +{ +	FMDirectoryView  *directory_view; +	CopyMoveDoneData *copy_move_done_data; +	DebutingFilesData  *debuting_files_data; + +	copy_move_done_data = (CopyMoveDoneData *) data; +	directory_view = copy_move_done_data->directory_view; + +	if (directory_view != NULL) { +		g_assert (FM_IS_DIRECTORY_VIEW (directory_view)); + +		debuting_files_data = g_new (DebutingFilesData, 1); +		debuting_files_data->debuting_files = g_hash_table_ref (debuting_files); +		debuting_files_data->added_files = eel_g_list_partition +			(copy_move_done_data->added_files, +			 copy_move_done_partition_func, +			 debuting_files, +			 ©_move_done_data->added_files); + +		/* We're passed the same data used by pre_copy_move_add_file_callback, so disconnecting +		 * it will free data. We've already siphoned off the added_files we need, and stashed the +		 * directory_view pointer. +		 */ +		g_signal_handlers_disconnect_by_func (directory_view, +						      G_CALLBACK (pre_copy_move_add_file_callback), +						      data); + +		/* Any items in the debuting_files hash table that have +		 * "FALSE" as their value aren't really being copied +		 * or moved, so we can't wait for an add_file signal +		 * to come in for those. +		 */ +		g_hash_table_foreach_remove (debuting_files, +					     remove_not_really_moved_files, +					     &debuting_files_data->added_files); + +		if (g_hash_table_size (debuting_files) == 0) { +			/* on the off-chance that all the icons have already been added */ +			if (debuting_files_data->added_files != NULL) { +				fm_directory_view_set_selection (directory_view, +								 debuting_files_data->added_files); +				fm_directory_view_reveal_selection (directory_view); +			} +			debuting_files_data_free (debuting_files_data); +		} else { +			/* We need to run after the default handler adds the folder we want to +			 * operate on. The ADD_FILE signal is registered as G_SIGNAL_RUN_LAST, so we +			 * must use connect_after. +			 */ +			g_signal_connect_data (GTK_OBJECT (directory_view), +					       "add_file", +					       G_CALLBACK (debuting_files_add_file_callback), +					       debuting_files_data, +					       (GClosureNotify) debuting_files_data_free, +					       G_CONNECT_AFTER); +		} +	} + +	copy_move_done_data_free (copy_move_done_data); +} + +static gboolean +real_file_still_belongs (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory) +{ +	if (view->details->model != directory && +	    g_list_find (view->details->subdirectory_list, directory) == NULL) { +		return FALSE; +	} + +	return caja_directory_contains_file (directory, file); +} + +static gboolean +still_should_show_file (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory) +{ +	return fm_directory_view_should_show_file (view, file) +		&& EEL_INVOKE_METHOD (FM_DIRECTORY_VIEW_CLASS, view, file_still_belongs, (view, file, directory)); +} + +static gboolean +ready_to_load (CajaFile *file) +{ +	return caja_file_check_if_ready (file, +					     CAJA_FILE_ATTRIBUTES_FOR_ICON); +} + +static int +compare_files_cover (gconstpointer a, gconstpointer b, gpointer callback_data) +{ +	const FileAndDirectory *fad1, *fad2; +	FMDirectoryView *view; + +	view = callback_data; +	fad1 = a; fad2 = b; + +	if (fad1->directory < fad2->directory) { +		return -1; +	} else if (fad1->directory > fad2->directory) { +		return 1; +	} else { +		return EEL_INVOKE_METHOD (FM_DIRECTORY_VIEW_CLASS, view, compare_files, +					  (view, fad1->file, fad2->file)); +	} +} +static void +sort_files (FMDirectoryView *view, GList **list) +{ +	*list = g_list_sort_with_data (*list, compare_files_cover, view); + +} + +/* Go through all the new added and changed files. + * Put any that are not ready to load in the non_ready_files hash table. + * Add all the rest to the old_added_files and old_changed_files lists. + * Sort the old_*_files lists if anything was added to them. + */ +static void +process_new_files (FMDirectoryView *view) +{ +	GList *new_added_files, *new_changed_files, *old_added_files, *old_changed_files; +	GHashTable *non_ready_files; +	GList *node, *next; +	FileAndDirectory *pending; +	gboolean in_non_ready; + +	new_added_files = view->details->new_added_files; +	view->details->new_added_files = NULL; +	new_changed_files = view->details->new_changed_files; +	view->details->new_changed_files = NULL; + +	non_ready_files = view->details->non_ready_files; + +	old_added_files = view->details->old_added_files; +	old_changed_files = view->details->old_changed_files; + +	/* Newly added files go into the old_added_files list if they're +	 * ready, and into the hash table if they're not. +	 */ +	for (node = new_added_files; node != NULL; node = next) { +		next = node->next; +		pending = (FileAndDirectory *)node->data; +		in_non_ready = g_hash_table_lookup (non_ready_files, pending) != NULL; +		if (fm_directory_view_should_show_file (view, pending->file)) { +			if (ready_to_load (pending->file)) { +				if (in_non_ready) { +					g_hash_table_remove (non_ready_files, pending); +				} +				new_added_files = g_list_delete_link (new_added_files, node); +				old_added_files = g_list_prepend (old_added_files, pending); +			} else { +				if (!in_non_ready) { +					new_added_files = g_list_delete_link (new_added_files, node); +					g_hash_table_insert (non_ready_files, pending, pending); +				} +			} +		} +	} +	file_and_directory_list_free (new_added_files); + +	/* Newly changed files go into the old_added_files list if they're ready +	 * and were seen non-ready in the past, into the old_changed_files list +	 * if they are read and were not seen non-ready in the past, and into +	 * the hash table if they're not ready. +	 */ +	for (node = new_changed_files; node != NULL; node = next) { +		next = node->next; +		pending = (FileAndDirectory *)node->data; +		if (!still_should_show_file (view, pending->file, pending->directory) || ready_to_load (pending->file)) { +			if (g_hash_table_lookup (non_ready_files, pending) != NULL) { +				g_hash_table_remove (non_ready_files, pending); +				if (still_should_show_file (view, pending->file, pending->directory)) { +					new_changed_files = g_list_delete_link (new_changed_files, node); +					old_added_files = g_list_prepend (old_added_files, pending); +				} +			} else if (fm_directory_view_should_show_file (view, pending->file)) { +				new_changed_files = g_list_delete_link (new_changed_files, node); +				old_changed_files = g_list_prepend (old_changed_files, pending); +			} +		} +	} +	file_and_directory_list_free (new_changed_files); + +	/* If any files were added to old_added_files, then resort it. */ +	if (old_added_files != view->details->old_added_files) { +		view->details->old_added_files = old_added_files; +		sort_files (view, &view->details->old_added_files); +	} + +	/* Resort old_changed_files too, since file attributes +	 * relevant to sorting could have changed. +	 */ +	if (old_changed_files != view->details->old_changed_files) { +		view->details->old_changed_files = old_changed_files; +		sort_files (view, &view->details->old_changed_files); +	} + +} + +static void +process_old_files (FMDirectoryView *view) +{ +	GList *files_added, *files_changed, *node; +	FileAndDirectory *pending; +	GList *selection, *files; +	gboolean send_selection_change; + +	files_added = view->details->old_added_files; +	files_changed = view->details->old_changed_files; + +	send_selection_change = FALSE; + +	if (files_added != NULL || files_changed != NULL) { +		g_signal_emit (view, signals[BEGIN_FILE_CHANGES], 0); + +		for (node = files_added; node != NULL; node = node->next) { +			pending = node->data; +			g_signal_emit (view, +				       signals[ADD_FILE], 0, pending->file, pending->directory); +		} + +		for (node = files_changed; node != NULL; node = node->next) { +			pending = node->data; +			g_signal_emit (view, +				       signals[still_should_show_file (view, pending->file, pending->directory) +					       ? FILE_CHANGED : REMOVE_FILE], 0, +				       pending->file, pending->directory); +		} + +		g_signal_emit (view, signals[END_FILE_CHANGES], 0); + +		if (files_changed != NULL) { +			selection = fm_directory_view_get_selection (view); +			files = file_and_directory_list_to_files (files_changed); +			send_selection_change = eel_g_lists_sort_and_check_for_intersection +				(&files, &selection); +			caja_file_list_free (files); +			caja_file_list_free (selection); +		} + +		file_and_directory_list_free (view->details->old_added_files); +		view->details->old_added_files = NULL; + +		file_and_directory_list_free (view->details->old_changed_files); +		view->details->old_changed_files = NULL; +	} + +	if (send_selection_change) { +		/* Send a selection change since some file names could +		 * have changed. +		 */ +		fm_directory_view_send_selection_change (view); +	} +} + +static void +display_pending_files (FMDirectoryView *view) +{ + +	/* Don't dispatch any updates while the view is frozen. */ +	if (view->details->updates_frozen) { +		return; +	} + +	process_new_files (view); +	process_old_files (view); + +	if (view->details->model != NULL +	    && caja_directory_are_all_files_seen (view->details->model) +	    && g_hash_table_size (view->details->non_ready_files) == 0) { +		done_loading (view, TRUE); +	} +} + +void +fm_directory_view_freeze_updates (FMDirectoryView *view) +{ +	view->details->updates_frozen = TRUE; +	view->details->updates_queued = 0; +	view->details->needs_reload = FALSE; +} + +void +fm_directory_view_unfreeze_updates (FMDirectoryView *view) +{ +	view->details->updates_frozen = FALSE; + +	if (view->details->needs_reload) { +		view->details->needs_reload = FALSE; +		if (view->details->model != NULL) { +			load_directory (view, view->details->model); +		} +	} else { +		schedule_idle_display_of_pending_files (view); +	} +} + +static gboolean +display_selection_info_idle_callback (gpointer data) +{ +	FMDirectoryView *view; + +	view = FM_DIRECTORY_VIEW (data); + +	g_object_ref (G_OBJECT (view)); + +	view->details->display_selection_idle_id = 0; +	fm_directory_view_display_selection_info (view); +	if (view->details->send_selection_change_to_shell) { +		fm_directory_view_send_selection_change (view); +	} + +	g_object_unref (G_OBJECT (view)); + +	return FALSE; +} + +static void +remove_update_menus_timeout_callback (FMDirectoryView *view) +{ +	if (view->details->update_menus_timeout_id != 0) { +		g_source_remove (view->details->update_menus_timeout_id); +		view->details->update_menus_timeout_id = 0; +	} +} + +static void +update_menus_if_pending (FMDirectoryView *view) +{ +	if (!view->details->menu_states_untrustworthy) { +		return; +	} + +	remove_update_menus_timeout_callback (view); +	fm_directory_view_update_menus (view); +} + +static gboolean +update_menus_timeout_callback (gpointer data) +{ +	FMDirectoryView *view; + +	view = FM_DIRECTORY_VIEW (data); + +	g_object_ref (G_OBJECT (view)); + +	view->details->update_menus_timeout_id = 0; +	fm_directory_view_update_menus (view); + +	g_object_unref (G_OBJECT (view)); + +	return FALSE; +} + +static gboolean +display_pending_callback (gpointer data) +{ +	FMDirectoryView *view; + +	view = FM_DIRECTORY_VIEW (data); + +	g_object_ref (G_OBJECT (view)); + +	view->details->display_pending_source_id = 0; + +	display_pending_files (view); + +	g_object_unref (G_OBJECT (view)); + +	return FALSE; +} + +static void +schedule_idle_display_of_pending_files (FMDirectoryView *view) +{ +	/* Get rid of a pending source as it might be a timeout */ +	unschedule_display_of_pending_files (view); + +	/* We want higher priority than the idle that handles the relayout +	   to avoid a resort on each add. But we still want to allow repaints +	   and other hight prio events while we have pending files to show. */ +	view->details->display_pending_source_id = +		g_idle_add_full (G_PRIORITY_DEFAULT_IDLE - 20, +				 display_pending_callback, view, NULL); +} + +static void +schedule_timeout_display_of_pending_files (FMDirectoryView *view, guint interval) +{ + 	/* No need to schedule an update if there's already one pending. */ +	if (view->details->display_pending_source_id != 0) { + 		return; +	} + +	view->details->display_pending_source_id = +		g_timeout_add (interval, display_pending_callback, view); +} + +static void +unschedule_display_of_pending_files (FMDirectoryView *view) +{ +	/* Get rid of source if it's active. */ +	if (view->details->display_pending_source_id != 0) { +		g_source_remove (view->details->display_pending_source_id); +		view->details->display_pending_source_id = 0; +	} +} + +static void +queue_pending_files (FMDirectoryView *view, +		     CajaDirectory *directory, +		     GList *files, +		     GList **pending_list) +{ +	if (files == NULL) { +		return; +	} + +	/* Don't queue any more updates if we need to reload anyway */ +	if (view->details->needs_reload) { +		return; +	} + +	if (view->details->updates_frozen) { +		view->details->updates_queued += g_list_length (files); +		/* Mark the directory for reload when there are too much queued +		 * changes to prevent the pending list from growing infinitely. +		 */ +		if (view->details->updates_queued > MAX_QUEUED_UPDATES) { +			view->details->needs_reload = TRUE; +			return; +		} +	} + + + +	*pending_list = g_list_concat (file_and_directory_list_from_files (directory, files), +				       *pending_list); + +	if (! view->details->loading || caja_directory_are_all_files_seen (directory)) { +		schedule_timeout_display_of_pending_files (view, view->details->update_interval); +	} +} + +static void +remove_changes_timeout_callback (FMDirectoryView *view) +{ +	if (view->details->changes_timeout_id != 0) { +		g_source_remove (view->details->changes_timeout_id); +		view->details->changes_timeout_id = 0; +	} +} + +static void +reset_update_interval (FMDirectoryView *view) +{ +	view->details->update_interval = UPDATE_INTERVAL_MIN; +	remove_changes_timeout_callback (view); +	/* Reschedule a pending timeout to idle */ +	if (view->details->display_pending_source_id != 0) { +		schedule_idle_display_of_pending_files (view); +	} +} + +static gboolean +changes_timeout_callback (gpointer data) +{ +	gint64 now; +	gint64 time_delta; +	gboolean ret; +	FMDirectoryView *view; + +	view = FM_DIRECTORY_VIEW (data); + +	g_object_ref (G_OBJECT (view)); + +	now = eel_get_system_time(); +	time_delta = now - view->details->last_queued; + +	if (time_delta < UPDATE_INTERVAL_RESET*1000) { +		if (view->details->update_interval < UPDATE_INTERVAL_MAX && +			view->details->loading) { +			/* Increase */ +			view->details->update_interval += UPDATE_INTERVAL_INC; +		} +		ret = TRUE; +	} else { +		/* Reset */ +		reset_update_interval (view); +		ret = FALSE; +	} + +	g_object_unref (G_OBJECT (view)); + +	return ret; +} + +static void +schedule_changes (FMDirectoryView *view) +{ +	/* Remember when the change was queued */ +	view->details->last_queued = eel_get_system_time(); + +	/* No need to schedule if there are already changes pending or during loading */ +	if (view->details->changes_timeout_id != 0 || +		view->details->loading) { +		return; +	} + +	view->details->changes_timeout_id = +		g_timeout_add (UPDATE_INTERVAL_TIMEOUT_INTERVAL, changes_timeout_callback, view); +} + +static void +files_added_callback (CajaDirectory *directory, +		      GList *files, +		      gpointer callback_data) +{ +	FMDirectoryView *view; +	GtkWindow *window; +	char *uri; + +	view = FM_DIRECTORY_VIEW (callback_data); + +	window = fm_directory_view_get_containing_window (view); +	uri = fm_directory_view_get_uri (view); +	caja_debug_log_with_file_list (FALSE, CAJA_DEBUG_LOG_DOMAIN_ASYNC, files, +					   "files added in window %p: %s", +					   window, +					   uri ? uri : "(no directory)"); +	g_free (uri); + +	schedule_changes (view); + +	queue_pending_files (view, directory, files, &view->details->new_added_files); + +	/* The number of items could have changed */ +	schedule_update_status (view); +} + +static void +files_changed_callback (CajaDirectory *directory, +			GList *files, +			gpointer callback_data) +{ +	FMDirectoryView *view; +	GtkWindow *window; +	char *uri; + +	view = FM_DIRECTORY_VIEW (callback_data); + +	window = fm_directory_view_get_containing_window (view); +	uri = fm_directory_view_get_uri (view); +	caja_debug_log_with_file_list (FALSE, CAJA_DEBUG_LOG_DOMAIN_ASYNC, files, +					   "files changed in window %p: %s", +					   window, +					   uri ? uri : "(no directory)"); +	g_free (uri); + +	schedule_changes (view); + +	queue_pending_files (view, directory, files, &view->details->new_changed_files); + +	/* The free space or the number of items could have changed */ +	schedule_update_status (view); + +	/* A change in MIME type could affect the Open with menu, for +	 * one thing, so we need to update menus when files change. +	 */ +	schedule_update_menus (view); +} + +static void +done_loading_callback (CajaDirectory *directory, +		       gpointer callback_data) +{ +	FMDirectoryView *view; + +	view = FM_DIRECTORY_VIEW (callback_data); + +	process_new_files (view); +	if (g_hash_table_size (view->details->non_ready_files) == 0) { +		/* Unschedule a pending update and schedule a new one with the minimal +		 * update interval. This gives the view a short chance at gathering the +		 * (cached) deep counts. +		 */ +		unschedule_display_of_pending_files (view); +		schedule_timeout_display_of_pending_files (view, UPDATE_INTERVAL_MIN); +	} +} + +static void +load_error_callback (CajaDirectory *directory, +		     GError *error, +		     gpointer callback_data) +{ +	FMDirectoryView *view; + +	view = FM_DIRECTORY_VIEW (callback_data); + +	/* FIXME: By doing a stop, we discard some pending files. Is +	 * that OK? +	 */ +	fm_directory_view_stop (view); + +	/* Emit a signal to tell subclasses that a load error has +	 * occurred, so they can handle it in the UI. +	 */ +	g_signal_emit (view, +		       signals[LOAD_ERROR], 0, error); +} + +static void +real_load_error (FMDirectoryView *view, GError *error) +{ +	/* Report only one error per failed directory load (from the UI +	 * point of view, not from the CajaDirectory point of view). +	 * Otherwise you can get multiple identical errors caused by +	 * unrelated code that just happens to try to iterate this +	 * directory. +	 */ +	if (!view->details->reported_load_error) { +		fm_report_error_loading_directory +			(fm_directory_view_get_directory_as_file (view), +			 error, +			 fm_directory_view_get_containing_window (view)); +	} +	view->details->reported_load_error = TRUE; +} + +void +fm_directory_view_add_subdirectory (FMDirectoryView  *view, +				    CajaDirectory*directory) +{ +	CajaFileAttributes attributes; + +	g_assert (!g_list_find (view->details->subdirectory_list, directory)); + +	caja_directory_ref (directory); + +	attributes = +		CAJA_FILE_ATTRIBUTES_FOR_ICON | +		CAJA_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT | +		CAJA_FILE_ATTRIBUTE_INFO | +		CAJA_FILE_ATTRIBUTE_LINK_INFO | +		CAJA_FILE_ATTRIBUTE_MOUNT | +		CAJA_FILE_ATTRIBUTE_EXTENSION_INFO; + +	caja_directory_file_monitor_add (directory, +					     &view->details->model, +					     view->details->show_hidden_files, +					     view->details->show_backup_files, +					     attributes, +					     files_added_callback, view); + +	g_signal_connect +		(directory, "files_added", +		 G_CALLBACK (files_added_callback), view); +	g_signal_connect +		(directory, "files_changed", +		 G_CALLBACK (files_changed_callback), view); + +	view->details->subdirectory_list = g_list_prepend ( +			   view->details->subdirectory_list, directory); +} + +void +fm_directory_view_remove_subdirectory (FMDirectoryView  *view, +				       CajaDirectory*directory) +{ +	g_assert (g_list_find (view->details->subdirectory_list, directory)); + +	view->details->subdirectory_list = g_list_remove ( +				view->details->subdirectory_list, directory); + +	g_signal_handlers_disconnect_by_func (directory, +					      G_CALLBACK (files_added_callback), +					      view); +	g_signal_handlers_disconnect_by_func (directory, +					      G_CALLBACK (files_changed_callback), +					      view); + +	caja_directory_file_monitor_remove (directory, &view->details->model); + +	caja_directory_unref (directory); +} + +/** + * fm_directory_view_clear: + * + * Emit the signal to clear the contents of the view. Subclasses must + * override the signal handler for this signal. This is normally called + * only by FMDirectoryView. + * @view: FMDirectoryView to empty. + * + **/ +void +fm_directory_view_clear (FMDirectoryView *view) +{ +	g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + +	g_signal_emit (view, signals[CLEAR], 0); +} + +/** + * fm_directory_view_begin_loading: + * + * Emit the signal to prepare for loading the contents of a new location. + * Subclasses might want to override the signal handler for this signal. + * This is normally called only by FMDirectoryView. + * @view: FMDirectoryView that is switching to view a new location. + * + **/ +void +fm_directory_view_begin_loading (FMDirectoryView *view) +{ +	g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + +	g_signal_emit (view, signals[BEGIN_LOADING], 0); +} + +/** + * fm_directory_view_end_loading: + * + * Emit the signal after loading the contents of a new location. + * Subclasses might want to override the signal handler for this signal. + * This is normally called only by FMDirectoryView. + * @view: FMDirectoryView that is switching to view a new location. + * + **/ +void +fm_directory_view_end_loading (FMDirectoryView *view, +			       gboolean all_files_seen) +{ +	g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + +	g_signal_emit (view, signals[END_LOADING], 0, all_files_seen); +} + +/** + * fm_directory_view_get_loading: + * @view: an #FMDirectoryView. + * + * Return value: #gboolean inicating whether @view is currently loaded. + * + **/ +gboolean +fm_directory_view_get_loading (FMDirectoryView *view) +{ +	g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + +	return view->details->loading; +} + +/** + * fm_directory_view_bump_zoom_level: + * + * bump the current zoom level by invoking the relevant subclass through the slot + * + **/ +void +fm_directory_view_bump_zoom_level (FMDirectoryView *view, int zoom_increment) +{ +	g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + +	if (!fm_directory_view_supports_zooming (view)) { +		return; +	} + +	EEL_CALL_METHOD +		(FM_DIRECTORY_VIEW_CLASS, view, +		 bump_zoom_level, (view, zoom_increment)); +} + +/** + * fm_directory_view_zoom_to_level: + * + * Set the current zoom level by invoking the relevant subclass through the slot + * + **/ +void +fm_directory_view_zoom_to_level (FMDirectoryView *view, +				 CajaZoomLevel zoom_level) +{ +	g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + +	if (!fm_directory_view_supports_zooming (view)) { +		return; +	} + +	EEL_CALL_METHOD +		(FM_DIRECTORY_VIEW_CLASS, view, +		 zoom_to_level, (view, zoom_level)); +} + + +CajaZoomLevel +fm_directory_view_get_zoom_level (FMDirectoryView *view) +{ +	g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), CAJA_ZOOM_LEVEL_STANDARD); + +	if (!fm_directory_view_supports_zooming (view)) { +		return CAJA_ZOOM_LEVEL_STANDARD; +	} + +	return EEL_CALL_METHOD_WITH_RETURN_VALUE +		(FM_DIRECTORY_VIEW_CLASS, view, +		 get_zoom_level, (view)); +} + +/** + * fm_directory_view_restore_default_zoom_level: + * + * restore to the default zoom level by invoking the relevant subclass through the slot + * + **/ +void +fm_directory_view_restore_default_zoom_level (FMDirectoryView *view) +{ +	g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + +	if (!fm_directory_view_supports_zooming (view)) { +		return; +	} + +	EEL_CALL_METHOD +		(FM_DIRECTORY_VIEW_CLASS, view, +		 restore_default_zoom_level, (view)); +} + +/** + * fm_directory_view_can_zoom_in: + * + * Determine whether the view can be zoomed any closer. + * @view: The zoomable FMDirectoryView. + * + * Return value: TRUE if @view can be zoomed any closer, FALSE otherwise. + * + **/ +gboolean +fm_directory_view_can_zoom_in (FMDirectoryView *view) +{ +	g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + +	if (!fm_directory_view_supports_zooming (view)) { +		return FALSE; +	} + +	return EEL_CALL_METHOD_WITH_RETURN_VALUE +		(FM_DIRECTORY_VIEW_CLASS, view, +		 can_zoom_in, (view)); +} + +/** + * fm_directory_view_can_rename_file + * + * Determine whether a file can be renamed. + * @file: A CajaFile + * + * Return value: TRUE if @file can be renamed, FALSE otherwise. + * + **/ +static gboolean +fm_directory_view_can_rename_file (FMDirectoryView *view, CajaFile *file) +{ +	return EEL_CALL_METHOD_WITH_RETURN_VALUE +		(FM_DIRECTORY_VIEW_CLASS, view, +		 can_rename_file, (view, file)); +} + +/** + * fm_directory_view_can_zoom_out: + * + * Determine whether the view can be zoomed any further away. + * @view: The zoomable FMDirectoryView. + * + * Return value: TRUE if @view can be zoomed any further away, FALSE otherwise. + * + **/ +gboolean +fm_directory_view_can_zoom_out (FMDirectoryView *view) +{ +	g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + +	if (!fm_directory_view_supports_zooming (view)) { +		return FALSE; +	} + +	return EEL_CALL_METHOD_WITH_RETURN_VALUE +		(FM_DIRECTORY_VIEW_CLASS, view, +		 can_zoom_out, (view)); +} + +GtkWidget * +fm_directory_view_get_background_widget (FMDirectoryView *view) +{ +	g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), NULL); + +	return EEL_CALL_METHOD_WITH_RETURN_VALUE +		(FM_DIRECTORY_VIEW_CLASS, view, +		 get_background_widget, (view)); +} + +EelBackground * +fm_directory_view_get_background (FMDirectoryView *view) +{ +	return eel_get_widget_background (fm_directory_view_get_background_widget (view)); +} + +static void +real_set_is_active (FMDirectoryView *view, +		    gboolean is_active) +{ +	EelBackground *bg; + +	bg = fm_directory_view_get_background (view); +	eel_background_set_active (bg, is_active); +} + +static void +fm_directory_view_set_is_active (FMDirectoryView *view, +				 gboolean is_active) +{ +	g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + +	EEL_CALL_METHOD (FM_DIRECTORY_VIEW_CLASS, view, +			 set_is_active, (view, is_active)); +} + +/** + * fm_directory_view_get_selection: + * + * Get a list of CajaFile pointers that represents the + * currently-selected items in this view. Subclasses must override + * the signal handler for the 'get_selection' signal. Callers are + * responsible for g_free-ing the list (but not its data). + * @view: FMDirectoryView whose selected items are of interest. + * + * Return value: GList of CajaFile pointers representing the selection. + * + **/ +GList * +fm_directory_view_get_selection (FMDirectoryView *view) +{ +	g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), NULL); + +	return EEL_CALL_METHOD_WITH_RETURN_VALUE +		(FM_DIRECTORY_VIEW_CLASS, view, +		 get_selection, (view)); +} + +void +fm_directory_view_invert_selection (FMDirectoryView *view) +{ +	g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + +	EEL_CALL_METHOD +		(FM_DIRECTORY_VIEW_CLASS, view, +		 invert_selection, (view)); +} + +GList * +fm_directory_view_get_selection_for_file_transfer (FMDirectoryView *view) +{ +	g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), NULL); + +	return EEL_CALL_METHOD_WITH_RETURN_VALUE +		(FM_DIRECTORY_VIEW_CLASS, view, +		 get_selection_for_file_transfer, (view)); +} + +guint +fm_directory_view_get_item_count (FMDirectoryView *view) +{ +	g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), 0); + +	return EEL_CALL_METHOD_WITH_RETURN_VALUE +		(FM_DIRECTORY_VIEW_CLASS, view, +		 get_item_count, (view)); +} + +GtkUIManager * +fm_directory_view_get_ui_manager (FMDirectoryView  *view) +{ +	if (view->details->window == NULL) { +		return NULL; +	} +	return caja_window_info_get_ui_manager (view->details->window); +} + +/** + * fm_directory_view_get_model: + * + * Get the model for this FMDirectoryView. + * @view: FMDirectoryView of interest. + * + * Return value: CajaDirectory for this view. + * + **/ +CajaDirectory * +fm_directory_view_get_model (FMDirectoryView *view) +{ +	g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), NULL); + +	return view->details->model; +} + +GdkAtom +fm_directory_view_get_copied_files_atom (FMDirectoryView *view) +{ +	g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), GDK_NONE); + +	return copied_files_atom; +} + +static void +prepend_uri_one (gpointer data, gpointer callback_data) +{ +	CajaFile *file; +	GList **result; + +	g_assert (CAJA_IS_FILE (data)); +	g_assert (callback_data != NULL); + +	result = (GList **) callback_data; +	file = (CajaFile *) data; +	*result = g_list_prepend (*result, caja_file_get_uri (file)); +} + +static void +offset_drop_points (GArray *relative_item_points, +		    int x_offset, int y_offset) +{ +	guint index; + +	if (relative_item_points == NULL) { +		return; +	} + +	for (index = 0; index < relative_item_points->len; index++) { +		g_array_index (relative_item_points, GdkPoint, index).x += x_offset; +		g_array_index (relative_item_points, GdkPoint, index).y += y_offset; +	} +} + +static void +fm_directory_view_create_links_for_files (FMDirectoryView *view, GList *files, +					  GArray *relative_item_points) +{ +	GList *uris; +	char *dir_uri; +	CopyMoveDoneData *copy_move_done_data; +	g_assert (relative_item_points->len == 0 +		  || g_list_length (files) == relative_item_points->len); + +        g_assert (FM_IS_DIRECTORY_VIEW (view)); +        g_assert (files != NULL); + +	/* create a list of URIs */ +	uris = NULL; +	g_list_foreach (files, prepend_uri_one, &uris); +	uris = g_list_reverse (uris); + +        g_assert (g_list_length (uris) == g_list_length (files)); + +	/* offset the drop locations a bit so that we don't pile +	 * up the icons on top of each other +	 */ +	offset_drop_points (relative_item_points, +			    DUPLICATE_HORIZONTAL_ICON_OFFSET, +			    DUPLICATE_VERTICAL_ICON_OFFSET); + +        copy_move_done_data = pre_copy_move (view); +	dir_uri = fm_directory_view_get_backing_uri (view); +	caja_file_operations_copy_move (uris, relative_item_points, dir_uri, GDK_ACTION_LINK, +					    GTK_WIDGET (view), copy_move_done_callback, copy_move_done_data); +	g_free (dir_uri); +	eel_g_list_free_deep (uris); +} + +static void +fm_directory_view_duplicate_selection (FMDirectoryView *view, GList *files, +				       GArray *relative_item_points) +{ +	GList *uris; +	CopyMoveDoneData *copy_move_done_data; + +        g_assert (FM_IS_DIRECTORY_VIEW (view)); +        g_assert (files != NULL); +	g_assert (g_list_length (files) == relative_item_points->len +		|| relative_item_points->len == 0); + +	/* create a list of URIs */ +	uris = NULL; +	g_list_foreach (files, prepend_uri_one, &uris); +	uris = g_list_reverse (uris); + +        g_assert (g_list_length (uris) == g_list_length (files)); + +	/* offset the drop locations a bit so that we don't pile +	 * up the icons on top of each other +	 */ +	offset_drop_points (relative_item_points, +			    DUPLICATE_HORIZONTAL_ICON_OFFSET, +			    DUPLICATE_VERTICAL_ICON_OFFSET); + +        copy_move_done_data = pre_copy_move (view); +	caja_file_operations_copy_move (uris, relative_item_points, NULL, GDK_ACTION_COPY, +		GTK_WIDGET (view), copy_move_done_callback, copy_move_done_data); +	eel_g_list_free_deep (uris); +} + +/* special_link_in_selection + * + * Return TRUE if one of our special links is in the selection. + * Special links include the following: + *	 CAJA_DESKTOP_LINK_TRASH, CAJA_DESKTOP_LINK_HOME, CAJA_DESKTOP_LINK_MOUNT + */ + +static gboolean +special_link_in_selection (FMDirectoryView *view) +{ +	gboolean saw_link; +	GList *selection, *node; +	CajaFile *file; + +	g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + +	saw_link = FALSE; + +	selection = fm_directory_view_get_selection (FM_DIRECTORY_VIEW (view)); + +	for (node = selection; node != NULL; node = node->next) { +		file = CAJA_FILE (node->data); + +		saw_link = CAJA_IS_DESKTOP_ICON_FILE (file); + +		if (saw_link) { +			break; +		} +	} + +	caja_file_list_free (selection); + +	return saw_link; +} + +/* desktop_or_home_dir_in_selection + * + * Return TRUE if either the desktop or the home directory is in the selection. + */ + +static gboolean +desktop_or_home_dir_in_selection (FMDirectoryView *view) +{ +	gboolean saw_desktop_or_home_dir; +	GList *selection, *node; +	CajaFile *file; + +	g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + +	saw_desktop_or_home_dir = FALSE; + +	selection = fm_directory_view_get_selection (FM_DIRECTORY_VIEW (view)); + +	for (node = selection; node != NULL; node = node->next) { +		file = CAJA_FILE (node->data); + +		saw_desktop_or_home_dir = +			caja_file_is_home (file) +			|| caja_file_is_desktop_directory (file); + +		if (saw_desktop_or_home_dir) { +			break; +		} +	} + +	caja_file_list_free (selection); + +	return saw_desktop_or_home_dir; +} + +static void +trash_or_delete_done_cb (GHashTable *debuting_uris, +			 gboolean user_cancel, +			 FMDirectoryView *view) +{ +	if (user_cancel) { +		view->details->selection_was_removed = FALSE; +	} +} + +static void +trash_or_delete_files (GtkWindow *parent_window, +		       const GList *files, +		       gboolean delete_if_all_already_in_trash, +		       FMDirectoryView *view) +{ +	GList *locations; +	const GList *node; + +	locations = NULL; +	for (node = files; node != NULL; node = node->next) { +		locations = g_list_prepend (locations, +					    caja_file_get_location ((CajaFile *) node->data)); +	} + +	locations = g_list_reverse (locations); + +	caja_file_operations_trash_or_delete (locations, +						  parent_window, +						  (CajaDeleteCallback) trash_or_delete_done_cb, +						  view); +	eel_g_object_list_free (locations); +} + +static gboolean +can_rename_file (FMDirectoryView *view, CajaFile *file) +{ +	return caja_file_can_rename (file); +} + +static void +start_renaming_file (FMDirectoryView *view, +		     CajaFile *file, +		     gboolean select_all) +{ +	if (file !=  NULL) { +		fm_directory_view_select_file (view, file); +	} +} + +typedef struct { +	FMDirectoryView *view; +	CajaFile *new_file; +} RenameData; + +static gboolean +delayed_rename_file_hack_callback (RenameData *data) +{ +	FMDirectoryView *view; +	CajaFile *new_file; + +	view = data->view; +	new_file = data->new_file; + +	if (view->details->window != NULL && +	    view->details->active) { +		EEL_CALL_METHOD (FM_DIRECTORY_VIEW_CLASS, view, start_renaming_file, (view, new_file, FALSE)); +		fm_directory_view_reveal_selection (view); +	} + +	return FALSE; +} + +static void +delayed_rename_file_hack_removed (RenameData *data) +{ +	g_object_unref (data->view); +	caja_file_unref (data->new_file); +	g_free (data); +} + + +static void +rename_file (FMDirectoryView *view, CajaFile *new_file) +{ +	RenameData *data; + +	/* HACK!!!! +	   This is a work around bug in listview. After the rename is +	   enabled we will get file changes due to info about the new +	   file being read, which will cause the model to change. When +	   the model changes GtkTreeView clears the editing. This hack just +	   delays editing for some time to try to avoid this problem. +	   A major problem is that the selection of the row causes us +	   to load the slow mimetype for the file, which leads to a +	   file_changed. So, before we delay we select the row. +	*/ +	if (FM_IS_LIST_VIEW (view)) { +		fm_directory_view_select_file (view, new_file); + +		data = g_new (RenameData, 1); +		data->view = g_object_ref (view); +		data->new_file = caja_file_ref (new_file); +		if (view->details->delayed_rename_file_id != 0) { +			g_source_remove (view->details->delayed_rename_file_id); +		} +		view->details->delayed_rename_file_id = +			g_timeout_add_full (G_PRIORITY_DEFAULT, +					    100, (GSourceFunc)delayed_rename_file_hack_callback, +					    data, (GDestroyNotify) delayed_rename_file_hack_removed); + +		return; +	} + +	/* no need to select because start_renaming_file selects +	 * fm_directory_view_select_file (view, new_file); +	 */ +	EEL_CALL_METHOD (FM_DIRECTORY_VIEW_CLASS, view, start_renaming_file, (view, new_file, FALSE)); +	fm_directory_view_reveal_selection (view); +} + +static void +reveal_newly_added_folder (FMDirectoryView *view, CajaFile *new_file, +			   CajaDirectory *directory, GFile *target_location) +{ +	GFile *location; + +	location = caja_file_get_location (new_file); +	if (g_file_equal (location, target_location)) { +		g_signal_handlers_disconnect_by_func (view, +						      G_CALLBACK (reveal_newly_added_folder), +						      (void *) target_location); +		rename_file (view, new_file); +	} +	g_object_unref (location); +} + +typedef struct { +	FMDirectoryView *directory_view; +	GHashTable *added_locations; +} NewFolderData; + + +static void +track_newly_added_locations (FMDirectoryView *view, CajaFile *new_file, +			     CajaDirectory *directory, gpointer user_data) +{ +	NewFolderData *data; + +	data = user_data; + +	g_hash_table_insert (data->added_locations, caja_file_get_location (new_file), NULL); +} + +static void +new_folder_done (GFile *new_folder, gpointer user_data) +{ +	FMDirectoryView *directory_view; +	CajaFile *file; +	char screen_string[32]; +	GdkScreen *screen; +	NewFolderData *data; + +	data = (NewFolderData *)user_data; + +	directory_view = data->directory_view; + +	if (directory_view == NULL) { +		goto fail; +	} + +	g_signal_handlers_disconnect_by_func (directory_view, +					      G_CALLBACK (track_newly_added_locations), +					      (void *) data); + +	if (new_folder == NULL) { +		goto fail; +	} + +	screen = gtk_widget_get_screen (GTK_WIDGET (directory_view)); +	g_snprintf (screen_string, sizeof (screen_string), "%d", gdk_screen_get_number (screen)); + + +	file = caja_file_get (new_folder); +	caja_file_set_metadata +		(file, CAJA_METADATA_KEY_SCREEN, +		 NULL, +		 screen_string); + +	if (g_hash_table_lookup_extended (data->added_locations, new_folder, NULL, NULL)) { +		/* The file was already added */ +		rename_file (directory_view, file); +	} else { +		/* We need to run after the default handler adds the folder we want to +		 * operate on. The ADD_FILE signal is registered as G_SIGNAL_RUN_LAST, so we +		 * must use connect_after. +		 */ +		g_signal_connect_data (directory_view, +				       "add_file", +				       G_CALLBACK (reveal_newly_added_folder), +				       g_object_ref (new_folder), +				       (GClosureNotify)g_object_unref, +				       G_CONNECT_AFTER); +	} +	caja_file_unref (file); + + fail: +	g_hash_table_destroy (data->added_locations); +	eel_remove_weak_pointer (&data->directory_view); +	g_free (data); +} + + +static NewFolderData * +new_folder_data_new (FMDirectoryView *directory_view) +{ +	NewFolderData *data; + +	data = g_new (NewFolderData, 1); +	data->directory_view = directory_view; +	data->added_locations = g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, +						       g_object_unref, NULL); +	eel_add_weak_pointer (&data->directory_view); + +	return data; +} + +static GdkPoint * +context_menu_to_file_operation_position (FMDirectoryView *directory_view) +{ +	g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (directory_view), NULL); + +	if (fm_directory_view_using_manual_layout (directory_view) +	    && directory_view->details->context_menu_position.x >= 0 +	    && directory_view->details->context_menu_position.y >= 0) { +		EEL_CALL_METHOD (FM_DIRECTORY_VIEW_CLASS, directory_view, +				 widget_to_file_operation_position, +				 (directory_view, &directory_view->details->context_menu_position)); +		return &directory_view->details->context_menu_position; +	} else { +		return NULL; +	} +} + +static void +update_context_menu_position_from_event (FMDirectoryView *view, +					 GdkEventButton  *event) +{ +	g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + +	if (event != NULL) { +		view->details->context_menu_position.x = event->x; +		view->details->context_menu_position.y = event->y; +	} else { +		view->details->context_menu_position.x = -1; +		view->details->context_menu_position.y = -1; +	} +} + +void +fm_directory_view_new_folder (FMDirectoryView *directory_view) +{ +	char *parent_uri; +	NewFolderData *data; +	GdkPoint *pos; + +	data = new_folder_data_new (directory_view); + +	g_signal_connect_data (directory_view, +			       "add_file", +			       G_CALLBACK (track_newly_added_locations), +			       data, +			       (GClosureNotify)NULL, +			       G_CONNECT_AFTER); + +	pos = context_menu_to_file_operation_position (directory_view); + +	parent_uri = fm_directory_view_get_backing_uri (directory_view); +	caja_file_operations_new_folder (GTK_WIDGET (directory_view), +					     pos, parent_uri, +					     new_folder_done, data); + +	g_free (parent_uri); +} + +static NewFolderData * +setup_new_folder_data (FMDirectoryView *directory_view) +{ +	NewFolderData *data; + +	data = new_folder_data_new (directory_view); + +	g_signal_connect_data (directory_view, +			       "add_file", +			       G_CALLBACK (track_newly_added_locations), +			       data, +			       (GClosureNotify)NULL, +			       G_CONNECT_AFTER); + +	return data; +} + +static void +fm_directory_view_new_file_with_initial_contents (FMDirectoryView *directory_view, +						  const char *parent_uri, +						  const char *filename, +						  const char *initial_contents, +						  int length, +						  GdkPoint *pos) +{ +	NewFolderData *data; + +	g_assert (parent_uri != NULL); + +	data = setup_new_folder_data (directory_view); + +	if (pos == NULL) { +		pos = context_menu_to_file_operation_position (directory_view); +	} + +	caja_file_operations_new_file (GTK_WIDGET (directory_view), +					   pos, parent_uri, filename, +					   initial_contents, length, +					   new_folder_done, data); +} + +void +fm_directory_view_new_file (FMDirectoryView *directory_view, +			    const char *parent_uri, +			    CajaFile *source) +{ +	GdkPoint *pos; +	NewFolderData *data; +	char *source_uri; +	char *container_uri; + +	container_uri = NULL; +	if (parent_uri == NULL) { +		container_uri = fm_directory_view_get_backing_uri (directory_view); +		g_assert (container_uri != NULL); +	} + +	if (source == NULL) { +		fm_directory_view_new_file_with_initial_contents (directory_view, +								  parent_uri != NULL ? parent_uri : container_uri, +								  NULL, +								  NULL, +								  0, +								  NULL); +		g_free (container_uri); +		return; +	} + +	g_return_if_fail (caja_file_is_local (source)); + +	pos = context_menu_to_file_operation_position (directory_view); + +	data = setup_new_folder_data (directory_view); + +	source_uri = caja_file_get_uri (source); + +	caja_file_operations_new_file_from_template (GTK_WIDGET (directory_view), +							 pos, +							 parent_uri != NULL ? parent_uri : container_uri, +							 NULL, +							 source_uri, +							 new_folder_done, data); + +	g_free (source_uri); +	g_free (container_uri); +} + +/* handle the open command */ + +static void +open_one_in_new_window (gpointer data, gpointer callback_data) +{ +	g_assert (CAJA_IS_FILE (data)); +	g_assert (FM_IS_DIRECTORY_VIEW (callback_data)); + +	fm_directory_view_activate_file (FM_DIRECTORY_VIEW (callback_data), +					 CAJA_FILE (data), +					 CAJA_WINDOW_OPEN_IN_NAVIGATION, +					 0); +} + +static void +open_one_in_folder_window (gpointer data, gpointer callback_data) +{ +	g_assert (CAJA_IS_FILE (data)); +	g_assert (FM_IS_DIRECTORY_VIEW (callback_data)); + +	fm_directory_view_activate_file (FM_DIRECTORY_VIEW (callback_data), +					 CAJA_FILE (data), +					 CAJA_WINDOW_OPEN_IN_SPATIAL, +					 0); +} + +CajaFile * +fm_directory_view_get_directory_as_file (FMDirectoryView *view) +{ +	g_assert (FM_IS_DIRECTORY_VIEW (view)); + +	return view->details->directory_as_file; +} + +static void +open_with_launch_application_callback (GtkAction *action, +				       gpointer callback_data) +{ +	ApplicationLaunchParameters *launch_parameters; + +	launch_parameters = (ApplicationLaunchParameters *) callback_data; +	caja_launch_application +		(launch_parameters->application, +		 launch_parameters->files, +		 fm_directory_view_get_containing_window (launch_parameters->directory_view)); +} + +static char * +escape_action_name (const char *action_name, +		    const char *prefix) +{ +	GString *s; + +	if (action_name == NULL) { +		return NULL; +	} + +	s = g_string_new (prefix); + +	while (*action_name != 0) { +		switch (*action_name) { +		case '\\': +			g_string_append (s, "\\\\"); +			break; +		case '/': +			g_string_append (s, "\\s"); +			break; +		case '&': +			g_string_append (s, "\\a"); +			break; +		case '"': +			g_string_append (s, "\\q"); +			break; +		default: +			g_string_append_c (s, *action_name); +		} + +		action_name ++; +	} +	return g_string_free (s, FALSE); +} + +static char * +escape_action_path (const char *action_path) +{ +	GString *s; + +	if (action_path == NULL) { +		return NULL; +	} + +	s = g_string_sized_new (strlen (action_path) + 2); + +	while (*action_path != 0) { +		switch (*action_path) { +		case '\\': +			g_string_append (s, "\\\\"); +			break; +		case '&': +			g_string_append (s, "\\a"); +			break; +		case '"': +			g_string_append (s, "\\q"); +			break; +		default: +			g_string_append_c (s, *action_path); +		} + +		action_path ++; +	} +	return g_string_free (s, FALSE); +} + + +static void +add_submenu (GtkUIManager *ui_manager, +	     GtkActionGroup *action_group, +	     guint merge_id, +	     const char *parent_path, +	     const char *uri, +	     const char *label, +	     GdkPixbuf *pixbuf, +	     gboolean add_action) +{ +	char *escaped_label; +	char *action_name; +	char *submenu_name; +	char *escaped_submenu_name; +	GtkAction *action; + +	if (parent_path != NULL) { +		action_name = escape_action_name (uri, "submenu_"); +		submenu_name = g_path_get_basename (uri); +		escaped_submenu_name = escape_action_path (submenu_name); +		escaped_label = eel_str_double_underscores (label); + +		if (add_action) { +			action = gtk_action_new (action_name, +						 escaped_label, +						 NULL, +						 NULL); +			if (pixbuf != NULL) { +				g_object_set_data_full (G_OBJECT (action), "menu-icon", +							g_object_ref (pixbuf), +							g_object_unref); +			} + +			g_object_set (action, "hide-if-empty", FALSE, NULL); + +			gtk_action_group_add_action (action_group, +						     action); +			g_object_unref (action); +		} + +		gtk_ui_manager_add_ui (ui_manager, +				       merge_id, +				       parent_path, +				       escaped_submenu_name, +				       action_name, +				       GTK_UI_MANAGER_MENU, +				       FALSE); +		g_free (action_name); +		g_free (escaped_label); +		g_free (submenu_name); +		g_free (escaped_submenu_name); +	} +} + +static void +add_application_to_open_with_menu (FMDirectoryView *view, +				   GAppInfo *application, +				   GList *files, +				   int index, +				   const char *menu_placeholder, +				   const char *popup_placeholder, +				   const gboolean submenu) +{ +	ApplicationLaunchParameters *launch_parameters; +	char *tip; +	char *label; +	char *action_name; +	char *escaped_app; +	char *path; +	GtkAction *action; +	GIcon *app_icon; +	GtkWidget *menuitem; + +	launch_parameters = application_launch_parameters_new +		(application, files, view); +	escaped_app = eel_str_double_underscores (g_app_info_get_display_name (application)); +	if (submenu) +		label = g_strdup_printf ("%s", escaped_app); +	else +		label = g_strdup_printf (_("Open With %s"), escaped_app); + +	tip = g_strdup_printf (ngettext ("Use \"%s\" to open the selected item", +					 "Use \"%s\" to open the selected items", +					 g_list_length (files)), +			       escaped_app); +	g_free (escaped_app); + +	action_name = g_strdup_printf ("open_with_%d", index); + +	action = gtk_action_new (action_name, +				 label, +				 tip, +				 NULL); + +	app_icon = g_app_info_get_icon (application); +	if (app_icon != NULL) { +		g_object_ref (app_icon); +	} else { +		app_icon = g_themed_icon_new ("application-x-executable"); +	} + +	gtk_action_set_gicon (action, app_icon); +	g_object_unref (app_icon); + +	g_signal_connect_data (action, "activate", +			       G_CALLBACK (open_with_launch_application_callback), +			       launch_parameters, +			       (GClosureNotify)application_launch_parameters_free, 0); + +	gtk_action_group_add_action (view->details->open_with_action_group, +				     action); +	g_object_unref (action); + +	gtk_ui_manager_add_ui (caja_window_info_get_ui_manager (view->details->window), +			       view->details->open_with_merge_id, +			       menu_placeholder, +			       action_name, +			       action_name, +			       GTK_UI_MANAGER_MENUITEM, +			       FALSE); + +	path = g_strdup_printf ("%s/%s", menu_placeholder, action_name); +	menuitem = gtk_ui_manager_get_widget ( +			caja_window_info_get_ui_manager (view->details->window), +			path); +	gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (menuitem), TRUE); +	g_free (path); + +	gtk_ui_manager_add_ui (caja_window_info_get_ui_manager (view->details->window), +			       view->details->open_with_merge_id, +			       popup_placeholder, +			       action_name, +			       action_name, +			       GTK_UI_MANAGER_MENUITEM, +			       FALSE); + +	path = g_strdup_printf ("%s/%s", popup_placeholder, action_name); +	menuitem = gtk_ui_manager_get_widget ( +			caja_window_info_get_ui_manager (view->details->window), +			path); +	gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (menuitem), TRUE); + +	g_free (path); +	g_free (action_name); +	g_free (label); +	g_free (tip); +} + +static void +get_x_content_async_callback (char **content, +			      gpointer user_data) +{ +	FMDirectoryView *view; + +	view = FM_DIRECTORY_VIEW (user_data); + +	if (view->details->window != NULL) { +		schedule_update_menus (view); +	} +	g_object_unref (view); +} + +static void +add_x_content_apps (FMDirectoryView *view, CajaFile *file, GList **applications) +{ +	GMount *mount; +	char **x_content_types; +	unsigned int n; + +	g_return_if_fail (applications != NULL); + +	mount = caja_file_get_mount (file); + +	if (mount == NULL) { +		return; +	} + +	x_content_types = caja_autorun_get_cached_x_content_types_for_mount (mount); +	if (x_content_types != NULL) { +		for (n = 0; x_content_types[n] != NULL; n++) { +			char *x_content_type = x_content_types[n]; +			GList *app_info_for_x_content_type; + +			app_info_for_x_content_type = g_app_info_get_all_for_type (x_content_type); +			*applications = g_list_concat (*applications, app_info_for_x_content_type); +		} +		g_strfreev (x_content_types); +	} else { +		caja_autorun_get_x_content_types_for_mount_async (mount, +								      get_x_content_async_callback, +								      NULL, +								      g_object_ref (view)); + +	} + +	g_object_unref (mount); +} + +static void +reset_open_with_menu (FMDirectoryView *view, GList *selection) +{ +	GList *applications, *node; +	CajaFile *file; +	gboolean submenu_visible, filter_default; +	int num_applications; +	int index; +	gboolean other_applications_visible; +	gboolean open_with_chooser_visible; +	GtkUIManager *ui_manager; +	GtkAction *action; +	GAppInfo *default_app; + +	/* Clear any previous inserted items in the applications and viewers placeholders */ + +	ui_manager = caja_window_info_get_ui_manager (view->details->window); +	caja_ui_unmerge_ui (ui_manager, +				&view->details->open_with_merge_id, +				&view->details->open_with_action_group); + +	caja_ui_prepare_merge_ui (ui_manager, +				      "OpenWithGroup", +				      &view->details->open_with_merge_id, +				      &view->details->open_with_action_group); + +	num_applications = 0; + +	other_applications_visible = (selection != NULL); +	filter_default = (selection != NULL); + +	for (node = selection; node != NULL; node = node->next) { + +		file = CAJA_FILE (node->data); + +		other_applications_visible &= +			(!caja_mime_file_opens_in_view (file) || +			 caja_file_is_directory (file)); +	} + +	default_app = NULL; +	if (filter_default) { +		default_app = caja_mime_get_default_application_for_files (selection); +	} + +	applications = NULL; +	if (other_applications_visible) { +		applications = caja_mime_get_applications_for_files (selection); +	} + +	if (g_list_length (selection) == 1) { +		add_x_content_apps (view, CAJA_FILE (selection->data), &applications); +	} + + +	num_applications = g_list_length (applications); + +	if (file_list_all_are_folders (selection)) { +		submenu_visible = (num_applications > 2); +	} else { +		submenu_visible = (num_applications > 3); +	} + +	for (node = applications, index = 0; node != NULL; node = node->next, index++) { +		GAppInfo *application; +		char *menu_path; +		char *popup_path; + +		application = node->data; + +		if (default_app != NULL && g_app_info_equal (default_app, application)) { +			continue; +		} + +		if (submenu_visible) { +			menu_path = FM_DIRECTORY_VIEW_MENU_PATH_APPLICATIONS_SUBMENU_PLACEHOLDER; +			popup_path = FM_DIRECTORY_VIEW_POPUP_PATH_APPLICATIONS_SUBMENU_PLACEHOLDER; +		} else { +			menu_path = FM_DIRECTORY_VIEW_MENU_PATH_APPLICATIONS_PLACEHOLDER; +			popup_path = FM_DIRECTORY_VIEW_POPUP_PATH_APPLICATIONS_PLACEHOLDER; +		} + +		gtk_ui_manager_add_ui (caja_window_info_get_ui_manager (view->details->window), +				       view->details->open_with_merge_id, +				       menu_path, +				       "separator", +				       NULL, +				       GTK_UI_MANAGER_SEPARATOR, +				       FALSE); + +		add_application_to_open_with_menu (view, +						   node->data, +						   selection, +						   index, +						   menu_path, popup_path, submenu_visible); +	} +	eel_g_object_list_free (applications); +	if (default_app != NULL) { +		g_object_unref (default_app); +	} + +	open_with_chooser_visible = other_applications_visible && +				    g_list_length (selection) == 1; + +	if (submenu_visible) { +		action = gtk_action_group_get_action (view->details->dir_action_group, +						      FM_ACTION_OTHER_APPLICATION1); +		gtk_action_set_visible (action, open_with_chooser_visible); +		action = gtk_action_group_get_action (view->details->dir_action_group, +						      FM_ACTION_OTHER_APPLICATION2); +		gtk_action_set_visible (action, FALSE); +	} else { +		action = gtk_action_group_get_action (view->details->dir_action_group, +						      FM_ACTION_OTHER_APPLICATION1); +		gtk_action_set_visible (action, FALSE); +		action = gtk_action_group_get_action (view->details->dir_action_group, +						      FM_ACTION_OTHER_APPLICATION2); +		gtk_action_set_visible (action, open_with_chooser_visible); +	} +} + +static GList * +get_all_extension_menu_items (GtkWidget *window, +			      GList *selection) +{ +	GList *items; +	GList *providers; +	GList *l; + +	providers = caja_module_get_extensions_for_type (CAJA_TYPE_MENU_PROVIDER); +	items = NULL; + +	for (l = providers; l != NULL; l = l->next) { +		CajaMenuProvider *provider; +		GList *file_items; + +		provider = CAJA_MENU_PROVIDER (l->data); +		file_items = caja_menu_provider_get_file_items (provider, +								    window, +								    selection); +		items = g_list_concat (items, file_items); +	} + +	caja_module_extension_list_free (providers); + +	return items; +} + +typedef struct +{ +	CajaMenuItem *item; +	FMDirectoryView *view; +	GList *selection; +	GtkAction *action; +} ExtensionActionCallbackData; + + +static void +extension_action_callback_data_free (ExtensionActionCallbackData *data) +{ +	g_object_unref (data->item); +	caja_file_list_free (data->selection); + +	g_free (data); +} + +static gboolean +search_in_menu_items (GList* items, const char *item_name) +{ +	GList* list; + +	for (list = items; list != NULL; list = list->next) { +		CajaMenu* menu; +		char *name; + +		g_object_get (list->data, "name", &name, NULL); +		if (strcmp (name, item_name) == 0) { +			g_free (name); +			return TRUE; +		} +		g_free (name); + +		menu = NULL; +		g_object_get (list->data, "menu", &menu, NULL); +		if (menu != NULL) { +			gboolean ret; +			GList* submenus; + +			submenus = caja_menu_get_items (menu); +			ret = search_in_menu_items (submenus, item_name); +			caja_menu_item_list_free (submenus); +			g_object_unref (menu); +			if (ret) { +			    return TRUE; +			} +		} +	} +	return FALSE; +} + +static void +extension_action_callback (GtkAction *action, +			   gpointer callback_data) +{ +	ExtensionActionCallbackData *data; +	char *item_name; +	gboolean is_valid; +	GList *l; +	GList *items; + +	data = callback_data; + +	/* Make sure the selected menu item is valid for the final sniffed +	 * mime type */ +	g_object_get (data->item, "name", &item_name, NULL); +	items = get_all_extension_menu_items (gtk_widget_get_toplevel (GTK_WIDGET (data->view)), +					      data->selection); + +	is_valid = search_in_menu_items (items, item_name); + +	for (l = items; l != NULL; l = l->next) { +		g_object_unref (l->data); +	} +	g_list_free (items); + +	g_free (item_name); + +	if (is_valid) { +		caja_menu_item_activate (data->item); +	} +} + +static GdkPixbuf * +get_menu_icon (const char *icon_name) +{ +	CajaIconInfo *info; +	GdkPixbuf *pixbuf; +	int size; + +	size = caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU); + +	if (g_path_is_absolute (icon_name)) { +		info = caja_icon_info_lookup_from_path (icon_name, size); +	} else { +		info = caja_icon_info_lookup_from_name (icon_name, size); +	} +	pixbuf = caja_icon_info_get_pixbuf_nodefault_at_size (info, size); +	g_object_unref (info); + +	return pixbuf; +} + +static GdkPixbuf * +get_menu_icon_for_file (CajaFile *file) +{ +	CajaIconInfo *info; +	GdkPixbuf *pixbuf; +	int size; + +	size = caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU); + +	info = caja_file_get_icon (file, size, 0); +	pixbuf = caja_icon_info_get_pixbuf_nodefault_at_size (info, size); +	g_object_unref (info); + +	return pixbuf; +} + +static GtkAction * +add_extension_action_for_files (FMDirectoryView *view, +				CajaMenuItem *item, +				GList *files) +{ +	char *name, *label, *tip, *icon; +	gboolean sensitive, priority; +	GtkAction *action; +	GdkPixbuf *pixbuf; +	ExtensionActionCallbackData *data; + +	g_object_get (G_OBJECT (item), +		      "name", &name, "label", &label, +		      "tip", &tip, "icon", &icon, +		      "sensitive", &sensitive, +		      "priority", &priority, +		      NULL); + +	action = gtk_action_new (name, +				 label, +				 tip, +				 icon); + +	if (icon != NULL) { +		pixbuf = get_menu_icon (icon); +		if (pixbuf != NULL) { +			g_object_set_data_full (G_OBJECT (action), "menu-icon", +						pixbuf, +						g_object_unref); +		} +	} + +	gtk_action_set_sensitive (action, sensitive); +	g_object_set (action, "is-important", priority, NULL); + +	data = g_new0 (ExtensionActionCallbackData, 1); +	data->item = g_object_ref (item); +	data->view = view; +	data->selection = caja_file_list_copy (files); +	data->action = action; + +	g_signal_connect_data (action, "activate", +			       G_CALLBACK (extension_action_callback), +			       data, +			       (GClosureNotify)extension_action_callback_data_free, 0); + +	gtk_action_group_add_action (view->details->extensions_menu_action_group, +				     GTK_ACTION (action)); +	g_object_unref (action); + +	g_free (name); +	g_free (label); +	g_free (tip); +	g_free (icon); + +	return action; +} + +static void +add_extension_menu_items (FMDirectoryView *view, +			  GList *files, +			  GList *menu_items, +			  const char *subdirectory) +{ +	GtkUIManager *ui_manager; +	GList *l; + +	ui_manager = caja_window_info_get_ui_manager (view->details->window); + +	for (l = menu_items; l; l = l->next) { +		CajaMenuItem *item; +		CajaMenu *menu; +		GtkAction *action; +		char *path; + +		item = CAJA_MENU_ITEM (l->data); + +		g_object_get (item, "menu", &menu, NULL); + +		action = add_extension_action_for_files (view, item, files); + +		path = g_build_path ("/", FM_DIRECTORY_VIEW_POPUP_PATH_EXTENSION_ACTIONS, subdirectory, NULL); +		gtk_ui_manager_add_ui (ui_manager, +				       view->details->extensions_menu_merge_id, +				       path, +				       gtk_action_get_name (action), +				       gtk_action_get_name (action), +				       (menu != NULL) ? GTK_UI_MANAGER_MENU : GTK_UI_MANAGER_MENUITEM, +				       FALSE); +		g_free (path); + +		path = g_build_path ("/", FM_DIRECTORY_VIEW_MENU_PATH_EXTENSION_ACTIONS_PLACEHOLDER, subdirectory, NULL); +		gtk_ui_manager_add_ui (ui_manager, +				       view->details->extensions_menu_merge_id, +				       path, +				       gtk_action_get_name (action), +				       gtk_action_get_name (action), +				       (menu != NULL) ? GTK_UI_MANAGER_MENU : GTK_UI_MANAGER_MENUITEM, +				       FALSE); +		g_free (path); + +		/* recursively fill the menu */ +		if (menu != NULL) { +			char *subdir; +			GList *children; + +			children = caja_menu_get_items (menu); + +			subdir = g_build_path ("/", subdirectory, gtk_action_get_name (action), NULL); +			add_extension_menu_items (view, +						  files, +						  children, +						  subdir); + +			caja_menu_item_list_free (children); +			g_free (subdir); +		} +	} +} + +static void +reset_extension_actions_menu (FMDirectoryView *view, GList *selection) +{ +	GList *items; +	GtkUIManager *ui_manager; + +	/* Clear any previous inserted items in the extension actions placeholder */ +	ui_manager = caja_window_info_get_ui_manager (view->details->window); + +	caja_ui_unmerge_ui (ui_manager, +				&view->details->extensions_menu_merge_id, +				&view->details->extensions_menu_action_group); + +	caja_ui_prepare_merge_ui (ui_manager, +				      "DirExtensionsMenuGroup", +				      &view->details->extensions_menu_merge_id, +				      &view->details->extensions_menu_action_group); + +	items = get_all_extension_menu_items (gtk_widget_get_toplevel (GTK_WIDGET (view)), +					      selection); +	if (items != NULL) { +		add_extension_menu_items (view, selection, items, ""); + +		g_list_foreach (items, (GFunc) g_object_unref, NULL); +		g_list_free (items); +	} +} + +static char * +change_to_view_directory (FMDirectoryView *view) +{ +	char *path; +	char *old_path; + +	old_path = g_get_current_dir (); + +	path = get_view_directory (view); + +	/* FIXME: What to do about non-local directories? */ +	if (path != NULL) { +		g_chdir (path); +	} + +	g_free (path); + +	return old_path; +} + +static char ** +get_file_names_as_parameter_array (GList *selection, +				   CajaDirectory *model) +{ +	CajaFile *file; +	char **parameters; +	GList *node; +	GFile *file_location; +	GFile *model_location; +	int i; + +	if (model == NULL) { +		return NULL; +	} + +	parameters = g_new (char *, g_list_length (selection) + 1); + +	model_location = caja_directory_get_location (model); + +	for (node = selection, i = 0; node != NULL; node = node->next, i++) { +		file = CAJA_FILE (node->data); + +		if (!caja_file_is_local (file)) { +			parameters[i] = NULL; +			g_strfreev (parameters); +			return NULL; +		} + +		file_location = caja_file_get_location (CAJA_FILE (node->data)); +		parameters[i] = g_file_get_relative_path (model_location, file_location); +		if (parameters[i] == NULL) { +			parameters[i] = g_file_get_path (file_location); +		} +		g_object_unref (file_location); +	} + +	g_object_unref (model_location); + +	parameters[i] = NULL; +	return parameters; +} + +static char * +get_file_paths_or_uris_as_newline_delimited_string (GList *selection, gboolean get_paths) +{ +	char *path; +	char *uri; +	char *result; +	CajaDesktopLink *link; +	GString *expanding_string; +	GList *node; +	GFile *location; + +	expanding_string = g_string_new (""); +	for (node = selection; node != NULL; node = node->next) { +		uri = NULL; +		if (CAJA_IS_DESKTOP_ICON_FILE (node->data)) { +			link = caja_desktop_icon_file_get_link (CAJA_DESKTOP_ICON_FILE (node->data)); +			if (link != NULL) { +				location = caja_desktop_link_get_activation_location (link); +				uri = g_file_get_uri (location); +				g_object_unref (location); +				g_object_unref (G_OBJECT (link)); +			} +		} else { +			uri = caja_file_get_uri (CAJA_FILE (node->data)); +		} +		if (uri == NULL) { +			continue; +		} + +		if (get_paths) { +			path = g_filename_from_uri (uri, NULL, NULL); +			if (path != NULL) { +				g_string_append (expanding_string, path); +				g_free (path); +				g_string_append (expanding_string, "\n"); +			} +		} else { +			g_string_append (expanding_string, uri); +			g_string_append (expanding_string, "\n"); +		} +		g_free (uri); +	} + +	result = expanding_string->str; +	g_string_free (expanding_string, FALSE); + +	return result; +} + +static char * +get_file_paths_as_newline_delimited_string (GList *selection) +{ +	return get_file_paths_or_uris_as_newline_delimited_string (selection, TRUE); +} + +static char * +get_file_uris_as_newline_delimited_string (GList *selection) +{ +	return get_file_paths_or_uris_as_newline_delimited_string (selection, FALSE); +} + +/* returns newly allocated strings for setting the environment variables */ +static void +get_strings_for_environment_variables (FMDirectoryView *view, GList *selected_files, +				       char **file_paths, char **uris, char **uri) +{ +	char *directory_uri; + +	/* We need to check that the directory uri starts with "file:" since +	 * caja_directory_is_local returns FALSE for nfs. +	 */ +	directory_uri = caja_directory_get_uri (view->details->model); +	if (eel_str_has_prefix (directory_uri, "file:") || +	    eel_uri_is_desktop (directory_uri) || +	    eel_uri_is_trash (directory_uri)) { +		*file_paths = get_file_paths_as_newline_delimited_string (selected_files); +	} else { +		*file_paths = g_strdup (""); +	} +	g_free (directory_uri); + +	*uris = get_file_uris_as_newline_delimited_string (selected_files); + +	*uri = caja_directory_get_uri (view->details->model); +	if (eel_uri_is_desktop (*uri)) { +		g_free (*uri); +		*uri = caja_get_desktop_directory_uri (); +	} +} + +static FMDirectoryView * +get_directory_view_of_extra_pane (FMDirectoryView *view) +{ +	CajaWindowSlotInfo *slot; +	CajaView *next_view; + +	slot = caja_window_info_get_extra_slot (fm_directory_view_get_caja_window (view)); +	if (slot != NULL) { +		next_view = caja_window_slot_info_get_current_view (slot); + +		if (FM_IS_DIRECTORY_VIEW (next_view)) { +			return FM_DIRECTORY_VIEW (next_view); +		} +	} +	return NULL; +} + +/* + * Set up some environment variables that scripts can use + * to take advantage of the current Caja state. + */ +static void set_script_environment_variables(FMDirectoryView* view, GList* selected_files) +{ +	char* file_paths; +	char* uris; +	char* uri; +	char* geometry_string; +	FMDirectoryView* next_view; + +	get_strings_for_environment_variables(view, selected_files, &file_paths, &uris, &uri); + +	g_setenv("CAJA_SCRIPT_SELECTED_FILE_PATHS", file_paths, TRUE); +	g_setenv("NAUTILUS_SCRIPT_SELECTED_FILE_PATHS", file_paths, TRUE); // compatibilidad GNOME + +	g_free(file_paths); + +	g_setenv("CAJA_SCRIPT_SELECTED_URIS", uris, TRUE); +	g_setenv("NAUTILUS_SCRIPT_SELECTED_URIS", uris, TRUE); // compatibilidad GNOME + +	g_free(uris); + +	g_setenv("CAJA_SCRIPT_CURRENT_URI", uri, TRUE); +	g_setenv("NAUTILUS_SCRIPT_CURRENT_URI", uri, TRUE); // compatibilidad GNOME + + +	g_free(uri); + +	geometry_string = eel_gtk_window_get_geometry_string(GTK_WINDOW (fm_directory_view_get_containing_window (view))); + +	g_setenv("CAJA_SCRIPT_WINDOW_GEOMETRY", geometry_string, TRUE); +	g_setenv("NAUTILUS_SCRIPT_WINDOW_GEOMETRY", geometry_string, TRUE); // compatibilidad GNOME + +	g_free(geometry_string); + +	/* next pane */ +	next_view = get_directory_view_of_extra_pane(view); + +	if (next_view) +	{ +		GList* next_pane_selected_files = fm_directory_view_get_selection (next_view); + +		get_strings_for_environment_variables(next_view, next_pane_selected_files, &file_paths, &uris, &uri); + +		caja_file_list_free(next_pane_selected_files); +	} +	else +	{ +		file_paths = g_strdup(""); +		uris = g_strdup(""); +		uri = g_strdup(""); +	} + +	g_setenv("CAJA_SCRIPT_NEXT_PANE_SELECTED_FILE_PATHS", file_paths, TRUE); +	g_setenv("NAUTILUS_SCRIPT_NEXT_PANE_SELECTED_FILE_PATHS", file_paths, TRUE); // compatibilidad GNOME +	g_free(file_paths); + +	g_setenv("CAJA_SCRIPT_NEXT_PANE_SELECTED_URIS", uris, TRUE); +	g_setenv("NAUTILUS_SCRIPT_NEXT_PANE_SELECTED_URIS", uris, TRUE); // compatibilidad GNOME +	g_free(uris); + +	g_setenv("CAJA_SCRIPT_NEXT_PANE_CURRENT_URI", uri, TRUE); +	g_setenv("NAUTILUS_SCRIPT_NEXT_PANE_CURRENT_URI", uri, TRUE); // compatibilidad GNOME +	g_free(uri); +} + +/* Unset all the special script environment variables. */ +static void unset_script_environment_variables(void) +{ +	g_unsetenv("CAJA_SCRIPT_SELECTED_FILE_PATHS"); +	g_unsetenv("NAUTILUS_SCRIPT_SELECTED_FILE_PATHS"); + +	g_unsetenv("CAJA_SCRIPT_SELECTED_URIS"); +	g_unsetenv("NAUTILUS_SCRIPT_SELECTED_URIS"); + +	g_unsetenv("CAJA_SCRIPT_CURRENT_URI"); +	g_unsetenv("NAUTILUS_SCRIPT_CURRENT_URI"); + +	g_unsetenv("CAJA_SCRIPT_WINDOW_GEOMETRY"); +	g_unsetenv("NAUTILUS_SCRIPT_WINDOW_GEOMETRY"); + +	g_unsetenv("CAJA_SCRIPT_NEXT_PANE_SELECTED_FILE_PATHS"); +	g_unsetenv("NAUTILUS_SCRIPT_NEXT_PANE_SELECTED_FILE_PATHS"); + +	g_unsetenv("CAJA_SCRIPT_NEXT_PANE_SELECTED_URIS"); +	g_unsetenv("NAUTILUS_SCRIPT_NEXT_PANE_SELECTED_URIS"); + +	g_unsetenv("CAJA_SCRIPT_NEXT_PANE_CURRENT_URI"); +	g_unsetenv("NAUTILUS_SCRIPT_NEXT_PANE_CURRENT_URI"); +} + +static void +run_script_callback (GtkAction *action, gpointer callback_data) +{ +	ScriptLaunchParameters *launch_parameters; +	GdkScreen *screen; +	GList *selected_files; +	char *file_uri; +	char *local_file_path; +	char *quoted_path; +	char *old_working_dir; +	char **parameters, *name; +	GtkWindow *window; + +	launch_parameters = (ScriptLaunchParameters *) callback_data; + +	file_uri = caja_file_get_uri (launch_parameters->file); +	local_file_path = g_filename_from_uri (file_uri, NULL, NULL); +	g_assert (local_file_path != NULL); +	g_free (file_uri); + +	quoted_path = g_shell_quote (local_file_path); +	g_free (local_file_path); + +	old_working_dir = change_to_view_directory (launch_parameters->directory_view); + +	selected_files = fm_directory_view_get_selection (launch_parameters->directory_view); +	set_script_environment_variables (launch_parameters->directory_view, selected_files); + +	parameters = get_file_names_as_parameter_array (selected_files, +						        launch_parameters->directory_view->details->model); + +	screen = gtk_widget_get_screen (GTK_WIDGET (launch_parameters->directory_view)); + +	name = caja_file_get_name (launch_parameters->file); +	/* FIXME: handle errors with dialog? Or leave up to each script? */ +	window = fm_directory_view_get_containing_window (launch_parameters->directory_view); +	caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, +			    "directory view run_script_callback, window=%p, name=\"%s\", script_path=\"%s\" (omitting script parameters)", +			    window, name, local_file_path); +	caja_launch_application_from_command_array (screen, name, quoted_path, FALSE, +							(const char * const *) parameters); +	g_free (name); +	g_strfreev (parameters); + +	caja_file_list_free (selected_files); +	unset_script_environment_variables (); +	g_chdir (old_working_dir); +	g_free (old_working_dir); +	g_free (quoted_path); +} + +static void +add_script_to_scripts_menus (FMDirectoryView *directory_view, +			     CajaFile *file, +			     const char *menu_path, +			     const char *popup_path, +			     const char *popup_bg_path) +{ +	ScriptLaunchParameters *launch_parameters; +	char *tip; +	char *name; +	char *uri; +	char *action_name; +	char *escaped_label; +	GdkPixbuf *pixbuf; +	GtkUIManager *ui_manager; +	GtkAction *action; + +	name = caja_file_get_display_name (file); +	uri = caja_file_get_uri (file); +	tip = g_strdup_printf (_("Run \"%s\" on any selected items"), name); + +	launch_parameters = script_launch_parameters_new (file, directory_view); + +	action_name = escape_action_name (uri, "script_"); +	escaped_label = eel_str_double_underscores (name); + +	action = gtk_action_new (action_name, +				 escaped_label, +				 tip, +				 NULL); + +	pixbuf = get_menu_icon_for_file (file); +	if (pixbuf != NULL) { +		g_object_set_data_full (G_OBJECT (action), "menu-icon", +					pixbuf, +					g_object_unref); +	} + +	g_signal_connect_data (action, "activate", +			       G_CALLBACK (run_script_callback), +			       launch_parameters, +			       (GClosureNotify)script_launch_parameters_free, 0); + +	gtk_action_group_add_action_with_accel (directory_view->details->scripts_action_group, +						action, NULL); +	g_object_unref (action); + +	ui_manager = caja_window_info_get_ui_manager (directory_view->details->window); + +	gtk_ui_manager_add_ui (ui_manager, +			       directory_view->details->scripts_merge_id, +			       menu_path, +			       action_name, +			       action_name, +			       GTK_UI_MANAGER_MENUITEM, +			       FALSE); +	gtk_ui_manager_add_ui (ui_manager, +			       directory_view->details->scripts_merge_id, +			       popup_path, +			       action_name, +			       action_name, +			       GTK_UI_MANAGER_MENUITEM, +			       FALSE); +	gtk_ui_manager_add_ui (ui_manager, +			       directory_view->details->scripts_merge_id, +			       popup_bg_path, +			       action_name, +			       action_name, +			       GTK_UI_MANAGER_MENUITEM, +			       FALSE); + +	g_free (name); +	g_free (uri); +	g_free (tip); +	g_free (action_name); +	g_free (escaped_label); +} + +static void +add_submenu_to_directory_menus (FMDirectoryView *directory_view, +				GtkActionGroup *action_group, +				guint merge_id, +				CajaFile *file, +				const char *menu_path, +				const char *popup_path, +				const char *popup_bg_path) +{ +	char *name; +	GdkPixbuf *pixbuf; +	char *uri; +	GtkUIManager *ui_manager; + +	ui_manager = caja_window_info_get_ui_manager (directory_view->details->window); +	uri = caja_file_get_uri (file); +	name = caja_file_get_display_name (file); +	pixbuf = get_menu_icon_for_file (file); +	add_submenu (ui_manager, action_group, merge_id, menu_path, uri, name, pixbuf, TRUE); +	add_submenu (ui_manager, action_group, merge_id, popup_path, uri, name, pixbuf, FALSE); +	add_submenu (ui_manager, action_group, merge_id, popup_bg_path, uri, name, pixbuf, FALSE); +	if (pixbuf) { +		g_object_unref (pixbuf); +	} +	g_free (name); +	g_free (uri); +} + +static gboolean +directory_belongs_in_scripts_menu (const char *uri) +{ +	int num_levels; +	int i; + +	if (!eel_str_has_prefix (uri, scripts_directory_uri)) { +		return FALSE; +	} + +	num_levels = 0; +	for (i = scripts_directory_uri_length; uri[i] != '\0'; i++) { +		if (uri[i] == '/') { +			num_levels++; +		} +	} + +	if (num_levels > MAX_MENU_LEVELS) { +		return FALSE; +	} + +	return TRUE; +} + +static gboolean +update_directory_in_scripts_menu (FMDirectoryView *view, CajaDirectory *directory) +{ +	char *menu_path, *popup_path, *popup_bg_path; +	GList *file_list, *filtered, *node; +	gboolean any_scripts; +	CajaFile *file; +	CajaDirectory *dir; +	char *uri; +	char *escaped_path; + +	uri = caja_directory_get_uri (directory); +	escaped_path = escape_action_path (uri + scripts_directory_uri_length); +	g_free (uri); +	menu_path = g_strconcat (FM_DIRECTORY_VIEW_MENU_PATH_SCRIPTS_PLACEHOLDER, +				 escaped_path, +				 NULL); +	popup_path = g_strconcat (FM_DIRECTORY_VIEW_POPUP_PATH_SCRIPTS_PLACEHOLDER, +				  escaped_path, +				  NULL); +	popup_bg_path = g_strconcat (FM_DIRECTORY_VIEW_POPUP_PATH_BACKGROUND_SCRIPTS_PLACEHOLDER, +				  escaped_path, +				  NULL); +	g_free (escaped_path); + +	file_list = caja_directory_get_file_list (directory); +	filtered = caja_file_list_filter_hidden_and_backup (file_list, FALSE, FALSE); +	caja_file_list_free (file_list); + +	file_list = caja_file_list_sort_by_display_name (filtered); + +	any_scripts = FALSE; +	for (node = file_list; node != NULL; node = node->next) { +		file = node->data; + +		if (caja_file_is_launchable (file)) { +			add_script_to_scripts_menus (view, file, menu_path, popup_path, popup_bg_path); +			any_scripts = TRUE; +		} else if (caja_file_is_directory (file)) { +			uri = caja_file_get_uri (file); +			if (directory_belongs_in_scripts_menu (uri)) { +				dir = caja_directory_get_by_uri (uri); +				add_directory_to_scripts_directory_list (view, dir); +				caja_directory_unref (dir); + +				add_submenu_to_directory_menus (view, +								view->details->scripts_action_group, +								view->details->scripts_merge_id, +								file, menu_path, popup_path, popup_bg_path); + +				any_scripts = TRUE; +			} +			g_free (uri); +		} +	} + +	caja_file_list_free (file_list); + +	g_free (popup_path); +	g_free (popup_bg_path); +	g_free (menu_path); + +	return any_scripts; +} + +static void +update_scripts_menu (FMDirectoryView *view) +{ +	gboolean any_scripts; +	GList *sorted_copy, *node; +	CajaDirectory *directory; +	char *uri; +	GtkUIManager *ui_manager; +	GtkAction *action; + +	/* There is a race condition here.  If we don't mark the scripts menu as +	   valid before we begin our task then we can lose script menu updates that +	   occur before we finish. */ +	view->details->scripts_invalid = FALSE; + +	ui_manager = caja_window_info_get_ui_manager (view->details->window); +	caja_ui_unmerge_ui (ui_manager, +				&view->details->scripts_merge_id, +				&view->details->scripts_action_group); + +	caja_ui_prepare_merge_ui (ui_manager, +				      "ScriptsGroup", +				      &view->details->scripts_merge_id, +				      &view->details->scripts_action_group); + +	/* As we walk through the directories, remove any that no longer belong. */ +	any_scripts = FALSE; +	sorted_copy = caja_directory_list_sort_by_uri +		(caja_directory_list_copy (view->details->scripts_directory_list)); +	for (node = sorted_copy; node != NULL; node = node->next) { +		directory = node->data; + +		uri = caja_directory_get_uri (directory); +		if (!directory_belongs_in_scripts_menu (uri)) { +			remove_directory_from_scripts_directory_list (view, directory); +		} else if (update_directory_in_scripts_menu (view, directory)) { +			any_scripts = TRUE; +		} +		g_free (uri); +	} +	caja_directory_list_free (sorted_copy); + +	action = gtk_action_group_get_action (view->details->dir_action_group, FM_ACTION_SCRIPTS); +	gtk_action_set_visible (action, any_scripts); +} + +static void +create_template_callback (GtkAction *action, gpointer callback_data) +{ +	CreateTemplateParameters *parameters; + +	parameters = callback_data; + +	fm_directory_view_new_file (parameters->directory_view, NULL, parameters->file); +} + +static void +add_template_to_templates_menus (FMDirectoryView *directory_view, +				 CajaFile *file, +				 const char *menu_path, +				 const char *popup_bg_path) +{ +	char *tmp, *tip, *uri, *name; +	char *escaped_label; +	GdkPixbuf *pixbuf; +	char *action_name; +	CreateTemplateParameters *parameters; +	GtkUIManager *ui_manager; +	GtkAction *action; + +	tmp = caja_file_get_display_name (file); +	name = eel_filename_strip_extension (tmp); +	g_free (tmp); + +	uri = caja_file_get_uri (file); +	tip = g_strdup_printf (_("Create Document from template \"%s\""), name); + +	action_name = escape_action_name (uri, "template_"); +	escaped_label = eel_str_double_underscores (name); + +	parameters = create_template_parameters_new (file, directory_view); + +	action = gtk_action_new (action_name, +				 escaped_label, +				 tip, +				 NULL); + +	pixbuf = get_menu_icon_for_file (file); +	if (pixbuf != NULL) { +		g_object_set_data_full (G_OBJECT (action), "menu-icon", +					pixbuf, +					g_object_unref); +	} + +	g_signal_connect_data (action, "activate", +			       G_CALLBACK (create_template_callback), +			       parameters, +			       (GClosureNotify)create_templates_parameters_free, 0); + +	gtk_action_group_add_action (directory_view->details->templates_action_group, +				     action); +	g_object_unref (action); + +	ui_manager = caja_window_info_get_ui_manager (directory_view->details->window); + +	gtk_ui_manager_add_ui (ui_manager, +			       directory_view->details->templates_merge_id, +			       menu_path, +			       action_name, +			       action_name, +			       GTK_UI_MANAGER_MENUITEM, +			       FALSE); + +	gtk_ui_manager_add_ui (ui_manager, +			       directory_view->details->templates_merge_id, +			       popup_bg_path, +			       action_name, +			       action_name, +			       GTK_UI_MANAGER_MENUITEM, +			       FALSE); + +	g_free (escaped_label); +	g_free (name); +	g_free (tip); +	g_free (uri); +	g_free (action_name); +} + +static void +update_templates_directory (FMDirectoryView *view) +{ +	CajaDirectory *templates_directory; +	GList *node, *next; +	char *templates_uri; + +	for (node = view->details->templates_directory_list; node != NULL; node = next) { +		next = node->next; +		remove_directory_from_templates_directory_list (view, node->data); +	} + +	if (caja_should_use_templates_directory ()) { +		templates_uri = caja_get_templates_directory_uri (); +		templates_directory = caja_directory_get_by_uri (templates_uri); +		g_free (templates_uri); +		add_directory_to_templates_directory_list (view, templates_directory); +		caja_directory_unref (templates_directory); +	} +} + +static void +user_dirs_changed (FMDirectoryView *view) +{ +	update_templates_directory (view); +	view->details->templates_invalid = TRUE; +	schedule_update_menus (view); +} + +static gboolean +directory_belongs_in_templates_menu (const char *templates_directory_uri, +				     const char *uri) +{ +	int num_levels; +	int i; + +	if (templates_directory_uri == NULL) { +		return FALSE; +	} + +	if (!g_str_has_prefix (uri, templates_directory_uri)) { +		return FALSE; +	} + +	num_levels = 0; +	for (i = strlen (templates_directory_uri); uri[i] != '\0'; i++) { +		if (uri[i] == '/') { +			num_levels++; +		} +	} + +	if (num_levels > MAX_MENU_LEVELS) { +		return FALSE; +	} + +	return TRUE; +} + +static gboolean +update_directory_in_templates_menu (FMDirectoryView *view, +				    const char *templates_directory_uri, +				    CajaDirectory *directory) +{ +	char *menu_path, *popup_bg_path; +	GList *file_list, *filtered, *node; +	gboolean any_templates; +	CajaFile *file; +	CajaDirectory *dir; +	char *escaped_path; +	char *uri; +	int num; + +	/* We know this directory belongs to the template dir, so it must exist */ +	g_assert (templates_directory_uri); + +	uri = caja_directory_get_uri (directory); +	escaped_path = escape_action_path (uri + strlen (templates_directory_uri)); +	g_free (uri); +	menu_path = g_strconcat (FM_DIRECTORY_VIEW_MENU_PATH_NEW_DOCUMENTS_PLACEHOLDER, +				 escaped_path, +				 NULL); +	popup_bg_path = g_strconcat (FM_DIRECTORY_VIEW_POPUP_PATH_BACKGROUND_NEW_DOCUMENTS_PLACEHOLDER, +				     escaped_path, +				     NULL); +	g_free (escaped_path); + +	file_list = caja_directory_get_file_list (directory); +	filtered = caja_file_list_filter_hidden_and_backup (file_list, FALSE, FALSE); +	caja_file_list_free (file_list); + +	file_list = caja_file_list_sort_by_display_name (filtered); + +	num = 0; +	any_templates = FALSE; +	for (node = file_list; num < TEMPLATE_LIMIT && node != NULL; node = node->next, num++) { +		file = node->data; + +		if (caja_file_is_directory (file)) { +			uri = caja_file_get_uri (file); +			if (directory_belongs_in_templates_menu (templates_directory_uri, uri)) { +				dir = caja_directory_get_by_uri (uri); +				add_directory_to_templates_directory_list (view, dir); +				caja_directory_unref (dir); + +				add_submenu_to_directory_menus (view, +								view->details->templates_action_group, +								view->details->templates_merge_id, +								file, menu_path, NULL, popup_bg_path); + +				any_templates = TRUE; +			} +			g_free (uri); +		} else if (caja_file_can_read (file)) { +			add_template_to_templates_menus (view, file, menu_path, popup_bg_path); +			any_templates = TRUE; +		} +	} + +	caja_file_list_free (file_list); + +	g_free (popup_bg_path); +	g_free (menu_path); + +	return any_templates; +} + + + +static void +update_templates_menu (FMDirectoryView *view) +{ +	gboolean any_templates; +	GList *sorted_copy, *node; +	CajaDirectory *directory; +	GtkUIManager *ui_manager; +	char *uri; +	GtkAction *action; +	char *templates_directory_uri; + +	if (caja_should_use_templates_directory ()) { +		templates_directory_uri = caja_get_templates_directory_uri (); +	} else { +		templates_directory_uri = NULL; +	} + +	/* There is a race condition here.  If we don't mark the scripts menu as +	   valid before we begin our task then we can lose template menu updates that +	   occur before we finish. */ +	view->details->templates_invalid = FALSE; + +	ui_manager = caja_window_info_get_ui_manager (view->details->window); +	caja_ui_unmerge_ui (ui_manager, +				&view->details->templates_merge_id, +				&view->details->templates_action_group); + +	caja_ui_prepare_merge_ui (ui_manager, +				      "TemplatesGroup", +				      &view->details->templates_merge_id, +				      &view->details->templates_action_group); + +	/* As we walk through the directories, remove any that no longer belong. */ +	any_templates = FALSE; +	sorted_copy = caja_directory_list_sort_by_uri +		(caja_directory_list_copy (view->details->templates_directory_list)); +	for (node = sorted_copy; node != NULL; node = node->next) { +		directory = node->data; + +		uri = caja_directory_get_uri (directory); +		if (!directory_belongs_in_templates_menu (templates_directory_uri, uri)) { +			remove_directory_from_templates_directory_list (view, directory); +		} else if (update_directory_in_templates_menu (view, +							       templates_directory_uri, +							       directory)) { +			any_templates = TRUE; +		} +		g_free (uri); +	} +	caja_directory_list_free (sorted_copy); + +	action = gtk_action_group_get_action (view->details->dir_action_group, FM_ACTION_NO_TEMPLATES); +	gtk_action_set_visible (action, !any_templates); + +	g_free (templates_directory_uri); +} + + +static void +action_open_scripts_folder_callback (GtkAction *action, +				     gpointer callback_data) +{ +	FMDirectoryView *view; + +	view = FM_DIRECTORY_VIEW (callback_data); + +	open_location (view, scripts_directory_uri, CAJA_WINDOW_OPEN_ACCORDING_TO_MODE, 0); + +	eel_show_info_dialog_with_details +		(_("All executable files in this folder will appear in the " +		   "Scripts menu."), +		 _("Choosing a script from the menu will run " +		   "that script with any selected items as input."), +		 _("All executable files in this folder will appear in the " +		   "Scripts menu. Choosing a script from the menu will run " +		   "that script.\n\n" +		   "When executed from a local folder, scripts will be passed " +		   "the selected file names. When executed from a remote folder " +		   "(e.g. a folder showing web or ftp content), scripts will " +		   "be passed no parameters.\n\n" +		   "In all cases, the following environment variables will be " +		   "set by Caja, which the scripts may use:\n\n" +		   "CAJA_SCRIPT_SELECTED_FILE_PATHS: newline-delimited paths for selected files (only if local)\n\n" +		   "CAJA_SCRIPT_SELECTED_URIS: newline-delimited URIs for selected files\n\n" +		   "CAJA_SCRIPT_CURRENT_URI: URI for current location\n\n" +		   "CAJA_SCRIPT_WINDOW_GEOMETRY: position and size of current window\n\n" +		   "CAJA_SCRIPT_NEXT_PANE_SELECTED_FILE_PATHS: newline-delimited paths for selected files in the inactive pane of a split-view window (only if local)\n\n" +		   "CAJA_SCRIPT_NEXT_PANE_SELECTED_URIS: newline-delimited URIs for selected files in the inactive pane of a split-view window\n\n" +		   "CAJA_SCRIPT_NEXT_PANE_CURRENT_URI: URI for current location in the inactive pane of a split-view window"), +		 fm_directory_view_get_containing_window (view)); +} + +static GtkMenu * +create_popup_menu (FMDirectoryView *view, const char *popup_path) +{ +	GtkWidget *menu; + +	menu = gtk_ui_manager_get_widget (caja_window_info_get_ui_manager (view->details->window), +					  popup_path); +	gtk_menu_set_screen (GTK_MENU (menu), +			     gtk_widget_get_screen (GTK_WIDGET (view))); +	gtk_widget_show (GTK_WIDGET (menu)); + +	return GTK_MENU (menu); +} + +static void +copy_or_cut_files (FMDirectoryView *view, +		   GList           *clipboard_contents, +		   gboolean         cut) +{ +	int count; +	char *status_string, *name; +	CajaClipboardInfo info; +        GtkTargetList *target_list; +        GtkTargetEntry *targets; +        int n_targets; + +	info.files = clipboard_contents; +	info.cut = cut; + +        target_list = gtk_target_list_new (NULL, 0); +        gtk_target_list_add (target_list, copied_files_atom, 0, 0); +        gtk_target_list_add_uri_targets (target_list, 0); +        gtk_target_list_add_text_targets (target_list, 0); + +        targets = gtk_target_table_new_from_list (target_list, &n_targets); +        gtk_target_list_unref (target_list); + +	gtk_clipboard_set_with_data (caja_clipboard_get (GTK_WIDGET (view)), +				     targets, n_targets, +				     caja_get_clipboard_callback, caja_clear_clipboard_callback, +				     NULL); +        gtk_target_table_free (targets, n_targets); + +	caja_clipboard_monitor_set_clipboard_info (caja_clipboard_monitor_get (), &info); + +	count = g_list_length (clipboard_contents); +	if (count == 1) { +		name = caja_file_get_display_name (clipboard_contents->data); +		if (cut) { +			status_string = g_strdup_printf (_("\"%s\" will be moved " +							   "if you select the Paste command"), +							 name); +		} else { +			status_string = g_strdup_printf (_("\"%s\" will be copied " +							   "if you select the Paste command"), +							 name); +		} +		g_free (name); +	} else { +		if (cut) { +			status_string = g_strdup_printf (ngettext("The %'d selected item will be moved " +								  "if you select the Paste command", +								  "The %'d selected items will be moved " +								  "if you select the Paste command", +								  count), +							 count); +		} else { +			status_string = g_strdup_printf (ngettext("The %'d selected item will be copied " +								  "if you select the Paste command", +								  "The %'d selected items will be copied " +								  "if you select the Paste command", +								  count), +							 count); +		} +	} + +	caja_window_slot_info_set_status (view->details->slot, +					      status_string); +	g_free (status_string); +} + +static void +action_copy_files_callback (GtkAction *action, +			    gpointer callback_data) +{ +	FMDirectoryView *view; +	GList *selection; + +	view = FM_DIRECTORY_VIEW (callback_data); + +	selection = fm_directory_view_get_selection_for_file_transfer (view); +	copy_or_cut_files (view, selection, FALSE); +	caja_file_list_free (selection); +} + +static void +move_copy_selection_to_location (FMDirectoryView *view, +				 int copy_action, +				 char *target_uri) +{ +	GList *selection, *uris, *l; + +	selection = fm_directory_view_get_selection_for_file_transfer (view); +	if (selection == NULL) { +		return; +	} + +	uris = NULL; +	for (l = selection; l != NULL; l = l->next) { +		uris = g_list_prepend (uris, +				       caja_file_get_uri ((CajaFile *) l->data)); +	} +	uris = g_list_reverse (uris); + +	fm_directory_view_move_copy_items (uris, NULL, target_uri, +					   copy_action, +					   0, 0, +					   view); + +	eel_g_list_free_deep (uris); +	caja_file_list_free (selection); +} + +static void +move_copy_selection_to_next_pane (FMDirectoryView *view, +				  int copy_action) +{ +	CajaWindowSlotInfo *slot; +	char *dest_location; + +	slot = caja_window_info_get_extra_slot (fm_directory_view_get_caja_window (view)); +	g_return_if_fail (slot != NULL); + +	dest_location = caja_window_slot_info_get_current_location (slot); +	g_return_if_fail (dest_location != NULL); + +	move_copy_selection_to_location (view, copy_action, dest_location); +} + +static void +action_copy_to_next_pane_callback (GtkAction *action, gpointer callback_data) +{ +	FMDirectoryView *view; + +	view = FM_DIRECTORY_VIEW (callback_data); +	move_copy_selection_to_next_pane (view, +					  GDK_ACTION_COPY); +} + +static void +action_move_to_next_pane_callback (GtkAction *action, gpointer callback_data) +{ +	CajaWindowSlotInfo *slot; +	char *dest_location; +	FMDirectoryView *view; + +	view = FM_DIRECTORY_VIEW (callback_data); + +	slot = caja_window_info_get_extra_slot (fm_directory_view_get_caja_window (view)); +	g_return_if_fail (slot != NULL); + +	dest_location = caja_window_slot_info_get_current_location (slot); +	g_return_if_fail (dest_location != NULL); + +	move_copy_selection_to_location (view, GDK_ACTION_MOVE, dest_location); +} + +static void +action_copy_to_home_callback (GtkAction *action, gpointer callback_data) +{ +	FMDirectoryView *view; +	char *dest_location; + +	view = FM_DIRECTORY_VIEW (callback_data); + +	dest_location = caja_get_home_directory_uri (); +	move_copy_selection_to_location (view, GDK_ACTION_COPY, dest_location); +	g_free (dest_location); +} + +static void +action_move_to_home_callback (GtkAction *action, gpointer callback_data) +{ +	FMDirectoryView *view; +	char *dest_location; + +	view = FM_DIRECTORY_VIEW (callback_data); + +	dest_location = caja_get_home_directory_uri (); +	move_copy_selection_to_location (view, GDK_ACTION_MOVE, dest_location); +	g_free (dest_location); +} + +static void +action_copy_to_desktop_callback (GtkAction *action, gpointer callback_data) +{ +	FMDirectoryView *view; +	char *dest_location; + +	view = FM_DIRECTORY_VIEW (callback_data); + +	dest_location = caja_get_desktop_directory_uri (); +	move_copy_selection_to_location (view, GDK_ACTION_COPY, dest_location); +	g_free (dest_location); +} + +static void +action_move_to_desktop_callback (GtkAction *action, gpointer callback_data) +{ +	FMDirectoryView *view; +	char *dest_location; + +	view = FM_DIRECTORY_VIEW (callback_data); + +	dest_location = caja_get_desktop_directory_uri (); +	move_copy_selection_to_location (view, GDK_ACTION_MOVE, dest_location); +	g_free (dest_location); +} + +static void +action_cut_files_callback (GtkAction *action, +			   gpointer callback_data) +{ +	FMDirectoryView *view; +	GList *selection; + +	view = FM_DIRECTORY_VIEW (callback_data); + +	selection = fm_directory_view_get_selection_for_file_transfer (view); +	copy_or_cut_files (view, selection, TRUE); +	caja_file_list_free (selection); +} + +static void +paste_clipboard_data (FMDirectoryView *view, +		      GtkSelectionData *selection_data, +		      char *destination_uri) +{ +	gboolean cut; +	GList *item_uris; + +	cut = FALSE; +	item_uris = caja_clipboard_get_uri_list_from_selection_data (selection_data, &cut, +									 copied_files_atom); + +	if (item_uris == NULL|| destination_uri == NULL) { +		caja_window_slot_info_set_status (view->details->slot, +						      _("There is nothing on the clipboard to paste.")); +	} else { +		fm_directory_view_move_copy_items (item_uris, NULL, destination_uri, +						   cut ? GDK_ACTION_MOVE : GDK_ACTION_COPY, +						   0, 0, +						   view); + +		/* If items are cut then remove from clipboard */ +		if (cut) { +			gtk_clipboard_clear (caja_clipboard_get (GTK_WIDGET (view))); +		} + +		eel_g_list_free_deep (item_uris); +	} +} + +static void +paste_clipboard_received_callback (GtkClipboard     *clipboard, +				   GtkSelectionData *selection_data, +				   gpointer          data) +{ +	FMDirectoryView *view; +	char *view_uri; + +	view = FM_DIRECTORY_VIEW (data); + +	view_uri = fm_directory_view_get_backing_uri (view); + +	if (view->details->window != NULL) { +		paste_clipboard_data (view, selection_data, view_uri); +	} + +	g_free (view_uri); + +	g_object_unref (view); +} + +typedef struct { +	FMDirectoryView *view; +	CajaFile *target; +} PasteIntoData; + +static void +paste_into_clipboard_received_callback (GtkClipboard     *clipboard, +					GtkSelectionData *selection_data, +					gpointer          callback_data) +{ +	PasteIntoData *data; +	FMDirectoryView *view; +	char *directory_uri; + +	data = (PasteIntoData *) callback_data; + +	view = FM_DIRECTORY_VIEW (data->view); + +	if (view->details->window != NULL) { +		directory_uri = caja_file_get_activation_uri (data->target); + +		paste_clipboard_data (view, selection_data, directory_uri); + +		g_free (directory_uri); +	} + +	g_object_unref (view); +	caja_file_unref (data->target); +	g_free (data); +} + +static void +action_paste_files_callback (GtkAction *action, +			     gpointer callback_data) +{ +	FMDirectoryView *view; + +	view = FM_DIRECTORY_VIEW (callback_data); + +	g_object_ref (view); +	gtk_clipboard_request_contents (caja_clipboard_get (GTK_WIDGET (view)), +					copied_files_atom, +					paste_clipboard_received_callback, +					view); +} + +static void +paste_into (FMDirectoryView *view, +	    CajaFile *target) +{ +	PasteIntoData *data; + +	g_assert (FM_IS_DIRECTORY_VIEW (view)); +	g_assert (CAJA_IS_FILE (target)); + +	data = g_new (PasteIntoData, 1); + +	data->view = g_object_ref (view); +	data->target = caja_file_ref (target); + +	gtk_clipboard_request_contents (caja_clipboard_get (GTK_WIDGET (view)), +					copied_files_atom, +					paste_into_clipboard_received_callback, +					data); +} + +static void +action_paste_files_into_callback (GtkAction *action, +				  gpointer callback_data) +{ +	FMDirectoryView *view; +	GList *selection; + +	view = FM_DIRECTORY_VIEW (callback_data); +	selection = fm_directory_view_get_selection (view); +	if (selection != NULL) { +		paste_into (view, CAJA_FILE (selection->data)); +		caja_file_list_free (selection); +	} + +} + +static void +real_action_rename (FMDirectoryView *view, +		    gboolean select_all) +{ +	CajaFile *file; +	GList *selection; + +	g_assert (FM_IS_DIRECTORY_VIEW (view)); + +	selection = fm_directory_view_get_selection (view); + +	if (selection_not_empty_in_menu_callback (view, selection)) { +		file = CAJA_FILE (selection->data); +		if (!select_all) { +			/* directories don't have a file extension, so +			 * they are always pre-selected as a whole */ +			select_all = caja_file_is_directory (file); +		} +		EEL_CALL_METHOD (FM_DIRECTORY_VIEW_CLASS, view, start_renaming_file, (view, file, select_all)); +	} + +	caja_file_list_free (selection); +} + +static void +action_rename_callback (GtkAction *action, +			gpointer callback_data) +{ +	real_action_rename (FM_DIRECTORY_VIEW (callback_data), FALSE); +} + +static void +action_rename_select_all_callback (GtkAction *action, +				   gpointer callback_data) +{ +	real_action_rename (FM_DIRECTORY_VIEW (callback_data), TRUE); +} + +static void +file_mount_callback (CajaFile  *file, +		     GFile         *result_location, +		     GError        *error, +		     gpointer       callback_data) +{ +	if (error != NULL && +	    (error->domain != G_IO_ERROR || +	     (error->code != G_IO_ERROR_CANCELLED && +	      error->code != G_IO_ERROR_FAILED_HANDLED && +	      error->code != G_IO_ERROR_ALREADY_MOUNTED))) { +		eel_show_error_dialog (_("Unable to mount location"), +				       error->message, NULL); +	} +} + +static void +file_unmount_callback (CajaFile  *file, +		       GFile         *result_location, +		       GError        *error, +		       gpointer       callback_data) +{ +	FMDirectoryView *view; + +	view = FM_DIRECTORY_VIEW (callback_data); +	fm_directory_view_set_initiated_unmount (view, FALSE); +	g_object_unref (view); + +	if (error != NULL && +	    (error->domain != G_IO_ERROR || +	     (error->code != G_IO_ERROR_CANCELLED && +	      error->code != G_IO_ERROR_FAILED_HANDLED))) { +		eel_show_error_dialog (_("Unable to unmount location"), +				       error->message, NULL); +	} +} + +static void +file_eject_callback (CajaFile  *file, +		     GFile         *result_location, +		     GError        *error, +		     gpointer       callback_data) +{ +	FMDirectoryView *view; + +	view = FM_DIRECTORY_VIEW (callback_data); +	fm_directory_view_set_initiated_unmount (view, FALSE); +	g_object_unref (view); + +	if (error != NULL && +	    (error->domain != G_IO_ERROR || +	     (error->code != G_IO_ERROR_CANCELLED && +	      error->code != G_IO_ERROR_FAILED_HANDLED))) { +		eel_show_error_dialog (_("Unable to eject location"), +				       error->message, NULL); +	} +} + +static void +file_stop_callback (CajaFile  *file, +		    GFile         *result_location, +		    GError        *error, +		    gpointer       callback_data) +{ +	if (error != NULL && +	    (error->domain != G_IO_ERROR || +	     (error->code != G_IO_ERROR_CANCELLED && +	      error->code != G_IO_ERROR_FAILED_HANDLED))) { +		eel_show_error_dialog (_("Unable to stop drive"), +				       error->message, NULL); +	} +} + +static void +action_mount_volume_callback (GtkAction *action, +			      gpointer data) +{ +	CajaFile *file; +	GList *selection, *l; +	FMDirectoryView *view; +	GMountOperation *mount_op; + +        view = FM_DIRECTORY_VIEW (data); + +	selection = fm_directory_view_get_selection (view); +	for (l = selection; l != NULL; l = l->next) { +		file = CAJA_FILE (l->data); + +		if (caja_file_can_mount (file)) { +			mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); +			g_mount_operation_set_password_save (mount_op, G_PASSWORD_SAVE_FOR_SESSION); +			caja_file_mount (file, mount_op, NULL, +					     file_mount_callback, NULL); +			g_object_unref (mount_op); +		} +	} +	caja_file_list_free (selection); +} + +static void +action_unmount_volume_callback (GtkAction *action, +				gpointer data) +{ +	CajaFile *file; +	GList *selection, *l; +	FMDirectoryView *view; + +        view = FM_DIRECTORY_VIEW (data); + +	selection = fm_directory_view_get_selection (view); + +	for (l = selection; l != NULL; l = l->next) { +		file = CAJA_FILE (l->data); +		if (caja_file_can_unmount (file)) { +			GMountOperation *mount_op; +			mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); +			fm_directory_view_set_initiated_unmount (view, TRUE); +			caja_file_unmount (file, mount_op, NULL, +					       file_unmount_callback, g_object_ref (view)); +			g_object_unref (mount_op); +		} +	} +	caja_file_list_free (selection); +} + +static void +action_format_volume_callback (GtkAction *action, +			       gpointer   data) +{ +#ifdef TODO_GIO +	CajaFile *file; +	GList *selection, *l; +	FMDirectoryView *view; + +        view = FM_DIRECTORY_VIEW (data); + +	selection = fm_directory_view_get_selection (view); +	for (l = selection; l != NULL; l = l->next) { +		file = CAJA_FILE (l->data); + +		if (something) { +			g_spawn_command_line_async ("gfloppy", NULL); +		} +	} +	caja_file_list_free (selection); +#endif +} + +static void +action_eject_volume_callback (GtkAction *action, +			      gpointer data) +{ +	CajaFile *file; +	GList *selection, *l; +	FMDirectoryView *view; + +        view = FM_DIRECTORY_VIEW (data); + +	selection = fm_directory_view_get_selection (view); +	for (l = selection; l != NULL; l = l->next) { +		file = CAJA_FILE (l->data); + +		if (caja_file_can_eject (file)) { +			GMountOperation *mount_op; +			mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); +			fm_directory_view_set_initiated_unmount (view, TRUE); +			caja_file_eject (file, mount_op, NULL, +					     file_eject_callback, g_object_ref (view)); +			g_object_unref (mount_op); +		} +	} +	caja_file_list_free (selection); +} + +static void +file_start_callback (CajaFile  *file, +		     GFile         *result_location, +		     GError        *error, +		     gpointer       callback_data) +{ +	if (error != NULL && +	    (error->domain != G_IO_ERROR || +	     (error->code != G_IO_ERROR_CANCELLED && +	      error->code != G_IO_ERROR_FAILED_HANDLED && +	      error->code != G_IO_ERROR_ALREADY_MOUNTED))) { +		eel_show_error_dialog (_("Unable to start location"), +				       error->message, NULL); +	} +} + +static void +action_start_volume_callback (GtkAction *action, +			      gpointer   data) +{ +	CajaFile *file; +	GList *selection, *l; +	FMDirectoryView *view; +	GMountOperation *mount_op; + +        view = FM_DIRECTORY_VIEW (data); + +	selection = fm_directory_view_get_selection (view); +	for (l = selection; l != NULL; l = l->next) { +		file = CAJA_FILE (l->data); + +		if (caja_file_can_start (file) || caja_file_can_start_degraded (file)) { +			mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); +			caja_file_start (file, mount_op, NULL, +					     file_start_callback, NULL); +			g_object_unref (mount_op); +		} +	} +	caja_file_list_free (selection); +} + +static void +action_stop_volume_callback (GtkAction *action, +			     gpointer   data) +{ +	CajaFile *file; +	GList *selection, *l; +	FMDirectoryView *view; + +        view = FM_DIRECTORY_VIEW (data); + +	selection = fm_directory_view_get_selection (view); +	for (l = selection; l != NULL; l = l->next) { +		file = CAJA_FILE (l->data); + +		if (caja_file_can_stop (file)) { +			GMountOperation *mount_op; +			mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); +			caja_file_stop (file, mount_op, NULL, +					    file_stop_callback, NULL); +			g_object_unref (mount_op); +		} +	} +	caja_file_list_free (selection); +} + +static void +action_detect_media_callback (GtkAction *action, +			      gpointer   data) +{ +	CajaFile *file; +	GList *selection, *l; +	FMDirectoryView *view; + +        view = FM_DIRECTORY_VIEW (data); + +	selection = fm_directory_view_get_selection (view); +	for (l = selection; l != NULL; l = l->next) { +		file = CAJA_FILE (l->data); + +		if (caja_file_can_poll_for_media (file) && !caja_file_is_media_check_automatic (file)) { +			caja_file_poll_for_media (file); +		} +	} +	caja_file_list_free (selection); +} + +static void +action_self_mount_volume_callback (GtkAction *action, +				   gpointer data) +{ +	CajaFile *file; +	FMDirectoryView *view; +	GMountOperation *mount_op; + +	view = FM_DIRECTORY_VIEW (data); + +	file = fm_directory_view_get_directory_as_file (view); +	if (file == NULL) { +		return; +	} + +	mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); +	g_mount_operation_set_password_save (mount_op, G_PASSWORD_SAVE_FOR_SESSION); +	caja_file_mount (file, mount_op, NULL, file_mount_callback, NULL); +	g_object_unref (mount_op); +} + +static void +action_self_unmount_volume_callback (GtkAction *action, +				     gpointer data) +{ +	CajaFile *file; +	FMDirectoryView *view; +	GMountOperation *mount_op; + +	view = FM_DIRECTORY_VIEW (data); + +	file = fm_directory_view_get_directory_as_file (view); +	if (file == NULL) { +		return; +	} + +	mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); +	fm_directory_view_set_initiated_unmount (view, TRUE); +	caja_file_unmount (file, mount_op, NULL, file_unmount_callback, g_object_ref (view)); +	g_object_unref (mount_op); +} + +static void +action_self_eject_volume_callback (GtkAction *action, +				   gpointer data) +{ +	CajaFile *file; +	FMDirectoryView *view; +	GMountOperation *mount_op; + +	view = FM_DIRECTORY_VIEW (data); + +	file = fm_directory_view_get_directory_as_file (view); +	if (file == NULL) { +		return; +	} + +	mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); +	fm_directory_view_set_initiated_unmount (view, TRUE); +	caja_file_eject (file, mount_op, NULL, file_eject_callback, g_object_ref (view)); +	g_object_unref (mount_op); +} + +static void +action_self_format_volume_callback (GtkAction *action, +				    gpointer   data) +{ +	CajaFile *file; +	FMDirectoryView *view; + +	view = FM_DIRECTORY_VIEW (data); + +	file = fm_directory_view_get_directory_as_file (view); +	if (file == NULL) { +		return; +	} + +#ifdef TODO_GIO +	if (something) { +		g_spawn_command_line_async ("gfloppy", NULL); +	} +#endif +} + +static void +action_self_start_volume_callback (GtkAction *action, +				   gpointer   data) +{ +	CajaFile *file; +	FMDirectoryView *view; +	GMountOperation *mount_op; + +	view = FM_DIRECTORY_VIEW (data); + +	file = fm_directory_view_get_directory_as_file (view); +	if (file == NULL) { +		return; +	} + +	mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); +	caja_file_start (file, mount_op, NULL, file_start_callback, NULL); +	g_object_unref (mount_op); +} + +static void +action_self_stop_volume_callback (GtkAction *action, +				  gpointer   data) +{ +	CajaFile *file; +	FMDirectoryView *view; +	GMountOperation *mount_op; + +	view = FM_DIRECTORY_VIEW (data); + +	file = fm_directory_view_get_directory_as_file (view); +	if (file == NULL) { +		return; +	} + +	mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); +	caja_file_stop (file, mount_op, NULL, +			    file_stop_callback, NULL); +	g_object_unref (mount_op); +} + +static void +action_self_detect_media_callback (GtkAction *action, +				   gpointer   data) +{ +	CajaFile *file; +	FMDirectoryView *view; + +	view = FM_DIRECTORY_VIEW (data); + +	file = fm_directory_view_get_directory_as_file (view); +	if (file == NULL) { +		return; +	} + +	caja_file_poll_for_media (file); +} + +static void +action_location_mount_volume_callback (GtkAction *action, +				       gpointer data) +{ +	CajaFile *file; +	FMDirectoryView *view; +	GMountOperation *mount_op; + +	view = FM_DIRECTORY_VIEW (data); + +	file = view->details->location_popup_directory_as_file; +	if (file == NULL) { +		return; +	} + +	mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); +	g_mount_operation_set_password_save (mount_op, G_PASSWORD_SAVE_FOR_SESSION); +	caja_file_mount (file, mount_op, NULL, file_mount_callback, NULL); +	g_object_unref (mount_op); +} + +static void +action_location_unmount_volume_callback (GtkAction *action, +					 gpointer data) +{ +	CajaFile *file; +	FMDirectoryView *view; +	GMountOperation *mount_op; + +	view = FM_DIRECTORY_VIEW (data); + +	file = view->details->location_popup_directory_as_file; +	if (file == NULL) { +		return; +	} + +	mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); +	fm_directory_view_set_initiated_unmount (view, TRUE); +	caja_file_unmount (file, mount_op, NULL, +			       file_unmount_callback, g_object_ref (view)); +	g_object_unref (mount_op); +} + +static void +action_location_eject_volume_callback (GtkAction *action, +				       gpointer data) +{ +	CajaFile *file; +	FMDirectoryView *view; +	GMountOperation *mount_op; + +	view = FM_DIRECTORY_VIEW (data); + +	file = view->details->location_popup_directory_as_file; +	if (file == NULL) { +		return; +	} + +	mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); +	fm_directory_view_set_initiated_unmount (view, TRUE); +	caja_file_eject (file, mount_op, NULL, +			     file_eject_callback, g_object_ref (view)); +	g_object_unref (mount_op); +} + +static void +action_location_format_volume_callback (GtkAction *action, +					gpointer   data) +{ +	CajaFile *file; +	FMDirectoryView *view; + +	view = FM_DIRECTORY_VIEW (data); + +	file = view->details->location_popup_directory_as_file; +	if (file == NULL) { +		return; +	} + +#ifdef TODO_GIO +	if (something) { +		g_spawn_command_line_async ("gfloppy", NULL); +	} +#endif +} + +static void +action_location_start_volume_callback (GtkAction *action, +				       gpointer   data) +{ +	CajaFile *file; +	FMDirectoryView *view; +	GMountOperation *mount_op; + +	view = FM_DIRECTORY_VIEW (data); + +	file = view->details->location_popup_directory_as_file; +	if (file == NULL) { +		return; +	} + +	mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); +	caja_file_start (file, mount_op, NULL, file_start_callback, NULL); +	g_object_unref (mount_op); +} + +static void +action_location_stop_volume_callback (GtkAction *action, +				      gpointer   data) +{ +	CajaFile *file; +	FMDirectoryView *view; +	GMountOperation *mount_op; + +	view = FM_DIRECTORY_VIEW (data); + +	file = view->details->location_popup_directory_as_file; +	if (file == NULL) { +		return; +	} + +	mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view)); +	caja_file_stop (file, mount_op, NULL, +			    file_stop_callback, NULL); +	g_object_unref (mount_op); +} + +static void +action_location_detect_media_callback (GtkAction *action, +				       gpointer   data) +{ +	CajaFile *file; +	FMDirectoryView *view; + +	view = FM_DIRECTORY_VIEW (data); + +	file = view->details->location_popup_directory_as_file; +	if (file == NULL) { +		return; +	} + +	caja_file_poll_for_media (file); +} + +static void +connect_to_server_response_callback (GtkDialog *dialog, +				     int response_id, +				     gpointer data) +{ +	GtkEntry *entry; +	char *uri; +	const char *name; +	char *icon; + +	entry = GTK_ENTRY (data); + +	switch (response_id) { +	case GTK_RESPONSE_OK: +		uri = g_object_get_data (G_OBJECT (dialog), "link-uri"); +		icon = g_object_get_data (G_OBJECT (dialog), "link-icon"); +		name = gtk_entry_get_text (entry); +#ifdef GIO_CONVERSION_DONE +		mate_vfs_connect_to_server (uri, (char *)name, icon); +#endif +		gtk_widget_destroy (GTK_WIDGET (dialog)); +		break; +	case GTK_RESPONSE_NONE: +	case GTK_RESPONSE_DELETE_EVENT: +	case GTK_RESPONSE_CANCEL: +		gtk_widget_destroy (GTK_WIDGET (dialog)); +		break; +	default : +		g_assert_not_reached (); +	} +} + +static void +entry_activate_callback (GtkEntry *entry, +			 gpointer user_data) +{ +	GtkDialog *dialog; + +	dialog = GTK_DIALOG (user_data); +	gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); +} + +static void +action_connect_to_server_link_callback (GtkAction *action, +					gpointer data) +{ +	CajaFile *file; +	GList *selection; +	FMDirectoryView *view; +	char *uri; +	CajaIconInfo *icon; +	const char *icon_name; +	char *name; +	GtkWidget *dialog; +	GtkWidget *label; +	GtkWidget *entry; +	GtkWidget *box; +	char *title; + +        view = FM_DIRECTORY_VIEW (data); + +	selection = fm_directory_view_get_selection (view); + +	if (!eel_g_list_exactly_one_item (selection)) { +		caja_file_list_free (selection); +		return; +	} + +	file = CAJA_FILE (selection->data); + +	uri = caja_file_get_activation_uri (file); +	icon = caja_file_get_icon (file, CAJA_ICON_SIZE_STANDARD, 0); +	icon_name = caja_icon_info_get_used_name (icon); +	name = caja_file_get_display_name (file); + +	if (uri != NULL) { +		title = g_strdup_printf (_("Connect to Server %s"), name); +		dialog = gtk_dialog_new_with_buttons (title, +						      fm_directory_view_get_containing_window (view), +						      GTK_DIALOG_NO_SEPARATOR, +						      GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, +						      _("_Connect"), GTK_RESPONSE_OK, +						      NULL); + +		g_object_set_data_full (G_OBJECT (dialog), "link-uri", g_strdup (uri), g_free); +		g_object_set_data_full (G_OBJECT (dialog), "link-icon", g_strdup (icon_name), g_free); + +		gtk_container_set_border_width (GTK_CONTAINER (dialog), 5); +		gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), 2); + +		box = gtk_hbox_new (FALSE, 12); +		gtk_widget_show (box); +		gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), +				    box, TRUE, TRUE, 0); + +		label = gtk_label_new_with_mnemonic (_("Link _name:")); +		gtk_widget_show (label); + +		gtk_box_pack_start (GTK_BOX (box), label, TRUE, TRUE, 12); + +		entry = gtk_entry_new (); +		if (name) { +			gtk_entry_set_text (GTK_ENTRY (entry), name); +		} +		g_signal_connect (entry, +				  "activate", +				  G_CALLBACK (entry_activate_callback), +				  dialog); + +		gtk_widget_show (entry); +		gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry); + +		gtk_box_pack_start (GTK_BOX (box), entry, TRUE, TRUE, 12); + +		gtk_dialog_set_default_response (GTK_DIALOG (dialog), +						 GTK_RESPONSE_OK); +		g_signal_connect (dialog, "response", +				  G_CALLBACK (connect_to_server_response_callback), +				  entry); +		gtk_widget_show (dialog); +	} + +	g_free (uri); +	g_object_unref (icon); +	g_free (name); +} + +static void +action_location_open_alternate_callback (GtkAction *action, +					 gpointer   callback_data) +{ +	FMDirectoryView *view; +	CajaFile *file; + +	view = FM_DIRECTORY_VIEW (callback_data); + +	file = view->details->location_popup_directory_as_file; +	if (file == NULL) { +		return; +	} + +	fm_directory_view_activate_file (view, +					 file, +					 CAJA_WINDOW_OPEN_IN_NAVIGATION, +					 0); +} + +static void +action_location_open_in_new_tab_callback (GtkAction *action, +					  gpointer   callback_data) +{ +	FMDirectoryView *view; +	CajaFile *file; + +	view = FM_DIRECTORY_VIEW (callback_data); + +	file = view->details->location_popup_directory_as_file; +	if (file == NULL) { +		return; +	} + +	fm_directory_view_activate_file (view, +					 file, +					 CAJA_WINDOW_OPEN_ACCORDING_TO_MODE, +					 CAJA_WINDOW_OPEN_FLAG_NEW_TAB); +} + +static void +action_location_open_folder_window_callback (GtkAction *action, +					     gpointer   callback_data) +{ +	FMDirectoryView *view; +	CajaFile *file; + +	view = FM_DIRECTORY_VIEW (callback_data); + +	file = view->details->location_popup_directory_as_file; +	g_return_if_fail (file != NULL); + +	fm_directory_view_activate_file (view, +					 file, +					 CAJA_WINDOW_OPEN_IN_SPATIAL, +					 0); +} + +static void +action_location_cut_callback (GtkAction *action, +			      gpointer   callback_data) +{ +	FMDirectoryView *view; +	CajaFile *file; +	GList *files; + +	view = FM_DIRECTORY_VIEW (callback_data); + +	file = view->details->location_popup_directory_as_file; +	g_return_if_fail (file != NULL); + +	files = g_list_append (NULL, file); +	copy_or_cut_files (view, files, TRUE); +	g_list_free (files); +} + +static void +action_location_copy_callback (GtkAction *action, +			       gpointer   callback_data) +{ +	FMDirectoryView *view; +	CajaFile *file; +	GList *files; + +	view = FM_DIRECTORY_VIEW (callback_data); + +	file = view->details->location_popup_directory_as_file; +	g_return_if_fail (file != NULL); + +	files = g_list_append (NULL, file); +	copy_or_cut_files (view, files, FALSE); +	g_list_free (files); +} + +static void +action_location_paste_files_into_callback (GtkAction *action, +					   gpointer callback_data) +{ +	FMDirectoryView *view; +	CajaFile *file; + +	view = FM_DIRECTORY_VIEW (callback_data); + +	file = view->details->location_popup_directory_as_file; +	g_return_if_fail (file != NULL); + +	paste_into (view, file); +} + +static void +action_location_trash_callback (GtkAction *action, +				gpointer   callback_data) +{ +	FMDirectoryView *view; +	CajaFile *file; +	GList *files; + +	view = FM_DIRECTORY_VIEW (callback_data); + +	file = view->details->location_popup_directory_as_file; +	g_return_if_fail (file != NULL); + +	files = g_list_append (NULL, file); +	trash_or_delete_files (fm_directory_view_get_containing_window (view), +			       files, TRUE, +			       view); +	g_list_free (files); +} + +static void +action_location_delete_callback (GtkAction *action, +				 gpointer   callback_data) +{ +	FMDirectoryView *view; +	CajaFile *file; +	GFile *location; +	GList *files; + +	view = FM_DIRECTORY_VIEW (callback_data); + +	file = view->details->location_popup_directory_as_file; +	g_return_if_fail (file != NULL); + +	location = caja_file_get_location (file); + +	files = g_list_append (NULL, location); +	caja_file_operations_delete (files, fm_directory_view_get_containing_window (view), +					 NULL, NULL); + +	eel_g_object_list_free (files); +} + +static void +action_location_restore_from_trash_callback (GtkAction *action, +					     gpointer callback_data) +{ +	FMDirectoryView *view; +	CajaFile *file; +	GList l; + +	view = FM_DIRECTORY_VIEW (callback_data); +	file = view->details->location_popup_directory_as_file; + +	l.prev = NULL; +	l.next = NULL; +	l.data = file; +	caja_restore_files_from_trash (&l, +					   fm_directory_view_get_containing_window (view)); +} + +static void +fm_directory_view_init_show_hidden_files (FMDirectoryView *view) +{ +	CajaWindowShowHiddenFilesMode mode; +	gboolean show_hidden_changed; +	gboolean show_hidden_default_setting; + +	if (view->details->ignore_hidden_file_preferences) { +		return; +	} + +	show_hidden_changed = FALSE; +	mode = caja_window_info_get_hidden_files_mode (view->details->window); + +	if (mode == CAJA_WINDOW_SHOW_HIDDEN_FILES_DEFAULT) { +		show_hidden_default_setting = eel_preferences_get_boolean (CAJA_PREFERENCES_SHOW_HIDDEN_FILES); +		if (show_hidden_default_setting != view->details->show_hidden_files) { +			view->details->show_hidden_files = show_hidden_default_setting; +			view->details->show_backup_files = show_hidden_default_setting; +			show_hidden_changed = TRUE; +		} +	} else { +		if (mode == CAJA_WINDOW_SHOW_HIDDEN_FILES_ENABLE) { +			show_hidden_changed = !view->details->show_hidden_files; +			view->details->show_hidden_files = TRUE; +			view->details->show_backup_files = TRUE; +		} else { +			show_hidden_changed = view->details->show_hidden_files; +			view->details->show_hidden_files = FALSE; +			view->details->show_backup_files = FALSE; +		} +	} + +	if (show_hidden_changed && (view->details->model != NULL)) { +		load_directory (view, view->details->model); +	} + +} + +static const GtkActionEntry directory_view_entries[] = { +  /* name, stock id, label */  { "New Documents", "document-new", N_("Create _Document") }, +  /* name, stock id, label */  { "Open With", NULL, N_("Open Wit_h"), +                                 NULL, N_("Choose a program with which to open the selected item") }, +  /* name, stock id */         { "Properties", GTK_STOCK_PROPERTIES, +  /* label, accelerator */       N_("_Properties"), "<alt>Return", +  /* tooltip */                  N_("View or modify the properties of each selected item"), +                                 G_CALLBACK (action_properties_callback) }, +  /* name, stock id */         { "PropertiesAccel", NULL, +  /* label, accelerator */       "PropertiesAccel", "<control>I", +  /* tooltip */                  NULL, +                                 G_CALLBACK (action_properties_callback) }, +  /* name, stock id */         { "New Folder", "folder-new", +  /* label, accelerator */       N_("Create _Folder"), "<control><shift>N", +  /* tooltip */                  N_("Create a new empty folder inside this folder"), +                                 G_CALLBACK (action_new_folder_callback) }, +  /* name, stock id, label */  { "No Templates", NULL, N_("No templates installed") }, +  /* name, stock id */         { "New Empty File", NULL, +    /* translators: this is used to indicate that a file doesn't contain anything */ +  /* label, accelerator */       N_("_Empty File"), NULL, +  /* tooltip */                  N_("Create a new empty file inside this folder"), +                                 G_CALLBACK (action_new_empty_file_callback) }, +  /* name, stock id */         { "New Launcher", NULL, +  /* label, accelerator */       N_("Create L_auncher..."), NULL, +  /* tooltip */                  N_("Create a new launcher"), +                                 G_CALLBACK (action_new_launcher_callback) }, +  /* name, stock id */         { "Open", NULL, +  /* label, accelerator */       N_("_Open"), "<control>o", +  /* tooltip */                  N_("Open the selected item in this window"), +                                 G_CALLBACK (action_open_callback) }, +  /* name, stock id */         { "OpenAccel", NULL, +  /* label, accelerator */       "OpenAccel", "<alt>Down", +  /* tooltip */                  NULL, +                                 G_CALLBACK (action_open_callback) }, +  /* name, stock id */         { "OpenAlternate", NULL, +  /* label, accelerator */       N_("Open in Navigation Window"), "<control><shift>o", +  /* tooltip */                  N_("Open each selected item in a navigation window"), +                                 G_CALLBACK (action_open_alternate_callback) }, +  /* name, stock id */         { "OpenInNewTab", NULL, +  /* label, accelerator */       N_("Open in New _Tab"), "<control><shift>o", +  /* tooltip */                  N_("Open each selected item in a new tab"), +                                 G_CALLBACK (action_open_new_tab_callback) }, +  /* name, stock id */         { "OpenFolderWindow", NULL, +  /* label, accelerator */       N_("Open in _Folder Window"), NULL, +  /* tooltip */                  N_("Open each selected item in a folder window"), +                                 G_CALLBACK (action_open_folder_window_callback) }, +  /* name, stock id */         { "OtherApplication1", NULL, +  /* label, accelerator */       N_("Other _Application..."), NULL, +  /* tooltip */                  N_("Choose another application with which to open the selected item"), +                                 G_CALLBACK (action_other_application_callback) }, +  /* name, stock id */         { "OtherApplication2", NULL, +  /* label, accelerator */       N_("Open With Other _Application..."), NULL, +  /* tooltip */                  N_("Choose another application with which to open the selected item"), +                                 G_CALLBACK (action_other_application_callback) }, +  /* name, stock id */         { "Open Scripts Folder", NULL, +  /* label, accelerator */       N_("_Open Scripts Folder"), NULL, +   /* tooltip */                 N_("Show the folder containing the scripts that appear in this menu"), +                                 G_CALLBACK (action_open_scripts_folder_callback) }, +  /* name, stock id */         { "Empty Trash", NULL, +  /* label, accelerator */       N_("E_mpty Trash"), NULL, +  /* tooltip */                  N_("Delete all items in the Trash"), +                                 G_CALLBACK (action_empty_trash_callback) }, +  /* name, stock id */         { "Cut", GTK_STOCK_CUT, +  /* label, accelerator */       NULL, NULL, +  /* tooltip */                  N_("Prepare the selected files to be moved with a Paste command"), +                                 G_CALLBACK (action_cut_files_callback) }, +  /* name, stock id */         { "Copy", GTK_STOCK_COPY, +  /* label, accelerator */       NULL, NULL, +  /* tooltip */                  N_("Prepare the selected files to be copied with a Paste command"), +                                 G_CALLBACK (action_copy_files_callback) }, +  /* name, stock id */         { "Paste", GTK_STOCK_PASTE, +  /* label, accelerator */       NULL, NULL, +  /* tooltip */                  N_("Move or copy files previously selected by a Cut or Copy command"), +                                 G_CALLBACK (action_paste_files_callback) }, +  /* We make accelerator "" instead of null here to not inherit the stock +     accelerator for paste */ +  /* name, stock id */         { "Paste Files Into", GTK_STOCK_PASTE, +  /* label, accelerator */       N_("_Paste Into Folder"), "", +  /* tooltip */                  N_("Move or copy files previously selected by a Cut or Copy command into the selected folder"), +                                 G_CALLBACK (action_paste_files_into_callback) }, +  /* name, stock id, label */  { "CopyToMenu", NULL, N_("Cop_y to") }, +  /* name, stock id, label */  { "MoveToMenu", NULL, N_("M_ove to") }, +  /* name, stock id */         { "Select All", NULL, +  /* label, accelerator */       N_("Select _All"), "<control>A", +  /* tooltip */                  N_("Select all items in this window"), +                                 G_CALLBACK (action_select_all_callback) }, +  /* name, stock id */         { "Select Pattern", NULL, +  /* label, accelerator */       N_("Select I_tems Matching..."), "<control>S", +  /* tooltip */                  N_("Select items in this window matching a given pattern"), +                                 G_CALLBACK (action_select_pattern_callback) }, +  /* name, stock id */         { "Invert Selection", NULL, +  /* label, accelerator */       N_("_Invert Selection"), "<control><shift>I", +  /* tooltip */                  N_("Select all and only the items that are not currently selected"), +                                 G_CALLBACK (action_invert_selection_callback) }, +  /* name, stock id */         { "Duplicate", NULL, +  /* label, accelerator */       N_("D_uplicate"), NULL, +  /* tooltip */                  N_("Duplicate each selected item"), +                                 G_CALLBACK (action_duplicate_callback) }, +  /* name, stock id */         { "Create Link", NULL, +  /* label, accelerator */       N_("Ma_ke Link"), "<control>M", +  /* tooltip */                  N_("Create a symbolic link for each selected item"), +                                 G_CALLBACK (action_create_link_callback) }, +  /* name, stock id */         { "Rename", NULL, +  /* label, accelerator */       N_("_Rename..."), "F2", +  /* tooltip */                  N_("Rename selected item"), +                                 G_CALLBACK (action_rename_callback) }, +  /* name, stock id */         { "RenameSelectAll", NULL, +  /* label, accelerator */       "RenameSelectAll", "<shift>F2", +  /* tooltip */                  NULL, +                                 G_CALLBACK (action_rename_select_all_callback) }, +  /* name, stock id */         { "Trash", NULL, +  /* label, accelerator */       N_("Mo_ve to Trash"), NULL, +  /* tooltip */                  N_("Move each selected item to the Trash"), +                                 G_CALLBACK (action_trash_callback) }, +  /* name, stock id */         { "Delete", NULL, +  /* label, accelerator */       N_("_Delete"), "<shift>Delete", +  /* tooltip */                  N_("Delete each selected item, without moving to the Trash"), +                                 G_CALLBACK (action_delete_callback) }, +  /* name, stock id */         { "Restore From Trash", NULL, +  /* label, accelerator */       N_("_Restore"), NULL, +				 NULL, +                                 G_CALLBACK (action_restore_from_trash_callback) }, +  /* +   * multiview-TODO: decide whether "Reset to Defaults" should +   * be window-wide, and not just view-wide. +   * Since this also resets the "Show hidden files" mode, +   * it is a mixture of both ATM. +   */ +  /* name, stock id */         { "Reset to Defaults", NULL, +  /* label, accelerator */       N_("Reset View to _Defaults"), NULL, +  /* tooltip */                  N_("Reset sorting order and zoom level to match preferences for this view"), +                                 G_CALLBACK (action_reset_to_defaults_callback) }, +  /* name, stock id */         { "Connect To Server Link", NULL, +  /* label, accelerator */       N_("Connect To This Server"), NULL, +  /* tooltip */                  N_("Make a permanent connection to this server"), +                                 G_CALLBACK (action_connect_to_server_link_callback) }, +  /* name, stock id */         { "Mount Volume", NULL, +  /* label, accelerator */       N_("_Mount"), NULL, +  /* tooltip */                  N_("Mount the selected volume"), +                                 G_CALLBACK (action_mount_volume_callback) }, +  /* name, stock id */         { "Unmount Volume", NULL, +  /* label, accelerator */       N_("_Unmount"), NULL, +  /* tooltip */                  N_("Unmount the selected volume"), +                                 G_CALLBACK (action_unmount_volume_callback) }, +  /* name, stock id */         { "Eject Volume", NULL, +  /* label, accelerator */       N_("_Eject"), NULL, +  /* tooltip */                  N_("Eject the selected volume"), +                                 G_CALLBACK (action_eject_volume_callback) }, +  /* name, stock id */         { "Format Volume", NULL, +  /* label, accelerator */       N_("_Format"), NULL, +  /* tooltip */                  N_("Format the selected volume"), +                                 G_CALLBACK (action_format_volume_callback) }, +  /* name, stock id */         { "Start Volume", NULL, +  /* label, accelerator */       N_("_Start"), NULL, +  /* tooltip */                  N_("Start the selected volume"), +                                 G_CALLBACK (action_start_volume_callback) }, +  /* name, stock id */         { "Stop Volume", NULL, +  /* label, accelerator */       N_("_Stop"), NULL, +  /* tooltip */                  N_("Stop the selected volume"), +                                 G_CALLBACK (action_stop_volume_callback) }, +  /* name, stock id */         { "Poll", NULL, +  /* label, accelerator */       N_("_Detect Media"), NULL, +  /* tooltip */                  N_("Detect media in the selected drive"), +                                 G_CALLBACK (action_detect_media_callback) }, +  /* name, stock id */         { "Self Mount Volume", NULL, +  /* label, accelerator */       N_("_Mount"), NULL, +  /* tooltip */                  N_("Mount the volume associated with the open folder"), +                                 G_CALLBACK (action_self_mount_volume_callback) }, +  /* name, stock id */         { "Self Unmount Volume", NULL, +  /* label, accelerator */       N_("_Unmount"), NULL, +  /* tooltip */                  N_("Unmount the volume associated with the open folder"), +                                 G_CALLBACK (action_self_unmount_volume_callback) }, +  /* name, stock id */         { "Self Eject Volume", NULL, +  /* label, accelerator */       N_("_Eject"), NULL, +  /* tooltip */                  N_("Eject the volume associated with the open folder"), +                                 G_CALLBACK (action_self_eject_volume_callback) }, +  /* name, stock id */         { "Self Format Volume", NULL, +  /* label, accelerator */       N_("_Format"), NULL, +  /* tooltip */                  N_("Format the volume associated with the open folder"), +                                 G_CALLBACK (action_self_format_volume_callback) }, +  /* name, stock id */         { "Self Start Volume", NULL, +  /* label, accelerator */       N_("_Start"), NULL, +  /* tooltip */                  N_("Start the volume associated with the open folder"), +                                 G_CALLBACK (action_self_start_volume_callback) }, +  /* name, stock id */         { "Self Stop Volume", NULL, +  /* label, accelerator */       N_("_Stop"), NULL, +  /* tooltip */                  N_("Stop the volume associated with the open folder"), +                                 G_CALLBACK (action_self_stop_volume_callback) }, +  /* name, stock id */         { "Self Poll", NULL, +  /* label, accelerator */       N_("_Detect Media"), NULL, +  /* tooltip */                  N_("Detect media in the selected drive"), +                                 G_CALLBACK (action_self_detect_media_callback) }, +  /* name, stock id */         { "OpenCloseParent", NULL, +  /* label, accelerator */       N_("Open File and Close window"), "<alt><shift>Down", +  /* tooltip */                  NULL, +                                 G_CALLBACK (action_open_close_parent_callback) }, +  /* name, stock id */         { "Save Search", NULL, +  /* label, accelerator */       N_("Sa_ve Search"), NULL, +  /* tooltip */                  N_("Save the edited search"), +                                 G_CALLBACK (action_save_search_callback) }, +  /* name, stock id */         { "Save Search As", NULL, +  /* label, accelerator */       N_("Sa_ve Search As..."), NULL, +  /* tooltip */                  N_("Save the current search as a file"), +                                 G_CALLBACK (action_save_search_as_callback) }, + +  /* Location-specific actions */ +  /* name, stock id */         { FM_ACTION_LOCATION_OPEN_ALTERNATE, NULL, +  /* label, accelerator */       N_("Open in Navigation Window"), "", +  /* tooltip */                  N_("Open this folder in a navigation window"), +                                 G_CALLBACK (action_location_open_alternate_callback) }, +  /* name, stock id */         { FM_ACTION_LOCATION_OPEN_IN_NEW_TAB, NULL, +  /* label, accelerator */       N_("Open in New _Tab"), "", +  /* tooltip */                  N_("Open this folder in a new tab"), +                                 G_CALLBACK (action_location_open_in_new_tab_callback) }, + +  /* name, stock id */         { FM_ACTION_LOCATION_OPEN_FOLDER_WINDOW, NULL, +  /* label, accelerator */       N_("Open in _Folder Window"), "", +  /* tooltip */                  N_("Open this folder in a folder window"), +                                 G_CALLBACK (action_location_open_folder_window_callback) }, + +  /* name, stock id */         { FM_ACTION_LOCATION_CUT, GTK_STOCK_CUT, +  /* label, accelerator */       NULL, "", +  /* tooltip */                  N_("Prepare this folder to be moved with a Paste command"), +                                 G_CALLBACK (action_location_cut_callback) }, +  /* name, stock id */         { FM_ACTION_LOCATION_COPY, GTK_STOCK_COPY, +  /* label, accelerator */       NULL, "", +  /* tooltip */                  N_("Prepare this folder to be copied with a Paste command"), +                                 G_CALLBACK (action_location_copy_callback) }, +  /* name, stock id */         { FM_ACTION_LOCATION_PASTE_FILES_INTO, GTK_STOCK_PASTE, +  /* label, accelerator */       N_("_Paste Into Folder"), "", +  /* tooltip */                  N_("Move or copy files previously selected by a Cut or Copy command into this folder"), +                                 G_CALLBACK (action_location_paste_files_into_callback) }, + +  /* name, stock id */         { FM_ACTION_LOCATION_TRASH, NULL, +  /* label, accelerator */       N_("Mo_ve to Trash"), "", +  /* tooltip */                  N_("Move this folder to the Trash"), +                                 G_CALLBACK (action_location_trash_callback) }, +  /* name, stock id */         { FM_ACTION_LOCATION_DELETE, CAJA_ICON_DELETE, +  /* label, accelerator */       N_("_Delete"), "", +  /* tooltip */                  N_("Delete this folder, without moving to the Trash"), +                                 G_CALLBACK (action_location_delete_callback) }, +  /* name, stock id */         { FM_ACTION_LOCATION_RESTORE_FROM_TRASH, NULL, +  /* label, accelerator */       N_("_Restore"), NULL, NULL, +                                 G_CALLBACK (action_location_restore_from_trash_callback) }, + +  /* name, stock id */         { "Location Mount Volume", NULL, +  /* label, accelerator */       N_("_Mount"), NULL, +  /* tooltip */                  N_("Mount the volume associated with this folder"), +                                 G_CALLBACK (action_location_mount_volume_callback) }, +  /* name, stock id */         { "Location Unmount Volume", NULL, +  /* label, accelerator */       N_("_Unmount"), NULL, +  /* tooltip */                  N_("Unmount the volume associated with this folder"), +                                 G_CALLBACK (action_location_unmount_volume_callback) }, +  /* name, stock id */         { "Location Eject Volume", NULL, +  /* label, accelerator */       N_("_Eject"), NULL, +  /* tooltip */                  N_("Eject the volume associated with this folder"), +                                 G_CALLBACK (action_location_eject_volume_callback) }, +  /* name, stock id */         { "Location Format Volume", NULL, +  /* label, accelerator */       N_("_Format"), NULL, +  /* tooltip */                  N_("Format the volume associated with this folder"), +                                 G_CALLBACK (action_location_format_volume_callback) }, +  /* name, stock id */         { "Location Start Volume", NULL, +  /* label, accelerator */       N_("_Start"), NULL, +  /* tooltip */                  N_("Start the volume associated with this folder"), +                                 G_CALLBACK (action_location_start_volume_callback) }, +  /* name, stock id */         { "Location Stop Volume", NULL, +  /* label, accelerator */       N_("_Stop"), NULL, +  /* tooltip */                  N_("Stop the volume associated with this folder"), +                                 G_CALLBACK (action_location_stop_volume_callback) }, +  /* name, stock id */         { "Location Poll", NULL, +  /* label, accelerator */       N_("_Detect Media"), NULL, +  /* tooltip */                  N_("Detect media in the selected drive"), +                                 G_CALLBACK (action_location_detect_media_callback) }, + +  /* name, stock id */         { "LocationProperties", GTK_STOCK_PROPERTIES, +  /* label, accelerator */       N_("_Properties"), NULL, +  /* tooltip */                  N_("View or modify the properties of this folder"), +                                 G_CALLBACK (action_location_properties_callback) }, + +  /* name, stock id, label */  {FM_ACTION_COPY_TO_NEXT_PANE, NULL, N_("_Other pane"), +				NULL, N_("Copy the current selection to the other pane in the window"), +				G_CALLBACK (action_copy_to_next_pane_callback) }, +  /* name, stock id, label */  {FM_ACTION_MOVE_TO_NEXT_PANE, NULL, N_("_Other pane"), +				NULL, N_("Move the current selection to the other pane in the window"), +				G_CALLBACK (action_move_to_next_pane_callback) }, +  /* name, stock id, label */  {FM_ACTION_COPY_TO_HOME, CAJA_ICON_HOME, +				N_("_Home Folder"), NULL, +				N_("Copy the current selection to the home folder"), +				G_CALLBACK (action_copy_to_home_callback) }, +  /* name, stock id, label */  {FM_ACTION_MOVE_TO_HOME, CAJA_ICON_HOME, +				N_("_Home Folder"), NULL, +				N_("Move the current selection to the home folder"), +				G_CALLBACK (action_move_to_home_callback) }, +  /* name, stock id, label */  {FM_ACTION_COPY_TO_DESKTOP, CAJA_ICON_DESKTOP, +				N_("_Desktop"), NULL, +				N_("Copy the current selection to the desktop"), +				G_CALLBACK (action_copy_to_desktop_callback) }, +  /* name, stock id, label */  {FM_ACTION_MOVE_TO_DESKTOP, CAJA_ICON_DESKTOP, +				N_("_Desktop"), NULL, +				N_("Move the current selection to the desktop"), +				G_CALLBACK (action_move_to_desktop_callback) }, +}; + +static void +connect_proxy (FMDirectoryView *view, +	       GtkAction *action, +	       GtkWidget *proxy, +	       GtkActionGroup *action_group) +{ +	GdkPixbuf *pixbuf; +	GtkWidget *image; + +	if (strcmp (gtk_action_get_name (action), FM_ACTION_NEW_EMPTY_FILE) == 0 && +	    GTK_IS_IMAGE_MENU_ITEM (proxy)) { +		pixbuf = get_menu_icon ("text-x-generic"); +		if (pixbuf != NULL) { +			image = gtk_image_new_from_pixbuf (pixbuf); +			gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (proxy), image); + +			g_object_unref (pixbuf); +		} +	} +} + +static void +pre_activate (FMDirectoryView *view, +	      GtkAction *action, +	      GtkActionGroup *action_group) +{ +	GdkEvent *event; +	GtkWidget *proxy; +	gboolean activated_from_popup; + +	/* check whether action was activated through a popup menu. +	 * If not, unset the last stored context menu popup position */ +	activated_from_popup = FALSE; + +	event = gtk_get_current_event (); +	proxy = gtk_get_event_widget (event); + +	if (proxy != NULL) { +		GtkWidget *toplevel; +		GdkWindowTypeHint hint; + +		toplevel = gtk_widget_get_toplevel (proxy); + +		if (GTK_IS_WINDOW (toplevel)) { +			hint = gtk_window_get_type_hint (GTK_WINDOW (toplevel)); + +			if (hint == GDK_WINDOW_TYPE_HINT_POPUP_MENU) { +				activated_from_popup = TRUE; +			} +		} +	} + +	if (!activated_from_popup) { +		update_context_menu_position_from_event (view, NULL); +	} +} + +static void +real_merge_menus (FMDirectoryView *view) +{ +	GtkActionGroup *action_group; +	GtkUIManager *ui_manager; +	GtkAction *action; +	const char *ui; +	char *tooltip; + +	ui_manager = caja_window_info_get_ui_manager (view->details->window); + +	action_group = gtk_action_group_new ("DirViewActions"); +	gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE); +	view->details->dir_action_group = action_group; +	gtk_action_group_add_actions (action_group, +				      directory_view_entries, G_N_ELEMENTS (directory_view_entries), +				      view); + +	/* Translators: %s is a directory */ +	tooltip = g_strdup_printf(_("Run or manage scripts from %s"), "~/.config/caja/scripts"); +	/* Create a script action here specially because its tooltip is dynamic */ +	action = gtk_action_new ("Scripts", _("_Scripts"), tooltip, NULL); +	gtk_action_group_add_action (action_group, action); +	g_object_unref (action); +	g_free (tooltip); + +	action = gtk_action_group_get_action (action_group, FM_ACTION_NO_TEMPLATES); +	gtk_action_set_sensitive (action, FALSE); + +	g_signal_connect_object (action_group, "connect-proxy", +				 G_CALLBACK (connect_proxy), G_OBJECT (view), +				 G_CONNECT_SWAPPED); +	g_signal_connect_object (action_group, "pre-activate", +				 G_CALLBACK (pre_activate), G_OBJECT (view), +				 G_CONNECT_SWAPPED); + +	/* Insert action group at end so clipboard action group ends up before it */ +	gtk_ui_manager_insert_action_group (ui_manager, action_group, -1); +	g_object_unref (action_group); /* owned by ui manager */ + +	ui = caja_ui_string_get ("caja-directory-view-ui.xml"); +	view->details->dir_merge_id = gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, NULL); +	g_signal_connect_object (fm_directory_view_get_background (view), "settings_changed", +				 G_CALLBACK (schedule_update_menus), G_OBJECT (view), +				 G_CONNECT_SWAPPED); + +	view->details->scripts_invalid = TRUE; +	view->details->templates_invalid = TRUE; +} + + +static gboolean +can_paste_into_file (CajaFile *file) +{ +	if (caja_file_is_directory (file) && +	    caja_file_can_write (file)) { +		return TRUE; +	} +	if (caja_file_has_activation_uri (file)) { +		GFile *location; +		CajaFile *activation_file; +		gboolean res; + +		location = caja_file_get_activation_location (file); +		activation_file = caja_file_get (location); +		g_object_unref (location); + +		/* The target location might not have data for it read yet, +		   and we can't want to do sync I/O, so treat the unknown +		   case as can-write */ +		res = (caja_file_get_file_type (activation_file) == G_FILE_TYPE_UNKNOWN) || +			(caja_file_get_file_type (activation_file) == G_FILE_TYPE_DIRECTORY && +			 caja_file_can_write (activation_file)); + +		caja_file_unref (activation_file); + +		return res; +	} + +	return FALSE; +} + +static void +clipboard_targets_received (GtkClipboard     *clipboard, +                            GdkAtom          *targets, +                            int               n_targets, +			    gpointer          user_data) +{ +	FMDirectoryView *view; +	gboolean can_paste; +	int i; +	GList *selection; +	int count; +	GtkAction *action; + +	view = FM_DIRECTORY_VIEW (user_data); +	can_paste = FALSE; + +	if (view->details->window == NULL || +	    !view->details->active) { +		/* We've been destroyed or became inactive since call */ +		g_object_unref (view); +		return; +	} + +	if (targets) { +		for (i=0; i < n_targets; i++) { +			if (targets[i] == copied_files_atom) { +				can_paste = TRUE; +			} +		} +	} + + +	selection = fm_directory_view_get_selection (view); +	count = g_list_length (selection); + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_PASTE); +	gtk_action_set_sensitive (action, +				  can_paste && !fm_directory_view_is_read_only (view)); + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_PASTE_FILES_INTO); +	gtk_action_set_sensitive (action, +	                          can_paste && count == 1 && +	                          can_paste_into_file (CAJA_FILE (selection->data))); + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_LOCATION_PASTE_FILES_INTO); +	g_object_set_data (G_OBJECT (action), +			   "can-paste-according-to-clipboard", +			   GINT_TO_POINTER (can_paste)); +	gtk_action_set_sensitive (action, +				  GPOINTER_TO_INT (g_object_get_data (G_OBJECT (action), +						   "can-paste-according-to-clipboard")) && +				  GPOINTER_TO_INT (g_object_get_data (G_OBJECT (action), +						   "can-paste-according-to-destination"))); + +	caja_file_list_free (selection); + +	g_object_unref (view); +} + +static gboolean +showing_trash_directory (FMDirectoryView *view) +{ +	CajaFile *file; + +	file = fm_directory_view_get_directory_as_file (view); +	if (file != NULL) { +		return caja_file_is_in_trash (file); +	} +	return FALSE; +} + +static gboolean +should_show_empty_trash (FMDirectoryView *view) +{ +	return (showing_trash_directory (view) || caja_window_info_get_window_type (view->details->window) == CAJA_WINDOW_NAVIGATION); +} + +static gboolean +file_list_all_are_folders (GList *file_list) +{ +	GList *l; +	CajaFile *file, *linked_file; +	char *activation_uri; +	gboolean is_dir; + +	for (l = file_list; l != NULL; l = l->next) { +		file = CAJA_FILE (l->data); +		if (caja_file_is_caja_link (file) && +		    !CAJA_IS_DESKTOP_ICON_FILE (file)) { +			if (caja_file_is_launcher (file)) { +				return FALSE; +			} + +			activation_uri = caja_file_get_activation_uri (file); + +			if (activation_uri == NULL) { +				g_free (activation_uri); +				return FALSE; +			} + +			linked_file = caja_file_get_existing_by_uri (activation_uri); + +			/* We might not actually know the type of the linked file yet, +			 * however we don't want to schedule a read, since that might do things +			 * like ask for password etc. This is a bit unfortunate, but I don't +			 * know any way around it, so we do various heuristics here +			 * to get things mostly right +			 */ +			is_dir = +				(linked_file != NULL && +				 caja_file_is_directory (linked_file)) || +				(activation_uri != NULL && +				 activation_uri[strlen (activation_uri) - 1] == '/'); + +			caja_file_unref (linked_file); +			g_free (activation_uri); + +			if (!is_dir) { +				return FALSE; +			} +		} else if (!(caja_file_is_directory (file) || +			     CAJA_IS_DESKTOP_ICON_FILE (file))) { +			return FALSE; +		} +	} +	return TRUE; +} + +static void +file_should_show_foreach (CajaFile        *file, +			  gboolean            *show_mount, +			  gboolean            *show_unmount, +			  gboolean            *show_eject, +			  gboolean            *show_connect, +			  gboolean            *show_format, +			  gboolean            *show_start, +			  gboolean            *show_stop, +			  gboolean            *show_poll, +			  GDriveStartStopType *start_stop_type) +{ +	char *uri; + +	*show_mount = FALSE; +	*show_unmount = FALSE; +	*show_eject = FALSE; +	*show_connect = FALSE; +	*show_format = FALSE; +	*show_start = FALSE; +	*show_stop = FALSE; +	*show_poll = FALSE; + +	if (caja_file_can_eject (file)) { +		*show_eject = TRUE; +	} + +	if (caja_file_can_mount (file)) { +		*show_mount = TRUE; + +#ifdef TODO_GIO +		if (something && +		    g_find_program_in_path ("gfloppy")) { +			*show_format = TRUE; +		} +#endif +	} + +	if (caja_file_can_start (file) || caja_file_can_start_degraded (file)) { +		*show_start = TRUE; +	} + +	if (caja_file_can_stop (file)) { +		*show_stop = TRUE; +	} + +	/* Dot not show both Unmount and Eject/Safe Removal; too confusing to +	 * have too many menu entries */ +	if (caja_file_can_unmount (file) && !*show_eject && !*show_stop) { +		*show_unmount = TRUE; +	} + +	if (caja_file_can_poll_for_media (file) && !caja_file_is_media_check_automatic (file)) { +		*show_poll = TRUE; +	} + +	*start_stop_type = caja_file_get_start_stop_type (file); + +	if (caja_file_is_caja_link (file)) { +		uri = caja_file_get_activation_uri (file); +		if (uri != NULL && +		    (eel_istr_has_prefix (uri, "ftp:") || +		     eel_istr_has_prefix (uri, "ssh:") || +		     eel_istr_has_prefix (uri, "sftp:") || +		     eel_istr_has_prefix (uri, "dav:") || +		     eel_istr_has_prefix (uri, "davs:"))) { +			*show_connect = TRUE; +		} +		g_free (uri); +	} +} + +static void +file_should_show_self (CajaFile        *file, +		       gboolean            *show_mount, +		       gboolean            *show_unmount, +		       gboolean            *show_eject, +		       gboolean            *show_format, +		       gboolean            *show_start, +		       gboolean            *show_stop, +		       gboolean            *show_poll, +		       GDriveStartStopType *start_stop_type) +{ +	*show_mount = FALSE; +	*show_unmount = FALSE; +	*show_eject = FALSE; +	*show_format = FALSE; +	*show_start = FALSE; +	*show_stop = FALSE; +	*show_poll = FALSE; + +	if (file == NULL) { +		return; +	} + +	if (caja_file_can_eject (file)) { +		*show_eject = TRUE; +	} + +	if (caja_file_can_mount (file)) { +		*show_mount = TRUE; +	} + +#ifdef TODO_GIO +	if (something && g_find_program_in_path ("gfloppy")) { +		*show_format = TRUE; +	} +#endif + +	if (caja_file_can_start (file) || caja_file_can_start_degraded (file)) { +		*show_start = TRUE; +	} + +	if (caja_file_can_stop (file)) { +		*show_stop = TRUE; +	} + +	/* Dot not show both Unmount and Eject/Safe Removal; too confusing to +	 * have too many menu entries */ +	if (caja_file_can_unmount (file) && !*show_eject && !*show_stop) { +		*show_unmount = TRUE; +	} + +	if (caja_file_can_poll_for_media (file) && !caja_file_is_media_check_automatic (file)) { +		*show_poll = TRUE; +	} + +	*start_stop_type = caja_file_get_start_stop_type (file); + +} + +static gboolean +files_are_all_directories (GList *files) +{ +	CajaFile *file; +	GList *l; +	gboolean all_directories; + +	all_directories = TRUE; + +	for (l = files; l != NULL; l = l->next) { +		file = CAJA_FILE (l->data); +		all_directories &= caja_file_is_directory (file); +	} + +	return all_directories; +} + +static gboolean +files_is_none_directory (GList *files) +{ +	CajaFile *file; +	GList *l; +	gboolean no_directory; + +	no_directory = TRUE; + +	for (l = files; l != NULL; l = l->next) { +		file = CAJA_FILE (l->data); +		no_directory &= !caja_file_is_directory (file); +	} + +	return no_directory; +} + +static void +update_restore_from_trash_action (GtkAction *action, +				  GList *files, +				  gboolean is_self) +{ +	CajaFile *original_file; +	CajaFile *original_dir; +	GHashTable *original_dirs_hash; +	GList *original_dirs; +	GFile *original_location; +	char *tooltip, *original_name; + +	original_file = NULL; +	original_dir = NULL; +	original_dirs = NULL; +	original_dirs_hash = NULL; +	original_location = NULL; +	original_name = NULL; + +	if (files != NULL) { +		if (g_list_length (files) == 1) { +			original_file = caja_file_get_trash_original_file (files->data); +		} else { +			original_dirs_hash = caja_trashed_files_get_original_directories (files, NULL); +			if (original_dirs_hash != NULL) { +				original_dirs = g_hash_table_get_keys (original_dirs_hash); +				if (g_list_length (original_dirs) == 1) { +					original_dir = caja_file_ref (CAJA_FILE (original_dirs->data)); +				} +			} +		} +	} + +	if (original_file != NULL || original_dirs != NULL) { +		gtk_action_set_visible (action, TRUE); + +		if (original_file != NULL) { +			original_location = caja_file_get_location (original_file); +		} else if (original_dir != NULL) { +			original_location = caja_file_get_location (original_dir); +		} + +		if (original_location != NULL) { +			original_name = g_file_get_parse_name (original_location); +		} + +		if (is_self) { +			g_assert (g_list_length (files) == 1); +			g_assert (original_location != NULL); +			tooltip = g_strdup_printf (_("Move the open folder out of the trash to \"%s\""), original_name); +		} else if (files_are_all_directories (files)) { +			if (original_name != NULL) { +				tooltip = g_strdup_printf (ngettext ("Move the selected folder out of the trash to \"%s\"", +								     "Move the selected folders out of the trash to \"%s\"", +								     g_list_length (files)), original_name); +			} else { +				tooltip = g_strdup_printf (ngettext ("Move the selected folder out of the trash", +								     "Move the selected folders out of the trash", +								     g_list_length (files))); +			} +		} else if (files_is_none_directory (files)) { +			if (original_name != NULL) { +				tooltip = g_strdup_printf (ngettext ("Move the selected file out of the trash to \"%s\"", +								     "Move the selected files out of the trash to \"%s\"", +								     g_list_length (files)), original_name); +			} else { +				tooltip = g_strdup_printf (ngettext ("Move the selected file out of the trash", +								     "Move the selected files out of the trash", +								     g_list_length (files))); +			} +		} else { +			if (original_name != NULL) { +				tooltip = g_strdup_printf (ngettext ("Move the selected item out of the trash to \"%s\"", +								     "Move the selected items out of the trash to \"%s\"", +								     g_list_length (files)), original_name); +			} else { +				tooltip = g_strdup_printf (ngettext ("Move the selected item out of the trash", +								     "Move the selected items out of the trash", +								     g_list_length (files))); +			} +		} +		g_free (original_name); + +		g_object_set (action, "tooltip", tooltip, NULL); + +		if (original_location != NULL) { +			g_object_unref (original_location); +		} +	} else { +		gtk_action_set_visible (action, FALSE); +	} + +	caja_file_unref (original_file); +	caja_file_unref (original_dir); +	g_list_free (original_dirs); + +	if (original_dirs_hash != NULL) { +		g_hash_table_destroy (original_dirs_hash); +	} +} + +static void +real_update_menus_volumes (FMDirectoryView *view, +			   GList *selection, +			   gint selection_count) +{ +	GList *l; +	CajaFile *file; +	gboolean show_mount; +	gboolean show_unmount; +	gboolean show_eject; +	gboolean show_connect; +	gboolean show_format; +	gboolean show_start; +	gboolean show_stop; +	gboolean show_poll; +	GDriveStartStopType start_stop_type; +	gboolean show_self_mount; +	gboolean show_self_unmount; +	gboolean show_self_eject; +	gboolean show_self_format; +	gboolean show_self_start; +	gboolean show_self_stop; +	gboolean show_self_poll; +	GDriveStartStopType self_start_stop_type; +	GtkAction *action; + +	show_mount = (selection != NULL); +	show_unmount = (selection != NULL); +	show_eject = (selection != NULL); +	show_connect = (selection != NULL && selection_count == 1); +	show_format = (selection != NULL && selection_count == 1); +	show_start = (selection != NULL && selection_count == 1); +	show_stop = (selection != NULL && selection_count == 1); +	show_poll = (selection != NULL && selection_count == 1); +	start_stop_type = G_DRIVE_START_STOP_TYPE_UNKNOWN; +	self_start_stop_type = G_DRIVE_START_STOP_TYPE_UNKNOWN; + +	for (l = selection; l != NULL && (show_mount || show_unmount +					  || show_eject || show_connect +                                          || show_format || show_start +					  || show_stop || show_poll); +	     l = l->next) { +		gboolean show_mount_one; +		gboolean show_unmount_one; +		gboolean show_eject_one; +		gboolean show_connect_one; +		gboolean show_format_one; +		gboolean show_start_one; +		gboolean show_stop_one; +		gboolean show_poll_one; + +		file = CAJA_FILE (l->data); +		file_should_show_foreach (file, +					  &show_mount_one, +					  &show_unmount_one, +					  &show_eject_one, +					  &show_connect_one, +                                          &show_format_one, +                                          &show_start_one, +                                          &show_stop_one, +					  &show_poll_one, +					  &start_stop_type); + +		show_mount &= show_mount_one; +		show_unmount &= show_unmount_one; +		show_eject &= show_eject_one; +		show_connect &= show_connect_one; +		show_format &= show_format_one; +		show_start &= show_start_one; +		show_stop &= show_stop_one; +		show_poll &= show_poll_one; +	} + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_CONNECT_TO_SERVER_LINK); +	gtk_action_set_visible (action, show_connect); + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_MOUNT_VOLUME); +	gtk_action_set_visible (action, show_mount); + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_UNMOUNT_VOLUME); +	gtk_action_set_visible (action, show_unmount); + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_EJECT_VOLUME); +	gtk_action_set_visible (action, show_eject); + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_FORMAT_VOLUME); +	gtk_action_set_visible (action, show_format); + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_START_VOLUME); +	gtk_action_set_visible (action, show_start); +	if (show_start) { +		switch (start_stop_type) { +		default: +		case G_DRIVE_START_STOP_TYPE_UNKNOWN: +			gtk_action_set_label (action, _("_Start")); +			gtk_action_set_tooltip (action, _("Start the selected drive")); +			break; +		case G_DRIVE_START_STOP_TYPE_SHUTDOWN: +			gtk_action_set_label (action, _("_Start")); +			gtk_action_set_tooltip (action, _("Start the selected drive")); +			break; +		case G_DRIVE_START_STOP_TYPE_NETWORK: +			gtk_action_set_label (action, _("_Connect")); +			gtk_action_set_tooltip (action, _("Connect to the selected drive")); +			break; +		case G_DRIVE_START_STOP_TYPE_MULTIDISK: +			gtk_action_set_label (action, _("_Start Multi-disk Drive")); +			gtk_action_set_tooltip (action, _("Start the selected multi-disk drive")); +			break; +		case G_DRIVE_START_STOP_TYPE_PASSWORD: +			gtk_action_set_label (action, _("U_nlock Drive")); +			gtk_action_set_tooltip (action, _("Unlock the selected drive")); +			break; +		} +	} + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_STOP_VOLUME); +	gtk_action_set_visible (action, show_stop); +	if (show_stop) { +		switch (start_stop_type) { +		default: +		case G_DRIVE_START_STOP_TYPE_UNKNOWN: +			gtk_action_set_label (action, _("_Stop")); +			gtk_action_set_tooltip (action, _("Stop the selected drive")); +			break; +		case G_DRIVE_START_STOP_TYPE_SHUTDOWN: +			gtk_action_set_label (action, _("_Safely Remove Drive")); +			gtk_action_set_tooltip (action, _("Safely remove the selected drive")); +			break; +		case G_DRIVE_START_STOP_TYPE_NETWORK: +			gtk_action_set_label (action, _("_Disconnect")); +			gtk_action_set_tooltip (action, _("Disconnect the selected drive")); +			break; +		case G_DRIVE_START_STOP_TYPE_MULTIDISK: +			gtk_action_set_label (action, _("_Stop Multi-disk Drive")); +			gtk_action_set_tooltip (action, _("Stop the selected multi-disk drive")); +			break; +		case G_DRIVE_START_STOP_TYPE_PASSWORD: +			gtk_action_set_label (action, _("_Lock Drive")); +			gtk_action_set_tooltip (action, _("Lock the selected drive")); +			break; +		} +	} + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_POLL); +	gtk_action_set_visible (action, show_poll); + +	show_self_mount = show_self_unmount = show_self_eject = +		show_self_format = show_self_start = show_self_stop = show_self_poll = FALSE; + +	file = fm_directory_view_get_directory_as_file (view); +	file_should_show_self (file, +			       &show_self_mount, +			       &show_self_unmount, +			       &show_self_eject, +			       &show_self_format, +			       &show_self_start, +			       &show_self_stop, +			       &show_self_poll, +			       &self_start_stop_type); + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_SELF_MOUNT_VOLUME); +	gtk_action_set_visible (action, show_self_mount); + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_SELF_UNMOUNT_VOLUME); +	gtk_action_set_visible (action, show_self_unmount); + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_SELF_EJECT_VOLUME); +	gtk_action_set_visible (action, show_self_eject); + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_SELF_FORMAT_VOLUME); +	gtk_action_set_visible (action, show_self_format); + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_SELF_START_VOLUME); +	gtk_action_set_visible (action, show_self_start); +	if (show_self_start) { +		switch (self_start_stop_type) { +		default: +		case G_DRIVE_START_STOP_TYPE_UNKNOWN: +			gtk_action_set_label (action, _("_Start")); +			gtk_action_set_tooltip (action, _("Start the drive associated with the open folder")); +			break; +		case G_DRIVE_START_STOP_TYPE_SHUTDOWN: +			gtk_action_set_label (action, _("_Start")); +			gtk_action_set_tooltip (action, _("Start the drive associated with the open folder")); +			break; +		case G_DRIVE_START_STOP_TYPE_NETWORK: +			gtk_action_set_label (action, _("_Connect")); +			gtk_action_set_tooltip (action, _("Connect to the drive associated with the open folder")); +			break; +		case G_DRIVE_START_STOP_TYPE_MULTIDISK: +			gtk_action_set_label (action, _("_Start Multi-disk Drive")); +			gtk_action_set_tooltip (action, _("Start the multi-disk drive associated with the open folder")); +			break; +		case G_DRIVE_START_STOP_TYPE_PASSWORD: +			gtk_action_set_label (action, _("_Unlock Drive")); +			gtk_action_set_tooltip (action, _("Unlock the drive associated with the open folder")); +			break; +		} +	} + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_SELF_STOP_VOLUME); +	gtk_action_set_visible (action, show_self_stop); +	if (show_self_stop) { +		switch (self_start_stop_type) { +		default: +		case G_DRIVE_START_STOP_TYPE_UNKNOWN: +			gtk_action_set_label (action, _("_Stop")); +			gtk_action_set_tooltip (action, _("_Stop the drive associated with the open folder")); +			break; +		case G_DRIVE_START_STOP_TYPE_SHUTDOWN: +			gtk_action_set_label (action, _("_Safely Remove Drive")); +			gtk_action_set_tooltip (action, _("Safely remove the drive associated with the open folder")); +			break; +		case G_DRIVE_START_STOP_TYPE_NETWORK: +			gtk_action_set_label (action, _("_Disconnect")); +			gtk_action_set_tooltip (action, _("Disconnect the drive associated with the open folder")); +			break; +		case G_DRIVE_START_STOP_TYPE_MULTIDISK: +			gtk_action_set_label (action, _("_Stop Multi-disk Drive")); +			gtk_action_set_tooltip (action, _("Stop the multi-disk drive associated with the open folder")); +			break; +		case G_DRIVE_START_STOP_TYPE_PASSWORD: +			gtk_action_set_label (action, _("_Lock Drive")); +			gtk_action_set_tooltip (action, _("Lock the drive associated with the open folder")); +			break; +		} +	} + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_SELF_POLL); +	gtk_action_set_visible (action, show_self_poll); + +} + +static void +real_update_location_menu_volumes (FMDirectoryView *view) +{ +	GtkAction *action; +	CajaFile *file; +	gboolean show_mount; +	gboolean show_unmount; +	gboolean show_eject; +	gboolean show_connect; +	gboolean show_format; +	gboolean show_start; +	gboolean show_stop; +	gboolean show_poll; +	GDriveStartStopType start_stop_type; + +	g_assert (FM_IS_DIRECTORY_VIEW (view)); +	g_assert (CAJA_IS_FILE (view->details->location_popup_directory_as_file)); + +	file = CAJA_FILE (view->details->location_popup_directory_as_file); +	file_should_show_foreach (file, +				  &show_mount, +				  &show_unmount, +				  &show_eject, +				  &show_connect, +				  &show_format, +				  &show_start, +				  &show_stop, +				  &show_poll, +				  &start_stop_type); + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_LOCATION_MOUNT_VOLUME); +	gtk_action_set_visible (action, show_mount); + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_LOCATION_UNMOUNT_VOLUME); +	gtk_action_set_visible (action, show_unmount); + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_LOCATION_EJECT_VOLUME); +	gtk_action_set_visible (action, show_eject); + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_LOCATION_FORMAT_VOLUME); +	gtk_action_set_visible (action, show_format); + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_LOCATION_START_VOLUME); +	gtk_action_set_visible (action, show_start); +	if (show_start) { +		switch (start_stop_type) { +		default: +		case G_DRIVE_START_STOP_TYPE_UNKNOWN: +			gtk_action_set_label (action, _("_Start")); +			gtk_action_set_tooltip (action, _("Start the selected drive")); +			break; +		case G_DRIVE_START_STOP_TYPE_SHUTDOWN: +			gtk_action_set_label (action, _("_Start")); +			gtk_action_set_tooltip (action, _("Start the selected drive")); +			break; +		case G_DRIVE_START_STOP_TYPE_NETWORK: +			gtk_action_set_label (action, _("_Connect")); +			gtk_action_set_tooltip (action, _("Connect to the selected drive")); +			break; +		case G_DRIVE_START_STOP_TYPE_MULTIDISK: +			gtk_action_set_label (action, _("_Start Multi-disk Drive")); +			gtk_action_set_tooltip (action, _("Start the selected multi-disk drive")); +			break; +		case G_DRIVE_START_STOP_TYPE_PASSWORD: +			gtk_action_set_label (action, _("_Unlock Drive")); +			gtk_action_set_tooltip (action, _("Unlock the selected drive")); +			break; +		} +	} + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_LOCATION_STOP_VOLUME); +	gtk_action_set_visible (action, show_stop); +	if (show_stop) { +		switch (start_stop_type) { +		default: +		case G_DRIVE_START_STOP_TYPE_UNKNOWN: +			gtk_action_set_label (action, _("_Stop")); +			gtk_action_set_tooltip (action, _("Stop the selected volume")); +			break; +		case G_DRIVE_START_STOP_TYPE_SHUTDOWN: +			gtk_action_set_label (action, _("_Safely Remove Drive")); +			gtk_action_set_tooltip (action, _("Safely remove the selected drive")); +			break; +		case G_DRIVE_START_STOP_TYPE_NETWORK: +			gtk_action_set_label (action, _("_Disconnect")); +			gtk_action_set_tooltip (action, _("Disconnect the selected drive")); +			break; +		case G_DRIVE_START_STOP_TYPE_MULTIDISK: +			gtk_action_set_label (action, _("_Stop Multi-disk Drive")); +			gtk_action_set_tooltip (action, _("Stop the selected multi-disk drive")); +			break; +		case G_DRIVE_START_STOP_TYPE_PASSWORD: +			gtk_action_set_label (action, _("_Lock Drive")); +			gtk_action_set_tooltip (action, _("Lock the selected drive")); +			break; +		} +	} + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_LOCATION_POLL); +	gtk_action_set_visible (action, show_poll); +} + +/* TODO: we should split out this routine into two functions: + * Update on clipboard changes + * Update on selection changes + */ +static void +real_update_paste_menu (FMDirectoryView *view, +			GList *selection, +			gint selection_count) +{ +	gboolean can_paste_files_into; +	gboolean selection_is_read_only; +	gboolean is_read_only; +	GtkAction *action; + +	selection_is_read_only = selection_count == 1 && +		(!caja_file_can_write (CAJA_FILE (selection->data)) && +		 !caja_file_has_activation_uri (CAJA_FILE (selection->data))); + +	is_read_only = fm_directory_view_is_read_only (view); + +	can_paste_files_into = (selection_count == 1 && +	                        can_paste_into_file (CAJA_FILE (selection->data))); + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_PASTE); +	gtk_action_set_sensitive (action, !is_read_only); + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_PASTE_FILES_INTO); +	gtk_action_set_visible (action, can_paste_files_into); +	gtk_action_set_sensitive (action, !selection_is_read_only); + +	/* Ask the clipboard */ +	g_object_ref (view); /* Need to keep the object alive until we get the reply */ +	gtk_clipboard_request_targets (caja_clipboard_get (GTK_WIDGET (view)), +				       clipboard_targets_received, +				       view); +} + +static void +real_update_location_menu (FMDirectoryView *view) +{ +	GtkAction *action; +	CajaFile *file; +	gboolean is_special_link; +	gboolean is_desktop_or_home_dir; +	gboolean can_delete_file, show_delete; +	gboolean show_separate_delete_command; +	gboolean show_open_folder_window; +	gboolean show_open_in_new_tab; +	GList l; +	char *label; +	char *tip; + +	show_open_folder_window = FALSE; +	show_open_in_new_tab = FALSE; + +	if (caja_window_info_get_window_type (view->details->window) == CAJA_WINDOW_NAVIGATION) { +		if (eel_preferences_get_boolean (CAJA_PREFERENCES_ALWAYS_USE_BROWSER)) { +			label = _("Open in New _Window"); +		} else { +			label = _("Browse in New _Window"); +			show_open_folder_window = TRUE; +		} + +		show_open_in_new_tab = TRUE; +	} else { +		label = g_strdup (ngettext ("_Browse Folder", +					    "_Browse Folders", 1)); +	} +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_LOCATION_OPEN_ALTERNATE); +	g_object_set (action, +		      "label", label, +		      NULL); + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_LOCATION_OPEN_IN_NEW_TAB); +	gtk_action_set_visible (action, show_open_in_new_tab); + +	if (show_open_in_new_tab) { +		if (eel_preferences_get_boolean (CAJA_PREFERENCES_ALWAYS_USE_BROWSER)) { +			label = _("Open in New _Tab"); +		} else { +			label = _("Browse in New _Tab"); +		} +		g_object_set (action, +			      "label", label, +			      NULL); +	} + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_LOCATION_OPEN_FOLDER_WINDOW); +	gtk_action_set_visible (action, show_open_folder_window); + +	file = view->details->location_popup_directory_as_file; +	g_assert (CAJA_IS_FILE (file)); +	g_assert (caja_file_check_if_ready (file, CAJA_FILE_ATTRIBUTE_INFO | +						      CAJA_FILE_ATTRIBUTE_MOUNT | +						      CAJA_FILE_ATTRIBUTE_FILESYSTEM_INFO)); + +	is_special_link = CAJA_IS_DESKTOP_ICON_FILE (file); +	is_desktop_or_home_dir = caja_file_is_home (file) +		|| caja_file_is_desktop_directory (file); + +	can_delete_file = +		caja_file_can_delete (file) && +		!is_special_link && +		!is_desktop_or_home_dir; + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_LOCATION_CUT); +	gtk_action_set_sensitive (action, can_delete_file); + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_LOCATION_PASTE_FILES_INTO); +	g_object_set_data (G_OBJECT (action), +			   "can-paste-according-to-destination", +			   GINT_TO_POINTER (can_paste_into_file (file))); +	gtk_action_set_sensitive (action, +				  GPOINTER_TO_INT (g_object_get_data (G_OBJECT (action), +						   "can-paste-according-to-clipboard")) && +				  GPOINTER_TO_INT (g_object_get_data (G_OBJECT (action), +						   "can-paste-according-to-destination"))); + +	show_delete = TRUE; + +	if (file != NULL && +	    caja_file_is_in_trash (file)) { +		if (caja_file_is_self_owned (file)) { +			show_delete = FALSE; +		} + +		label = _("_Delete Permanently"); +		tip = _("Delete the open folder permanently"); +		show_separate_delete_command = FALSE; +	} else { +		label = _("Mo_ve to Trash"); +		tip = _("Move the open folder to the Trash"); +		show_separate_delete_command = show_delete_command_auto_value; +	} + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_LOCATION_TRASH); +	g_object_set (action, +		      "label", label, +		      "tooltip", tip, +		      "icon-name", (file != NULL && +				   caja_file_is_in_trash (file)) ? +					CAJA_ICON_DELETE : CAJA_ICON_TRASH_FULL, +		      NULL); +	gtk_action_set_sensitive (action, can_delete_file); +	gtk_action_set_visible (action, show_delete); + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_LOCATION_DELETE); +	gtk_action_set_visible (action, show_separate_delete_command); +	if (show_separate_delete_command) { +		gtk_action_set_sensitive (action, can_delete_file); +		g_object_set (action, +			      "icon-name", CAJA_ICON_DELETE, +			      "sensitive", can_delete_file, +			      NULL); +	} + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_LOCATION_RESTORE_FROM_TRASH); +	l.prev = NULL; +	l.next = NULL; +	l.data = file; +	update_restore_from_trash_action (action, &l, TRUE); + +	real_update_location_menu_volumes (view); + +	/* we silently assume that fm_directory_view_supports_properties always returns the same value. +	 * Therefore, we don't update the sensitivity of FM_ACTION_LOCATION_PROPERTIES */ +} + +static void +clipboard_changed_callback (CajaClipboardMonitor *monitor, FMDirectoryView *view) +{ +	GList *selection; +	gint selection_count; + +	if (!view->details->active) { +		return; +	} + +	selection = fm_directory_view_get_selection (view); +	selection_count = g_list_length (selection); + +	real_update_paste_menu (view, selection, selection_count); + +	caja_file_list_free (selection); + +} + +static gboolean +can_delete_all (GList *files) +{ +	CajaFile *file; +	GList *l; + +	for (l = files; l != NULL; l = l->next) { +		file = l->data; +		if (!caja_file_can_delete (file)) { +			return FALSE; +		} +	} +	return TRUE; +} + +static gboolean +has_writable_extra_pane (FMDirectoryView *view) +{ +	FMDirectoryView *other_view; + +	other_view = get_directory_view_of_extra_pane (view); +	if (other_view != NULL) { +		return !fm_directory_view_is_read_only (other_view); +	} +	return FALSE; +} + +static void +real_update_menus (FMDirectoryView *view) +{ +	GList *selection, *l; +	gint selection_count; +	const char *tip, *label; +	char *label_with_underscore; +	gboolean selection_contains_special_link; +	gboolean selection_contains_desktop_or_home_dir; +	gboolean can_create_files; +	gboolean can_delete_files; +	gboolean can_copy_files; +	gboolean can_link_files; +	gboolean can_duplicate_files; +	gboolean show_separate_delete_command; +	gboolean vfolder_directory; +	gboolean disable_command_line; +	gboolean show_open_alternate; +	gboolean can_open; +	gboolean show_app; +	gboolean show_save_search; +	gboolean save_search_sensitive; +	gboolean show_save_search_as; +	gboolean show_open_folder_window; +	GtkAction *action; +	GAppInfo *app; +	GIcon *app_icon; +	GtkWidget *menuitem; +	gboolean next_pane_is_writable; +	gboolean show_properties; + +	selection = fm_directory_view_get_selection (view); +	selection_count = g_list_length (selection); + +	selection_contains_special_link = special_link_in_selection (view); +	selection_contains_desktop_or_home_dir = desktop_or_home_dir_in_selection (view); + +	can_create_files = fm_directory_view_supports_creating_files (view); +	can_delete_files = +		can_delete_all (selection) && +		selection_count != 0 && +		!selection_contains_special_link && +		!selection_contains_desktop_or_home_dir; +	can_copy_files = selection_count != 0 +		&& !selection_contains_special_link; + +	can_duplicate_files = can_create_files && can_copy_files; +	can_link_files = can_create_files && can_copy_files; + +	vfolder_directory = we_are_in_vfolder_desktop_dir (view); + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_RENAME); +	gtk_action_set_sensitive (action, +				  selection_count == 1 && +				  fm_directory_view_can_rename_file (view, selection->data)); + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_NEW_FOLDER); +	gtk_action_set_sensitive (action, can_create_files); + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_OPEN); +	gtk_action_set_sensitive (action, selection_count != 0); + +	can_open = show_app = selection_count != 0; + +	for (l = selection; l != NULL; l = l->next) { +		CajaFile *file; + +		file = CAJA_FILE (selection->data); + +		if (!caja_mime_file_opens_in_external_app (file)) { +			show_app = FALSE; +		} + +		if (!show_app) { +			break; +		} +	} + +	label_with_underscore = NULL; + +	app = NULL; +	app_icon = NULL; + +	if (can_open && show_app) { +		app = caja_mime_get_default_application_for_files (selection); +	} + +	if (app != NULL) { +		char *escaped_app; + +		escaped_app = eel_str_double_underscores (g_app_info_get_display_name (app)); +		label_with_underscore = g_strdup_printf (_("_Open With %s"), +							 escaped_app); + +		app_icon = g_app_info_get_icon (app); +		if (app_icon != NULL) { +			g_object_ref (app_icon); +		} + +		g_free (escaped_app); +		g_object_unref (app); +	} + +	g_object_set (action, "label", +		      label_with_underscore ? label_with_underscore : _("_Open"), +		      NULL); + +	menuitem = gtk_ui_manager_get_widget ( +			caja_window_info_get_ui_manager (view->details->window), +			FM_DIRECTORY_VIEW_MENU_PATH_OPEN); + +	/* Only force displaying the icon if it is an application icon */ +	gtk_image_menu_item_set_always_show_image ( +		GTK_IMAGE_MENU_ITEM (menuitem), app_icon != NULL); + +	menuitem = gtk_ui_manager_get_widget ( +			caja_window_info_get_ui_manager (view->details->window), +			FM_DIRECTORY_VIEW_POPUP_PATH_OPEN); + +	/* Only force displaying the icon if it is an application icon */ +	gtk_image_menu_item_set_always_show_image ( +		GTK_IMAGE_MENU_ITEM (menuitem), app_icon != NULL); + +	if (app_icon == NULL) { +		app_icon = g_themed_icon_new (GTK_STOCK_OPEN); +	} + +	gtk_action_set_gicon (action, app_icon); +	g_object_unref (app_icon); + +	gtk_action_set_visible (action, can_open); + +	g_free (label_with_underscore); + +	show_open_alternate = file_list_all_are_folders (selection) && +				selection_count > 0 && +				!(caja_window_info_get_window_type (view->details->window) == CAJA_WINDOW_DESKTOP && +					eel_preferences_get_boolean (CAJA_PREFERENCES_ALWAYS_USE_BROWSER)); +	show_open_folder_window = FALSE; +	if (caja_window_info_get_window_type (view->details->window) == CAJA_WINDOW_NAVIGATION) { +		if (eel_preferences_get_boolean (CAJA_PREFERENCES_ALWAYS_USE_BROWSER)) { +			if (selection_count == 0 || selection_count == 1) { +				label_with_underscore = g_strdup (_("Open in New _Window")); +			} else { +				label_with_underscore = g_strdup_printf (ngettext("Open in %'d New _Window", +										  "Open in %'d New _Windows", +										  selection_count), +									 selection_count); +			} +		} else { +			if (selection_count == 0 || selection_count == 1) { +				label_with_underscore = g_strdup (_("Browse in New _Window")); +			} else { +				label_with_underscore = g_strdup_printf (ngettext("Browse in %'d New _Window", +										  "Browse in %'d New _Windows", +										  selection_count), +									 selection_count); +			} +			show_open_folder_window = show_open_alternate; +		} +	} else { +		label_with_underscore = g_strdup (ngettext ("_Browse Folder", +							    "_Browse Folders", +							    selection_count)); +	} + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_OPEN_ALTERNATE); +	g_object_set (action, "label", +		      label_with_underscore, +		      NULL); +	g_free (label_with_underscore); + +	gtk_action_set_sensitive (action,  selection_count != 0); +	gtk_action_set_visible (action, show_open_alternate); + +	/* Open in New Tab action */ +	if (caja_window_info_get_window_type (view->details->window) == CAJA_WINDOW_NAVIGATION) { + +		if (eel_preferences_get_boolean (CAJA_PREFERENCES_ALWAYS_USE_BROWSER)) { +			if (selection_count == 0 || selection_count == 1) { +				label_with_underscore = g_strdup (_("Open in New _Tab")); +			} else { +				label_with_underscore = g_strdup_printf (ngettext("Open in %'d New _Tab", +										  "Open in %'d New _Tabs", +										  selection_count), +									 selection_count); +			} +		} else { +			if (selection_count == 0 || selection_count == 1) { +				label_with_underscore = g_strdup (_("Browse in New _Tab")); +			} else { +				label_with_underscore = g_strdup_printf (ngettext("Browse in %'d New _Tab", +										  "Browse in %'d New _Tabs", +										  selection_count), +									 selection_count); +			} +		} +		action = gtk_action_group_get_action (view->details->dir_action_group, +						      FM_ACTION_OPEN_IN_NEW_TAB); +		gtk_action_set_sensitive (action, selection_count != 0); +		gtk_action_set_visible (action, show_open_alternate); +		g_object_set (action, "label", +			      label_with_underscore, +			      NULL); +		g_free (label_with_underscore); +	} else { +		action = gtk_action_group_get_action (view->details->dir_action_group, +						      FM_ACTION_OPEN_IN_NEW_TAB); +		gtk_action_set_visible (action, FALSE); +	} + +	/* next pane actions, only in navigation mode */ +	if (caja_window_info_get_window_type (view->details->window) != CAJA_WINDOW_NAVIGATION) { +		action = gtk_action_group_get_action (view->details->dir_action_group, +						      FM_ACTION_COPY_TO_NEXT_PANE); +		gtk_action_set_visible (action, FALSE); +		action = gtk_action_group_get_action (view->details->dir_action_group, +						      FM_ACTION_MOVE_TO_NEXT_PANE); +		gtk_action_set_visible (action, FALSE); +	} + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_OPEN_FOLDER_WINDOW); +	gtk_action_set_visible (action, show_open_folder_window); + +	/* Broken into its own function just for convenience */ +	reset_open_with_menu (view, selection); +	reset_extension_actions_menu (view, selection); + +	if (all_selected_items_in_trash (view)) { +		label = _("_Delete Permanently"); +		tip = _("Delete all selected items permanently"); +		show_separate_delete_command = FALSE; +	} else { +		label = _("Mo_ve to Trash"); +		tip = _("Move each selected item to the Trash"); +		show_separate_delete_command = show_delete_command_auto_value; +	} + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_TRASH); +	g_object_set (action, +		      "label", label, +		      "tooltip", tip, +		      "icon-name", all_selected_items_in_trash (view) ? +					CAJA_ICON_DELETE : CAJA_ICON_TRASH_FULL, +		      NULL); +	gtk_action_set_sensitive (action, can_delete_files); + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_DELETE); +	gtk_action_set_visible (action, show_separate_delete_command); + +	if (show_separate_delete_command) { +		g_object_set (action, +			      "label", _("_Delete"), +			      "icon-name", CAJA_ICON_DELETE, +			      NULL); +	} +	gtk_action_set_sensitive (action, can_delete_files); + + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_RESTORE_FROM_TRASH); +	update_restore_from_trash_action (action, selection, FALSE); + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_DUPLICATE); +	gtk_action_set_sensitive (action, can_duplicate_files); + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_CREATE_LINK); +	gtk_action_set_sensitive (action, can_link_files); +	g_object_set (action, "label", +		      ngettext ("Ma_ke Link", +			      	"Ma_ke Links", +				selection_count), +		      NULL); + +	show_properties = (!FM_IS_DESKTOP_ICON_VIEW (view) || selection_count > 0) && +			   fm_directory_view_supports_properties (view); + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_PROPERTIES); + +	gtk_action_set_sensitive (action, show_properties); + +	if (selection_count == 0) { +		gtk_action_set_tooltip (action, _("View or modify the properties of the open folder")); +	} else { +		gtk_action_set_tooltip (action, _("View or modify the properties of each selected item")); +	} + +	gtk_action_set_visible (action, show_properties); + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_PROPERTIES_ACCEL); + +	gtk_action_set_sensitive (action, show_properties); + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_EMPTY_TRASH); +	g_object_set (action, +		      "label", _("E_mpty Trash"), +		      NULL); +	gtk_action_set_sensitive (action, !caja_trash_monitor_is_empty ()); +	gtk_action_set_visible (action, should_show_empty_trash (view)); + +	show_save_search = FALSE; +	save_search_sensitive = FALSE; +	show_save_search_as = FALSE; +	if (view->details->model && +	    CAJA_IS_SEARCH_DIRECTORY (view->details->model)) { +		CajaSearchDirectory *search; + +		search = CAJA_SEARCH_DIRECTORY (view->details->model); +		if (caja_search_directory_is_saved_search (search)) { +			show_save_search = TRUE; +			save_search_sensitive = caja_search_directory_is_modified (search); +		} else { +			show_save_search_as = TRUE; +		} +	} +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_SAVE_SEARCH); +	gtk_action_set_visible (action, show_save_search); +	gtk_action_set_sensitive (action, save_search_sensitive); +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_SAVE_SEARCH_AS); +	gtk_action_set_visible (action, show_save_search_as); + + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_SELECT_ALL); +	gtk_action_set_sensitive (action, !fm_directory_view_is_empty (view)); + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_SELECT_PATTERN); +	gtk_action_set_sensitive (action, !fm_directory_view_is_empty (view)); + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_INVERT_SELECTION); +	gtk_action_set_sensitive (action, !fm_directory_view_is_empty (view)); + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_CUT); +	gtk_action_set_sensitive (action, can_delete_files); + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_COPY); +	gtk_action_set_sensitive (action, can_copy_files); + +	real_update_paste_menu (view, selection, selection_count); + +	disable_command_line = eel_preferences_get_boolean (CAJA_PREFERENCES_LOCKDOWN_COMMAND_LINE); +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_NEW_LAUNCHER); +	gtk_action_set_visible (action, vfolder_directory && !disable_command_line); +	gtk_action_set_sensitive (action, can_create_files); + +	real_update_menus_volumes (view, selection, selection_count); + +	caja_file_list_free (selection); + +	if (view->details->scripts_invalid) { +		update_scripts_menu (view); +	} + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_NEW_DOCUMENTS); +	gtk_action_set_sensitive (action, can_create_files); + +	if (can_create_files && view->details->templates_invalid) { +		update_templates_menu (view); +	} + +	next_pane_is_writable = has_writable_extra_pane (view); + +	/* next pane: works if file is copyable, and next pane is writable */ +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_COPY_TO_NEXT_PANE); +	gtk_action_set_sensitive (action, can_copy_files && next_pane_is_writable); + +	/* move to next pane: works if file is cuttable, and next pane is writable */ +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_MOVE_TO_NEXT_PANE); +	gtk_action_set_sensitive (action, can_delete_files && next_pane_is_writable); + + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_COPY_TO_HOME); +	gtk_action_set_sensitive (action, can_copy_files); +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_COPY_TO_DESKTOP); +	gtk_action_set_sensitive (action, can_copy_files); + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_MOVE_TO_HOME); +	gtk_action_set_sensitive (action, can_delete_files); +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      FM_ACTION_MOVE_TO_DESKTOP); +	gtk_action_set_sensitive (action, can_delete_files); + +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      "CopyToMenu"); +	gtk_action_set_sensitive (action, can_copy_files); +	action = gtk_action_group_get_action (view->details->dir_action_group, +					      "MoveToMenu"); +	gtk_action_set_sensitive (action, can_delete_files); +} + +/** + * fm_directory_view_pop_up_selection_context_menu + * + * Pop up a context menu appropriate to the selected items. + * @view: FMDirectoryView of interest. + * @event: The event that triggered this context menu. + * + * Return value: CajaDirectory for this view. + * + **/ +void +fm_directory_view_pop_up_selection_context_menu  (FMDirectoryView *view, +						  GdkEventButton  *event) +{ +	g_assert (FM_IS_DIRECTORY_VIEW (view)); + +	/* Make the context menu items not flash as they update to proper disabled, +	 * etc. states by forcing menus to update now. +	 */ +	update_menus_if_pending (view); + +	update_context_menu_position_from_event (view, event); + +	eel_pop_up_context_menu (create_popup_menu +				      	(view, FM_DIRECTORY_VIEW_POPUP_PATH_SELECTION), +				      EEL_DEFAULT_POPUP_MENU_DISPLACEMENT, +				      EEL_DEFAULT_POPUP_MENU_DISPLACEMENT, +				      event); +} + +/** + * fm_directory_view_pop_up_background_context_menu + * + * Pop up a context menu appropriate to the view globally at the last right click location. + * @view: FMDirectoryView of interest. + * + * Return value: CajaDirectory for this view. + * + **/ +void +fm_directory_view_pop_up_background_context_menu (FMDirectoryView *view, +						  GdkEventButton  *event) +{ +	g_assert (FM_IS_DIRECTORY_VIEW (view)); + +	/* Make the context menu items not flash as they update to proper disabled, +	 * etc. states by forcing menus to update now. +	 */ +	update_menus_if_pending (view); + +	update_context_menu_position_from_event (view, event); + + +	eel_pop_up_context_menu (create_popup_menu +				      (view, FM_DIRECTORY_VIEW_POPUP_PATH_BACKGROUND), +				      EEL_DEFAULT_POPUP_MENU_DISPLACEMENT, +				      EEL_DEFAULT_POPUP_MENU_DISPLACEMENT, +				      event); +} + +static void +real_pop_up_location_context_menu (FMDirectoryView *view) +{ +	/* always update the menu before showing it. Shouldn't be too expensive. */ +	real_update_location_menu (view); + +	update_context_menu_position_from_event (view, view->details->location_popup_event); + +	eel_pop_up_context_menu (create_popup_menu +				      (view, FM_DIRECTORY_VIEW_POPUP_PATH_LOCATION), +				      EEL_DEFAULT_POPUP_MENU_DISPLACEMENT, +				      EEL_DEFAULT_POPUP_MENU_DISPLACEMENT, +				      view->details->location_popup_event); +} + +static void +location_popup_file_attributes_ready (CajaFile *file, +				      gpointer      data) +{ +	FMDirectoryView *view; + +	view = FM_DIRECTORY_VIEW (data); +	g_assert (FM_IS_DIRECTORY_VIEW (view)); + +	g_assert (file == view->details->location_popup_directory_as_file); + +	real_pop_up_location_context_menu (view); +} + +static void +unschedule_pop_up_location_context_menu (FMDirectoryView *view) +{ +	if (view->details->location_popup_directory_as_file != NULL) { +		g_assert (CAJA_IS_FILE (view->details->location_popup_directory_as_file)); +		caja_file_cancel_call_when_ready (view->details->location_popup_directory_as_file, +						      location_popup_file_attributes_ready, +						      view); +		caja_file_unref (view->details->location_popup_directory_as_file); +		view->details->location_popup_directory_as_file = NULL; +	} +} + +static void +schedule_pop_up_location_context_menu (FMDirectoryView *view, +				       GdkEventButton  *event, +				       CajaFile    *file) +{ +	g_assert (CAJA_IS_FILE (file)); + +	if (view->details->location_popup_event != NULL) { +		gdk_event_free ((GdkEvent *) view->details->location_popup_event); +	} +	view->details->location_popup_event = (GdkEventButton *) gdk_event_copy ((GdkEvent *)event); + +	if (file == view->details->location_popup_directory_as_file) { +		if (caja_file_check_if_ready (file, CAJA_FILE_ATTRIBUTE_INFO | +							CAJA_FILE_ATTRIBUTE_MOUNT | +							CAJA_FILE_ATTRIBUTE_FILESYSTEM_INFO)) { +			real_pop_up_location_context_menu (view); +		} +	} else { +		unschedule_pop_up_location_context_menu (view); + +		view->details->location_popup_directory_as_file = caja_file_ref (file); +		caja_file_call_when_ready (view->details->location_popup_directory_as_file, +					       CAJA_FILE_ATTRIBUTE_INFO | +					       CAJA_FILE_ATTRIBUTE_MOUNT | +					       CAJA_FILE_ATTRIBUTE_FILESYSTEM_INFO, +					       location_popup_file_attributes_ready, +					       view); +	} +} + +/** + * fm_directory_view_pop_up_location_context_menu + * + * Pop up a context menu appropriate to the view globally. + * @view: FMDirectoryView of interest. + * @event: GdkEventButton triggering the popup. + * @location: The location the popup-menu should be created for, + * or NULL for the currently displayed location. + * + **/ +void +fm_directory_view_pop_up_location_context_menu (FMDirectoryView *view, +						GdkEventButton  *event, +						const char      *location) +{ +	CajaFile *file; + +	g_assert (FM_IS_DIRECTORY_VIEW (view)); + +	if (location != NULL) { +		file = caja_file_get_by_uri (location); +	} else { +		file = caja_file_ref (view->details->directory_as_file); +	} + +	if (file != NULL) { +		schedule_pop_up_location_context_menu (view, event, file); +		caja_file_unref (file); +	} +} + +static void +fm_directory_view_drop_proxy_received_uris (FMDirectoryView *view, +					    const GList *source_uri_list, +					    const char *target_uri, +					    GdkDragAction action) +{ +	char *container_uri; + +	container_uri = NULL; +	if (target_uri == NULL) { +		container_uri = fm_directory_view_get_backing_uri (view); +		g_assert (container_uri != NULL); +	} + +	if (action == GDK_ACTION_ASK) { +		action = caja_drag_drop_action_ask +			(GTK_WIDGET (view), +			 GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK); +		if (action == 0) { +			return; +		} +	} + +	caja_clipboard_clear_if_colliding_uris (GTK_WIDGET (view), +						    source_uri_list, +						    fm_directory_view_get_copied_files_atom (view)); + +	fm_directory_view_move_copy_items (source_uri_list, NULL, +					   target_uri != NULL ? target_uri : container_uri, +					   action, 0, 0, view); + +	g_free (container_uri); +} + +static void +fm_directory_view_drop_proxy_received_netscape_url (FMDirectoryView *view, +						    const char *netscape_url, +						    const char *target_uri, +						    GdkDragAction action) +{ +	fm_directory_view_handle_netscape_url_drop (view, +						    netscape_url, +						    target_uri, +						    action, 0, 0); +} + +static void +schedule_update_menus (FMDirectoryView *view) +{ +	g_assert (FM_IS_DIRECTORY_VIEW (view)); + +	/* Don't schedule updates after destroy (#349551), + 	 * or if we are not active. + 	*/ +	if (view->details->window == NULL || +	    !view->details->active) { +		return; +	} + +	view->details->menu_states_untrustworthy = TRUE; + +	/* Schedule a menu update with the current update interval */ +	if (view->details->update_menus_timeout_id == 0) { +		view->details->update_menus_timeout_id +			= g_timeout_add (view->details->update_interval, update_menus_timeout_callback, view); +	} +} + +static void +remove_update_status_idle_callback (FMDirectoryView *view) +{ +	if (view->details->update_status_idle_id != 0) { +		g_source_remove (view->details->update_status_idle_id); +		view->details->update_status_idle_id = 0; +	} +} + +static gboolean +update_status_idle_callback (gpointer data) +{ +	FMDirectoryView *view; + +	view = FM_DIRECTORY_VIEW (data); +	fm_directory_view_display_selection_info (view); +	view->details->update_status_idle_id = 0; +	return FALSE; +} + +static void +schedule_update_status (FMDirectoryView *view) +{ +	g_assert (FM_IS_DIRECTORY_VIEW (view)); + +	/* Make sure we haven't already destroyed it */ +	if (view->details->window == NULL) { +		return; +	} + +	if (view->details->loading) { +		/* Don't update status bar while loading the dir */ +		return; +	} + +	if (view->details->update_status_idle_id == 0) { +		view->details->update_status_idle_id = +			g_idle_add_full (G_PRIORITY_DEFAULT_IDLE - 20, +					 update_status_idle_callback, view, NULL); +	} +} + +/** + * fm_directory_view_notify_selection_changed: + * + * Notify this view that the selection has changed. This is normally + * called only by subclasses. + * @view: FMDirectoryView whose selection has changed. + * + **/ +void +fm_directory_view_notify_selection_changed (FMDirectoryView *view) +{ +	GList *selection; +	GtkWindow *window; + +	g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + +	if (caja_debug_log_is_domain_enabled (CAJA_DEBUG_LOG_DOMAIN_USER)) { +		selection = fm_directory_view_get_selection (view); + +		window = fm_directory_view_get_containing_window (view); +		caja_debug_log_with_file_list (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, selection, +						   "selection changed in window %p", +						   window); +		caja_file_list_free (selection); +	} + +	view->details->selection_was_removed = FALSE; + +	if (!view->details->selection_change_is_due_to_shell) { +		view->details->send_selection_change_to_shell = TRUE; +	} + +	/* Schedule a display of the new selection. */ +	if (view->details->display_selection_idle_id == 0) { +		view->details->display_selection_idle_id +			= g_idle_add (display_selection_info_idle_callback, +				      view); +	} + +	if (view->details->batching_selection_level != 0) { +		view->details->selection_changed_while_batched = TRUE; +	} else { +		/* Here is the work we do only when we're not +		 * batching selection changes. In other words, it's the slower +		 * stuff that we don't want to slow down selection techniques +		 * such as rubberband-selecting in icon view. +		 */ + +		/* Schedule an update of menu item states to match selection */ +		schedule_update_menus (view); +	} +} + +static void +file_changed_callback (CajaFile *file, gpointer callback_data) +{ +	FMDirectoryView *view = FM_DIRECTORY_VIEW (callback_data); + +	schedule_changes (view); + +	schedule_update_menus (view); +	schedule_update_status (view); + +	/* We might have different capabilities, so we need to update +	 * relative icon emblems . (Writeable etc). +	 * Don't do this for trash, as it never changes writability +	 * but does change a lot for the file count attribute. +	 */ +	if (!caja_file_is_in_trash (file)) { +		EEL_CALL_METHOD +			(FM_DIRECTORY_VIEW_CLASS, view, emblems_changed, (view)); +	} +} + +/** + * load_directory: + * + * Switch the displayed location to a new uri. If the uri is not valid, + * the location will not be switched; user feedback will be provided instead. + * @view: FMDirectoryView whose location will be changed. + * @uri: A string representing the uri to switch to. + * + **/ +static void +load_directory (FMDirectoryView *view, +		CajaDirectory *directory) +{ +	CajaDirectory *old_directory; +	CajaFile *old_file; +	CajaFileAttributes attributes; + +	g_assert (FM_IS_DIRECTORY_VIEW (view)); +	g_assert (CAJA_IS_DIRECTORY (directory)); + +	fm_directory_view_stop (view); +	fm_directory_view_clear (view); + +	view->details->loading = TRUE; + +	/* Update menus when directory is empty, before going to new +	 * location, so they won't have any false lingering knowledge +	 * of old selection. +	 */ +	schedule_update_menus (view); + +	while (view->details->subdirectory_list != NULL) { +		fm_directory_view_remove_subdirectory (view, +				view->details->subdirectory_list->data); +	} + +	disconnect_model_handlers (view); + +	old_directory = view->details->model; +	caja_directory_ref (directory); +	view->details->model = directory; +	caja_directory_unref (old_directory); + +	old_file = view->details->directory_as_file; +	view->details->directory_as_file = +		caja_directory_get_corresponding_file (directory); +	caja_file_unref (old_file); + +	view->details->reported_load_error = FALSE; + +	/* FIXME bugzilla.gnome.org 45062: In theory, we also need to monitor metadata here (as +         * well as doing a call when ready), in case external forces +         * change the directory's file metadata. +	 */ +	attributes = +		CAJA_FILE_ATTRIBUTE_INFO | +		CAJA_FILE_ATTRIBUTE_MOUNT | +		CAJA_FILE_ATTRIBUTE_FILESYSTEM_INFO; +	view->details->metadata_for_directory_as_file_pending = TRUE; +	view->details->metadata_for_files_in_directory_pending = TRUE; +	caja_file_call_when_ready +		(view->details->directory_as_file, +		 attributes, +		 metadata_for_directory_as_file_ready_callback, view); +	caja_directory_call_when_ready +		(view->details->model, +		 attributes, +		 FALSE, +		 metadata_for_files_in_directory_ready_callback, view); + +	/* If capabilities change, then we need to update the menus +	 * because of New Folder, and relative emblems. +	 */ +	attributes = +		CAJA_FILE_ATTRIBUTE_INFO | +		CAJA_FILE_ATTRIBUTE_FILESYSTEM_INFO; +	caja_file_monitor_add (view->details->directory_as_file, +				   &view->details->directory_as_file, +				   attributes); + +	view->details->file_changed_handler_id = g_signal_connect +		(view->details->directory_as_file, "changed", +		 G_CALLBACK (file_changed_callback), view); +} + +static void +finish_loading (FMDirectoryView *view) +{ +	CajaFileAttributes attributes; + +	caja_window_info_report_load_underway (view->details->window, +						   CAJA_VIEW (view)); + +	/* Tell interested parties that we've begun loading this directory now. +	 * Subclasses use this to know that the new metadata is now available. +	 */ +	fm_directory_view_begin_loading (view); + +	/* Assume we have now all information to show window */ +	caja_window_info_view_visible  (view->details->window, CAJA_VIEW (view)); + +	if (caja_directory_are_all_files_seen (view->details->model)) { +		/* Unschedule a pending update and schedule a new one with the minimal +		 * update interval. This gives the view a short chance at gathering the +		 * (cached) deep counts. +		 */ +		unschedule_display_of_pending_files (view); +		schedule_timeout_display_of_pending_files (view, UPDATE_INTERVAL_MIN); +	} + +	/* Start loading. */ + +	/* Connect handlers to learn about loading progress. */ +	view->details->done_loading_handler_id = g_signal_connect +		(view->details->model, "done_loading", +		 G_CALLBACK (done_loading_callback), view); +	view->details->load_error_handler_id = g_signal_connect +		(view->details->model, "load_error", +		 G_CALLBACK (load_error_callback), view); + +	/* Monitor the things needed to get the right icon. Also +	 * monitor a directory's item count because the "size" +	 * attribute is based on that, and the file's metadata +	 * and possible custom name. +	 */ +	attributes = +		CAJA_FILE_ATTRIBUTES_FOR_ICON | +		CAJA_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT | +		CAJA_FILE_ATTRIBUTE_INFO | +		CAJA_FILE_ATTRIBUTE_LINK_INFO | +		CAJA_FILE_ATTRIBUTE_MOUNT | +		CAJA_FILE_ATTRIBUTE_EXTENSION_INFO; + +	caja_directory_file_monitor_add (view->details->model, +					     &view->details->model, +					     view->details->show_hidden_files, +					     view->details->show_backup_files, +					     attributes, +					     files_added_callback, view); + +    	view->details->files_added_handler_id = g_signal_connect +		(view->details->model, "files_added", +		 G_CALLBACK (files_added_callback), view); +	view->details->files_changed_handler_id = g_signal_connect +		(view->details->model, "files_changed", +		 G_CALLBACK (files_changed_callback), view); +} + +static void +finish_loading_if_all_metadata_loaded (FMDirectoryView *view) +{ +	if (!view->details->metadata_for_directory_as_file_pending && +	    !view->details->metadata_for_files_in_directory_pending) { +		finish_loading (view); +	} +} + +static void +metadata_for_directory_as_file_ready_callback (CajaFile *file, +			      		       gpointer callback_data) +{ +	FMDirectoryView *view; + +	view = callback_data; + +	g_assert (FM_IS_DIRECTORY_VIEW (view)); +	g_assert (view->details->directory_as_file == file); +	g_assert (view->details->metadata_for_directory_as_file_pending); + +	view->details->metadata_for_directory_as_file_pending = FALSE; + +	finish_loading_if_all_metadata_loaded (view); +} + +static void +metadata_for_files_in_directory_ready_callback (CajaDirectory *directory, +				   		GList *files, +			           		gpointer callback_data) +{ +	FMDirectoryView *view; + +	view = callback_data; + +	g_assert (FM_IS_DIRECTORY_VIEW (view)); +	g_assert (view->details->model == directory); +	g_assert (view->details->metadata_for_files_in_directory_pending); + +	view->details->metadata_for_files_in_directory_pending = FALSE; + +	finish_loading_if_all_metadata_loaded (view); +} + +char ** +fm_directory_view_get_emblem_names_to_exclude (FMDirectoryView *view) +{ +	g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), NULL); + +	return EEL_CALL_METHOD_WITH_RETURN_VALUE +		(FM_DIRECTORY_VIEW_CLASS, view, +		 get_emblem_names_to_exclude, (view)); +} + +static char ** +real_get_emblem_names_to_exclude (FMDirectoryView *view) +{ +	char **excludes; +	int i; + +	g_assert (FM_IS_DIRECTORY_VIEW (view)); + +	excludes = g_new (char *, 3); + +	i = 0; +	excludes[i++] = g_strdup (CAJA_FILE_EMBLEM_NAME_TRASH); + +	if (!caja_file_can_write (view->details->directory_as_file)) { +		excludes[i++] = g_strdup (CAJA_FILE_EMBLEM_NAME_CANT_WRITE); +	} + +	excludes[i++] = NULL; + +	return excludes; +} + +/** + * fm_directory_view_merge_menus: + * + * Add this view's menus to the window's menu bar. + * @view: FMDirectoryView in question. + */ +static void +fm_directory_view_merge_menus (FMDirectoryView *view) +{ +	g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + +	EEL_CALL_METHOD +		(FM_DIRECTORY_VIEW_CLASS, view, +		 merge_menus, (view)); +} + +static void +fm_directory_view_unmerge_menus (FMDirectoryView *view) +{ +	g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + +	EEL_CALL_METHOD +		(FM_DIRECTORY_VIEW_CLASS, view, +		 unmerge_menus, (view)); +} + +static void +disconnect_handler (GObject *object, int *id) +{ +	if (*id != 0) { +		g_signal_handler_disconnect (object, *id); +		*id = 0; +	} +} + +static void +disconnect_directory_handler (FMDirectoryView *view, int *id) +{ +	disconnect_handler (G_OBJECT (view->details->model), id); +} + +static void +disconnect_directory_as_file_handler (FMDirectoryView *view, int *id) +{ +	disconnect_handler (G_OBJECT (view->details->directory_as_file), id); +} + +static void +disconnect_model_handlers (FMDirectoryView *view) +{ +	if (view->details->model == NULL) { +		return; +	} +	disconnect_directory_handler (view, &view->details->files_added_handler_id); +	disconnect_directory_handler (view, &view->details->files_changed_handler_id); +	disconnect_directory_handler (view, &view->details->done_loading_handler_id); +	disconnect_directory_handler (view, &view->details->load_error_handler_id); +	disconnect_directory_as_file_handler (view, &view->details->file_changed_handler_id); +	caja_file_cancel_call_when_ready (view->details->directory_as_file, +					      metadata_for_directory_as_file_ready_callback, +					      view); +	caja_directory_cancel_callback (view->details->model, +					    metadata_for_files_in_directory_ready_callback, +					    view); +	caja_directory_file_monitor_remove (view->details->model, +						&view->details->model); +	caja_file_monitor_remove (view->details->directory_as_file, +				      &view->details->directory_as_file); +} + +/** + * fm_directory_view_reset_to_defaults: + * + * set sorting order, zoom level, etc. to match defaults + * + **/ +void +fm_directory_view_reset_to_defaults (FMDirectoryView *view) +{ +	CajaWindowShowHiddenFilesMode mode; + +	g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + +	EEL_CALL_METHOD +		(FM_DIRECTORY_VIEW_CLASS, view, +		 reset_to_defaults, (view)); +	mode = caja_window_info_get_hidden_files_mode (view->details->window); +	if (mode != CAJA_WINDOW_SHOW_HIDDEN_FILES_DEFAULT) { +		caja_window_info_set_hidden_files_mode (view->details->window, +							    CAJA_WINDOW_SHOW_HIDDEN_FILES_DEFAULT); +	} +} + +/** + * fm_directory_view_select_all: + * + * select all the items in the view + * + **/ +void +fm_directory_view_select_all (FMDirectoryView *view) +{ +	g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + +	EEL_CALL_METHOD +		(FM_DIRECTORY_VIEW_CLASS, view, +		 select_all, (view)); +} + +/** + * fm_directory_view_set_selection: + * + * set the selection to the items identified in @selection. @selection + * should be a list of CajaFiles + * + **/ +void +fm_directory_view_set_selection (FMDirectoryView *view, GList *selection) +{ +	g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + +	EEL_CALL_METHOD +		(FM_DIRECTORY_VIEW_CLASS, view, +		 set_selection, (view, selection)); +} + +static void +fm_directory_view_select_file (FMDirectoryView *view, CajaFile *file) +{ +	GList file_list; + +	file_list.data = file; +	file_list.next = NULL; +	file_list.prev = NULL; +	fm_directory_view_set_selection (view, &file_list); +} + +/** + * fm_directory_view_get_selected_icon_locations: + * + * return an array of locations of selected icons if available + * Return value: GArray of GdkPoints + * + **/ +GArray * +fm_directory_view_get_selected_icon_locations (FMDirectoryView *view) +{ +	g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), NULL); + +	return EEL_CALL_METHOD_WITH_RETURN_VALUE +		(FM_DIRECTORY_VIEW_CLASS, view, +		 get_selected_icon_locations, (view)); +} + +/** + * fm_directory_view_reveal_selection: + * + * Scroll as necessary to reveal the selected items. + **/ +void +fm_directory_view_reveal_selection (FMDirectoryView *view) +{ +	g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + +	EEL_CALL_METHOD +		(FM_DIRECTORY_VIEW_CLASS, view, +		 reveal_selection, (view)); +} + +static gboolean +remove_all (gpointer key, gpointer value, gpointer callback_data) +{ +	return TRUE; +} + +/** + * fm_directory_view_stop: + * + * Stop the current ongoing process, such as switching to a new uri. + * @view: FMDirectoryView in question. + * + **/ +void +fm_directory_view_stop (FMDirectoryView *view) +{ +	g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + +	unschedule_display_of_pending_files (view); +	reset_update_interval (view); + +	/* Free extra undisplayed files */ +	file_and_directory_list_free (view->details->new_added_files); +	view->details->new_added_files = NULL; +	file_and_directory_list_free (view->details->new_changed_files); +	view->details->new_changed_files = NULL; +	g_hash_table_foreach_remove (view->details->non_ready_files, remove_all, NULL); +	file_and_directory_list_free (view->details->old_added_files); +	view->details->old_added_files = NULL; +	file_and_directory_list_free (view->details->old_changed_files); +	view->details->old_changed_files = NULL; +	eel_g_object_list_free (view->details->pending_locations_selected); +	view->details->pending_locations_selected = NULL; + +	if (view->details->model != NULL) { +		caja_directory_file_monitor_remove (view->details->model, view); +	} +	done_loading (view, FALSE); +} + +gboolean +fm_directory_view_is_read_only (FMDirectoryView *view) +{ +	g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + +	return EEL_CALL_METHOD_WITH_RETURN_VALUE +		(FM_DIRECTORY_VIEW_CLASS, view, +		 is_read_only, (view)); +} + +gboolean +fm_directory_view_is_empty (FMDirectoryView *view) +{ +	g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + +	return EEL_CALL_METHOD_WITH_RETURN_VALUE +		(FM_DIRECTORY_VIEW_CLASS, view, +		 is_empty, (view)); +} + +gboolean +fm_directory_view_is_editable (FMDirectoryView *view) +{ +	CajaDirectory *directory; + +	directory = fm_directory_view_get_model (view); + +	if (directory != NULL) { +		return caja_directory_is_editable (directory); +	} + +	return TRUE; +} + +void +fm_directory_view_set_initiated_unmount (FMDirectoryView *view, +					 gboolean initiated_unmount) +{ +	if (view->details->window != NULL) { +		caja_window_info_set_initiated_unmount(view->details->window, +							   initiated_unmount); +	} +} + +static gboolean +real_is_read_only (FMDirectoryView *view) +{ +	CajaFile *file; + +	if (!fm_directory_view_is_editable (view)) { +		return TRUE; +	} + +	file = fm_directory_view_get_directory_as_file (view); +	if (file != NULL) { +		return !caja_file_can_write (file); +	} +	return FALSE; +} + +gboolean +fm_directory_view_supports_creating_files (FMDirectoryView *view) +{ +	g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + +	return EEL_CALL_METHOD_WITH_RETURN_VALUE +		(FM_DIRECTORY_VIEW_CLASS, view, +		 supports_creating_files, (view)); +} + +gboolean +fm_directory_view_accepts_dragged_files (FMDirectoryView *view) +{ +	g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + +	return EEL_CALL_METHOD_WITH_RETURN_VALUE +		(FM_DIRECTORY_VIEW_CLASS, view, +		 accepts_dragged_files, (view)); +} + +/** + * fm_directory_view_should_show_file + * + * Returns whether or not this file should be displayed based on + * current filtering options. + */ +gboolean +fm_directory_view_should_show_file (FMDirectoryView *view, CajaFile *file) +{ +	return caja_file_should_show (file, +					  view->details->show_hidden_files, +					  view->details->show_backup_files, +					  view->details->show_foreign_files); +} + +static gboolean +real_supports_creating_files (FMDirectoryView *view) +{ +	g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + +	return !fm_directory_view_is_read_only (view) && !showing_trash_directory (view); +} + +static gboolean +real_accepts_dragged_files (FMDirectoryView *view) +{ +	g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + +	return !fm_directory_view_is_read_only (view); +} + +gboolean +fm_directory_view_supports_properties (FMDirectoryView *view) +{ +	g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + +	return EEL_CALL_METHOD_WITH_RETURN_VALUE +		(FM_DIRECTORY_VIEW_CLASS, view, +		 supports_properties, (view)); +} + +static gboolean +real_supports_properties (FMDirectoryView *view) +{ +	g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + +	return TRUE; +} + +gboolean +fm_directory_view_supports_zooming (FMDirectoryView *view) +{ +	g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + +	return EEL_CALL_METHOD_WITH_RETURN_VALUE +		(FM_DIRECTORY_VIEW_CLASS, view, +		 supports_zooming, (view)); +} + +static gboolean +real_supports_zooming (FMDirectoryView *view) +{ +	g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + +	return TRUE; +} + +gboolean +fm_directory_view_using_manual_layout (FMDirectoryView  *view) +{ +	g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + +	return EEL_CALL_METHOD_WITH_RETURN_VALUE +		(FM_DIRECTORY_VIEW_CLASS, view, +		 using_manual_layout, (view)); +} + +static gboolean +real_using_manual_layout (FMDirectoryView *view) +{ +	g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + +	return FALSE; +} + +/** + * fm_directory_view_update_menus: + * + * Update the sensitivity and wording of dynamic menu items. + * @view: FMDirectoryView in question. + */ +void +fm_directory_view_update_menus (FMDirectoryView *view) +{ +	g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + +	if (!view->details->active) { +		return; +	} + + +	EEL_CALL_METHOD +		(FM_DIRECTORY_VIEW_CLASS, view, +		 update_menus, (view)); + +	view->details->menu_states_untrustworthy = FALSE; +} + +static void +schedule_update_menus_callback (gpointer callback_data) +{ +	schedule_update_menus (FM_DIRECTORY_VIEW (callback_data)); +} + +void +fm_directory_view_ignore_hidden_file_preferences (FMDirectoryView *view) +{ +	g_return_if_fail (view->details->model == NULL); + +	if (view->details->ignore_hidden_file_preferences) { +		return; +	} + +	view->details->show_hidden_files = FALSE; +	view->details->show_backup_files = FALSE; +	view->details->ignore_hidden_file_preferences = TRUE; +} + +void +fm_directory_view_set_show_foreign (FMDirectoryView *view, +		                    gboolean show_foreign) +{ +	view->details->show_foreign_files = show_foreign; +} + +char * +fm_directory_view_get_uri (FMDirectoryView *view) +{ +	g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), NULL); +	if (view->details->model == NULL) { +		return NULL; +	} +	return caja_directory_get_uri (view->details->model); +} + +/* Get the real directory where files will be stored and created */ +char * +fm_directory_view_get_backing_uri (FMDirectoryView *view) +{ +	CajaDirectory *directory; +	char *uri; + +	g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), NULL); + +	if (view->details->model == NULL) { +		return NULL; +	} + +	directory = view->details->model; + +	if (CAJA_IS_DESKTOP_DIRECTORY (directory)) { +		directory = caja_desktop_directory_get_real_directory (CAJA_DESKTOP_DIRECTORY (directory)); +	} else { +		caja_directory_ref (directory); +	} + +	uri = caja_directory_get_uri (directory); + +	caja_directory_unref (directory); + +	return uri; +} + +void +fm_directory_view_move_copy_items (const GList *item_uris, +				   GArray *relative_item_points, +				   const char *target_uri, +				   int copy_action, +				   int x, int y, +				   FMDirectoryView *view) +{ +	CajaFile *target_file; + +	g_assert (relative_item_points == NULL +		  || relative_item_points->len == 0 +		  || g_list_length ((GList *)item_uris) == relative_item_points->len); + +	/* add the drop location to the icon offsets */ +	offset_drop_points (relative_item_points, x, y); + +	target_file = caja_file_get_existing_by_uri (target_uri); +	/* special-case "command:" here instead of starting a move/copy */ +	if (target_file != NULL && caja_file_is_launcher (target_file)) { +		caja_file_unref (target_file); +		caja_launch_desktop_file ( +				gtk_widget_get_screen (GTK_WIDGET (view)), +				target_uri, item_uris, +				fm_directory_view_get_containing_window (view)); +		return; +	} else if (copy_action == GDK_ACTION_COPY && +		   caja_is_file_roller_installed () && +		   target_file != NULL && +		   caja_file_is_archive (target_file)) { +		char *command, *quoted_uri, *tmp; +		const GList *l; +		GdkScreen  *screen; + +		/* Handle dropping onto a file-roller archiver file, instead of starting a move/copy */ + +		caja_file_unref (target_file); + +		quoted_uri = g_shell_quote (target_uri); +		command = g_strconcat ("file-roller -a ", quoted_uri, NULL); +		g_free (quoted_uri); + +		for (l = item_uris; l != NULL; l = l->next) { +			quoted_uri = g_shell_quote ((char *) l->data); + +			tmp = g_strconcat (command, " ", quoted_uri, NULL); +			g_free (command); +			command = tmp; + +			g_free (quoted_uri); +		} + +		screen = gtk_widget_get_screen (GTK_WIDGET (view)); +		if (screen == NULL) { +			screen = gdk_screen_get_default (); +		} +		gdk_spawn_command_line_on_screen (screen, command, NULL); +		g_free (command); + +		return; +	} +	caja_file_unref (target_file); + +	caja_file_operations_copy_move +		(item_uris, relative_item_points, +		 target_uri, copy_action, GTK_WIDGET (view), +		 copy_move_done_callback, pre_copy_move (view)); +} + +gboolean +fm_directory_view_can_accept_item (CajaFile *target_item, +				   const char *item_uri, +				   FMDirectoryView *view) +{ +	g_return_val_if_fail (CAJA_IS_FILE (target_item), FALSE); +	g_return_val_if_fail (item_uri != NULL, FALSE); +	g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE); + +	return caja_drag_can_accept_item (target_item, item_uri); +} + +static void +fm_directory_view_trash_state_changed_callback (CajaTrashMonitor *trash_monitor, +						gboolean state, gpointer callback_data) +{ +	FMDirectoryView *view; + +	view = (FMDirectoryView *) callback_data; +	g_assert (FM_IS_DIRECTORY_VIEW (view)); + +	schedule_update_menus (view); +} + +void +fm_directory_view_start_batching_selection_changes (FMDirectoryView *view) +{ +	g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); + +	++view->details->batching_selection_level; +	view->details->selection_changed_while_batched = FALSE; +} + +void +fm_directory_view_stop_batching_selection_changes (FMDirectoryView *view) +{ +	g_return_if_fail (FM_IS_DIRECTORY_VIEW (view)); +	g_return_if_fail (view->details->batching_selection_level > 0); + +	if (--view->details->batching_selection_level == 0) { +		if (view->details->selection_changed_while_batched) { +			fm_directory_view_notify_selection_changed (view); +		} +	} +} + +static void +revert_slashes (char *string) +{ +	while (*string != 0) { +		if (*string == '/') { +			*string = '\\'; +		} +		string++; +	} +} + + +static GdkDragAction +ask_link_action (FMDirectoryView *view) +{ +	int button_pressed; +	GdkDragAction result; +	GtkWindow *parent_window; +	GtkWidget *dialog; + +	parent_window = NULL; + +	/* Don't use desktop window as parent, since that means +	   we show up an all desktops etc */ +	if (! FM_IS_DESKTOP_ICON_VIEW (view)) { +		parent_window = GTK_WINDOW (fm_directory_view_get_containing_window (view)); +	} + +	dialog = gtk_message_dialog_new (parent_window, +					 GTK_DIALOG_DESTROY_WITH_PARENT, +					 GTK_MESSAGE_QUESTION, +					 GTK_BUTTONS_NONE, +					 _("Download location?")); + +	gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), +						  _("You can download it or make a link to it.")); + +	gtk_dialog_add_button (GTK_DIALOG (dialog), +			       _("Make a _Link"), 0); +	gtk_dialog_add_button (GTK_DIALOG (dialog), +			       GTK_STOCK_CANCEL, 1); +	gtk_dialog_add_button (GTK_DIALOG (dialog), +			       _("_Download"), 2); + +	gtk_window_set_title (GTK_WINDOW (dialog), ""); /* as per HIG */ +	gtk_window_set_focus_on_map (GTK_WINDOW (dialog), TRUE); +	gtk_dialog_set_default_response (GTK_DIALOG (dialog), 2); + +	gtk_window_present (GTK_WINDOW (dialog)); + +	button_pressed = gtk_dialog_run (GTK_DIALOG (dialog)); + +	gtk_widget_destroy (dialog); + +	switch (button_pressed) { +	case 0: +		result = GDK_ACTION_LINK; +		break; +	case 1: +	case GTK_RESPONSE_DELETE_EVENT: +		result = 0; +		break; +	case 2: +		result = GDK_ACTION_COPY; +		break; +	default: +		g_assert_not_reached (); +		result = 0; +	} + +	return result; +} + +typedef struct { +	FMDirectoryView  *view; +	GCancellable *cancellable; +	char *encoded_url; +	char *target_uri; +	int x; +	int y; +	guint timeout; +} NetscapeUrlDropAsk; + +static void +handle_netscape_url_drop_ask_cb (GObject *source_object, +				 GAsyncResult *res, +				 gpointer user_data) +{ +	NetscapeUrlDropAsk *data; +	GdkDragAction action; +	GFileInfo *info; +	GFile *f; +	const char *mime_type; + +	data = user_data; +	f = G_FILE (source_object); + +	info = g_file_query_info_finish (f, res, NULL); +	mime_type = NULL; + +	if (info) { +		mime_type = g_file_info_get_content_type (info); +	} + +	if (mime_type != NULL && +	    (g_content_type_equals (mime_type, "text/html") || +	     g_content_type_equals (mime_type, "text/xml")  || +	     g_content_type_equals (mime_type, "application/xhtml+xml"))) { +		action = GDK_ACTION_LINK; +	} else if (mime_type != NULL && +		   g_content_type_equals (mime_type, "text/plain")) { +		action = ask_link_action (data->view); +	} else { +		action = GDK_ACTION_COPY; +	} +	if (info) { +		g_object_unref (info); +	} + +	if (action != 0) { +		fm_directory_view_handle_netscape_url_drop (data->view, +							    data->encoded_url, +							    data->target_uri, +							    action, +							    data->x, data->y); +	} + +	g_object_unref (data->view); +	g_object_unref (data->cancellable); +	if (data->timeout != 0) { +		g_source_remove (data->timeout); +	} +	g_free (data->encoded_url); +	g_free (data->target_uri); +	g_free (data); +} + +static gboolean +handle_netscape_url_drop_timeout (gpointer user_data) +{ +	NetscapeUrlDropAsk *data; + +	data = user_data; + +	g_cancellable_cancel (data->cancellable); +	data->timeout = 0; + +	return FALSE; +} + +static inline void +fm_directory_view_widget_to_file_operation_position (FMDirectoryView *view, +						     GdkPoint *position) +{ +	EEL_CALL_METHOD (FM_DIRECTORY_VIEW_CLASS, view, +			 widget_to_file_operation_position, +			 (view, position)); +} + +static void +fm_directory_view_widget_to_file_operation_position_xy (FMDirectoryView *view, +							int *x, int *y) +{ +	GdkPoint position; + +	position.x = *x; +	position.y = *y; +	fm_directory_view_widget_to_file_operation_position (view, &position); +	*x = position.x; +	*y = position.y; +} + +void +fm_directory_view_handle_netscape_url_drop (FMDirectoryView  *view, +					    const char       *encoded_url, +					    const char       *target_uri, +					    GdkDragAction     action, +					    int               x, +					    int               y) +{ +	GdkPoint point; +	GdkScreen *screen; +	int screen_num; +	char *url, *title; +	char *link_name, *link_display_name; +	char *container_uri; +	GArray *points; +	char **bits; +	GList *uri_list = NULL; +	GFile *f; + +	if (encoded_url == NULL) { +		return; +	} + +	container_uri = NULL; +	if (target_uri == NULL) { +		container_uri = fm_directory_view_get_backing_uri (view); +		g_assert (container_uri != NULL); +	} + +	f = g_file_new_for_uri (target_uri != NULL ? target_uri : container_uri); +	if (!g_file_is_native (f)) { +		eel_show_warning_dialog (_("Drag and drop is not supported."), +					 _("Drag and drop is only supported on local file systems."), +					 fm_directory_view_get_containing_window (view)); +		g_object_unref (f); +		g_free (container_uri); +		return; +	} +	g_object_unref (f); + +	/* _NETSCAPE_URL_ works like this: $URL\n$TITLE */ +	bits = g_strsplit (encoded_url, "\n", 0); +	switch (g_strv_length (bits)) { +	case 0: +		g_strfreev (bits); +		g_free (container_uri); +		return; +	case 1: +		url = bits[0]; +		title = NULL; +		break; +	default: +		url = bits[0]; +		title = bits[1]; +	} + +	if (action == GDK_ACTION_ASK) { +		NetscapeUrlDropAsk *data; + +		f = g_file_new_for_uri (url); +		data = g_new0 (NetscapeUrlDropAsk, 1); +		data->view = g_object_ref (view); +		data->cancellable = g_cancellable_new (); +		data->encoded_url = g_strdup (encoded_url); +		data->target_uri = g_strdup (target_uri); +		data->x = x; +		data->y = y; +		/* Ensure we wait at most 1 second for mimetype */ +		data->timeout = g_timeout_add (1000, +					       handle_netscape_url_drop_timeout, +					       data); +		g_file_query_info_async (f, +					 G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0, +					 0, data->cancellable, +					 handle_netscape_url_drop_ask_cb, +					 data); + +		g_free (container_uri); +		return; +	} + +	fm_directory_view_widget_to_file_operation_position_xy (view, &x, &y); + +	/* We don't support GDK_ACTION_ASK or GDK_ACTION_PRIVATE +	 * and we don't support combinations either. */ +	if ((action != GDK_ACTION_DEFAULT) && +	    (action != GDK_ACTION_COPY) && +	    (action != GDK_ACTION_MOVE) && +	    (action != GDK_ACTION_LINK)) { +		eel_show_warning_dialog (_("Drag and drop is not supported."), +					 _("An invalid drag type was used."), +					 fm_directory_view_get_containing_window (view)); +		g_free (container_uri); +		return; +	} + +	if (action == GDK_ACTION_LINK) { +		if (eel_str_is_empty (title)) { +			GFile *f; + +			f = g_file_new_for_uri (url); +			link_name = g_file_get_basename (f); +			g_object_unref (f); +		} else { +			link_name = g_strdup (title); +		} + +		if (!eel_str_is_empty (link_name)) { +			link_display_name = g_strdup_printf (_("Link to %s"), link_name); + +			/* The filename can't contain slashes, strip em. +			   (the basename of http://foo/ is http://foo/) */ +			revert_slashes (link_name); + +			point.x = x; +			point.y = y; + +			screen = gtk_widget_get_screen (GTK_WIDGET (view)); +			screen_num = gdk_screen_get_number (screen); + +			caja_link_local_create (target_uri != NULL ? target_uri : container_uri, +						    link_name, +						    link_display_name, +						    "mate-fs-bookmark", +						    url, +						    &point, +						    screen_num, +						    TRUE); + +			g_free (link_display_name); +		} +		g_free (link_name); +	} else { +		GdkPoint tmp_point = { 0, 0 }; + +		/* pass in a 1-item array of icon positions, relative to x, y */ +		points = g_array_new (FALSE, TRUE, sizeof (GdkPoint)); +		g_array_append_val (points, tmp_point); + +		uri_list = g_list_append (uri_list, url); + +		fm_directory_view_move_copy_items (uri_list, points, +						   target_uri != NULL ? target_uri : container_uri, +						   action, x, y, view); + +		g_list_free (uri_list); +		g_array_free (points, TRUE); +	} + +	g_strfreev (bits); + +	g_free (container_uri); +} + +void +fm_directory_view_handle_uri_list_drop (FMDirectoryView  *view, +					const char       *item_uris, +					const char       *target_uri, +					GdkDragAction     action, +					int               x, +					int               y) +{ +	gchar **uri_list; +	GList *real_uri_list = NULL; +	char *container_uri; +	int n_uris, i; +	GArray *points; + +	if (item_uris == NULL) { +		return; +	} + +	container_uri = NULL; +	if (target_uri == NULL) { +		container_uri = fm_directory_view_get_backing_uri (view); +		g_assert (container_uri != NULL); +	} + +	if (action == GDK_ACTION_ASK) { +		action = caja_drag_drop_action_ask +			(GTK_WIDGET (view), +			 GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK); +		if (action == 0) { +			g_free (container_uri); +			return; +		} +	} + +	/* We don't support GDK_ACTION_ASK or GDK_ACTION_PRIVATE +	 * and we don't support combinations either. */ +	if ((action != GDK_ACTION_DEFAULT) && +	    (action != GDK_ACTION_COPY) && +	    (action != GDK_ACTION_MOVE) && +	    (action != GDK_ACTION_LINK)) { +		eel_show_warning_dialog (_("Drag and drop is not supported."), +					 _("An invalid drag type was used."), +					 fm_directory_view_get_containing_window (view)); +		g_free (container_uri); +		return; +	} + +	n_uris = 0; +	uri_list = g_uri_list_extract_uris (item_uris); +	for (i = 0; uri_list[i] != NULL; i++) { +		real_uri_list = g_list_append (real_uri_list, uri_list[i]); +		n_uris++; +	} +	g_free (uri_list); + +	/* do nothing if no real uris are left */ +	if (n_uris == 0) { +		g_free (container_uri); +		return; +	} + +	if (n_uris == 1) { +		GdkPoint tmp_point = { 0, 0 }; + +		/* pass in a 1-item array of icon positions, relative to x, y */ +		points = g_array_new (FALSE, TRUE, sizeof (GdkPoint)); +		g_array_append_val (points, tmp_point); +	} else { +		points = NULL; +	} + +	fm_directory_view_widget_to_file_operation_position_xy (view, &x, &y); + +	fm_directory_view_move_copy_items (real_uri_list, points, +					   target_uri != NULL ? target_uri : container_uri, +					   action, x, y, view); + +	eel_g_list_free_deep (real_uri_list); + +	if (points != NULL) +		g_array_free (points, TRUE); + +	g_free (container_uri); +} + +void +fm_directory_view_handle_text_drop (FMDirectoryView  *view, +				    const char       *text, +				    const char       *target_uri, +				    GdkDragAction     action, +				    int               x, +				    int               y) +{ +	int length; +	char *container_uri; +	GdkPoint pos; + +	if (text == NULL) { +		return; +	} + +	g_return_if_fail (action == GDK_ACTION_COPY); + +	container_uri = NULL; +	if (target_uri == NULL) { +		container_uri = fm_directory_view_get_backing_uri (view); +		g_assert (container_uri != NULL); +	} + +	length = strlen (text); + +	pos.x = x; +	pos.y = y; +	fm_directory_view_widget_to_file_operation_position (view, &pos); + +	fm_directory_view_new_file_with_initial_contents ( +		view, target_uri != NULL ? target_uri : container_uri, +		/* Translator: This is the filename used for when you dnd text to a directory */ +		_("dropped text.txt"), +		text, length, &pos); + +	g_free (container_uri); +} + +void +fm_directory_view_handle_raw_drop (FMDirectoryView  *view, +				    const char       *raw_data, +				    int               length, +				    const char       *target_uri, +				    const char       *direct_save_uri, +				    GdkDragAction     action, +				    int               x, +				    int               y) +{ +	char *container_uri, *filename; +	GFile *direct_save_full; +	GdkPoint pos; + +	if (raw_data == NULL) { +		return; +	} + +	g_return_if_fail (action == GDK_ACTION_COPY); + +	container_uri = NULL; +	if (target_uri == NULL) { +		container_uri = fm_directory_view_get_backing_uri (view); +		g_assert (container_uri != NULL); +	} + +	pos.x = x; +	pos.y = y; +	fm_directory_view_widget_to_file_operation_position (view, &pos); + +	filename = NULL; +	if (direct_save_uri != NULL) { +		direct_save_full = g_file_new_for_uri (direct_save_uri); +		filename = g_file_get_basename (direct_save_full); +	} +	if (filename == NULL) { +		/* Translator: This is the filename used for when you dnd raw +		 * data to a directory, if the source didn't supply a name. +		 */ +		filename = _("dropped data"); +	} + +	fm_directory_view_new_file_with_initial_contents ( +		view, target_uri != NULL ? target_uri : container_uri, +		filename, raw_data, length, &pos); + +	g_free (container_uri); +} + +gboolean +fm_directory_view_get_active (FMDirectoryView *view) +{ +	g_assert (FM_IS_DIRECTORY_VIEW (view)); +	return view->details->active; +} + +static GArray * +real_get_selected_icon_locations (FMDirectoryView *view) +{ +        /* By default, just return an empty list. */ +        return g_array_new (FALSE, TRUE, sizeof (GdkPoint)); +} + +static void +fm_directory_view_set_property (GObject         *object, +				guint            prop_id, +				const GValue    *value, +				GParamSpec      *pspec) +{ +  FMDirectoryView *directory_view; +  CajaWindowSlotInfo *slot; +  CajaWindowInfo *window; + +  directory_view = FM_DIRECTORY_VIEW (object); + +  switch (prop_id)  { +  case PROP_WINDOW_SLOT: +	  g_assert (directory_view->details->slot == NULL); + +	  slot = CAJA_WINDOW_SLOT_INFO (g_value_get_object (value)); +          window = caja_window_slot_info_get_window (slot); + +	  directory_view->details->slot = slot; +	  directory_view->details->window = window; + +	  g_signal_connect_object (directory_view->details->slot, +				   "active", G_CALLBACK (slot_active), +				   directory_view, 0); +	  g_signal_connect_object (directory_view->details->slot, +				   "inactive", G_CALLBACK (slot_inactive), +				   directory_view, 0); + +	  g_signal_connect_object (directory_view->details->window, +				   "hidden-files-mode-changed", G_CALLBACK (hidden_files_mode_changed), +				   directory_view, 0); +	  fm_directory_view_init_show_hidden_files (directory_view); +      break; +    default: +      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); +      break; +    } +} + + +gboolean +fm_directory_view_handle_scroll_event (FMDirectoryView *directory_view, +				       GdkEventScroll *event) +{ +	if (event->state & GDK_CONTROL_MASK) { +		switch (event->direction) { +		case GDK_SCROLL_UP: +			/* Zoom In */ +			fm_directory_view_bump_zoom_level (directory_view, 1); +			return TRUE; + +		case GDK_SCROLL_DOWN: +			/* Zoom Out */ +			fm_directory_view_bump_zoom_level (directory_view, -1); +			return TRUE; + +		case GDK_SCROLL_LEFT: +		case GDK_SCROLL_RIGHT: +			break; + +		default: +			g_assert_not_reached (); +		} +	} + +	return FALSE; +} + +/* handle Shift+Scroll, which will cause a zoom-in/out */ +static gboolean +fm_directory_view_scroll_event (GtkWidget *widget, +				GdkEventScroll *event) +{ +	FMDirectoryView *directory_view; + +	directory_view = FM_DIRECTORY_VIEW (widget); +	if (fm_directory_view_handle_scroll_event (directory_view, event)) { +		return TRUE; +	} + +	return GTK_WIDGET_CLASS (parent_class)->scroll_event (widget, event); +} + + +static void +fm_directory_view_parent_set (GtkWidget *widget, +			      GtkWidget *old_parent) +{ +	FMDirectoryView *view; +	GtkWidget *parent; + +	view = FM_DIRECTORY_VIEW (widget); + +	parent = gtk_widget_get_parent (widget); +	g_assert (parent == NULL || old_parent == NULL); + +	if (GTK_WIDGET_CLASS (parent_class)->parent_set != NULL) { +		GTK_WIDGET_CLASS (parent_class)->parent_set (widget, old_parent); +	} + +	if (parent != NULL) { +		g_assert (old_parent == NULL); + +		if (view->details->slot == +		    caja_window_info_get_active_slot (view->details->window)) { +			view->details->active = TRUE; + +			fm_directory_view_merge_menus (view); +			schedule_update_menus (view); +		} +	} else { +		fm_directory_view_unmerge_menus (view); +		remove_update_menus_timeout_callback (view); +	} +} + +static void +fm_directory_view_class_init (FMDirectoryViewClass *klass) +{ +	GtkWidgetClass *widget_class; +	GtkScrolledWindowClass *scrolled_window_class; +	GtkBindingSet *binding_set; + +	widget_class = GTK_WIDGET_CLASS (klass); +	scrolled_window_class = GTK_SCROLLED_WINDOW_CLASS (klass); + +	G_OBJECT_CLASS (klass)->finalize = fm_directory_view_finalize; +	G_OBJECT_CLASS (klass)->set_property = fm_directory_view_set_property; + +	GTK_OBJECT_CLASS (klass)->destroy = fm_directory_view_destroy; + +	widget_class->scroll_event = fm_directory_view_scroll_event; +	widget_class->parent_set = fm_directory_view_parent_set; + +	/* Get rid of the strange 3-pixel gap that GtkScrolledWindow +	 * uses by default. It does us no good. +	 */ +	scrolled_window_class->scrollbar_spacing = 0; + +	signals[ADD_FILE] = +		g_signal_new ("add_file", +		              G_TYPE_FROM_CLASS (klass), +		              G_SIGNAL_RUN_LAST, +		              G_STRUCT_OFFSET (FMDirectoryViewClass, add_file), +		              NULL, NULL, +		              caja_marshal_VOID__OBJECT_OBJECT, +		              G_TYPE_NONE, 2, CAJA_TYPE_FILE, CAJA_TYPE_DIRECTORY); +	signals[BEGIN_FILE_CHANGES] = +		g_signal_new ("begin_file_changes", +		              G_TYPE_FROM_CLASS (klass), +		              G_SIGNAL_RUN_LAST, +		              G_STRUCT_OFFSET (FMDirectoryViewClass, begin_file_changes), +		              NULL, NULL, +		              g_cclosure_marshal_VOID__VOID, +		              G_TYPE_NONE, 0); +	signals[BEGIN_LOADING] = +		g_signal_new ("begin_loading", +		              G_TYPE_FROM_CLASS (klass), +		              G_SIGNAL_RUN_LAST, +		              G_STRUCT_OFFSET (FMDirectoryViewClass, begin_loading), +		              NULL, NULL, +		              g_cclosure_marshal_VOID__VOID, +		              G_TYPE_NONE, 0); +	signals[CLEAR] = +		g_signal_new ("clear", +		              G_TYPE_FROM_CLASS (klass), +		              G_SIGNAL_RUN_LAST, +		              G_STRUCT_OFFSET (FMDirectoryViewClass, clear), +		              NULL, NULL, +		              g_cclosure_marshal_VOID__VOID, +		              G_TYPE_NONE, 0); +	signals[END_FILE_CHANGES] = +		g_signal_new ("end_file_changes", +		              G_TYPE_FROM_CLASS (klass), +		              G_SIGNAL_RUN_LAST, +		              G_STRUCT_OFFSET (FMDirectoryViewClass, end_file_changes), +		              NULL, NULL, +		              g_cclosure_marshal_VOID__VOID, +		              G_TYPE_NONE, 0); +	signals[FLUSH_ADDED_FILES] = +		g_signal_new ("flush_added_files", +		              G_TYPE_FROM_CLASS (klass), +		              G_SIGNAL_RUN_LAST, +		              G_STRUCT_OFFSET (FMDirectoryViewClass, flush_added_files), +		              NULL, NULL, +		              g_cclosure_marshal_VOID__VOID, +		              G_TYPE_NONE, 0); +	signals[END_LOADING] = +		g_signal_new ("end_loading", +		              G_TYPE_FROM_CLASS (klass), +		              G_SIGNAL_RUN_LAST, +		              G_STRUCT_OFFSET (FMDirectoryViewClass, end_loading), +		              NULL, NULL, +		              g_cclosure_marshal_VOID__BOOLEAN, +		              G_TYPE_NONE, 1, G_TYPE_BOOLEAN); +	signals[FILE_CHANGED] = +		g_signal_new ("file_changed", +		              G_TYPE_FROM_CLASS (klass), +		              G_SIGNAL_RUN_LAST, +		              G_STRUCT_OFFSET (FMDirectoryViewClass, file_changed), +		              NULL, NULL, +		              caja_marshal_VOID__OBJECT_OBJECT, +		              G_TYPE_NONE, 2, CAJA_TYPE_FILE, CAJA_TYPE_DIRECTORY); +	signals[LOAD_ERROR] = +		g_signal_new ("load_error", +		              G_TYPE_FROM_CLASS (klass), +		              G_SIGNAL_RUN_LAST, +		              G_STRUCT_OFFSET (FMDirectoryViewClass, load_error), +		              NULL, NULL, +		              g_cclosure_marshal_VOID__POINTER, +		              G_TYPE_NONE, 1, G_TYPE_POINTER); +	signals[REMOVE_FILE] = +		g_signal_new ("remove_file", +		              G_TYPE_FROM_CLASS (klass), +		              G_SIGNAL_RUN_LAST, +		              G_STRUCT_OFFSET (FMDirectoryViewClass, remove_file), +		              NULL, NULL, +		              caja_marshal_VOID__OBJECT_OBJECT, +		              G_TYPE_NONE, 2, CAJA_TYPE_FILE, CAJA_TYPE_DIRECTORY); + +	klass->accepts_dragged_files = real_accepts_dragged_files; +	klass->file_limit_reached = real_file_limit_reached; +	klass->file_still_belongs = real_file_still_belongs; +	klass->get_emblem_names_to_exclude = real_get_emblem_names_to_exclude; +	klass->get_selected_icon_locations = real_get_selected_icon_locations; +	klass->is_read_only = real_is_read_only; +	klass->load_error = real_load_error; +	klass->can_rename_file = can_rename_file; +	klass->start_renaming_file = start_renaming_file; +	klass->supports_creating_files = real_supports_creating_files; +	klass->supports_properties = real_supports_properties; +	klass->supports_zooming = real_supports_zooming; +	klass->using_manual_layout = real_using_manual_layout; +        klass->merge_menus = real_merge_menus; +        klass->unmerge_menus = real_unmerge_menus; +        klass->update_menus = real_update_menus; +	klass->set_is_active = real_set_is_active; + +	/* Function pointers that subclasses must override */ +	EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, add_file); +	EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, bump_zoom_level); +	EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, can_zoom_in); +	EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, can_zoom_out); +	EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, clear); +	EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, file_changed); +	EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, get_background_widget); +	EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, get_selection); +	EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, get_selection_for_file_transfer); +	EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, get_item_count); +	EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, is_empty); +	EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, reset_to_defaults); +	EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, restore_default_zoom_level); +	EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, select_all); +	EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, set_selection); +	EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, invert_selection); +	EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, zoom_to_level); +	EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, get_zoom_level); + +	copied_files_atom = gdk_atom_intern ("x-special/mate-copied-files", FALSE); + +	g_object_class_install_property (G_OBJECT_CLASS (klass), +					 PROP_WINDOW_SLOT, +					 g_param_spec_object ("window-slot", +							      "Window Slot", +							      "The parent window slot reference", +							      CAJA_TYPE_WINDOW_SLOT_INFO, +							      G_PARAM_WRITABLE | +							      G_PARAM_CONSTRUCT_ONLY)); + +	signals[TRASH] = +		g_signal_new ("trash", +			      G_TYPE_FROM_CLASS (klass), +			      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, +			      G_STRUCT_OFFSET (FMDirectoryViewClass, trash), +			      g_signal_accumulator_true_handled, NULL, +			      eel_marshal_BOOLEAN__VOID, +			      G_TYPE_BOOLEAN, 0); +	signals[DELETE] = +		g_signal_new ("delete", +			      G_TYPE_FROM_CLASS (klass), +			      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, +			      G_STRUCT_OFFSET (FMDirectoryViewClass, delete), +			      g_signal_accumulator_true_handled, NULL, +			      eel_marshal_BOOLEAN__VOID, +			      G_TYPE_BOOLEAN, 0); + +	binding_set = gtk_binding_set_by_class (klass); +	gtk_binding_entry_add_signal (binding_set, GDK_Delete, 0, +				      "trash", 0); +	gtk_binding_entry_add_signal (binding_set, GDK_KP_Delete, 0, +				      "trash", 0); +	gtk_binding_entry_add_signal (binding_set, GDK_KP_Delete, GDK_SHIFT_MASK, +				      "delete", 0); + +	klass->trash = real_trash; +	klass->delete = real_delete; +} diff --git a/src/file-manager/fm-directory-view.h b/src/file-manager/fm-directory-view.h new file mode 100644 index 00000000..f38cbad1 --- /dev/null +++ b/src/file-manager/fm-directory-view.h @@ -0,0 +1,495 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* fm-directory-view.h + * + * Copyright (C) 1999, 2000  Free Software Foundaton + * Copyright (C) 2000, 2001  Eazel, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Ettore Perazzoli + * 	    Darin Adler <[email protected]> + * 	    John Sullivan <[email protected]> + *          Pavel Cisler <[email protected]> + */ + +#ifndef FM_DIRECTORY_VIEW_H +#define FM_DIRECTORY_VIEW_H + +#include <gtk/gtk.h> +#include <eel/eel-background.h> +#include <libcaja-private/caja-directory.h> +#include <libcaja-private/caja-file.h> +#include <libcaja-private/caja-icon-container.h> +#include <libcaja-private/caja-link.h> +#include <libcaja-private/caja-view.h> +#include <libcaja-private/caja-window-info.h> +#include <libcaja-private/caja-window-slot-info.h> +#include <gio/gio.h> + +typedef struct FMDirectoryView FMDirectoryView; +typedef struct FMDirectoryViewClass FMDirectoryViewClass; + +#define FM_TYPE_DIRECTORY_VIEW fm_directory_view_get_type() +#define FM_DIRECTORY_VIEW(obj) \ +  (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_DIRECTORY_VIEW, FMDirectoryView)) +#define FM_DIRECTORY_VIEW_CLASS(klass) \ +  (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_DIRECTORY_VIEW, FMDirectoryViewClass)) +#define FM_IS_DIRECTORY_VIEW(obj) \ +  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_DIRECTORY_VIEW)) +#define FM_IS_DIRECTORY_VIEW_CLASS(klass) \ +  (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_DIRECTORY_VIEW)) +#define FM_DIRECTORY_VIEW_GET_CLASS(obj) \ +  (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_DIRECTORY_VIEW, FMDirectoryViewClass)) + +typedef struct FMDirectoryViewDetails FMDirectoryViewDetails; + +struct FMDirectoryView +{ +    GtkScrolledWindow parent; +    FMDirectoryViewDetails *details; +}; + +struct FMDirectoryViewClass +{ +    GtkScrolledWindowClass parent_class; + +    /* The 'clear' signal is emitted to empty the view of its contents. +     * It must be replaced by each subclass. +     */ +    void 	(* clear) 		 (FMDirectoryView *view); + +    /* The 'begin_file_changes' signal is emitted before a set of files +     * are added to the view. It can be replaced by a subclass to do any +     * necessary preparation for a set of new files. The default +     * implementation does nothing. +     */ +    void 	(* begin_file_changes) (FMDirectoryView *view); + +    /* The 'add_file' signal is emitted to add one file to the view. +     * It must be replaced by each subclass. +     */ +    void    (* add_file) 		 (FMDirectoryView *view, +                                  CajaFile *file, +                                  CajaDirectory *directory); +    void    (* remove_file)		 (FMDirectoryView *view, +                                  CajaFile *file, +                                  CajaDirectory *directory); + +    /* The 'file_changed' signal is emitted to signal a change in a file, +     * including the file being removed. +     * It must be replaced by each subclass. +     */ +    void 	(* file_changed)         (FMDirectoryView *view, +                                      CajaFile *file, +                                      CajaDirectory *directory); + +    /* The 'end_file_changes' signal is emitted after a set of files +     * are added to the view. It can be replaced by a subclass to do any +     * necessary cleanup (typically, cleanup for code in begin_file_changes). +     * The default implementation does nothing. +     */ +    void 	(* end_file_changes)    (FMDirectoryView *view); + +    void    (* flush_added_files)	 (FMDirectoryView *view); + +    /* The 'begin_loading' signal is emitted before any of the contents +     * of a directory are added to the view. It can be replaced by a +     * subclass to do any necessary preparation to start dealing with a +     * new directory. The default implementation does nothing. +     */ +    void 	(* begin_loading) 	 (FMDirectoryView *view); + +    /* The 'end_loading' signal is emitted after all of the contents +     * of a directory are added to the view. It can be replaced by a +     * subclass to do any necessary clean-up. The default implementation +     * does nothing. +     * +     * If all_files_seen is true, the handler may assume that +     * no load error ocurred, and all files of the underlying +     * directory were loaded. +     * +     * Otherwise, end_loading was emitted due to cancellation, +     * which usually means that not all files are available. +     */ +    void 	(* end_loading) 	 (FMDirectoryView *view, +                                  gboolean all_files_seen); + +    /* The 'load_error' signal is emitted when the directory model +     * reports an error in the process of monitoring the directory's +     * contents.  The load error indicates that the process of +     * loading the contents has ended, but the directory is still +     * being monitored. The default implementation handles common +     * load failures like ACCESS_DENIED. +     */ +    void    (* load_error)           (FMDirectoryView *view, +                                      GError *error); + +    /* Function pointers that don't have corresponding signals */ + +    /* reset_to_defaults is a function pointer that subclasses must +     * override to set sort order, zoom level, etc to match default +     * values. +     */ +    void     (* reset_to_defaults)	         (FMDirectoryView *view); + +    /* get_selection is not a signal; it is just a function pointer for +     * subclasses to replace (override). Subclasses must replace it +     * with a function that returns a newly-allocated GList of +     * CajaFile pointers. +     */ +    GList *	(* get_selection) 	 	(FMDirectoryView *view); + +    /* get_selection_for_file_transfer  is a function pointer for +     * subclasses to replace (override). Subclasses must replace it +     * with a function that returns a newly-allocated GList of +     * CajaFile pointers. The difference from get_selection is +     * that any files in the selection that also has a parent folder +     * in the selection is not included. +     */ +    GList *	(* get_selection_for_file_transfer)(FMDirectoryView *view); + +    /* select_all is a function pointer that subclasses must override to +     * select all of the items in the view */ +    void     (* select_all)	         	(FMDirectoryView *view); + +    /* set_selection is a function pointer that subclasses must +     * override to select the specified items (and unselect all +     * others). The argument is a list of CajaFiles. */ + +    void     (* set_selection)	 	(FMDirectoryView *view, +                                     GList *selection); + +    /* invert_selection is a function pointer that subclasses must +     * override to invert selection. */ + +    void     (* invert_selection)	 	(FMDirectoryView *view); + +    /* Return an array of locations of selected icons in their view. */ +    GArray * (* get_selected_icon_locations) (FMDirectoryView *view); + +    guint    (* get_item_count)             (FMDirectoryView *view); + +    /* bump_zoom_level is a function pointer that subclasses must override +     * to change the zoom level of an object. */ +    void    (* bump_zoom_level)      	(FMDirectoryView *view, +                                         int zoom_increment); + +    /* zoom_to_level is a function pointer that subclasses must override +     * to set the zoom level of an object to the specified level. */ +    void    (* zoom_to_level) 		(FMDirectoryView *view, +                                     CajaZoomLevel level); + +    CajaZoomLevel (* get_zoom_level)    (FMDirectoryView *view); + +    /* restore_default_zoom_level is a function pointer that subclasses must override +         * to restore the zoom level of an object to a default setting. */ +    void    (* restore_default_zoom_level) (FMDirectoryView *view); + +    /* can_zoom_in is a function pointer that subclasses must override to +     * return whether the view is at maximum size (furthest-in zoom level) */ +    gboolean (* can_zoom_in)	 	(FMDirectoryView *view); + +    /* can_zoom_out is a function pointer that subclasses must override to +     * return whether the view is at minimum size (furthest-out zoom level) */ +    gboolean (* can_zoom_out)	 	(FMDirectoryView *view); + +    /* reveal_selection is a function pointer that subclasses may +     * override to make sure the selected items are sufficiently +     * apparent to the user (e.g., scrolled into view). By default, +     * this does nothing. +     */ +    void     (* reveal_selection)	 	(FMDirectoryView *view); + +    /* get_background is a function pointer that subclasses must +     * override to return the EelBackground for this view. +     */ +    GtkWidget * (* get_background_widget)	(FMDirectoryView *view); + +    /* merge_menus is a function pointer that subclasses can override to +     * add their own menu items to the window's menu bar. +     * If overridden, subclasses must call parent class's function. +     */ +    void    (* merge_menus)         	(FMDirectoryView *view); +    void    (* unmerge_menus)         	(FMDirectoryView *view); + +    /* update_menus is a function pointer that subclasses can override to +     * update the sensitivity or wording of menu items in the menu bar. +     * It is called (at least) whenever the selection changes. If overridden, +     * subclasses must call parent class's function. +     */ +    void    (* update_menus)         	(FMDirectoryView *view); + +    /* sort_files is a function pointer that subclasses can override +     * to provide a sorting order to determine which files should be +     * presented when only a partial list is provided. +     */ +    int     (* compare_files)              (FMDirectoryView *view, +                                            CajaFile    *a, +                                            CajaFile    *b); + +    /* get_emblem_names_to_exclude is a function pointer that subclasses +     * may override to specify a set of emblem names that should not +     * be displayed with each file. By default, all emblems returned by +     * CajaFile are displayed. +     */ +    char ** (* get_emblem_names_to_exclude)	(FMDirectoryView *view); + +    /* file_limit_reached is a function pointer that subclasses may +     * override to control what happens when a directory is loaded +     * that has more files than Caja can handle. The default +     * implmentation puts up a dialog box that is appropriate to +     * display when the user explicitly tried to visit a location that +     * they would think of as a normal directory. +     */ +    void (* file_limit_reached)		(FMDirectoryView *view); + +    /* supports_properties is a function pointer that subclasses may +     * override to control whether the "Show Properties" menu item +     * should be enabled for selected items. The default implementation +     * returns TRUE. +     */ +    gboolean (* supports_properties)	(FMDirectoryView *view); + +    /* supports_zooming is a function pointer that subclasses may +     * override to control whether or not the zooming control and +     * menu items should be enabled. The default implementation +     * returns TRUE. +     */ +    gboolean (* supports_zooming)		(FMDirectoryView *view); + +    /* using_manual_layout is a function pointer that subclasses may +     * override to control whether or not items can be freely positioned +     * on the user-visible area. +     * Note that this value is not guaranteed to be constant within the +     * view's lifecycle. */ +    gboolean (* using_manual_layout)     (FMDirectoryView *view); + +    /* is_read_only is a function pointer that subclasses may +     * override to control whether or not the user is allowed to +     * change the contents of the currently viewed directory. The +     * default implementation checks the permissions of the +     * directory. +     */ +    gboolean (* is_read_only)	        (FMDirectoryView *view); + +    /* is_empty is a function pointer that subclasses must +     * override to report whether the view contains any items. +     */ +    gboolean (* is_empty)                   (FMDirectoryView *view); + +    /* supports_creating_files is a function pointer that subclasses may +     * override to control whether or not new items can be created. +     * be accepted. The default implementation checks whether the +     * user has write permissions for the viewed directory, and whether +     * the viewed directory is in the trash. +     */ +    gboolean (* supports_creating_files)	(FMDirectoryView *view); + +    /* accepts_dragged_files is a function pointer that subclasses may +     * override to control whether or not files can be dropped in this +     * location. The default implementation returns TRUE. +     */ +    gboolean (* accepts_dragged_files)	(FMDirectoryView *view); + +    gboolean (* can_rename_file)            (FMDirectoryView *view, +            CajaFile *file); +    /* select_all specifies whether the whole filename should be selected +     * or only its basename (i.e. everything except the extension) +     * */ +    void	 (* start_renaming_file)        (FMDirectoryView *view, +            CajaFile *file, +            gboolean select_all); + +    gboolean (* file_still_belongs)		(FMDirectoryView *view, +                                         CajaFile	 *file, +                                         CajaDirectory *directory); + +    /* convert *point from widget's coordinate system to a coordinate +     * system used for specifying file operation positions, which is view-specific. +     * +     * This is used by the the icon view, which converts the screen position to a zoom +     * level-independent coordinate system. +     */ +    void (* widget_to_file_operation_position) (FMDirectoryView *view, +            GdkPoint        *position); + +    /* Preference change callbacks, overriden by icon and list views. +     * Icon and list views respond by synchronizing to the new preference +     * values and forcing an update if appropriate. +     */ +    void	(* text_attribute_names_changed)   (FMDirectoryView *view); +    void	(* embedded_text_policy_changed)   (FMDirectoryView *view); +    void	(* image_display_policy_changed)   (FMDirectoryView *view); +    void	(* click_policy_changed)	   (FMDirectoryView *view); +    void	(* sort_directories_first_changed) (FMDirectoryView *view); + +    void	(* emblems_changed)                (FMDirectoryView *view); + +    void    (* set_is_active)                  (FMDirectoryView *view, +            gboolean         is_active); + +    /* Signals used only for keybindings */ +    gboolean (* trash)                         (FMDirectoryView *view); +    gboolean (* delete)                        (FMDirectoryView *view); +}; + +/* GObject support */ +GType               fm_directory_view_get_type                         (void); + +/* Functions callable from the user interface and elsewhere. */ +CajaWindowInfo *fm_directory_view_get_caja_window              (FMDirectoryView  *view); +CajaWindowSlotInfo *fm_directory_view_get_caja_window_slot     (FMDirectoryView  *view); +char *              fm_directory_view_get_uri                          (FMDirectoryView  *view); +char *              fm_directory_view_get_backing_uri                  (FMDirectoryView  *view); +gboolean            fm_directory_view_can_accept_item                  (CajaFile     *target_item, +        const char       *item_uri, +        FMDirectoryView  *view); +void                fm_directory_view_display_selection_info           (FMDirectoryView  *view); +GList *             fm_directory_view_get_selection                    (FMDirectoryView  *view); +GList *             fm_directory_view_get_selection_for_file_transfer  (FMDirectoryView  *view); +void                fm_directory_view_invert_selection                 (FMDirectoryView  *view); +void                fm_directory_view_stop                             (FMDirectoryView  *view); +guint               fm_directory_view_get_item_count                   (FMDirectoryView  *view); +gboolean            fm_directory_view_can_zoom_in                      (FMDirectoryView  *view); +gboolean            fm_directory_view_can_zoom_out                     (FMDirectoryView  *view); +GtkWidget *         fm_directory_view_get_background_widget            (FMDirectoryView  *view); +void                fm_directory_view_bump_zoom_level                  (FMDirectoryView  *view, +        int               zoom_increment); +void                fm_directory_view_zoom_to_level                    (FMDirectoryView  *view, +        CajaZoomLevel zoom_level); +CajaZoomLevel   fm_directory_view_get_zoom_level                   (FMDirectoryView  *view); +void                fm_directory_view_restore_default_zoom_level       (FMDirectoryView  *view); +void                fm_directory_view_reset_to_defaults                (FMDirectoryView  *view); +void                fm_directory_view_select_all                       (FMDirectoryView  *view); +void                fm_directory_view_set_selection                    (FMDirectoryView  *view, +        GList            *selection); +GArray *            fm_directory_view_get_selected_icon_locations      (FMDirectoryView  *view); +void                fm_directory_view_reveal_selection                 (FMDirectoryView  *view); +gboolean            fm_directory_view_is_empty                         (FMDirectoryView  *view); +gboolean            fm_directory_view_is_read_only                     (FMDirectoryView  *view); +gboolean            fm_directory_view_supports_creating_files          (FMDirectoryView  *view); +gboolean            fm_directory_view_accepts_dragged_files            (FMDirectoryView  *view); +gboolean            fm_directory_view_supports_properties              (FMDirectoryView  *view); +gboolean            fm_directory_view_supports_zooming                 (FMDirectoryView  *view); +gboolean            fm_directory_view_using_manual_layout              (FMDirectoryView  *view); +void                fm_directory_view_move_copy_items                  (const GList      *item_uris, +        GArray           *relative_item_points, +        const char       *target_uri, +        int               copy_action, +        int               x, +        int               y, +        FMDirectoryView  *view); +GdkAtom	            fm_directory_view_get_copied_files_atom            (FMDirectoryView  *view); +gboolean            fm_directory_view_get_active                       (FMDirectoryView  *view); + +/* Wrappers for signal emitters. These are normally called + * only by FMDirectoryView itself. They have corresponding signals + * that observers might want to connect with. + */ +void                fm_directory_view_clear                            (FMDirectoryView  *view); +void                fm_directory_view_begin_loading                    (FMDirectoryView  *view); +void                fm_directory_view_end_loading                      (FMDirectoryView  *view, +        gboolean          all_files_seen); + +gboolean            fm_directory_view_get_loading                      (FMDirectoryView  *view); + +/* Hooks for subclasses to call. These are normally called only by + * FMDirectoryView and its subclasses + */ +void                fm_directory_view_activate_files                   (FMDirectoryView        *view, +        GList                  *files, +        CajaWindowOpenMode  mode, +        CajaWindowOpenFlags flags, +        gboolean                confirm_multiple); +void                fm_directory_view_activate_file                    (FMDirectoryView        *view, +        CajaFile           *file, +        CajaWindowOpenMode  mode, +        CajaWindowOpenFlags flags); +void                fm_directory_view_start_batching_selection_changes (FMDirectoryView  *view); +void                fm_directory_view_stop_batching_selection_changes  (FMDirectoryView  *view); +void                fm_directory_view_queue_file_change                (FMDirectoryView  *view, +        CajaFile     *file); +void                fm_directory_view_notify_selection_changed         (FMDirectoryView  *view); +GtkUIManager *      fm_directory_view_get_ui_manager                   (FMDirectoryView  *view); +char **             fm_directory_view_get_emblem_names_to_exclude      (FMDirectoryView  *view); +CajaDirectory  *fm_directory_view_get_model                        (FMDirectoryView  *view); +GtkWindow	   *fm_directory_view_get_containing_window	       (FMDirectoryView  *view); +CajaFile       *fm_directory_view_get_directory_as_file            (FMDirectoryView  *view); +EelBackground *     fm_directory_view_get_background                   (FMDirectoryView  *view); +gboolean            fm_directory_view_get_allow_moves                  (FMDirectoryView  *view); +void                fm_directory_view_pop_up_background_context_menu   (FMDirectoryView  *view, +        GdkEventButton   *event); +void                fm_directory_view_pop_up_selection_context_menu    (FMDirectoryView  *view, +        GdkEventButton   *event); +void                fm_directory_view_pop_up_location_context_menu     (FMDirectoryView  *view, +        GdkEventButton   *event, +        const char       *location); +void                fm_directory_view_send_selection_change            (FMDirectoryView *view); +gboolean            fm_directory_view_should_show_file                 (FMDirectoryView  *view, +        CajaFile     *file); +gboolean	    fm_directory_view_should_sort_directories_first    (FMDirectoryView  *view); +void                fm_directory_view_update_menus                     (FMDirectoryView  *view); +void                fm_directory_view_new_folder                       (FMDirectoryView  *view); +void                fm_directory_view_new_file                         (FMDirectoryView  *view, +        const char       *parent_uri, +        CajaFile     *source); +void                fm_directory_view_ignore_hidden_file_preferences   (FMDirectoryView  *view); +void                fm_directory_view_set_show_foreign                 (FMDirectoryView  *view, +        gboolean          show_foreign); +void                fm_directory_view_init_view_iface                  (CajaViewIface *iface); +gboolean            fm_directory_view_handle_scroll_event              (FMDirectoryView  *view, +        GdkEventScroll   *event); +void                fm_directory_view_handle_netscape_url_drop         (FMDirectoryView  *view, +        const char       *encoded_url, +        const char       *target_uri, +        GdkDragAction     action, +        int               x, +        int               y); +void                fm_directory_view_handle_uri_list_drop             (FMDirectoryView  *view, +        const char       *item_uris, +        const char       *target_uri, +        GdkDragAction     action, +        int               x, +        int               y); +void                fm_directory_view_handle_text_drop                 (FMDirectoryView  *view, +        const char       *text, +        const char       *target_uri, +        GdkDragAction     action, +        int               x, +        int               y); +void                fm_directory_view_handle_raw_drop                 (FMDirectoryView  *view, +        const char       *raw_data, +        int               length, +        const char       *target_uri, +        const char       *direct_save_uri, +        GdkDragAction     action, +        int               x, +        int               y); +void                fm_directory_view_freeze_updates                   (FMDirectoryView  *view); +void                fm_directory_view_unfreeze_updates                 (FMDirectoryView  *view); +void                fm_directory_view_add_subdirectory                (FMDirectoryView  *view, +        CajaDirectory*directory); +void                fm_directory_view_remove_subdirectory             (FMDirectoryView  *view, +        CajaDirectory*directory); + +gboolean            fm_directory_view_is_editable                     (FMDirectoryView *view); +void		    fm_directory_view_set_initiated_unmount	      (FMDirectoryView *view, +        gboolean inititated_unmount); + +/* operations affecting two directory views */ +void                fm_directory_view_move_copy_items_between_views   (FMDirectoryView *source, FMDirectoryView *target, gboolean copy); + +#endif /* FM_DIRECTORY_VIEW_H */ diff --git a/src/file-manager/fm-ditem-page.c b/src/file-manager/fm-ditem-page.c new file mode 100644 index 00000000..af88b753 --- /dev/null +++ b/src/file-manager/fm-ditem-page.c @@ -0,0 +1,563 @@ +/* + *  fm-ditem-page.c: Desktop item editing support + * + *  Copyright (C) 2004 James Willcox + * + *  This library is free software; you can redistribute it and/or + *  modify it under the terms of the GNU General Public + *  License as published by the Free Software Foundation; either + *  version 2 of the License, or (at your option) any later version. + * + *  This library is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + *  Library General Public License for more details. + * + *  You should have received a copy of the GNU General Public + *  License along with this library; if not, write to the Free + *  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + *  Authors: James Willcox <[email protected]> + * + */ + +#include <config.h> +#include "fm-ditem-page.h" + +#include <string.h> + +#include <eel/eel-glib-extensions.h> +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <libcaja-extension/caja-extension-types.h> +#include <libcaja-extension/caja-file-info.h> +#include <libcaja-private/caja-file.h> +#include <libcaja-private/caja-file-attributes.h> + +#define MAIN_GROUP "Desktop Entry" + +typedef struct ItemEntry +{ +    const char *field; +    const char *description; +    char *current_value; +    gboolean localized; +    gboolean filename; +} ItemEntry; + +enum +{ +    TARGET_URI_LIST +}; + +static const GtkTargetEntry target_table[] = +{ +    { "text/uri-list",  0, TARGET_URI_LIST } +}; + +static gboolean +_g_key_file_load_from_gfile (GKeyFile *key_file, +                             GFile *file, +                             GKeyFileFlags flags, +                             GError **error) +{ +    char *data; +    gsize len; +    gboolean res; + +    if (!g_file_load_contents (file, NULL, &data, &len, NULL, error)) +    { +        return FALSE; +    } + +    res = g_key_file_load_from_data (key_file, data, len, flags, error); + +    g_free (data); + +    return res; +} + +static gboolean +_g_key_file_save_to_uri (GKeyFile *key_file, +                         const char *uri, +                         GError  **error) +{ +    GFile *file; +    char *data; +    gsize len; + +    data = g_key_file_to_data (key_file, &len, error); +    if (data == NULL) +    { +        return FALSE; +    } +    file = g_file_new_for_uri (uri); +    if (!g_file_replace_contents (file, +                                  data, len, +                                  NULL, FALSE, +                                  G_FILE_CREATE_NONE, +                                  NULL, NULL, error)) +    { +        g_object_unref (file); +        g_free (data); +        return FALSE; +    } +    g_object_unref (file); +    g_free (data); +    return TRUE; +} + +static GKeyFile * +_g_key_file_new_from_file (GFile *file, +                           GKeyFileFlags flags, +                           GError **error) +{ +    GKeyFile *key_file; + +    key_file = g_key_file_new (); +    if (!_g_key_file_load_from_gfile (key_file, file, flags, error)) +    { +        g_key_file_free (key_file); +        key_file = NULL; +    } +    return key_file; +} + +static GKeyFile * +_g_key_file_new_from_uri (const char *uri, +                          GKeyFileFlags flags, +                          GError **error) +{ +    GKeyFile *key_file; +    GFile *file; + +    file = g_file_new_for_uri (uri); +    key_file = _g_key_file_new_from_file (file, flags, error); +    g_object_unref (file); +    return key_file; +} + +static ItemEntry * +item_entry_new (const char *field, +                const char *description, +                gboolean localized, +                gboolean filename) +{ +    ItemEntry *entry; + +    entry = g_new0 (ItemEntry, 1); +    entry->field = field; +    entry->description = description; +    entry->localized = localized; +    entry->filename = filename; + +    return entry; +} + +static void +item_entry_free (ItemEntry *entry) +{ +    g_free (entry->current_value); +    g_free (entry); +} + +static void +fm_ditem_page_url_drag_data_received (GtkWidget *widget, GdkDragContext *context, +                                      int x, int y, +                                      GtkSelectionData *selection_data, +                                      guint info, guint time, +                                      GtkEntry *entry) +{ +    char **uris; +    gboolean exactly_one; +    char *path; + +    uris = g_strsplit (gtk_selection_data_get_data (selection_data), "\r\n", 0); +    exactly_one = uris[0] != NULL && (uris[1] == NULL || uris[1][0] == '\0'); + +    if (!exactly_one) +    { +        g_strfreev (uris); +        return; +    } + +    path = g_filename_from_uri (uris[0], NULL, NULL); +    if (path != NULL) +    { +        gtk_entry_set_text (entry, path); +        g_free (path); +    } +    else +    { +        gtk_entry_set_text (entry, uris[0]); +    } + +    g_strfreev (uris); +} + +static void +fm_ditem_page_exec_drag_data_received (GtkWidget *widget, GdkDragContext *context, +                                       int x, int y, +                                       GtkSelectionData *selection_data, +                                       guint info, guint time, +                                       GtkEntry *entry) +{ +    char **uris; +    gboolean exactly_one; +    CajaFile *file; +    GKeyFile *key_file; +    char *uri, *type, *exec; + +    uris = g_strsplit (gtk_selection_data_get_data (selection_data), "\r\n", 0); +    exactly_one = uris[0] != NULL && (uris[1] == NULL || uris[1][0] == '\0'); + +    if (!exactly_one) +    { +        g_strfreev (uris); +        return; +    } + +    file = caja_file_get_by_uri (uris[0]); + +    g_return_if_fail (file != NULL); + +    uri = caja_file_get_uri (file); +    if (caja_file_is_mime_type (file, "application/x-desktop")) +    { +        key_file = _g_key_file_new_from_uri (uri, G_KEY_FILE_NONE, NULL); +        if (key_file != NULL) +        { +            type = g_key_file_get_string (key_file, MAIN_GROUP, "Type", NULL); +            if (type != NULL && strcmp (type, "Application") == 0) +            { +                exec = g_key_file_get_string (key_file, MAIN_GROUP, "Exec", NULL); +                if (exec != NULL) +                { +                    g_free (uri); +                    uri = exec; +                } +            } +            g_free (type); +            g_key_file_free (key_file); +        } +    } +    gtk_entry_set_text (entry, +                        uri?uri:""); +    gtk_widget_grab_focus (GTK_WIDGET (entry)); + +    g_free (uri); + +    caja_file_unref (file); + +    g_strfreev (uris); +} + +static void +save_entry (GtkEntry *entry, GKeyFile *key_file, const char *uri) +{ +    GError *error; +    ItemEntry *item_entry; +    const char *val; +    gchar **languages; + +    item_entry = g_object_get_data (G_OBJECT (entry), "item_entry"); +    val = gtk_entry_get_text (entry); + +    if (strcmp (val, item_entry->current_value) == 0) +    { +        return; /* No actual change, don't update file */ +    } + +    g_free (item_entry->current_value); +    item_entry->current_value = g_strdup (val); + +    if (item_entry->localized) +    { +        languages = (gchar **) g_get_language_names (); +        g_key_file_set_locale_string (key_file, MAIN_GROUP, item_entry->field, languages[0], val); +    } +    else +    { +        g_key_file_set_string (key_file, MAIN_GROUP, item_entry->field, val); +    } + +    error = NULL; + +    if (!_g_key_file_save_to_uri (key_file, uri, &error)) +    { +        g_warning ("%s", error->message); +        g_error_free (error); +    } +} + +static void +entry_activate_cb (GtkWidget *entry, +                   GtkWidget *container) +{ +    const char *uri; +    GKeyFile *key_file; + +    uri = g_object_get_data (G_OBJECT (container), "uri"); +    key_file = g_object_get_data (G_OBJECT (container), "keyfile"); +    save_entry (GTK_ENTRY (entry), key_file, uri); +} + +static gboolean +entry_focus_out_cb (GtkWidget *entry, +                    GdkEventFocus *event, +                    GtkWidget *container) +{ +    const char *uri; +    GKeyFile *key_file; + +    uri = g_object_get_data (G_OBJECT (container), "uri"); +    key_file = g_object_get_data (G_OBJECT (container), "keyfile"); +    save_entry (GTK_ENTRY (entry), key_file, uri); +    return FALSE; +} + +static GtkWidget * +build_table (GtkWidget *container, +             GKeyFile *key_file, +             GtkSizeGroup *label_size_group, +             GList *entries) +{ +    GtkWidget *table; +    GtkWidget *label; +    GtkWidget *entry; +    GList *l; +    char *val; +    int i; + +    table = gtk_table_new (g_list_length (entries) + 1, 2, FALSE); +    gtk_table_set_row_spacings (GTK_TABLE (table), 6); +    gtk_table_set_col_spacings (GTK_TABLE (table), 12); +    i = 0; + +    for (l = entries; l; l = l->next) +    { +        ItemEntry *item_entry = (ItemEntry *)l->data; +        char *label_text; + +        label_text = g_strdup_printf ("%s:", item_entry->description); +        label = gtk_label_new (label_text); +        gtk_label_set_use_markup (GTK_LABEL (label), TRUE); +        g_free (label_text); +        gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); +        gtk_size_group_add_widget (label_size_group, label); + +        entry = gtk_entry_new (); + +        if (item_entry->localized) +        { +            val = g_key_file_get_locale_string (key_file, +                                                MAIN_GROUP, +                                                item_entry->field, +                                                NULL, NULL); +        } +        else +        { +            val = g_key_file_get_string (key_file, +                                         MAIN_GROUP, +                                         item_entry->field, +                                         NULL); +        } + +        item_entry->current_value = g_strdup (val?val:""); +        gtk_entry_set_text (GTK_ENTRY (entry), item_entry->current_value); +        g_free (val); + +        gtk_table_attach (GTK_TABLE (table), label, +                          0, 1, i, i+1, GTK_FILL, GTK_FILL, +                          0, 0); +        gtk_table_attach (GTK_TABLE (table), entry, +                          1, 2, i, i+1, GTK_EXPAND|GTK_FILL, GTK_EXPAND|GTK_FILL, +                          0, 0); +        g_signal_connect (entry, "activate", +                          G_CALLBACK (entry_activate_cb), +                          container); +        g_signal_connect (entry, "focus_out_event", +                          G_CALLBACK (entry_focus_out_cb), +                          container); + +        g_object_set_data_full (G_OBJECT (entry), "item_entry", item_entry, +                                (GDestroyNotify)item_entry_free); + +        if (item_entry->filename) +        { +            gtk_drag_dest_set (GTK_WIDGET (entry), +                               GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP, +                               target_table, G_N_ELEMENTS (target_table), +                               GDK_ACTION_COPY | GDK_ACTION_MOVE); + +            g_signal_connect (entry, "drag_data_received", +                              G_CALLBACK (fm_ditem_page_url_drag_data_received), +                              entry); +        } +        else if (strcmp (item_entry->field, "Exec") == 0) +        { +            gtk_drag_dest_set (GTK_WIDGET (entry), +                               GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP, +                               target_table, G_N_ELEMENTS (target_table), +                               GDK_ACTION_COPY | GDK_ACTION_MOVE); + +            g_signal_connect (entry, "drag_data_received", +                              G_CALLBACK (fm_ditem_page_exec_drag_data_received), +                              entry); +        } + +        i++; +    } + +    /* append dummy row */ +    label = gtk_label_new (""); +    gtk_table_attach (GTK_TABLE (table), label, +                      0, 1, i, i+1, GTK_FILL, GTK_FILL, +                      0, 0); +    gtk_size_group_add_widget (label_size_group, label); + + +    gtk_widget_show_all (table); +    return table; +} + +static void +create_page (GKeyFile *key_file, GtkWidget *box) +{ +    GtkWidget *table; +    GList *entries; +    GtkSizeGroup *label_size_group; +    char *type; + +    entries = NULL; + +    type = g_key_file_get_string (key_file, MAIN_GROUP, "Type", NULL); + +    if (g_strcmp0 (type, "Link") == 0) +    { +        entries = g_list_prepend (entries, +                                  item_entry_new ("Comment", +                                          _("Comment"), TRUE, FALSE)); +        entries = g_list_prepend (entries, +                                  item_entry_new ("URL", +                                          _("URL"), FALSE, TRUE)); +        entries = g_list_prepend (entries, +                                  item_entry_new ("GenericName", +                                          _("Description"), TRUE, FALSE)); +    } +    else if (g_strcmp0 (type, "Application") == 0) +    { +        entries = g_list_prepend (entries, +                                  item_entry_new ("Comment", +                                          _("Comment"), TRUE, FALSE)); +        entries = g_list_prepend (entries, +                                  item_entry_new ("Exec", +                                          _("Command"), FALSE, FALSE)); +        entries = g_list_prepend (entries, +                                  item_entry_new ("GenericName", +                                          _("Description"), TRUE, FALSE)); +    } +    else +    { +        /* we only handle launchers and links */ + +        /* ensure that we build an empty table with a dummy row at the end */ +        goto build_table; +    } +    g_free (type); + +build_table: +    label_size_group = g_object_get_data (G_OBJECT (box), "label-size-group"); + +    table = build_table (box, key_file, label_size_group, entries); +    g_list_free (entries); + +    gtk_box_pack_start (GTK_BOX (box), table, FALSE, TRUE, 0); +    gtk_widget_show_all (GTK_WIDGET (box)); +} + + +static void +ditem_read_cb (GObject *source_object, +               GAsyncResult *res, +               gpointer user_data) +{ +    GKeyFile *key_file; +    GtkWidget *box; +    gsize file_size; +    char *file_contents; + +    box = GTK_WIDGET (user_data); + +    if (g_file_load_contents_finish (G_FILE (source_object), +                                     res, +                                     &file_contents, &file_size, +                                     NULL, NULL)) +    { +        key_file = g_key_file_new (); +        g_object_set_data_full (G_OBJECT (box), "keyfile", key_file, (GDestroyNotify)g_key_file_free); +        if (g_key_file_load_from_data (key_file, file_contents, file_size, 0, NULL)) +        { +            create_page (key_file, box); +        } +        g_free (file_contents); + +    } +    g_object_unref (box); +} + +static void +fm_ditem_page_create_begin (const char *uri, +                            GtkWidget *box) +{ +    GFile *location; + +    location = g_file_new_for_uri (uri); +    g_object_set_data_full (G_OBJECT (box), "uri", g_strdup (uri), g_free); +    g_file_load_contents_async (location, NULL, ditem_read_cb, g_object_ref (box)); +    g_object_unref (location); +} + +GtkWidget * +fm_ditem_page_make_box (GtkSizeGroup *label_size_group, +                        GList *files) +{ +    CajaFileInfo *info; +    char *uri; +    GtkWidget *box; + +    g_assert (fm_ditem_page_should_show (files)); + +    box = gtk_vbox_new (FALSE, 6); +    g_object_set_data_full (G_OBJECT (box), "label-size-group", +                            label_size_group, (GDestroyNotify) g_object_unref); + +    info = CAJA_FILE_INFO (files->data); + +    uri = caja_file_info_get_uri (info); +    fm_ditem_page_create_begin (uri, box); +    g_free (uri); + +    return box; +} + +gboolean +fm_ditem_page_should_show (GList *files) +{ +    CajaFileInfo *info; + +    if (!files || files->next) +    { +        return FALSE; +    } + +    info = CAJA_FILE_INFO (files->data); + +    if (!caja_file_info_is_mime_type (info, "application/x-desktop")) +    { +        return FALSE; +    } + +    return TRUE; +} + diff --git a/src/file-manager/fm-ditem-page.h b/src/file-manager/fm-ditem-page.h new file mode 100644 index 00000000..bf55d002 --- /dev/null +++ b/src/file-manager/fm-ditem-page.h @@ -0,0 +1,51 @@ +/* + *  fm-ditem-page.h - A property page for desktop items + * + *  Copyright (C) 2004 James Willcox + * + *  This library is free software; you can redistribute it and/or + *  modify it under the terms of the GNU General Public + *  License as published by the Free Software Foundation; either + *  version 2 of the License, or (at your option) any later version. + * + *  This library is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + *  Library General Public License for more details. + * + *  You should have received a copy of the GNU General Public + *  License along with this library; if not, write to the Free + *  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + *  Authors: James Willcox <[email protected]> + * + */ + +#ifndef FM_DITEM_PAGE_H +#define FM_DITEM_PAGE_H + +#include <glib.h> +#include <glib-object.h> +#include <gtk/gtk.h> + +#ifdef __cplusplus +extern "C" { +#endif + +    /* This is a mis-nomer. Launcher editables initially were displayed on separate +     * a property notebook page, which implemented the CajaPropertyPageProvider +     * interface. +     * +     * Nowadays, they are displayed on the "Basic" page, so just the setup +     * routines are left. +     */ + +    GtkWidget *fm_ditem_page_make_box (GtkSizeGroup *label_size_group, +                                       GList *files); +    gboolean   fm_ditem_page_should_show (GList *files); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/file-manager/fm-empty-view.c b/src/file-manager/fm-empty-view.c new file mode 100644 index 00000000..d85c702c --- /dev/null +++ b/src/file-manager/fm-empty-view.c @@ -0,0 +1,412 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-empty-view.c - implementation of empty view of directory. + +   Copyright (C) 2006 Free Software Foundation, Inc. + +   The Mate Library is free software; you can redistribute it and/or +   modify it under the terms of the GNU Library General Public License as +   published by the Free Software Foundation; either version 2 of the +   License, or (at your option) any later version. + +   The Mate Library is distributed in the hope that it will be useful, +   but WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +   Library General Public License for more details. + +   You should have received a copy of the GNU Library General Public +   License along with the Mate Library; see the file COPYING.LIB.  If not, +   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +   Boston, MA 02111-1307, USA. + +   Authors: Christian Neumair <[email protected]> +*/ + +#include <config.h> +#include "fm-empty-view.h" + +#include <string.h> +#include <libcaja-private/caja-file-utilities.h> +#include <libcaja-private/caja-view.h> +#include <libcaja-private/caja-view-factory.h> +#include <eel/eel-glib-extensions.h> +#include <eel/eel-gtk-macros.h> +#include <eel/eel-vfs-extensions.h> + +struct FMEmptyViewDetails +{ +    int number_of_files; +}; + +static GList *fm_empty_view_get_selection                   (FMDirectoryView   *view); +static GList *fm_empty_view_get_selection_for_file_transfer (FMDirectoryView   *view); +static void   fm_empty_view_scroll_to_file                  (CajaView      *view, +        const char        *uri); +static void   fm_empty_view_iface_init                      (CajaViewIface *iface); + +G_DEFINE_TYPE_WITH_CODE (FMEmptyView, fm_empty_view, FM_TYPE_DIRECTORY_VIEW, +                         G_IMPLEMENT_INTERFACE (CAJA_TYPE_VIEW, +                                 fm_empty_view_iface_init)); + +/* for EEL_CALL_PARENT */ +#define parent_class fm_empty_view_parent_class + +static void +fm_empty_view_add_file (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory) +{ +    static GTimer *timer = NULL; +    static gdouble cumu = 0, elaps; +    FM_EMPTY_VIEW (view)->details->number_of_files++; +    GdkPixbuf *icon; + +    if (!timer) timer = g_timer_new (); + +    g_timer_start (timer); +    icon = caja_file_get_icon_pixbuf (file, caja_get_icon_size_for_zoom_level (CAJA_ZOOM_LEVEL_STANDARD), TRUE, 0); + +    elaps = g_timer_elapsed (timer, NULL); +    g_timer_stop (timer); + +    g_object_unref (icon); + +    cumu += elaps; +    g_message ("entire loading: %.3f, cumulative %.3f", elaps, cumu); +} + + +static void +fm_empty_view_begin_loading (FMDirectoryView *view) +{ +} + +static void +fm_empty_view_clear (FMDirectoryView *view) +{ +} + + +static void +fm_empty_view_file_changed (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory) +{ +} + +static GtkWidget * +fm_empty_view_get_background_widget (FMDirectoryView *view) +{ +    return GTK_WIDGET (view); +} + +static GList * +fm_empty_view_get_selection (FMDirectoryView *view) +{ +    return NULL; +} + + +static GList * +fm_empty_view_get_selection_for_file_transfer (FMDirectoryView *view) +{ +    return NULL; +} + +static guint +fm_empty_view_get_item_count (FMDirectoryView *view) +{ +    return FM_EMPTY_VIEW (view)->details->number_of_files; +} + +static gboolean +fm_empty_view_is_empty (FMDirectoryView *view) +{ +    return FM_EMPTY_VIEW (view)->details->number_of_files == 0; +} + +static void +fm_empty_view_end_file_changes (FMDirectoryView *view) +{ +} + +static void +fm_empty_view_remove_file (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory) +{ +    FM_EMPTY_VIEW (view)->details->number_of_files--; +    g_assert (FM_EMPTY_VIEW (view)->details->number_of_files >= 0); +} + +static void +fm_empty_view_set_selection (FMDirectoryView *view, GList *selection) +{ +    fm_directory_view_notify_selection_changed (view); +} + +static void +fm_empty_view_select_all (FMDirectoryView *view) +{ +} + +static void +fm_empty_view_reveal_selection (FMDirectoryView *view) +{ +} + +static void +fm_empty_view_merge_menus (FMDirectoryView *view) +{ +    EEL_CALL_PARENT (FM_DIRECTORY_VIEW_CLASS, merge_menus, (view)); +} + +static void +fm_empty_view_update_menus (FMDirectoryView *view) +{ +    EEL_CALL_PARENT (FM_DIRECTORY_VIEW_CLASS, update_menus, (view)); +} + +/* Reset sort criteria and zoom level to match defaults */ +static void +fm_empty_view_reset_to_defaults (FMDirectoryView *view) +{ +} + +static void +fm_empty_view_bump_zoom_level (FMDirectoryView *view, int zoom_increment) +{ +} + +static CajaZoomLevel +fm_empty_view_get_zoom_level (FMDirectoryView *view) +{ +    return CAJA_ZOOM_LEVEL_STANDARD; +} + +static void +fm_empty_view_zoom_to_level (FMDirectoryView *view, +                             CajaZoomLevel zoom_level) +{ +} + +static void +fm_empty_view_restore_default_zoom_level (FMDirectoryView *view) +{ +} + +static gboolean +fm_empty_view_can_zoom_in (FMDirectoryView *view) +{ +    return FALSE; +} + +static gboolean +fm_empty_view_can_zoom_out (FMDirectoryView *view) +{ +    return FALSE; +} + +static void +fm_empty_view_start_renaming_file (FMDirectoryView *view, +                                   CajaFile *file, +                                   gboolean select_all) +{ +} + +static void +fm_empty_view_click_policy_changed (FMDirectoryView *directory_view) +{ +} + + +static int +fm_empty_view_compare_files (FMDirectoryView *view, CajaFile *file1, CajaFile *file2) +{ +    if (file1 < file2) +    { +        return -1; +    } + +    if (file1 > file2) +    { +        return +1; +    } + +    return 0; +} + +static gboolean +fm_empty_view_using_manual_layout (FMDirectoryView *view) +{ +    return FALSE; +} + +static void +fm_empty_view_end_loading (FMDirectoryView *view, +                           gboolean all_files_seen) +{ +} + +static void +fm_empty_view_finalize (GObject *object) +{ +    FMEmptyView *empty_view; + +    empty_view = FM_EMPTY_VIEW (object); +    g_free (empty_view->details); + +    G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +fm_empty_view_emblems_changed (FMDirectoryView *directory_view) +{ +} + +static char * +fm_empty_view_get_first_visible_file (CajaView *view) +{ +    return NULL; +} + +static void +fm_empty_view_scroll_to_file (CajaView *view, +                              const char *uri) +{ +} + +static void +fm_empty_view_grab_focus (CajaView *view) +{ +    gtk_widget_grab_focus (GTK_WIDGET (view)); +} + +static void +fm_empty_view_sort_directories_first_changed (FMDirectoryView *view) +{ +} + +static void +fm_empty_view_class_init (FMEmptyViewClass *class) +{ +    FMDirectoryViewClass *fm_directory_view_class; + +    fm_directory_view_class = FM_DIRECTORY_VIEW_CLASS (class); + +    G_OBJECT_CLASS (class)->finalize = fm_empty_view_finalize; + +    fm_directory_view_class->add_file = fm_empty_view_add_file; +    fm_directory_view_class->begin_loading = fm_empty_view_begin_loading; +    fm_directory_view_class->bump_zoom_level = fm_empty_view_bump_zoom_level; +    fm_directory_view_class->can_zoom_in = fm_empty_view_can_zoom_in; +    fm_directory_view_class->can_zoom_out = fm_empty_view_can_zoom_out; +    fm_directory_view_class->click_policy_changed = fm_empty_view_click_policy_changed; +    fm_directory_view_class->clear = fm_empty_view_clear; +    fm_directory_view_class->file_changed = fm_empty_view_file_changed; +    fm_directory_view_class->get_background_widget = fm_empty_view_get_background_widget; +    fm_directory_view_class->get_selection = fm_empty_view_get_selection; +    fm_directory_view_class->get_selection_for_file_transfer = fm_empty_view_get_selection_for_file_transfer; +    fm_directory_view_class->get_item_count = fm_empty_view_get_item_count; +    fm_directory_view_class->is_empty = fm_empty_view_is_empty; +    fm_directory_view_class->remove_file = fm_empty_view_remove_file; +    fm_directory_view_class->merge_menus = fm_empty_view_merge_menus; +    fm_directory_view_class->update_menus = fm_empty_view_update_menus; +    fm_directory_view_class->reset_to_defaults = fm_empty_view_reset_to_defaults; +    fm_directory_view_class->restore_default_zoom_level = fm_empty_view_restore_default_zoom_level; +    fm_directory_view_class->reveal_selection = fm_empty_view_reveal_selection; +    fm_directory_view_class->select_all = fm_empty_view_select_all; +    fm_directory_view_class->set_selection = fm_empty_view_set_selection; +    fm_directory_view_class->compare_files = fm_empty_view_compare_files; +    fm_directory_view_class->sort_directories_first_changed = fm_empty_view_sort_directories_first_changed; +    fm_directory_view_class->start_renaming_file = fm_empty_view_start_renaming_file; +    fm_directory_view_class->get_zoom_level = fm_empty_view_get_zoom_level; +    fm_directory_view_class->zoom_to_level = fm_empty_view_zoom_to_level; +    fm_directory_view_class->emblems_changed = fm_empty_view_emblems_changed; +    fm_directory_view_class->end_file_changes = fm_empty_view_end_file_changes; +    fm_directory_view_class->using_manual_layout = fm_empty_view_using_manual_layout; +    fm_directory_view_class->end_loading = fm_empty_view_end_loading; +} + +static const char * +fm_empty_view_get_id (CajaView *view) +{ +    return FM_EMPTY_VIEW_ID; +} + + +static void +fm_empty_view_iface_init (CajaViewIface *iface) +{ +    fm_directory_view_init_view_iface (iface); + +    iface->get_view_id = fm_empty_view_get_id; +    iface->get_first_visible_file = fm_empty_view_get_first_visible_file; +    iface->scroll_to_file = fm_empty_view_scroll_to_file; +    iface->get_title = NULL; +    iface->grab_focus = fm_empty_view_grab_focus; +} + + +static void +fm_empty_view_init (FMEmptyView *empty_view) +{ +    empty_view->details = g_new0 (FMEmptyViewDetails, 1); +} + +static CajaView * +fm_empty_view_create (CajaWindowSlotInfo *slot) +{ +    FMEmptyView *view; + +    g_assert (CAJA_IS_WINDOW_SLOT_INFO (slot)); + +    view = g_object_new (FM_TYPE_EMPTY_VIEW, +                         "window-slot", slot, +                         NULL); + +    return CAJA_VIEW (view); +} + +static gboolean +fm_empty_view_supports_uri (const char *uri, +                            GFileType file_type, +                            const char *mime_type) +{ +    if (file_type == G_FILE_TYPE_DIRECTORY) +    { +        return TRUE; +    } +    if (strcmp (mime_type, CAJA_SAVED_SEARCH_MIMETYPE) == 0) +    { +        return TRUE; +    } +    if (g_str_has_prefix (uri, "trash:")) +    { +        return TRUE; +    } +    if (g_str_has_prefix (uri, EEL_SEARCH_URI)) +    { +        return TRUE; +    } + +    return FALSE; +} + +static CajaViewInfo fm_empty_view = +{ +    FM_EMPTY_VIEW_ID, +    "Empty", +    "Empty View", +    "_Empty View", +    "The empty view encountered an error.", +    "Display this location with the empty view.", +    fm_empty_view_create, +    fm_empty_view_supports_uri +}; + +void +fm_empty_view_register (void) +{ +    fm_empty_view.id = fm_empty_view.id; +    fm_empty_view.view_combo_label = fm_empty_view.view_combo_label; +    fm_empty_view.view_menu_label_with_mnemonic = fm_empty_view.view_menu_label_with_mnemonic; +    fm_empty_view.error_label = fm_empty_view.error_label; +    fm_empty_view.display_location_label = fm_empty_view.display_location_label; + +    caja_view_factory_register (&fm_empty_view); +} diff --git a/src/file-manager/fm-empty-view.h b/src/file-manager/fm-empty-view.h new file mode 100644 index 00000000..795dd27a --- /dev/null +++ b/src/file-manager/fm-empty-view.h @@ -0,0 +1,60 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-empty-view.h - interface for empty view of directory. + +   Copyright (C) 2006 Free Software Foundation, Inc. + +   The Mate Library is free software; you can redistribute it and/or +   modify it under the terms of the GNU Library General Public License as +   published by the Free Software Foundation; either version 2 of the +   License, or (at your option) any later version. + +   The Mate Library is distributed in the hope that it will be useful, +   but WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +   Library General Public License for more details. + +   You should have received a copy of the GNU Library General Public +   License along with the Mate Library; see the file COPYING.LIB.  If not, +   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +   Boston, MA 02111-1307, USA. + +   Authors: Christian Neumair <[email protected]> +*/ + +#ifndef FM_EMPTY_VIEW_H +#define FM_EMPTY_VIEW_H + +#include "fm-directory-view.h" + +#define FM_TYPE_EMPTY_VIEW fm_empty_view_get_type() +#define FM_EMPTY_VIEW(obj) \ +  (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_EMPTY_VIEW, FMEmptyView)) +#define FM_EMPTY_VIEW_CLASS(klass) \ +  (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_EMPTY_VIEW, FMEmptyViewClass)) +#define FM_IS_EMPTY_VIEW(obj) \ +  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_EMPTY_VIEW)) +#define FM_IS_EMPTY_VIEW_CLASS(klass) \ +  (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_EMPTY_VIEW)) +#define FM_EMPTY_VIEW_GET_CLASS(obj) \ +  (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_EMPTY_VIEW, FMEmptyViewClass)) + +#define FM_EMPTY_VIEW_ID "OAFIID:Caja_File_Manager_Empty_View" + +typedef struct FMEmptyViewDetails FMEmptyViewDetails; + +typedef struct +{ +    FMDirectoryView parent_instance; +    FMEmptyViewDetails *details; +} FMEmptyView; + +typedef struct +{ +    FMDirectoryViewClass parent_class; +} FMEmptyViewClass; + +GType fm_empty_view_get_type (void); +void  fm_empty_view_register (void); + +#endif /* FM_EMPTY_VIEW_H */ diff --git a/src/file-manager/fm-error-reporting.c b/src/file-manager/fm-error-reporting.c new file mode 100644 index 00000000..f301e9a9 --- /dev/null +++ b/src/file-manager/fm-error-reporting.c @@ -0,0 +1,380 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-error-reporting.h - implementation of file manager functions that report + 			  errors to the user. + +   Copyright (C) 2000 Eazel, Inc. + +   The Mate Library is free software; you can redistribute it and/or +   modify it under the terms of the GNU Library General Public License as +   published by the Free Software Foundation; either version 2 of the +   License, or (at your option) any later version. + +   The Mate Library is distributed in the hope that it will be useful, +   but WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +   Library General Public License for more details. + +   You should have received a copy of the GNU Library General Public +   License along with the Mate Library; see the file COPYING.LIB.  If not, +   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +   Boston, MA 02111-1307, USA. + +   Authors: John Sullivan <[email protected]> +*/ + +#include <config.h> +#include "fm-error-reporting.h" + +#include <string.h> +#include <glib/gi18n.h> +#include <libcaja-private/caja-debug-log.h> +#include <libcaja-private/caja-file.h> +#include <eel/eel-string.h> +#include <eel/eel-stock-dialogs.h> + +#define NEW_NAME_TAG "Caja: new name" +#define MAXIMUM_DISPLAYED_FILE_NAME_LENGTH	50 + +static void finish_rename (CajaFile *file, gboolean stop_timer, GError *error); + +void +fm_report_error_loading_directory (CajaFile *file, +                                   GError *error, +                                   GtkWindow *parent_window) +{ +    char *file_name; +    char *message; + +    if (error == NULL || +            error->message == NULL) +    { +        return; +    } + +    if (error->domain == G_IO_ERROR && +            error->code == G_IO_ERROR_NOT_MOUNTED) +    { +        /* This case is retried automatically */ +        return; +    } + +    file_name = caja_file_get_display_name (file); + +    if (error->domain == G_IO_ERROR) +    { +        switch (error->code) +        { +        case G_IO_ERROR_PERMISSION_DENIED: +            message = g_strdup_printf (_("You do not have the permissions necessary to view the contents of \"%s\"."), +                                       file_name); +            break; +        case G_IO_ERROR_NOT_FOUND: +            message = g_strdup_printf (_("\"%s\" could not be found. Perhaps it has recently been deleted."), +                                       file_name); +            break; +        default: +            message = g_strdup_printf (_("Sorry, could not display all the contents of \"%s\": %s"), file_name, +                                       error->message); +        } +    } +    else +    { +        message = g_strdup (error->message); +    } + +    eel_show_error_dialog (_("The folder contents could not be displayed."), message, parent_window); + +    g_free (file_name); +    g_free (message); +} + +void +fm_report_error_renaming_file (CajaFile *file, +                               const char *new_name, +                               GError *error, +                               GtkWindow *parent_window) +{ +    char *original_name, *original_name_truncated; +    char *new_name_truncated; +    char *message; + +    /* Truncate names for display since very long file names with no spaces +     * in them won't get wrapped, and can create insanely wide dialog boxes. +     */ +    original_name = caja_file_get_display_name (file); +    original_name_truncated = eel_str_middle_truncate (original_name, MAXIMUM_DISPLAYED_FILE_NAME_LENGTH); +    g_free (original_name); + +    new_name_truncated = eel_str_middle_truncate (new_name, MAXIMUM_DISPLAYED_FILE_NAME_LENGTH); + +    message = NULL; +    if (error->domain == G_IO_ERROR) +    { +        switch (error->code) +        { +        case G_IO_ERROR_EXISTS: +            message = g_strdup_printf (_("The name \"%s\" is already used in this folder. " +                                         "Please use a different name."), +                                       new_name_truncated); +            break; +        case G_IO_ERROR_NOT_FOUND: +            message = g_strdup_printf (_("There is no \"%s\" in this folder. " +                                         "Perhaps it was just moved or deleted?"), +                                       original_name_truncated); +            break; +        case G_IO_ERROR_PERMISSION_DENIED: +            message = g_strdup_printf (_("You do not have the permissions necessary to rename \"%s\"."), +                                       original_name_truncated); +            break; +        case G_IO_ERROR_INVALID_FILENAME: +            if (strchr (new_name, '/') != NULL) +            { +                message = g_strdup_printf (_("The name \"%s\" is not valid because it contains the character \"/\". " +                                             "Please use a different name."), +                                           new_name_truncated); +            } +            else +            { +                message = g_strdup_printf (_("The name \"%s\" is not valid. " +                                             "Please use a different name."), +                                           new_name_truncated); +            } +            break; +        default: +            break; +        } +    } + +    if (message == NULL) +    { +        /* We should invent decent error messages for every case we actually experience. */ +        g_warning ("Hit unhandled case %s:%d in fm_report_error_renaming_file", +                   g_quark_to_string (error->domain), error->code); +        /* fall through */ +        message = g_strdup_printf (_("Sorry, could not rename \"%s\" to \"%s\": %s"), +                                   original_name_truncated, new_name_truncated, +                                   error->message); +    } + +    g_free (original_name_truncated); +    g_free (new_name_truncated); + +    eel_show_error_dialog (_("The item could not be renamed."), message, parent_window); +    g_free (message); +} + +void +fm_report_error_setting_group (CajaFile *file, +                               GError *error, +                               GtkWindow *parent_window) +{ +    char *file_name; +    char *message; + +    if (error == NULL) +    { +        return; +    } + +    file_name = caja_file_get_display_name (file); + +    message = NULL; +    if (error->domain == G_IO_ERROR) +    { +        switch (error->code) +        { +        case G_IO_ERROR_PERMISSION_DENIED: +            message = g_strdup_printf (_("You do not have the permissions necessary to change the group of \"%s\"."), +                                       file_name); +            break; +        default: +            break; +        } +    } + +    if (message == NULL) +    { +        /* We should invent decent error messages for every case we actually experience. */ +        g_warning ("Hit unhandled case %s:%d in fm_report_error_setting_group", +                   g_quark_to_string (error->domain), error->code); +        /* fall through */ +        message = g_strdup_printf (_("Sorry, could not change the group of \"%s\": %s"), file_name, +                                   error->message); +    } + + +    eel_show_error_dialog (_("The group could not be changed."), message, parent_window); + +    g_free (file_name); +    g_free (message); +} + +void +fm_report_error_setting_owner (CajaFile *file, +                               GError *error, +                               GtkWindow *parent_window) +{ +    char *file_name; +    char *message; + +    if (error == NULL) +    { +        return; +    } + +    file_name = caja_file_get_display_name (file); + +    message = g_strdup_printf (_("Sorry, could not change the owner of \"%s\": %s"), file_name, error->message); + +    eel_show_error_dialog (_("The owner could not be changed."), message, parent_window); + +    g_free (file_name); +    g_free (message); +} + +void +fm_report_error_setting_permissions (CajaFile *file, +                                     GError *error, +                                     GtkWindow *parent_window) +{ +    char *file_name; +    char *message; + +    if (error == NULL) +    { +        return; +    } + +    file_name = caja_file_get_display_name (file); + +    message = g_strdup_printf (_("Sorry, could not change the permissions of \"%s\": %s"), file_name, error->message); + +    eel_show_error_dialog (_("The permissions could not be changed."), message, parent_window); + +    g_free (file_name); +    g_free (message); +} + +typedef struct _FMRenameData +{ +    char *name; +    CajaFileOperationCallback callback; +    gpointer callback_data; +} FMRenameData; + +static void +fm_rename_data_free (FMRenameData *data) +{ +    g_free (data->name); +    g_free (data); +} + +static void +rename_callback (CajaFile *file, GFile *result_location, +                 GError *error, gpointer callback_data) +{ +    FMRenameData *data; + +    g_assert (CAJA_IS_FILE (file)); +    g_assert (callback_data == NULL); + +    data = g_object_get_data (G_OBJECT (file), NEW_NAME_TAG); +    g_assert (data != NULL); + +    if (error && +            !(error->domain == G_IO_ERROR && error->code == G_IO_ERROR_CANCELLED)) +    { +        /* If rename failed, notify the user. */ +        fm_report_error_renaming_file (file, data->name, error, NULL); +    } + +    finish_rename (file, TRUE, error); +} + +static void +cancel_rename_callback (gpointer callback_data) +{ +    GError *error; + +    error = g_error_new (G_IO_ERROR, G_IO_ERROR_CANCELLED, "Cancelled"); +    finish_rename (CAJA_FILE (callback_data), FALSE, error); +    g_error_free (error); +} + +static void +finish_rename (CajaFile *file, gboolean stop_timer, GError *error) +{ +    FMRenameData *data; + +    data = g_object_get_data (G_OBJECT (file), NEW_NAME_TAG); +    if (data == NULL) +    { +        return; +    } + +    /* Cancel both the rename and the timed wait. */ +    caja_file_cancel (file, rename_callback, NULL); +    if (stop_timer) +    { +        eel_timed_wait_stop (cancel_rename_callback, file); +    } + +    if (data->callback != NULL) +    { +        data->callback (file, NULL, error, data->callback_data); +    } + +    /* Let go of file name. */ +    g_object_set_data (G_OBJECT (file), NEW_NAME_TAG, NULL); +} + +void +fm_rename_file (CajaFile *file, +                const char *new_name, +                CajaFileOperationCallback callback, +                gpointer callback_data) +{ +    char *old_name, *wait_message; +    FMRenameData *data; +    char *uri; +    GError *error; + +    g_return_if_fail (CAJA_IS_FILE (file)); +    g_return_if_fail (new_name != NULL); + +    /* Stop any earlier rename that's already in progress. */ +    error = g_error_new (G_IO_ERROR, G_IO_ERROR_CANCELLED, "Cancelled"); +    finish_rename (file, TRUE, error); +    g_error_free (error); + +    data = g_new0 (FMRenameData, 1); +    data->name = g_strdup (new_name); +    data->callback = callback; +    data->callback_data = callback_data; + +    /* Attach the new name to the file. */ +    g_object_set_data_full (G_OBJECT (file), +                            NEW_NAME_TAG, +                            data, (GDestroyNotify)fm_rename_data_free); + +    /* Start the timed wait to cancel the rename. */ +    old_name = caja_file_get_display_name (file); +    wait_message = g_strdup_printf (_("Renaming \"%s\" to \"%s\"."), +                                    old_name, +                                    new_name); +    g_free (old_name); +    eel_timed_wait_start (cancel_rename_callback, file, wait_message, +                          NULL); /* FIXME bugzilla.gnome.org 42395: Parent this? */ +    g_free (wait_message); + +    uri = caja_file_get_uri (file); +    caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, +                    "rename file old=\"%s\", new=\"%s\"", +                    uri, new_name); +    g_free (uri); + +    /* Start the rename. */ +    caja_file_rename (file, new_name, +                      rename_callback, NULL); +} diff --git a/src/file-manager/fm-error-reporting.h b/src/file-manager/fm-error-reporting.h new file mode 100644 index 00000000..556e5ae2 --- /dev/null +++ b/src/file-manager/fm-error-reporting.h @@ -0,0 +1,55 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-error-reporting.h - interface for file manager functions that report + 			  errors to the user. + +   Copyright (C) 2000 Eazel, Inc. + +   The Mate Library is free software; you can redistribute it and/or +   modify it under the terms of the GNU Library General Public License as +   published by the Free Software Foundation; either version 2 of the +   License, or (at your option) any later version. + +   The Mate Library is distributed in the hope that it will be useful, +   but WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +   Library General Public License for more details. + +   You should have received a copy of the GNU Library General Public +   License along with the Mate Library; see the file COPYING.LIB.  If not, +   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +   Boston, MA 02111-1307, USA. + +   Authors: John Sullivan <[email protected]> +*/ + +#ifndef FM_ERROR_REPORTING_H +#define FM_ERROR_REPORTING_H + +#include <gtk/gtk.h> +#include <libcaja-private/caja-file.h> + +void fm_report_error_loading_directory	 (CajaFile   *file, +        GError         *error, +        GtkWindow	 *parent_window); +void fm_report_error_renaming_file       (CajaFile   *file, +        const char     *new_name, +        GError         *error, +        GtkWindow	 *parent_window); +void fm_report_error_setting_permissions (CajaFile   *file, +        GError         *error, +        GtkWindow	 *parent_window); +void fm_report_error_setting_owner       (CajaFile   *file, +        GError         *error, +        GtkWindow	 *parent_window); +void fm_report_error_setting_group       (CajaFile   *file, +        GError         *error, +        GtkWindow	 *parent_window); + +/* FIXME bugzilla.gnome.org 42394: Should this file be renamed or should this function be moved? */ +void fm_rename_file                      (CajaFile   *file, +        const char     *new_name, +        CajaFileOperationCallback callback, +        gpointer callback_data); + +#endif /* FM_ERROR_REPORTING_H */ diff --git a/src/file-manager/fm-icon-container.c b/src/file-manager/fm-icon-container.c new file mode 100644 index 00000000..86e54669 --- /dev/null +++ b/src/file-manager/fm-icon-container.c @@ -0,0 +1,625 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-icon-container.h - the container widget for file manager icons + +   Copyright (C) 2002 Sun Microsystems, Inc. + +   The Mate Library is free software; you can redistribute it and/or +   modify it under the terms of the GNU Library General Public License as +   published by the Free Software Foundation; either version 2 of the +   License, or (at your option) any later version. + +   The Mate Library is distributed in the hope that it will be useful, +   but WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +   Library General Public License for more details. + +   You should have received a copy of the GNU Library General Public +   License along with the Mate Library; see the file COPYING.LIB.  If not, +   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +   Boston, MA 02111-1307, USA. + +   Author: Michael Meeks <[email protected]> +*/ +#include <config.h> + +#include <string.h> +#include <glib/gi18n.h> +#include <gio/gio.h> +#include <libcaja-private/caja-global-preferences.h> +#include <libcaja-private/caja-file-attributes.h> +#include <libcaja-private/caja-thumbnails.h> +#include <libcaja-private/caja-desktop-icon-file.h> + +#include "fm-icon-container.h" + +#define ICON_TEXT_ATTRIBUTES_NUM_ITEMS		3 +#define ICON_TEXT_ATTRIBUTES_DEFAULT_TOKENS	"size,date_modified,type" + +G_DEFINE_TYPE (FMIconContainer, fm_icon_container, CAJA_TYPE_ICON_CONTAINER); + +static GQuark attribute_none_q; + +static FMIconView * +get_icon_view (CajaIconContainer *container) +{ +    /* Type unsafe comparison for performance */ +    return ((FMIconContainer *)container)->view; +} + +static CajaIconInfo * +fm_icon_container_get_icon_images (CajaIconContainer *container, +                                   CajaIconData      *data, +                                   int                    size, +                                   GList                **emblem_pixbufs, +                                   char                 **embedded_text, +                                   gboolean               for_drag_accept, +                                   gboolean               need_large_embeddded_text, +                                   gboolean              *embedded_text_needs_loading, +                                   gboolean              *has_window_open) +{ +    FMIconView *icon_view; +    char **emblems_to_ignore; +    CajaFile *file; +    gboolean use_embedding; +    CajaFileIconFlags flags; +    guint emblem_size; + +    file = (CajaFile *) data; + +    g_assert (CAJA_IS_FILE (file)); +    icon_view = get_icon_view (container); +    g_return_val_if_fail (icon_view != NULL, NULL); + +    use_embedding = FALSE; +    if (embedded_text) +    { +        *embedded_text = caja_file_peek_top_left_text (file, need_large_embeddded_text, embedded_text_needs_loading); +        use_embedding = *embedded_text != NULL; +    } + +    if (emblem_pixbufs != NULL) +    { +        emblem_size = caja_icon_get_emblem_size_for_icon_size (size); +        /* don't return images larger than the actual icon size */ +        emblem_size = MIN (emblem_size, size); + +        if (emblem_size > 0) +        { +            emblems_to_ignore = fm_directory_view_get_emblem_names_to_exclude +                                (FM_DIRECTORY_VIEW (icon_view)); +            *emblem_pixbufs = caja_file_get_emblem_pixbufs (file, +                              emblem_size, +                              FALSE, +                              emblems_to_ignore); +            g_strfreev (emblems_to_ignore); +        } +    } + +    *has_window_open = caja_file_has_open_window (file); + +    flags = CAJA_FILE_ICON_FLAGS_USE_MOUNT_ICON_AS_EMBLEM; +    if (!fm_icon_view_is_compact (icon_view) || +            caja_icon_container_get_zoom_level (container) > CAJA_ZOOM_LEVEL_STANDARD) +    { +        flags |= CAJA_FILE_ICON_FLAGS_USE_THUMBNAILS; +        if (fm_icon_view_is_compact (icon_view)) +        { +            flags |= CAJA_FILE_ICON_FLAGS_FORCE_THUMBNAIL_SIZE; +        } +    } + +    if (use_embedding) +    { +        flags |= CAJA_FILE_ICON_FLAGS_EMBEDDING_TEXT; +    } +    if (for_drag_accept) +    { +        flags |= CAJA_FILE_ICON_FLAGS_FOR_DRAG_ACCEPT; +    } + +    return caja_file_get_icon (file, size, flags); +} + +static char * +fm_icon_container_get_icon_description (CajaIconContainer *container, +                                        CajaIconData      *data) +{ +    CajaFile *file; +    char *mime_type; +    const char *description; + +    file = CAJA_FILE (data); +    g_assert (CAJA_IS_FILE (file)); + +    if (CAJA_IS_DESKTOP_ICON_FILE (file)) +    { +        return NULL; +    } + +    mime_type = caja_file_get_mime_type (file); +    description = g_content_type_get_description (mime_type); +    g_free (mime_type); +    return g_strdup (description); +} + +static void +fm_icon_container_start_monitor_top_left (CajaIconContainer *container, +        CajaIconData      *data, +        gconstpointer          client, +        gboolean               large_text) +{ +    CajaFile *file; +    CajaFileAttributes attributes; + +    file = (CajaFile *) data; + +    g_assert (CAJA_IS_FILE (file)); + +    attributes = CAJA_FILE_ATTRIBUTE_TOP_LEFT_TEXT; +    if (large_text) +    { +        attributes |= CAJA_FILE_ATTRIBUTE_LARGE_TOP_LEFT_TEXT; +    } +    caja_file_monitor_add (file, client, attributes); +} + +static void +fm_icon_container_stop_monitor_top_left (CajaIconContainer *container, +        CajaIconData      *data, +        gconstpointer          client) +{ +    CajaFile *file; + +    file = (CajaFile *) data; + +    g_assert (CAJA_IS_FILE (file)); + +    caja_file_monitor_remove (file, client); +} + +static void +fm_icon_container_prioritize_thumbnailing (CajaIconContainer *container, +        CajaIconData      *data) +{ +    CajaFile *file; +    char *uri; + +    file = (CajaFile *) data; + +    g_assert (CAJA_IS_FILE (file)); + +    if (caja_file_is_thumbnailing (file)) +    { +        uri = caja_file_get_uri (file); +        caja_thumbnail_prioritize (uri); +        g_free (uri); +    } +} + +/* + * Get the preference for which caption text should appear + * beneath icons. + */ +static GQuark * +fm_icon_container_get_icon_text_attributes_from_preferences (void) +{ +    static GQuark *attributes = NULL; + +    if (attributes == NULL) +    { +        eel_preferences_add_auto_string_array_as_quarks (CAJA_PREFERENCES_ICON_VIEW_CAPTIONS, +                &attributes); +    } + +    /* We don't need to sanity check the attributes list even though it came +     * from preferences. +     * +     * There are 2 ways that the values in the list could be bad. +     * +     * 1) The user picks "bad" values.  "bad" values are those that result in +     *    there being duplicate attributes in the list. +     * +     * 2) Value stored in MateConf are tampered with.  Its possible physically do +     *    this by pulling the rug underneath MateConf and manually editing its +     *    config files.  Its also possible to use a third party MateConf key +     *    editor and store garbage for the keys in question. +     * +     * Thankfully, the Caja preferences machinery deals with both of +     * these cases. +     * +     * In the first case, the preferences dialog widgetry prevents +     * duplicate attributes by making "bad" choices insensitive. +     * +     * In the second case, the preferences getter (and also the auto storage) for +     * string_array values are always valid members of the enumeration associated +     * with the preference. +     * +     * So, no more error checking on attributes is needed here and we can return +     * a the auto stored value. +     */ +    return attributes; +} + +static int +quarkv_length (GQuark *attributes) +{ +    int i; +    i = 0; +    while (attributes[i] != 0) +    { +        i++; +    } +    return i; +} + +/** + * fm_icon_view_get_icon_text_attribute_names: + * + * Get a list representing which text attributes should be displayed + * beneath an icon. The result is dependent on zoom level and possibly + * user configuration. Don't free the result. + * @view: FMIconView to query. + * + **/ +static GQuark * +fm_icon_container_get_icon_text_attribute_names (CajaIconContainer *container, +        int *len) +{ +    GQuark *attributes; +    int piece_count; + +    const int pieces_by_level[] = +    { +        0,	/* CAJA_ZOOM_LEVEL_SMALLEST */ +        0,	/* CAJA_ZOOM_LEVEL_SMALLER */ +        0,	/* CAJA_ZOOM_LEVEL_SMALL */ +        1,	/* CAJA_ZOOM_LEVEL_STANDARD */ +        2,	/* CAJA_ZOOM_LEVEL_LARGE */ +        2,	/* CAJA_ZOOM_LEVEL_LARGER */ +        3	/* CAJA_ZOOM_LEVEL_LARGEST */ +    }; + +    piece_count = pieces_by_level[caja_icon_container_get_zoom_level (container)]; + +    attributes = fm_icon_container_get_icon_text_attributes_from_preferences (); + +    *len = MIN (piece_count, quarkv_length (attributes)); + +    return attributes; +} + +/* This callback returns the text, both the editable part, and the + * part below that is not editable. + */ +static void +fm_icon_container_get_icon_text (CajaIconContainer *container, +                                 CajaIconData      *data, +                                 char                 **editable_text, +                                 char                 **additional_text, +                                 gboolean               include_invisible) +{ +    char *actual_uri; +    gchar *description; +    GQuark *attributes; +    char *text_array[4]; +    int i, j, num_attributes; +    FMIconView *icon_view; +    CajaFile *file; +    gboolean use_additional; + +    file = CAJA_FILE (data); + +    g_assert (CAJA_IS_FILE (file)); +    g_assert (editable_text != NULL); +    icon_view = get_icon_view (container); +    g_return_if_fail (icon_view != NULL); + +    use_additional = (additional_text != NULL); + +    /* In the smallest zoom mode, no text is drawn. */ +    if (caja_icon_container_get_zoom_level (container) == CAJA_ZOOM_LEVEL_SMALLEST && +            !include_invisible) +    { +        *editable_text = NULL; +    } +    else +    { +        /* Strip the suffix for caja object xml files. */ +        *editable_text = caja_file_get_display_name (file); +    } + +    if (!use_additional) +    { +        return; +    } + +    if (fm_icon_view_is_compact (icon_view)) +    { +        *additional_text = NULL; +        return; +    } + +    if (CAJA_IS_DESKTOP_ICON_FILE (file)) +    { +        /* Don't show the normal extra information for desktop icons, it doesn't +         * make sense. */ +        *additional_text = NULL; +        return; +    } + +    /* Handle link files specially. */ +    if (caja_file_is_caja_link (file)) +    { +        /* FIXME bugzilla.gnome.org 42531: Does sync. I/O and works only locally. */ +        *additional_text = NULL; +        if (caja_file_is_local (file)) +        { +            actual_uri = caja_file_get_uri (file); +            description = caja_link_local_get_additional_text (actual_uri); +            if (description) +                *additional_text = g_strdup_printf (" \n%s\n ", description); +            g_free (description); +            g_free (actual_uri); +        } +        /* Don't show the normal extra information for desktop files, it doesn't +         * make sense. */ +        return; +    } + +    /* Find out what attributes go below each icon. */ +    attributes = fm_icon_container_get_icon_text_attribute_names (container, +                 &num_attributes); + +    /* Get the attributes. */ +    j = 0; +    for (i = 0; i < num_attributes; ++i) +    { +        if (attributes[i] == attribute_none_q) +        { +            continue; +        } + +        text_array[j++] = +            caja_file_get_string_attribute_with_default_q (file, attributes[i]); +    } +    text_array[j] = NULL; + +    /* Return them. */ +    if (j == 0) +    { +        *additional_text = NULL; +    } +    else if (j == 1) +    { +        /* Only one item, avoid the strdup + free */ +        *additional_text = text_array[0]; +    } +    else +    { +        *additional_text = g_strjoinv ("\n", text_array); + +        for (i = 0; i < j; i++) +        { +            g_free (text_array[i]); +        } +    } +} + +/* Sort as follows: + *   0) computer link + *   1) home link + *   2) network link + *   3) mount links + *   4) other + *   5) trash link + */ +typedef enum +{ +    SORT_COMPUTER_LINK, +    SORT_HOME_LINK, +    SORT_NETWORK_LINK, +    SORT_MOUNT_LINK, +    SORT_OTHER, +    SORT_TRASH_LINK +} SortCategory; + +static SortCategory +get_sort_category (CajaFile *file) +{ +    CajaDesktopLink *link; +    SortCategory category; + +    category = SORT_OTHER; + +    if (CAJA_IS_DESKTOP_ICON_FILE (file)) +    { +        link = caja_desktop_icon_file_get_link (CAJA_DESKTOP_ICON_FILE (file)); +        if (link != NULL) +        { +            switch (caja_desktop_link_get_link_type (link)) +            { +            case CAJA_DESKTOP_LINK_COMPUTER: +                category = SORT_COMPUTER_LINK; +                break; +            case CAJA_DESKTOP_LINK_HOME: +                category = SORT_HOME_LINK; +                break; +            case CAJA_DESKTOP_LINK_MOUNT: +                category = SORT_MOUNT_LINK; +                break; +            case CAJA_DESKTOP_LINK_TRASH: +                category = SORT_TRASH_LINK; +                break; +            case CAJA_DESKTOP_LINK_NETWORK: +                category = SORT_NETWORK_LINK; +                break; +            default: +                category = SORT_OTHER; +                break; +            } +            g_object_unref (link); +        } +    } + +    return category; +} + +static int +fm_desktop_icon_container_icons_compare (CajaIconContainer *container, +        CajaIconData      *data_a, +        CajaIconData      *data_b) +{ +    CajaFile *file_a; +    CajaFile *file_b; +    FMDirectoryView *directory_view; +    SortCategory category_a, category_b; + +    file_a = (CajaFile *) data_a; +    file_b = (CajaFile *) data_b; + +    directory_view = FM_DIRECTORY_VIEW (FM_ICON_CONTAINER (container)->view); +    g_return_val_if_fail (directory_view != NULL, 0); + +    category_a = get_sort_category (file_a); +    category_b = get_sort_category (file_b); + +    if (category_a == category_b) +    { +        return caja_file_compare_for_sort +               (file_a, file_b, CAJA_FILE_SORT_BY_DISPLAY_NAME, +                fm_directory_view_should_sort_directories_first (directory_view), +                FALSE); +    } + +    if (category_a < category_b) +    { +        return -1; +    } +    else +    { +        return +1; +    } +} + +static int +fm_icon_container_compare_icons (CajaIconContainer *container, +                                 CajaIconData      *icon_a, +                                 CajaIconData      *icon_b) +{ +    FMIconView *icon_view; + +    icon_view = get_icon_view (container); +    g_return_val_if_fail (icon_view != NULL, 0); + +    if (FM_ICON_CONTAINER (container)->sort_for_desktop) +    { +        return fm_desktop_icon_container_icons_compare +               (container, icon_a, icon_b); +    } + +    /* Type unsafe comparisons for performance */ +    return fm_icon_view_compare_files (icon_view, +                                       (CajaFile *)icon_a, +                                       (CajaFile *)icon_b); +} + +static int +fm_icon_container_compare_icons_by_name (CajaIconContainer *container, +        CajaIconData      *icon_a, +        CajaIconData      *icon_b) +{ +    return caja_file_compare_for_sort +           (CAJA_FILE (icon_a), +            CAJA_FILE (icon_b), +            CAJA_FILE_SORT_BY_DISPLAY_NAME, +            FALSE, FALSE); +} + +static void +fm_icon_container_freeze_updates (CajaIconContainer *container) +{ +    FMIconView *icon_view; +    icon_view = get_icon_view (container); +    g_return_if_fail (icon_view != NULL); +    fm_directory_view_freeze_updates (FM_DIRECTORY_VIEW (icon_view)); +} + +static void +fm_icon_container_unfreeze_updates (CajaIconContainer *container) +{ +    FMIconView *icon_view; +    icon_view = get_icon_view (container); +    g_return_if_fail (icon_view != NULL); +    fm_directory_view_unfreeze_updates (FM_DIRECTORY_VIEW (icon_view)); +} + +static void +fm_icon_container_dispose (GObject *object) +{ +    FMIconContainer *icon_container; + +    icon_container = FM_ICON_CONTAINER (object); + +    icon_container->view = NULL; + +    G_OBJECT_CLASS (fm_icon_container_parent_class)->dispose (object); +} + +static void +fm_icon_container_class_init (FMIconContainerClass *klass) +{ +    CajaIconContainerClass *ic_class; + +    ic_class = &klass->parent_class; + +    attribute_none_q = g_quark_from_static_string ("none"); + +    ic_class->get_icon_text = fm_icon_container_get_icon_text; +    ic_class->get_icon_images = fm_icon_container_get_icon_images; +    ic_class->get_icon_description = fm_icon_container_get_icon_description; +    ic_class->start_monitor_top_left = fm_icon_container_start_monitor_top_left; +    ic_class->stop_monitor_top_left = fm_icon_container_stop_monitor_top_left; +    ic_class->prioritize_thumbnailing = fm_icon_container_prioritize_thumbnailing; + +    ic_class->compare_icons = fm_icon_container_compare_icons; +    ic_class->compare_icons_by_name = fm_icon_container_compare_icons_by_name; +    ic_class->freeze_updates = fm_icon_container_freeze_updates; +    ic_class->unfreeze_updates = fm_icon_container_unfreeze_updates; + +    G_OBJECT_CLASS (klass)->dispose = fm_icon_container_dispose; +} + +static void +fm_icon_container_init (FMIconContainer *icon_container) +{ +} + +CajaIconContainer * +fm_icon_container_construct (FMIconContainer *icon_container, FMIconView *view) +{ +    AtkObject *atk_obj; + +    g_return_val_if_fail (FM_IS_ICON_VIEW (view), NULL); + +    icon_container->view = view; +    atk_obj = gtk_widget_get_accessible (GTK_WIDGET (icon_container)); +    atk_object_set_name (atk_obj, _("Icon View")); + +    return CAJA_ICON_CONTAINER (icon_container); +} + +CajaIconContainer * +fm_icon_container_new (FMIconView *view) +{ +    return fm_icon_container_construct +           (g_object_new (FM_TYPE_ICON_CONTAINER, NULL), +            view); +} + +void +fm_icon_container_set_sort_desktop (FMIconContainer *container, +                                    gboolean         desktop) +{ +    container->sort_for_desktop = desktop; +} diff --git a/src/file-manager/fm-icon-container.h b/src/file-manager/fm-icon-container.h new file mode 100644 index 00000000..a0527acf --- /dev/null +++ b/src/file-manager/fm-icon-container.h @@ -0,0 +1,68 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-icon-container.h - the container widget for file manager icons + +   Copyright (C) 2002 Sun Microsystems, Inc. + +   The Mate Library is free software; you can redistribute it and/or +   modify it under the terms of the GNU Library General Public License as +   published by the Free Software Foundation; either version 2 of the +   License, or (at your option) any later version. + +   The Mate Library is distributed in the hope that it will be useful, +   but WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +   Library General Public License for more details. + +   You should have received a copy of the GNU Library General Public +   License along with the Mate Library; see the file COPYING.LIB.  If not, +   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +   Boston, MA 02111-1307, USA. + +   Author: Michael Meeks <[email protected]> +*/ + +#ifndef FM_ICON_CONTAINER_H +#define FM_ICON_CONTAINER_H + +#include <libcaja-private/caja-icon-container.h> +#include "fm-icon-view.h" + +typedef struct FMIconContainer FMIconContainer; +typedef struct FMIconContainerClass FMIconContainerClass; + +#define FM_TYPE_ICON_CONTAINER fm_icon_container_get_type() +#define FM_ICON_CONTAINER(obj) \ +  (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_ICON_CONTAINER, FMIconContainer)) +#define FM_ICON_CONTAINER_CLASS(klass) \ +  (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_ICON_CONTAINER, FMIconContainerClass)) +#define FM_IS_ICON_CONTAINER(obj) \ +  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_ICON_CONTAINER)) +#define FM_IS_ICON_CONTAINER_CLASS(klass) \ +  (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_ICON_CONTAINER)) +#define FM_ICON_CONTAINER_GET_CLASS(obj) \ +  (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_ICON_CONTAINER, FMIconContainerClass)) + +typedef struct FMIconContainerDetails FMIconContainerDetails; + +struct FMIconContainer +{ +    CajaIconContainer parent; + +    FMIconView *view; +    gboolean    sort_for_desktop; +}; + +struct FMIconContainerClass +{ +    CajaIconContainerClass parent_class; +}; + +GType                  fm_icon_container_get_type         (void); +CajaIconContainer *fm_icon_container_construct        (FMIconContainer *icon_container, +        FMIconView      *view); +CajaIconContainer *fm_icon_container_new              (FMIconView      *view); +void                   fm_icon_container_set_sort_desktop (FMIconContainer *container, +        gboolean         desktop); + +#endif /* FM_ICON_CONTAINER_H */ diff --git a/src/file-manager/fm-icon-view.c b/src/file-manager/fm-icon-view.c new file mode 100644 index 00000000..7d8687a2 --- /dev/null +++ b/src/file-manager/fm-icon-view.c @@ -0,0 +1,3439 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-icon-view.c - implementation of icon view of directory. + +   Copyright (C) 2000, 2001 Eazel, Inc. + +   The Mate Library is free software; you can redistribute it and/or +   modify it under the terms of the GNU Library General Public License as +   published by the Free Software Foundation; either version 2 of the +   License, or (at your option) any later version. + +   The Mate Library is distributed in the hope that it will be useful, +   but WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +   Library General Public License for more details. + +   You should have received a copy of the GNU Library General Public +   License along with the Mate Library; see the file COPYING.LIB.  If not, +   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +   Boston, MA 02111-1307, USA. + +   Authors: John Sullivan <[email protected]> +*/ + +#include <config.h> +#include "fm-icon-view.h" + +#include "fm-actions.h" +#include "fm-icon-container.h" +#include "fm-desktop-icon-view.h" +#include "fm-error-reporting.h" +#include <stdlib.h> +#include <eel/eel-background.h> +#include <eel/eel-glib-extensions.h> +#include <eel/eel-gtk-extensions.h> +#include <eel/eel-gtk-macros.h> +#include <eel/eel-stock-dialogs.h> +#include <eel/eel-string.h> +#include <eel/eel-vfs-extensions.h> +#include <errno.h> +#include <fcntl.h> +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <gio/gio.h> +#include <libcaja-private/caja-clipboard-monitor.h> +#include <libcaja-private/caja-directory-background.h> +#include <libcaja-private/caja-directory.h> +#include <libcaja-private/caja-dnd.h> +#include <libcaja-private/caja-file-utilities.h> +#include <libcaja-private/caja-ui-utilities.h> +#include <libcaja-private/caja-global-preferences.h> +#include <libcaja-private/caja-icon-container.h> +#include <libcaja-private/caja-icon-dnd.h> +#include <libcaja-private/caja-link.h> +#include <libcaja-private/caja-metadata.h> +#include <libcaja-private/caja-view-factory.h> +#include <libcaja-private/caja-clipboard.h> +#include <libcaja-private/caja-desktop-icon-file.h> +#include <locale.h> +#include <signal.h> +#include <stdio.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "caja-audio-mime-types.h" + +#define POPUP_PATH_ICON_APPEARANCE		"/selection/Icon Appearance Items" + +enum +{ +    PROP_0, +    PROP_COMPACT +}; + +typedef struct +{ +    const CajaFileSortType sort_type; +    const char *metadata_text; +    const char *action; +    const char *menu_label; +    const char *menu_hint; +} SortCriterion; + +typedef enum +{ +    MENU_ITEM_TYPE_STANDARD, +    MENU_ITEM_TYPE_CHECK, +    MENU_ITEM_TYPE_RADIO, +    MENU_ITEM_TYPE_TREE +} MenuItemType; + +struct FMIconViewDetails +{ +    GList *icons_not_positioned; + +    guint react_to_icon_change_idle_id; + +    const SortCriterion *sort; +    gboolean sort_reversed; + +    GtkActionGroup *icon_action_group; +    guint icon_merge_id; + +    int audio_preview_timeout; +    CajaFile *audio_preview_file; +    int audio_preview_child_watch; +    GPid audio_preview_child_pid; + +    gboolean filter_by_screen; +    int num_screens; + +    gboolean compact; + +    gulong clipboard_handler_id; +}; + + +/* Note that the first item in this list is the default sort, + * and that the items show up in the menu in the order they + * appear in this list. + */ +static const SortCriterion sort_criteria[] = +{ +    { +        CAJA_FILE_SORT_BY_DISPLAY_NAME, +        "name", +        "Sort by Name", +        N_("by _Name"), +        N_("Keep icons sorted by name in rows") +    }, +    { +        CAJA_FILE_SORT_BY_SIZE, +        "size", +        "Sort by Size", +        N_("by _Size"), +        N_("Keep icons sorted by size in rows") +    }, +    { +        CAJA_FILE_SORT_BY_TYPE, +        "type", +        "Sort by Type", +        N_("by _Type"), +        N_("Keep icons sorted by type in rows") +    }, +    { +        CAJA_FILE_SORT_BY_MTIME, +        "modification date", +        "Sort by Modification Date", +        N_("by Modification _Date"), +        N_("Keep icons sorted by modification date in rows") +    }, +    { +        CAJA_FILE_SORT_BY_EMBLEMS, +        "emblems", +        "Sort by Emblems", +        N_("by _Emblems"), +        N_("Keep icons sorted by emblems in rows") +    }, +    { +        CAJA_FILE_SORT_BY_TRASHED_TIME, +        "trashed", +        "Sort by Trash Time", +        N_("by T_rash Time"), +        N_("Keep icons sorted by trash time in rows") +    } +}; + +static gboolean default_sort_in_reverse_order = FALSE; +static int preview_sound_auto_value; + +static void                 fm_icon_view_set_directory_sort_by        (FMIconView           *icon_view, +        CajaFile         *file, +        const char           *sort_by); +static void                 fm_icon_view_set_zoom_level               (FMIconView           *view, +        CajaZoomLevel     new_level, +        gboolean              always_emit); +static void                 fm_icon_view_update_click_mode            (FMIconView           *icon_view); +static void                 fm_icon_view_set_directory_tighter_layout (FMIconView           *icon_view, +        CajaFile         *file, +        gboolean              tighter_layout); +static gboolean             fm_icon_view_supports_manual_layout       (FMIconView           *icon_view); +static gboolean             fm_icon_view_supports_scaling	      (FMIconView           *icon_view); +static void                 fm_icon_view_reveal_selection             (FMDirectoryView      *view); +static const SortCriterion *get_sort_criterion_by_sort_type           (CajaFileSortType  sort_type); +static void                 set_sort_criterion_by_sort_type           (FMIconView           *icon_view, +        CajaFileSortType  sort_type); +static gboolean             set_sort_reversed                         (FMIconView           *icon_view, +        gboolean              new_value); +static void                 switch_to_manual_layout                   (FMIconView           *view); +static void                 preview_audio                             (FMIconView           *icon_view, +        CajaFile         *file, +        gboolean              start_flag); +static void                 update_layout_menus                       (FMIconView           *view); +static CajaFileSortType get_default_sort_order                    (CajaFile         *file, +        gboolean             *reversed); + + +static void fm_icon_view_iface_init (CajaViewIface *iface); + +G_DEFINE_TYPE_WITH_CODE (FMIconView, fm_icon_view, FM_TYPE_DIRECTORY_VIEW, +                         G_IMPLEMENT_INTERFACE (CAJA_TYPE_VIEW, +                                 fm_icon_view_iface_init)); + +static void +fm_icon_view_destroy (GtkObject *object) +{ +    FMIconView *icon_view; + +    icon_view = FM_ICON_VIEW (object); + +    if (icon_view->details->react_to_icon_change_idle_id != 0) +    { +        g_source_remove (icon_view->details->react_to_icon_change_idle_id); +        icon_view->details->react_to_icon_change_idle_id = 0; +    } + +    if (icon_view->details->clipboard_handler_id != 0) +    { +        g_signal_handler_disconnect (caja_clipboard_monitor_get (), +                                     icon_view->details->clipboard_handler_id); +        icon_view->details->clipboard_handler_id = 0; +    } + +    /* kill any sound preview process that is ongoing */ +    preview_audio (icon_view, NULL, FALSE); + +    if (icon_view->details->icons_not_positioned) +    { +        caja_file_list_free (icon_view->details->icons_not_positioned); +        icon_view->details->icons_not_positioned = NULL; +    } + +    GTK_OBJECT_CLASS (fm_icon_view_parent_class)->destroy (object); +} + + +static void +fm_icon_view_finalize (GObject *object) +{ +    FMIconView *icon_view; + +    icon_view = FM_ICON_VIEW (object); + +    g_free (icon_view->details); + +    G_OBJECT_CLASS (fm_icon_view_parent_class)->finalize (object); +} + +static CajaIconContainer * +get_icon_container (FMIconView *icon_view) +{ +    return CAJA_ICON_CONTAINER (gtk_bin_get_child (GTK_BIN (icon_view))); +} + +static gboolean +get_stored_icon_position_callback (CajaIconContainer *container, +                                   CajaFile *file, +                                   CajaIconPosition *position, +                                   FMIconView *icon_view) +{ +    char *position_string, *scale_string; +    gboolean position_good; +    char c; + +    g_assert (CAJA_IS_ICON_CONTAINER (container)); +    g_assert (CAJA_IS_FILE (file)); +    g_assert (position != NULL); +    g_assert (FM_IS_ICON_VIEW (icon_view)); + +    if (!fm_icon_view_supports_manual_layout (icon_view)) +    { +        return FALSE; +    } + +    /* Get the current position of this icon from the metadata. */ +    position_string = caja_file_get_metadata +                      (file, CAJA_METADATA_KEY_ICON_POSITION, ""); +    position_good = sscanf +                    (position_string, " %d , %d %c", +                     &position->x, &position->y, &c) == 2; +    g_free (position_string); + +    /* If it is the desktop directory, maybe the mate-libs metadata has information about it */ + +    /* Disable scaling if not on the desktop */ +    if (fm_icon_view_supports_scaling (icon_view)) +    { +        /* Get the scale of the icon from the metadata. */ +        scale_string = caja_file_get_metadata +                       (file, CAJA_METADATA_KEY_ICON_SCALE, "1"); +        position->scale = g_ascii_strtod (scale_string, NULL); +        if (errno != 0) +        { +            position->scale = 1.0; +        } + +        g_free (scale_string); +    } +    else +    { +        position->scale = 1.0; +    } + +    return position_good; +} + +static void +real_set_sort_criterion (FMIconView *icon_view, +                         const SortCriterion *sort, +                         gboolean clear) +{ +    CajaFile *file; + +    file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (icon_view)); + +    if (clear) +    { +        caja_file_set_metadata (file, +                                CAJA_METADATA_KEY_ICON_VIEW_SORT_BY, NULL, NULL); +        caja_file_set_metadata (file, +                                CAJA_METADATA_KEY_ICON_VIEW_SORT_REVERSED, NULL, NULL); +        icon_view->details->sort = +            get_sort_criterion_by_sort_type	(get_default_sort_order +                                             (file, &icon_view->details->sort_reversed)); +    } +    else +    { +        /* Store the new sort setting. */ +        fm_icon_view_set_directory_sort_by (icon_view, +                                            file, +                                            sort->metadata_text); +    } + +    /* Update the layout menus to match the new sort setting. */ +    update_layout_menus (icon_view); +} + +static void +set_sort_criterion (FMIconView *icon_view, const SortCriterion *sort) +{ +    if (sort == NULL || +            icon_view->details->sort == sort) +    { +        return; +    } + +    icon_view->details->sort = sort; + +    real_set_sort_criterion (icon_view, sort, FALSE); +} + +static void +clear_sort_criterion (FMIconView *icon_view) +{ +    real_set_sort_criterion (icon_view, NULL, TRUE); +} + +static void +action_stretch_callback (GtkAction *action, +                         gpointer callback_data) +{ +    g_assert (FM_IS_ICON_VIEW (callback_data)); + +    caja_icon_container_show_stretch_handles +    (get_icon_container (FM_ICON_VIEW (callback_data))); +} + +static void +action_unstretch_callback (GtkAction *action, +                           gpointer callback_data) +{ +    g_assert (FM_IS_ICON_VIEW (callback_data)); + +    caja_icon_container_unstretch +    (get_icon_container (FM_ICON_VIEW (callback_data))); +} + +static void +fm_icon_view_clean_up (FMIconView *icon_view) +{ +    EEL_CALL_METHOD (FM_ICON_VIEW_CLASS, icon_view, clean_up, (icon_view)); +} + +static void +fm_icon_view_real_clean_up (FMIconView *icon_view) +{ +    CajaIconContainer *icon_container; +    gboolean saved_sort_reversed; + +    icon_container = get_icon_container (icon_view); + +    /* Hardwire Clean Up to always be by name, in forward order */ +    saved_sort_reversed = icon_view->details->sort_reversed; + +    set_sort_reversed (icon_view, FALSE); +    set_sort_criterion (icon_view, &sort_criteria[0]); + +    caja_icon_container_sort (icon_container); +    caja_icon_container_freeze_icon_positions (icon_container); + +    set_sort_reversed (icon_view, saved_sort_reversed); +} + +static void +action_clean_up_callback (GtkAction *action, gpointer callback_data) +{ +    fm_icon_view_clean_up (FM_ICON_VIEW (callback_data)); +} + +static void +set_tighter_layout (FMIconView *icon_view, gboolean new_value) +{ +    fm_icon_view_set_directory_tighter_layout (icon_view, +            fm_directory_view_get_directory_as_file +            (FM_DIRECTORY_VIEW (icon_view)), +            new_value); +    caja_icon_container_set_tighter_layout (get_icon_container (icon_view), +                                            new_value); +} + +static void +action_tighter_layout_callback (GtkAction *action, +                                gpointer user_data) +{ +    g_assert (FM_IS_ICON_VIEW (user_data)); + +    set_tighter_layout (FM_ICON_VIEW (user_data), +                        gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))); +} + + +static gboolean +fm_icon_view_using_auto_layout (FMIconView *icon_view) +{ +    return caja_icon_container_is_auto_layout +           (get_icon_container (icon_view)); +} + +static gboolean +fm_icon_view_using_tighter_layout (FMIconView *icon_view) +{ +    return caja_icon_container_is_tighter_layout +           (get_icon_container (icon_view)); +} + +static void +action_sort_radio_callback (GtkAction *action, +                            GtkRadioAction *current, +                            FMIconView *view) +{ +    CajaFileSortType sort_type; + +    sort_type = gtk_radio_action_get_current_value (current); + +    /* Note that id might be a toggle item. +     * Ignore non-sort ids so that they don't cause sorting. +     */ +    if (sort_type == CAJA_FILE_SORT_NONE) +    { +        switch_to_manual_layout (view); +    } +    else +    { +        set_sort_criterion_by_sort_type (view, sort_type); +    } +} + +static void +list_covers (CajaIconData *data, gpointer callback_data) +{ +    GSList **file_list; + +    file_list = callback_data; + +    *file_list = g_slist_prepend (*file_list, data); +} + +static void +unref_cover (CajaIconData *data, gpointer callback_data) +{ +    caja_file_unref (CAJA_FILE (data)); +} + +static void +fm_icon_view_clear (FMDirectoryView *view) +{ +    CajaIconContainer *icon_container; +    GSList *file_list; + +    g_return_if_fail (FM_IS_ICON_VIEW (view)); + +    icon_container = get_icon_container (FM_ICON_VIEW (view)); +    if (!icon_container) +        return; + +    /* Clear away the existing icons. */ +    file_list = NULL; +    caja_icon_container_for_each (icon_container, list_covers, &file_list); +    caja_icon_container_clear (icon_container); +    g_slist_foreach (file_list, (GFunc)unref_cover, NULL); +    g_slist_free (file_list); +} + + +static gboolean +should_show_file_on_screen (FMDirectoryView *view, CajaFile *file) +{ +    char *screen_string; +    int screen_num; +    FMIconView *icon_view; +    GdkScreen *screen; + +    icon_view = FM_ICON_VIEW (view); + +    if (!fm_directory_view_should_show_file (view, file)) +    { +        return FALSE; +    } + +    /* Get the screen for this icon from the metadata. */ +    screen_string = caja_file_get_metadata +                    (file, CAJA_METADATA_KEY_SCREEN, "0"); +    screen_num = atoi (screen_string); +    g_free (screen_string); +    screen = gtk_widget_get_screen (GTK_WIDGET (view)); + +    if (screen_num != gdk_screen_get_number (screen) && +            (screen_num < icon_view->details->num_screens || +             gdk_screen_get_number (screen) > 0)) +    { +        return FALSE; +    } + +    return TRUE; +} + +static void +fm_icon_view_remove_file (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory) +{ +    FMIconView *icon_view; + +    /* This used to assert that 'directory == fm_directory_view_get_model (view)', but that +     * resulted in a lot of crash reports (bug #352592). I don't see how that trace happens. +     * It seems that somehow we get a files_changed event sent to the view from a directory +     * that isn't the model, but the code disables the monitor and signal callback handlers when +     * changing directories. Maybe we can get some more information when this happens. +     * Further discussion in bug #368178. +     */ +    if (directory != fm_directory_view_get_model (view)) +    { +        char *file_uri, *dir_uri, *model_uri; +        file_uri = caja_file_get_uri (file); +        dir_uri = caja_directory_get_uri (directory); +        model_uri = caja_directory_get_uri (fm_directory_view_get_model (view)); +        g_warning ("fm_icon_view_remove_file() - directory not icon view model, shouldn't happen.\n" +                   "file: %p:%s, dir: %p:%s, model: %p:%s, view loading: %d\n" +                   "If you see this, please add this info to http://bugzilla.gnome.org/show_bug.cgi?id=368178", +                   file, file_uri, directory, dir_uri, fm_directory_view_get_model (view), model_uri, fm_directory_view_get_loading (view)); +        g_free (file_uri); +        g_free (dir_uri); +        g_free (model_uri); +    } + +    icon_view = FM_ICON_VIEW (view); + +    if (caja_icon_container_remove (get_icon_container (icon_view), +                                    CAJA_ICON_CONTAINER_ICON_DATA (file))) +    { +        if (file == icon_view->details->audio_preview_file) +        { +            preview_audio (icon_view, NULL, FALSE); +        } + +        caja_file_unref (file); +    } +} + +static void +fm_icon_view_add_file (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory) +{ +    FMIconView *icon_view; +    CajaIconContainer *icon_container; + +    g_assert (directory == fm_directory_view_get_model (view)); + +    icon_view = FM_ICON_VIEW (view); +    icon_container = get_icon_container (icon_view); + +    if (icon_view->details->filter_by_screen && +            !should_show_file_on_screen (view, file)) +    { +        return; +    } + +    /* Reset scroll region for the first icon added when loading a directory. */ +    if (fm_directory_view_get_loading (view) && caja_icon_container_is_empty (icon_container)) +    { +        caja_icon_container_reset_scroll_region (icon_container); +    } + +    if (caja_icon_container_add (icon_container, +                                 CAJA_ICON_CONTAINER_ICON_DATA (file))) +    { +        caja_file_ref (file); +    } +} + +static void +fm_icon_view_flush_added_files (FMDirectoryView *view) +{ +    caja_icon_container_layout_now (get_icon_container (FM_ICON_VIEW (view))); +} + +static void +fm_icon_view_file_changed (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory) +{ +    FMIconView *icon_view; + +    g_assert (directory == fm_directory_view_get_model (view)); + +    g_return_if_fail (view != NULL); +    icon_view = FM_ICON_VIEW (view); + +    if (!icon_view->details->filter_by_screen) +    { +        caja_icon_container_request_update +        (get_icon_container (icon_view), +         CAJA_ICON_CONTAINER_ICON_DATA (file)); +        return; +    } + +    if (!should_show_file_on_screen (view, file)) +    { +        fm_icon_view_remove_file (view, file, directory); +    } +    else +    { + +        caja_icon_container_request_update +        (get_icon_container (icon_view), +         CAJA_ICON_CONTAINER_ICON_DATA (file)); +    } +} + +static gboolean +fm_icon_view_supports_auto_layout (FMIconView *view) +{ +    g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE); + +    return EEL_CALL_METHOD_WITH_RETURN_VALUE +           (FM_ICON_VIEW_CLASS, view, +            supports_auto_layout, (view)); +} + +static gboolean +fm_icon_view_supports_scaling (FMIconView *view) +{ +    g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE); + +    return EEL_CALL_METHOD_WITH_RETURN_VALUE +           (FM_ICON_VIEW_CLASS, view, +            supports_scaling, (view)); +} + +static gboolean +fm_icon_view_supports_manual_layout (FMIconView *view) +{ +    g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE); + +    return EEL_CALL_METHOD_WITH_RETURN_VALUE +           (FM_ICON_VIEW_CLASS, view, +            supports_manual_layout, (view)); +} + +static gboolean +fm_icon_view_supports_keep_aligned (FMIconView *view) +{ +    g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE); + +    return EEL_CALL_METHOD_WITH_RETURN_VALUE +           (FM_ICON_VIEW_CLASS, view, +            supports_keep_aligned, (view)); +} + +static gboolean +fm_icon_view_supports_labels_beside_icons (FMIconView *view) +{ +    g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE); + +    return EEL_CALL_METHOD_WITH_RETURN_VALUE +           (FM_ICON_VIEW_CLASS, view, +            supports_labels_beside_icons, (view)); +} + +static gboolean +fm_icon_view_supports_tighter_layout (FMIconView *view) +{ +    return !fm_icon_view_is_compact (view); +} + +static void +update_layout_menus (FMIconView *view) +{ +    gboolean is_auto_layout; +    GtkAction *action; +    const char *action_name; +    CajaFile *file; + +    if (view->details->icon_action_group == NULL) +    { +        return; +    } + +    is_auto_layout = fm_icon_view_using_auto_layout (view); +    file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (view)); + +    if (fm_icon_view_supports_auto_layout (view)) +    { +        /* Mark sort criterion. */ +        action_name = is_auto_layout ? view->details->sort->action : FM_ACTION_MANUAL_LAYOUT; +        action = gtk_action_group_get_action (view->details->icon_action_group, +                                              action_name); +        gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE); + +        action = gtk_action_group_get_action (view->details->icon_action_group, +                                              FM_ACTION_TIGHTER_LAYOUT); +        gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), +                                      fm_icon_view_using_tighter_layout (view)); +        gtk_action_set_sensitive (action, fm_icon_view_supports_tighter_layout (view)); +        gtk_action_set_visible (action, fm_icon_view_supports_tighter_layout (view)); + +        action = gtk_action_group_get_action (view->details->icon_action_group, +                                              FM_ACTION_REVERSED_ORDER); +        gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), +                                      view->details->sort_reversed); +        gtk_action_set_sensitive (action, is_auto_layout); + +        action = gtk_action_group_get_action (view->details->icon_action_group, +                                              FM_ACTION_SORT_TRASH_TIME); + +        if (file != NULL && caja_file_is_in_trash (file)) +        { +            gtk_action_set_visible (action, TRUE); +        } +        else +        { +            gtk_action_set_visible (action, FALSE); +        } +    } + +    action = gtk_action_group_get_action (view->details->icon_action_group, +                                          FM_ACTION_MANUAL_LAYOUT); +    gtk_action_set_visible (action, +                            fm_icon_view_supports_manual_layout (view)); + +    /* Clean Up is only relevant for manual layout */ +    action = gtk_action_group_get_action (view->details->icon_action_group, +                                          FM_ACTION_CLEAN_UP); +    gtk_action_set_sensitive (action, !is_auto_layout); + +    if (FM_IS_DESKTOP_ICON_VIEW (view)) +    { +        gtk_action_set_label (action, _("_Organize Desktop by Name")); +    } + +    action = gtk_action_group_get_action (view->details->icon_action_group, +                                          FM_ACTION_KEEP_ALIGNED); +    gtk_action_set_visible (action, +                            fm_icon_view_supports_keep_aligned (view)); +    gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), +                                  caja_icon_container_is_keep_aligned (get_icon_container (view))); +    gtk_action_set_sensitive (action, !is_auto_layout); +} + + +static char * +fm_icon_view_get_directory_sort_by (FMIconView *icon_view, +                                    CajaFile *file) +{ +    if (!fm_icon_view_supports_auto_layout (icon_view)) +    { +        return g_strdup ("name"); +    } + +    return EEL_CALL_METHOD_WITH_RETURN_VALUE +           (FM_ICON_VIEW_CLASS, icon_view, +            get_directory_sort_by, (icon_view, file)); +} + +static CajaFileSortType default_sort_order = CAJA_FILE_SORT_BY_DISPLAY_NAME; + +static CajaFileSortType +get_default_sort_order (CajaFile *file, gboolean *reversed) +{ +    static gboolean auto_storaged_added = FALSE; +    CajaFileSortType retval; + +    if (auto_storaged_added == FALSE) +    { +        auto_storaged_added = TRUE; +        eel_preferences_add_auto_enum (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_SORT_ORDER, +                                       (int *) &default_sort_order); +        eel_preferences_add_auto_boolean (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_SORT_IN_REVERSE_ORDER, +                                          &default_sort_in_reverse_order); + +    } + +    retval = caja_file_get_default_sort_type (file, reversed); + +    if (retval == CAJA_FILE_SORT_NONE) +    { + +        if (reversed != NULL) +        { +            *reversed = default_sort_in_reverse_order; +        } + +        retval = CLAMP (default_sort_order, CAJA_FILE_SORT_BY_DISPLAY_NAME, +                        CAJA_FILE_SORT_BY_EMBLEMS); +    } + +    return retval; +} + +static char * +fm_icon_view_real_get_directory_sort_by (FMIconView *icon_view, +        CajaFile *file) +{ +    const SortCriterion *default_sort_criterion; +    default_sort_criterion = get_sort_criterion_by_sort_type (get_default_sort_order (file, NULL)); +    g_return_val_if_fail (default_sort_criterion != NULL, NULL); + +    return caja_file_get_metadata +           (file, CAJA_METADATA_KEY_ICON_VIEW_SORT_BY, +            default_sort_criterion->metadata_text); +} + +static void +fm_icon_view_set_directory_sort_by (FMIconView *icon_view, +                                    CajaFile *file, +                                    const char *sort_by) +{ +    if (!fm_icon_view_supports_auto_layout (icon_view)) +    { +        return; +    } + +    EEL_CALL_METHOD (FM_ICON_VIEW_CLASS, icon_view, +                     set_directory_sort_by, (icon_view, file, sort_by)); +} + +static void +fm_icon_view_real_set_directory_sort_by (FMIconView *icon_view, +        CajaFile *file, +        const char *sort_by) +{ +    const SortCriterion *default_sort_criterion; +    default_sort_criterion = get_sort_criterion_by_sort_type (get_default_sort_order (file, NULL)); +    g_return_if_fail (default_sort_criterion != NULL); + +    caja_file_set_metadata +    (file, CAJA_METADATA_KEY_ICON_VIEW_SORT_BY, +     default_sort_criterion->metadata_text, +     sort_by); +} + +static gboolean +fm_icon_view_get_directory_sort_reversed (FMIconView *icon_view, +        CajaFile *file) +{ +    if (!fm_icon_view_supports_auto_layout (icon_view)) +    { +        return FALSE; +    } + +    return EEL_CALL_METHOD_WITH_RETURN_VALUE +           (FM_ICON_VIEW_CLASS, icon_view, +            get_directory_sort_reversed, (icon_view, file)); +} + +static gboolean +fm_icon_view_real_get_directory_sort_reversed (FMIconView *icon_view, +        CajaFile *file) +{ +    gboolean reversed; + +    get_default_sort_order (file, &reversed); +    return caja_file_get_boolean_metadata +           (file, +            CAJA_METADATA_KEY_ICON_VIEW_SORT_REVERSED, +            reversed); +} + +static void +fm_icon_view_set_directory_sort_reversed (FMIconView *icon_view, +        CajaFile *file, +        gboolean sort_reversed) +{ +    if (!fm_icon_view_supports_auto_layout (icon_view)) +    { +        return; +    } + +    EEL_CALL_METHOD (FM_ICON_VIEW_CLASS, icon_view, +                     set_directory_sort_reversed, +                     (icon_view, file, sort_reversed)); +} + +static void +fm_icon_view_real_set_directory_sort_reversed (FMIconView *icon_view, +        CajaFile *file, +        gboolean sort_reversed) +{ +    gboolean reversed; + +    get_default_sort_order (file, &reversed); +    caja_file_set_boolean_metadata +    (file, +     CAJA_METADATA_KEY_ICON_VIEW_SORT_REVERSED, +     reversed, sort_reversed); +} + +static gboolean +get_default_directory_keep_aligned (void) +{ +    return TRUE; +} + +static gboolean +fm_icon_view_get_directory_keep_aligned (FMIconView *icon_view, +        CajaFile *file) +{ +    if (!fm_icon_view_supports_keep_aligned (icon_view)) +    { +        return FALSE; +    } + +    return  caja_file_get_boolean_metadata +            (file, +             CAJA_METADATA_KEY_ICON_VIEW_KEEP_ALIGNED, +             get_default_directory_keep_aligned ()); +} + +static void +fm_icon_view_set_directory_keep_aligned (FMIconView *icon_view, +        CajaFile *file, +        gboolean keep_aligned) +{ +    if (!fm_icon_view_supports_keep_aligned (icon_view)) +    { +        return; +    } + +    caja_file_set_boolean_metadata +    (file, CAJA_METADATA_KEY_ICON_VIEW_KEEP_ALIGNED, +     get_default_directory_keep_aligned (), +     keep_aligned); +} + +/* maintainence of auto layout boolean */ +static gboolean default_directory_manual_layout = FALSE; + +static gboolean +get_default_directory_manual_layout (void) +{ +    static gboolean auto_storaged_added = FALSE; + +    if (auto_storaged_added == FALSE) +    { +        auto_storaged_added = TRUE; +        eel_preferences_add_auto_boolean (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_USE_MANUAL_LAYOUT, +                                          &default_directory_manual_layout); +    } + +    return default_directory_manual_layout; +} + +static gboolean +fm_icon_view_get_directory_auto_layout (FMIconView *icon_view, +                                        CajaFile *file) +{ +    if (!fm_icon_view_supports_auto_layout (icon_view)) +    { +        return FALSE; +    } + +    if (!fm_icon_view_supports_manual_layout (icon_view)) +    { +        return TRUE; +    } + +    return EEL_CALL_METHOD_WITH_RETURN_VALUE +           (FM_ICON_VIEW_CLASS, icon_view, +            get_directory_auto_layout, (icon_view, file)); +} + +static gboolean +fm_icon_view_real_get_directory_auto_layout (FMIconView *icon_view, +        CajaFile *file) +{ + + +    return caja_file_get_boolean_metadata +           (file, CAJA_METADATA_KEY_ICON_VIEW_AUTO_LAYOUT, !get_default_directory_manual_layout ()); +} + +static void +fm_icon_view_set_directory_auto_layout (FMIconView *icon_view, +                                        CajaFile *file, +                                        gboolean auto_layout) +{ +    if (!fm_icon_view_supports_auto_layout (icon_view) || +            !fm_icon_view_supports_manual_layout (icon_view)) +    { +        return; +    } + +    EEL_CALL_METHOD (FM_ICON_VIEW_CLASS, icon_view, +                     set_directory_auto_layout, (icon_view, file, auto_layout)); +} + +static void +fm_icon_view_real_set_directory_auto_layout (FMIconView *icon_view, +        CajaFile *file, +        gboolean auto_layout) +{ +    if (!fm_icon_view_supports_manual_layout (icon_view)) +    { +        return; +    } + +    caja_file_set_boolean_metadata +    (file, CAJA_METADATA_KEY_ICON_VIEW_AUTO_LAYOUT, +     !get_default_directory_manual_layout (), +     auto_layout); +} +/* maintainence of tighter layout boolean */ + +static gboolean +fm_icon_view_get_directory_tighter_layout (FMIconView *icon_view, +        CajaFile *file) +{ +    return EEL_CALL_METHOD_WITH_RETURN_VALUE +           (FM_ICON_VIEW_CLASS, icon_view, +            get_directory_tighter_layout, (icon_view, file)); +} + +static gboolean default_directory_tighter_layout = FALSE; + +static gboolean +get_default_directory_tighter_layout (void) +{ +    static gboolean auto_storaged_added = FALSE; + +    if (auto_storaged_added == FALSE) +    { +        auto_storaged_added = TRUE; +        eel_preferences_add_auto_boolean (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_USE_TIGHTER_LAYOUT, +                                          &default_directory_tighter_layout); +    } + +    return default_directory_tighter_layout; +} + +static gboolean +fm_icon_view_real_get_directory_tighter_layout (FMIconView *icon_view, +        CajaFile *file) +{ +    if (!fm_icon_view_supports_tighter_layout (icon_view)) +    { +        return FALSE; +    } + +    return caja_file_get_boolean_metadata +           (file, +            CAJA_METADATA_KEY_ICON_VIEW_TIGHTER_LAYOUT, +            get_default_directory_tighter_layout ()); +} + +static void +fm_icon_view_set_directory_tighter_layout (FMIconView *icon_view, +        CajaFile *file, +        gboolean tighter_layout) +{ +    EEL_CALL_METHOD (FM_ICON_VIEW_CLASS, icon_view, +                     set_directory_tighter_layout, (icon_view, file, tighter_layout)); +} + +static void +fm_icon_view_real_set_directory_tighter_layout (FMIconView *icon_view, +        CajaFile *file, +        gboolean tighter_layout) +{ +    if (!fm_icon_view_supports_tighter_layout (icon_view)) +    { +        return; +    } + +    caja_file_set_boolean_metadata +    (file, CAJA_METADATA_KEY_ICON_VIEW_TIGHTER_LAYOUT, +     get_default_directory_tighter_layout (), +     tighter_layout); +} + +static gboolean +real_supports_auto_layout (FMIconView *view) +{ +    g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE); + +    return TRUE; +} + +static gboolean +real_supports_scaling (FMIconView *view) +{ +    g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE); + +    return FALSE; +} + +static gboolean +real_supports_manual_layout (FMIconView *view) +{ +    g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE); + +    return !fm_icon_view_is_compact (view); +} + +static gboolean +real_supports_keep_aligned (FMIconView *view) +{ +    g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE); + +    return FALSE; +} + +static gboolean +real_supports_labels_beside_icons (FMIconView *view) +{ +    g_return_val_if_fail (FM_IS_ICON_VIEW (view), TRUE); + +    return TRUE; +} + +static gboolean +set_sort_reversed (FMIconView *icon_view, gboolean new_value) +{ +    if (icon_view->details->sort_reversed == new_value) +    { +        return FALSE; +    } +    icon_view->details->sort_reversed = new_value; + +    /* Store the new sort setting. */ +    fm_icon_view_set_directory_sort_reversed (icon_view, fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (icon_view)), new_value); + +    /* Update the layout menus to match the new sort-order setting. */ +    update_layout_menus (icon_view); + +    return TRUE; +} + +static const SortCriterion * +get_sort_criterion_by_metadata_text (const char *metadata_text) +{ +    guint i; + +    /* Figure out what the new sort setting should be. */ +    for (i = 0; i < G_N_ELEMENTS (sort_criteria); i++) +    { +        if (strcmp (sort_criteria[i].metadata_text, metadata_text) == 0) +        { +            return &sort_criteria[i]; +        } +    } +    return NULL; +} + +static const SortCriterion * +get_sort_criterion_by_sort_type (CajaFileSortType sort_type) +{ +    guint i; + +    /* Figure out what the new sort setting should be. */ +    for (i = 0; i < G_N_ELEMENTS (sort_criteria); i++) +    { +        if (sort_type == sort_criteria[i].sort_type) +        { +            return &sort_criteria[i]; +        } +    } + +    return NULL; +} + +static CajaZoomLevel default_zoom_level = CAJA_ZOOM_LEVEL_STANDARD; +static CajaZoomLevel default_compact_zoom_level = CAJA_ZOOM_LEVEL_STANDARD; +#define DEFAULT_ZOOM_LEVEL(icon_view) icon_view->details->compact ? default_compact_zoom_level : default_zoom_level + +static CajaZoomLevel +get_default_zoom_level (FMIconView *icon_view) +{ +    static gboolean auto_storage_added = FALSE; + +    if (!auto_storage_added) +    { +        auto_storage_added = TRUE; +        eel_preferences_add_auto_enum (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_ZOOM_LEVEL, +                                       (int *) &default_zoom_level); +        eel_preferences_add_auto_enum (CAJA_PREFERENCES_COMPACT_VIEW_DEFAULT_ZOOM_LEVEL, +                                       (int *) &default_compact_zoom_level); +    } + +    return CLAMP (DEFAULT_ZOOM_LEVEL(icon_view), CAJA_ZOOM_LEVEL_SMALLEST, CAJA_ZOOM_LEVEL_LARGEST); +} + +static void +set_labels_beside_icons (FMIconView *icon_view) +{ +    gboolean labels_beside; + +    if (fm_icon_view_supports_labels_beside_icons (icon_view)) +    { +        labels_beside = fm_icon_view_is_compact (icon_view) || +                        eel_preferences_get_boolean (CAJA_PREFERENCES_ICON_VIEW_LABELS_BESIDE_ICONS); + +        if (labels_beside) +        { +            caja_icon_container_set_label_position +            (get_icon_container (icon_view), +             CAJA_ICON_LABEL_POSITION_BESIDE); +        } +        else +        { +            caja_icon_container_set_label_position +            (get_icon_container (icon_view), +             CAJA_ICON_LABEL_POSITION_UNDER); +        } +    } +} + +static void +set_columns_same_width (FMIconView *icon_view) +{ +    gboolean all_columns_same_width; + +    if (fm_icon_view_is_compact (icon_view)) +    { +        all_columns_same_width = eel_preferences_get_boolean (CAJA_PREFERENCES_COMPACT_VIEW_ALL_COLUMNS_SAME_WIDTH); +        caja_icon_container_set_all_columns_same_width (get_icon_container (icon_view), all_columns_same_width); +    } +} + +static void +fm_icon_view_begin_loading (FMDirectoryView *view) +{ +    FMIconView *icon_view; +    GtkWidget *icon_container; +    CajaFile *file; +    int level; +    char *sort_name; + +    g_return_if_fail (FM_IS_ICON_VIEW (view)); + +    icon_view = FM_ICON_VIEW (view); +    file = fm_directory_view_get_directory_as_file (view); +    icon_container = GTK_WIDGET (get_icon_container (icon_view)); + +    caja_icon_container_begin_loading (CAJA_ICON_CONTAINER (icon_container)); + +    caja_icon_container_set_allow_moves (CAJA_ICON_CONTAINER (icon_container), +                                         fm_directory_view_get_allow_moves (view)); + +    /* kill any sound preview process that is ongoing */ +    preview_audio (icon_view, NULL, FALSE); + +    /* FIXME bugzilla.gnome.org 45060: Should use methods instead +     * of hardcoding desktop knowledge in here. +     */ +    if (FM_IS_DESKTOP_ICON_VIEW (view)) +    { +        caja_connect_desktop_background_to_file_metadata (CAJA_ICON_CONTAINER (icon_container), file); +    } +    else +    { +        GdkDragAction default_action; + +        if (caja_window_info_get_window_type (fm_directory_view_get_caja_window (view)) == CAJA_WINDOW_NAVIGATION) +        { +            default_action = CAJA_DND_ACTION_SET_AS_GLOBAL_BACKGROUND; +        } +        else +        { +            default_action = CAJA_DND_ACTION_SET_AS_FOLDER_BACKGROUND; +        } + +        caja_connect_background_to_file_metadata +        (icon_container, +         file, +         default_action); +    } + + +    /* Set up the zoom level from the metadata. */ +    if (fm_directory_view_supports_zooming (FM_DIRECTORY_VIEW (icon_view))) +    { +        if (icon_view->details->compact) +        { +            level = caja_file_get_integer_metadata +                    (file, +                     CAJA_METADATA_KEY_COMPACT_VIEW_ZOOM_LEVEL, +                     get_default_zoom_level (icon_view)); +        } +        else +        { +            level = caja_file_get_integer_metadata +                    (file, +                     CAJA_METADATA_KEY_ICON_VIEW_ZOOM_LEVEL, +                     get_default_zoom_level (icon_view)); +        } + +        fm_icon_view_set_zoom_level (icon_view, level, TRUE); +    } + +    /* Set the sort mode. +     * It's OK not to resort the icons because the +     * container doesn't have any icons at this point. +     */ +    sort_name = fm_icon_view_get_directory_sort_by (icon_view, file); +    set_sort_criterion (icon_view, get_sort_criterion_by_metadata_text (sort_name)); +    g_free (sort_name); + +    /* Set the sort direction from the metadata. */ +    set_sort_reversed (icon_view, fm_icon_view_get_directory_sort_reversed (icon_view, file)); + +    caja_icon_container_set_keep_aligned +    (get_icon_container (icon_view), +     fm_icon_view_get_directory_keep_aligned (icon_view, file)); +    caja_icon_container_set_tighter_layout +    (get_icon_container (icon_view), +     fm_icon_view_get_directory_tighter_layout (icon_view, file)); + +    set_labels_beside_icons (icon_view); +    set_columns_same_width (icon_view); + +    /* We must set auto-layout last, because it invokes the layout_changed +     * callback, which works incorrectly if the other layout criteria are +     * not already set up properly (see bug 6500, e.g.) +     */ +    caja_icon_container_set_auto_layout +    (get_icon_container (icon_view), +     fm_icon_view_get_directory_auto_layout (icon_view, file)); + +    /* e.g. keep aligned may have changed */ +    update_layout_menus (icon_view); +} + +static void +icon_view_notify_clipboard_info (CajaClipboardMonitor *monitor, +                                 CajaClipboardInfo *info, +                                 FMIconView *icon_view) +{ +    GList *icon_data; + +    icon_data = NULL; +    if (info && info->cut) +    { +        icon_data = info->files; +    } + +    caja_icon_container_set_highlighted_for_clipboard ( +        get_icon_container (icon_view), icon_data); +} + +static void +fm_icon_view_end_loading (FMDirectoryView *view, +                          gboolean all_files_seen) +{ +    FMIconView *icon_view; +    GtkWidget *icon_container; +    CajaClipboardMonitor *monitor; +    CajaClipboardInfo *info; + +    icon_view = FM_ICON_VIEW (view); + +    icon_container = GTK_WIDGET (get_icon_container (icon_view)); +    caja_icon_container_end_loading (CAJA_ICON_CONTAINER (icon_container), all_files_seen); + +    monitor = caja_clipboard_monitor_get (); +    info = caja_clipboard_monitor_get_clipboard_info (monitor); + +    icon_view_notify_clipboard_info (monitor, info, icon_view); +} + +static CajaZoomLevel +fm_icon_view_get_zoom_level (FMDirectoryView *view) +{ +    g_return_val_if_fail (FM_IS_ICON_VIEW (view), CAJA_ZOOM_LEVEL_STANDARD); + +    return caja_icon_container_get_zoom_level (get_icon_container (FM_ICON_VIEW (view))); +} + +static void +fm_icon_view_set_zoom_level (FMIconView *view, +                             CajaZoomLevel new_level, +                             gboolean always_emit) +{ +    CajaIconContainer *icon_container; + +    g_return_if_fail (FM_IS_ICON_VIEW (view)); +    g_return_if_fail (new_level >= CAJA_ZOOM_LEVEL_SMALLEST && +                      new_level <= CAJA_ZOOM_LEVEL_LARGEST); + +    icon_container = get_icon_container (view); +    if (caja_icon_container_get_zoom_level (icon_container) == new_level) +    { +        if (always_emit) +        { +            g_signal_emit_by_name (view, "zoom_level_changed"); +        } +        return; +    } + +    if (view->details->compact) +    { +        caja_file_set_integer_metadata +        (fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (view)), +         CAJA_METADATA_KEY_COMPACT_VIEW_ZOOM_LEVEL, +         get_default_zoom_level (view), +         new_level); +    } +    else +    { +        caja_file_set_integer_metadata +        (fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (view)), +         CAJA_METADATA_KEY_ICON_VIEW_ZOOM_LEVEL, +         get_default_zoom_level (view), +         new_level); +    } + +    caja_icon_container_set_zoom_level (icon_container, new_level); + +    g_signal_emit_by_name (view, "zoom_level_changed"); + +    if (fm_directory_view_get_active (FM_DIRECTORY_VIEW (view))) +    { +        fm_directory_view_update_menus (FM_DIRECTORY_VIEW (view)); +    } +} + +static void +fm_icon_view_bump_zoom_level (FMDirectoryView *view, int zoom_increment) +{ +    FMIconView *icon_view; +    CajaZoomLevel new_level; + +    g_return_if_fail (FM_IS_ICON_VIEW (view)); + +    icon_view = FM_ICON_VIEW (view); +    new_level = fm_icon_view_get_zoom_level (view) + zoom_increment; + +    if (new_level >= CAJA_ZOOM_LEVEL_SMALLEST && +            new_level <= CAJA_ZOOM_LEVEL_LARGEST) +    { +        fm_directory_view_zoom_to_level (view, new_level); +    } +} + +static void +fm_icon_view_zoom_to_level (FMDirectoryView *view, +                            CajaZoomLevel zoom_level) +{ +    FMIconView *icon_view; + +    g_assert (FM_IS_ICON_VIEW (view)); + +    icon_view = FM_ICON_VIEW (view); +    fm_icon_view_set_zoom_level (icon_view, zoom_level, FALSE); +} + +static void +fm_icon_view_restore_default_zoom_level (FMDirectoryView *view) +{ +    FMIconView *icon_view; + +    g_return_if_fail (FM_IS_ICON_VIEW (view)); + +    icon_view = FM_ICON_VIEW (view); +    fm_directory_view_zoom_to_level +    (view, get_default_zoom_level (icon_view)); +} + +static gboolean +fm_icon_view_can_zoom_in (FMDirectoryView *view) +{ +    g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE); + +    return fm_icon_view_get_zoom_level (view) +           < CAJA_ZOOM_LEVEL_LARGEST; +} + +static gboolean +fm_icon_view_can_zoom_out (FMDirectoryView *view) +{ +    g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE); + +    return fm_icon_view_get_zoom_level (view) +           > CAJA_ZOOM_LEVEL_SMALLEST; +} + +static GtkWidget * +fm_icon_view_get_background_widget (FMDirectoryView *view) +{ +    g_return_val_if_fail (FM_IS_ICON_VIEW (view), NULL); + +    return GTK_WIDGET (get_icon_container (FM_ICON_VIEW (view))); +} + +static gboolean +fm_icon_view_is_empty (FMDirectoryView *view) +{ +    g_assert (FM_IS_ICON_VIEW (view)); + +    return caja_icon_container_is_empty +           (get_icon_container (FM_ICON_VIEW (view))); +} + +static GList * +fm_icon_view_get_selection (FMDirectoryView *view) +{ +    GList *list; + +    g_return_val_if_fail (FM_IS_ICON_VIEW (view), NULL); + +    list = caja_icon_container_get_selection +           (get_icon_container (FM_ICON_VIEW (view))); +    caja_file_list_ref (list); +    return list; +} + +static void +count_item (CajaIconData *icon_data, +            gpointer callback_data) +{ +    guint *count; + +    count = callback_data; +    (*count)++; +} + +static guint +fm_icon_view_get_item_count (FMDirectoryView *view) +{ +    guint count; + +    g_return_val_if_fail (FM_IS_ICON_VIEW (view), 0); + +    count = 0; + +    caja_icon_container_for_each +    (get_icon_container (FM_ICON_VIEW (view)), +     count_item, &count); + +    return count; +} + +static void +set_sort_criterion_by_sort_type (FMIconView *icon_view, +                                 CajaFileSortType  sort_type) +{ +    const SortCriterion *sort; + +    g_assert (FM_IS_ICON_VIEW (icon_view)); + +    sort = get_sort_criterion_by_sort_type (sort_type); +    g_return_if_fail (sort != NULL); + +    if (sort == icon_view->details->sort +            && fm_icon_view_using_auto_layout (icon_view)) +    { +        return; +    } + +    set_sort_criterion (icon_view, sort); +    caja_icon_container_sort (get_icon_container (icon_view)); +    fm_icon_view_reveal_selection (FM_DIRECTORY_VIEW (icon_view)); +} + + +static void +action_reversed_order_callback (GtkAction *action, +                                gpointer user_data) +{ +    FMIconView *icon_view; + +    icon_view = FM_ICON_VIEW (user_data); + +    if (set_sort_reversed (icon_view, +                           gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))) +    { +        caja_icon_container_sort (get_icon_container (icon_view)); +        fm_icon_view_reveal_selection (FM_DIRECTORY_VIEW (icon_view)); +    } +} + +static void +action_keep_aligned_callback (GtkAction *action, +                              gpointer user_data) +{ +    FMIconView *icon_view; +    CajaFile *file; +    gboolean keep_aligned; + +    icon_view = FM_ICON_VIEW (user_data); + +    keep_aligned = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)); + +    file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (icon_view)); +    fm_icon_view_set_directory_keep_aligned (icon_view, +            file, +            keep_aligned); + +    caja_icon_container_set_keep_aligned (get_icon_container (icon_view), +                                          keep_aligned); +} + +static void +switch_to_manual_layout (FMIconView *icon_view) +{ +    if (!fm_icon_view_using_auto_layout (icon_view)) +    { +        return; +    } + +    icon_view->details->sort = &sort_criteria[0]; + +    caja_icon_container_set_auto_layout +    (get_icon_container (icon_view), FALSE); +} + +static void +layout_changed_callback (CajaIconContainer *container, +                         FMIconView *icon_view) +{ +    CajaFile *file; + +    g_assert (FM_IS_ICON_VIEW (icon_view)); +    g_assert (container == get_icon_container (icon_view)); + +    file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (icon_view)); + +    if (file != NULL) +    { +        fm_icon_view_set_directory_auto_layout +        (icon_view, +         file, +         fm_icon_view_using_auto_layout (icon_view)); +        fm_icon_view_set_directory_tighter_layout +        (icon_view, +         file, +         fm_icon_view_using_tighter_layout (icon_view)); +    } + +    update_layout_menus (icon_view); +} + +static gboolean +fm_icon_view_can_rename_file (FMDirectoryView *view, CajaFile *file) +{ +    if (!(fm_icon_view_get_zoom_level (view) > CAJA_ZOOM_LEVEL_SMALLEST)) +    { +        return FALSE; +    } + +    return FM_DIRECTORY_VIEW_CLASS(fm_icon_view_parent_class)->can_rename_file (view, file); +} + +static void +fm_icon_view_start_renaming_file (FMDirectoryView *view, +                                  CajaFile *file, +                                  gboolean select_all) +{ +    /* call parent class to make sure the right icon is selected */ +    FM_DIRECTORY_VIEW_CLASS(fm_icon_view_parent_class)->start_renaming_file (view, file, select_all); + +    /* start renaming */ +    caja_icon_container_start_renaming_selected_item +    (get_icon_container (FM_ICON_VIEW (view)), select_all); +} + +static const GtkActionEntry icon_view_entries[] = +{ +    /* name, stock id, label */  { "Arrange Items", NULL, N_("Arran_ge Items") }, +    /* name, stock id */         { "Stretch", NULL, +        /* label, accelerator */       N_("Resize Icon..."), NULL, +        /* tooltip */                  N_("Make the selected icon resizable"), +        G_CALLBACK (action_stretch_callback) +    }, +    /* name, stock id */         { "Unstretch", NULL, +        /* label, accelerator */       N_("Restore Icons' Original Si_zes"), NULL, +        /* tooltip */                  N_("Restore each selected icon to its original size"), +        G_CALLBACK (action_unstretch_callback) +    }, +    /* name, stock id */         { "Clean Up", NULL, +        /* label, accelerator */       N_("_Organize by Name"), NULL, +        /* tooltip */                  N_("Reposition icons to better fit in the window and avoid overlapping"), +        G_CALLBACK (action_clean_up_callback) +    }, +}; + +static const GtkToggleActionEntry icon_view_toggle_entries[] = +{ +    /* name, stock id */      { "Tighter Layout", NULL, +        /* label, accelerator */    N_("Compact _Layout"), NULL, +        /* tooltip */               N_("Toggle using a tighter layout scheme"), +        G_CALLBACK (action_tighter_layout_callback), +        0 +    }, +    /* name, stock id */      { "Reversed Order", NULL, +        /* label, accelerator */    N_("Re_versed Order"), NULL, +        /* tooltip */               N_("Display icons in the opposite order"), +        G_CALLBACK (action_reversed_order_callback), +        0 +    }, +    /* name, stock id */      { "Keep Aligned", NULL, +        /* label, accelerator */    N_("_Keep Aligned"), NULL, +        /* tooltip */               N_("Keep icons lined up on a grid"), +        G_CALLBACK (action_keep_aligned_callback), +        0 +    }, +}; + +static const GtkRadioActionEntry arrange_radio_entries[] = +{ +    { +        "Manual Layout", NULL, +        N_("_Manually"), NULL, +        N_("Leave icons wherever they are dropped"), +        CAJA_FILE_SORT_NONE +    }, +    { +        "Sort by Name", NULL, +        N_("By _Name"), NULL, +        N_("Keep icons sorted by name in rows"), +        CAJA_FILE_SORT_BY_DISPLAY_NAME +    }, +    { +        "Sort by Size", NULL, +        N_("By _Size"), NULL, +        N_("Keep icons sorted by size in rows"), +        CAJA_FILE_SORT_BY_SIZE +    }, +    { +        "Sort by Type", NULL, +        N_("By _Type"), NULL, +        N_("Keep icons sorted by type in rows"), +        CAJA_FILE_SORT_BY_TYPE +    }, +    { +        "Sort by Modification Date", NULL, +        N_("By Modification _Date"), NULL, +        N_("Keep icons sorted by modification date in rows"), +        CAJA_FILE_SORT_BY_MTIME +    }, +    { +        "Sort by Emblems", NULL, +        N_("By _Emblems"), NULL, +        N_("Keep icons sorted by emblems in rows"), +        CAJA_FILE_SORT_BY_EMBLEMS +    }, +    { +        "Sort by Trash Time", NULL, +        N_("By T_rash Time"), NULL, +        N_("Keep icons sorted by trash time in rows"), +        CAJA_FILE_SORT_BY_TRASHED_TIME +    }, +}; + +static void +fm_icon_view_merge_menus (FMDirectoryView *view) +{ +    FMIconView *icon_view; +    GtkUIManager *ui_manager; +    GtkActionGroup *action_group; +    GtkAction *action; +    const char *ui; + +    g_assert (FM_IS_ICON_VIEW (view)); + +    FM_DIRECTORY_VIEW_CLASS (fm_icon_view_parent_class)->merge_menus (view); + +    icon_view = FM_ICON_VIEW (view); + +    ui_manager = fm_directory_view_get_ui_manager (FM_DIRECTORY_VIEW (icon_view)); + +    action_group = gtk_action_group_new ("IconViewActions"); +    gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE); +    icon_view->details->icon_action_group = action_group; +    gtk_action_group_add_actions (action_group, +                                  icon_view_entries, G_N_ELEMENTS (icon_view_entries), +                                  icon_view); +    gtk_action_group_add_toggle_actions (action_group, +                                         icon_view_toggle_entries, G_N_ELEMENTS (icon_view_toggle_entries), +                                         icon_view); +    gtk_action_group_add_radio_actions (action_group, +                                        arrange_radio_entries, +                                        G_N_ELEMENTS (arrange_radio_entries), +                                        -1, +                                        G_CALLBACK (action_sort_radio_callback), +                                        icon_view); + +    gtk_ui_manager_insert_action_group (ui_manager, action_group, 0); +    g_object_unref (action_group); /* owned by ui manager */ + +    ui = caja_ui_string_get ("caja-icon-view-ui.xml"); +    icon_view->details->icon_merge_id = +        gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, NULL); + +    /* Do one-time state-setting here; context-dependent state-setting +     * is done in update_menus. +     */ +    if (!fm_icon_view_supports_auto_layout (icon_view)) +    { +        action = gtk_action_group_get_action (action_group, +                                              FM_ACTION_ARRANGE_ITEMS); +        gtk_action_set_visible (action, FALSE); +    } + +    if (fm_icon_view_supports_scaling (icon_view)) +    { +        gtk_ui_manager_add_ui (ui_manager, +                               icon_view->details->icon_merge_id, +                               POPUP_PATH_ICON_APPEARANCE, +                               FM_ACTION_STRETCH, +                               FM_ACTION_STRETCH, +                               GTK_UI_MANAGER_MENUITEM, +                               FALSE); +        gtk_ui_manager_add_ui (ui_manager, +                               icon_view->details->icon_merge_id, +                               POPUP_PATH_ICON_APPEARANCE, +                               FM_ACTION_UNSTRETCH, +                               FM_ACTION_UNSTRETCH, +                               GTK_UI_MANAGER_MENUITEM, +                               FALSE); +    } + +    update_layout_menus (icon_view); +} + +static void +fm_icon_view_unmerge_menus (FMDirectoryView *view) +{ +    FMIconView *icon_view; +    GtkUIManager *ui_manager; + +    icon_view = FM_ICON_VIEW (view); + +    FM_DIRECTORY_VIEW_CLASS (fm_icon_view_parent_class)->unmerge_menus (view); + +    ui_manager = fm_directory_view_get_ui_manager (view); +    if (ui_manager != NULL) +    { +        caja_ui_unmerge_ui (ui_manager, +                            &icon_view->details->icon_merge_id, +                            &icon_view->details->icon_action_group); +    } +} + +static void +fm_icon_view_update_menus (FMDirectoryView *view) +{ +    FMIconView *icon_view; +    GList *selection; +    int selection_count; +    GtkAction *action; +    CajaIconContainer *icon_container; +    gboolean editable; + +    icon_view = FM_ICON_VIEW (view); + +    FM_DIRECTORY_VIEW_CLASS (fm_icon_view_parent_class)->update_menus(view); + +    selection = fm_directory_view_get_selection (view); +    selection_count = g_list_length (selection); +    icon_container = get_icon_container (icon_view); + +    action = gtk_action_group_get_action (icon_view->details->icon_action_group, +                                          FM_ACTION_STRETCH); +    gtk_action_set_sensitive (action, +                              selection_count == 1 +                              && icon_container != NULL +                              && !caja_icon_container_has_stretch_handles (icon_container)); + +    gtk_action_set_visible (action, +                            fm_icon_view_supports_scaling (icon_view)); + +    action = gtk_action_group_get_action (icon_view->details->icon_action_group, +                                          FM_ACTION_UNSTRETCH); +    g_object_set (action, "label", +                  eel_g_list_more_than_one_item (selection) +                  ? _("Restore Icons' Original Si_zes") +                  : _("Restore Icon's Original Si_ze"), +                  NULL); +    gtk_action_set_sensitive (action, +                              icon_container != NULL +                              && caja_icon_container_is_stretched (icon_container)); + +    gtk_action_set_visible (action, +                            fm_icon_view_supports_scaling (icon_view)); + +    caja_file_list_free (selection); + +    editable = fm_directory_view_is_editable (view); +    action = gtk_action_group_get_action (icon_view->details->icon_action_group, +                                          FM_ACTION_MANUAL_LAYOUT); +    gtk_action_set_sensitive (action, editable); +} + +static void +fm_icon_view_reset_to_defaults (FMDirectoryView *view) +{ +    CajaIconContainer *icon_container; +    FMIconView *icon_view; + +    icon_view = FM_ICON_VIEW (view); +    icon_container = get_icon_container (icon_view); + +    clear_sort_criterion (icon_view); +    caja_icon_container_set_keep_aligned +    (icon_container, get_default_directory_keep_aligned ()); +    caja_icon_container_set_tighter_layout +    (icon_container, get_default_directory_tighter_layout ()); + +    caja_icon_container_sort (icon_container); + +    /* Switch to manual layout of the default calls for it. +     * This needs to happen last for the sort order menus +     * to be in sync. +     */ +    if (get_default_directory_manual_layout ()) +    { +        switch_to_manual_layout (icon_view); +    } + +    update_layout_menus (icon_view); + +    fm_icon_view_restore_default_zoom_level (view); +} + +static void +fm_icon_view_select_all (FMDirectoryView *view) +{ +    CajaIconContainer *icon_container; + +    g_return_if_fail (FM_IS_ICON_VIEW (view)); + +    icon_container = get_icon_container (FM_ICON_VIEW (view)); +    caja_icon_container_select_all (icon_container); +} + +static void +fm_icon_view_reveal_selection (FMDirectoryView *view) +{ +    GList *selection; + +    g_return_if_fail (FM_IS_ICON_VIEW (view)); + +    selection = fm_directory_view_get_selection (view); + +    /* Make sure at least one of the selected items is scrolled into view */ +    if (selection != NULL) +    { +        caja_icon_container_reveal +        (get_icon_container (FM_ICON_VIEW (view)), +         selection->data); +    } + +    caja_file_list_free (selection); +} + +static GArray * +fm_icon_view_get_selected_icon_locations (FMDirectoryView *view) +{ +    g_return_val_if_fail (FM_IS_ICON_VIEW (view), NULL); + +    return caja_icon_container_get_selected_icon_locations +           (get_icon_container (FM_ICON_VIEW (view))); +} + + +static void +fm_icon_view_set_selection (FMDirectoryView *view, GList *selection) +{ +    g_return_if_fail (FM_IS_ICON_VIEW (view)); + +    caja_icon_container_set_selection +    (get_icon_container (FM_ICON_VIEW (view)), selection); +} + +static void +fm_icon_view_invert_selection (FMDirectoryView *view) +{ +    g_return_if_fail (FM_IS_ICON_VIEW (view)); + +    caja_icon_container_invert_selection +    (get_icon_container (FM_ICON_VIEW (view))); +} + +static gboolean +fm_icon_view_using_manual_layout (FMDirectoryView *view) +{ +    g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE); + +    return !fm_icon_view_using_auto_layout (FM_ICON_VIEW (view)); +} + +static void +fm_icon_view_widget_to_file_operation_position (FMDirectoryView *view, +        GdkPoint *position) +{ +    g_assert (FM_IS_ICON_VIEW (view)); + +    caja_icon_container_widget_to_file_operation_position +    (get_icon_container (FM_ICON_VIEW (view)), position); +} + +static void +icon_container_activate_callback (CajaIconContainer *container, +                                  GList *file_list, +                                  FMIconView *icon_view) +{ +    g_assert (FM_IS_ICON_VIEW (icon_view)); +    g_assert (container == get_icon_container (icon_view)); + +    fm_directory_view_activate_files (FM_DIRECTORY_VIEW (icon_view), +                                      file_list, +                                      CAJA_WINDOW_OPEN_ACCORDING_TO_MODE, 0, +                                      TRUE); +} + +static void +icon_container_activate_alternate_callback (CajaIconContainer *container, +        GList *file_list, +        FMIconView *icon_view) +{ +    GdkEvent *event; +    GdkEventButton *button_event; +    GdkEventKey *key_event; +    gboolean open_in_tab; +    CajaWindowInfo *window_info; +    CajaWindowOpenFlags flags; + +    g_assert (FM_IS_ICON_VIEW (icon_view)); +    g_assert (container == get_icon_container (icon_view)); + +    open_in_tab = FALSE; + +    window_info = fm_directory_view_get_caja_window (FM_DIRECTORY_VIEW (icon_view)); + +    if (caja_window_info_get_window_type (window_info) == CAJA_WINDOW_NAVIGATION) +    { +        event = gtk_get_current_event (); +        if (event->type == GDK_BUTTON_PRESS || +                event->type == GDK_BUTTON_RELEASE || +                event->type == GDK_2BUTTON_PRESS || +                event->type == GDK_3BUTTON_PRESS) +        { +            button_event = (GdkEventButton *) event; +            open_in_tab = (button_event->state & GDK_SHIFT_MASK) == 0; +        } +        else if (event->type == GDK_KEY_PRESS || +                 event->type == GDK_KEY_RELEASE) +        { +            key_event = (GdkEventKey *) event; +            open_in_tab = !((key_event->state & GDK_SHIFT_MASK) != 0 && +                            (key_event->state & GDK_CONTROL_MASK) != 0); +        } +        else +        { +            open_in_tab = TRUE; +        } +    } + +    flags = CAJA_WINDOW_OPEN_FLAG_CLOSE_BEHIND; +    if (open_in_tab) +    { +        flags |= CAJA_WINDOW_OPEN_FLAG_NEW_TAB; +    } +    else +    { +        flags |= CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW; +    } + +    fm_directory_view_activate_files (FM_DIRECTORY_VIEW (icon_view), +                                      file_list, +                                      CAJA_WINDOW_OPEN_ACCORDING_TO_MODE, +                                      flags, +                                      TRUE); +} + +static void +band_select_started_callback (CajaIconContainer *container, +                              FMIconView *icon_view) +{ +    g_assert (FM_IS_ICON_VIEW (icon_view)); +    g_assert (container == get_icon_container (icon_view)); + +    fm_directory_view_start_batching_selection_changes (FM_DIRECTORY_VIEW (icon_view)); +} + +static void +band_select_ended_callback (CajaIconContainer *container, +                            FMIconView *icon_view) +{ +    g_assert (FM_IS_ICON_VIEW (icon_view)); +    g_assert (container == get_icon_container (icon_view)); + +    fm_directory_view_stop_batching_selection_changes (FM_DIRECTORY_VIEW (icon_view)); +} + +/* handle the preview signal by inspecting the mime type.  For now, we only preview local sound files. */ + +static char ** +get_preview_argv (char *uri) +{ +    char *command; +    char **argv; +    int i; + +    command = g_find_program_in_path ("totem-audio-preview"); +    if (command) +    { +        argv = g_new (char *, 3); +        argv[0] = command; +        argv[1] = g_strdup (uri); +        argv[2] = NULL; + +        return argv; +    } + +    command = g_find_program_in_path ("gst-launch-0.10"); +    if (command) +    { +        argv = g_new (char *, 10); +        i = 0; +        argv[i++] = command; +        argv[i++] = g_strdup ("playbin"); +        argv[i++] = g_strconcat ("uri=", uri, NULL); +        /* do not display videos */ +        argv[i++] = g_strdup ("current-video=-1"); +        argv[i++] = NULL; +        return argv; +    } + +    return NULL; +} + +static void +audio_child_died (GPid     pid, +                  gint     status, +                  gpointer data) +{ +    FMIconView *icon_view; + +    icon_view = FM_ICON_VIEW (data); + +    icon_view->details->audio_preview_child_watch = 0; +    icon_view->details->audio_preview_child_pid = 0; +} + +/* here's the timer task that actually plays the file using mpg123, ogg123 or play. */ +/* FIXME bugzilla.gnome.org 41258: we should get the application from our mime-type stuff */ +static gboolean +play_file (gpointer callback_data) +{ +    CajaFile *file; +    FMIconView *icon_view; +    GPid child_pid; +    char **argv; +    GError *error; +    char *uri; + +    icon_view = FM_ICON_VIEW (callback_data); + +    /* Stop timeout */ +    icon_view->details->audio_preview_timeout = 0; + +    file = icon_view->details->audio_preview_file; +    uri = caja_file_get_uri (file); +    argv = get_preview_argv (uri); +    g_free (uri); +    if (argv == NULL) +    { +        return FALSE; +    } + +    error = NULL; +    if (!g_spawn_async_with_pipes (NULL, +                                   argv, +                                   NULL, +                                   G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL, +                                   NULL, +                                   NULL /* user_data */, +                                   &child_pid, +                                   NULL, NULL, NULL, +                                   &error)) +    { +        g_strfreev (argv); +        g_warning ("Error spawning sound preview: %s\n", error->message); +        g_error_free (error); +        return FALSE; +    } +    g_strfreev (argv); + +    icon_view->details->audio_preview_child_watch = +        g_child_watch_add (child_pid, +                           audio_child_died, NULL); +    icon_view->details->audio_preview_child_pid = child_pid; + +    return FALSE; +} + +/* FIXME bugzilla.gnome.org 42530: Hardcoding this here sucks. We should be using components + * for open ended things like this. + */ + +/* this routine is invoked from the preview signal handler to preview a sound file.  We +   want to wait a suitable delay until we actually do it, so set up a timer task to actually +   start playing.  If we move out before the task files, we remove it. */ + +static void +preview_audio (FMIconView *icon_view, CajaFile *file, gboolean start_flag) +{ +    /* Stop current audio playback */ +    if (icon_view->details->audio_preview_child_pid != 0) +    { +        kill (icon_view->details->audio_preview_child_pid, SIGTERM); +        g_source_remove (icon_view->details->audio_preview_child_watch); +        waitpid (icon_view->details->audio_preview_child_pid, NULL, 0); +        icon_view->details->audio_preview_child_pid = 0; +    } + +    if (icon_view->details->audio_preview_timeout != 0) +    { +        g_source_remove (icon_view->details->audio_preview_timeout); +        icon_view->details->audio_preview_timeout = 0; +    } + +    if (start_flag) +    { +        icon_view->details->audio_preview_file = file; +        icon_view->details->audio_preview_timeout = g_timeout_add_seconds (1, play_file, icon_view); +    } +} + +static gboolean +sound_preview_type_supported (CajaFile *file) +{ +    char *mime_type; +    guint i; + +    mime_type = caja_file_get_mime_type (file); +    if (mime_type == NULL) +    { +        return FALSE; +    } +    for (i = 0; i < G_N_ELEMENTS (audio_mime_types); i++) +    { +        if (g_content_type_is_a (mime_type, audio_mime_types[i])) +        { +            g_free (mime_type); +            return TRUE; +        } +    } + +    g_free (mime_type); +    return FALSE; +} + + +static gboolean +should_preview_sound (CajaFile *file) +{ +    GFile *location; +    GFilesystemPreviewType use_preview; + +    use_preview = caja_file_get_filesystem_use_preview (file); + +    location = caja_file_get_location (file); +    if (g_file_has_uri_scheme (location, "burn")) +    { +        g_object_unref (location); +        return FALSE; +    } +    g_object_unref (location); + +    /* Check user performance preference */ +    if (preview_sound_auto_value == CAJA_SPEED_TRADEOFF_NEVER) +    { +        return FALSE; +    } + +    if (preview_sound_auto_value == CAJA_SPEED_TRADEOFF_ALWAYS) +    { +        if (use_preview == G_FILESYSTEM_PREVIEW_TYPE_NEVER) +        { +            return FALSE; +        } +        else +        { +            return TRUE; +        } +    } + +    if (use_preview == G_FILESYSTEM_PREVIEW_TYPE_NEVER) +    { +        /* file system says to never preview anything */ +        return FALSE; +    } +    else if (use_preview == G_FILESYSTEM_PREVIEW_TYPE_IF_LOCAL) +    { +        /* file system says we should treat file as if it's local */ +        return TRUE; +    } +    else +    { +        /* only local files */ +        return caja_file_is_local (file); +    } +} + +static int +icon_container_preview_callback (CajaIconContainer *container, +                                 CajaFile *file, +                                 gboolean start_flag, +                                 FMIconView *icon_view) +{ +    int result; +    char *file_name, *message; + +    result = 0; + +    /* preview files based on the mime_type. */ +    /* at first, we just handle sounds */ +    if (should_preview_sound (file)) +    { +        if (sound_preview_type_supported (file)) +        { +            result = 1; +            preview_audio (icon_view, file, start_flag); +        } +    } + +    /* Display file name in status area at low zoom levels, since +     * the name is not displayed or hard to read in the icon view. +     */ +    if (fm_icon_view_get_zoom_level (FM_DIRECTORY_VIEW (icon_view)) <= CAJA_ZOOM_LEVEL_SMALLER) +    { +        if (start_flag) +        { +            file_name = caja_file_get_display_name (file); +            message = g_strdup_printf (_("pointing at \"%s\""), file_name); +            g_free (file_name); +            caja_window_slot_info_set_status +            (fm_directory_view_get_caja_window_slot (FM_DIRECTORY_VIEW (icon_view)), +             message); +            g_free (message); +        } +        else +        { +            fm_directory_view_display_selection_info (FM_DIRECTORY_VIEW(icon_view)); +        } +    } + +    return result; +} + +static void +renaming_icon_callback (CajaIconContainer *container, +                        GtkWidget *widget, +                        gpointer callback_data) +{ +    FMDirectoryView *directory_view; + +    directory_view = FM_DIRECTORY_VIEW (callback_data); +    caja_clipboard_set_up_editable +    (GTK_EDITABLE (widget), +     fm_directory_view_get_ui_manager (directory_view), +     FALSE); +} + +int +fm_icon_view_compare_files (FMIconView   *icon_view, +                            CajaFile *a, +                            CajaFile *b) +{ +    return caja_file_compare_for_sort +           (a, b, icon_view->details->sort->sort_type, +            /* Use type-unsafe cast for performance */ +            fm_directory_view_should_sort_directories_first ((FMDirectoryView *)icon_view), +            icon_view->details->sort_reversed); +} + +static int +compare_files (FMDirectoryView   *icon_view, +               CajaFile *a, +               CajaFile *b) +{ +    return fm_icon_view_compare_files ((FMIconView *)icon_view, a, b); +} + + +void +fm_icon_view_filter_by_screen (FMIconView *icon_view, +                               gboolean filter) +{ +    icon_view->details->filter_by_screen = filter; +    icon_view->details->num_screens = gdk_display_get_n_screens (gtk_widget_get_display (GTK_WIDGET (icon_view))); +} + +static void +fm_icon_view_screen_changed (GtkWidget *widget, +                             GdkScreen *previous_screen) +{ +    FMDirectoryView *view; +    GList *files, *l; +    CajaFile *file; +    CajaDirectory *directory; +    CajaIconContainer *icon_container; + +    if (GTK_WIDGET_CLASS (fm_icon_view_parent_class)->screen_changed) +    { +        GTK_WIDGET_CLASS (fm_icon_view_parent_class)->screen_changed (widget, previous_screen); +    } + +    view = FM_DIRECTORY_VIEW (widget); +    if (FM_ICON_VIEW (view)->details->filter_by_screen) +    { +        icon_container = get_icon_container (FM_ICON_VIEW (view)); + +        directory = fm_directory_view_get_model (view); +        files = caja_directory_get_file_list (directory); + +        for (l = files; l != NULL; l = l->next) +        { +            file = l->data; + +            if (!should_show_file_on_screen (view, file)) +            { +                fm_icon_view_remove_file (view, file, directory); +            } +            else +            { +                if (caja_icon_container_add (icon_container, +                                             CAJA_ICON_CONTAINER_ICON_DATA (file))) +                { +                    caja_file_ref (file); +                } +            } +        } + +        caja_file_list_unref (files); +        g_list_free (files); +    } +} + +static gboolean +fm_icon_view_scroll_event (GtkWidget *widget, +                           GdkEventScroll *scroll_event) +{ +    FMIconView *icon_view; +    GdkEvent *event_copy; +    GdkEventScroll *scroll_event_copy; +    gboolean ret; + +    icon_view = FM_ICON_VIEW (widget); + +    if (icon_view->details->compact && +            (scroll_event->direction == GDK_SCROLL_UP || +             scroll_event->direction == GDK_SCROLL_DOWN)) +    { +        ret = fm_directory_view_handle_scroll_event (FM_DIRECTORY_VIEW (icon_view), scroll_event); +        if (!ret) +        { +            /* in column-wise layout, re-emit vertical mouse scroll events as horizontal ones, +             * if they don't bump zoom */ +            event_copy = gdk_event_copy ((GdkEvent *) scroll_event); + +            scroll_event_copy = (GdkEventScroll *) event_copy; +            if (scroll_event_copy->direction == GDK_SCROLL_UP) +            { +                scroll_event_copy->direction = GDK_SCROLL_LEFT; +            } +            else +            { +                scroll_event_copy->direction = GDK_SCROLL_RIGHT; +            } + +            ret = gtk_widget_event (widget, event_copy); +            gdk_event_free (event_copy); +        } + +        return ret; +    } + +    return GTK_WIDGET_CLASS (fm_icon_view_parent_class)->scroll_event (widget, scroll_event); +} + +static void +selection_changed_callback (CajaIconContainer *container, +                            FMIconView *icon_view) +{ +    g_assert (FM_IS_ICON_VIEW (icon_view)); +    g_assert (container == get_icon_container (icon_view)); + +    fm_directory_view_notify_selection_changed (FM_DIRECTORY_VIEW (icon_view)); +} + +static void +icon_container_context_click_selection_callback (CajaIconContainer *container, +        GdkEventButton *event, +        FMIconView *icon_view) +{ +    g_assert (CAJA_IS_ICON_CONTAINER (container)); +    g_assert (FM_IS_ICON_VIEW (icon_view)); + +    fm_directory_view_pop_up_selection_context_menu +    (FM_DIRECTORY_VIEW (icon_view), event); +} + +static void +icon_container_context_click_background_callback (CajaIconContainer *container, +        GdkEventButton *event, +        FMIconView *icon_view) +{ +    g_assert (CAJA_IS_ICON_CONTAINER (container)); +    g_assert (FM_IS_ICON_VIEW (icon_view)); + +    fm_directory_view_pop_up_background_context_menu +    (FM_DIRECTORY_VIEW (icon_view), event); +} + +static gboolean +fm_icon_view_react_to_icon_change_idle_callback (gpointer data) +{ +    FMIconView *icon_view; + +    g_assert (FM_IS_ICON_VIEW (data)); + +    icon_view = FM_ICON_VIEW (data); +    icon_view->details->react_to_icon_change_idle_id = 0; + +    /* Rebuild the menus since some of them (e.g. Restore Stretched Icons) +     * may be different now. +     */ +    fm_directory_view_update_menus (FM_DIRECTORY_VIEW (icon_view)); + +    /* Don't call this again (unless rescheduled) */ +    return FALSE; +} + +static void +icon_position_changed_callback (CajaIconContainer *container, +                                CajaFile *file, +                                const CajaIconPosition *position, +                                FMIconView *icon_view) +{ +    char *position_string; +    char scale_string[G_ASCII_DTOSTR_BUF_SIZE]; + +    g_assert (FM_IS_ICON_VIEW (icon_view)); +    g_assert (container == get_icon_container (icon_view)); +    g_assert (CAJA_IS_FILE (file)); + +    /* Schedule updating menus for the next idle. Doing it directly here +     * noticeably slows down icon stretching.  The other work here to +     * store the icon position and scale does not seem to noticeably +     * slow down icon stretching. It would be trickier to move to an +     * idle call, because we'd have to keep track of potentially multiple +     * sets of file/geometry info. +     */ +    if (fm_directory_view_get_active (FM_DIRECTORY_VIEW (icon_view)) && +            icon_view->details->react_to_icon_change_idle_id == 0) +    { +        icon_view->details->react_to_icon_change_idle_id +            = g_idle_add (fm_icon_view_react_to_icon_change_idle_callback, +                          icon_view); +    } + +    /* Store the new position of the icon in the metadata. */ +    if (!fm_icon_view_using_auto_layout (icon_view)) +    { +        position_string = g_strdup_printf +                          ("%d,%d", position->x, position->y); +        caja_file_set_metadata +        (file, CAJA_METADATA_KEY_ICON_POSITION, +         NULL, position_string); +        g_free (position_string); +    } + + +    g_ascii_dtostr (scale_string, sizeof (scale_string), position->scale); +    caja_file_set_metadata +    (file, CAJA_METADATA_KEY_ICON_SCALE, +     "1.0", scale_string); +} + +/* Attempt to change the filename to the new text.  Notify user if operation fails. */ +static void +fm_icon_view_icon_text_changed_callback (CajaIconContainer *container, +        CajaFile *file, +        char *new_name, +        FMIconView *icon_view) +{ +    g_assert (CAJA_IS_FILE (file)); +    g_assert (new_name != NULL); + +    /* Don't allow a rename with an empty string. Revert to original +     * without notifying the user. +     */ +    if (new_name[0] == '\0') +    { +        return; +    } +    fm_rename_file (file, new_name, NULL, NULL); +} + +static char * +get_icon_uri_callback (CajaIconContainer *container, +                       CajaFile *file, +                       FMIconView *icon_view) +{ +    g_assert (CAJA_IS_ICON_CONTAINER (container)); +    g_assert (CAJA_IS_FILE (file)); +    g_assert (FM_IS_ICON_VIEW (icon_view)); + +    return caja_file_get_uri (file); +} + +static char * +get_icon_drop_target_uri_callback (CajaIconContainer *container, +                                   CajaFile *file, +                                   FMIconView *icon_view) +{ +    g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (container), NULL); +    g_return_val_if_fail (CAJA_IS_FILE (file), NULL); +    g_return_val_if_fail (FM_IS_ICON_VIEW (icon_view), NULL); + +    return caja_file_get_drop_target_uri (file); +} + +/* Preferences changed callbacks */ +static void +fm_icon_view_text_attribute_names_changed (FMDirectoryView *directory_view) +{ +    g_assert (FM_IS_ICON_VIEW (directory_view)); + +    caja_icon_container_request_update_all (get_icon_container (FM_ICON_VIEW (directory_view))); +} + +static void +fm_icon_view_embedded_text_policy_changed (FMDirectoryView *directory_view) +{ +    g_assert (FM_IS_ICON_VIEW (directory_view)); + +    caja_icon_container_request_update_all (get_icon_container (FM_ICON_VIEW (directory_view))); +} + +static void +fm_icon_view_image_display_policy_changed (FMDirectoryView *directory_view) +{ +    g_assert (FM_IS_ICON_VIEW (directory_view)); + +    caja_icon_container_request_update_all (get_icon_container (FM_ICON_VIEW (directory_view))); +} + +static void +fm_icon_view_click_policy_changed (FMDirectoryView *directory_view) +{ +    g_assert (FM_IS_ICON_VIEW (directory_view)); + +    fm_icon_view_update_click_mode (FM_ICON_VIEW (directory_view)); +} + +static void +fm_icon_view_emblems_changed (FMDirectoryView *directory_view) +{ +    g_assert (FM_IS_ICON_VIEW (directory_view)); + +    caja_icon_container_request_update_all (get_icon_container (FM_ICON_VIEW (directory_view))); +} + +static void +default_sort_order_changed_callback (gpointer callback_data) +{ +    FMIconView *icon_view; +    CajaFile *file; +    char *sort_name; +    CajaIconContainer *icon_container; + +    g_return_if_fail (FM_IS_ICON_VIEW (callback_data)); + +    icon_view = FM_ICON_VIEW (callback_data); + +    file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (icon_view)); +    sort_name = fm_icon_view_get_directory_sort_by (icon_view, file); +    set_sort_criterion (icon_view, get_sort_criterion_by_metadata_text (sort_name)); +    g_free (sort_name); + +    icon_container = get_icon_container (icon_view); +    g_return_if_fail (CAJA_IS_ICON_CONTAINER (icon_container)); + +    caja_icon_container_request_update_all (icon_container); +} + +static void +default_sort_in_reverse_order_changed_callback (gpointer callback_data) +{ +    FMIconView *icon_view; +    CajaFile *file; +    CajaIconContainer *icon_container; + +    g_return_if_fail (FM_IS_ICON_VIEW (callback_data)); + +    icon_view = FM_ICON_VIEW (callback_data); + +    file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (icon_view)); +    set_sort_reversed (icon_view, fm_icon_view_get_directory_sort_reversed (icon_view, file)); +    icon_container = get_icon_container (icon_view); +    g_return_if_fail (CAJA_IS_ICON_CONTAINER (icon_container)); + +    caja_icon_container_request_update_all (icon_container); +} + +static void +default_use_tighter_layout_changed_callback (gpointer callback_data) +{ +    FMIconView *icon_view; +    CajaFile *file; +    CajaIconContainer *icon_container; + +    g_return_if_fail (FM_IS_ICON_VIEW (callback_data)); + +    icon_view = FM_ICON_VIEW (callback_data); + +    file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (icon_view)); +    icon_container = get_icon_container (icon_view); +    g_return_if_fail (CAJA_IS_ICON_CONTAINER (icon_container)); + +    caja_icon_container_set_tighter_layout ( +        icon_container, +        fm_icon_view_get_directory_tighter_layout (icon_view, file)); + +    caja_icon_container_request_update_all (icon_container); +} + +static void +default_use_manual_layout_changed_callback (gpointer callback_data) +{ +    FMIconView *icon_view; +    CajaFile *file; +    CajaIconContainer *icon_container; + +    g_return_if_fail (FM_IS_ICON_VIEW (callback_data)); + +    icon_view = FM_ICON_VIEW (callback_data); + +    file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (icon_view)); +    icon_container = get_icon_container (icon_view); +    g_return_if_fail (CAJA_IS_ICON_CONTAINER (icon_container)); + +    caja_icon_container_set_auto_layout ( +        icon_container, +        fm_icon_view_get_directory_auto_layout (icon_view, file)); + +    caja_icon_container_request_update_all (icon_container); +} + +static void +default_zoom_level_changed_callback (gpointer callback_data) +{ +    FMIconView *icon_view; +    CajaFile *file; +    int level; + +    g_return_if_fail (FM_IS_ICON_VIEW (callback_data)); + +    icon_view = FM_ICON_VIEW (callback_data); + +    if (fm_directory_view_supports_zooming (FM_DIRECTORY_VIEW (icon_view))) +    { +        file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (icon_view)); + +        if (fm_icon_view_is_compact (icon_view)) +        { +            level = caja_file_get_integer_metadata (file, +                                                    CAJA_METADATA_KEY_COMPACT_VIEW_ZOOM_LEVEL, +                                                    get_default_zoom_level (icon_view)); +        } +        else +        { +            level = caja_file_get_integer_metadata (file, +                                                    CAJA_METADATA_KEY_ICON_VIEW_ZOOM_LEVEL, +                                                    get_default_zoom_level (icon_view)); +        } +        fm_directory_view_zoom_to_level (FM_DIRECTORY_VIEW (icon_view), level); +    } +} + +static void +labels_beside_icons_changed_callback (gpointer callback_data) +{ +    FMIconView *icon_view; + +    g_return_if_fail (FM_IS_ICON_VIEW (callback_data)); + +    icon_view = FM_ICON_VIEW (callback_data); + +    set_labels_beside_icons (icon_view); +} + +static void +all_columns_same_width_changed_callback (gpointer callback_data) +{ +    FMIconView *icon_view; + +    g_assert (FM_IS_ICON_VIEW (callback_data)); + +    icon_view = FM_ICON_VIEW (callback_data); + +    set_columns_same_width (icon_view); +} + + +static void +fm_icon_view_sort_directories_first_changed (FMDirectoryView *directory_view) +{ +    FMIconView *icon_view; + +    icon_view = FM_ICON_VIEW (directory_view); + +    if (fm_icon_view_using_auto_layout (icon_view)) +    { +        caja_icon_container_sort +        (get_icon_container (icon_view)); +    } +} + +/* GtkObject methods. */ + +static gboolean +icon_view_can_accept_item (CajaIconContainer *container, +                           CajaFile *target_item, +                           const char *item_uri, +                           FMDirectoryView *view) +{ +    return fm_directory_view_can_accept_item (target_item, item_uri, view); +} + +static char * +icon_view_get_container_uri (CajaIconContainer *container, +                             FMDirectoryView *view) +{ +    return fm_directory_view_get_uri (view); +} + +static void +icon_view_move_copy_items (CajaIconContainer *container, +                           const GList *item_uris, +                           GArray *relative_item_points, +                           const char *target_dir, +                           int copy_action, +                           int x, int y, +                           FMDirectoryView *view) +{ +    caja_clipboard_clear_if_colliding_uris (GTK_WIDGET (view), +                                            item_uris, +                                            fm_directory_view_get_copied_files_atom (view)); +    fm_directory_view_move_copy_items (item_uris, relative_item_points, target_dir, +                                       copy_action, x, y, view); +} + +static void +fm_icon_view_update_click_mode (FMIconView *icon_view) +{ +    CajaIconContainer	*icon_container; +    int			click_mode; + +    icon_container = get_icon_container (icon_view); +    g_assert (icon_container != NULL); + +    click_mode = eel_preferences_get_enum (CAJA_PREFERENCES_CLICK_POLICY); + +    caja_icon_container_set_single_click_mode (icon_container, +            click_mode == CAJA_CLICK_POLICY_SINGLE); +} + +static gboolean +get_stored_layout_timestamp (CajaIconContainer *container, +                             CajaIconData *icon_data, +                             time_t *timestamp, +                             FMIconView *view) +{ +    CajaFile *file; +    CajaDirectory *directory; + +    if (icon_data == NULL) +    { +        directory = fm_directory_view_get_model (FM_DIRECTORY_VIEW (view)); +        if (directory == NULL) +        { +            return FALSE; +        } + +        file = caja_directory_get_corresponding_file (directory); +        *timestamp = caja_file_get_time_metadata (file, +                     CAJA_METADATA_KEY_ICON_VIEW_LAYOUT_TIMESTAMP); +        caja_file_unref (file); +    } +    else +    { +        *timestamp = caja_file_get_time_metadata (CAJA_FILE (icon_data), +                     CAJA_METADATA_KEY_ICON_POSITION_TIMESTAMP); +    } + +    return TRUE; +} + +static gboolean +store_layout_timestamp (CajaIconContainer *container, +                        CajaIconData *icon_data, +                        const time_t *timestamp, +                        FMIconView *view) +{ +    CajaFile *file; +    CajaDirectory *directory; + +    if (icon_data == NULL) +    { +        directory = fm_directory_view_get_model (FM_DIRECTORY_VIEW (view)); +        if (directory == NULL) +        { +            return FALSE; +        } + +        file = caja_directory_get_corresponding_file (directory); +        caja_file_set_time_metadata (file, +                                     CAJA_METADATA_KEY_ICON_VIEW_LAYOUT_TIMESTAMP, +                                     (time_t) *timestamp); +        caja_file_unref (file); +    } +    else +    { +        caja_file_set_time_metadata (CAJA_FILE (icon_data), +                                     CAJA_METADATA_KEY_ICON_POSITION_TIMESTAMP, +                                     (time_t) *timestamp); +    } + +    return TRUE; +} + +static gboolean +focus_in_event_callback (GtkWidget *widget, GdkEventFocus *event, gpointer user_data) +{ +    CajaWindowSlotInfo *slot_info; +    FMIconView *icon_view = FM_ICON_VIEW (user_data); + +    /* make the corresponding slot (and the pane that contains it) active */ +    slot_info = fm_directory_view_get_caja_window_slot (FM_DIRECTORY_VIEW (icon_view)); +    caja_window_slot_info_make_hosting_pane_active (slot_info); + +    return FALSE; +} + +static CajaIconContainer * +create_icon_container (FMIconView *icon_view) +{ +    CajaIconContainer *icon_container; + +    icon_container = fm_icon_container_new (icon_view); + +    gtk_widget_set_can_focus (GTK_WIDGET (icon_container), TRUE); + +    g_signal_connect_object (icon_container, "focus_in_event", +                             G_CALLBACK (focus_in_event_callback), icon_view, 0); +    g_signal_connect_object (icon_container, "activate", +                             G_CALLBACK (icon_container_activate_callback), icon_view, 0); +    g_signal_connect_object (icon_container, "activate_alternate", +                             G_CALLBACK (icon_container_activate_alternate_callback), icon_view, 0); +    g_signal_connect_object (icon_container, "band_select_started", +                             G_CALLBACK (band_select_started_callback), icon_view, 0); +    g_signal_connect_object (icon_container, "band_select_ended", +                             G_CALLBACK (band_select_ended_callback), icon_view, 0); +    g_signal_connect_object (icon_container, "context_click_selection", +                             G_CALLBACK (icon_container_context_click_selection_callback), icon_view, 0); +    g_signal_connect_object (icon_container, "context_click_background", +                             G_CALLBACK (icon_container_context_click_background_callback), icon_view, 0); +    g_signal_connect_object (icon_container, "icon_position_changed", +                             G_CALLBACK (icon_position_changed_callback), icon_view, 0); +    g_signal_connect_object (icon_container, "icon_text_changed", +                             G_CALLBACK (fm_icon_view_icon_text_changed_callback), icon_view, 0); +    g_signal_connect_object (icon_container, "selection_changed", +                             G_CALLBACK (selection_changed_callback), icon_view, 0); +    /* FIXME: many of these should move into fm-icon-container as virtual methods */ +    g_signal_connect_object (icon_container, "get_icon_uri", +                             G_CALLBACK (get_icon_uri_callback), icon_view, 0); +    g_signal_connect_object (icon_container, "get_icon_drop_target_uri", +                             G_CALLBACK (get_icon_drop_target_uri_callback), icon_view, 0); +    g_signal_connect_object (icon_container, "move_copy_items", +                             G_CALLBACK (icon_view_move_copy_items), icon_view, 0); +    g_signal_connect_object (icon_container, "get_container_uri", +                             G_CALLBACK (icon_view_get_container_uri), icon_view, 0); +    g_signal_connect_object (icon_container, "can_accept_item", +                             G_CALLBACK (icon_view_can_accept_item), icon_view, 0); +    g_signal_connect_object (icon_container, "get_stored_icon_position", +                             G_CALLBACK (get_stored_icon_position_callback), icon_view, 0); +    g_signal_connect_object (icon_container, "layout_changed", +                             G_CALLBACK (layout_changed_callback), icon_view, 0); +    g_signal_connect_object (icon_container, "preview", +                             G_CALLBACK (icon_container_preview_callback), icon_view, 0); +    g_signal_connect_object (icon_container, "renaming_icon", +                             G_CALLBACK (renaming_icon_callback), icon_view, 0); +    g_signal_connect_object (icon_container, "icon_stretch_started", +                             G_CALLBACK (fm_directory_view_update_menus), icon_view, +                             G_CONNECT_SWAPPED); +    g_signal_connect_object (icon_container, "icon_stretch_ended", +                             G_CALLBACK (fm_directory_view_update_menus), icon_view, +                             G_CONNECT_SWAPPED); + +    g_signal_connect_object (icon_container, "get_stored_layout_timestamp", +                             G_CALLBACK (get_stored_layout_timestamp), icon_view, 0); +    g_signal_connect_object (icon_container, "store_layout_timestamp", +                             G_CALLBACK (store_layout_timestamp), icon_view, 0); + +    gtk_container_add (GTK_CONTAINER (icon_view), +                       GTK_WIDGET (icon_container)); + +    fm_icon_view_update_click_mode (icon_view); + +    gtk_widget_show (GTK_WIDGET (icon_container)); + +    return icon_container; +} + +/* Handles an URL received from Mozilla */ +static void +icon_view_handle_netscape_url (CajaIconContainer *container, const char *encoded_url, +                               const char *target_uri, +                               GdkDragAction action, int x, int y, FMIconView *view) +{ +    fm_directory_view_handle_netscape_url_drop (FM_DIRECTORY_VIEW (view), +            encoded_url, target_uri, action, x, y); +} + +static void +icon_view_handle_uri_list (CajaIconContainer *container, const char *item_uris, +                           const char *target_uri, +                           GdkDragAction action, int x, int y, FMIconView *view) +{ +    fm_directory_view_handle_uri_list_drop (FM_DIRECTORY_VIEW (view), +                                            item_uris, target_uri, action, x, y); +} + +static void +icon_view_handle_text (CajaIconContainer *container, const char *text, +                       const char *target_uri, +                       GdkDragAction action, int x, int y, FMIconView *view) +{ +    fm_directory_view_handle_text_drop (FM_DIRECTORY_VIEW (view), +                                        text, target_uri, action, x, y); +} + +static void +icon_view_handle_raw (CajaIconContainer *container, const char *raw_data, +                      int length, const char *target_uri, const char *direct_save_uri, +                      GdkDragAction action, int x, int y, FMIconView *view) +{ +    fm_directory_view_handle_raw_drop (FM_DIRECTORY_VIEW (view), +                                       raw_data, length, target_uri, direct_save_uri, action, x, y); +} + +static char * +icon_view_get_first_visible_file (CajaView *view) +{ +    CajaFile *file; +    FMIconView *icon_view; + +    icon_view = FM_ICON_VIEW (view); + +    file = CAJA_FILE (caja_icon_container_get_first_visible_icon (get_icon_container (icon_view))); + +    if (file) +    { +        return caja_file_get_uri (file); +    } + +    return NULL; +} + +static void +icon_view_scroll_to_file (CajaView *view, +                          const char *uri) +{ +    CajaFile *file; +    FMIconView *icon_view; + +    icon_view = FM_ICON_VIEW (view); + +    if (uri != NULL) +    { +        /* Only if existing, since we don't want to add the file to +           the directory if it has been removed since then */ +        file = caja_file_get_existing_by_uri (uri); +        if (file != NULL) +        { +            caja_icon_container_scroll_to_icon (get_icon_container (icon_view), +                                                CAJA_ICON_CONTAINER_ICON_DATA (file)); +            caja_file_unref (file); +        } +    } +} + +static void +fm_icon_view_set_property (GObject         *object, +                           guint            prop_id, +                           const GValue    *value, +                           GParamSpec      *pspec) +{ +    FMIconView *icon_view; + +    icon_view = FM_ICON_VIEW (object); + +    switch (prop_id) +    { +    case PROP_COMPACT: +        icon_view->details->compact = g_value_get_boolean (value); +        if (icon_view->details->compact) +        { +            caja_icon_container_set_layout_mode (get_icon_container (icon_view), +                                                 gtk_widget_get_direction (GTK_WIDGET(icon_view)) == GTK_TEXT_DIR_RTL ? +                                                 CAJA_ICON_LAYOUT_T_B_R_L : +                                                 CAJA_ICON_LAYOUT_T_B_L_R); +            caja_icon_container_set_forced_icon_size (get_icon_container (icon_view), +                    CAJA_ICON_SIZE_SMALLEST); +        } +        break; + +    default: +        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); +        break; +    } +} + + + +static void +fm_icon_view_class_init (FMIconViewClass *klass) +{ +    FMDirectoryViewClass *fm_directory_view_class; + +    fm_directory_view_class = FM_DIRECTORY_VIEW_CLASS (klass); + +    G_OBJECT_CLASS (klass)->set_property = fm_icon_view_set_property; +    G_OBJECT_CLASS (klass)->finalize = fm_icon_view_finalize; + +    GTK_OBJECT_CLASS (klass)->destroy = fm_icon_view_destroy; + +    GTK_WIDGET_CLASS (klass)->screen_changed = fm_icon_view_screen_changed; +    GTK_WIDGET_CLASS (klass)->scroll_event = fm_icon_view_scroll_event; + +    fm_directory_view_class->add_file = fm_icon_view_add_file; +    fm_directory_view_class->flush_added_files = fm_icon_view_flush_added_files; +    fm_directory_view_class->begin_loading = fm_icon_view_begin_loading; +    fm_directory_view_class->bump_zoom_level = fm_icon_view_bump_zoom_level; +    fm_directory_view_class->can_rename_file = fm_icon_view_can_rename_file; +    fm_directory_view_class->can_zoom_in = fm_icon_view_can_zoom_in; +    fm_directory_view_class->can_zoom_out = fm_icon_view_can_zoom_out; +    fm_directory_view_class->clear = fm_icon_view_clear; +    fm_directory_view_class->end_loading = fm_icon_view_end_loading; +    fm_directory_view_class->file_changed = fm_icon_view_file_changed; +    fm_directory_view_class->get_background_widget = fm_icon_view_get_background_widget; +    fm_directory_view_class->get_selected_icon_locations = fm_icon_view_get_selected_icon_locations; +    fm_directory_view_class->get_selection = fm_icon_view_get_selection; +    fm_directory_view_class->get_selection_for_file_transfer = fm_icon_view_get_selection; +    fm_directory_view_class->get_item_count = fm_icon_view_get_item_count; +    fm_directory_view_class->is_empty = fm_icon_view_is_empty; +    fm_directory_view_class->remove_file = fm_icon_view_remove_file; +    fm_directory_view_class->reset_to_defaults = fm_icon_view_reset_to_defaults; +    fm_directory_view_class->restore_default_zoom_level = fm_icon_view_restore_default_zoom_level; +    fm_directory_view_class->reveal_selection = fm_icon_view_reveal_selection; +    fm_directory_view_class->select_all = fm_icon_view_select_all; +    fm_directory_view_class->set_selection = fm_icon_view_set_selection; +    fm_directory_view_class->invert_selection = fm_icon_view_invert_selection; +    fm_directory_view_class->compare_files = compare_files; +    fm_directory_view_class->zoom_to_level = fm_icon_view_zoom_to_level; +    fm_directory_view_class->get_zoom_level = fm_icon_view_get_zoom_level; +    fm_directory_view_class->click_policy_changed = fm_icon_view_click_policy_changed; +    fm_directory_view_class->embedded_text_policy_changed = fm_icon_view_embedded_text_policy_changed; +    fm_directory_view_class->emblems_changed = fm_icon_view_emblems_changed; +    fm_directory_view_class->image_display_policy_changed = fm_icon_view_image_display_policy_changed; +    fm_directory_view_class->merge_menus = fm_icon_view_merge_menus; +    fm_directory_view_class->unmerge_menus = fm_icon_view_unmerge_menus; +    fm_directory_view_class->sort_directories_first_changed = fm_icon_view_sort_directories_first_changed; +    fm_directory_view_class->start_renaming_file = fm_icon_view_start_renaming_file; +    fm_directory_view_class->text_attribute_names_changed = fm_icon_view_text_attribute_names_changed; +    fm_directory_view_class->update_menus = fm_icon_view_update_menus; +    fm_directory_view_class->using_manual_layout = fm_icon_view_using_manual_layout; +    fm_directory_view_class->widget_to_file_operation_position = fm_icon_view_widget_to_file_operation_position; + +    klass->clean_up = fm_icon_view_real_clean_up; +    klass->supports_auto_layout = real_supports_auto_layout; +    klass->supports_scaling = real_supports_scaling; +    klass->supports_manual_layout = real_supports_manual_layout; +    klass->supports_keep_aligned = real_supports_keep_aligned; +    klass->supports_labels_beside_icons = real_supports_labels_beside_icons; +    klass->get_directory_auto_layout = fm_icon_view_real_get_directory_auto_layout; +    klass->get_directory_sort_by = fm_icon_view_real_get_directory_sort_by; +    klass->get_directory_sort_reversed = fm_icon_view_real_get_directory_sort_reversed; +    klass->get_directory_tighter_layout = fm_icon_view_real_get_directory_tighter_layout; +    klass->set_directory_auto_layout = fm_icon_view_real_set_directory_auto_layout; +    klass->set_directory_sort_by = fm_icon_view_real_set_directory_sort_by; +    klass->set_directory_sort_reversed = fm_icon_view_real_set_directory_sort_reversed; +    klass->set_directory_tighter_layout = fm_icon_view_real_set_directory_tighter_layout; + +    g_object_class_install_property (G_OBJECT_CLASS (klass), +                                     PROP_COMPACT, +                                     g_param_spec_boolean ("compact", +                                             "Compact", +                                             "Whether this view provides a compact listing", +                                             FALSE, +                                             G_PARAM_WRITABLE | +                                             G_PARAM_CONSTRUCT_ONLY)); + +} + +static const char * +fm_icon_view_get_id (CajaView *view) +{ +    if (FM_IS_DESKTOP_ICON_VIEW (view)) +    { +        return FM_DESKTOP_ICON_VIEW_ID; +    } + +    if (fm_icon_view_is_compact (FM_ICON_VIEW (view))) +    { +        return FM_COMPACT_VIEW_ID; +    } + +    return FM_ICON_VIEW_ID; +} + +static void +fm_icon_view_iface_init (CajaViewIface *iface) +{ +    fm_directory_view_init_view_iface (iface); + +    iface->get_view_id = fm_icon_view_get_id; +    iface->get_first_visible_file = icon_view_get_first_visible_file; +    iface->scroll_to_file = icon_view_scroll_to_file; +    iface->get_title = NULL; +} + +static void +fm_icon_view_init (FMIconView *icon_view) +{ +    static gboolean setup_sound_preview = FALSE; +    CajaIconContainer *icon_container; + +    g_return_if_fail (gtk_bin_get_child (GTK_BIN (icon_view)) == NULL); + +    icon_view->details = g_new0 (FMIconViewDetails, 1); +    icon_view->details->sort = &sort_criteria[0]; +    icon_view->details->filter_by_screen = FALSE; + +    icon_container = create_icon_container (icon_view); + +    /* Set our default layout mode */ +    caja_icon_container_set_layout_mode (icon_container, +                                         gtk_widget_get_direction (GTK_WIDGET(icon_container)) == GTK_TEXT_DIR_RTL ? +                                         CAJA_ICON_LAYOUT_R_L_T_B : +                                         CAJA_ICON_LAYOUT_L_R_T_B); + +    if (!setup_sound_preview) +    { +        eel_preferences_add_auto_enum (CAJA_PREFERENCES_PREVIEW_SOUND, +                                       &preview_sound_auto_value); + +        setup_sound_preview = TRUE; +    } + +    eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_SORT_ORDER, +            default_sort_order_changed_callback, +            icon_view, G_OBJECT (icon_view)); +    eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_SORT_IN_REVERSE_ORDER, +            default_sort_in_reverse_order_changed_callback, +            icon_view, G_OBJECT (icon_view)); +    eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_USE_TIGHTER_LAYOUT, +            default_use_tighter_layout_changed_callback, +            icon_view, G_OBJECT (icon_view)); +    eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_USE_MANUAL_LAYOUT, +            default_use_manual_layout_changed_callback, +            icon_view, G_OBJECT (icon_view)); +    eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_ZOOM_LEVEL, +            default_zoom_level_changed_callback, +            icon_view, G_OBJECT (icon_view)); +    eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_ICON_VIEW_LABELS_BESIDE_ICONS, +            labels_beside_icons_changed_callback, +            icon_view, G_OBJECT (icon_view)); + +    eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_COMPACT_VIEW_DEFAULT_ZOOM_LEVEL, +            default_zoom_level_changed_callback, +            icon_view, G_OBJECT (icon_view)); +    eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_COMPACT_VIEW_ALL_COLUMNS_SAME_WIDTH, +            all_columns_same_width_changed_callback, +            icon_view, G_OBJECT (icon_view)); + +    g_signal_connect_object (get_icon_container (icon_view), "handle_netscape_url", +                             G_CALLBACK (icon_view_handle_netscape_url), icon_view, 0); +    g_signal_connect_object (get_icon_container (icon_view), "handle_uri_list", +                             G_CALLBACK (icon_view_handle_uri_list), icon_view, 0); +    g_signal_connect_object (get_icon_container (icon_view), "handle_text", +                             G_CALLBACK (icon_view_handle_text), icon_view, 0); +    g_signal_connect_object (get_icon_container (icon_view), "handle_raw", +                             G_CALLBACK (icon_view_handle_raw), icon_view, 0); + +    icon_view->details->clipboard_handler_id = +        g_signal_connect (caja_clipboard_monitor_get (), +                          "clipboard_info", +                          G_CALLBACK (icon_view_notify_clipboard_info), icon_view); +} + +static CajaView * +fm_icon_view_create (CajaWindowSlotInfo *slot) +{ +    FMIconView *view; + +    view = g_object_new (FM_TYPE_ICON_VIEW, +                         "window-slot", slot, +                         "compact", FALSE, +                         NULL); +    return CAJA_VIEW (view); +} + +static CajaView * +fm_compact_view_create (CajaWindowSlotInfo *slot) +{ +    FMIconView *view; + +    view = g_object_new (FM_TYPE_ICON_VIEW, +                         "window-slot", slot, +                         "compact", TRUE, +                         NULL); +    return CAJA_VIEW (view); +} + +static gboolean +fm_icon_view_supports_uri (const char *uri, +                           GFileType file_type, +                           const char *mime_type) +{ +    if (file_type == G_FILE_TYPE_DIRECTORY) +    { +        return TRUE; +    } +    if (strcmp (mime_type, CAJA_SAVED_SEARCH_MIMETYPE) == 0) +    { +        return TRUE; +    } +    if (g_str_has_prefix (uri, "trash:")) +    { +        return TRUE; +    } +    if (g_str_has_prefix (uri, EEL_SEARCH_URI)) +    { +        return TRUE; +    } + +    return FALSE; +} + +#define TRANSLATE_VIEW_INFO(view_info) \ +	view_info.view_combo_label = _(view_info.view_combo_label); \ +	view_info.view_menu_label_with_mnemonic = _(view_info.view_menu_label_with_mnemonic); \ +	view_info.error_label = _(view_info.error_label); \ +	view_info.startup_error_label = _(view_info.startup_error_label); \ +	view_info.display_location_label = _(view_info.display_location_label); \ + + +static CajaViewInfo fm_icon_view = +{ +    FM_ICON_VIEW_ID, +    /* translators: this is used in the view selection dropdown +     * of navigation windows and in the preferences dialog */ +    N_("Icon View"), +    /* translators: this is used in the view menu */ +    N_("_Icons"), +    N_("The icon view encountered an error."), +    N_("The icon view encountered an error while starting up."), +    N_("Display this location with the icon view."), +    fm_icon_view_create, +    fm_icon_view_supports_uri +}; + +static CajaViewInfo fm_compact_view = +{ +    FM_COMPACT_VIEW_ID, +    /* translators: this is used in the view selection dropdown +     * of navigation windows and in the preferences dialog */ +    N_("Compact View"), +    /* translators: this is used in the view menu */ +    N_("_Compact"), +    N_("The compact view encountered an error."), +    N_("The compact view encountered an error while starting up."), +    N_("Display this location with the compact view."), +    fm_compact_view_create, +    fm_icon_view_supports_uri +}; + +gboolean +fm_icon_view_is_compact (FMIconView *view) +{ +    return view->details->compact; +} + +void +fm_icon_view_register (void) +{ +    TRANSLATE_VIEW_INFO (fm_icon_view) +    caja_view_factory_register (&fm_icon_view); +} + +void +fm_compact_view_register (void) +{ +    TRANSLATE_VIEW_INFO (fm_compact_view) +    caja_view_factory_register (&fm_compact_view); +} + diff --git a/src/file-manager/fm-icon-view.h b/src/file-manager/fm-icon-view.h new file mode 100644 index 00000000..2ef11a56 --- /dev/null +++ b/src/file-manager/fm-icon-view.h @@ -0,0 +1,135 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-icon-view.h - interface for icon view of directory. + +   Copyright (C) 2000 Eazel, Inc. + +   The Mate Library is free software; you can redistribute it and/or +   modify it under the terms of the GNU Library General Public License as +   published by the Free Software Foundation; either version 2 of the +   License, or (at your option) any later version. + +   The Mate Library is distributed in the hope that it will be useful, +   but WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +   Library General Public License for more details. + +   You should have received a copy of the GNU Library General Public +   License along with the Mate Library; see the file COPYING.LIB.  If not, +   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +   Boston, MA 02111-1307, USA. + +   Authors: John Sullivan <[email protected]> +*/ + +#ifndef FM_ICON_VIEW_H +#define FM_ICON_VIEW_H + +#include "fm-directory-view.h" + +typedef struct FMIconView FMIconView; +typedef struct FMIconViewClass FMIconViewClass; + +#define FM_TYPE_ICON_VIEW fm_icon_view_get_type() +#define FM_ICON_VIEW(obj) \ +  (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_ICON_VIEW, FMIconView)) +#define FM_ICON_VIEW_CLASS(klass) \ +  (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_ICON_VIEW, FMIconViewClass)) +#define FM_IS_ICON_VIEW(obj) \ +  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_ICON_VIEW)) +#define FM_IS_ICON_VIEW_CLASS(klass) \ +  (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_ICON_VIEW)) +#define FM_ICON_VIEW_GET_CLASS(obj) \ +  (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_ICON_VIEW, FMIconViewClass)) + +#define FM_ICON_VIEW_ID "OAFIID:Caja_File_Manager_Icon_View" +#define FM_COMPACT_VIEW_ID "OAFIID:Caja_File_Manager_Compact_View" + +typedef struct FMIconViewDetails FMIconViewDetails; + +struct FMIconView +{ +    FMDirectoryView parent; +    FMIconViewDetails *details; +}; + +struct FMIconViewClass +{ +    FMDirectoryViewClass parent_class; + +    /* Methods that can be overriden for settings you don't want to come from metadata. +     */ + +    /* Note: get_directory_sort_by must return a string that can/will be g_freed. +     */ +    char *	 (* get_directory_sort_by)       (FMIconView *icon_view, +            CajaFile *file); +    void     (* set_directory_sort_by)       (FMIconView *icon_view, +            CajaFile *file, +            const char* sort_by); + +    gboolean (* get_directory_sort_reversed) (FMIconView *icon_view, +            CajaFile *file); +    void     (* set_directory_sort_reversed) (FMIconView *icon_view, +            CajaFile *file, +            gboolean sort_reversed); + +    gboolean (* get_directory_auto_layout)   (FMIconView *icon_view, +            CajaFile *file); +    void     (* set_directory_auto_layout)   (FMIconView *icon_view, +            CajaFile *file, +            gboolean auto_layout); + +    gboolean (* get_directory_tighter_layout) (FMIconView *icon_view, +            CajaFile *file); +    void     (* set_directory_tighter_layout)   (FMIconView *icon_view, +            CajaFile *file, +            gboolean tighter_layout); + +    /* Override "clean_up" if your subclass has its own notion of where icons should be positioned */ +    void	 (* clean_up)			 (FMIconView *icon_view); + +    /* supports_auto_layout is a function pointer that subclasses may +     * override to control whether or not the automatic layout options +     * should be enabled. The default implementation returns TRUE. +     */ +    gboolean (* supports_auto_layout)	 (FMIconView *view); + +    /* supports_manual_layout is a function pointer that subclasses may +     * override to control whether or not the manual layout options +     * should be enabled. The default implementation returns TRUE iff +     * not in compact mode. +     */ +    gboolean (* supports_manual_layout)	 (FMIconView *view); + +    /* supports_scaling is a function pointer that subclasses may +     * override to control whether or not the manual layout supports +     * scaling. The default implementation returns FALSE +     */ +    gboolean (* supports_scaling)	 (FMIconView *view); + +    /* supports_auto_layout is a function pointer that subclasses may +     * override to control whether snap-to-grid mode +     * should be enabled. The default implementation returns FALSE. +     */ +    gboolean (* supports_keep_aligned)	 (FMIconView *view); + +    /* supports_auto_layout is a function pointer that subclasses may +     * override to control whether snap-to-grid mode +     * should be enabled. The default implementation returns FALSE. +     */ +    gboolean (* supports_labels_beside_icons)	 (FMIconView *view); +}; + +/* GObject support */ +GType   fm_icon_view_get_type      (void); +int     fm_icon_view_compare_files (FMIconView   *icon_view, +                                    CajaFile *a, +                                    CajaFile *b); +void    fm_icon_view_filter_by_screen (FMIconView *icon_view, gboolean filter); +gboolean fm_icon_view_is_compact   (FMIconView *icon_view); + +void    fm_icon_view_register       (void); +void    fm_compact_view_register    (void); + +#endif /* FM_ICON_VIEW_H */ diff --git a/src/file-manager/fm-list-model.c b/src/file-manager/fm-list-model.c new file mode 100644 index 00000000..b5e12da2 --- /dev/null +++ b/src/file-manager/fm-list-model.c @@ -0,0 +1,1882 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-list-model.h - a GtkTreeModel for file lists. + +   Copyright (C) 2001, 2002 Anders Carlsson +   Copyright (C) 2003, Soeren Sandmann +   Copyright (C) 2004, Novell, Inc. + +   The Mate Library is free software; you can redistribute it and/or +   modify it under the terms of the GNU Library General Public License as +   published by the Free Software Foundation; either version 2 of the +   License, or (at your option) any later version. + +   The Mate Library is distributed in the hope that it will be useful, +   but WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +   Library General Public License for more details. + +   You should have received a copy of the GNU Library General Public +   License along with the Mate Library; see the file COPYING.LIB.  If not, +   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +   Boston, MA 02111-1307, USA. + +   Authors: Anders Carlsson <[email protected]>, Soeren Sandmann ([email protected]), Dave Camp <[email protected]> +*/ + +#include <config.h> +#include "fm-list-model.h" +#include <libegg/eggtreemultidnd.h> + +#include <string.h> +#include <eel/eel-gtk-macros.h> +#include <eel/eel-glib-extensions.h> +#include <eel/eel-gdk-pixbuf-extensions.h> +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <libcaja-private/caja-dnd.h> +#include <glib.h> + + +enum +{ +    SUBDIRECTORY_UNLOADED, +    LAST_SIGNAL +}; + +static GQuark attribute_name_q, +       attribute_modification_date_q, +       attribute_date_modified_q; + +/* msec delay after Loading... dummy row turns into (empty) */ +#define LOADING_TO_EMPTY_DELAY 100 + +static guint list_model_signals[LAST_SIGNAL] = { 0 }; + +static int fm_list_model_file_entry_compare_func (gconstpointer a, +        gconstpointer b, +        gpointer      user_data); +static void fm_list_model_tree_model_init (GtkTreeModelIface *iface); +static void fm_list_model_sortable_init (GtkTreeSortableIface *iface); +static void fm_list_model_multi_drag_source_init (EggTreeMultiDragSourceIface *iface); + +struct FMListModelDetails +{ +    GSequence *files; +    GHashTable *directory_reverse_map; /* map from directory to GSequenceIter's */ +    GHashTable *top_reverse_map;	   /* map from files in top dir to GSequenceIter's */ + +    int stamp; + +    GQuark sort_attribute; +    GtkSortType order; + +    gboolean sort_directories_first; + +    GtkTreeView *drag_view; +    int drag_begin_x; +    int drag_begin_y; + +    GPtrArray *columns; + +    GList *highlight_files; +}; + +typedef struct +{ +    FMListModel *model; + +    GList *path_list; +} DragDataGetInfo; + +typedef struct FileEntry FileEntry; + +struct FileEntry +{ +    CajaFile *file; +    GHashTable *reverse_map;	/* map from files to GSequenceIter's */ +    CajaDirectory *subdirectory; +    FileEntry *parent; +    GSequence *files; +    GSequenceIter *ptr; +    guint loaded : 1; +}; + +G_DEFINE_TYPE_WITH_CODE (FMListModel, fm_list_model, G_TYPE_OBJECT, +                         G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL, +                                 fm_list_model_tree_model_init) +                         G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_SORTABLE, +                                 fm_list_model_sortable_init) +                         G_IMPLEMENT_INTERFACE (EGG_TYPE_TREE_MULTI_DRAG_SOURCE, +                                 fm_list_model_multi_drag_source_init)); + +static const GtkTargetEntry drag_types [] = +{ +    { CAJA_ICON_DND_MATE_ICON_LIST_TYPE, 0, CAJA_ICON_DND_MATE_ICON_LIST }, +    { CAJA_ICON_DND_URI_LIST_TYPE, 0, CAJA_ICON_DND_URI_LIST }, +}; + +static GtkTargetList *drag_target_list = NULL; + +static void +file_entry_free (FileEntry *file_entry) +{ +    caja_file_unref (file_entry->file); +    if (file_entry->reverse_map) +    { +        g_hash_table_destroy (file_entry->reverse_map); +        file_entry->reverse_map = NULL; +    } +    if (file_entry->subdirectory != NULL) +    { +        caja_directory_unref (file_entry->subdirectory); +    } +    if (file_entry->files != NULL) +    { +        g_sequence_free (file_entry->files); +    } +    g_free (file_entry); +} + +static GtkTreeModelFlags +fm_list_model_get_flags (GtkTreeModel *tree_model) +{ +    return GTK_TREE_MODEL_ITERS_PERSIST; +} + +static int +fm_list_model_get_n_columns (GtkTreeModel *tree_model) +{ +    return FM_LIST_MODEL_NUM_COLUMNS + FM_LIST_MODEL (tree_model)->details->columns->len; +} + +static GType +fm_list_model_get_column_type (GtkTreeModel *tree_model, int index) +{ +    switch (index) +    { +    case FM_LIST_MODEL_FILE_COLUMN: +        return CAJA_TYPE_FILE; +    case FM_LIST_MODEL_SUBDIRECTORY_COLUMN: +        return CAJA_TYPE_DIRECTORY; +    case FM_LIST_MODEL_SMALLEST_ICON_COLUMN: +    case FM_LIST_MODEL_SMALLER_ICON_COLUMN: +    case FM_LIST_MODEL_SMALL_ICON_COLUMN: +    case FM_LIST_MODEL_STANDARD_ICON_COLUMN: +    case FM_LIST_MODEL_LARGE_ICON_COLUMN: +    case FM_LIST_MODEL_LARGER_ICON_COLUMN: +    case FM_LIST_MODEL_LARGEST_ICON_COLUMN: +    case FM_LIST_MODEL_SMALLEST_EMBLEM_COLUMN: +    case FM_LIST_MODEL_SMALLER_EMBLEM_COLUMN: +    case FM_LIST_MODEL_SMALL_EMBLEM_COLUMN: +    case FM_LIST_MODEL_STANDARD_EMBLEM_COLUMN: +    case FM_LIST_MODEL_LARGE_EMBLEM_COLUMN: +    case FM_LIST_MODEL_LARGER_EMBLEM_COLUMN: +    case FM_LIST_MODEL_LARGEST_EMBLEM_COLUMN: +        return GDK_TYPE_PIXBUF; +    case FM_LIST_MODEL_FILE_NAME_IS_EDITABLE_COLUMN: +        return G_TYPE_BOOLEAN; +    default: +        if (index < FM_LIST_MODEL_NUM_COLUMNS + FM_LIST_MODEL (tree_model)->details->columns->len) +        { +            return G_TYPE_STRING; +        } +        else +        { +            return G_TYPE_INVALID; +        } +    } +} + +static void +fm_list_model_ptr_to_iter (FMListModel *model, GSequenceIter *ptr, GtkTreeIter *iter) +{ +    g_assert (!g_sequence_iter_is_end (ptr)); +    if (iter != NULL) +    { +        iter->stamp = model->details->stamp; +        iter->user_data = ptr; +    } +} + +static gboolean +fm_list_model_get_iter (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreePath *path) +{ +    FMListModel *model; +    GSequence *files; +    GSequenceIter *ptr; +    FileEntry *file_entry; +    int i, d; + +    model = (FMListModel *)tree_model; +    ptr = NULL; + +    files = model->details->files; +    for (d = 0; d < gtk_tree_path_get_depth (path); d++) +    { +        i = gtk_tree_path_get_indices (path)[d]; + +        if (files == NULL || i >= g_sequence_get_length (files)) +        { +            return FALSE; +        } + +        ptr = g_sequence_get_iter_at_pos (files, i); +        file_entry = g_sequence_get (ptr); +        files = file_entry->files; +    } + +    fm_list_model_ptr_to_iter (model, ptr, iter); + +    return TRUE; +} + +static GtkTreePath * +fm_list_model_get_path (GtkTreeModel *tree_model, GtkTreeIter *iter) +{ +    GtkTreePath *path; +    FMListModel *model; +    GSequenceIter *ptr; +    FileEntry *file_entry; + + +    model = (FMListModel *)tree_model; + +    g_return_val_if_fail (iter->stamp == model->details->stamp, NULL); + +    if (g_sequence_iter_is_end (iter->user_data)) +    { +        /* FIXME is this right? */ +        return NULL; +    } + +    path = gtk_tree_path_new (); +    ptr = iter->user_data; +    while (ptr != NULL) +    { +        gtk_tree_path_prepend_index (path, g_sequence_iter_get_position (ptr)); +        file_entry = g_sequence_get (ptr); +        if (file_entry->parent != NULL) +        { +            ptr = file_entry->parent->ptr; +        } +        else +        { +            ptr = NULL; +        } +    } + +    return path; +} + +static void +fm_list_model_get_value (GtkTreeModel *tree_model, GtkTreeIter *iter, int column, GValue *value) +{ +    FMListModel *model; +    FileEntry *file_entry; +    CajaFile *file; +    char *str; +    GdkPixbuf *icon, *rendered_icon; +    int icon_size; +    guint emblem_size; +    CajaZoomLevel zoom_level; +    GList *emblem_pixbufs; +    CajaFile *parent_file; +    char *emblems_to_ignore[3]; +    int i; +    CajaFileIconFlags flags; + +    model = (FMListModel *)tree_model; + +    g_return_if_fail (model->details->stamp == iter->stamp); +    g_return_if_fail (!g_sequence_iter_is_end (iter->user_data)); + +    file_entry = g_sequence_get (iter->user_data); +    file = file_entry->file; + +    switch (column) +    { +    case FM_LIST_MODEL_FILE_COLUMN: +        g_value_init (value, CAJA_TYPE_FILE); + +        g_value_set_object (value, file); +        break; +    case FM_LIST_MODEL_SUBDIRECTORY_COLUMN: +        g_value_init (value, CAJA_TYPE_DIRECTORY); + +        g_value_set_object (value, file_entry->subdirectory); +        break; +    case FM_LIST_MODEL_SMALLEST_ICON_COLUMN: +    case FM_LIST_MODEL_SMALLER_ICON_COLUMN: +    case FM_LIST_MODEL_SMALL_ICON_COLUMN: +    case FM_LIST_MODEL_STANDARD_ICON_COLUMN: +    case FM_LIST_MODEL_LARGE_ICON_COLUMN: +    case FM_LIST_MODEL_LARGER_ICON_COLUMN: +    case FM_LIST_MODEL_LARGEST_ICON_COLUMN: +        g_value_init (value, GDK_TYPE_PIXBUF); + +        if (file != NULL) +        { +            zoom_level = fm_list_model_get_zoom_level_from_column_id (column); +            icon_size = caja_get_icon_size_for_zoom_level (zoom_level); + +            flags = CAJA_FILE_ICON_FLAGS_USE_THUMBNAILS | +                    CAJA_FILE_ICON_FLAGS_FORCE_THUMBNAIL_SIZE | +                    CAJA_FILE_ICON_FLAGS_USE_MOUNT_ICON_AS_EMBLEM; +            if (model->details->drag_view != NULL) +            { +                GtkTreePath *path_a, *path_b; + +                gtk_tree_view_get_drag_dest_row (model->details->drag_view, +                                                 &path_a, +                                                 NULL); +                if (path_a != NULL) +                { +                    path_b = gtk_tree_model_get_path (tree_model, iter); + +                    if (gtk_tree_path_compare (path_a, path_b) == 0) +                    { +                        flags |= CAJA_FILE_ICON_FLAGS_FOR_DRAG_ACCEPT; +                    } + +                    gtk_tree_path_free (path_a); +                    gtk_tree_path_free (path_b); +                } +            } + +            icon = caja_file_get_icon_pixbuf (file, icon_size, TRUE, flags); + +            if (model->details->highlight_files != NULL && +                    g_list_find_custom (model->details->highlight_files, +                                        file, (GCompareFunc) caja_file_compare_location)) +            { +                rendered_icon = eel_gdk_pixbuf_render (icon, 1, 255, 255, 0, 0); + +                if (rendered_icon != NULL) +                { +                    g_object_unref (icon); +                    icon = rendered_icon; +                } +            } + +            g_value_set_object (value, icon); +            g_object_unref (icon); +        } +        break; +    case FM_LIST_MODEL_SMALLEST_EMBLEM_COLUMN: +    case FM_LIST_MODEL_SMALLER_EMBLEM_COLUMN: +    case FM_LIST_MODEL_SMALL_EMBLEM_COLUMN: +    case FM_LIST_MODEL_STANDARD_EMBLEM_COLUMN: +    case FM_LIST_MODEL_LARGE_EMBLEM_COLUMN: +    case FM_LIST_MODEL_LARGER_EMBLEM_COLUMN: +    case FM_LIST_MODEL_LARGEST_EMBLEM_COLUMN: +        g_value_init (value, GDK_TYPE_PIXBUF); + +        if (file != NULL) +        { +            parent_file = caja_file_get_parent (file); +            i = 0; +            emblems_to_ignore[i++] = CAJA_FILE_EMBLEM_NAME_TRASH; +            if (parent_file) +            { +                if (!caja_file_can_write (parent_file)) +                { +                    emblems_to_ignore[i++] = CAJA_FILE_EMBLEM_NAME_CANT_WRITE; +                } +                caja_file_unref (parent_file); +            } +            emblems_to_ignore[i++] = NULL; + +            zoom_level = fm_list_model_get_zoom_level_from_emblem_column_id (column); +            icon_size = caja_get_icon_size_for_zoom_level (zoom_level); +            emblem_size = caja_icon_get_emblem_size_for_icon_size (icon_size); +            if (emblem_size != 0) +            { +                emblem_pixbufs = caja_file_get_emblem_pixbufs (file, +                                 emblem_size, +                                 TRUE, +                                 emblems_to_ignore); +                if (emblem_pixbufs != NULL) +                { +                    icon = emblem_pixbufs->data; +                    g_value_set_object (value, icon); +                } +                eel_gdk_pixbuf_list_free (emblem_pixbufs); +            } +        } +        break; +    case FM_LIST_MODEL_FILE_NAME_IS_EDITABLE_COLUMN: +        g_value_init (value, G_TYPE_BOOLEAN); + +        g_value_set_boolean (value, file != NULL && caja_file_can_rename (file)); +        break; +    default: +        if (column >= FM_LIST_MODEL_NUM_COLUMNS || column < FM_LIST_MODEL_NUM_COLUMNS + model->details->columns->len) +        { +            CajaColumn *caja_column; +            GQuark attribute; +            caja_column = model->details->columns->pdata[column - FM_LIST_MODEL_NUM_COLUMNS]; + +            g_value_init (value, G_TYPE_STRING); +            g_object_get (caja_column, +                          "attribute_q", &attribute, +                          NULL); +            if (file != NULL) +            { +                str = caja_file_get_string_attribute_with_default_q (file, +                        attribute); +                g_value_take_string (value, str); +            } +            else if (attribute == attribute_name_q) +            { +                if (file_entry->parent->loaded) +                { +                    g_value_set_string (value, _("(Empty)")); +                } +                else +                { +                    g_value_set_string (value, _("Loading...")); +                } +            } +        } +        else +        { +            g_assert_not_reached (); +        } +    } +} + +static gboolean +fm_list_model_iter_next (GtkTreeModel *tree_model, GtkTreeIter *iter) +{ +    FMListModel *model; + +    model = (FMListModel *)tree_model; + +    g_return_val_if_fail (model->details->stamp == iter->stamp, FALSE); + +    iter->user_data = g_sequence_iter_next (iter->user_data); + +    return !g_sequence_iter_is_end (iter->user_data); +} + +static gboolean +fm_list_model_iter_children (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *parent) +{ +    FMListModel *model; +    GSequence *files; +    FileEntry *file_entry; + +    model = (FMListModel *)tree_model; + +    if (parent == NULL) +    { +        files = model->details->files; +    } +    else +    { +        file_entry = g_sequence_get (parent->user_data); +        files = file_entry->files; +    } + +    if (files == NULL || g_sequence_get_length (files) == 0) +    { +        return FALSE; +    } + +    iter->stamp = model->details->stamp; +    iter->user_data = g_sequence_get_begin_iter (files); + +    return TRUE; +} + +static gboolean +fm_list_model_iter_has_child (GtkTreeModel *tree_model, GtkTreeIter *iter) +{ +    FileEntry *file_entry; + +    if (iter == NULL) +    { +        return !fm_list_model_is_empty (FM_LIST_MODEL (tree_model)); +    } + +    file_entry = g_sequence_get (iter->user_data); + +    return (file_entry->files != NULL && g_sequence_get_length (file_entry->files) > 0); +} + +static int +fm_list_model_iter_n_children (GtkTreeModel *tree_model, GtkTreeIter *iter) +{ +    FMListModel *model; +    GSequence *files; +    FileEntry *file_entry; + +    model = (FMListModel *)tree_model; + +    if (iter == NULL) +    { +        files = model->details->files; +    } +    else +    { +        file_entry = g_sequence_get (iter->user_data); +        files = file_entry->files; +    } + +    return g_sequence_get_length (files); +} + +static gboolean +fm_list_model_iter_nth_child (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *parent, int n) +{ +    FMListModel *model; +    GSequenceIter *child; +    GSequence *files; +    FileEntry *file_entry; + +    model = (FMListModel *)tree_model; + +    if (parent != NULL) +    { +        file_entry = g_sequence_get (parent->user_data); +        files = file_entry->files; +    } +    else +    { +        files = model->details->files; +    } + +    child = g_sequence_get_iter_at_pos (files, n); + +    if (g_sequence_iter_is_end (child)) +    { +        return FALSE; +    } + +    iter->stamp = model->details->stamp; +    iter->user_data = child; + +    return TRUE; +} + +static gboolean +fm_list_model_iter_parent (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *child) +{ +    FMListModel *model; +    FileEntry *file_entry; + +    model = (FMListModel *)tree_model; + +    file_entry = g_sequence_get (child->user_data); + +    if (file_entry->parent == NULL) +    { +        return FALSE; +    } + +    iter->stamp = model->details->stamp; +    iter->user_data = file_entry->parent->ptr; + +    return TRUE; +} + +static GSequenceIter * +lookup_file (FMListModel *model, CajaFile *file, +             CajaDirectory *directory) +{ +    FileEntry *file_entry; +    GSequenceIter *ptr, *parent_ptr; + +    parent_ptr = NULL; +    if (directory) +    { +        parent_ptr = g_hash_table_lookup (model->details->directory_reverse_map, +                                          directory); +    } + +    if (parent_ptr) +    { +        file_entry = g_sequence_get (parent_ptr); +        ptr = g_hash_table_lookup (file_entry->reverse_map, file); +    } +    else +    { +        ptr = g_hash_table_lookup (model->details->top_reverse_map, file); +    } + +    if (ptr) +    { +        g_assert (((FileEntry *)g_sequence_get (ptr))->file == file); +    } + +    return ptr; +} + + +struct GetIters +{ +    FMListModel *model; +    CajaFile *file; +    GList *iters; +}; + +static void +dir_to_iters (struct GetIters *data, +              GHashTable *reverse_map) +{ +    GSequenceIter *ptr; + +    ptr = g_hash_table_lookup (reverse_map, data->file); +    if (ptr) +    { +        GtkTreeIter *iter; +        iter = g_new0 (GtkTreeIter, 1); +        fm_list_model_ptr_to_iter (data->model, ptr, iter); +        data->iters = g_list_prepend (data->iters, iter); +    } +} + +static void +file_to_iter_cb (gpointer  key, +                 gpointer  value, +                 gpointer  user_data) +{ +    struct GetIters *data; +    FileEntry *dir_file_entry; + +    data = user_data; +    dir_file_entry = g_sequence_get ((GSequenceIter *)value); +    dir_to_iters (data, dir_file_entry->reverse_map); +} + +GList * +fm_list_model_get_all_iters_for_file (FMListModel *model, CajaFile *file) +{ +    struct GetIters data; + +    data.file = file; +    data.model = model; +    data.iters = NULL; + +    dir_to_iters (&data, model->details->top_reverse_map); +    g_hash_table_foreach (model->details->directory_reverse_map, +                          file_to_iter_cb, &data); + +    return g_list_reverse (data.iters); +} + +gboolean +fm_list_model_get_first_iter_for_file (FMListModel          *model, +                                       CajaFile         *file, +                                       GtkTreeIter          *iter) +{ +    GList *list; +    gboolean res; + +    res = FALSE; + +    list = fm_list_model_get_all_iters_for_file (model, file); +    if (list != NULL) +    { +        res = TRUE; +        *iter = *(GtkTreeIter *)list->data; +    } +    eel_g_list_free_deep (list); + +    return res; +} + + +gboolean +fm_list_model_get_tree_iter_from_file (FMListModel *model, CajaFile *file, +                                       CajaDirectory *directory, +                                       GtkTreeIter *iter) +{ +    GSequenceIter *ptr; + +    ptr = lookup_file (model, file, directory); +    if (!ptr) +    { +        return FALSE; +    } + +    fm_list_model_ptr_to_iter (model, ptr, iter); + +    return TRUE; +} + +static int +fm_list_model_file_entry_compare_func (gconstpointer a, +                                       gconstpointer b, +                                       gpointer      user_data) +{ +    FileEntry *file_entry1; +    FileEntry *file_entry2; +    FMListModel *model; +    int result; + +    model = (FMListModel *)user_data; + +    file_entry1 = (FileEntry *)a; +    file_entry2 = (FileEntry *)b; + +    if (file_entry1->file != NULL && file_entry2->file != NULL) +    { +        result = caja_file_compare_for_sort_by_attribute_q (file_entry1->file, file_entry2->file, +                 model->details->sort_attribute, +                 model->details->sort_directories_first, +                 (model->details->order == GTK_SORT_DESCENDING)); +    } +    else if (file_entry1->file == NULL) +    { +        return -1; +    } +    else +    { +        return 1; +    } + +    return result; +} + +int +fm_list_model_compare_func (FMListModel *model, +                            CajaFile *file1, +                            CajaFile *file2) +{ +    int result; + +    result = caja_file_compare_for_sort_by_attribute_q (file1, file2, +             model->details->sort_attribute, +             model->details->sort_directories_first, +             (model->details->order == GTK_SORT_DESCENDING)); + +    return result; +} + +static void +fm_list_model_sort_file_entries (FMListModel *model, GSequence *files, GtkTreePath *path) +{ +    GSequenceIter **old_order; +    GtkTreeIter iter; +    int *new_order; +    int length; +    int i; +    FileEntry *file_entry; +    gboolean has_iter; + +    length = g_sequence_get_length (files); + +    if (length <= 1) +    { +        return; +    } + +    /* generate old order of GSequenceIter's */ +    old_order = g_new (GSequenceIter *, length); +    for (i = 0; i < length; ++i) +    { +        GSequenceIter *ptr = g_sequence_get_iter_at_pos (files, i); + +        file_entry = g_sequence_get (ptr); +        if (file_entry->files != NULL) +        { +            gtk_tree_path_append_index (path, i); +            fm_list_model_sort_file_entries (model, file_entry->files, path); +            gtk_tree_path_up (path); +        } + +        old_order[i] = ptr; +    } + +    /* sort */ +    g_sequence_sort (files, fm_list_model_file_entry_compare_func, model); + +    /* generate new order */ +    new_order = g_new (int, length); +    /* Note: new_order[newpos] = oldpos */ +    for (i = 0; i < length; ++i) +    { +        new_order[g_sequence_iter_get_position (old_order[i])] = i; +    } + +    /* Let the world know about our new order */ + +    g_assert (new_order != NULL); + +    has_iter = FALSE; +    if (gtk_tree_path_get_depth (path) != 0) +    { +        gboolean get_iter_result; +        has_iter = TRUE; +        get_iter_result = gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path); +        g_assert (get_iter_result); +    } + +    gtk_tree_model_rows_reordered (GTK_TREE_MODEL (model), +                                   path, has_iter ? &iter : NULL, new_order); + +    g_free (old_order); +    g_free (new_order); +} + +static void +fm_list_model_sort (FMListModel *model) +{ +    GtkTreePath *path; + +    path = gtk_tree_path_new (); + +    fm_list_model_sort_file_entries (model, model->details->files, path); + +    gtk_tree_path_free (path); +} + +static gboolean +fm_list_model_get_sort_column_id (GtkTreeSortable *sortable, +                                  gint            *sort_column_id, +                                  GtkSortType     *order) +{ +    FMListModel *model; +    int id; + +    model = (FMListModel *)sortable; + +    id = fm_list_model_get_sort_column_id_from_attribute +         (model, model->details->sort_attribute); + +    if (id == -1) +    { +        return FALSE; +    } + +    if (sort_column_id != NULL) +    { +        *sort_column_id = id; +    } + +    if (order != NULL) +    { +        *order = model->details->order; +    } + +    return TRUE; +} + +static void +fm_list_model_set_sort_column_id (GtkTreeSortable *sortable, gint sort_column_id, GtkSortType order) +{ +    FMListModel *model; + +    model = (FMListModel *)sortable; + +    model->details->sort_attribute = fm_list_model_get_attribute_from_sort_column_id (model, sort_column_id); + +    model->details->order = order; + +    fm_list_model_sort (model); +    gtk_tree_sortable_sort_column_changed (sortable); +} + +static gboolean +fm_list_model_has_default_sort_func (GtkTreeSortable *sortable) +{ +    return FALSE; +} + +static gboolean +fm_list_model_multi_row_draggable (EggTreeMultiDragSource *drag_source, GList *path_list) +{ +    return TRUE; +} + +static void +each_path_get_data_binder (CajaDragEachSelectedItemDataGet data_get, +                           gpointer context, +                           gpointer data) +{ +    DragDataGetInfo *info; +    GList *l; +    CajaFile *file; +    GtkTreeRowReference *row; +    GtkTreePath *path; +    char *uri; +    GdkRectangle cell_area; +    GtkTreeViewColumn *column; + +    info = context; + +    g_return_if_fail (info->model->details->drag_view); + +    column = gtk_tree_view_get_column (info->model->details->drag_view, 0); + +    for (l = info->path_list; l != NULL; l = l->next) +    { +        row = l->data; + +        path = gtk_tree_row_reference_get_path (row); +        file = fm_list_model_file_for_path (info->model, path); +        if (file) +        { +            gtk_tree_view_get_cell_area +            (info->model->details->drag_view, +             path, +             column, +             &cell_area); + +            uri = caja_file_get_uri (file); + +            (*data_get) (uri, +                         0, +                         cell_area.y - info->model->details->drag_begin_y, +                         cell_area.width, cell_area.height, +                         data); + +            g_free (uri); + +            caja_file_unref (file); +        } + +        gtk_tree_path_free (path); +    } +} + +static gboolean +fm_list_model_multi_drag_data_get (EggTreeMultiDragSource *drag_source, +                                   GList *path_list, +                                   GtkSelectionData *selection_data) +{ +    FMListModel *model; +    DragDataGetInfo context; +    guint target_info; + +    model = FM_LIST_MODEL (drag_source); + +    context.model = model; +    context.path_list = path_list; + +    if (!drag_target_list) +    { +        drag_target_list = fm_list_model_get_drag_target_list (); +    } + +    if (gtk_target_list_find (drag_target_list, +                              gtk_selection_data_get_target (selection_data), +                              &target_info)) +    { +        caja_drag_drag_data_get (NULL, +                                 NULL, +                                 selection_data, +                                 target_info, +                                 GDK_CURRENT_TIME, +                                 &context, +                                 each_path_get_data_binder); +        return TRUE; +    } +    else +    { +        return FALSE; +    } +} + +static gboolean +fm_list_model_multi_drag_data_delete (EggTreeMultiDragSource *drag_source, GList *path_list) +{ +    return TRUE; +} + +static void +add_dummy_row (FMListModel *model, FileEntry *parent_entry) +{ +    FileEntry *dummy_file_entry; +    GtkTreeIter iter; +    GtkTreePath *path; + +    dummy_file_entry = g_new0 (FileEntry, 1); +    dummy_file_entry->parent = parent_entry; +    dummy_file_entry->ptr = g_sequence_insert_sorted (parent_entry->files, dummy_file_entry, +                            fm_list_model_file_entry_compare_func, model); +    iter.stamp = model->details->stamp; +    iter.user_data = dummy_file_entry->ptr; + +    path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); +    gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter); +    gtk_tree_path_free (path); +} + +gboolean +fm_list_model_add_file (FMListModel *model, CajaFile *file, +                        CajaDirectory *directory) +{ +    GtkTreeIter iter; +    GtkTreePath *path; +    FileEntry *file_entry; +    GSequenceIter *ptr, *parent_ptr; +    GSequence *files; +    gboolean replace_dummy; +    GHashTable *parent_hash; + +    parent_ptr = g_hash_table_lookup (model->details->directory_reverse_map, +                                      directory); +    if (parent_ptr) +    { +        file_entry = g_sequence_get (parent_ptr); +        ptr = g_hash_table_lookup (file_entry->reverse_map, file); +    } +    else +    { +        file_entry = NULL; +        ptr = g_hash_table_lookup (model->details->top_reverse_map, file); +    } + +    if (ptr != NULL) +    { +        g_warning ("file already in tree (parent_ptr: %p)!!!\n", parent_ptr); +        return FALSE; +    } + +    file_entry = g_new0 (FileEntry, 1); +    file_entry->file = caja_file_ref (file); +    file_entry->parent = NULL; +    file_entry->subdirectory = NULL; +    file_entry->files = NULL; + +    files = model->details->files; +    parent_hash = model->details->top_reverse_map; + +    replace_dummy = FALSE; + +    if (parent_ptr != NULL) +    { +        file_entry->parent = g_sequence_get (parent_ptr); +        /* At this point we set loaded. Either we saw +         * "done" and ignored it waiting for this, or we do this +         * earlier, but then we replace the dummy row anyway, +         * so it doesn't matter */ +        file_entry->parent->loaded = 1; +        parent_hash = file_entry->parent->reverse_map; +        files = file_entry->parent->files; +        if (g_sequence_get_length (files) == 1) +        { +            GSequenceIter *dummy_ptr = g_sequence_get_iter_at_pos (files, 0); +            FileEntry *dummy_entry = g_sequence_get (dummy_ptr); +            if (dummy_entry->file == NULL) +            { +                /* replace the dummy loading entry */ +                model->details->stamp++; +                g_sequence_remove (dummy_ptr); + +                replace_dummy = TRUE; +            } +        } +    } + + +    file_entry->ptr = g_sequence_insert_sorted (files, file_entry, +                      fm_list_model_file_entry_compare_func, model); + +    g_hash_table_insert (parent_hash, file, file_entry->ptr); + +    iter.stamp = model->details->stamp; +    iter.user_data = file_entry->ptr; + +    path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); +    if (replace_dummy) +    { +        gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter); +    } +    else +    { +        gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter); +    } + +    if (caja_file_is_directory (file)) +    { +        file_entry->files = g_sequence_new ((GDestroyNotify)file_entry_free); + +        add_dummy_row (model, file_entry); + +        gtk_tree_model_row_has_child_toggled (GTK_TREE_MODEL (model), +                                              path, &iter); +    } +    gtk_tree_path_free (path); + +    return TRUE; +} + +void +fm_list_model_file_changed (FMListModel *model, CajaFile *file, +                            CajaDirectory *directory) +{ +    FileEntry *parent_file_entry; +    GtkTreeIter iter; +    GtkTreePath *path, *parent_path; +    GSequenceIter *ptr; +    int pos_before, pos_after, length, i, old; +    int *new_order; +    gboolean has_iter; +    GSequence *files; + +    ptr = lookup_file (model, file, directory); +    if (!ptr) +    { +        return; +    } + + +    pos_before = g_sequence_iter_get_position (ptr); + +    g_sequence_sort_changed (ptr, fm_list_model_file_entry_compare_func, model); + +    pos_after = g_sequence_iter_get_position (ptr); + +    if (pos_before != pos_after) +    { +        /* The file moved, we need to send rows_reordered */ + +        parent_file_entry = ((FileEntry *)g_sequence_get (ptr))->parent; + +        if (parent_file_entry == NULL) +        { +            has_iter = FALSE; +            parent_path = gtk_tree_path_new (); +            files = model->details->files; +        } +        else +        { +            has_iter = TRUE; +            fm_list_model_ptr_to_iter (model, parent_file_entry->ptr, &iter); +            parent_path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); +            files = parent_file_entry->files; +        } + +        length = g_sequence_get_length (files); +        new_order = g_new (int, length); +        /* Note: new_order[newpos] = oldpos */ +        for (i = 0, old = 0; i < length; ++i) +        { +            if (i == pos_after) +            { +                new_order[i] = pos_before; +            } +            else +            { +                if (old == pos_before) +                    old++; +                new_order[i] = old++; +            } +        } + +        gtk_tree_model_rows_reordered (GTK_TREE_MODEL (model), +                                       parent_path, has_iter ? &iter : NULL, new_order); + +        gtk_tree_path_free (parent_path); +        g_free (new_order); +    } + +    fm_list_model_ptr_to_iter (model, ptr, &iter); +    path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); +    gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter); +    gtk_tree_path_free (path); +} + +gboolean +fm_list_model_is_empty (FMListModel *model) +{ +    return (g_sequence_get_length (model->details->files) == 0); +} + +guint +fm_list_model_get_length (FMListModel *model) +{ +    return g_sequence_get_length (model->details->files); +} + +static void +fm_list_model_remove (FMListModel *model, GtkTreeIter *iter) +{ +    GSequenceIter *ptr, *child_ptr; +    FileEntry *file_entry, *child_file_entry, *parent_file_entry; +    GtkTreePath *path; +    GtkTreeIter parent_iter; + +    ptr = iter->user_data; +    file_entry = g_sequence_get (ptr); + +    if (file_entry->files != NULL) +    { +        while (g_sequence_get_length (file_entry->files) > 0) +        { +            child_ptr = g_sequence_get_begin_iter (file_entry->files); +            child_file_entry = g_sequence_get (child_ptr); +            if (child_file_entry->file != NULL) +            { +                fm_list_model_remove_file (model, +                                           child_file_entry->file, +                                           file_entry->subdirectory); +            } +            else +            { +                path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter); +                gtk_tree_path_append_index (path, 0); +                model->details->stamp++; +                g_sequence_remove (child_ptr); +                gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path); +                gtk_tree_path_free (path); +            } + +            /* the parent iter didn't actually change */ +            iter->stamp = model->details->stamp; +        } + +    } + +    if (file_entry->file != NULL)   /* Don't try to remove dummy row */ +    { +        if (file_entry->parent != NULL) +        { +            g_hash_table_remove (file_entry->parent->reverse_map, file_entry->file); +        } +        else +        { +            g_hash_table_remove (model->details->top_reverse_map, file_entry->file); +        } +    } + +    parent_file_entry = file_entry->parent; +    if (parent_file_entry && g_sequence_get_length (parent_file_entry->files) == 1 && +            file_entry->file != NULL) +    { +        /* this is the last non-dummy child, add a dummy node */ +        /* We need to do this before removing the last file to avoid +         * collapsing the row. +         */ +        add_dummy_row (model, parent_file_entry); +    } + +    if (file_entry->subdirectory != NULL) +    { +        g_signal_emit (model, +                       list_model_signals[SUBDIRECTORY_UNLOADED], 0, +                       file_entry->subdirectory); +        g_hash_table_remove (model->details->directory_reverse_map, +                             file_entry->subdirectory); +    } + +    path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter); + +    g_sequence_remove (ptr); +    model->details->stamp++; +    gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path); + +    gtk_tree_path_free (path); + +    if (parent_file_entry && g_sequence_get_length (parent_file_entry->files) == 0) +    { +        parent_iter.stamp = model->details->stamp; +        parent_iter.user_data = parent_file_entry->ptr; +        path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &parent_iter); +        gtk_tree_model_row_has_child_toggled (GTK_TREE_MODEL (model), +                                              path, &parent_iter); +        gtk_tree_path_free (path); +    } +} + +void +fm_list_model_remove_file (FMListModel *model, CajaFile *file, +                           CajaDirectory *directory) +{ +    GtkTreeIter iter; + +    if (fm_list_model_get_tree_iter_from_file (model, file, directory, &iter)) +    { +        fm_list_model_remove (model, &iter); +    } +} + +static void +fm_list_model_clear_directory (FMListModel *model, GSequence *files) +{ +    GtkTreeIter iter; +    FileEntry *file_entry; + +    while (g_sequence_get_length (files) > 0) +    { +        iter.user_data = g_sequence_get_begin_iter (files); + +        file_entry = g_sequence_get (iter.user_data); +        if (file_entry->files != NULL) +        { +            fm_list_model_clear_directory (model, file_entry->files); +        } + +        iter.stamp = model->details->stamp; +        fm_list_model_remove (model, &iter); +    } +} + +void +fm_list_model_clear (FMListModel *model) +{ +    g_return_if_fail (model != NULL); + +    fm_list_model_clear_directory (model, model->details->files); +} + +CajaFile * +fm_list_model_file_for_path (FMListModel *model, GtkTreePath *path) +{ +    CajaFile *file; +    GtkTreeIter iter; + +    file = NULL; +    if (gtk_tree_model_get_iter (GTK_TREE_MODEL (model), +                                 &iter, path)) +    { +        gtk_tree_model_get (GTK_TREE_MODEL (model), +                            &iter, +                            FM_LIST_MODEL_FILE_COLUMN, &file, +                            -1); +    } +    return file; +} + +gboolean +fm_list_model_load_subdirectory (FMListModel *model, GtkTreePath *path, CajaDirectory **directory) +{ +    GtkTreeIter iter; +    FileEntry *file_entry; +    CajaDirectory *subdirectory; + +    if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path)) +    { +        return FALSE; +    } + +    file_entry = g_sequence_get (iter.user_data); +    if (file_entry->file == NULL || +            file_entry->subdirectory != NULL) +    { +        return FALSE; +    } + +    subdirectory = caja_directory_get_for_file (file_entry->file); + +    if (g_hash_table_lookup (model->details->directory_reverse_map, +                             subdirectory) != NULL) +    { +        caja_directory_unref (subdirectory); +        g_warning ("Already in directory_reverse_map, failing\n"); +        return FALSE; +    } + +    file_entry->subdirectory = subdirectory, +                g_hash_table_insert (model->details->directory_reverse_map, +                                     subdirectory, file_entry->ptr); +    file_entry->reverse_map = g_hash_table_new (g_direct_hash, g_direct_equal); + +    /* Return a ref too */ +    caja_directory_ref (subdirectory); +    *directory = subdirectory; + +    return TRUE; +} + +/* removes all children of the subfolder and unloads the subdirectory */ +void +fm_list_model_unload_subdirectory (FMListModel *model, GtkTreeIter *iter) +{ +    GSequenceIter *child_ptr; +    FileEntry *file_entry, *child_file_entry; +    GtkTreeIter child_iter; + +    file_entry = g_sequence_get (iter->user_data); +    if (file_entry->file == NULL || +            file_entry->subdirectory == NULL) +    { +        return; +    } + +    file_entry->loaded = 0; + +    /* Remove all children */ +    while (g_sequence_get_length (file_entry->files) > 0) +    { +        child_ptr = g_sequence_get_begin_iter (file_entry->files); +        child_file_entry = g_sequence_get (child_ptr); +        if (child_file_entry->file == NULL) +        { +            /* Don't delete the dummy node */ +            break; +        } +        else +        { +            fm_list_model_ptr_to_iter (model, child_ptr, &child_iter); +            fm_list_model_remove (model, &child_iter); +        } +    } + +    /* Emit unload signal */ +    g_signal_emit (model, +                   list_model_signals[SUBDIRECTORY_UNLOADED], 0, +                   file_entry->subdirectory); + +    /* actually unload */ +    g_hash_table_remove (model->details->directory_reverse_map, +                         file_entry->subdirectory); +    caja_directory_unref (file_entry->subdirectory); +    file_entry->subdirectory = NULL; + +    g_assert (g_hash_table_size (file_entry->reverse_map) == 0); +    g_hash_table_destroy (file_entry->reverse_map); +    file_entry->reverse_map = NULL; +} + + + +void +fm_list_model_set_should_sort_directories_first (FMListModel *model, gboolean sort_directories_first) +{ +    if (model->details->sort_directories_first == sort_directories_first) +    { +        return; +    } + +    model->details->sort_directories_first = sort_directories_first; +    fm_list_model_sort (model); +} + +int +fm_list_model_get_sort_column_id_from_attribute (FMListModel *model, +        GQuark attribute) +{ +    guint i; + +    if (attribute == 0) +    { +        return -1; +    } + +    /* Hack - the preferences dialog sets modification_date for some +     * rather than date_modified for some reason.  Make sure that +     * works. */ +    if (attribute == attribute_modification_date_q) +    { +        attribute = attribute_date_modified_q; +    } + +    for (i = 0; i < model->details->columns->len; i++) +    { +        CajaColumn *column; +        GQuark column_attribute; + +        column = +            CAJA_COLUMN (model->details->columns->pdata[i]); +        g_object_get (G_OBJECT (column), +                      "attribute_q", &column_attribute, +                      NULL); +        if (column_attribute == attribute) +        { +            return FM_LIST_MODEL_NUM_COLUMNS + i; +        } +    } + +    return -1; +} + +GQuark +fm_list_model_get_attribute_from_sort_column_id (FMListModel *model, +        int sort_column_id) +{ +    CajaColumn *column; +    int index; +    GQuark attribute; + +    index = sort_column_id - FM_LIST_MODEL_NUM_COLUMNS; + +    if (index < 0 || index >= model->details->columns->len) +    { +        g_warning ("unknown sort column id: %d", sort_column_id); +        return 0; +    } + +    column = CAJA_COLUMN (model->details->columns->pdata[index]); +    g_object_get (G_OBJECT (column), "attribute_q", &attribute, NULL); + +    return attribute; +} + +CajaZoomLevel +fm_list_model_get_zoom_level_from_column_id (int column) +{ +    switch (column) +    { +    case FM_LIST_MODEL_SMALLEST_ICON_COLUMN: +        return CAJA_ZOOM_LEVEL_SMALLEST; +    case FM_LIST_MODEL_SMALLER_ICON_COLUMN: +        return CAJA_ZOOM_LEVEL_SMALLER; +    case FM_LIST_MODEL_SMALL_ICON_COLUMN: +        return CAJA_ZOOM_LEVEL_SMALL; +    case FM_LIST_MODEL_STANDARD_ICON_COLUMN: +        return CAJA_ZOOM_LEVEL_STANDARD; +    case FM_LIST_MODEL_LARGE_ICON_COLUMN: +        return CAJA_ZOOM_LEVEL_LARGE; +    case FM_LIST_MODEL_LARGER_ICON_COLUMN: +        return CAJA_ZOOM_LEVEL_LARGER; +    case FM_LIST_MODEL_LARGEST_ICON_COLUMN: +        return CAJA_ZOOM_LEVEL_LARGEST; +    } + +    g_return_val_if_reached (CAJA_ZOOM_LEVEL_STANDARD); +} + +int +fm_list_model_get_column_id_from_zoom_level (CajaZoomLevel zoom_level) +{ +    switch (zoom_level) +    { +    case CAJA_ZOOM_LEVEL_SMALLEST: +        return FM_LIST_MODEL_SMALLEST_ICON_COLUMN; +    case CAJA_ZOOM_LEVEL_SMALLER: +        return FM_LIST_MODEL_SMALLER_ICON_COLUMN; +    case CAJA_ZOOM_LEVEL_SMALL: +        return FM_LIST_MODEL_SMALL_ICON_COLUMN; +    case CAJA_ZOOM_LEVEL_STANDARD: +        return FM_LIST_MODEL_STANDARD_ICON_COLUMN; +    case CAJA_ZOOM_LEVEL_LARGE: +        return FM_LIST_MODEL_LARGE_ICON_COLUMN; +    case CAJA_ZOOM_LEVEL_LARGER: +        return FM_LIST_MODEL_LARGER_ICON_COLUMN; +    case CAJA_ZOOM_LEVEL_LARGEST: +        return FM_LIST_MODEL_LARGEST_ICON_COLUMN; +    } + +    g_return_val_if_reached (FM_LIST_MODEL_STANDARD_ICON_COLUMN); +} + +CajaZoomLevel +fm_list_model_get_zoom_level_from_emblem_column_id (int column) +{ +    switch (column) +    { +    case FM_LIST_MODEL_SMALLEST_EMBLEM_COLUMN: +        return CAJA_ZOOM_LEVEL_SMALLEST; +    case FM_LIST_MODEL_SMALLER_EMBLEM_COLUMN: +        return CAJA_ZOOM_LEVEL_SMALLER; +    case FM_LIST_MODEL_SMALL_EMBLEM_COLUMN: +        return CAJA_ZOOM_LEVEL_SMALL; +    case FM_LIST_MODEL_STANDARD_EMBLEM_COLUMN: +        return CAJA_ZOOM_LEVEL_STANDARD; +    case FM_LIST_MODEL_LARGE_EMBLEM_COLUMN: +        return CAJA_ZOOM_LEVEL_LARGE; +    case FM_LIST_MODEL_LARGER_EMBLEM_COLUMN: +        return CAJA_ZOOM_LEVEL_LARGER; +    case FM_LIST_MODEL_LARGEST_EMBLEM_COLUMN: +        return CAJA_ZOOM_LEVEL_LARGEST; +    } + +    g_return_val_if_reached (CAJA_ZOOM_LEVEL_STANDARD); +} + +int +fm_list_model_get_emblem_column_id_from_zoom_level (CajaZoomLevel zoom_level) +{ +    switch (zoom_level) +    { +    case CAJA_ZOOM_LEVEL_SMALLEST: +        return FM_LIST_MODEL_SMALLEST_EMBLEM_COLUMN; +    case CAJA_ZOOM_LEVEL_SMALLER: +        return FM_LIST_MODEL_SMALLER_EMBLEM_COLUMN; +    case CAJA_ZOOM_LEVEL_SMALL: +        return FM_LIST_MODEL_SMALL_EMBLEM_COLUMN; +    case CAJA_ZOOM_LEVEL_STANDARD: +        return FM_LIST_MODEL_STANDARD_EMBLEM_COLUMN; +    case CAJA_ZOOM_LEVEL_LARGE: +        return FM_LIST_MODEL_LARGE_EMBLEM_COLUMN; +    case CAJA_ZOOM_LEVEL_LARGER: +        return FM_LIST_MODEL_LARGER_EMBLEM_COLUMN; +    case CAJA_ZOOM_LEVEL_LARGEST: +        return FM_LIST_MODEL_LARGEST_EMBLEM_COLUMN; +    } + +    g_return_val_if_reached (FM_LIST_MODEL_STANDARD_EMBLEM_COLUMN); +} + +void +fm_list_model_set_drag_view (FMListModel *model, +                             GtkTreeView *view, +                             int drag_begin_x, +                             int drag_begin_y) +{ +    g_return_if_fail (model != NULL); +    g_return_if_fail (FM_IS_LIST_MODEL (model)); +    g_return_if_fail (!view || GTK_IS_TREE_VIEW (view)); + +    model->details->drag_view = view; +    model->details->drag_begin_x = drag_begin_x; +    model->details->drag_begin_y = drag_begin_y; +} + +GtkTargetList * +fm_list_model_get_drag_target_list () +{ +    GtkTargetList *target_list; + +    target_list = gtk_target_list_new (drag_types, G_N_ELEMENTS (drag_types)); +    gtk_target_list_add_text_targets (target_list, CAJA_ICON_DND_TEXT); + +    return target_list; +} + +int +fm_list_model_add_column (FMListModel *model, +                          CajaColumn *column) +{ +    g_ptr_array_add (model->details->columns, column); +    g_object_ref (column); + +    return FM_LIST_MODEL_NUM_COLUMNS + (model->details->columns->len - 1); +} + +int +fm_list_model_get_column_number (FMListModel *model, +                                 const char *column_name) +{ +    int i; + +    for (i = 0; i < model->details->columns->len; i++) +    { +        CajaColumn *column; +        char *name; + +        column = model->details->columns->pdata[i]; + +        g_object_get (G_OBJECT (column), "name", &name, NULL); + +        if (!strcmp (name, column_name)) +        { +            g_free (name); +            return FM_LIST_MODEL_NUM_COLUMNS + i; +        } +        g_free (name); +    } + +    return -1; +} + +static void +fm_list_model_dispose (GObject *object) +{ +    FMListModel *model; +    int i; + +    model = FM_LIST_MODEL (object); + +    if (model->details->columns) +    { +        for (i = 0; i < model->details->columns->len; i++) +        { +            g_object_unref (model->details->columns->pdata[i]); +        } +        g_ptr_array_free (model->details->columns, TRUE); +        model->details->columns = NULL; +    } + +    if (model->details->files) +    { +        g_sequence_free (model->details->files); +        model->details->files = NULL; +    } + +    if (model->details->top_reverse_map) +    { +        g_hash_table_destroy (model->details->top_reverse_map); +        model->details->top_reverse_map = NULL; +    } +    if (model->details->directory_reverse_map) +    { +        g_hash_table_destroy (model->details->directory_reverse_map); +        model->details->directory_reverse_map = NULL; +    } + +    G_OBJECT_CLASS (fm_list_model_parent_class)->dispose (object); +} + +static void +fm_list_model_finalize (GObject *object) +{ +    FMListModel *model; + +    model = FM_LIST_MODEL (object); + +    if (model->details->highlight_files != NULL) +    { +        caja_file_list_free (model->details->highlight_files); +        model->details->highlight_files = NULL; +    } + +    g_free (model->details); + +    G_OBJECT_CLASS (fm_list_model_parent_class)->finalize (object); +} + +static void +fm_list_model_init (FMListModel *model) +{ +    model->details = g_new0 (FMListModelDetails, 1); +    model->details->files = g_sequence_new ((GDestroyNotify)file_entry_free); +    model->details->top_reverse_map = g_hash_table_new (g_direct_hash, g_direct_equal); +    model->details->directory_reverse_map = g_hash_table_new (g_direct_hash, g_direct_equal); +    model->details->stamp = g_random_int (); +    model->details->sort_attribute = 0; +    model->details->columns = g_ptr_array_new (); +} + +static void +fm_list_model_class_init (FMListModelClass *klass) +{ +    GObjectClass *object_class; + +    attribute_name_q = g_quark_from_static_string ("name"); +    attribute_modification_date_q = g_quark_from_static_string ("modification_date"); +    attribute_date_modified_q = g_quark_from_static_string ("date_modified"); + +    object_class = (GObjectClass *)klass; +    object_class->finalize = fm_list_model_finalize; +    object_class->dispose = fm_list_model_dispose; + +    list_model_signals[SUBDIRECTORY_UNLOADED] = +        g_signal_new ("subdirectory_unloaded", +                      FM_TYPE_LIST_MODEL, +                      G_SIGNAL_RUN_FIRST, +                      G_STRUCT_OFFSET (FMListModelClass, subdirectory_unloaded), +                      NULL, NULL, +                      g_cclosure_marshal_VOID__OBJECT, +                      G_TYPE_NONE, 1, +                      CAJA_TYPE_DIRECTORY); +} + +static void +fm_list_model_tree_model_init (GtkTreeModelIface *iface) +{ +    iface->get_flags = fm_list_model_get_flags; +    iface->get_n_columns = fm_list_model_get_n_columns; +    iface->get_column_type = fm_list_model_get_column_type; +    iface->get_iter = fm_list_model_get_iter; +    iface->get_path = fm_list_model_get_path; +    iface->get_value = fm_list_model_get_value; +    iface->iter_next = fm_list_model_iter_next; +    iface->iter_children = fm_list_model_iter_children; +    iface->iter_has_child = fm_list_model_iter_has_child; +    iface->iter_n_children = fm_list_model_iter_n_children; +    iface->iter_nth_child = fm_list_model_iter_nth_child; +    iface->iter_parent = fm_list_model_iter_parent; +} + +static void +fm_list_model_sortable_init (GtkTreeSortableIface *iface) +{ +    iface->get_sort_column_id = fm_list_model_get_sort_column_id; +    iface->set_sort_column_id = fm_list_model_set_sort_column_id; +    iface->has_default_sort_func = fm_list_model_has_default_sort_func; +} + +static void +fm_list_model_multi_drag_source_init (EggTreeMultiDragSourceIface *iface) +{ +    iface->row_draggable = fm_list_model_multi_row_draggable; +    iface->drag_data_get = fm_list_model_multi_drag_data_get; +    iface->drag_data_delete = fm_list_model_multi_drag_data_delete; +} + +void +fm_list_model_subdirectory_done_loading (FMListModel *model, CajaDirectory *directory) +{ +    GtkTreeIter iter; +    GtkTreePath *path; +    FileEntry *file_entry, *dummy_entry; +    GSequenceIter *parent_ptr, *dummy_ptr; +    GSequence *files; + +    if (model == NULL || model->details->directory_reverse_map == NULL) +    { +        return; +    } +    parent_ptr = g_hash_table_lookup (model->details->directory_reverse_map, +                                      directory); +    if (parent_ptr == NULL) +    { +        return; +    } + +    file_entry = g_sequence_get (parent_ptr); +    files = file_entry->files; + +    /* Only swap loading -> empty if we saw no files yet at "done", +     * otherwise, toggle loading at first added file to the model. +     */ +    if (!caja_directory_is_not_empty (directory) && +            g_sequence_get_length (files) == 1) +    { +        dummy_ptr = g_sequence_get_iter_at_pos (file_entry->files, 0); +        dummy_entry = g_sequence_get (dummy_ptr); +        if (dummy_entry->file == NULL) +        { +            /* was the dummy file */ +            file_entry->loaded = 1; + +            iter.stamp = model->details->stamp; +            iter.user_data = dummy_ptr; + +            path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); +            gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter); +            gtk_tree_path_free (path); +        } +    } +} + +static void +refresh_row (gpointer data, +             gpointer user_data) +{ +    CajaFile *file; +    FMListModel *model; +    GList *iters, *l; +    GtkTreePath *path; + +    model = user_data; +    file = data; + +    iters = fm_list_model_get_all_iters_for_file (model, file); +    for (l = iters; l != NULL; l = l->next) +    { +        path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), l->data); +        gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, l->data); + +        gtk_tree_path_free (path); +    } + +    eel_g_list_free_deep (iters); +} + +void +fm_list_model_set_highlight_for_files (FMListModel *model, +                                       GList *files) +{ +    if (model->details->highlight_files != NULL) +    { +        g_list_foreach (model->details->highlight_files, +                        refresh_row, model); +        caja_file_list_free (model->details->highlight_files); +        model->details->highlight_files = NULL; +    } + +    if (files != NULL) +    { +        model->details->highlight_files = caja_file_list_copy (files); +        g_list_foreach (model->details->highlight_files, +                        refresh_row, model); + +    } +} diff --git a/src/file-manager/fm-list-model.h b/src/file-manager/fm-list-model.h new file mode 100644 index 00000000..49f39078 --- /dev/null +++ b/src/file-manager/fm-list-model.h @@ -0,0 +1,148 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-list-model.h - a GtkTreeModel for file lists. + +   Copyright (C) 2001, 2002 Anders Carlsson + +   The Mate Library is free software; you can redistribute it and/or +   modify it under the terms of the GNU Library General Public License as +   published by the Free Software Foundation; either version 2 of the +   License, or (at your option) any later version. + +   The Mate Library is distributed in the hope that it will be useful, +   but WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +   Library General Public License for more details. + +   You should have received a copy of the GNU Library General Public +   License along with the Mate Library; see the file COPYING.LIB.  If not, +   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +   Boston, MA 02111-1307, USA. + +   Authors: Anders Carlsson <[email protected]> +*/ + +#include <gtk/gtk.h> +#include <gdk/gdk.h> +#include <libcaja-private/caja-file.h> +#include <libcaja-private/caja-directory.h> +#include <libcaja-extension/caja-column.h> + +#ifndef FM_LIST_MODEL_H +#define FM_LIST_MODEL_H + +#define FM_TYPE_LIST_MODEL fm_list_model_get_type() +#define FM_LIST_MODEL(obj) \ +  (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_LIST_MODEL, FMListModel)) +#define FM_LIST_MODEL_CLASS(klass) \ +  (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_LIST_MODEL, FMListModelClass)) +#define FM_IS_LIST_MODEL(obj) \ +  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_LIST_MODEL)) +#define FM_IS_LIST_MODEL_CLASS(klass) \ +  (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_LIST_MODEL)) +#define FM_LIST_MODEL_GET_CLASS(obj) \ +  (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_LIST_MODEL, FMListModelClass)) + +enum +{ +    FM_LIST_MODEL_FILE_COLUMN, +    FM_LIST_MODEL_SUBDIRECTORY_COLUMN, +    FM_LIST_MODEL_SMALLEST_ICON_COLUMN, +    FM_LIST_MODEL_SMALLER_ICON_COLUMN, +    FM_LIST_MODEL_SMALL_ICON_COLUMN, +    FM_LIST_MODEL_STANDARD_ICON_COLUMN, +    FM_LIST_MODEL_LARGE_ICON_COLUMN, +    FM_LIST_MODEL_LARGER_ICON_COLUMN, +    FM_LIST_MODEL_LARGEST_ICON_COLUMN, +    FM_LIST_MODEL_SMALLEST_EMBLEM_COLUMN, +    FM_LIST_MODEL_SMALLER_EMBLEM_COLUMN, +    FM_LIST_MODEL_SMALL_EMBLEM_COLUMN, +    FM_LIST_MODEL_STANDARD_EMBLEM_COLUMN, +    FM_LIST_MODEL_LARGE_EMBLEM_COLUMN, +    FM_LIST_MODEL_LARGER_EMBLEM_COLUMN, +    FM_LIST_MODEL_LARGEST_EMBLEM_COLUMN, +    FM_LIST_MODEL_FILE_NAME_IS_EDITABLE_COLUMN, +    FM_LIST_MODEL_NUM_COLUMNS +}; + +typedef struct FMListModelDetails FMListModelDetails; + +typedef struct FMListModel +{ +    GObject parent_instance; +    FMListModelDetails *details; +} FMListModel; + +typedef struct +{ +    GObjectClass parent_class; + +    void (* subdirectory_unloaded)(FMListModel *model, +                                   CajaDirectory *subdirectory); +} FMListModelClass; + +GType    fm_list_model_get_type                          (void); +gboolean fm_list_model_add_file                          (FMListModel          *model, +        CajaFile         *file, +        CajaDirectory    *directory); +void     fm_list_model_file_changed                      (FMListModel          *model, +        CajaFile         *file, +        CajaDirectory    *directory); +gboolean fm_list_model_is_empty                          (FMListModel          *model); +guint    fm_list_model_get_length                        (FMListModel          *model); +void     fm_list_model_remove_file                       (FMListModel          *model, +        CajaFile         *file, +        CajaDirectory    *directory); +void     fm_list_model_clear                             (FMListModel          *model); +gboolean fm_list_model_get_tree_iter_from_file           (FMListModel          *model, +        CajaFile         *file, +        CajaDirectory    *directory, +        GtkTreeIter          *iter); +GList *  fm_list_model_get_all_iters_for_file            (FMListModel          *model, +        CajaFile         *file); +gboolean fm_list_model_get_first_iter_for_file           (FMListModel          *model, +        CajaFile         *file, +        GtkTreeIter          *iter); +void     fm_list_model_set_should_sort_directories_first (FMListModel          *model, +        gboolean              sort_directories_first); + +int      fm_list_model_get_sort_column_id_from_attribute (FMListModel *model, +        GQuark       attribute); +GQuark   fm_list_model_get_attribute_from_sort_column_id (FMListModel *model, +        int sort_column_id); +void     fm_list_model_sort_files                        (FMListModel *model, +        GList **files); + +CajaZoomLevel fm_list_model_get_zoom_level_from_column_id (int               column); +int               fm_list_model_get_column_id_from_zoom_level (CajaZoomLevel zoom_level); +CajaZoomLevel fm_list_model_get_zoom_level_from_emblem_column_id (int               column); +int               fm_list_model_get_emblem_column_id_from_zoom_level (CajaZoomLevel zoom_level); + +CajaFile *    fm_list_model_file_for_path (FMListModel *model, GtkTreePath *path); +gboolean          fm_list_model_load_subdirectory (FMListModel *model, GtkTreePath *path, CajaDirectory **directory); +void              fm_list_model_unload_subdirectory (FMListModel *model, GtkTreeIter *iter); + +void              fm_list_model_set_drag_view (FMListModel *model, +        GtkTreeView *view, +        int begin_x, +        int begin_y); + +GtkTargetList *   fm_list_model_get_drag_target_list (void); + +int               fm_list_model_compare_func (FMListModel *model, +        CajaFile *file1, +        CajaFile *file2); + + +int               fm_list_model_add_column (FMListModel *model, +        CajaColumn *column); +int               fm_list_model_get_column_number (FMListModel *model, +        const char *column_name); + +void              fm_list_model_subdirectory_done_loading (FMListModel       *model, +        CajaDirectory *directory); + +void              fm_list_model_set_highlight_for_files (FMListModel *model, +        GList *files); + +#endif /* FM_LIST_MODEL_H */ diff --git a/src/file-manager/fm-list-view-private.h b/src/file-manager/fm-list-view-private.h new file mode 100644 index 00000000..2225cfca --- /dev/null +++ b/src/file-manager/fm-list-view-private.h @@ -0,0 +1,43 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-list-view-private.h - Private functions for both the list and search list +   view to share + +   Copyright (C) 2000 Eazel, Inc. + +   The Mate Library is free software; you can redistribute it and/or +   modify it under the terms of the GNU Library General Public License as +   published by the Free Software Foundation; either version 2 of the +   License, or (at your option) any later version. + +   The Mate Library is distributed in the hope that it will be useful, +   but WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +   Library General Public License for more details. + +   You should have received a copy of the GNU Library General Public +   License along with the Mate Library; see the file COPYING.LIB.  If not, +   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +   Boston, MA 02111-1307, USA. + +   Authors: Rebecca Schulman <[email protected]> + +*/ + +struct FMListViewColumn +{ +    const char *attribute; +    const char *title; +    CajaFileSortType sort_criterion; +    int minimum_width, default_width, maximum_width; +    gboolean right_justified; +}; + +void  fm_list_view_column_set    (FMListViewColumn     *column, +                                  const char           *attribute, +                                  const char           *title, +                                  CajaFileSortType  sort_criterion, +                                  int                   minimum_width, +                                  int                   default_width, +                                  int                   maximum_width, +                                  gboolean              right_justified); diff --git a/src/file-manager/fm-list-view.c b/src/file-manager/fm-list-view.c new file mode 100644 index 00000000..1c82af9b --- /dev/null +++ b/src/file-manager/fm-list-view.c @@ -0,0 +1,3415 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-list-view.c - implementation of list view of directory. + +   Copyright (C) 2000 Eazel, Inc. +   Copyright (C) 2001, 2002 Anders Carlsson <[email protected]> + +   The Mate Library is free software; you can redistribute it and/or +   modify it under the terms of the GNU Library General Public License as +   published by the Free Software Foundation; either version 2 of the +   License, or (at your option) any later version. + +   The Mate Library is distributed in the hope that it will be useful, +   but WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +   Library General Public License for more details. + +   You should have received a copy of the GNU Library General Public +   License along with the Mate Library; see the file COPYING.LIB.  If not, +   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +   Boston, MA 02111-1307, USA. + +   Authors: John Sullivan <[email protected]> +            Anders Carlsson <[email protected]> +	    David Emory Watson <[email protected]> +*/ + +#include <config.h> +#include "fm-list-view.h" + +#include <string.h> +#include "fm-error-reporting.h" +#include "fm-list-model.h" +#include <string.h> +#include <eel/eel-vfs-extensions.h> +#include <eel/eel-gdk-extensions.h> +#include <eel/eel-glib-extensions.h> +#include <eel/eel-gtk-macros.h> +#include <gdk/gdk.h> +#include <gdk/gdkkeysyms.h> +#include <gtk/gtk.h> +#include <libegg/eggtreemultidnd.h> +#include <glib/gi18n.h> +#include <glib-object.h> +#include <libcaja-extension/caja-column-provider.h> +#include <libcaja-private/caja-clipboard-monitor.h> +#include <libcaja-private/caja-column-chooser.h> +#include <libcaja-private/caja-column-utilities.h> +#include <libcaja-private/caja-debug-log.h> +#include <libcaja-private/caja-directory-background.h> +#include <libcaja-private/caja-dnd.h> +#include <libcaja-private/caja-file-dnd.h> +#include <libcaja-private/caja-file-utilities.h> +#include <libcaja-private/caja-ui-utilities.h> +#include <libcaja-private/caja-global-preferences.h> +#include <libcaja-private/caja-icon-dnd.h> +#include <libcaja-private/caja-metadata.h> +#include <libcaja-private/caja-module.h> +#include <libcaja-private/caja-tree-view-drag-dest.h> +#include <libcaja-private/caja-view-factory.h> +#include <libcaja-private/caja-clipboard.h> +#include <libcaja-private/caja-cell-renderer-pixbuf-emblem.h> +#include <libcaja-private/caja-cell-renderer-text-ellipsized.h> + +struct FMListViewDetails +{ +    GtkTreeView *tree_view; +    FMListModel *model; +    GtkActionGroup *list_action_group; +    guint list_merge_id; + +    GtkTreeViewColumn   *file_name_column; +    int file_name_column_num; + +    GtkCellRendererPixbuf *pixbuf_cell; +    GtkCellRendererText   *file_name_cell; +    GList *cells; +    GtkCellEditable *editable_widget; + +    CajaZoomLevel zoom_level; + +    CajaTreeViewDragDest *drag_dest; + +    GtkTreePath *double_click_path[2]; /* Both clicks in a double click need to be on the same row */ + +    GtkTreePath *new_selection_path;   /* Path of the new selection after removing a file */ + +    GtkTreePath *hover_path; + +    guint drag_button; +    int drag_x; +    int drag_y; + +    gboolean drag_started; + +    gboolean ignore_button_release; + +    gboolean row_selected_on_button_down; + +    gboolean menus_ready; + +    GHashTable *columns; +    GtkWidget *column_editor; + +    char *original_name; + +    CajaFile *renaming_file; +    gboolean rename_done; +    guint renaming_file_activate_timeout; + +    gulong clipboard_handler_id; + +    GQuark last_sort_attr; +}; + +struct SelectionForeachData +{ +    GList *list; +    GtkTreeSelection *selection; +}; + +/* + * The row height should be large enough to not clip emblems. + * Computing this would be costly, so we just choose a number + * that works well with the set of emblems we've designed. + */ +#define LIST_VIEW_MINIMUM_ROW_HEIGHT	28 + +/* We wait two seconds after row is collapsed to unload the subdirectory */ +#define COLLAPSE_TO_UNLOAD_DELAY 2 + +/* Wait for the rename to end when activating a file being renamed */ +#define WAIT_FOR_RENAME_ON_ACTIVATE 200 + +static int                      click_policy_auto_value; +static char *              	default_sort_order_auto_value; +static gboolean			default_sort_reversed_auto_value; +static CajaZoomLevel        default_zoom_level_auto_value; +static char **                  default_visible_columns_auto_value; +static char **                  default_column_order_auto_value; +static GdkCursor *              hand_cursor = NULL; + +static GtkTargetList *          source_target_list = NULL; + +static GList *fm_list_view_get_selection                   (FMDirectoryView   *view); +static GList *fm_list_view_get_selection_for_file_transfer (FMDirectoryView   *view); +static void   fm_list_view_set_zoom_level                  (FMListView        *view, +        CajaZoomLevel  new_level, +        gboolean           always_set_level); +static void   fm_list_view_scale_font_size                 (FMListView        *view, +        CajaZoomLevel  new_level); +static void   fm_list_view_scroll_to_file                  (FMListView        *view, +        CajaFile      *file); +static void   fm_list_view_iface_init                      (CajaViewIface *iface); +static void   fm_list_view_rename_callback                 (CajaFile      *file, +        GFile             *result_location, +        GError            *error, +        gpointer           callback_data); + + +G_DEFINE_TYPE_WITH_CODE (FMListView, fm_list_view, FM_TYPE_DIRECTORY_VIEW, +                         G_IMPLEMENT_INTERFACE (CAJA_TYPE_VIEW, +                                 fm_list_view_iface_init)); + +static const char * default_trash_visible_columns[] = +{ +    "name", "size", "type", "trashed_on", "trash_orig_path", NULL +}; + +static const char * default_trash_columns_order[] = +{ +    "name", "size", "type", "trashed_on", "trash_orig_path", NULL +}; + +/* for EEL_CALL_PARENT */ +#define parent_class fm_list_view_parent_class + +static const gchar* +get_default_sort_order (CajaFile *file, gboolean *reversed) +{ +    const gchar *retval; + +    retval = caja_file_get_default_sort_attribute (file, reversed); + +    if (retval == NULL) +    { +        retval = default_sort_order_auto_value; +        *reversed = default_sort_reversed_auto_value; +    } + +    return retval; +} + +static void +list_selection_changed_callback (GtkTreeSelection *selection, gpointer user_data) +{ +    FMDirectoryView *view; + +    view = FM_DIRECTORY_VIEW (user_data); + +    fm_directory_view_notify_selection_changed (view); +} + +/* Move these to eel? */ + +static void +tree_selection_foreach_set_boolean (GtkTreeModel *model, +                                    GtkTreePath *path, +                                    GtkTreeIter *iter, +                                    gpointer callback_data) +{ +    * (gboolean *) callback_data = TRUE; +} + +static gboolean +tree_selection_not_empty (GtkTreeSelection *selection) +{ +    gboolean not_empty; + +    not_empty = FALSE; +    gtk_tree_selection_selected_foreach (selection, +                                         tree_selection_foreach_set_boolean, +                                         ¬_empty); +    return not_empty; +} + +static gboolean +tree_view_has_selection (GtkTreeView *view) +{ +    return tree_selection_not_empty (gtk_tree_view_get_selection (view)); +} + +static void +activate_selected_items (FMListView *view) +{ +    GList *file_list; + +    file_list = fm_list_view_get_selection (FM_DIRECTORY_VIEW (view)); + + +    if (view->details->renaming_file) +    { +        /* We're currently renaming a file, wait until the rename is +           finished, or the activation uri will be wrong */ +        if (view->details->renaming_file_activate_timeout == 0) +        { +            view->details->renaming_file_activate_timeout = +                g_timeout_add (WAIT_FOR_RENAME_ON_ACTIVATE, (GSourceFunc) activate_selected_items, view); +        } +        return; +    } + +    if (view->details->renaming_file_activate_timeout != 0) +    { +        g_source_remove (view->details->renaming_file_activate_timeout); +        view->details->renaming_file_activate_timeout = 0; +    } + +    fm_directory_view_activate_files (FM_DIRECTORY_VIEW (view), +                                      file_list, +                                      CAJA_WINDOW_OPEN_ACCORDING_TO_MODE, +                                      0, +                                      TRUE); +    caja_file_list_free (file_list); + +} + +static void +activate_selected_items_alternate (FMListView *view, +                                   CajaFile *file, +                                   gboolean open_in_tab) +{ +    GList *file_list; +    CajaWindowOpenFlags flags; + +    flags = CAJA_WINDOW_OPEN_FLAG_CLOSE_BEHIND; + +    if (open_in_tab) +    { +        flags |= CAJA_WINDOW_OPEN_FLAG_NEW_TAB; +    } +    else +    { +        flags |= CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW; +    } + +    if (file != NULL) +    { +        caja_file_ref (file); +        file_list = g_list_prepend (NULL, file); +    } +    else +    { +        file_list = fm_list_view_get_selection (FM_DIRECTORY_VIEW (view)); +    } +    fm_directory_view_activate_files (FM_DIRECTORY_VIEW (view), +                                      file_list, +                                      CAJA_WINDOW_OPEN_ACCORDING_TO_MODE, +                                      flags, +                                      TRUE); +    caja_file_list_free (file_list); + +} + +static gboolean +button_event_modifies_selection (GdkEventButton *event) +{ +    return (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) != 0; +} + +static void +fm_list_view_did_not_drag (FMListView *view, +                           GdkEventButton *event) +{ +    GtkTreeView *tree_view; +    GtkTreeSelection *selection; +    GtkTreePath *path; + +    tree_view = view->details->tree_view; +    selection = gtk_tree_view_get_selection (tree_view); + +    if (gtk_tree_view_get_path_at_pos (tree_view, event->x, event->y, +                                       &path, NULL, NULL, NULL)) +    { +        if ((event->button == 1 || event->button == 2) +                && ((event->state & GDK_CONTROL_MASK) != 0 || +                    (event->state & GDK_SHIFT_MASK) == 0) +                && view->details->row_selected_on_button_down) +        { +            if (!button_event_modifies_selection (event)) +            { +                gtk_tree_selection_unselect_all (selection); +                gtk_tree_selection_select_path (selection, path); +            } +            else +            { +                gtk_tree_selection_unselect_path (selection, path); +            } +        } + +        if ((click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE) +                && !button_event_modifies_selection(event)) +        { +            if (event->button == 1) +            { +                activate_selected_items (view); +            } +            else if (event->button == 2) +            { +                activate_selected_items_alternate (view, NULL, TRUE); +            } +        } +        gtk_tree_path_free (path); +    } + +} + +static void +drag_data_get_callback (GtkWidget *widget, +                        GdkDragContext *context, +                        GtkSelectionData *selection_data, +                        guint info, +                        guint time) +{ +    GtkTreeView *tree_view; +    GtkTreeModel *model; +    GList *ref_list; + +    tree_view = GTK_TREE_VIEW (widget); + +    model = gtk_tree_view_get_model (tree_view); + +    if (model == NULL) +    { +        return; +    } + +    ref_list = g_object_get_data (G_OBJECT (context), "drag-info"); + +    if (ref_list == NULL) +    { +        return; +    } + +    if (EGG_IS_TREE_MULTI_DRAG_SOURCE (model)) +    { +        egg_tree_multi_drag_source_drag_data_get (EGG_TREE_MULTI_DRAG_SOURCE (model), +                ref_list, +                selection_data); +    } +} + +static void +filtered_selection_foreach (GtkTreeModel *model, +                            GtkTreePath *path, +                            GtkTreeIter *iter, +                            gpointer data) +{ +    struct SelectionForeachData *selection_data; +    GtkTreeIter parent; +    GtkTreeIter child; + +    selection_data = data; + +    /* If the parent folder is also selected, don't include this file in the +     * file operation, since that would copy it to the toplevel target instead +     * of keeping it as a child of the copied folder +     */ +    child = *iter; +    while (gtk_tree_model_iter_parent (model, &parent, &child)) +    { +        if (gtk_tree_selection_iter_is_selected (selection_data->selection, +                &parent)) +        { +            return; +        } +        child = parent; +    } + +    selection_data->list = g_list_prepend (selection_data->list, +                                           gtk_tree_row_reference_new (model, path)); +} + +static GList * +get_filtered_selection_refs (GtkTreeView *tree_view) +{ +    struct SelectionForeachData selection_data; + +    selection_data.list = NULL; +    selection_data.selection = gtk_tree_view_get_selection (tree_view); + +    gtk_tree_selection_selected_foreach (selection_data.selection, +                                         filtered_selection_foreach, +                                         &selection_data); +    return g_list_reverse (selection_data.list); +} + +static void +ref_list_free (GList *ref_list) +{ +    g_list_foreach (ref_list, (GFunc) gtk_tree_row_reference_free, NULL); +    g_list_free (ref_list); +} + +static void +stop_drag_check (FMListView *view) +{ +    view->details->drag_button = 0; +} + +static GdkPixbuf * +get_drag_pixbuf (FMListView *view) +{ +    GtkTreeModel *model; +    GtkTreePath *path; +    GtkTreeIter iter; +    GdkPixbuf *ret; +    GdkRectangle cell_area; + +    ret = NULL; + +    if (gtk_tree_view_get_path_at_pos (view->details->tree_view, +                                       view->details->drag_x, +                                       view->details->drag_y, +                                       &path, NULL, NULL, NULL)) +    { +        model = gtk_tree_view_get_model (view->details->tree_view); +        gtk_tree_model_get_iter (model, &iter, path); +        gtk_tree_model_get (model, &iter, +                            fm_list_model_get_column_id_from_zoom_level (view->details->zoom_level), +                            &ret, +                            -1); + +        gtk_tree_view_get_cell_area (view->details->tree_view, +                                     path, +                                     view->details->file_name_column, +                                     &cell_area); + +        gtk_tree_path_free (path); +    } + +    return ret; +} + +static void +drag_begin_callback (GtkWidget *widget, +                     GdkDragContext *context, +                     FMListView *view) +{ +    GList *ref_list; +    GdkPixbuf *pixbuf; + +    pixbuf = get_drag_pixbuf (view); +    if (pixbuf) +    { +        gtk_drag_set_icon_pixbuf (context, +                                  pixbuf, +                                  0, 0); +        g_object_unref (pixbuf); +    } +    else +    { +        gtk_drag_set_icon_default (context); +    } + +    stop_drag_check (view); +    view->details->drag_started = TRUE; + +    ref_list = get_filtered_selection_refs (GTK_TREE_VIEW (widget)); +    g_object_set_data_full (G_OBJECT (context), +                            "drag-info", +                            ref_list, +                            (GDestroyNotify)ref_list_free); +} + +static gboolean +motion_notify_callback (GtkWidget *widget, +                        GdkEventMotion *event, +                        gpointer callback_data) +{ +    FMListView *view; +    GdkDragContext *context; + +    view = FM_LIST_VIEW (callback_data); + +    if (event->window != gtk_tree_view_get_bin_window (GTK_TREE_VIEW (widget))) +    { +        return FALSE; +    } + +    if (click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE) +    { +        GtkTreePath *old_hover_path; + +        old_hover_path = view->details->hover_path; +        gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget), +                                       event->x, event->y, +                                       &view->details->hover_path, +                                       NULL, NULL, NULL); + +        if ((old_hover_path != NULL) != (view->details->hover_path != NULL)) +        { +            if (view->details->hover_path != NULL) +            { +                gdk_window_set_cursor (gtk_widget_get_window (widget), hand_cursor); +            } +            else +            { +                gdk_window_set_cursor (gtk_widget_get_window (widget), NULL); +            } +        } + +        if (old_hover_path != NULL) +        { +            gtk_tree_path_free (old_hover_path); +        } +    } + +    if (view->details->drag_button != 0) +    { +        if (!source_target_list) +        { +            source_target_list = fm_list_model_get_drag_target_list (); +        } + +        if (gtk_drag_check_threshold (widget, +                                      view->details->drag_x, +                                      view->details->drag_y, +                                      event->x, +                                      event->y)) +        { +            context = gtk_drag_begin +                      (widget, +                       source_target_list, +                       GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK | GDK_ACTION_ASK, +                       view->details->drag_button, +                       (GdkEvent*)event); +        } +        return TRUE; +    } + +    return FALSE; +} + +static gboolean +leave_notify_callback (GtkWidget *widget, +                       GdkEventCrossing *event, +                       gpointer callback_data) +{ +    FMListView *view; + +    view = FM_LIST_VIEW (callback_data); + +    if (click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE && +            view->details->hover_path != NULL) +    { +        gtk_tree_path_free (view->details->hover_path); +        view->details->hover_path = NULL; +    } + +    return FALSE; +} + +static gboolean +enter_notify_callback (GtkWidget *widget, +                       GdkEventCrossing *event, +                       gpointer callback_data) +{ +    FMListView *view; + +    view = FM_LIST_VIEW (callback_data); + +    if (click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE) +    { +        if (view->details->hover_path != NULL) +        { +            gtk_tree_path_free (view->details->hover_path); +        } + +        gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget), +                                       event->x, event->y, +                                       &view->details->hover_path, +                                       NULL, NULL, NULL); + +        if (view->details->hover_path != NULL) +        { +            gdk_window_set_cursor (gtk_widget_get_window (widget), hand_cursor); +        } +    } + +    return FALSE; +} + +static void +do_popup_menu (GtkWidget *widget, FMListView *view, GdkEventButton *event) +{ +    if (tree_view_has_selection (GTK_TREE_VIEW (widget))) +    { +        fm_directory_view_pop_up_selection_context_menu (FM_DIRECTORY_VIEW (view), event); +    } +    else +    { +        fm_directory_view_pop_up_background_context_menu (FM_DIRECTORY_VIEW (view), event); +    } +} + +static gboolean +button_press_callback (GtkWidget *widget, GdkEventButton *event, gpointer callback_data) +{ +    FMListView *view; +    GtkTreeView *tree_view; +    GtkTreePath *path; +    gboolean call_parent; +    gboolean allow_drag; +    GtkTreeSelection *selection; +    GtkWidgetClass *tree_view_class; +    gint64 current_time; +    static gint64 last_click_time = 0; +    static int click_count = 0; +    int double_click_time; +    int expander_size, horizontal_separator; +    gboolean on_expander; + +    view = FM_LIST_VIEW (callback_data); +    tree_view = GTK_TREE_VIEW (widget); +    tree_view_class = GTK_WIDGET_GET_CLASS (tree_view); +    selection = gtk_tree_view_get_selection (tree_view); + +    if (event->window != gtk_tree_view_get_bin_window (tree_view)) +    { +        return FALSE; +    } + +    fm_list_model_set_drag_view +    (FM_LIST_MODEL (gtk_tree_view_get_model (tree_view)), +     tree_view, +     event->x, event->y); + +    g_object_get (G_OBJECT (gtk_widget_get_settings (widget)), +                  "gtk-double-click-time", &double_click_time, +                  NULL); + +    /* Determine click count */ +    current_time = eel_get_system_time (); +    if (current_time - last_click_time < double_click_time * 1000) +    { +        click_count++; +    } +    else +    { +        click_count = 0; +    } + +    /* Stash time for next compare */ +    last_click_time = current_time; + +    /* Ignore double click if we are in single click mode */ +    if (click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE && click_count >= 2) +    { +        return TRUE; +    } + +    view->details->ignore_button_release = FALSE; + +    call_parent = TRUE; +    allow_drag = FALSE; +    if (gtk_tree_view_get_path_at_pos (tree_view, event->x, event->y, +                                       &path, NULL, NULL, NULL)) +    { +        gtk_widget_style_get (widget, +                              "expander-size", &expander_size, +                              "horizontal-separator", &horizontal_separator, +                              NULL); +        /* TODO we should not hardcode this extra padding. It is +         * EXPANDER_EXTRA_PADDING from GtkTreeView. +         */ +        expander_size += 4; +        on_expander = (event->x <= horizontal_separator / 2 + +                       gtk_tree_path_get_depth (path) * expander_size); + +        /* Keep track of path of last click so double clicks only happen +         * on the same item */ +        if ((event->button == 1 || event->button == 2)  && +                event->type == GDK_BUTTON_PRESS) +        { +            if (view->details->double_click_path[1]) +            { +                gtk_tree_path_free (view->details->double_click_path[1]); +            } +            view->details->double_click_path[1] = view->details->double_click_path[0]; +            view->details->double_click_path[0] = gtk_tree_path_copy (path); +        } +        if (event->type == GDK_2BUTTON_PRESS) +        { +            /* Double clicking does not trigger a D&D action. */ +            view->details->drag_button = 0; +            if (view->details->double_click_path[1] && +                    gtk_tree_path_compare (view->details->double_click_path[0], view->details->double_click_path[1]) == 0 && +                    !on_expander) +            { +                /* NOTE: Activation can actually destroy the view if we're switching */ +                if (!button_event_modifies_selection (event)) +                { +                    if ((event->button == 1 || event->button == 3)) +                    { +                        activate_selected_items (view); +                    } +                    else if (event->button == 2) +                    { +                        activate_selected_items_alternate (view, NULL, TRUE); +                    } +                } +                else if (event->button == 1 && +                         (event->state & GDK_SHIFT_MASK) != 0) +                { +                    CajaFile *file; +                    file = fm_list_model_file_for_path (view->details->model, path); +                    if (file != NULL) +                    { +                        activate_selected_items_alternate (view, file, TRUE); +                        caja_file_unref (file); +                    } +                } +            } +            else +            { +                tree_view_class->button_press_event (widget, event); +            } +        } +        else +        { + +            /* We're going to filter out some situations where +             * we can't let the default code run because all +             * but one row would be would be deselected. We don't +             * want that; we want the right click menu or single +             * click to apply to everything that's currently selected. */ + +            if (event->button == 3 && gtk_tree_selection_path_is_selected (selection, path)) +            { +                call_parent = FALSE; +            } + +            if ((event->button == 1 || event->button == 2) && +                    ((event->state & GDK_CONTROL_MASK) != 0 || +                     (event->state & GDK_SHIFT_MASK) == 0)) +            { +                view->details->row_selected_on_button_down = gtk_tree_selection_path_is_selected (selection, path); +                if (view->details->row_selected_on_button_down) +                { +                    call_parent = on_expander; +                    view->details->ignore_button_release = call_parent; +                } +                else if ((event->state & GDK_CONTROL_MASK) != 0) +                { +                    GList *selected_rows; +                    GList *l; + +                    call_parent = FALSE; +                    if ((event->state & GDK_SHIFT_MASK) != 0) +                    { +                        GtkTreePath *cursor; +                        gtk_tree_view_get_cursor (tree_view, &cursor, NULL); +                        if (cursor != NULL) +                        { +                            gtk_tree_selection_select_range (selection, cursor, path); +                        } +                        else +                        { +                            gtk_tree_selection_select_path (selection, path); +                        } +                    } +                    else +                    { +                        gtk_tree_selection_select_path (selection, path); +                    } +                    selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL); + +                    /* This unselects everything */ +                    gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE); + +                    /* So select it again */ +                    l = selected_rows; +                    while (l != NULL) +                    { +                        GtkTreePath *p = l->data; +                        l = l->next; +                        gtk_tree_selection_select_path (selection, p); +                        gtk_tree_path_free (p); +                    } +                    g_list_free (selected_rows); +                } +                else +                { +                    view->details->ignore_button_release = on_expander; +                } +            } + +            if (call_parent) +            { +                tree_view_class->button_press_event (widget, event); +            } +            else if (gtk_tree_selection_path_is_selected (selection, path)) +            { +                gtk_widget_grab_focus (widget); +            } + +            if ((event->button == 1 || event->button == 2) && +                    event->type == GDK_BUTTON_PRESS) +            { +                view->details->drag_started = FALSE; +                view->details->drag_button = event->button; +                view->details->drag_x = event->x; +                view->details->drag_y = event->y; +            } + +            if (event->button == 3) +            { +                do_popup_menu (widget, view, event); +            } +        } + +        gtk_tree_path_free (path); +    } +    else +    { +        if ((event->button == 1 || event->button == 2)  && +                event->type == GDK_BUTTON_PRESS) +        { +            if (view->details->double_click_path[1]) +            { +                gtk_tree_path_free (view->details->double_click_path[1]); +            } +            view->details->double_click_path[1] = view->details->double_click_path[0]; +            view->details->double_click_path[0] = NULL; +        } +        /* Deselect if people click outside any row. It's OK to +           let default code run; it won't reselect anything. */ +        gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (tree_view)); +        tree_view_class->button_press_event (widget, event); + +        if (event->button == 3) +        { +            do_popup_menu (widget, view, event); +        } +    } + +    /* We chained to the default handler in this method, so never +     * let the default handler run */ +    return TRUE; +} + +static gboolean +button_release_callback (GtkWidget *widget, +                         GdkEventButton *event, +                         gpointer callback_data) +{ +    FMListView *view; + +    view = FM_LIST_VIEW (callback_data); + +    if (event->button == view->details->drag_button) +    { +        stop_drag_check (view); +        if (!view->details->drag_started && +                !view->details->ignore_button_release) +        { +            fm_list_view_did_not_drag (view, event); +        } +    } +    return FALSE; +} + +static gboolean +popup_menu_callback (GtkWidget *widget, gpointer callback_data) +{ +    FMListView *view; + +    view = FM_LIST_VIEW (callback_data); + +    do_popup_menu (widget, view, NULL); + +    return TRUE; +} + +static void +subdirectory_done_loading_callback (CajaDirectory *directory, FMListView *view) +{ +    fm_list_model_subdirectory_done_loading (view->details->model, directory); +} + +static void +row_expanded_callback (GtkTreeView *treeview, GtkTreeIter *iter, GtkTreePath *path, gpointer callback_data) +{ +    FMListView *view; +    CajaDirectory *directory; + +    view = FM_LIST_VIEW (callback_data); + +    if (fm_list_model_load_subdirectory (view->details->model, path, &directory)) +    { +        char *uri; + +        uri = caja_directory_get_uri (directory); +        caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, +                        "list view row expanded window=%p: %s", +                        fm_directory_view_get_containing_window (FM_DIRECTORY_VIEW (view)), +                        uri); +        g_free (uri); + +        fm_directory_view_add_subdirectory (FM_DIRECTORY_VIEW (view), directory); + +        if (caja_directory_are_all_files_seen (directory)) +        { +            fm_list_model_subdirectory_done_loading (view->details->model, +                    directory); +        } +        else +        { +            g_signal_connect_object (directory, "done_loading", +                                     G_CALLBACK (subdirectory_done_loading_callback), +                                     view, 0); +        } + +        caja_directory_unref (directory); +    } +} + +struct UnloadDelayData +{ +    CajaFile *file; +    CajaDirectory *directory; +    FMListView *view; +}; + +static gboolean +unload_file_timeout (gpointer data) +{ +    struct UnloadDelayData *unload_data = data; +    GtkTreeIter iter; +    FMListModel *model; +    GtkTreePath *path; + +    if (unload_data->view != NULL) +    { +        model = unload_data->view->details->model; +        if (fm_list_model_get_tree_iter_from_file (model, +                unload_data->file, +                unload_data->directory, +                &iter)) +        { +            path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); +            if (!gtk_tree_view_row_expanded (unload_data->view->details->tree_view, +                                             path)) +            { +                fm_list_model_unload_subdirectory (model, &iter); +            } +            gtk_tree_path_free (path); +        } +    } + +    eel_remove_weak_pointer (&unload_data->view); + +    if (unload_data->directory) +    { +        caja_directory_unref (unload_data->directory); +    } +    caja_file_unref (unload_data->file); +    g_free (unload_data); +    return FALSE; +} + +static void +row_collapsed_callback (GtkTreeView *treeview, GtkTreeIter *iter, GtkTreePath *path, gpointer callback_data) +{ +    FMListView *view; +    CajaFile *file; +    CajaDirectory *directory; +    GtkTreeIter parent; +    struct UnloadDelayData *unload_data; +    GtkTreeModel *model; +    char *uri; + +    view = FM_LIST_VIEW (callback_data); +    model = GTK_TREE_MODEL (view->details->model); + +    gtk_tree_model_get (model, iter, +                        FM_LIST_MODEL_FILE_COLUMN, &file, +                        -1); + +    directory = NULL; +    if (gtk_tree_model_iter_parent (model, &parent, iter)) +    { +        gtk_tree_model_get (model, &parent, +                            FM_LIST_MODEL_SUBDIRECTORY_COLUMN, &directory, +                            -1); +    } + + +    uri = caja_file_get_uri (file); +    caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, +                    "list view row collapsed window=%p: %s", +                    fm_directory_view_get_containing_window (FM_DIRECTORY_VIEW (view)), +                    uri); +    g_free (uri); + +    unload_data = g_new (struct UnloadDelayData, 1); +    unload_data->view = view; +    unload_data->file = file; +    unload_data->directory = directory; + +    eel_add_weak_pointer (&unload_data->view); + +    g_timeout_add_seconds (COLLAPSE_TO_UNLOAD_DELAY, +                           unload_file_timeout, +                           unload_data); +} + +static void +row_activated_callback (GtkTreeView *treeview, GtkTreePath *path, +                        GtkTreeViewColumn *column, FMListView *view) +{ +    activate_selected_items (view); +} + +static void +subdirectory_unloaded_callback (FMListModel *model, +                                CajaDirectory *directory, +                                gpointer callback_data) +{ +    FMListView *view; + +    g_return_if_fail (FM_IS_LIST_MODEL (model)); +    g_return_if_fail (CAJA_IS_DIRECTORY (directory)); + +    view = FM_LIST_VIEW(callback_data); + +    g_signal_handlers_disconnect_by_func (directory, +                                          G_CALLBACK (subdirectory_done_loading_callback), +                                          view); +    fm_directory_view_remove_subdirectory (FM_DIRECTORY_VIEW (view), directory); +} + +static gboolean +key_press_callback (GtkWidget *widget, GdkEventKey *event, gpointer callback_data) +{ +    FMDirectoryView *view; +    GdkEventButton button_event = { 0 }; +    gboolean handled; +    GtkTreeView *tree_view; +    GtkTreePath *path; + +    tree_view = GTK_TREE_VIEW (widget); + +    view = FM_DIRECTORY_VIEW (callback_data); +    handled = FALSE; + +    switch (event->keyval) +    { +    case GDK_F10: +        if (event->state & GDK_CONTROL_MASK) +        { +            fm_directory_view_pop_up_background_context_menu (view, &button_event); +            handled = TRUE; +        } +        break; +    case GDK_Right: +        gtk_tree_view_get_cursor (tree_view, &path, NULL); +        if (path) +        { +            gtk_tree_view_expand_row (tree_view, path, FALSE); +            gtk_tree_path_free (path); +        } +        handled = TRUE; +        break; +    case GDK_Left: +        gtk_tree_view_get_cursor (tree_view, &path, NULL); +        if (path) +        { +            gtk_tree_view_collapse_row (tree_view, path); +            gtk_tree_path_free (path); +        } +        handled = TRUE; +        break; +    case GDK_space: +        if (event->state & GDK_CONTROL_MASK) +        { +            handled = FALSE; +            break; +        } +        if (!gtk_widget_has_focus (GTK_WIDGET (FM_LIST_VIEW (view)->details->tree_view))) +        { +            handled = FALSE; +            break; +        } +        if ((event->state & GDK_SHIFT_MASK) != 0) +        { +            activate_selected_items_alternate (FM_LIST_VIEW (view), NULL, TRUE); +        } +        else +        { +            activate_selected_items (FM_LIST_VIEW (view)); +        } +        handled = TRUE; +        break; +    case GDK_Return: +    case GDK_KP_Enter: +        if ((event->state & GDK_SHIFT_MASK) != 0) +        { +            activate_selected_items_alternate (FM_LIST_VIEW (view), NULL, TRUE); +        } +        else +        { +            activate_selected_items (FM_LIST_VIEW (view)); +        } +        handled = TRUE; +        break; +    case GDK_v: +        /* Eat Control + v to not enable type ahead */ +        if ((event->state & GDK_CONTROL_MASK) != 0) +        { +            handled = TRUE; +        } +        break; + +    default: +        handled = FALSE; +    } + +    return handled; +} + +static void +fm_list_view_reveal_selection (FMDirectoryView *view) +{ +    GList *selection; + +    g_return_if_fail (FM_IS_LIST_VIEW (view)); + +    selection = fm_directory_view_get_selection (view); + +    /* Make sure at least one of the selected items is scrolled into view */ +    if (selection != NULL) +    { +        FMListView *list_view; +        CajaFile *file; +        GtkTreeIter iter; +        GtkTreePath *path; + +        list_view = FM_LIST_VIEW (view); +        file = selection->data; +        if (fm_list_model_get_first_iter_for_file (list_view->details->model, file, &iter)) +        { +            path = gtk_tree_model_get_path (GTK_TREE_MODEL (list_view->details->model), &iter); + +            gtk_tree_view_scroll_to_cell (list_view->details->tree_view, path, NULL, FALSE, 0.0, 0.0); + +            gtk_tree_path_free (path); +        } +    } + +    caja_file_list_free (selection); +} + +static gboolean +sort_criterion_changes_due_to_user (GtkTreeView *tree_view) +{ +    GList *columns, *p; +    GtkTreeViewColumn *column; +    GSignalInvocationHint *ihint; +    unsigned int sort_signal_id; +    gboolean ret; + +    sort_signal_id = g_signal_lookup ("clicked", gtk_tree_view_column_get_type ()); + +    ret = FALSE; + +    columns = gtk_tree_view_get_columns (tree_view); +    for (p = columns; p != NULL; p = p->next) +    { +        column = p->data; +        ihint = g_signal_get_invocation_hint (column); +        if (ihint != NULL) +        { +            ret = TRUE; +            break; +        } +    } +    g_list_free (columns); + +    return ret; +} + +static void +sort_column_changed_callback (GtkTreeSortable *sortable, +                              FMListView *view) +{ +    CajaFile *file; +    gint sort_column_id, default_sort_column_id; +    GtkSortType reversed; +    GQuark sort_attr, default_sort_attr; +    char *reversed_attr, *default_reversed_attr; +    gboolean default_sort_reversed; + +    file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (view)); + +    gtk_tree_sortable_get_sort_column_id (sortable, &sort_column_id, &reversed); +    sort_attr = fm_list_model_get_attribute_from_sort_column_id (view->details->model, sort_column_id); + +    default_sort_column_id = fm_list_model_get_sort_column_id_from_attribute (view->details->model, +                             g_quark_from_string (get_default_sort_order (file, &default_sort_reversed))); +    default_sort_attr = fm_list_model_get_attribute_from_sort_column_id (view->details->model, default_sort_column_id); +    caja_file_set_metadata (file, CAJA_METADATA_KEY_LIST_VIEW_SORT_COLUMN, +                            g_quark_to_string (default_sort_attr), g_quark_to_string (sort_attr)); + +    default_reversed_attr = (default_sort_reversed ? "true" : "false"); + +    if (view->details->last_sort_attr != sort_attr && +            sort_criterion_changes_due_to_user (view->details->tree_view)) +    { +        /* at this point, the sort order is always GTK_SORT_ASCENDING, if the sort column ID +         * switched. Invert the sort order, if it's the default criterion with a reversed preference, +         * or if it makes sense for the attribute (i.e. date). */ +        if (sort_attr == default_sort_attr) +        { +            /* use value from preferences */ +            reversed = eel_preferences_get_boolean (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_SORT_IN_REVERSE_ORDER); +        } +        else +        { +            reversed = caja_file_is_date_sort_attribute_q (sort_attr); +        } + +        if (reversed) +        { +            g_signal_handlers_block_by_func (sortable, sort_column_changed_callback, view); +            gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (view->details->model), +                                                  sort_column_id, +                                                  GTK_SORT_DESCENDING); +            g_signal_handlers_unblock_by_func (sortable, sort_column_changed_callback, view); +        } +    } + + +    reversed_attr = (reversed ? "true" : "false"); +    caja_file_set_metadata (file, CAJA_METADATA_KEY_LIST_VIEW_SORT_REVERSED, +                            default_reversed_attr, reversed_attr); + +    /* Make sure selected item(s) is visible after sort */ +    fm_list_view_reveal_selection (FM_DIRECTORY_VIEW (view)); + +    view->details->last_sort_attr = sort_attr; +} + +static void +cell_renderer_editing_started_cb (GtkCellRenderer *renderer, +                                  GtkCellEditable *editable, +                                  const gchar *path_str, +                                  FMListView *list_view) +{ +    GtkEntry *entry; +    gint start_offset, end_offset; + +    entry = GTK_ENTRY (editable); +    list_view->details->editable_widget = editable; + +    /* Free a previously allocated original_name */ +    g_free (list_view->details->original_name); + +    list_view->details->original_name = g_strdup (gtk_entry_get_text (entry)); +    eel_filename_get_rename_region (list_view->details->original_name, +                                    &start_offset, &end_offset); +    gtk_editable_select_region (GTK_EDITABLE (entry), start_offset, end_offset); + +    caja_clipboard_set_up_editable +    (GTK_EDITABLE (entry), +     fm_directory_view_get_ui_manager (FM_DIRECTORY_VIEW (list_view)), +     FALSE); +} + +static void +cell_renderer_editing_canceled (GtkCellRendererText *cell, +                                FMListView          *view) +{ +    view->details->editable_widget = NULL; + +    fm_directory_view_unfreeze_updates (FM_DIRECTORY_VIEW (view)); +} + +static void +cell_renderer_edited (GtkCellRendererText *cell, +                      const char          *path_str, +                      const char          *new_text, +                      FMListView          *view) +{ +    GtkTreePath *path; +    CajaFile *file; +    GtkTreeIter iter; + +    view->details->editable_widget = NULL; + +    /* Don't allow a rename with an empty string. Revert to original +     * without notifying the user. +     */ +    if (new_text[0] == '\0') +    { +        g_object_set (G_OBJECT (view->details->file_name_cell), +                      "editable", FALSE, +                      NULL); +        fm_directory_view_unfreeze_updates (FM_DIRECTORY_VIEW (view)); +        return; +    } + +    path = gtk_tree_path_new_from_string (path_str); + +    gtk_tree_model_get_iter (GTK_TREE_MODEL (view->details->model), +                             &iter, path); + +    gtk_tree_path_free (path); + +    gtk_tree_model_get (GTK_TREE_MODEL (view->details->model), +                        &iter, +                        FM_LIST_MODEL_FILE_COLUMN, &file, +                        -1); + +    /* Only rename if name actually changed */ +    if (strcmp (new_text, view->details->original_name) != 0) +    { +        view->details->renaming_file = caja_file_ref (file); +        view->details->rename_done = FALSE; +        fm_rename_file (file, new_text, fm_list_view_rename_callback, g_object_ref (view)); +        g_free (view->details->original_name); +        view->details->original_name = g_strdup (new_text); +    } + +    caja_file_unref (file); + +    /*We're done editing - make the filename-cells readonly again.*/ +    g_object_set (G_OBJECT (view->details->file_name_cell), +                  "editable", FALSE, +                  NULL); + +    fm_directory_view_unfreeze_updates (FM_DIRECTORY_VIEW (view)); +} + +static char * +get_root_uri_callback (CajaTreeViewDragDest *dest, +                       gpointer user_data) +{ +    FMListView *view; + +    view = FM_LIST_VIEW (user_data); + +    return fm_directory_view_get_uri (FM_DIRECTORY_VIEW (view)); +} + +static CajaFile * +get_file_for_path_callback (CajaTreeViewDragDest *dest, +                            GtkTreePath *path, +                            gpointer user_data) +{ +    FMListView *view; + +    view = FM_LIST_VIEW (user_data); + +    return fm_list_model_file_for_path (view->details->model, path); +} + +/* Handles an URL received from Mozilla */ +static void +list_view_handle_netscape_url (CajaTreeViewDragDest *dest, const char *encoded_url, +                               const char *target_uri, GdkDragAction action, int x, int y, FMListView *view) +{ +    fm_directory_view_handle_netscape_url_drop (FM_DIRECTORY_VIEW (view), +            encoded_url, target_uri, action, x, y); +} + +static void +list_view_handle_uri_list (CajaTreeViewDragDest *dest, const char *item_uris, +                           const char *target_uri, +                           GdkDragAction action, int x, int y, FMListView *view) +{ +    fm_directory_view_handle_uri_list_drop (FM_DIRECTORY_VIEW (view), +                                            item_uris, target_uri, action, x, y); +} + +static void +list_view_handle_text (CajaTreeViewDragDest *dest, const char *text, +                       const char *target_uri, +                       GdkDragAction action, int x, int y, FMListView *view) +{ +    fm_directory_view_handle_text_drop (FM_DIRECTORY_VIEW (view), +                                        text, target_uri, action, x, y); +} + +static void +list_view_handle_raw (CajaTreeViewDragDest *dest, const char *raw_data, +                      int length, const char *target_uri, const char *direct_save_uri, +                      GdkDragAction action, int x, int y, FMListView *view) +{ +    fm_directory_view_handle_raw_drop (FM_DIRECTORY_VIEW (view), +                                       raw_data, length, target_uri, direct_save_uri, +                                       action, x, y); +} + +static void +move_copy_items_callback (CajaTreeViewDragDest *dest, +                          const GList *item_uris, +                          const char *target_uri, +                          guint action, +                          int x, +                          int y, +                          gpointer user_data) + +{ +    FMDirectoryView *view = user_data; + +    caja_clipboard_clear_if_colliding_uris (GTK_WIDGET (view), +                                            item_uris, +                                            fm_directory_view_get_copied_files_atom (view)); +    fm_directory_view_move_copy_items (item_uris, +                                       NULL, +                                       target_uri, +                                       action, +                                       x, y, +                                       view); +} + +static void +apply_columns_settings (FMListView *list_view, +                        char **column_order, +                        char **visible_columns) +{ +    GList *all_columns; +    CajaFile *file; +    GList *old_view_columns, *view_columns; +    GHashTable *visible_columns_hash; +    GtkTreeViewColumn *prev_view_column; +    GList *l; +    int i; + +    file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (list_view)); + +    /* prepare ordered list of view columns using column_order and visible_columns */ +    view_columns = NULL; + +    all_columns = caja_get_columns_for_file (file); +    all_columns = caja_sort_columns (all_columns, column_order); + +    /* hash table to lookup if a given column should be visible */ +    visible_columns_hash = g_hash_table_new_full (g_str_hash, +                           g_str_equal, +                           (GDestroyNotify) g_free, +                           (GDestroyNotify) g_free); +    for (i = 0; visible_columns[i] != NULL; ++i) +    { +        g_hash_table_insert (visible_columns_hash, +                             g_ascii_strdown (visible_columns[i], -1), +                             g_ascii_strdown (visible_columns[i], -1)); +    } + +    for (l = all_columns; l != NULL; l = l->next) +    { +        char *name; +        char *lowercase; + +        g_object_get (G_OBJECT (l->data), "name", &name, NULL); +        lowercase = g_ascii_strdown (name, -1); + +        if (g_hash_table_lookup (visible_columns_hash, lowercase) != NULL) +        { +            GtkTreeViewColumn *view_column; + +            view_column = g_hash_table_lookup (list_view->details->columns, name); +            if (view_column != NULL) +            { +                view_columns = g_list_prepend (view_columns, view_column); +            } +        } + +        g_free (name); +        g_free (lowercase); +    } + +    g_hash_table_destroy (visible_columns_hash); +    caja_column_list_free (all_columns); + +    view_columns = g_list_reverse (view_columns); + +    /* remove columns that are not present in the configuration */ +    old_view_columns = gtk_tree_view_get_columns (list_view->details->tree_view); +    for (l = old_view_columns; l != NULL; l = l->next) +    { +        if (g_list_find (view_columns, l->data) == NULL) +        { +            gtk_tree_view_remove_column (list_view->details->tree_view, l->data); +        } +    } +    g_list_free (old_view_columns); + +    /* append new columns from the configuration */ +    old_view_columns = gtk_tree_view_get_columns (list_view->details->tree_view); +    for (l = view_columns; l != NULL; l = l->next) +    { +        if (g_list_find (old_view_columns, l->data) == NULL) +        { +            gtk_tree_view_append_column (list_view->details->tree_view, l->data); +        } +    } +    g_list_free (old_view_columns); + +    /* place columns in the correct order */ +    prev_view_column = NULL; +    for (l = view_columns; l != NULL; l = l->next) +    { +        gtk_tree_view_move_column_after (list_view->details->tree_view, l->data, prev_view_column); +        prev_view_column = l->data; +    } +    g_list_free (view_columns); +} + +static void +filename_cell_data_func (GtkTreeViewColumn *column, +                         GtkCellRenderer   *renderer, +                         GtkTreeModel      *model, +                         GtkTreeIter       *iter, +                         FMListView        *view) +{ +    char *text; +    GtkTreePath *path; +    PangoUnderline underline; + +    gtk_tree_model_get (model, iter, +                        view->details->file_name_column_num, &text, +                        -1); + +    if (click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE) +    { +        path = gtk_tree_model_get_path (model, iter); + +        if (view->details->hover_path == NULL || +                gtk_tree_path_compare (path, view->details->hover_path)) +        { +            underline = PANGO_UNDERLINE_NONE; +        } +        else +        { +            underline = PANGO_UNDERLINE_SINGLE; +        } + +        gtk_tree_path_free (path); +    } +    else +    { +        underline = PANGO_UNDERLINE_NONE; +    } + +    g_object_set (G_OBJECT (renderer), +                  "text", text, +                  "underline", underline, +                  NULL); +    g_free (text); +} + +static gboolean +focus_in_event_callback (GtkWidget *widget, GdkEventFocus *event, gpointer user_data) +{ +    CajaWindowSlotInfo *slot_info; +    FMListView *list_view = FM_LIST_VIEW (user_data); + +    /* make the corresponding slot (and the pane that contains it) active */ +    slot_info = fm_directory_view_get_caja_window_slot (FM_DIRECTORY_VIEW (list_view)); +    caja_window_slot_info_make_hosting_pane_active (slot_info); + +    return FALSE; +} + +static void +create_and_set_up_tree_view (FMListView *view) +{ +    GtkCellRenderer *cell; +    GtkTreeViewColumn *column; +    GtkBindingSet *binding_set; +    AtkObject *atk_obj; +    GList *caja_columns; +    GList *l; + +    view->details->tree_view = GTK_TREE_VIEW (gtk_tree_view_new ()); +    view->details->columns = g_hash_table_new_full (g_str_hash, +                             g_str_equal, +                             (GDestroyNotify)g_free, +                             (GDestroyNotify) g_object_unref); +    gtk_tree_view_set_enable_search (view->details->tree_view, TRUE); + +    /* Don't handle backspace key. It's used to open the parent folder. */ +    binding_set = gtk_binding_set_by_class (GTK_WIDGET_GET_CLASS (view->details->tree_view)); +    gtk_binding_entry_remove (binding_set, GDK_BackSpace, 0); + +    view->details->drag_dest = +        caja_tree_view_drag_dest_new (view->details->tree_view); + +    g_signal_connect_object (view->details->drag_dest, +                             "get_root_uri", +                             G_CALLBACK (get_root_uri_callback), +                             view, 0); +    g_signal_connect_object (view->details->drag_dest, +                             "get_file_for_path", +                             G_CALLBACK (get_file_for_path_callback), +                             view, 0); +    g_signal_connect_object (view->details->drag_dest, +                             "move_copy_items", +                             G_CALLBACK (move_copy_items_callback), +                             view, 0); +    g_signal_connect_object (view->details->drag_dest, "handle_netscape_url", +                             G_CALLBACK (list_view_handle_netscape_url), view, 0); +    g_signal_connect_object (view->details->drag_dest, "handle_uri_list", +                             G_CALLBACK (list_view_handle_uri_list), view, 0); +    g_signal_connect_object (view->details->drag_dest, "handle_text", +                             G_CALLBACK (list_view_handle_text), view, 0); +    g_signal_connect_object (view->details->drag_dest, "handle_raw", +                             G_CALLBACK (list_view_handle_raw), view, 0); + +    g_signal_connect_object (gtk_tree_view_get_selection (view->details->tree_view), +                             "changed", +                             G_CALLBACK (list_selection_changed_callback), view, 0); + +    g_signal_connect_object (view->details->tree_view, "drag_begin", +                             G_CALLBACK (drag_begin_callback), view, 0); +    g_signal_connect_object (view->details->tree_view, "drag_data_get", +                             G_CALLBACK (drag_data_get_callback), view, 0); +    g_signal_connect_object (view->details->tree_view, "motion_notify_event", +                             G_CALLBACK (motion_notify_callback), view, 0); +    g_signal_connect_object (view->details->tree_view, "enter_notify_event", +                             G_CALLBACK (enter_notify_callback), view, 0); +    g_signal_connect_object (view->details->tree_view, "leave_notify_event", +                             G_CALLBACK (leave_notify_callback), view, 0); +    g_signal_connect_object (view->details->tree_view, "button_press_event", +                             G_CALLBACK (button_press_callback), view, 0); +    g_signal_connect_object (view->details->tree_view, "button_release_event", +                             G_CALLBACK (button_release_callback), view, 0); +    g_signal_connect_object (view->details->tree_view, "key_press_event", +                             G_CALLBACK (key_press_callback), view, 0); +    g_signal_connect_object (view->details->tree_view, "popup_menu", +                             G_CALLBACK (popup_menu_callback), view, 0); +    g_signal_connect_object (view->details->tree_view, "row_expanded", +                             G_CALLBACK (row_expanded_callback), view, 0); +    g_signal_connect_object (view->details->tree_view, "row_collapsed", +                             G_CALLBACK (row_collapsed_callback), view, 0); +    g_signal_connect_object (view->details->tree_view, "row-activated", +                             G_CALLBACK (row_activated_callback), view, 0); + +    g_signal_connect_object (view->details->tree_view, "focus_in_event", +                             G_CALLBACK(focus_in_event_callback), view, 0); + +    view->details->model = g_object_new (FM_TYPE_LIST_MODEL, NULL); +    gtk_tree_view_set_model (view->details->tree_view, GTK_TREE_MODEL (view->details->model)); +    /* Need the model for the dnd drop icon "accept" change */ +    fm_list_model_set_drag_view (FM_LIST_MODEL (view->details->model), +                                 view->details->tree_view,  0, 0); + +    g_signal_connect_object (view->details->model, "sort_column_changed", +                             G_CALLBACK (sort_column_changed_callback), view, 0); + +    g_signal_connect_object (view->details->model, "subdirectory_unloaded", +                             G_CALLBACK (subdirectory_unloaded_callback), view, 0); + +    gtk_tree_selection_set_mode (gtk_tree_view_get_selection (view->details->tree_view), GTK_SELECTION_MULTIPLE); +    gtk_tree_view_set_rules_hint (view->details->tree_view, TRUE); + +    caja_columns = caja_get_all_columns (); + +    for (l = caja_columns; l != NULL; l = l->next) +    { +        CajaColumn *caja_column; +        int column_num; +        char *name; +        char *label; +        float xalign; + +        caja_column = CAJA_COLUMN (l->data); + +        g_object_get (caja_column, +                      "name", &name, +                      "label", &label, +                      "xalign", &xalign, NULL); + +        column_num = fm_list_model_add_column (view->details->model, +                                               caja_column); + +        /* Created the name column specially, because it +         * has the icon in it.*/ +        if (!strcmp (name, "name")) +        { +            /* Create the file name column */ +            cell = caja_cell_renderer_pixbuf_emblem_new (); +            view->details->pixbuf_cell = (GtkCellRendererPixbuf *)cell; + +            view->details->file_name_column = gtk_tree_view_column_new (); +            g_object_ref_sink (view->details->file_name_column); +            view->details->file_name_column_num = column_num; + +            g_hash_table_insert (view->details->columns, +                                 g_strdup ("name"), +                                 view->details->file_name_column); + +            gtk_tree_view_set_search_column (view->details->tree_view, column_num); + +            gtk_tree_view_column_set_sort_column_id (view->details->file_name_column, column_num); +            gtk_tree_view_column_set_title (view->details->file_name_column, _("Name")); +            gtk_tree_view_column_set_resizable (view->details->file_name_column, TRUE); + +            gtk_tree_view_column_pack_start (view->details->file_name_column, cell, FALSE); +            gtk_tree_view_column_set_attributes (view->details->file_name_column, +                                                 cell, +                                                 "pixbuf", FM_LIST_MODEL_SMALLEST_ICON_COLUMN, +                                                 "pixbuf_emblem", FM_LIST_MODEL_SMALLEST_EMBLEM_COLUMN, +                                                 NULL); + +            cell = caja_cell_renderer_text_ellipsized_new (); +            view->details->file_name_cell = (GtkCellRendererText *)cell; +            g_signal_connect (cell, "edited", G_CALLBACK (cell_renderer_edited), view); +            g_signal_connect (cell, "editing-canceled", G_CALLBACK (cell_renderer_editing_canceled), view); +            g_signal_connect (cell, "editing-started", G_CALLBACK (cell_renderer_editing_started_cb), view); + +            gtk_tree_view_column_pack_start (view->details->file_name_column, cell, TRUE); +            gtk_tree_view_column_set_cell_data_func (view->details->file_name_column, cell, +                    (GtkTreeCellDataFunc) filename_cell_data_func, +                    view, NULL); +        } +        else +        { +            cell = gtk_cell_renderer_text_new (); +            g_object_set (cell, "xalign", xalign, NULL); +            view->details->cells = g_list_append (view->details->cells, +                                                  cell); +            column = gtk_tree_view_column_new_with_attributes (label, +                     cell, +                     "text", column_num, +                     NULL); +            g_object_ref_sink (column); +            gtk_tree_view_column_set_sort_column_id (column, column_num); +            g_hash_table_insert (view->details->columns, +                                 g_strdup (name), +                                 column); + +            gtk_tree_view_column_set_resizable (column, TRUE); +            gtk_tree_view_column_set_visible (column, TRUE); +        } +        g_free (name); +        g_free (label); +    } +    caja_column_list_free (caja_columns); + +    /* Apply the default column order and visible columns, to get it +     * right most of the time. The metadata will be checked when a +     * folder is loaded */ +    apply_columns_settings (view, +                            default_column_order_auto_value, +                            default_visible_columns_auto_value); + +    gtk_widget_show (GTK_WIDGET (view->details->tree_view)); +    gtk_container_add (GTK_CONTAINER (view), GTK_WIDGET (view->details->tree_view)); + + +    atk_obj = gtk_widget_get_accessible (GTK_WIDGET (view->details->tree_view)); +    atk_object_set_name (atk_obj, _("List View")); +} + +static void +fm_list_view_add_file (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory) +{ +    FMListModel *model; + +    model = FM_LIST_VIEW (view)->details->model; +    fm_list_model_add_file (model, file, directory); +} + +static char ** +get_visible_columns (FMListView *list_view) +{ +    CajaFile *file; +    GList *visible_columns; +    char **ret; + +    ret = NULL; + +    file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (list_view)); + +    visible_columns = caja_file_get_metadata_list +                      (file, +                       CAJA_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS); + +    if (visible_columns) +    { +        GPtrArray *res; +        GList *l; + +        res = g_ptr_array_new (); +        for (l = visible_columns; l != NULL; l = l->next) +        { +            g_ptr_array_add (res, l->data); +        } +        g_ptr_array_add (res, NULL); + +        ret = (char **) g_ptr_array_free (res, FALSE); +        g_list_free (visible_columns); +    } + +    if (ret != NULL) +    { +        return ret; +    } + +    return caja_file_is_in_trash (file) ? +           g_strdupv ((gchar **) default_trash_visible_columns) : +           g_strdupv (default_visible_columns_auto_value); +} + +static char ** +get_column_order (FMListView *list_view) +{ +    CajaFile *file; +    GList *column_order; +    char **ret; + +    ret = NULL; + +    file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (list_view)); + +    column_order = caja_file_get_metadata_list +                   (file, +                    CAJA_METADATA_KEY_LIST_VIEW_COLUMN_ORDER); + +    if (column_order) +    { +        GPtrArray *res; +        GList *l; + +        res = g_ptr_array_new (); +        for (l = column_order; l != NULL; l = l->next) +        { +            g_ptr_array_add (res, l->data); +        } +        g_ptr_array_add (res, NULL); + +        ret = (char **) g_ptr_array_free (res, FALSE); +        g_list_free (column_order); +    } + +    if (ret != NULL) +    { +        return ret; +    } + +    return caja_file_is_in_trash (file) ? +           g_strdupv ((gchar **) default_trash_columns_order) : +           g_strdupv (default_column_order_auto_value); +} + +static void +set_columns_settings_from_metadata_and_preferences (FMListView *list_view) +{ +    char **column_order; +    char **visible_columns; + +    column_order = get_column_order (list_view); +    visible_columns = get_visible_columns (list_view); + +    apply_columns_settings (list_view, column_order, visible_columns); + +    g_strfreev (column_order); +    g_strfreev (visible_columns); +} + +static void +set_sort_order_from_metadata_and_preferences (FMListView *list_view) +{ +    char *sort_attribute; +    int sort_column_id; +    CajaFile *file; +    gboolean sort_reversed, default_sort_reversed; +    const gchar *default_sort_order; + +    file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (list_view)); +    sort_attribute = caja_file_get_metadata (file, +                     CAJA_METADATA_KEY_LIST_VIEW_SORT_COLUMN, +                     NULL); +    sort_column_id = fm_list_model_get_sort_column_id_from_attribute (list_view->details->model, +                     g_quark_from_string (sort_attribute)); +    g_free (sort_attribute); + +    default_sort_order = get_default_sort_order (file, &default_sort_reversed); + +    if (sort_column_id == -1) +    { +        sort_column_id = +            fm_list_model_get_sort_column_id_from_attribute (list_view->details->model, +                    g_quark_from_string (default_sort_order)); +    } + +    sort_reversed = caja_file_get_boolean_metadata (file, +                    CAJA_METADATA_KEY_LIST_VIEW_SORT_REVERSED, +                    default_sort_reversed); + +    gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (list_view->details->model), +                                          sort_column_id, +                                          sort_reversed ? GTK_SORT_DESCENDING : GTK_SORT_ASCENDING); +} + +static gboolean +list_view_changed_foreach (GtkTreeModel *model, +                           GtkTreePath  *path, +                           GtkTreeIter  *iter, +                           gpointer      data) +{ +    gtk_tree_model_row_changed (model, path, iter); +    return FALSE; +} + +static CajaZoomLevel +get_default_zoom_level (void) +{ +    CajaZoomLevel default_zoom_level; + +    default_zoom_level = default_zoom_level_auto_value; + +    if (default_zoom_level <  CAJA_ZOOM_LEVEL_SMALLEST +            || CAJA_ZOOM_LEVEL_LARGEST < default_zoom_level) +    { +        default_zoom_level = CAJA_ZOOM_LEVEL_SMALL; +    } + +    return default_zoom_level; +} + +static void +set_zoom_level_from_metadata_and_preferences (FMListView *list_view) +{ +    CajaFile *file; +    int level; + +    if (fm_directory_view_supports_zooming (FM_DIRECTORY_VIEW (list_view))) +    { +        file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (list_view)); +        level = caja_file_get_integer_metadata (file, +                                                CAJA_METADATA_KEY_LIST_VIEW_ZOOM_LEVEL, +                                                get_default_zoom_level ()); +        fm_list_view_set_zoom_level (list_view, level, TRUE); + +        /* updated the rows after updating the font size */ +        gtk_tree_model_foreach (GTK_TREE_MODEL (list_view->details->model), +                                list_view_changed_foreach, NULL); +    } +} + +static void +fm_list_view_begin_loading (FMDirectoryView *view) +{ +    FMListView *list_view; + +    list_view = FM_LIST_VIEW (view); + +    set_sort_order_from_metadata_and_preferences (list_view); +    set_zoom_level_from_metadata_and_preferences (list_view); +    set_columns_settings_from_metadata_and_preferences (list_view); +} + +static void +stop_cell_editing (FMListView *list_view) +{ +    GtkTreeViewColumn *column; + +    /* Stop an ongoing rename to commit the name changes when the user +     * changes directories without exiting cell edit mode. It also prevents +     * the edited handler from being called on the cleared list model. +     */ +    column = list_view->details->file_name_column; +    if (column != NULL && list_view->details->editable_widget != NULL && +            GTK_IS_CELL_EDITABLE (list_view->details->editable_widget)) +    { +        gtk_cell_editable_editing_done (list_view->details->editable_widget); +    } +} + +static void +fm_list_view_clear (FMDirectoryView *view) +{ +    FMListView *list_view; + +    list_view = FM_LIST_VIEW (view); + +    if (list_view->details->model != NULL) +    { +        stop_cell_editing (list_view); +        fm_list_model_clear (list_view->details->model); +    } +} + +static void +fm_list_view_rename_callback (CajaFile *file, +                              GFile *result_location, +                              GError *error, +                              gpointer callback_data) +{ +    FMListView *view; + +    view = FM_LIST_VIEW (callback_data); + +    if (view->details->renaming_file) +    { +        view->details->rename_done = TRUE; + +        if (error != NULL) +        { +            /* If the rename failed (or was cancelled), kill renaming_file. +             * We won't get a change event for the rename, so otherwise +             * it would stay around forever. +             */ +            caja_file_unref (view->details->renaming_file); +            view->details->renaming_file = NULL; +        } +    } + +    g_object_unref (view); +} + + +static void +fm_list_view_file_changed (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory) +{ +    FMListView *listview; +    GtkTreeIter iter; +    GtkTreePath *file_path; + +    listview = FM_LIST_VIEW (view); + +    fm_list_model_file_changed (listview->details->model, file, directory); + +    if (listview->details->renaming_file != NULL && +            file == listview->details->renaming_file && +            listview->details->rename_done) +    { +        /* This is (probably) the result of the rename operation, and +         * the tree-view changes above could have resorted the list, so +         * scroll to the new position +         */ +        if (fm_list_model_get_tree_iter_from_file (listview->details->model, file, directory, &iter)) +        { +            file_path = gtk_tree_model_get_path (GTK_TREE_MODEL (listview->details->model), &iter); +            gtk_tree_view_scroll_to_cell (listview->details->tree_view, +                                          file_path, NULL, +                                          FALSE, 0.0, 0.0); +            gtk_tree_path_free (file_path); +        } + +        caja_file_unref (listview->details->renaming_file); +        listview->details->renaming_file = NULL; +    } +} + +static GtkWidget * +fm_list_view_get_background_widget (FMDirectoryView *view) +{ +    return GTK_WIDGET (view); +} + +static void +fm_list_view_get_selection_foreach_func (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) +{ +    GList **list; +    CajaFile *file; + +    list = data; + +    gtk_tree_model_get (model, iter, +                        FM_LIST_MODEL_FILE_COLUMN, &file, +                        -1); + +    if (file != NULL) +    { +        (* list) = g_list_prepend ((* list), file); +    } +} + +static GList * +fm_list_view_get_selection (FMDirectoryView *view) +{ +    GList *list; + +    list = NULL; + +    gtk_tree_selection_selected_foreach (gtk_tree_view_get_selection (FM_LIST_VIEW (view)->details->tree_view), +                                         fm_list_view_get_selection_foreach_func, &list); + +    return g_list_reverse (list); +} + +static void +fm_list_view_get_selection_for_file_transfer_foreach_func (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) +{ +    CajaFile *file; +    struct SelectionForeachData *selection_data; +    GtkTreeIter parent, child; + +    selection_data = data; + +    gtk_tree_model_get (model, iter, +                        FM_LIST_MODEL_FILE_COLUMN, &file, +                        -1); + +    if (file != NULL) +    { +        /* If the parent folder is also selected, don't include this file in the +         * file operation, since that would copy it to the toplevel target instead +         * of keeping it as a child of the copied folder +         */ +        child = *iter; +        while (gtk_tree_model_iter_parent (model, &parent, &child)) +        { +            if (gtk_tree_selection_iter_is_selected (selection_data->selection, +                    &parent)) +            { +                return; +            } +            child = parent; +        } + +        caja_file_ref (file); +        selection_data->list = g_list_prepend (selection_data->list, file); +    } +} + + +static GList * +fm_list_view_get_selection_for_file_transfer (FMDirectoryView *view) +{ +    struct SelectionForeachData selection_data; + +    selection_data.list = NULL; +    selection_data.selection = gtk_tree_view_get_selection (FM_LIST_VIEW (view)->details->tree_view); + +    gtk_tree_selection_selected_foreach (selection_data.selection, +                                         fm_list_view_get_selection_for_file_transfer_foreach_func, &selection_data); + +    return g_list_reverse (selection_data.list); +} + + + + +static guint +fm_list_view_get_item_count (FMDirectoryView *view) +{ +    g_return_val_if_fail (FM_IS_LIST_VIEW (view), 0); + +    return fm_list_model_get_length (FM_LIST_VIEW (view)->details->model); +} + +static gboolean +fm_list_view_is_empty (FMDirectoryView *view) +{ +    return fm_list_model_is_empty (FM_LIST_VIEW (view)->details->model); +} + +static void +fm_list_view_end_file_changes (FMDirectoryView *view) +{ +    FMListView *list_view; + +    list_view = FM_LIST_VIEW (view); + +    if (list_view->details->new_selection_path) +    { +        gtk_tree_view_set_cursor (list_view->details->tree_view, +                                  list_view->details->new_selection_path, +                                  NULL, FALSE); +        gtk_tree_path_free (list_view->details->new_selection_path); +        list_view->details->new_selection_path = NULL; +    } +} + +static void +fm_list_view_remove_file (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory) +{ +    GtkTreePath *path; +    GtkTreePath *file_path; +    GtkTreeIter iter; +    GtkTreeIter temp_iter; +    GtkTreeRowReference* row_reference; +    FMListView *list_view; +    GtkTreeModel* tree_model; +    GtkTreeSelection *selection; + +    path = NULL; +    row_reference = NULL; +    list_view = FM_LIST_VIEW (view); +    tree_model = GTK_TREE_MODEL(list_view->details->model); + +    if (fm_list_model_get_tree_iter_from_file (list_view->details->model, file, directory, &iter)) +    { +        selection = gtk_tree_view_get_selection (list_view->details->tree_view); +        file_path = gtk_tree_model_get_path (tree_model, &iter); + +        if (gtk_tree_selection_path_is_selected (selection, file_path)) +        { +            /* get reference for next element in the list view. If the element to be deleted is the +             * last one, get reference to previous element. If there is only one element in view +             * no need to select anything. +            */ +            temp_iter = iter; + +            if (gtk_tree_model_iter_next (tree_model, &iter)) +            { +                path = gtk_tree_model_get_path (tree_model, &iter); +                row_reference = gtk_tree_row_reference_new (tree_model, path); +            } +            else +            { +                path = gtk_tree_model_get_path (tree_model, &temp_iter); +                if (gtk_tree_path_prev (path)) +                { +                    row_reference = gtk_tree_row_reference_new (tree_model, path); +                } +            } +            gtk_tree_path_free (path); +        } + +        gtk_tree_path_free (file_path); + +        fm_list_model_remove_file (list_view->details->model, file, directory); + +        if (gtk_tree_row_reference_valid (row_reference)) +        { +            if (list_view->details->new_selection_path) +            { +                gtk_tree_path_free (list_view->details->new_selection_path); +            } +            list_view->details->new_selection_path = gtk_tree_row_reference_get_path (row_reference); +        } + +        if (row_reference) +        { +            gtk_tree_row_reference_free (row_reference); +        } +    } + + +} + +static void +fm_list_view_set_selection (FMDirectoryView *view, GList *selection) +{ +    FMListView *list_view; +    GtkTreeSelection *tree_selection; +    GList *node; +    GList *iters, *l; +    CajaFile *file; + +    list_view = FM_LIST_VIEW (view); +    tree_selection = gtk_tree_view_get_selection (list_view->details->tree_view); + +    g_signal_handlers_block_by_func (tree_selection, list_selection_changed_callback, view); + +    gtk_tree_selection_unselect_all (tree_selection); +    for (node = selection; node != NULL; node = node->next) +    { +        file = node->data; +        iters = fm_list_model_get_all_iters_for_file (list_view->details->model, file); + +        for (l = iters; l != NULL; l = l->next) +        { +            gtk_tree_selection_select_iter (tree_selection, +                                            (GtkTreeIter *)l->data); +        } +        eel_g_list_free_deep (iters); +    } + +    g_signal_handlers_unblock_by_func (tree_selection, list_selection_changed_callback, view); +    fm_directory_view_notify_selection_changed (view); +} + +static void +fm_list_view_invert_selection (FMDirectoryView *view) +{ +    FMListView *list_view; +    GtkTreeSelection *tree_selection; +    GList *node; +    GList *iters, *l; +    CajaFile *file; +    GList *selection = NULL; + +    list_view = FM_LIST_VIEW (view); +    tree_selection = gtk_tree_view_get_selection (list_view->details->tree_view); + +    g_signal_handlers_block_by_func (tree_selection, list_selection_changed_callback, view); + +    gtk_tree_selection_selected_foreach (tree_selection, +                                         fm_list_view_get_selection_foreach_func, &selection); + +    gtk_tree_selection_select_all (tree_selection); + +    for (node = selection; node != NULL; node = node->next) +    { +        file = node->data; +        iters = fm_list_model_get_all_iters_for_file (list_view->details->model, file); + +        for (l = iters; l != NULL; l = l->next) +        { +            gtk_tree_selection_unselect_iter (tree_selection, +                                              (GtkTreeIter *)l->data); +        } +        eel_g_list_free_deep (iters); +    } + +    g_list_free (selection); + +    g_signal_handlers_unblock_by_func (tree_selection, list_selection_changed_callback, view); +    fm_directory_view_notify_selection_changed (view); +} + +static void +fm_list_view_select_all (FMDirectoryView *view) +{ +    gtk_tree_selection_select_all (gtk_tree_view_get_selection (FM_LIST_VIEW (view)->details->tree_view)); +} + +static void +column_editor_response_callback (GtkWidget *dialog, +                                 int response_id, +                                 gpointer user_data) +{ +    gtk_widget_destroy (GTK_WIDGET (dialog)); +} + +static void +column_chooser_changed_callback (CajaColumnChooser *chooser, +                                 FMListView *view) +{ +    CajaFile *file; +    char **visible_columns; +    char **column_order; +    GList *list; +    int i; + +    file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (view)); + +    caja_column_chooser_get_settings (chooser, +                                      &visible_columns, +                                      &column_order); + +    list = NULL; +    for (i = 0; visible_columns[i] != NULL; ++i) +    { +        list = g_list_prepend (list, visible_columns[i]); +    } +    list = g_list_reverse (list); +    caja_file_set_metadata_list (file, +                                 CAJA_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS, +                                 list); +    g_list_free (list); + +    list = NULL; +    for (i = 0; column_order[i] != NULL; ++i) +    { +        list = g_list_prepend (list, column_order[i]); +    } +    list = g_list_reverse (list); +    caja_file_set_metadata_list (file, +                                 CAJA_METADATA_KEY_LIST_VIEW_COLUMN_ORDER, +                                 list); +    g_list_free (list); + +    apply_columns_settings (view, column_order, visible_columns); + +    g_strfreev (visible_columns); +    g_strfreev (column_order); +} + +static void +column_chooser_set_from_arrays (CajaColumnChooser *chooser, +                                FMListView *view, +                                char **visible_columns, +                                char **column_order) +{ +    g_signal_handlers_block_by_func +    (chooser, G_CALLBACK (column_chooser_changed_callback), view); + +    caja_column_chooser_set_settings (chooser, +                                      visible_columns, +                                      column_order); + +    g_signal_handlers_unblock_by_func +    (chooser, G_CALLBACK (column_chooser_changed_callback), view); +} + +static void +column_chooser_set_from_settings (CajaColumnChooser *chooser, +                                  FMListView *view) +{ +    char **visible_columns; +    char **column_order; + +    visible_columns = get_visible_columns (view); +    column_order = get_column_order (view); + +    column_chooser_set_from_arrays (chooser, view, +                                    visible_columns, column_order); + +    g_strfreev (visible_columns); +    g_strfreev (column_order); +} + +static void +column_chooser_use_default_callback (CajaColumnChooser *chooser, +                                     FMListView *view) +{ +    CajaFile *file; +    char **default_columns; +    char **default_order; + +    file = fm_directory_view_get_directory_as_file +           (FM_DIRECTORY_VIEW (view)); + +    caja_file_set_metadata_list (file, CAJA_METADATA_KEY_LIST_VIEW_COLUMN_ORDER, NULL); +    caja_file_set_metadata_list (file, CAJA_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS, NULL); + +    /* set view values ourselves, as new metadata could not have been +     * updated yet. +     */ +    default_columns = caja_file_is_in_trash (file) ? +                      g_strdupv ((gchar **) default_trash_visible_columns) : +                      g_strdupv (default_visible_columns_auto_value); + +    default_order = caja_file_is_in_trash (file) ? +                    g_strdupv ((gchar **) default_trash_columns_order) : +                    g_strdupv (default_column_order_auto_value); + +    apply_columns_settings (view, default_order, default_columns); +    column_chooser_set_from_arrays (chooser, view, +                                    default_columns, default_order); + +    g_strfreev (default_columns); +    g_strfreev (default_order); +} + +static GtkWidget * +create_column_editor (FMListView *view) +{ +    GtkWidget *window; +    GtkWidget *label; +    GtkWidget *box; +    GtkWidget *column_chooser; +    GtkWidget *alignment; +    CajaFile *file; +    char *str; +    char *name; +    const char *label_text; + +    file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (view)); +    name = caja_file_get_display_name (file); +    str = g_strdup_printf (_("%s Visible Columns"), name); +    g_free (name); + +    window = gtk_dialog_new_with_buttons (str, +                                          GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (view))), +                                          GTK_DIALOG_DESTROY_WITH_PARENT, +                                          GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, +                                          NULL); +    g_free (str); +    g_signal_connect (window, "response", +                      G_CALLBACK (column_editor_response_callback), NULL); + +    gtk_window_set_default_size (GTK_WINDOW (window), 300, 400); + +    box = gtk_vbox_new (FALSE, 12); +    gtk_container_set_border_width (GTK_CONTAINER (box), 12); +    gtk_widget_show (box); +    gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (window))), box); + +    label_text = _("Choose the order of information to appear in this folder:"); +    str = g_strconcat ("<b>", label_text, "</b>", NULL); +    label = gtk_label_new (NULL); +    gtk_label_set_markup (GTK_LABEL (label), str); +    gtk_label_set_line_wrap (GTK_LABEL (label), FALSE); +    gtk_misc_set_alignment (GTK_MISC (label), 0, 0); +    gtk_widget_show (label); +    gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0); + +    g_free (str); + +    alignment = gtk_alignment_new (0.5, 0.5, 1, 1); +    gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), +                               0, 0, 12, 0); +    gtk_widget_show (alignment); +    gtk_box_pack_start (GTK_BOX (box), alignment, TRUE, TRUE, 0); + +    column_chooser = caja_column_chooser_new (file); +    gtk_widget_show (column_chooser); +    gtk_container_add (GTK_CONTAINER (alignment), column_chooser); + +    g_signal_connect (column_chooser, "changed", +                      G_CALLBACK (column_chooser_changed_callback), +                      view); +    g_signal_connect (column_chooser, "use_default", +                      G_CALLBACK (column_chooser_use_default_callback), +                      view); + +    column_chooser_set_from_settings +    (CAJA_COLUMN_CHOOSER (column_chooser), view); + +    return window; +} + +static void +action_visible_columns_callback (GtkAction *action, +                                 gpointer callback_data) +{ +    FMListView *list_view; + +    list_view = FM_LIST_VIEW (callback_data); + +    if (list_view->details->column_editor) +    { +        gtk_window_present (GTK_WINDOW (list_view->details->column_editor)); +    } +    else +    { +        list_view->details->column_editor = create_column_editor (list_view); +        eel_add_weak_pointer (&list_view->details->column_editor); + +        gtk_widget_show (list_view->details->column_editor); +    } +} + +static const GtkActionEntry list_view_entries[] = +{ +    /* name, stock id */     { "Visible Columns", NULL, +        /* label, accelerator */   N_("Visible _Columns..."), NULL, +        /* tooltip */              N_("Select the columns visible in this folder"), +        G_CALLBACK (action_visible_columns_callback) +    }, +}; + +static void +fm_list_view_merge_menus (FMDirectoryView *view) +{ +    FMListView *list_view; +    GtkUIManager *ui_manager; +    GtkActionGroup *action_group; +    const char *ui; + +    EEL_CALL_PARENT (FM_DIRECTORY_VIEW_CLASS, merge_menus, (view)); + +    list_view = FM_LIST_VIEW (view); + +    ui_manager = fm_directory_view_get_ui_manager (view); + +    action_group = gtk_action_group_new ("ListViewActions"); +    gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE); +    list_view->details->list_action_group = action_group; +    gtk_action_group_add_actions (action_group, +                                  list_view_entries, G_N_ELEMENTS (list_view_entries), +                                  list_view); + +    gtk_ui_manager_insert_action_group (ui_manager, action_group, 0); +    g_object_unref (action_group); /* owned by ui manager */ + +    ui = caja_ui_string_get ("caja-list-view-ui.xml"); +    list_view->details->list_merge_id = gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, NULL); + +    list_view->details->menus_ready = TRUE; +} + +static void +fm_list_view_unmerge_menus (FMDirectoryView *view) +{ +    FMListView *list_view; +    GtkUIManager *ui_manager; + +    list_view = FM_LIST_VIEW (view); + +    FM_DIRECTORY_VIEW_CLASS (fm_list_view_parent_class)->unmerge_menus (view); + +    ui_manager = fm_directory_view_get_ui_manager (view); +    if (ui_manager != NULL) +    { +        caja_ui_unmerge_ui (ui_manager, +                            &list_view->details->list_merge_id, +                            &list_view->details->list_action_group); +    } +} + +static void +fm_list_view_update_menus (FMDirectoryView *view) +{ +    FMListView *list_view; + +    list_view = FM_LIST_VIEW (view); + +    /* don't update if the menus aren't ready */ +    if (!list_view->details->menus_ready) +    { +        return; +    } + +    EEL_CALL_PARENT (FM_DIRECTORY_VIEW_CLASS, update_menus, (view)); +} + +/* Reset sort criteria and zoom level to match defaults */ +static void +fm_list_view_reset_to_defaults (FMDirectoryView *view) +{ +    CajaFile *file; +    const gchar *default_sort_order; +    gboolean default_sort_reversed; + +    file = fm_directory_view_get_directory_as_file (view); + +    caja_file_set_metadata (file, CAJA_METADATA_KEY_LIST_VIEW_SORT_COLUMN, NULL, NULL); +    caja_file_set_metadata (file, CAJA_METADATA_KEY_LIST_VIEW_SORT_REVERSED, NULL, NULL); +    caja_file_set_metadata (file, CAJA_METADATA_KEY_LIST_VIEW_ZOOM_LEVEL, NULL, NULL); +    caja_file_set_metadata_list (file, CAJA_METADATA_KEY_LIST_VIEW_COLUMN_ORDER, NULL); +    caja_file_set_metadata_list (file, CAJA_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS, NULL); + +    default_sort_order = get_default_sort_order (file, &default_sort_reversed); + +    gtk_tree_sortable_set_sort_column_id +    (GTK_TREE_SORTABLE (FM_LIST_VIEW (view)->details->model), +     fm_list_model_get_sort_column_id_from_attribute (FM_LIST_VIEW (view)->details->model, +             g_quark_from_string (default_sort_order)), +     default_sort_reversed ? GTK_SORT_DESCENDING : GTK_SORT_ASCENDING); + +    fm_list_view_set_zoom_level (FM_LIST_VIEW (view), get_default_zoom_level (), FALSE); +    set_columns_settings_from_metadata_and_preferences (FM_LIST_VIEW (view)); +} + +static void +fm_list_view_scale_font_size (FMListView *view, +                              CajaZoomLevel new_level) +{ +    GList *l; +    static gboolean first_time = TRUE; +    static double pango_scale[7]; +    int medium; +    int i; + +    g_return_if_fail (new_level >= CAJA_ZOOM_LEVEL_SMALLEST && +                      new_level <= CAJA_ZOOM_LEVEL_LARGEST); + +    if (first_time) +    { +        first_time = FALSE; +        medium = CAJA_ZOOM_LEVEL_SMALLER; +        pango_scale[medium] = PANGO_SCALE_MEDIUM; +        for (i = medium; i > CAJA_ZOOM_LEVEL_SMALLEST; i--) +        { +            pango_scale[i - 1] = (1 / 1.2) * pango_scale[i]; +        } +        for (i = medium; i < CAJA_ZOOM_LEVEL_LARGEST; i++) +        { +            pango_scale[i + 1] = 1.2 * pango_scale[i]; +        } +    } + +    g_object_set (G_OBJECT (view->details->file_name_cell), +                  "scale", pango_scale[new_level], +                  NULL); +    for (l = view->details->cells; l != NULL; l = l->next) +    { +        g_object_set (G_OBJECT (l->data), +                      "scale", pango_scale[new_level], +                      NULL); +    } +} + +static void +fm_list_view_set_zoom_level (FMListView *view, +                             CajaZoomLevel new_level, +                             gboolean always_emit) +{ +    int icon_size; +    int column, emblem_column; + +    g_return_if_fail (FM_IS_LIST_VIEW (view)); +    g_return_if_fail (new_level >= CAJA_ZOOM_LEVEL_SMALLEST && +                      new_level <= CAJA_ZOOM_LEVEL_LARGEST); + +    if (view->details->zoom_level == new_level) +    { +        if (always_emit) +        { +            g_signal_emit_by_name (FM_DIRECTORY_VIEW(view), "zoom_level_changed"); +        } +        return; +    } + +    view->details->zoom_level = new_level; +    g_signal_emit_by_name (FM_DIRECTORY_VIEW(view), "zoom_level_changed"); + +    caja_file_set_integer_metadata +    (fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (view)), +     CAJA_METADATA_KEY_LIST_VIEW_ZOOM_LEVEL, +     get_default_zoom_level (), +     new_level); + +    /* Select correctly scaled icons. */ +    column = fm_list_model_get_column_id_from_zoom_level (new_level); +    emblem_column = fm_list_model_get_emblem_column_id_from_zoom_level (new_level); +    gtk_tree_view_column_set_attributes (view->details->file_name_column, +                                         GTK_CELL_RENDERER (view->details->pixbuf_cell), +                                         "pixbuf", column, +                                         "pixbuf_emblem", emblem_column, +                                         NULL); + +    /* Scale text. */ +    fm_list_view_scale_font_size (view, new_level); + +    /* Make all rows the same size. */ +    icon_size = caja_get_icon_size_for_zoom_level (new_level); +    gtk_cell_renderer_set_fixed_size (GTK_CELL_RENDERER (view->details->pixbuf_cell), +                                      -1, icon_size); + +    fm_directory_view_update_menus (FM_DIRECTORY_VIEW (view)); +} + +static void +fm_list_view_bump_zoom_level (FMDirectoryView *view, int zoom_increment) +{ +    FMListView *list_view; +    gint new_level; + +    g_return_if_fail (FM_IS_LIST_VIEW (view)); + +    list_view = FM_LIST_VIEW (view); +    new_level = list_view->details->zoom_level + zoom_increment; + +    if (new_level >= CAJA_ZOOM_LEVEL_SMALLEST && +            new_level <= CAJA_ZOOM_LEVEL_LARGEST) +    { +        fm_list_view_set_zoom_level (list_view, new_level, FALSE); +    } +} + +static CajaZoomLevel +fm_list_view_get_zoom_level (FMDirectoryView *view) +{ +    FMListView *list_view; + +    g_return_val_if_fail (FM_IS_LIST_VIEW (view), CAJA_ZOOM_LEVEL_STANDARD); + +    list_view = FM_LIST_VIEW (view); + +    return list_view->details->zoom_level; +} + +static void +fm_list_view_zoom_to_level (FMDirectoryView *view, +                            CajaZoomLevel zoom_level) +{ +    FMListView *list_view; + +    g_return_if_fail (FM_IS_LIST_VIEW (view)); + +    list_view = FM_LIST_VIEW (view); + +    fm_list_view_set_zoom_level (list_view, zoom_level, FALSE); +} + +static void +fm_list_view_restore_default_zoom_level (FMDirectoryView *view) +{ +    FMListView *list_view; + +    g_return_if_fail (FM_IS_LIST_VIEW (view)); + +    list_view = FM_LIST_VIEW (view); + +    fm_list_view_set_zoom_level (list_view, get_default_zoom_level (), FALSE); +} + +static gboolean +fm_list_view_can_zoom_in (FMDirectoryView *view) +{ +    g_return_val_if_fail (FM_IS_LIST_VIEW (view), FALSE); + +    return FM_LIST_VIEW (view)->details->zoom_level	< CAJA_ZOOM_LEVEL_LARGEST; +} + +static gboolean +fm_list_view_can_zoom_out (FMDirectoryView *view) +{ +    g_return_val_if_fail (FM_IS_LIST_VIEW (view), FALSE); + +    return FM_LIST_VIEW (view)->details->zoom_level > CAJA_ZOOM_LEVEL_SMALLEST; +} + +static void +fm_list_view_start_renaming_file (FMDirectoryView *view, +                                  CajaFile *file, +                                  gboolean select_all) +{ +    FMListView *list_view; +    GtkTreeIter iter; +    GtkTreePath *path; + +    list_view = FM_LIST_VIEW (view); + +    /* Select all if we are in renaming mode already */ +    if (list_view->details->file_name_column && list_view->details->editable_widget) +    { +        gtk_editable_select_region ( +            GTK_EDITABLE (list_view->details->editable_widget), +            0, +            -1); +        return; +    } + +    if (!fm_list_model_get_first_iter_for_file (list_view->details->model, file, &iter)) +    { +        return; +    } + +    /* Freeze updates to the view to prevent losing rename focus when the tree view updates */ +    fm_directory_view_freeze_updates (FM_DIRECTORY_VIEW (view)); + +    path = gtk_tree_model_get_path (GTK_TREE_MODEL (list_view->details->model), &iter); + +    /* Make filename-cells editable. */ +    g_object_set (G_OBJECT (list_view->details->file_name_cell), +                  "editable", TRUE, +                  NULL); + +    gtk_tree_view_scroll_to_cell (list_view->details->tree_view, +                                  NULL, +                                  list_view->details->file_name_column, +                                  TRUE, 0.0, 0.0); +    gtk_tree_view_set_cursor (list_view->details->tree_view, +                              path, +                              list_view->details->file_name_column, +                              TRUE); + +    gtk_tree_path_free (path); +} + +static void +fm_list_view_click_policy_changed (FMDirectoryView *directory_view) +{ +    GdkWindow *win; +    GdkDisplay *display; +    FMListView *view; +    GtkTreeIter iter; +    GtkTreeView *tree; + +    view = FM_LIST_VIEW (directory_view); + +    /* ensure that we unset the hand cursor and refresh underlined rows */ +    if (click_policy_auto_value == CAJA_CLICK_POLICY_DOUBLE) +    { +        if (view->details->hover_path != NULL) +        { +            if (gtk_tree_model_get_iter (GTK_TREE_MODEL (view->details->model), +                                         &iter, view->details->hover_path)) +            { +                gtk_tree_model_row_changed (GTK_TREE_MODEL (view->details->model), +                                            view->details->hover_path, &iter); +            } + +            gtk_tree_path_free (view->details->hover_path); +            view->details->hover_path = NULL; +        } + +        tree = view->details->tree_view; +        if (gtk_widget_get_realized (GTK_WIDGET (tree))) +        { +            win = gtk_widget_get_window (GTK_WIDGET (tree)); +            gdk_window_set_cursor (win, NULL); + +            display = gtk_widget_get_display (GTK_WIDGET (view)); +            if (display != NULL) +            { +                gdk_display_flush (display); +            } +        } + +        if (hand_cursor != NULL) +        { +            gdk_cursor_unref (hand_cursor); +            hand_cursor = NULL; +        } +    } +    else if (click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE) +    { +        if (hand_cursor == NULL) +        { +            hand_cursor = gdk_cursor_new(GDK_HAND2); +        } +    } +} + +static void +default_sort_order_changed_callback (gpointer callback_data) +{ +    FMListView *list_view; + +    list_view = FM_LIST_VIEW (callback_data); + +    set_sort_order_from_metadata_and_preferences (list_view); +} + +static void +default_zoom_level_changed_callback (gpointer callback_data) +{ +    FMListView *list_view; + +    list_view = FM_LIST_VIEW (callback_data); + +    set_zoom_level_from_metadata_and_preferences (list_view); +} + +static void +default_visible_columns_changed_callback (gpointer callback_data) +{ +    FMListView *list_view; + +    list_view = FM_LIST_VIEW (callback_data); + +    set_columns_settings_from_metadata_and_preferences (list_view); +} + +static void +default_column_order_changed_callback (gpointer callback_data) +{ +    FMListView *list_view; + +    list_view = FM_LIST_VIEW (callback_data); + +    set_columns_settings_from_metadata_and_preferences (list_view); +} + +static void +fm_list_view_sort_directories_first_changed (FMDirectoryView *view) +{ +    FMListView *list_view; + +    list_view = FM_LIST_VIEW (view); + +    fm_list_model_set_should_sort_directories_first (list_view->details->model, +            fm_directory_view_should_sort_directories_first (view)); +} + +static int +fm_list_view_compare_files (FMDirectoryView *view, CajaFile *file1, CajaFile *file2) +{ +    FMListView *list_view; + +    list_view = FM_LIST_VIEW (view); +    return fm_list_model_compare_func (list_view->details->model, file1, file2); +} + +static gboolean +fm_list_view_using_manual_layout (FMDirectoryView *view) +{ +    g_return_val_if_fail (FM_IS_LIST_VIEW (view), FALSE); + +    return FALSE; +} + +static void +fm_list_view_dispose (GObject *object) +{ +    FMListView *list_view; + +    list_view = FM_LIST_VIEW (object); + +    if (list_view->details->model) +    { +        stop_cell_editing (list_view); +        g_object_unref (list_view->details->model); +        list_view->details->model = NULL; +    } + +    if (list_view->details->drag_dest) +    { +        g_object_unref (list_view->details->drag_dest); +        list_view->details->drag_dest = NULL; +    } + +    if (list_view->details->renaming_file_activate_timeout != 0) +    { +        g_source_remove (list_view->details->renaming_file_activate_timeout); +        list_view->details->renaming_file_activate_timeout = 0; +    } + +    if (list_view->details->clipboard_handler_id != 0) +    { +        g_signal_handler_disconnect (caja_clipboard_monitor_get (), +                                     list_view->details->clipboard_handler_id); +        list_view->details->clipboard_handler_id = 0; +    } + +    G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +fm_list_view_finalize (GObject *object) +{ +    FMListView *list_view; + +    list_view = FM_LIST_VIEW (object); + +    g_free (list_view->details->original_name); +    list_view->details->original_name = NULL; + +    if (list_view->details->double_click_path[0]) +    { +        gtk_tree_path_free (list_view->details->double_click_path[0]); +    } +    if (list_view->details->double_click_path[1]) +    { +        gtk_tree_path_free (list_view->details->double_click_path[1]); +    } +    if (list_view->details->new_selection_path) +    { +        gtk_tree_path_free (list_view->details->new_selection_path); +    } + +    g_list_free (list_view->details->cells); +    g_hash_table_destroy (list_view->details->columns); + +    if (list_view->details->hover_path != NULL) +    { +        gtk_tree_path_free (list_view->details->hover_path); +    } + +    if (list_view->details->column_editor != NULL) +    { +        gtk_widget_destroy (list_view->details->column_editor); +    } + +    g_free (list_view->details); + +    G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +fm_list_view_emblems_changed (FMDirectoryView *directory_view) +{ +    g_assert (FM_IS_LIST_VIEW (directory_view)); + +    /* FIXME: This needs to update the emblems of the icons, since +     * relative emblems may have changed. +     */ +} + +static char * +fm_list_view_get_first_visible_file (CajaView *view) +{ +    CajaFile *file; +    GtkTreePath *path; +    GtkTreeIter iter; +    FMListView *list_view; + +    list_view = FM_LIST_VIEW (view); + +    if (gtk_tree_view_get_path_at_pos (list_view->details->tree_view, +                                       0, 0, +                                       &path, NULL, NULL, NULL)) +    { +        gtk_tree_model_get_iter (GTK_TREE_MODEL (list_view->details->model), +                                 &iter, path); + +        gtk_tree_path_free (path); + +        gtk_tree_model_get (GTK_TREE_MODEL (list_view->details->model), +                            &iter, +                            FM_LIST_MODEL_FILE_COLUMN, &file, +                            -1); +        if (file) +        { +            char *uri; + +            uri = caja_file_get_uri (file); + +            caja_file_unref (file); + +            return uri; +        } +    } + +    return NULL; +} + +static void +fm_list_view_scroll_to_file (FMListView *view, +                             CajaFile *file) +{ +    GtkTreePath *path; +    GtkTreeIter iter; + +    if (!fm_list_model_get_first_iter_for_file (view->details->model, file, &iter)) +    { +        return; +    } + +    path = gtk_tree_model_get_path (GTK_TREE_MODEL (view->details->model), &iter); + +    gtk_tree_view_scroll_to_cell (view->details->tree_view, +                                  path, NULL, +                                  TRUE, 0.0, 0.0); + +    gtk_tree_path_free (path); +} + +static void +list_view_scroll_to_file (CajaView *view, +                          const char *uri) +{ +    CajaFile *file; + +    if (uri != NULL) +    { +        /* Only if existing, since we don't want to add the file to +           the directory if it has been removed since then */ +        file = caja_file_get_existing_by_uri (uri); +        if (file != NULL) +        { +            fm_list_view_scroll_to_file (FM_LIST_VIEW (view), file); +            caja_file_unref (file); +        } +    } +} + +static void +list_view_notify_clipboard_info (CajaClipboardMonitor *monitor, +                                 CajaClipboardInfo *info, +                                 FMListView *view) +{ +    /* this could be called as a result of _end_loading() being +     * called after _dispose(), where the model is cleared. +     */ +    if (view->details->model == NULL) +    { +        return; +    } + +    if (info != NULL && info->cut) +    { +        fm_list_model_set_highlight_for_files (view->details->model, info->files); +    } +    else +    { +        fm_list_model_set_highlight_for_files (view->details->model, NULL); +    } +} + +static void +fm_list_view_end_loading (FMDirectoryView *view, +                          gboolean all_files_seen) +{ +    CajaClipboardMonitor *monitor; +    CajaClipboardInfo *info; + +    monitor = caja_clipboard_monitor_get (); +    info = caja_clipboard_monitor_get_clipboard_info (monitor); + +    list_view_notify_clipboard_info (monitor, info, FM_LIST_VIEW (view)); +} + +static void +real_set_is_active (FMDirectoryView *view, +                    gboolean is_active) +{ +    GtkWidget *tree_view; +    GtkStyle *style; +    GdkColor color; + +    tree_view = GTK_WIDGET (fm_list_view_get_tree_view (FM_LIST_VIEW (view))); + +    if (is_active) +    { +        gtk_widget_modify_base (tree_view, GTK_STATE_NORMAL, NULL); +    } +    else +    { +        style = gtk_widget_get_style (tree_view); +        color = style->base[GTK_STATE_INSENSITIVE]; +        gtk_widget_modify_base (tree_view, GTK_STATE_NORMAL, &color); +    } + +    EEL_CALL_PARENT (FM_DIRECTORY_VIEW_CLASS, +                     set_is_active, (view, is_active)); +} + +static void +fm_list_view_class_init (FMListViewClass *class) +{ +    FMDirectoryViewClass *fm_directory_view_class; + +    fm_directory_view_class = FM_DIRECTORY_VIEW_CLASS (class); + +    G_OBJECT_CLASS (class)->dispose = fm_list_view_dispose; +    G_OBJECT_CLASS (class)->finalize = fm_list_view_finalize; + +    fm_directory_view_class->add_file = fm_list_view_add_file; +    fm_directory_view_class->begin_loading = fm_list_view_begin_loading; +    fm_directory_view_class->end_loading = fm_list_view_end_loading; +    fm_directory_view_class->bump_zoom_level = fm_list_view_bump_zoom_level; +    fm_directory_view_class->can_zoom_in = fm_list_view_can_zoom_in; +    fm_directory_view_class->can_zoom_out = fm_list_view_can_zoom_out; +    fm_directory_view_class->click_policy_changed = fm_list_view_click_policy_changed; +    fm_directory_view_class->clear = fm_list_view_clear; +    fm_directory_view_class->file_changed = fm_list_view_file_changed; +    fm_directory_view_class->get_background_widget = fm_list_view_get_background_widget; +    fm_directory_view_class->get_selection = fm_list_view_get_selection; +    fm_directory_view_class->get_selection_for_file_transfer = fm_list_view_get_selection_for_file_transfer; +    fm_directory_view_class->get_item_count = fm_list_view_get_item_count; +    fm_directory_view_class->is_empty = fm_list_view_is_empty; +    fm_directory_view_class->remove_file = fm_list_view_remove_file; +    fm_directory_view_class->merge_menus = fm_list_view_merge_menus; +    fm_directory_view_class->unmerge_menus = fm_list_view_unmerge_menus; +    fm_directory_view_class->update_menus = fm_list_view_update_menus; +    fm_directory_view_class->reset_to_defaults = fm_list_view_reset_to_defaults; +    fm_directory_view_class->restore_default_zoom_level = fm_list_view_restore_default_zoom_level; +    fm_directory_view_class->reveal_selection = fm_list_view_reveal_selection; +    fm_directory_view_class->select_all = fm_list_view_select_all; +    fm_directory_view_class->set_selection = fm_list_view_set_selection; +    fm_directory_view_class->invert_selection = fm_list_view_invert_selection; +    fm_directory_view_class->compare_files = fm_list_view_compare_files; +    fm_directory_view_class->sort_directories_first_changed = fm_list_view_sort_directories_first_changed; +    fm_directory_view_class->start_renaming_file = fm_list_view_start_renaming_file; +    fm_directory_view_class->get_zoom_level = fm_list_view_get_zoom_level; +    fm_directory_view_class->zoom_to_level = fm_list_view_zoom_to_level; +    fm_directory_view_class->emblems_changed = fm_list_view_emblems_changed; +    fm_directory_view_class->end_file_changes = fm_list_view_end_file_changes; +    fm_directory_view_class->using_manual_layout = fm_list_view_using_manual_layout; +    fm_directory_view_class->set_is_active = real_set_is_active; + +    eel_preferences_add_auto_enum (CAJA_PREFERENCES_CLICK_POLICY, +                                   &click_policy_auto_value); +    eel_preferences_add_auto_string (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_SORT_ORDER, +                                     (const char **) &default_sort_order_auto_value); +    eel_preferences_add_auto_boolean (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_SORT_IN_REVERSE_ORDER, +                                      &default_sort_reversed_auto_value); +    eel_preferences_add_auto_enum (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_ZOOM_LEVEL, +                                   (int *) &default_zoom_level_auto_value); +    eel_preferences_add_auto_string_array (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS, +                                           &default_visible_columns_auto_value); +    eel_preferences_add_auto_string_array (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_COLUMN_ORDER, +                                           &default_column_order_auto_value); +} + +static const char * +fm_list_view_get_id (CajaView *view) +{ +    return FM_LIST_VIEW_ID; +} + + +static void +fm_list_view_iface_init (CajaViewIface *iface) +{ +    fm_directory_view_init_view_iface (iface); + +    iface->get_view_id = fm_list_view_get_id; +    iface->get_first_visible_file = fm_list_view_get_first_visible_file; +    iface->scroll_to_file = list_view_scroll_to_file; +    iface->get_title = NULL; +} + + +static void +fm_list_view_init (FMListView *list_view) +{ +    list_view->details = g_new0 (FMListViewDetails, 1); + +    create_and_set_up_tree_view (list_view); + +    eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_SORT_ORDER, +            default_sort_order_changed_callback, +            list_view, G_OBJECT (list_view)); +    eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_SORT_IN_REVERSE_ORDER, +            default_sort_order_changed_callback, +            list_view, G_OBJECT (list_view)); +    eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_ZOOM_LEVEL, +            default_zoom_level_changed_callback, +            list_view, G_OBJECT (list_view)); +    eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS, +            default_visible_columns_changed_callback, +            list_view, G_OBJECT (list_view)); +    eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_COLUMN_ORDER, +            default_column_order_changed_callback, +            list_view, G_OBJECT (list_view)); + +    fm_list_view_click_policy_changed (FM_DIRECTORY_VIEW (list_view)); + +    fm_list_view_sort_directories_first_changed (FM_DIRECTORY_VIEW (list_view)); + +    /* ensure that the zoom level is always set in begin_loading */ +    list_view->details->zoom_level = CAJA_ZOOM_LEVEL_SMALLEST - 1; + +    list_view->details->hover_path = NULL; +    list_view->details->clipboard_handler_id = +        g_signal_connect (caja_clipboard_monitor_get (), +                          "clipboard_info", +                          G_CALLBACK (list_view_notify_clipboard_info), list_view); +} + +static CajaView * +fm_list_view_create (CajaWindowSlotInfo *slot) +{ +    FMListView *view; + +    view = g_object_new (FM_TYPE_LIST_VIEW, +                         "window-slot", slot, +                         NULL); +    return CAJA_VIEW (view); +} + +static gboolean +fm_list_view_supports_uri (const char *uri, +                           GFileType file_type, +                           const char *mime_type) +{ +    if (file_type == G_FILE_TYPE_DIRECTORY) +    { +        return TRUE; +    } +    if (strcmp (mime_type, CAJA_SAVED_SEARCH_MIMETYPE) == 0) +    { +        return TRUE; +    } +    if (g_str_has_prefix (uri, "trash:")) +    { +        return TRUE; +    } +    if (g_str_has_prefix (uri, EEL_SEARCH_URI)) +    { +        return TRUE; +    } + +    return FALSE; +} + +static CajaViewInfo fm_list_view = +{ +    FM_LIST_VIEW_ID, +    /* translators: this is used in the view selection dropdown +     * of navigation windows and in the preferences dialog */ +    N_("List View"), +    /* translators: this is used in the view menu */ +    N_("_List"), +    N_("The list view encountered an error."), +    N_("The list view encountered an error while starting up."), +    N_("Display this location with the list view."), +    fm_list_view_create, +    fm_list_view_supports_uri +}; + +void +fm_list_view_register (void) +{ +    fm_list_view.view_combo_label = _(fm_list_view.view_combo_label); +    fm_list_view.view_menu_label_with_mnemonic = _(fm_list_view.view_menu_label_with_mnemonic); +    fm_list_view.error_label = _(fm_list_view.error_label); +    fm_list_view.startup_error_label = _(fm_list_view.startup_error_label); +    fm_list_view.display_location_label = _(fm_list_view.display_location_label); + +    caja_view_factory_register (&fm_list_view); +} + +GtkTreeView* +fm_list_view_get_tree_view (FMListView *list_view) +{ +    return list_view->details->tree_view; +} diff --git a/src/file-manager/fm-list-view.h b/src/file-manager/fm-list-view.h new file mode 100644 index 00000000..2defc6ca --- /dev/null +++ b/src/file-manager/fm-list-view.h @@ -0,0 +1,63 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-list-view.h - interface for list view of directory. + +   Copyright (C) 2000 Eazel, Inc. +   Copyright (C) 2001 Anders Carlsson <[email protected]> + +   The Mate Library is free software; you can redistribute it and/or +   modify it under the terms of the GNU Library General Public License as +   published by the Free Software Foundation; either version 2 of the +   License, or (at your option) any later version. + +   The Mate Library is distributed in the hope that it will be useful, +   but WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +   Library General Public License for more details. + +   You should have received a copy of the GNU Library General Public +   License along with the Mate Library; see the file COPYING.LIB.  If not, +   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +   Boston, MA 02111-1307, USA. + +   Authors: John Sullivan <[email protected]> +            Anders Carlsson <[email protected]> +*/ + +#ifndef FM_LIST_VIEW_H +#define FM_LIST_VIEW_H + +#include "fm-directory-view.h" + +#define FM_TYPE_LIST_VIEW fm_list_view_get_type() +#define FM_LIST_VIEW(obj) \ +  (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_LIST_VIEW, FMListView)) +#define FM_LIST_VIEW_CLASS(klass) \ +  (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_LIST_VIEW, FMListViewClass)) +#define FM_IS_LIST_VIEW(obj) \ +  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_LIST_VIEW)) +#define FM_IS_LIST_VIEW_CLASS(klass) \ +  (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_LIST_VIEW)) +#define FM_LIST_VIEW_GET_CLASS(obj) \ +  (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_LIST_VIEW, FMListViewClass)) + +#define FM_LIST_VIEW_ID "OAFIID:Caja_File_Manager_List_View" + +typedef struct FMListViewDetails FMListViewDetails; + +typedef struct +{ +    FMDirectoryView parent_instance; +    FMListViewDetails *details; +} FMListView; + +typedef struct +{ +    FMDirectoryViewClass parent_class; +} FMListViewClass; + +GType fm_list_view_get_type (void); +void  fm_list_view_register (void); +GtkTreeView* fm_list_view_get_tree_view (FMListView *list_view); + +#endif /* FM_LIST_VIEW_H */ diff --git a/src/file-manager/fm-properties-window.c b/src/file-manager/fm-properties-window.c new file mode 100644 index 00000000..6310b407 --- /dev/null +++ b/src/file-manager/fm-properties-window.c @@ -0,0 +1,5835 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-properties-window.c - window that lets user modify file properties + +   Copyright (C) 2000 Eazel, Inc. + +   The Mate Library is free software; you can redistribute it and/or +   modify it under the terms of the GNU Library General Public License as +   published by the Free Software Foundation; either version 2 of the +   License, or (at your option) any later version. + +   The Mate Library is distributed in the hope that it will be useful, +   but WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +   Library General Public License for more details. + +   You should have received a copy of the GNU Library General Public +   License along with the Mate Library; see the file COPYING.LIB.  If not, +   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +   Boston, MA 02111-1307, USA. + +   Authors: Darin Adler <[email protected]> +*/ + +#include <config.h> +#include "fm-properties-window.h" +#include "fm-ditem-page.h" + +#define MATE_DESKTOP_USE_UNSTABLE_API + +#include "fm-error-reporting.h" +#include "libcaja-private/caja-mime-application-chooser.h" +#include <eel/eel-accessibility.h> +#include <eel/eel-gdk-pixbuf-extensions.h> +#include <eel/eel-glib-extensions.h> +#include <eel/eel-mate-extensions.h> +#include <eel/eel-gtk-extensions.h> +#include <eel/eel-labeled-image.h> +#include <eel/eel-stock-dialogs.h> +#include <eel/eel-string.h> +#include <eel/eel-vfs-extensions.h> +#include <eel/eel-wrap-table.h> +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> +#include <glib/gi18n.h> +#include <libmateui/mate-desktop-thumbnail.h> +#include <libcaja-extension/caja-property-page-provider.h> +#include <libcaja-private/caja-customization-data.h> +#include <libcaja-private/caja-entry.h> +#include <libcaja-private/caja-file-attributes.h> +#include <libcaja-private/caja-file-operations.h> +#include <libcaja-private/caja-desktop-icon-file.h> +#include <libcaja-private/caja-global-preferences.h> +#include <libcaja-private/caja-emblem-utils.h> +#include <libcaja-private/caja-link.h> +#include <libcaja-private/caja-metadata.h> +#include <libcaja-private/caja-module.h> +#include <libcaja-private/caja-undo-signal-handlers.h> +#include <libcaja-private/caja-mime-actions.h> +#include <libcaja-private/caja-undo.h> +#include <string.h> +#include <sys/stat.h> +#include <cairo.h> + +#if HAVE_SYS_VFS_H +#include <sys/vfs.h> +#elif HAVE_SYS_MOUNT_H +#if HAVE_SYS_PARAM_H +#include <sys/param.h> +#endif +#include <sys/mount.h> +#endif + +#define USED_FILL_R  (0.988235294 * 65535) +#define USED_FILL_G  (0.91372549 * 65535) +#define USED_FILL_B  (0.309803922 * 65535) + +#define FREE_FILL_R  (0.447058824 * 65535) +#define FREE_FILL_G  (0.623529412 * 65535) +#define FREE_FILL_B  (0.811764706 * 65535) + + +#define PREVIEW_IMAGE_WIDTH 96 + +#define ROW_PAD 6 + +static GHashTable *windows; +static GHashTable *pending_lists; + +struct FMPropertiesWindowDetails { +	GList *original_files; +	GList *target_files; + +	GtkNotebook *notebook; + +	GtkTable *basic_table; +	GtkTable *permissions_table; +	gboolean advanced_permissions; + +	GtkWidget *icon_button; +	GtkWidget *icon_image; +	GtkWidget *icon_chooser; + +	GtkLabel *name_label; +	GtkWidget *name_field; +	unsigned int name_row; +	char *pending_name; + +	GtkLabel *directory_contents_title_field; +	GtkLabel *directory_contents_value_field; +	guint update_directory_contents_timeout_id; +	guint update_files_timeout_id; + +	GList *emblem_buttons; +	GHashTable *initial_emblems; + +	CajaFile *group_change_file; +	char         *group_change_group; +	unsigned int  group_change_timeout; +	CajaFile *owner_change_file; +	char         *owner_change_owner; +	unsigned int  owner_change_timeout; + +	GList *permission_buttons; +	GList *permission_combos; +	GHashTable *initial_permissions; +	gboolean has_recursive_apply; + +	GList *value_fields; + +	GList *mime_list; + +	gboolean deep_count_finished; + +	guint total_count; +	goffset total_size; + +	guint long_operation_underway; + + 	GList *changed_files; + + 	guint64 volume_capacity; + 	guint64 volume_free; + +	GdkColor used_color; +	GdkColor free_color; +	GdkColor used_stroke_color; +	GdkColor free_stroke_color; +}; + +enum { +	PERMISSIONS_CHECKBOXES_OWNER_ROW, +	PERMISSIONS_CHECKBOXES_GROUP_ROW, +	PERMISSIONS_CHECKBOXES_OTHERS_ROW, +	PERMISSIONS_CHECKBOXES_ROW_COUNT +}; + +enum { +	PERMISSIONS_CHECKBOXES_READ_COLUMN, +	PERMISSIONS_CHECKBOXES_WRITE_COLUMN, +	PERMISSIONS_CHECKBOXES_EXECUTE_COLUMN, +	PERMISSIONS_CHECKBOXES_COLUMN_COUNT +}; + +enum { +	TITLE_COLUMN, +	VALUE_COLUMN, +	COLUMN_COUNT +}; + +typedef struct { +	GList *original_files; +	GList *target_files; +	GtkWidget *parent_widget; +	char *pending_key; +	GHashTable *pending_files; +} StartupData; + +/* drag and drop definitions */ + +enum { +	TARGET_URI_LIST, +	TARGET_MATE_URI_LIST, +	TARGET_RESET_BACKGROUND +}; + +static const GtkTargetEntry target_table[] = { +	{ "text/uri-list",  0, TARGET_URI_LIST }, +	{ "x-special/mate-icon-list",  0, TARGET_MATE_URI_LIST }, +	{ "x-special/mate-reset-background", 0, TARGET_RESET_BACKGROUND } +}; + +#define DIRECTORY_CONTENTS_UPDATE_INTERVAL	200 /* milliseconds */ +#define FILES_UPDATE_INTERVAL			200 /* milliseconds */ +#define STANDARD_EMBLEM_HEIGHT			52 +#define EMBLEM_LABEL_SPACING			2 + +/* + * A timeout before changes through the user/group combo box will be applied. + * When quickly changing owner/groups (i.e. by keyboard or scroll wheel), + * this ensures that the GUI doesn't end up unresponsive. + * + * Both combos react on changes by scheduling a new change and unscheduling + * or cancelling old pending changes. + */ +#define CHOWN_CHGRP_TIMEOUT			300 /* milliseconds */ + +static void directory_contents_value_field_update (FMPropertiesWindow *window); +static void file_changed_callback                 (CajaFile       *file, +						   gpointer            user_data); +static void permission_button_update              (FMPropertiesWindow *window, +						   GtkToggleButton    *button); +static void permission_combo_update               (FMPropertiesWindow *window, +						   GtkComboBox        *combo); +static void value_field_update                    (FMPropertiesWindow *window, +						   GtkLabel           *field); +static void properties_window_update              (FMPropertiesWindow *window, +						   GList              *files); +static void is_directory_ready_callback           (CajaFile       *file, +						   gpointer            data); +static void cancel_group_change_callback          (FMPropertiesWindow *window); +static void cancel_owner_change_callback          (FMPropertiesWindow *window); +static void parent_widget_destroyed_callback      (GtkWidget          *widget, +						   gpointer            callback_data); +static void select_image_button_callback          (GtkWidget          *widget, +						   FMPropertiesWindow *properties_window); +static void set_icon                              (const char         *icon_path, +						   FMPropertiesWindow *properties_window); +static void remove_pending                        (StartupData        *data, +						   gboolean            cancel_call_when_ready, +						   gboolean            cancel_timed_wait, +						   gboolean            cancel_destroy_handler); +static void append_extension_pages                (FMPropertiesWindow *window); + +static gboolean name_field_focus_out              (CajaEntry *name_field, +						   GdkEventFocus *event, +						   gpointer callback_data); +static void name_field_activate                   (CajaEntry *name_field, +						   gpointer callback_data); +static GtkLabel *attach_ellipsizing_value_label   (GtkTable *table, +						   int row, +						   int column, +						   const char *initial_text); + +static GtkWidget* create_pie_widget 		  (FMPropertiesWindow *window); + +G_DEFINE_TYPE (FMPropertiesWindow, fm_properties_window, GTK_TYPE_DIALOG); +#define parent_class fm_properties_window_parent_class + +static gboolean +is_multi_file_window (FMPropertiesWindow *window) +{ +	GList *l; +	int count; + +	count = 0; + +	for (l = window->details->original_files; l != NULL; l = l->next) { +		if (!caja_file_is_gone (CAJA_FILE (l->data))) { +			count++; +			if (count > 1) { +				return TRUE; +			} +		} +	} + +	return FALSE; +} + +static int +get_not_gone_original_file_count (FMPropertiesWindow *window) +{ +	GList *l; +	int count; + +	count = 0; + +	for (l = window->details->original_files; l != NULL; l = l->next) { +		if (!caja_file_is_gone (CAJA_FILE (l->data))) { +			count++; +		} +	} + +	return count; +} + +static CajaFile * +get_original_file (FMPropertiesWindow *window) +{ +	g_return_val_if_fail (!is_multi_file_window (window), NULL); + +	if (window->details->original_files == NULL) { +		return NULL; +	} + +	return CAJA_FILE (window->details->original_files->data); +} + +static CajaFile * +get_target_file_for_original_file (CajaFile *file) +{ +	CajaFile *target_file; +	GFile *location; +	char *uri_to_display; +	CajaDesktopLink *link; + +	target_file = NULL; +	if (CAJA_IS_DESKTOP_ICON_FILE (file)) { +		link = caja_desktop_icon_file_get_link (CAJA_DESKTOP_ICON_FILE (file)); + +		if (link != NULL) { +			/* map to linked URI for these types of links */ +			location = caja_desktop_link_get_activation_location (link); +			if (location) { +				target_file = caja_file_get (location); +				g_object_unref (location); +			} + +			g_object_unref (link); +		} +        } else { +		uri_to_display = caja_file_get_activation_uri (file); +		if (uri_to_display != NULL) { +			target_file = caja_file_get_by_uri (uri_to_display); +			g_free (uri_to_display); +		} +	} + +	if (target_file != NULL) { +		return target_file; +	} + +	/* Ref passed-in file here since we've decided to use it. */ +	caja_file_ref (file); +	return file; +} + +static CajaFile * +get_target_file (FMPropertiesWindow *window) +{ +	return CAJA_FILE (window->details->target_files->data); +} + +static void +add_prompt (GtkVBox *vbox, const char *prompt_text, gboolean pack_at_start) +{ +	GtkWidget *prompt; + +	prompt = gtk_label_new (prompt_text); +   	gtk_label_set_justify (GTK_LABEL (prompt), GTK_JUSTIFY_LEFT); +	gtk_label_set_line_wrap (GTK_LABEL (prompt), TRUE); +	gtk_widget_show (prompt); +	if (pack_at_start) { +		gtk_box_pack_start (GTK_BOX (vbox), prompt, FALSE, FALSE, 0); +	} else { +		gtk_box_pack_end (GTK_BOX (vbox), prompt, FALSE, FALSE, 0); +	} +} + +static void +add_prompt_and_separator (GtkVBox *vbox, const char *prompt_text) +{ +	GtkWidget *separator_line; + +	add_prompt (vbox, prompt_text, FALSE); + + 	separator_line = gtk_hseparator_new (); +  	gtk_widget_show (separator_line); +  	gtk_box_pack_end (GTK_BOX (vbox), separator_line, TRUE, TRUE, 2*ROW_PAD); +} + +static void +get_image_for_properties_window (FMPropertiesWindow *window, +				 char **icon_name, +				 GdkPixbuf **icon_pixbuf) +{ +	CajaIconInfo *icon, *new_icon; +	GList *l; + +	icon = NULL; +	for (l = window->details->original_files; l != NULL; l = l->next) { +		CajaFile *file; + +		file = CAJA_FILE (l->data); + +		if (!icon) { +			icon = caja_file_get_icon (file, CAJA_ICON_SIZE_STANDARD, CAJA_FILE_ICON_FLAGS_USE_THUMBNAILS | CAJA_FILE_ICON_FLAGS_IGNORE_VISITING); +		} else { +			new_icon = caja_file_get_icon (file, CAJA_ICON_SIZE_STANDARD, CAJA_FILE_ICON_FLAGS_USE_THUMBNAILS | CAJA_FILE_ICON_FLAGS_IGNORE_VISITING); +			if (!new_icon || new_icon != icon) { +				g_object_unref (icon); +				g_object_unref (new_icon); +				icon = NULL; +				break; +			} +			g_object_unref (new_icon); +		} +	} + +	if (!icon) { +		icon = caja_icon_info_lookup_from_name ("text-x-generic", CAJA_ICON_SIZE_STANDARD); +	} + +	if (icon_name != NULL) { +		*icon_name = g_strdup (caja_icon_info_get_used_name (icon)); +	} + +	if (icon_pixbuf != NULL) { +		*icon_pixbuf = caja_icon_info_get_pixbuf_at_size (icon, CAJA_ICON_SIZE_STANDARD); +	} + +	g_object_unref (icon); +} + + +static void +update_properties_window_icon (GtkImage *image) +{ +	FMPropertiesWindow *window; +	GdkPixbuf *pixbuf; +	char *name; + +	window = g_object_get_data (G_OBJECT (image), "properties_window"); + +	get_image_for_properties_window (window, &name, &pixbuf); + +	if (name != NULL) { +		gtk_window_set_icon_name (GTK_WINDOW (window), name); +	} else { +		gtk_window_set_icon (GTK_WINDOW (window), pixbuf); +	} + +	gtk_image_set_from_pixbuf (image, pixbuf); + +	g_free (name); +	g_object_unref (pixbuf); +} + +/* utility to test if a uri refers to a local image */ +static gboolean +uri_is_local_image (const char *uri) +{ +	GdkPixbuf *pixbuf; +	char *image_path; + +	image_path = g_filename_from_uri (uri, NULL, NULL); +	if (image_path == NULL) { +		return FALSE; +	} + +	pixbuf = gdk_pixbuf_new_from_file (image_path, NULL); +	g_free (image_path); + +	if (pixbuf == NULL) { +		return FALSE; +	} +	g_object_unref (pixbuf); +	return TRUE; +} + + +static void +reset_icon (FMPropertiesWindow *properties_window) +{ +	GList *l; + +	for (l = properties_window->details->original_files; l != NULL; l = l->next) { +		CajaFile *file; + +		file = CAJA_FILE (l->data); + +		caja_file_set_metadata (file, +					    CAJA_METADATA_KEY_ICON_SCALE, +					    NULL, NULL); +		caja_file_set_metadata (file, +					    CAJA_METADATA_KEY_CUSTOM_ICON, +					    NULL, NULL); +	} +} + + +static void +fm_properties_window_drag_data_received (GtkWidget *widget, GdkDragContext *context, +					 int x, int y, +					 GtkSelectionData *selection_data, +					 guint info, guint time) +{ +	char **uris; +	gboolean exactly_one; +	GtkImage *image; + 	GtkWindow *window; + +	image = GTK_IMAGE (widget); + 	window = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (image))); + +	if (info == TARGET_RESET_BACKGROUND) { +		reset_icon (FM_PROPERTIES_WINDOW (window)); + +		return; +	} + +	uris = g_strsplit (gtk_selection_data_get_data (selection_data), "\r\n", 0); +	exactly_one = uris[0] != NULL && (uris[1] == NULL || uris[1][0] == '\0'); + + +	if (!exactly_one) { +		eel_show_error_dialog +			(_("You cannot assign more than one custom icon at a time!"), +			 _("Please drag just one image to set a custom icon."), +			 window); +	} else { +		if (uri_is_local_image (uris[0])) { +			set_icon (uris[0], FM_PROPERTIES_WINDOW (window)); +		} else { +			GFile *f; + +			f = g_file_new_for_uri (uris[0]); +			if (!g_file_is_native (f)) { +				eel_show_error_dialog +					(_("The file that you dropped is not local."), +					 _("You can only use local images as custom icons."), +					 window); + +			} else { +				eel_show_error_dialog +					(_("The file that you dropped is not an image."), +					 _("You can only use local images as custom icons."), +					 window); +			} +			g_object_unref (f); +		} +	} +	g_strfreev (uris); +} + +static GtkWidget * +create_image_widget (FMPropertiesWindow *window, +		     gboolean is_customizable) +{ + 	GtkWidget *button; +	GtkWidget *image; +	GdkPixbuf *pixbuf; + +	get_image_for_properties_window (window, NULL, &pixbuf); + +	image = gtk_image_new (); +	gtk_widget_show (image); + +	button = NULL; +	if (is_customizable) { +		button = gtk_button_new (); +		gtk_container_add (GTK_CONTAINER (button), image); + +		/* prepare the image to receive dropped objects to assign custom images */ +		gtk_drag_dest_set (GTK_WIDGET (image), +				   GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP, +				   target_table, G_N_ELEMENTS (target_table), +				   GDK_ACTION_COPY | GDK_ACTION_MOVE); + +		g_signal_connect (image, "drag_data_received", +				  G_CALLBACK (fm_properties_window_drag_data_received), NULL); +		g_signal_connect (button, "clicked", +				  G_CALLBACK (select_image_button_callback), window); +	} + +	gtk_image_set_from_pixbuf (GTK_IMAGE (image), pixbuf); + +	g_object_unref (pixbuf); + +	g_object_set_data (G_OBJECT (image), "properties_window", window); + +	window->details->icon_image = image; +	window->details->icon_button = button; + +	return button != NULL ? button : image; +} + +static void +set_name_field (FMPropertiesWindow *window, const gchar *original_name, +					 const gchar *name) +{ +	gboolean new_widget; +	gboolean use_label; + +	/* There are four cases here: +	 * 1) Changing the text of a label +	 * 2) Changing the text of an entry +	 * 3) Creating label (potentially replacing entry) +	 * 4) Creating entry (potentially replacing label) +	 */ +	use_label = is_multi_file_window (window) || !caja_file_can_rename (get_original_file (window)); +	new_widget = !window->details->name_field || (use_label ? CAJA_IS_ENTRY (window->details->name_field) : GTK_IS_LABEL (window->details->name_field)); + +	if (new_widget) { +		if (window->details->name_field) { +			gtk_widget_destroy (window->details->name_field); +		} + +		if (use_label) { +			window->details->name_field = +				GTK_WIDGET (attach_ellipsizing_value_label +					(window->details->basic_table, +					 window->details->name_row, +					 VALUE_COLUMN, name)); +		} else { +			window->details->name_field = caja_entry_new (); +			gtk_entry_set_text (GTK_ENTRY (window->details->name_field), name); +			gtk_widget_show (window->details->name_field); +			gtk_table_attach (window->details->basic_table, +					  window->details->name_field, +					  VALUE_COLUMN, +					  VALUE_COLUMN + 1, +					  window->details->name_row, +					  window->details->name_row + 1, +					  GTK_FILL, 0, +					  0, 0); +			gtk_label_set_mnemonic_widget (GTK_LABEL (window->details->name_label), window->details->name_field); + +			/* FIXME bugzilla.gnome.org 42151: +			 * With this (and one place elsewhere in this file, not sure which is the +			 * trouble-causer) code in place, bug 2151 happens (crash on quit). Since +			 * we've removed Undo from Caja for now, I'm just ifdeffing out this +			 * code rather than trying to fix 2151 now. Note that it might be possible +			 * to fix 2151 without making Undo actually work, it's just not worth the +			 * trouble. +			 */ +#ifdef UNDO_ENABLED +			/* Set up name field for undo */ +			caja_undo_set_up_caja_entry_for_undo ( CAJA_ENTRY (window->details->name_field)); +			caja_undo_editable_set_undo_key (GTK_EDITABLE (window->details->name_field), TRUE); +#endif + +			g_signal_connect_object (window->details->name_field, "focus_out_event", +						 G_CALLBACK (name_field_focus_out), window, 0); +			g_signal_connect_object (window->details->name_field, "activate", +						 G_CALLBACK (name_field_activate), window, 0); +		} + +		gtk_widget_show (window->details->name_field); +	} +	/* Only replace text if the file's name has changed. */ +	else if (original_name == NULL || strcmp (original_name, name) != 0) { + +		if (use_label) { +			gtk_label_set_text (GTK_LABEL (window->details->name_field), name); +		} else { +			/* Only reset the text if it's different from what is +			 * currently showing. This causes minimal ripples (e.g. +			 * selection change). +			 */ +			gchar *displayed_name = gtk_editable_get_chars (GTK_EDITABLE (window->details->name_field), 0, -1); +			if (strcmp (displayed_name, name) != 0) { +				gtk_entry_set_text (GTK_ENTRY (window->details->name_field), name); +			} +			g_free (displayed_name); +		} +	} +} + +static void +update_name_field (FMPropertiesWindow *window) +{ +	CajaFile *file; + +	gtk_label_set_text_with_mnemonic (window->details->name_label, +					  ngettext ("_Name:", "_Names:", +						    get_not_gone_original_file_count (window))); + +	if (is_multi_file_window (window)) { +		/* Multifile property dialog, show all names */ +		GString *str; +		char *name; +		gboolean first; +		GList *l; + +		str = g_string_new (""); + +		first = TRUE; + +		for (l = window->details->target_files; l != NULL; l = l->next) { +			file = CAJA_FILE (l->data); + +			if (!caja_file_is_gone (file)) { +				if (!first) { +					g_string_append (str, ", "); +				} +				first = FALSE; + +				name = caja_file_get_display_name (file); +				g_string_append (str, name); +				g_free (name); +			} +		} +		set_name_field (window, NULL, str->str); +		g_string_free (str, TRUE); +	} else { +		const char *original_name = NULL; +		char *current_name; + +		file = get_original_file (window); + +		if (file == NULL || caja_file_is_gone (file)) { +			current_name = g_strdup (""); +		} else { +			current_name = caja_file_get_display_name (file); +		} + +		/* If the file name has changed since the original name was stored, +		 * update the text in the text field, possibly (deliberately) clobbering +		 * an edit in progress. If the name hasn't changed (but some other +		 * aspect of the file might have), then don't clobber changes. +		 */ +		if (window->details->name_field) { +			original_name = (const char *) g_object_get_data (G_OBJECT (window->details->name_field), "original_name"); +		} + +		set_name_field (window, original_name, current_name); + +		if (original_name == NULL || +		    eel_strcmp (original_name, current_name) != 0) { +			g_object_set_data_full (G_OBJECT (window->details->name_field), +						"original_name", +						current_name, +						g_free); +		} else { +			g_free (current_name); +		} +	} +} + +static void +name_field_restore_original_name (CajaEntry *name_field) +{ +	const char *original_name; +	char *displayed_name; + +	original_name = (const char *) g_object_get_data (G_OBJECT (name_field), +							  "original_name"); + +	if (!original_name) { +		return; +	} + +	displayed_name = gtk_editable_get_chars (GTK_EDITABLE (name_field), 0, -1); + +	if (strcmp (original_name, displayed_name) != 0) { +		gtk_entry_set_text (GTK_ENTRY (name_field), original_name); +	} +	caja_entry_select_all (name_field); + +	g_free (displayed_name); +} + +static void +rename_callback (CajaFile *file, GFile *res_loc, GError *error, gpointer callback_data) +{ +	FMPropertiesWindow *window; +	char *new_name; + +	window = FM_PROPERTIES_WINDOW (callback_data); + +	/* Complain to user if rename failed. */ +	if (error != NULL) { +		new_name = window->details->pending_name; +		fm_report_error_renaming_file (file, +					       window->details->pending_name, +					       error, +					       GTK_WINDOW (window)); +		if (window->details->name_field != NULL) { +			name_field_restore_original_name (CAJA_ENTRY (window->details->name_field)); +		} +	} + +	g_object_unref (window); +} + +static void +set_pending_name (FMPropertiesWindow *window, const char *name) +{ +	g_free (window->details->pending_name); +	window->details->pending_name = g_strdup (name); +} + +static void +name_field_done_editing (CajaEntry *name_field, FMPropertiesWindow *window) +{ +	CajaFile *file; +	char *new_name; +	const char *original_name; + +	g_return_if_fail (CAJA_IS_ENTRY (name_field)); + +	/* Don't apply if the dialog has more than one file */ +	if (is_multi_file_window (window)) { +		return; +	} + +	file = get_original_file (window); + +	/* This gets called when the window is closed, which might be +	 * caused by the file having been deleted. +	 */ +	if (file == NULL || caja_file_is_gone  (file)) { +		return; +	} + +	new_name = gtk_editable_get_chars (GTK_EDITABLE (name_field), 0, -1); + +	/* Special case: silently revert text if new text is empty. */ +	if (strlen (new_name) == 0) { +		name_field_restore_original_name (CAJA_ENTRY (name_field)); +	} else { +		original_name = (const char *) g_object_get_data (G_OBJECT (window->details->name_field), +								  "original_name"); +		/* Don't rename if not changed since we read the display name. +		   This is needed so that we don't save the display name to the +		   file when nothing is changed */ +		if (strcmp (new_name, original_name) != 0) { +			set_pending_name (window, new_name); +			g_object_ref (window); +			caja_file_rename (file, new_name, +					      rename_callback, window); +		} +	} + +	g_free (new_name); +} + +static gboolean +name_field_focus_out (CajaEntry *name_field, +		      GdkEventFocus *event, +		      gpointer callback_data) +{ +	g_assert (FM_IS_PROPERTIES_WINDOW (callback_data)); + +	if (gtk_widget_get_sensitive (GTK_WIDGET (name_field))) { +		name_field_done_editing (name_field, FM_PROPERTIES_WINDOW (callback_data)); +	} + +	return FALSE; +} + +static void +name_field_activate (CajaEntry *name_field, gpointer callback_data) +{ +	g_assert (CAJA_IS_ENTRY (name_field)); +	g_assert (FM_IS_PROPERTIES_WINDOW (callback_data)); + +	/* Accept changes. */ +	name_field_done_editing (name_field, FM_PROPERTIES_WINDOW (callback_data)); + +	caja_entry_select_all_at_idle (name_field); +} + +static gboolean +file_has_keyword (CajaFile *file, const char *keyword) +{ +	GList *keywords, *word; + +	keywords = caja_file_get_keywords (file); +	word = g_list_find_custom (keywords, keyword, (GCompareFunc) strcmp); +	eel_g_list_free_deep (keywords); + +	return (word != NULL); +} + +static void +get_initial_emblem_state (FMPropertiesWindow *window, +			  const char *name, +			  GList **on, +			  GList **off) +{ +	GList *l; + +	*on = NULL; +	*off = NULL; + +	for (l = window->details->original_files; l != NULL; l = l->next) { +		GList *initial_emblems; + +		initial_emblems = g_hash_table_lookup (window->details->initial_emblems, +						       l->data); + +		if (g_list_find_custom (initial_emblems, name, (GCompareFunc) strcmp)) { +			*on = g_list_prepend (*on, l->data); +		} else { +			*off = g_list_prepend (*off, l->data); +		} +	} +} + +static void +emblem_button_toggled (GtkToggleButton *button, +		       FMPropertiesWindow *window) +{ +	GList *l; +	GList *keywords; +	GList *word; +	char *name; +	GList *files_on; +	GList *files_off; + +	name = g_object_get_data (G_OBJECT (button), "caja_emblem_name"); + +	files_on = NULL; +	files_off = NULL; +	if (gtk_toggle_button_get_active (button) +	    && !gtk_toggle_button_get_inconsistent (button)) { +		/* Go to the initial state unless the initial state was +		   consistent */ +		get_initial_emblem_state (window, name, +					  &files_on, &files_off); + +		if (!(files_on && files_off)) { +			g_list_free (files_on); +			g_list_free (files_off); +			files_on = g_list_copy (window->details->original_files); +			files_off = NULL; +		} +	} else if (gtk_toggle_button_get_inconsistent (button) +		   && !gtk_toggle_button_get_active (button)) { +		files_on = g_list_copy (window->details->original_files); +		files_off = NULL; +	} else { +		files_off = g_list_copy (window->details->original_files); +		files_on = NULL; +	} + +	g_signal_handlers_block_by_func (G_OBJECT (button), +					 G_CALLBACK (emblem_button_toggled), +					 window); + +	gtk_toggle_button_set_active (button, files_on != NULL); +	gtk_toggle_button_set_inconsistent (button, files_on && files_off); + +	g_signal_handlers_unblock_by_func (G_OBJECT (button), +					   G_CALLBACK (emblem_button_toggled), +					   window); + +	for (l = files_on; l != NULL; l = l->next) { +		CajaFile *file; + +		file = CAJA_FILE (l->data); + +		keywords = caja_file_get_keywords (file); + +		word = g_list_find_custom (keywords, name,  (GCompareFunc)strcmp); +		if (!word) { +			keywords = g_list_prepend (keywords, g_strdup (name)); +		} +		caja_file_set_keywords (file, keywords); +		eel_g_list_free_deep (keywords); +	} + +	for (l = files_off; l != NULL; l = l->next) { +		CajaFile *file; + +		file = CAJA_FILE (l->data); + +		keywords = caja_file_get_keywords (file); + +		word = g_list_find_custom (keywords, name,  (GCompareFunc)strcmp); +		if (word) { +			keywords = g_list_remove_link (keywords, word); +			eel_g_list_free_deep (word); +		} +		caja_file_set_keywords (file, keywords); +		eel_g_list_free_deep (keywords); +	} + +	g_list_free (files_on); +	g_list_free (files_off); +} + +static void +emblem_button_update (FMPropertiesWindow *window, +			GtkToggleButton *button) +{ +	GList *l; +	char *name; +	gboolean all_set; +	gboolean all_unset; + +	name = g_object_get_data (G_OBJECT (button), "caja_emblem_name"); + +	all_set = TRUE; +	all_unset = TRUE; +	for (l = window->details->original_files; l != NULL; l = l->next) { +		gboolean has_keyword; +		CajaFile *file; + +		file = CAJA_FILE (l->data); + +		has_keyword = file_has_keyword (file, name); + +		if (has_keyword) { +			all_unset = FALSE; +		} else { +			all_set = FALSE; +		} +	} + +	g_signal_handlers_block_by_func (G_OBJECT (button), +					 G_CALLBACK (emblem_button_toggled), +					 window); + +	gtk_toggle_button_set_active (button, !all_unset); +	gtk_toggle_button_set_inconsistent (button, !all_unset && !all_set); + +	g_signal_handlers_unblock_by_func (G_OBJECT (button), +					   G_CALLBACK (emblem_button_toggled), +					   window); + +} + +static void +update_properties_window_title (FMPropertiesWindow *window) +{ +	char *name, *title; +	CajaFile *file; + +	g_return_if_fail (GTK_IS_WINDOW (window)); + +	title = g_strdup_printf (_("Properties")); + +	if (!is_multi_file_window (window)) { +		file = get_original_file (window); + +		if (file != NULL) { +			g_free (title); +			name = caja_file_get_display_name (file); +			title = g_strdup_printf (_("%s Properties"), name); +			g_free (name); +		} +	} + +  	gtk_window_set_title (GTK_WINDOW (window), title); + +	g_free (title); +} + +static void +clear_extension_pages (FMPropertiesWindow *window) +{ +	int i; +	int num_pages; +	GtkWidget *page; + +	num_pages = gtk_notebook_get_n_pages +				(GTK_NOTEBOOK (window->details->notebook)); + +	for (i = 0; i < num_pages; i++) { +		page = gtk_notebook_get_nth_page +				(GTK_NOTEBOOK (window->details->notebook), i); + +		if (g_object_get_data (G_OBJECT (page), "is-extension-page")) { +			gtk_notebook_remove_page +				(GTK_NOTEBOOK (window->details->notebook), i); +			num_pages--; +			i--; +		} +	} +} + +static void +refresh_extension_pages (FMPropertiesWindow *window) +{ +	clear_extension_pages (window); +	append_extension_pages (window); +} + +static void +remove_from_dialog (FMPropertiesWindow *window, +		    CajaFile *file) +{ +	int index; +	GList *original_link; +	GList *target_link; +	CajaFile *original_file; +	CajaFile *target_file; + +	index = g_list_index (window->details->target_files, file); +	if (index == -1) { +		index = g_list_index (window->details->original_files, file); +		g_return_if_fail (index != -1); +	} + +	original_link = g_list_nth (window->details->original_files, index); +	target_link = g_list_nth (window->details->target_files, index); + +	g_return_if_fail (original_link && target_link); + +	original_file = CAJA_FILE (original_link->data); +	target_file = CAJA_FILE (target_link->data); + +	window->details->original_files = g_list_remove_link (window->details->original_files, original_link); +	g_list_free (original_link); + +	window->details->target_files = g_list_remove_link (window->details->target_files, target_link); +	g_list_free (target_link); + +	g_hash_table_remove (window->details->initial_emblems, original_file); +	g_hash_table_remove (window->details->initial_permissions, target_file); + +	g_signal_handlers_disconnect_by_func (original_file, +					      G_CALLBACK (file_changed_callback), +					      window); +	g_signal_handlers_disconnect_by_func (target_file, +					      G_CALLBACK (file_changed_callback), +					      window); + +	caja_file_monitor_remove (original_file, &window->details->original_files); +	caja_file_monitor_remove (target_file, &window->details->target_files); + +	caja_file_unref (original_file); +	caja_file_unref (target_file); + +} + +static gboolean +mime_list_equal (GList *a, GList *b) +{ +	while (a && b) { +		if (strcmp (a->data, b->data)) { +			return FALSE; +		} +		a = a->next; +		b = b->next; +	} + +	return (a == b); +} + +static GList * +get_mime_list (FMPropertiesWindow *window) +{ +	GList *ret; +	GList *l; + +	ret = NULL; +	for (l = window->details->target_files; l != NULL; l = l->next) { +		ret = g_list_append (ret, caja_file_get_mime_type (CAJA_FILE (l->data))); +	} +	ret = g_list_reverse (ret); +	return ret; +} + +static void +properties_window_update (FMPropertiesWindow *window, +			  GList *files) +{ +	GList *l; +	GList *mime_list; +	GList *tmp; +	CajaFile *changed_file; +	gboolean dirty_original = FALSE; +	gboolean dirty_target = FALSE; + +	if (files == NULL) { +		dirty_original = TRUE; +		dirty_target = TRUE; +	} + +	for (tmp = files; tmp != NULL; tmp = tmp->next) { +		changed_file = CAJA_FILE (tmp->data); + +		if (changed_file && caja_file_is_gone (changed_file)) { +			/* Remove the file from the property dialog */ +			remove_from_dialog (window, changed_file); +			changed_file = NULL; + +			if (window->details->original_files == NULL) { +				return; +			} +		} +		if (changed_file == NULL || +		    g_list_find (window->details->original_files, changed_file)) { +			dirty_original = TRUE; +		} +		if (changed_file == NULL || +		    g_list_find (window->details->target_files, changed_file)) { +			dirty_target = TRUE; +		} + +	} + +	if (dirty_original) { +		update_properties_window_title (window); +		update_properties_window_icon (GTK_IMAGE (window->details->icon_image)); + +		update_name_field (window); + +		for (l = window->details->emblem_buttons; l != NULL; l = l->next) { +			emblem_button_update (window, GTK_TOGGLE_BUTTON (l->data)); +		} + +		/* If any of the value fields start to depend on the original +		 * value, value_field_updates should be added here */ +	} + +	if (dirty_target) { +		for (l = window->details->permission_buttons; l != NULL; l = l->next) { +			permission_button_update (window, GTK_TOGGLE_BUTTON (l->data)); +		} + +		for (l = window->details->permission_combos; l != NULL; l = l->next) { +			permission_combo_update (window, GTK_COMBO_BOX (l->data)); +		} + +		for (l = window->details->value_fields; l != NULL; l = l->next) { +			value_field_update (window, GTK_LABEL (l->data)); +		} +	} + +	mime_list = get_mime_list (window); + +	if (!window->details->mime_list) { +		window->details->mime_list = mime_list; +	} else { +		if (!mime_list_equal (window->details->mime_list, mime_list)) { +			refresh_extension_pages (window); +		} + +		eel_g_list_free_deep (window->details->mime_list); +		window->details->mime_list = mime_list; +	} +} + +static gboolean +update_files_callback (gpointer data) +{ + 	FMPropertiesWindow *window; + + 	window = FM_PROPERTIES_WINDOW (data); + +	window->details->update_files_timeout_id = 0; + +	properties_window_update (window, window->details->changed_files); + +	if (window->details->original_files == NULL) { +		/* Close the window if no files are left */ +		gtk_widget_destroy (GTK_WIDGET (window)); +	} else { +		caja_file_list_free (window->details->changed_files); +		window->details->changed_files = NULL; +	} + + 	return FALSE; + } + +static void +schedule_files_update (FMPropertiesWindow *window) + { + 	g_assert (FM_IS_PROPERTIES_WINDOW (window)); + +	if (window->details->update_files_timeout_id == 0) { +		window->details->update_files_timeout_id +			= g_timeout_add (FILES_UPDATE_INTERVAL, +					 update_files_callback, + 					 window); + 	} + } + +static gboolean +file_list_attributes_identical (GList *file_list, const char *attribute_name) +{ +	gboolean identical; +	char *first_attr; +	GList *l; + +	first_attr = NULL; +	identical = TRUE; + +	for (l = file_list; l != NULL; l = l->next) { +		CajaFile *file; + +		file = CAJA_FILE (l->data); + +		if (caja_file_is_gone (file)) { +			continue; +		} + +		if (first_attr == NULL) { +			first_attr = caja_file_get_string_attribute_with_default (file, attribute_name); +		} else { +			char *attr; +			attr = caja_file_get_string_attribute_with_default (file, attribute_name); +			if (strcmp (attr, first_attr)) { +				identical = FALSE; +				g_free (attr); +				break; +			} +			g_free (attr); +		} +	} + +	g_free (first_attr); +	return identical; +} + +static char * +file_list_get_string_attribute (GList *file_list, +				const char *attribute_name, +				const char *inconsistent_value) +{ +	if (file_list_attributes_identical (file_list, attribute_name)) { +		GList *l; + +		for (l = file_list; l != NULL; l = l->next) { +			CajaFile *file; + +			file = CAJA_FILE (l->data); +			if (!caja_file_is_gone (file)) { +				return caja_file_get_string_attribute_with_default +					(file, +					 attribute_name); +			} +		} +		return g_strdup (_("unknown")); +	} else { +		return g_strdup (inconsistent_value); +	} +} + + +static gboolean +file_list_all_directories (GList *file_list) +{ +	GList *l; +	for (l = file_list; l != NULL; l = l->next) { +		if (!caja_file_is_directory (CAJA_FILE (l->data))) { +			return FALSE; +		} +	} +	return TRUE; +} + +static void +value_field_update_internal (GtkLabel *label, +			     GList *file_list) +{ +	const char *attribute_name; +	char *attribute_value; +	char *inconsistent_string; +	char *mime_type, *tmp; + +	g_assert (GTK_IS_LABEL (label)); + +	attribute_name = g_object_get_data (G_OBJECT (label), "file_attribute"); +	inconsistent_string = g_object_get_data (G_OBJECT (label), "inconsistent_string"); +	attribute_value = file_list_get_string_attribute (file_list, +							  attribute_name, +							  inconsistent_string); +	if (!strcmp (attribute_name, "type") && strcmp (attribute_value, inconsistent_string)) { +		mime_type = file_list_get_string_attribute (file_list, +							    "mime_type", +							    inconsistent_string); +		if (strcmp (mime_type, inconsistent_string)) { +			tmp = attribute_value; +			attribute_value = g_strdup_printf (C_("MIME type description (MIME type)", "%s (%s)"), attribute_value, mime_type); +			g_free (tmp); +		} +		g_free (mime_type); +	} + +	gtk_label_set_text (label, attribute_value); +	g_free (attribute_value); +} + +static void +value_field_update (FMPropertiesWindow *window, GtkLabel *label) +{ +	gboolean use_original; + +	use_original = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (label), "show_original")); + +	value_field_update_internal (label, +				     (use_original ? +				      window->details->original_files : +				      window->details->target_files)); +} + +static GtkLabel * +attach_label (GtkTable *table, +	      int row, +	      int column, +	      const char *initial_text, +	      gboolean right_aligned, +	      gboolean bold, +	      gboolean ellipsize_text, +	      gboolean selectable, +	      gboolean mnemonic) +{ +	GtkWidget *label_field; + +	if (ellipsize_text) { +		label_field = gtk_label_new (initial_text); +                gtk_label_set_ellipsize (GTK_LABEL (label_field), +                                         right_aligned ? PANGO_ELLIPSIZE_START : +                                                         PANGO_ELLIPSIZE_END); +	} else if (mnemonic) { +		label_field = gtk_label_new_with_mnemonic (initial_text); +	} else { +		label_field = gtk_label_new (initial_text); +	} + +	if (selectable) { +		gtk_label_set_selectable (GTK_LABEL (label_field), TRUE); +	} + +	if (bold) { +		eel_gtk_label_make_bold (GTK_LABEL (label_field)); +	} +	gtk_misc_set_alignment (GTK_MISC (label_field), right_aligned ? 1 : 0, 0.5); +	gtk_widget_show (label_field); +	gtk_table_attach (table, label_field, +			  column, column + 1, +			  row, row + 1, +			  ellipsize_text +			    ? GTK_FILL | GTK_EXPAND +			    : GTK_FILL, +			  0, +			  0, 0); + +	return GTK_LABEL (label_field); +} + +static GtkLabel * +attach_value_label (GtkTable *table, +	      		  int row, +	      		  int column, +	      		  const char *initial_text) +{ +	return attach_label (table, row, column, initial_text, FALSE, FALSE, FALSE, TRUE, FALSE); +} + +static GtkLabel * +attach_ellipsizing_value_label (GtkTable *table, +				int row, +				int column, +				const char *initial_text) +{ +	return attach_label (table, row, column, initial_text, FALSE, FALSE, TRUE, TRUE, FALSE); +} + +static GtkWidget* +attach_value_field_internal (FMPropertiesWindow *window, +			     GtkTable *table, +			     int row, +			     int column, +			     const char *file_attribute_name, +			     const char *inconsistent_string, +			     gboolean show_original, +			     gboolean ellipsize_text) +{ +	GtkLabel *value_field; + +	if (ellipsize_text) { +		value_field = attach_ellipsizing_value_label (table, row, column, ""); +	} else { +		value_field = attach_value_label (table, row, column, ""); +	} + +  	/* Stash a copy of the file attribute name in this field for the callback's sake. */ +	g_object_set_data_full (G_OBJECT (value_field), "file_attribute", +				g_strdup (file_attribute_name), g_free); + +	g_object_set_data_full (G_OBJECT (value_field), "inconsistent_string", +				g_strdup (inconsistent_string), g_free); + +	g_object_set_data (G_OBJECT (value_field), "show_original", GINT_TO_POINTER (show_original)); + +	window->details->value_fields = g_list_prepend (window->details->value_fields, +							value_field); +	return GTK_WIDGET(value_field); +} + +static GtkWidget* +attach_value_field (FMPropertiesWindow *window, +		    GtkTable *table, +		    int row, +		    int column, +		    const char *file_attribute_name, +		    const char *inconsistent_string, +		    gboolean show_original) +{ +	return attach_value_field_internal (window, +				     table, row, column, +				     file_attribute_name, +				     inconsistent_string, +				     show_original, +				     FALSE); +} + +static GtkWidget* +attach_ellipsizing_value_field (FMPropertiesWindow *window, +				GtkTable *table, +		    	  	int row, +		    		int column, +		    		const char *file_attribute_name, +				const char *inconsistent_string, +				gboolean show_original) +{ +	return attach_value_field_internal (window, +				     table, row, column, +				     file_attribute_name, +				     inconsistent_string, +				     show_original, +				     TRUE); +} + +static void +group_change_callback (CajaFile *file, +		       GFile *res_loc, +		       GError *error, +		       FMPropertiesWindow *window) +{ +	char *group; + +	g_assert (FM_IS_PROPERTIES_WINDOW (window)); +	g_assert (window->details->group_change_file == file); + +	group = window->details->group_change_group; +	g_assert (group != NULL); + +	/* Report the error if it's an error. */ +	eel_timed_wait_stop ((EelCancelCallback) cancel_group_change_callback, window); +	fm_report_error_setting_group (file, error, GTK_WINDOW (window)); + +	caja_file_unref (file); +	g_free (group); + +	window->details->group_change_file = NULL; +	window->details->group_change_group = NULL; +	g_object_unref (G_OBJECT (window)); +} + +static void +cancel_group_change_callback (FMPropertiesWindow *window) +{ +	CajaFile *file; +	char *group; + +	file = window->details->group_change_file; +	g_assert (CAJA_IS_FILE (file)); + +	group = window->details->group_change_group; +	g_assert (group != NULL); + +	caja_file_cancel (file, (CajaFileOperationCallback) group_change_callback, window); + +	g_free (group); +	caja_file_unref (file); + +	window->details->group_change_file = NULL; +	window->details->group_change_group = NULL; +	g_object_unref (window); +} + +static gboolean +schedule_group_change_timeout (FMPropertiesWindow *window) +{ +	CajaFile *file; +	char *group; + +	g_assert (FM_IS_PROPERTIES_WINDOW (window)); + +	file = window->details->group_change_file; +	g_assert (CAJA_IS_FILE (file)); + +	group = window->details->group_change_group; +	g_assert (group != NULL); + +	eel_timed_wait_start +		((EelCancelCallback) cancel_group_change_callback, +		 window, +		 _("Cancel Group Change?"), +		 GTK_WINDOW (window)); + +	caja_file_set_group +		(file,  group, +		 (CajaFileOperationCallback) group_change_callback, window); + +	window->details->group_change_timeout = 0; +	return FALSE; +} + +static void +schedule_group_change (FMPropertiesWindow *window, +		       CajaFile       *file, +		       const char         *group) +{ +	g_assert (FM_IS_PROPERTIES_WINDOW (window)); +	g_assert (window->details->group_change_group == NULL); +	g_assert (window->details->group_change_file == NULL); +	g_assert (CAJA_IS_FILE (file)); + +	window->details->group_change_file = caja_file_ref (file); +	window->details->group_change_group = g_strdup (group); +	g_object_ref (G_OBJECT (window)); +	window->details->group_change_timeout = +		g_timeout_add (CHOWN_CHGRP_TIMEOUT, +			       (GSourceFunc) schedule_group_change_timeout, +			       window); +} + +static void +unschedule_or_cancel_group_change (FMPropertiesWindow *window) +{ +	CajaFile *file; +	char *group; + +	g_assert (FM_IS_PROPERTIES_WINDOW (window)); + +	file = window->details->group_change_file; +	group = window->details->group_change_group; + +	g_assert ((file == NULL && group == NULL) || +		  (file != NULL && group != NULL)); + +	if (file != NULL) { +		g_assert (CAJA_IS_FILE (file)); + +		if (window->details->group_change_timeout == 0) { +			caja_file_cancel (file, +					      (CajaFileOperationCallback) group_change_callback, window); +			eel_timed_wait_stop ((EelCancelCallback) cancel_group_change_callback, window); +		} + +		caja_file_unref (file); +		g_free (group); + +		window->details->group_change_file = NULL; +		window->details->group_change_group = NULL; +		g_object_unref (G_OBJECT (window)); +	} + +	if (window->details->group_change_timeout > 0) { +		g_assert (file != NULL); +		g_source_remove (window->details->group_change_timeout); +		window->details->group_change_timeout = 0; +	} +} + +static void +changed_group_callback (GtkComboBox *combo_box, CajaFile *file) +{ +	FMPropertiesWindow *window; +	char *group; +	char *cur_group; + +	g_assert (GTK_IS_COMBO_BOX (combo_box)); +	g_assert (CAJA_IS_FILE (file)); + +	group = gtk_combo_box_get_active_text (combo_box); +	cur_group = caja_file_get_group_name (file); + +	if (group != NULL && strcmp (group, cur_group) != 0) { +		/* Try to change file group. If this fails, complain to user. */ +		window = FM_PROPERTIES_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (combo_box), GTK_TYPE_WINDOW)); + +		unschedule_or_cancel_group_change (window); +		schedule_group_change (window, file, group); +	} +	g_free (group); +	g_free (cur_group); +} + +/* checks whether the given column at the first level + * of model has the specified entries in the given order. */ +static gboolean +tree_model_entries_equal (GtkTreeModel *model, +			  unsigned int  column, +			  GList        *entries) +{ +	GtkTreeIter iter; +	gboolean empty_model; + +	g_assert (GTK_IS_TREE_MODEL (model)); +	g_assert (gtk_tree_model_get_column_type (model, column) == G_TYPE_STRING); + +	empty_model = !gtk_tree_model_get_iter_first (model, &iter); + +	if (!empty_model && entries != NULL) { +		GList *l; + +		l = entries; + +		do { +			char *val; + +			gtk_tree_model_get (model, &iter, +					    column, &val, +					    -1); +			if ((val == NULL && l->data != NULL) || +			    (val != NULL && l->data == NULL) || +			    (val != NULL && strcmp (val, l->data))) { +				g_free (val); +				return FALSE; +			} + +			g_free (val); +			l = l->next; +		} while (gtk_tree_model_iter_next (model, &iter)); + +		return l == NULL; +	} else { +		return (empty_model && entries == NULL) || +		       (!empty_model && entries != NULL); +	} +} + +static char * +combo_box_get_active_entry (GtkComboBox *combo_box, +			    unsigned int column) +{ +	GtkTreeModel *model; +	GtkTreeIter iter; +	char *val; + +	g_assert (GTK_IS_COMBO_BOX (combo_box)); + +	if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo_box), &iter)) { +		model = gtk_combo_box_get_model (combo_box); +		g_assert (GTK_IS_TREE_MODEL (model)); + +		gtk_tree_model_get (model, &iter, +				    column, &val, +				    -1); +		return val; +	} + +	return NULL; +} + +/* returns the index of the given entry in the the given column + * at the first level of model. Returns -1 if entry can't be found + * or entry is NULL. + * */ +static int +tree_model_get_entry_index (GtkTreeModel *model, +			    unsigned int  column, +			    const char   *entry) +{ +	GtkTreeIter iter; +	int index; +	gboolean empty_model; + +	g_assert (GTK_IS_TREE_MODEL (model)); +	g_assert (gtk_tree_model_get_column_type (model, column) == G_TYPE_STRING); + +	empty_model = !gtk_tree_model_get_iter_first (model, &iter); +	if (!empty_model && entry != NULL) { +		index = 0; + +		do { +			char *val; + +			gtk_tree_model_get (model, &iter, +					    column, &val, +					    -1); +			if (val != NULL && !strcmp (val, entry)) { +				g_free (val); +				return index; +			} + +			g_free (val); +			index++; +		} while (gtk_tree_model_iter_next (model, &iter)); +	} + +	return -1; +} + + +static void +synch_groups_combo_box (GtkComboBox *combo_box, CajaFile *file) +{ +	GList *groups; +	GList *node; +	GtkTreeModel *model; +	GtkListStore *store; +	const char *group_name; +	char *current_group_name; +	int group_index; +	int current_group_index; + +	g_assert (GTK_IS_COMBO_BOX (combo_box)); +	g_assert (CAJA_IS_FILE (file)); + +	if (caja_file_is_gone (file)) { +		return; +	} + +	groups = caja_file_get_settable_group_names (file); + +	model = gtk_combo_box_get_model (combo_box); +	store = GTK_LIST_STORE (model); +	g_assert (GTK_IS_LIST_STORE (model)); + +	if (!tree_model_entries_equal (model, 0, groups)) { +		/* Clear the contents of ComboBox in a wacky way because there +		 * is no function to clear all items and also no function to obtain +		 * the number of items in a combobox. +		 */ +		gtk_list_store_clear (store); + +		for (node = groups, group_index = 0; node != NULL; node = node->next, ++group_index) { +			group_name = (const char *)node->data; +			gtk_combo_box_append_text (combo_box, group_name); +		} +	} + +	current_group_name = caja_file_get_group_name (file); +	current_group_index = tree_model_get_entry_index (model, 0, current_group_name); + +	/* If current group wasn't in list, we prepend it (with a separator). +	 * This can happen if the current group is an id with no matching +	 * group in the groups file. +	 */ +	if (current_group_index < 0 && current_group_name != NULL) { +		if (groups != NULL) { +			/* add separator */ +			gtk_combo_box_prepend_text (combo_box, "-"); +		} + +		gtk_combo_box_prepend_text (combo_box, current_group_name); +		current_group_index = 0; +	} +	gtk_combo_box_set_active (combo_box, current_group_index); + +	g_free (current_group_name); +	eel_g_list_free_deep (groups); +} + +static gboolean +combo_box_row_separator_func (GtkTreeModel *model, +			      GtkTreeIter  *iter, +			      gpointer      data) +{ +  	gchar *text; +	gboolean ret; + +  	gtk_tree_model_get (model, iter, 0, &text, -1); + +	if (text == NULL) { +		return FALSE; +	} + +  	if (strcmp (text, "-") == 0) { +    		ret = TRUE; +	} else { +		ret = FALSE; +	} + +  	g_free (text); +  	return ret; +} + +static GtkComboBox * +attach_combo_box (GtkTable *table, +		  int row, +		  gboolean two_columns) +{ +	GtkWidget *combo_box; +	GtkWidget *aligner; + +	if (!two_columns) { +		combo_box = gtk_combo_box_new_text (); +	} else { +		GtkTreeModel *model; +		GtkCellRenderer *renderer; + +		model = GTK_TREE_MODEL (gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING)); +		combo_box = gtk_combo_box_new_with_model (model); +		g_object_unref (G_OBJECT (model)); + +		renderer = gtk_cell_renderer_text_new (); +		gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box), renderer, TRUE); +		gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (combo_box), renderer, +					       "text", 0); + +	} +	gtk_widget_show (combo_box); + +  	gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (combo_box), +					      combo_box_row_separator_func, +					      NULL, +					      NULL); + +	/* Put combo box in alignment to make it left-justified +	 * but minimally sized. +	 */ +	aligner = gtk_alignment_new (0, 0.5, 0, 0); +	gtk_widget_show (aligner); + +	gtk_container_add (GTK_CONTAINER (aligner), combo_box); +	gtk_table_attach (table, aligner, +			  VALUE_COLUMN, VALUE_COLUMN + 1, +			  row, row + 1, +			  GTK_FILL, 0, +			  0, 0); + +	return GTK_COMBO_BOX (combo_box); +} + +static GtkComboBox* +attach_group_combo_box (GtkTable *table, +		        int row, +		        CajaFile *file) +{ +	GtkComboBox *combo_box; + +	combo_box = attach_combo_box (table, row, FALSE); + +	synch_groups_combo_box (combo_box, file); + +	/* Connect to signal to update menu when file changes. */ +	g_signal_connect_object (file, "changed", +				 G_CALLBACK (synch_groups_combo_box), +				 combo_box, G_CONNECT_SWAPPED); +	g_signal_connect_data (combo_box, "changed", +			       G_CALLBACK (changed_group_callback), +			       caja_file_ref (file), +			       (GClosureNotify)caja_file_unref, 0); + +	return combo_box; +} + +static void +owner_change_callback (CajaFile *file, +                       GFile 	    *result_location, +		       GError        *error, +		       FMPropertiesWindow *window) +{ +	char *owner; + +	g_assert (FM_IS_PROPERTIES_WINDOW (window)); +	g_assert (window->details->owner_change_file == file); + +	owner = window->details->owner_change_owner; +	g_assert (owner != NULL); + +	/* Report the error if it's an error. */ +	eel_timed_wait_stop ((EelCancelCallback) cancel_owner_change_callback, window); +	fm_report_error_setting_owner (file, error, GTK_WINDOW (window)); + +	caja_file_unref (file); +	g_free (owner); + +	window->details->owner_change_file = NULL; +	window->details->owner_change_owner = NULL; +	g_object_unref (G_OBJECT (window)); +} + +static void +cancel_owner_change_callback (FMPropertiesWindow *window) +{ +	CajaFile *file; +	char *owner; + +	file = window->details->owner_change_file; +	g_assert (CAJA_IS_FILE (file)); + +	owner = window->details->owner_change_owner; +	g_assert (owner != NULL); + +	caja_file_cancel (file, (CajaFileOperationCallback) owner_change_callback, window); + +	caja_file_unref (file); +	g_free (owner); + +	window->details->owner_change_file = NULL; +	window->details->owner_change_owner = NULL; +	g_object_unref (window); +} + +static gboolean +schedule_owner_change_timeout (FMPropertiesWindow *window) +{ +	CajaFile *file; +	char *owner; + +	g_assert (FM_IS_PROPERTIES_WINDOW (window)); + +	file = window->details->owner_change_file; +	g_assert (CAJA_IS_FILE (file)); + +	owner = window->details->owner_change_owner; +	g_assert (owner != NULL); + +	eel_timed_wait_start +		((EelCancelCallback) cancel_owner_change_callback, +		 window, +		 _("Cancel Owner Change?"), +		 GTK_WINDOW (window)); + +	caja_file_set_owner +		(file,  owner, +		 (CajaFileOperationCallback) owner_change_callback, window); + +	window->details->owner_change_timeout = 0; +	return FALSE; +} + +static void +schedule_owner_change (FMPropertiesWindow *window, +		       CajaFile       *file, +		       const char         *owner) +{ +	g_assert (FM_IS_PROPERTIES_WINDOW (window)); +	g_assert (window->details->owner_change_owner == NULL); +	g_assert (window->details->owner_change_file == NULL); +	g_assert (CAJA_IS_FILE (file)); + +	window->details->owner_change_file = caja_file_ref (file); +	window->details->owner_change_owner = g_strdup (owner); +	g_object_ref (G_OBJECT (window)); +	window->details->owner_change_timeout = +		g_timeout_add (CHOWN_CHGRP_TIMEOUT, +			       (GSourceFunc) schedule_owner_change_timeout, +			       window); +} + +static void +unschedule_or_cancel_owner_change (FMPropertiesWindow *window) +{ +	CajaFile *file; +	char *owner; + +	g_assert (FM_IS_PROPERTIES_WINDOW (window)); + +	file = window->details->owner_change_file; +	owner = window->details->owner_change_owner; + +	g_assert ((file == NULL && owner == NULL) || +		  (file != NULL && owner != NULL)); + +	if (file != NULL) { +		g_assert (CAJA_IS_FILE (file)); + +		if (window->details->owner_change_timeout == 0) { +			caja_file_cancel (file, +					      (CajaFileOperationCallback) owner_change_callback, window); +			eel_timed_wait_stop ((EelCancelCallback) cancel_owner_change_callback, window); +		} + +		caja_file_unref (file); +		g_free (owner); + +		window->details->owner_change_file = NULL; +		window->details->owner_change_owner = NULL; +		g_object_unref (G_OBJECT (window)); +	} + +	if (window->details->owner_change_timeout > 0) { +		g_assert (file != NULL); +		g_source_remove (window->details->owner_change_timeout); +		window->details->owner_change_timeout = 0; +	} +} + +static void +changed_owner_callback (GtkComboBox *combo_box, CajaFile* file) +{ +	FMPropertiesWindow *window; +	char *owner_text; +	char **name_array; +	char *new_owner; +	char *cur_owner; + +	g_assert (GTK_IS_COMBO_BOX (combo_box)); +	g_assert (CAJA_IS_FILE (file)); + +	owner_text = combo_box_get_active_entry (combo_box, 0); +        if (! owner_text) +	    return; +    	name_array = g_strsplit (owner_text, " - ", 2); +	new_owner = name_array[0]; +	g_free (owner_text); +	cur_owner = caja_file_get_owner_name (file); + +	if (strcmp (new_owner, cur_owner) != 0) { +		/* Try to change file owner. If this fails, complain to user. */ +		window = FM_PROPERTIES_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (combo_box), GTK_TYPE_WINDOW)); + +		unschedule_or_cancel_owner_change (window); +		schedule_owner_change (window, file, new_owner); +	} +	g_strfreev (name_array); +	g_free (cur_owner); +} + +static void +synch_user_menu (GtkComboBox *combo_box, CajaFile *file) +{ +	GList *users; +	GList *node; +	GtkTreeModel *model; +	GtkListStore *store; +	GtkTreeIter iter; +	char *user_name; +	char *owner_name; +	int user_index; +	int owner_index; +	char **name_array; +	char *combo_text; + +	g_assert (GTK_IS_COMBO_BOX (combo_box)); +	g_assert (CAJA_IS_FILE (file)); + +	if (caja_file_is_gone (file)) { +		return; +	} + +	users = caja_get_user_names (); + +	model = gtk_combo_box_get_model (combo_box); +	store = GTK_LIST_STORE (model); +	g_assert (GTK_IS_LIST_STORE (model)); + +	if (!tree_model_entries_equal (model, 1, users)) { +		/* Clear the contents of ComboBox in a wacky way because there +		 * is no function to clear all items and also no function to obtain +		 * the number of items in a combobox. +		 */ +		gtk_list_store_clear (store); + +		for (node = users, user_index = 0; node != NULL; node = node->next, ++user_index) { +			user_name = (char *)node->data; + +			name_array = g_strsplit (user_name, "\n", 2); +			if (name_array[1] != NULL) { +				combo_text = g_strdup_printf ("%s - %s", name_array[0], name_array[1]); +			} else { +				combo_text = g_strdup (name_array[0]); +			} + +			gtk_list_store_append (store, &iter); +			gtk_list_store_set (store, &iter, +					    0, combo_text, +					    1, user_name, +					    -1); + +			g_strfreev (name_array); +			g_free (combo_text); +		} +	} + +	owner_name = caja_file_get_string_attribute (file, "owner"); +	owner_index = tree_model_get_entry_index (model, 0, owner_name); + +	/* If owner wasn't in list, we prepend it (with a separator). +	 * This can happen if the owner is an id with no matching +	 * identifier in the passwords file. +	 */ +	if (owner_index < 0 && owner_name != NULL) { +		if (users != NULL) { +			/* add separator */ +			gtk_list_store_prepend (store, &iter); +			gtk_list_store_set (store, &iter, +					    0, "-", +					    1, NULL, +					    -1); +		} + +		name_array = g_strsplit (owner_name, " - ", 2); +		if (name_array[1] != NULL) { +			user_name = g_strdup_printf ("%s\n%s", name_array[0], name_array[1]); +		} else { +			user_name = g_strdup (name_array[0]); +		} +		owner_index = 0; + +		gtk_list_store_prepend (store, &iter); +		gtk_list_store_set (store, &iter, +				    0, owner_name, +				    1, user_name, +				    -1); + +		g_free (user_name); +		g_strfreev (name_array); +	} + +	gtk_combo_box_set_active (combo_box, owner_index); + +	g_free (owner_name); +	eel_g_list_free_deep (users); +} + +static GtkComboBox* +attach_owner_combo_box (GtkTable *table, +		        int row, +		        CajaFile *file) +{ +	GtkComboBox *combo_box; + +	combo_box = attach_combo_box (table, row, TRUE); + +	synch_user_menu (combo_box, file); + +	/* Connect to signal to update menu when file changes. */ +	g_signal_connect_object (file, "changed", +				 G_CALLBACK (synch_user_menu), +				 combo_box, G_CONNECT_SWAPPED); +	g_signal_connect_data (combo_box, "changed", +			       G_CALLBACK (changed_owner_callback), +			       caja_file_ref (file), +			       (GClosureNotify)caja_file_unref, 0); + +	return combo_box; +} + +static guint +append_row (GtkTable *table) +{ +	guint new_row_count; +	gint nrows, ncols; + +	g_object_get (table, "n-rows", &nrows, "n-columns", &ncols, NULL); + +	new_row_count = nrows + 1; + +	gtk_table_resize (table, new_row_count, ncols); +	gtk_table_set_row_spacing (table, new_row_count - 1, ROW_PAD); + +	return new_row_count - 1; +} + +static gboolean +file_has_prefix (CajaFile *file, +		 GList *prefix_candidates) +{ +	GList *p; +	GFile *location, *candidate_location; + +	location = caja_file_get_location (file); + +	for (p = prefix_candidates; p != NULL; p = p->next) { +		if (file == p->data) { +			continue; +		} + +		candidate_location = caja_file_get_location (CAJA_FILE (p->data)); +		if (g_file_has_prefix (location, candidate_location)) { +			g_object_unref (location); +			g_object_unref (candidate_location); +			return TRUE; +		} +		g_object_unref (candidate_location); +	} + +	g_object_unref (location); + +	return FALSE; +} + +static void +directory_contents_value_field_update (FMPropertiesWindow *window) +{ +	CajaRequestStatus file_status, status; +	char *text, *temp; +	guint directory_count; +	guint file_count; +	guint total_count; +	guint unreadable_directory_count; +	goffset total_size; +	gboolean used_two_lines; +	CajaFile *file; +	GList *l; +	guint file_unreadable; +	goffset file_size; + +	g_assert (FM_IS_PROPERTIES_WINDOW (window)); + +	status = CAJA_REQUEST_DONE; +	file_status = CAJA_REQUEST_NOT_STARTED; +	total_count = window->details->total_count; +	total_size = window->details->total_size; +	unreadable_directory_count = FALSE; + +	for (l = window->details->target_files; l; l = l->next) { +		file = CAJA_FILE (l->data); + +		if (file_has_prefix (file, window->details->target_files)) { +			/* don't count nested files twice */ +			continue; +		} + +		if (caja_file_is_directory (file)) { +			file_status = caja_file_get_deep_counts (file, +					 &directory_count, +					 &file_count, +					 &file_unreadable, +					 &file_size, +					 TRUE); +			total_count += (file_count + directory_count); +			total_size += file_size; + +			if (file_unreadable) { +				unreadable_directory_count = TRUE; +			} + +			if (file_status != CAJA_REQUEST_DONE) { +				status = file_status; +			} +		} else { +			++total_count; +			total_size += caja_file_get_size (file); +		} +	} + +	/* If we've already displayed the total once, don't do another visible +	 * count-up if the deep_count happens to get invalidated. +	 * But still display the new total, since it might have changed. +	 */ +	if (window->details->deep_count_finished && +	    status != CAJA_REQUEST_DONE) { +		return; +	} + +	text = NULL; +	used_two_lines = FALSE; + +	if (total_count == 0) { +		switch (status) { +		case CAJA_REQUEST_DONE: +			if (unreadable_directory_count == 0) { +				text = g_strdup (_("nothing")); +			} else { +				text = g_strdup (_("unreadable")); +			} + +			break; +		default: +			text = g_strdup ("..."); +		} +	} else { +		char *size_str; + +		#if GLIB_CHECK_VERSION(2, 30, 0) +			size_str = g_format_size(total_size); +		#else +			size_str = g_format_size_for_display(total_size); +		#endif + +		text = g_strdup_printf (ngettext("%'d item, with size %s", +						 "%'d items, totalling %s", +						 total_count), +					total_count, size_str); +		g_free (size_str); + +		if (unreadable_directory_count != 0) { +			temp = text; +			text = g_strconcat (temp, "\n", +					    _("(some contents unreadable)"), +					    NULL); +			g_free (temp); +			used_two_lines = TRUE; +		} +	} + +	gtk_label_set_text (window->details->directory_contents_value_field, +			    text); +	g_free (text); + +	/* Also set the title field here, with a trailing carriage return & +	 * space if the value field has two lines. This is a hack to get the +	 * "Contents:" title to line up with the first line of the +	 * 2-line value. Maybe there's a better way to do this, but I +	 * couldn't think of one. +	 */ +	text = g_strdup (_("Contents:")); +	if (used_two_lines) { +		temp = text; +		text = g_strconcat (temp, "\n ", NULL); +		g_free (temp); +	} +	gtk_label_set_text (window->details->directory_contents_title_field, +			    text); +	g_free (text); + +	if (status == CAJA_REQUEST_DONE) { +		window->details->deep_count_finished = TRUE; +	} +} + +static gboolean +update_directory_contents_callback (gpointer data) +{ +	FMPropertiesWindow *window; + +	window = FM_PROPERTIES_WINDOW (data); + +	window->details->update_directory_contents_timeout_id = 0; +	directory_contents_value_field_update (window); + +	return FALSE; +} + +static void +schedule_directory_contents_update (FMPropertiesWindow *window) +{ +	g_assert (FM_IS_PROPERTIES_WINDOW (window)); + +	if (window->details->update_directory_contents_timeout_id == 0) { +		window->details->update_directory_contents_timeout_id +			= g_timeout_add (DIRECTORY_CONTENTS_UPDATE_INTERVAL, +					 update_directory_contents_callback, +					 window); +	} +} + +static GtkLabel * +attach_directory_contents_value_field (FMPropertiesWindow *window, +				       GtkTable *table, +				       int row) +{ +	GtkLabel *value_field; +	GList *l; +	CajaFile *file; + +	value_field = attach_value_label (table, row, VALUE_COLUMN, ""); + +	g_assert (window->details->directory_contents_value_field == NULL); +	window->details->directory_contents_value_field = value_field; + +	gtk_label_set_line_wrap (value_field, TRUE); + +	/* Fill in the initial value. */ +	directory_contents_value_field_update (window); + +	for (l = window->details->target_files; l; l = l->next) { +		file = CAJA_FILE (l->data); +		caja_file_recompute_deep_counts (file); + +		g_signal_connect_object (file, +					 "updated_deep_count_in_progress", +					 G_CALLBACK (schedule_directory_contents_update), +					 window, G_CONNECT_SWAPPED); +	} + +	return value_field; +} + +static GtkLabel * +attach_title_field (GtkTable *table, +		     int row, +		     const char *title) +{ +	return attach_label (table, row, TITLE_COLUMN, title, FALSE, FALSE, FALSE, FALSE, TRUE); +} + +static guint +append_title_field (GtkTable *table, const char *title, GtkLabel **label) +{ +	guint last_row; +	GtkLabel *title_label; + +	last_row = append_row (table); +	title_label = attach_title_field (table, last_row, title); + +	if (label) { +		*label = title_label; +	} + +	return last_row; +} + +#define INCONSISTENT_STATE_STRING \ +	"\xE2\x80\x92" + +static guint +append_title_value_pair (FMPropertiesWindow *window, +			 GtkTable *table, +			 const char *title, + 			 const char *file_attribute_name, +			 const char *inconsistent_state, +			 gboolean show_original) +{ +	guint last_row; +	GtkLabel *title_label; +	GtkWidget *value; + +	last_row = append_title_field (table, title, &title_label); +	value = attach_value_field (window, table, last_row, VALUE_COLUMN, +			    file_attribute_name, +			    inconsistent_state, +			    show_original); +	gtk_label_set_mnemonic_widget (title_label, value); +	return last_row; +} + +static guint +append_title_and_ellipsizing_value (FMPropertiesWindow *window, +				    GtkTable *table, +				    const char *title, +				    const char *file_attribute_name, +				    const char *inconsistent_state, +				    gboolean show_original) +{ +	GtkLabel *title_label; +	GtkWidget *value; +	guint last_row; + +	last_row = append_title_field (table, title, &title_label); +	value = attach_ellipsizing_value_field (window, table, last_row, VALUE_COLUMN, +					file_attribute_name, +					inconsistent_state, +					show_original); +	gtk_label_set_mnemonic_widget (title_label, value); + +	return last_row; +} + +static guint +append_directory_contents_fields (FMPropertiesWindow *window, +				  GtkTable *table) +{ +	GtkLabel *title_field, *value_field; +	guint last_row; + +	last_row = append_row (table); + +	title_field = attach_title_field (table, last_row, ""); +	window->details->directory_contents_title_field = title_field; +	gtk_label_set_line_wrap (title_field, TRUE); + +	value_field = attach_directory_contents_value_field +		(window, table, last_row); + +	gtk_label_set_mnemonic_widget(title_field, GTK_WIDGET(value_field)); +	return last_row; +} + +static GtkWidget * +create_page_with_hbox (GtkNotebook *notebook, +		       const char *title) +{ +	GtkWidget *hbox; + +	g_assert (GTK_IS_NOTEBOOK (notebook)); +	g_assert (title != NULL); + +	hbox = gtk_hbox_new (FALSE, 0); +	gtk_widget_show (hbox); +	gtk_container_set_border_width (GTK_CONTAINER (hbox), 12); +	gtk_box_set_spacing (GTK_BOX (hbox), 12); +	gtk_notebook_append_page (notebook, hbox, gtk_label_new (title)); + +	return hbox; +} + +static GtkWidget * +create_page_with_vbox (GtkNotebook *notebook, +		       const char *title) +{ +	GtkWidget *vbox; + +	g_assert (GTK_IS_NOTEBOOK (notebook)); +	g_assert (title != NULL); + +	vbox = gtk_vbox_new (FALSE, 0); +	gtk_widget_show (vbox); +	gtk_container_set_border_width (GTK_CONTAINER (vbox), 12); +	gtk_notebook_append_page (notebook, vbox, gtk_label_new (title)); + +	return vbox; +} + +static GtkWidget * +append_blank_row (GtkTable *table) +{ +	GtkWidget *separator; + +	append_title_field (table, "", (GtkLabel **) &separator); + +	return separator; +} + +static void +apply_standard_table_padding (GtkTable *table) +{ +	gtk_table_set_row_spacings (table, ROW_PAD); +	gtk_table_set_col_spacings (table, 12); +} + +static GtkWidget * +create_attribute_value_table (GtkVBox *vbox, int row_count) +{ +	GtkWidget *table; + +	table = gtk_table_new (row_count, COLUMN_COUNT, FALSE); +	apply_standard_table_padding (GTK_TABLE (table)); +	gtk_widget_show (table); +	gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0); + +	return table; +} + +static gboolean +is_merged_trash_directory (CajaFile *file) +{ +	char *file_uri; +	gboolean result; + +	file_uri = caja_file_get_uri (file); +	result = strcmp (file_uri, "trash:///") == 0; +	g_free (file_uri); + +	return result; +} + +static gboolean +is_computer_directory (CajaFile *file) +{ +	char *file_uri; +	gboolean result; + +	file_uri = caja_file_get_uri (file); +	result = strcmp (file_uri, "computer:///") == 0; +	g_free (file_uri); + +	return result; +} + +static gboolean +is_network_directory (CajaFile *file) +{ +	char *file_uri; +	gboolean result; + +	file_uri = caja_file_get_uri (file); +	result = strcmp (file_uri, "network:///") == 0; +	g_free (file_uri); + +	return result; +} + +static gboolean +is_burn_directory (CajaFile *file) +{ +	char *file_uri; +	gboolean result; + +	file_uri = caja_file_get_uri (file); +	result = strcmp (file_uri, "burn:///") == 0; +	g_free (file_uri); + +	return result; +} + +static gboolean +should_show_custom_icon_buttons (FMPropertiesWindow *window) +{ +	if (is_multi_file_window (window)) { +		return FALSE; +	} + +	return TRUE; +} + +static gboolean +should_show_file_type (FMPropertiesWindow *window) +{ +	if (!is_multi_file_window (window) +	    && (is_merged_trash_directory (get_target_file (window)) || +		is_computer_directory (get_target_file (window)) || +		is_network_directory (get_target_file (window)) || +		is_burn_directory (get_target_file (window)))) { +		return FALSE; +	} + + +	return TRUE; +} + +static gboolean +should_show_location_info (FMPropertiesWindow *window) +{ +	if (!is_multi_file_window (window) +	    && (is_merged_trash_directory (get_target_file (window)) || +		is_computer_directory (get_target_file (window)) || +		is_network_directory (get_target_file (window)) || +		is_burn_directory (get_target_file (window)))) { +		return FALSE; +	} + +	return TRUE; +} + +static gboolean +should_show_accessed_date (FMPropertiesWindow *window) +{ +	/* Accessed date for directory seems useless. If we some +	 * day decide that it is useful, we should separately +	 * consider whether it's useful for "trash:". +	 */ +	if (file_list_all_directories (window->details->target_files)) { +		return FALSE; +	} + +	return TRUE; +} + +static gboolean +should_show_link_target (FMPropertiesWindow *window) +{ +	if (!is_multi_file_window (window) +	    && caja_file_is_symbolic_link (get_target_file (window))) { +		return TRUE; +	} + +	return FALSE; +} + +static gboolean +should_show_free_space (FMPropertiesWindow *window) +{ + +	if (!is_multi_file_window (window) +	    && (is_merged_trash_directory (get_target_file (window)) || +		is_computer_directory (get_target_file (window)) || +		is_network_directory (get_target_file (window)) || +		is_burn_directory (get_target_file (window)))) { +		return FALSE; +	} + +	if (file_list_all_directories (window->details->target_files)) { +		return TRUE; +	} + +	return FALSE; +} + +static gboolean +should_show_volume_usage (FMPropertiesWindow *window) +{ +	CajaFile 		*file; +	gboolean 		success = FALSE; + +	if (is_multi_file_window (window)) { +		return FALSE; +	} + +	file = get_original_file (window); + +	if (file == NULL) { +		return FALSE; +	} + +	if (caja_file_can_unmount (file)) { +		return TRUE; +	} + +#ifdef TODO_GIO +	/* Look at is_mountpoint for activation uri */ +#endif +	return success; +} + +static void +paint_used_legend (GtkWidget *widget, GdkEventExpose *eev, gpointer data) +{ +	FMPropertiesWindow *window; +	cairo_t *cr; +	gint width, height; +	GtkAllocation allocation; + +	gtk_widget_get_allocation (widget, &allocation); + +  	width  = allocation.width; +  	height = allocation.height; + +	window = FM_PROPERTIES_WINDOW (data); + +	cr = gdk_cairo_create (gtk_widget_get_window (widget)); + +	cairo_rectangle  (cr, +			  2, +			  2, +			  width - 4, +			  height - 4); + +	cairo_set_source_rgb (cr, (double) window->details->used_color.red / 65535, (double) window->details->used_color.green / 65535, (double) window->details->used_color.blue / 65535); +	cairo_fill_preserve (cr); + +	cairo_set_source_rgb (cr, (double) window->details->used_stroke_color.red / 65535, (double) window->details->used_stroke_color.green / 65535, (double) window->details->used_stroke_color.blue / 65535); +	cairo_stroke (cr); + +	cairo_destroy (cr); +} + +static void +paint_free_legend (GtkWidget *widget, GdkEventExpose *eev, gpointer data) +{ +	FMPropertiesWindow *window; +	cairo_t *cr; +	gint width, height; +	GtkAllocation allocation; + +	window = FM_PROPERTIES_WINDOW (data); +	gtk_widget_get_allocation (widget, &allocation); + +  	width  = allocation.width; +  	height = allocation.height; +  	cr = gdk_cairo_create (gtk_widget_get_window (widget)); + +	cairo_rectangle (cr, +			 2, +			 2, +			 width - 4, +			 height - 4); + +	cairo_set_source_rgb (cr, (double) window->details->free_color.red / 65535, (double) window->details->free_color.green / 65535, (double) window->details->free_color.blue / 65535); +	cairo_fill_preserve(cr); + +	cairo_set_source_rgb (cr, (double) window->details->free_stroke_color.red / 65535, (double) window->details->free_stroke_color.green / 65535, (double) window->details->free_stroke_color.blue / 65535); +	cairo_stroke (cr); + +	cairo_destroy (cr); +} + +static void +paint_pie_chart (GtkWidget *widget, GdkEventExpose *eev, gpointer data) +{ + +  	FMPropertiesWindow *window; +  	cairo_t *cr; +	gint width, height; +	double free, used; +	double angle1, angle2, split, xc, yc, radius; +	GtkAllocation allocation; + +	window = FM_PROPERTIES_WINDOW (data); +	gtk_widget_get_allocation (widget, &allocation); + +	width  = allocation.width; +  	height = allocation.height; + + +	free = (double)window->details->volume_free / (double)window->details->volume_capacity; +	used =  1.0 - free; + +	angle1 = free * 2 * G_PI; +	angle2 = used * 2 * G_PI; +	split = (2 * G_PI - angle1) * .5; +	xc = width / 2; +	yc = height / 2; + +  	cr = gdk_cairo_create (gtk_widget_get_window (widget)); + +	if (width < height) { +		radius = width / 2 - 8; +	} else { +		radius = height / 2 - 8; +	} + +	if (angle1 != 2 * G_PI && angle1 != 0) { +		angle1 = angle1 + split; +	} + +	if (angle2 != 2 * G_PI && angle2 != 0) { +		angle2 = angle2 - split; +	} + +	if (used > 0) { +		if (free != 0) { +			cairo_move_to (cr,xc,yc); +		} + +		cairo_arc (cr, xc, yc, radius, angle1, angle2); + +		if (free != 0) { +			cairo_line_to (cr,xc,yc); +		} + +		cairo_set_source_rgb (cr, (double) window->details->used_color.red / 65535, (double) window->details->used_color.green / 65535, (double) window->details->used_color.blue / 65535); +		cairo_fill_preserve (cr); + +		cairo_set_source_rgb (cr, (double) window->details->used_stroke_color.red / 65535, (double) window->details->used_stroke_color.green / 65535, (double) window->details->used_stroke_color.blue / 65535); +		cairo_stroke (cr); +	} + +	if (free > 0) { +		if (used != 0) { +			cairo_move_to (cr,xc,yc); +		} + +		cairo_arc_negative (cr, xc, yc, radius, angle1, angle2); + +		if (used != 0) { +			cairo_line_to (cr,xc,yc); +		} + +		cairo_set_source_rgb (cr, (double) window->details->free_color.red / 65535, (double) window->details->free_color.green / 65535,(double) window->details->free_color.blue / 65535); +		cairo_fill_preserve(cr); + +		cairo_set_source_rgb (cr, (double) window->details->free_stroke_color.red / 65535, (double) window->details->free_stroke_color.green / 65535, (double) window->details->free_stroke_color.blue / 65535); +		cairo_stroke (cr); +	} + +  	cairo_destroy (cr); +} + + +/* Copied from gtk/gtkstyle.c */ + +static void +rgb_to_hls (gdouble *r, +            gdouble *g, +            gdouble *b) +{ +  gdouble min; +  gdouble max; +  gdouble red; +  gdouble green; +  gdouble blue; +  gdouble h, l, s; +  gdouble delta; + +  red = *r; +  green = *g; +  blue = *b; + +  if (red > green) +    { +      if (red > blue) +        max = red; +      else +        max = blue; + +      if (green < blue) +        min = green; +      else +        min = blue; +    } +  else +    { +      if (green > blue) +        max = green; +      else +        max = blue; + +      if (red < blue) +        min = red; +      else +        min = blue; +    } + +  l = (max + min) / 2; +  s = 0; +  h = 0; + +  if (max != min) +    { +      if (l <= 0.5) +        s = (max - min) / (max + min); +      else +        s = (max - min) / (2 - max - min); + +      delta = max -min; +      if (red == max) +        h = (green - blue) / delta; +      else if (green == max) +        h = 2 + (blue - red) / delta; +      else if (blue == max) +        h = 4 + (red - green) / delta; + +      h *= 60; +      if (h < 0.0) +        h += 360; +    } + +  *r = h; +  *g = l; +  *b = s; +} + +static void +hls_to_rgb (gdouble *h, +            gdouble *l, +            gdouble *s) +{ +  gdouble hue; +  gdouble lightness; +  gdouble saturation; +  gdouble m1, m2; +  gdouble r, g, b; + +  lightness = *l; +  saturation = *s; + +  if (lightness <= 0.5) +    m2 = lightness * (1 + saturation); +  else +    m2 = lightness + saturation - lightness * saturation; +  m1 = 2 * lightness - m2; + +  if (saturation == 0) +    { +      *h = lightness; +      *l = lightness; +      *s = lightness; +    } +  else +    { +      hue = *h + 120; +      while (hue > 360) +        hue -= 360; +      while (hue < 0) +        hue += 360; + +      if (hue < 60) +        r = m1 + (m2 - m1) * hue / 60; +      else if (hue < 180) +        r = m2; +      else if (hue < 240) +        r = m1 + (m2 - m1) * (240 - hue) / 60; +      else +        r = m1; + +      hue = *h; +      while (hue > 360) +        hue -= 360; +      while (hue < 0) +        hue += 360; + +      if (hue < 60) +        g = m1 + (m2 - m1) * hue / 60; +      else if (hue < 180) +        g = m2; +      else if (hue < 240) +        g = m1 + (m2 - m1) * (240 - hue) / 60; +      else +        g = m1; + +      hue = *h - 120; +      while (hue > 360) +        hue -= 360; +      while (hue < 0) +        hue += 360; + +      if (hue < 60) +        b = m1 + (m2 - m1) * hue / 60; +      else if (hue < 180) +        b = m2; +      else if (hue < 240) +        b = m1 + (m2 - m1) * (240 - hue) / 60; +      else +        b = m1; + +      *h = r; +      *l = g; +      *s = b; +    } +} +static void +_pie_style_shade (GdkColor *a, +                  GdkColor *b, +                  gdouble   k) +{ +  gdouble red; +  gdouble green; +  gdouble blue; + +  red = (gdouble) a->red / 65535.0; +  green = (gdouble) a->green / 65535.0; +  blue = (gdouble) a->blue / 65535.0; + +  rgb_to_hls (&red, &green, &blue); + +  green *= k; +  if (green > 1.0) +    green = 1.0; +  else if (green < 0.0) +    green = 0.0; + +  blue *= k; +  if (blue > 1.0) +    blue = 1.0; +  else if (blue < 0.0) +    blue = 0.0; + +  hls_to_rgb (&red, &green, &blue); + +  b->red = red * 65535.0; +  b->green = green * 65535.0; +  b->blue = blue * 65535.0; +} + + +static GtkWidget* +create_pie_widget (FMPropertiesWindow *window) +{ +	CajaFile		*file; +	GtkTable 		*table; +	GtkStyle		*style; +	GtkWidget 		*pie_canvas; +	GtkWidget 		*used_canvas; +	GtkWidget 		*used_label; +	GtkWidget 		*free_canvas; +	GtkWidget 		*free_label; +	GtkWidget 		*capacity_label; +	GtkWidget 		*fstype_label; +	gchar			*capacity; +	gchar 			*used; +	gchar 			*free; +	const char		*fs_type; +	gchar			*uri; +	GFile *location; +	GFileInfo *info; + +	#if GLIB_CHECK_VERSION(2, 30, 0) +		capacity = g_format_size(window->details->volume_capacity); +		free = g_format_size(window->details->volume_free); +		used = g_format_size(window->details->volume_capacity - window->details->volume_free); +	#else +		capacity = g_format_size_for_display(window->details->volume_capacity); +		free = g_format_size_for_display(window->details->volume_free); +		used = g_format_size_for_display(window->details->volume_capacity - window->details->volume_free); +	#endif + +	file = get_original_file (window); + +	uri = caja_file_get_activation_uri (file); + +	table = GTK_TABLE (gtk_table_new (4, 3, FALSE)); + +	style = gtk_rc_get_style (GTK_WIDGET(table)); + +	if (!gtk_style_lookup_color (style, "chart_color_1", &window->details->used_color)) { +		window->details->used_color.red = USED_FILL_R; +		window->details->used_color.green = USED_FILL_G; +		window->details->used_color.blue = USED_FILL_B; +	} + +	if (!gtk_style_lookup_color (style, "chart_color_2", &window->details->free_color)) { +		window->details->free_color.red = FREE_FILL_R; +		window->details->free_color.green = FREE_FILL_G; +		window->details->free_color.blue = FREE_FILL_B; +	} + +	_pie_style_shade (&window->details->used_color, &window->details->used_stroke_color, 0.7); +	_pie_style_shade (&window->details->free_color, &window->details->free_stroke_color, 0.7); + +	pie_canvas = gtk_drawing_area_new (); +	gtk_widget_set_size_request (pie_canvas, 200, 200); + +	used_canvas = gtk_drawing_area_new (); +	gtk_widget_set_size_request (used_canvas, 20, 20); +	/* Translators: "used" refers to the capacity of the filesystem */ +	used_label = gtk_label_new (g_strconcat (used, " ", _("used"), NULL)); + +	free_canvas = gtk_drawing_area_new (); +	gtk_widget_set_size_request (free_canvas,20,20); +	/* Translators: "free" refers to the capacity of the filesystem */ +	free_label = gtk_label_new (g_strconcat (free, " ", _("free"), NULL)); + +	capacity_label = gtk_label_new (g_strconcat (_("Total capacity:"), " ", capacity, NULL)); +	fstype_label = gtk_label_new (NULL); + +	location = g_file_new_for_uri (uri); +	info = g_file_query_filesystem_info (location, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, +					     NULL, NULL); +	if (info) { +		fs_type = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE); +		if (fs_type != NULL) { +			gtk_label_set_text (GTK_LABEL (fstype_label), g_strconcat (_("Filesystem type:"), " ", fs_type, NULL)); +		} + +		g_object_unref (info); +	} +	g_object_unref (location); + +	g_free (uri); +	g_free (capacity); +	g_free (used); +	g_free (free); + +	gtk_table_attach (table, pie_canvas , 0, 1, 0, 4, GTK_FILL, 	GTK_SHRINK, 5, 5); + +	gtk_table_attach (table, used_canvas, 1, 2, 0, 1, 0, 	    	0, 	    5, 5); +	gtk_table_attach (table, used_label , 2, 3, 0, 1, GTK_FILL, 	0,          5, 5); + +	gtk_table_attach (table, free_canvas, 1, 2, 1, 2, 0, 		0,          5, 5); +	gtk_table_attach (table, free_label , 2, 3, 1, 2, GTK_FILL, 	0,          5, 5); + +	gtk_table_attach (table, capacity_label , 1, 3, 2, 3, GTK_FILL, 0,          5, 5); +	gtk_table_attach (table, fstype_label , 1, 3, 3, 4, GTK_FILL, 0,          5, 5); + +	g_signal_connect (G_OBJECT (pie_canvas), "expose-event", G_CALLBACK (paint_pie_chart), window); +	g_signal_connect (G_OBJECT (used_canvas), "expose-event", G_CALLBACK (paint_used_legend), window); +	g_signal_connect (G_OBJECT (free_canvas), "expose-event", G_CALLBACK (paint_free_legend), window); + +	return GTK_WIDGET (table); +} + +static GtkWidget* +create_volume_usage_widget (FMPropertiesWindow *window) +{ +	GtkWidget *piewidget; +	gchar *uri; +	CajaFile *file; +	GFile *location; +	GFileInfo *info; + +	file = get_original_file (window); + +	uri = caja_file_get_activation_uri (file); + +	location = g_file_new_for_uri (uri); +	info = g_file_query_filesystem_info (location, "filesystem::*", NULL, NULL); + +	if (info) { +		window->details->volume_capacity = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE); +		window->details->volume_free = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE); + +		g_object_unref (info); +	} else { +		window->details->volume_capacity = 0; +		window->details->volume_free = 0; +	} + +	g_object_unref (location); + +	piewidget = create_pie_widget (window); + +        gtk_widget_show_all (piewidget); + +	return piewidget; +} + +static void +create_basic_page (FMPropertiesWindow *window) +{ +	GtkTable *table; +	GtkWidget *icon_aligner; +	GtkWidget *icon_pixmap_widget; +	GtkWidget *volume_usage; +	GtkWidget *hbox, *vbox; + +	guint last_row, row; + +	hbox = create_page_with_hbox (window->details->notebook, _("Basic")); + +	/* Icon pixmap */ + +	icon_pixmap_widget = create_image_widget ( +		window, should_show_custom_icon_buttons (window)); +	gtk_widget_show (icon_pixmap_widget); + +	icon_aligner = gtk_alignment_new (1, 0, 0, 0); +	gtk_widget_show (icon_aligner); + +	gtk_container_add (GTK_CONTAINER (icon_aligner), icon_pixmap_widget); +	gtk_box_pack_start (GTK_BOX (hbox), icon_aligner, FALSE, FALSE, 0); + +	window->details->icon_chooser = NULL; + +	/* Table */ + +	vbox = gtk_vbox_new (FALSE, 0); +	gtk_widget_show (vbox); +	gtk_container_add (GTK_CONTAINER (hbox), vbox); + +	table = GTK_TABLE (create_attribute_value_table (GTK_VBOX (vbox), 0)); +	window->details->basic_table = table; + +	/* Name label.  The text will be determined in update_name_field */ +	row = append_title_field (table, NULL, &window->details->name_label); +	window->details->name_row = row; + +	/* Name field */ +	window->details->name_field = NULL; +	update_name_field (window); + +	/* Start with name field selected, if it's an entry. */ +	if (CAJA_IS_ENTRY (window->details->name_field)) { +		caja_entry_select_all (CAJA_ENTRY (window->details->name_field)); +		gtk_widget_grab_focus (GTK_WIDGET (window->details->name_field)); +	} + +	if (fm_ditem_page_should_show (window->details->target_files)) { +		GtkSizeGroup *label_size_group; +		GtkWidget *box; + +		row = append_row (table); + +		label_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); +		gtk_size_group_add_widget (label_size_group, +					   GTK_WIDGET (window->details->name_label)); +		box = fm_ditem_page_make_box (label_size_group, +					      window->details->target_files); + +		gtk_table_attach (window->details->basic_table, box, +				  TITLE_COLUMN, VALUE_COLUMN + 1, +				  row, row + 1, +				  GTK_FILL, 0, +				  0, 0); +	} + +	if (should_show_file_type (window)) { +		append_title_value_pair (window, +					 table, _("Type:"), +					 "type", +					 INCONSISTENT_STATE_STRING, +					 FALSE); +	} + +	if (should_show_link_target (window)) { +		append_title_and_ellipsizing_value (window, table, +						    _("Link target:"), +						    "link_target", +						    INCONSISTENT_STATE_STRING, +						    FALSE); +	} + +	if (is_multi_file_window (window) || +	    caja_file_is_directory (get_target_file (window))) { +		append_directory_contents_fields (window, table); +	} else { +		append_title_value_pair (window, table, _("Size:"), +					 "size_detail", +					 INCONSISTENT_STATE_STRING, +					 FALSE); +	} + +	append_blank_row (table); + +	if (should_show_location_info (window)) { +		append_title_and_ellipsizing_value (window, table, _("Location:"), +						    "where", +						    INCONSISTENT_STATE_STRING, +						    TRUE); + +		append_title_and_ellipsizing_value (window, table, +						    _("Volume:"), +						    "volume", +						    INCONSISTENT_STATE_STRING, +						    FALSE); +	} + +	if (should_show_accessed_date (window)) { +		append_blank_row (table); + +		append_title_value_pair (window, table, _("Accessed:"), +					 "date_accessed", +					 INCONSISTENT_STATE_STRING, +					 FALSE); +		append_title_value_pair (window, table, _("Modified:"), +					 "date_modified", +					 INCONSISTENT_STATE_STRING, +					 FALSE); +	} + +	if (should_show_free_space (window)) { +		append_blank_row (table); + +		append_title_value_pair (window, table, _("Free space:"), +					 "free_space", +					 INCONSISTENT_STATE_STRING, +					 FALSE); +	} + +	if (should_show_volume_usage (window)) { +		last_row = append_row (table); +		volume_usage = create_volume_usage_widget (window); +		gtk_table_attach_defaults (GTK_TABLE(table), volume_usage, 0, 2, last_row, last_row+1); +	} +} + +static GHashTable * +get_initial_emblems (GList *files) +{ +	GHashTable *ret; +	GList *l; + +	ret = g_hash_table_new_full (g_direct_hash, +				     g_direct_equal, +				     NULL, +				     (GDestroyNotify)eel_g_list_free_deep); + +	for (l = files; l != NULL; l = l->next) { +		CajaFile *file; +		GList *keywords; + +		file = CAJA_FILE (l->data); + +		keywords = caja_file_get_keywords (file); +		g_hash_table_insert (ret, file, keywords); +	} + +	return ret; +} + +static gboolean +files_has_directory (FMPropertiesWindow *window) +{ +	GList *l; + +	for (l = window->details->target_files; l != NULL; l = l->next) { +		CajaFile *file; +		file = CAJA_FILE (l->data); +		if (caja_file_is_directory (file)) { +			return TRUE; +		} + +	} + +	return FALSE; +} + +static gboolean +files_has_changable_permissions_directory (FMPropertiesWindow *window) +{ +	GList *l; + +	for (l = window->details->target_files; l != NULL; l = l->next) { +		CajaFile *file; +		file = CAJA_FILE (l->data); +		if (caja_file_is_directory (file) && +		    caja_file_can_get_permissions (file) && +		    caja_file_can_set_permissions (file)) { +			return TRUE; +		} + +	} + +	return FALSE; +} + + +static gboolean +files_has_file (FMPropertiesWindow *window) +{ +	GList *l; + +	for (l = window->details->target_files; l != NULL; l = l->next) { +		CajaFile *file; +		file = CAJA_FILE (l->data); +		if (!caja_file_is_directory (file)) { +			return TRUE; +		} + +	} + +	return FALSE; +} + + +static void +create_emblems_page (FMPropertiesWindow *window) +{ +	GtkWidget *emblems_table, *button, *scroller; +	char *emblem_name; +	GdkPixbuf *pixbuf; +	char *label; +	GList *icons, *l; +	CajaIconInfo *info; + +	/* The emblems wrapped table */ +	scroller = eel_scrolled_wrap_table_new (TRUE, GTK_SHADOW_NONE, &emblems_table); + +	gtk_container_set_border_width (GTK_CONTAINER (emblems_table), 12); + +	gtk_widget_show (scroller); + +	gtk_notebook_append_page (window->details->notebook, +				  scroller, gtk_label_new (_("Emblems"))); + +	icons = caja_emblem_list_available (); + +	window->details->initial_emblems = get_initial_emblems (window->details->original_files); + +	l = icons; +	while (l != NULL) { +		emblem_name = l->data; +		l = l->next; + +		if (!caja_emblem_should_show_in_list (emblem_name)) { +			continue; +		} + +		info = caja_icon_info_lookup_from_name (emblem_name, CAJA_ICON_SIZE_SMALL); +		pixbuf = caja_icon_info_get_pixbuf_nodefault_at_size (info, CAJA_ICON_SIZE_SMALL); + +		if (pixbuf == NULL) { +			continue; +		} + +		label = g_strdup (caja_icon_info_get_display_name (info)); +		g_object_unref (info); + +		if (label == NULL) { +			label = caja_emblem_get_keyword_from_icon_name (emblem_name); +		} + +		button = eel_labeled_image_check_button_new (label, pixbuf); +		eel_labeled_image_set_fixed_image_height (EEL_LABELED_IMAGE (gtk_bin_get_child (GTK_BIN (button))), STANDARD_EMBLEM_HEIGHT); +		eel_labeled_image_set_spacing (EEL_LABELED_IMAGE (gtk_bin_get_child (GTK_BIN (button))), EMBLEM_LABEL_SPACING); + +		g_free (label); +		g_object_unref (pixbuf); + +		/* Attach parameters and signal handler. */ +		g_object_set_data_full (G_OBJECT (button), "caja_emblem_name", +					caja_emblem_get_keyword_from_icon_name (emblem_name), g_free); + +		window->details->emblem_buttons = +			g_list_append (window->details->emblem_buttons, +				       button); + +		g_signal_connect_object (button, "toggled", +					 G_CALLBACK (emblem_button_toggled), +					 G_OBJECT (window), +					 0); + +		gtk_container_add (GTK_CONTAINER (emblems_table), button); +	} +	eel_g_list_free_deep (icons); +	gtk_widget_show_all (emblems_table); +} + +static void +start_long_operation (FMPropertiesWindow *window) +{ +	if (window->details->long_operation_underway == 0) { +		/* start long operation */ +		GdkCursor * cursor; + +		cursor = gdk_cursor_new (GDK_WATCH); +		gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (window)), cursor); +		gdk_cursor_unref (cursor); +	} +	window->details->long_operation_underway ++; +} + +static void +end_long_operation (FMPropertiesWindow *window) +{ +	if (gtk_widget_get_window (GTK_WIDGET (window)) != NULL && +	    window->details->long_operation_underway == 1) { +		/* finished !! */ +		gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (window)), NULL); +	} +	window->details->long_operation_underway--; +} + +static void +permission_change_callback (CajaFile *file, +			    GFile *res_loc, +			    GError *error, +			    gpointer callback_data) +{ +	FMPropertiesWindow *window; +	g_assert (callback_data != NULL); + +	window = FM_PROPERTIES_WINDOW (callback_data); +	end_long_operation (window); + +	/* Report the error if it's an error. */ +	fm_report_error_setting_permissions (file, error, NULL); + +	g_object_unref (window); +} + +static void +update_permissions (FMPropertiesWindow *window, +		    guint32 vfs_new_perm, +		    guint32 vfs_mask, +		    gboolean is_folder, +		    gboolean apply_to_both_folder_and_dir, +		    gboolean use_original) +{ +	GList *l; + +	for (l = window->details->target_files; l != NULL; l = l->next) { +		CajaFile *file; +		guint32 permissions; + +		file = CAJA_FILE (l->data); + +		if (!caja_file_can_get_permissions (file)) { +			continue; +		} + +		if (!apply_to_both_folder_and_dir && +		    ((caja_file_is_directory (file) && !is_folder) || +		     (!caja_file_is_directory (file) && is_folder))) { +			continue; +		} + +		permissions = caja_file_get_permissions (file); +		if (use_original) { +			gpointer ptr; +			if (g_hash_table_lookup_extended (window->details->initial_permissions, +							  file, NULL, &ptr)) { +				permissions = (permissions & ~vfs_mask) | (GPOINTER_TO_INT (ptr) & vfs_mask); +			} +		} else { +			permissions = (permissions & ~vfs_mask) | vfs_new_perm; +		} + +		start_long_operation (window); +		g_object_ref (window); +		caja_file_set_permissions +			(file, permissions, +			 permission_change_callback, +			 window); +	} +} + +static gboolean +initial_permission_state_consistent (FMPropertiesWindow *window, +				     guint32 mask, +				     gboolean is_folder, +				     gboolean both_folder_and_dir) +{ +	GList *l; +	gboolean first; +	guint32 first_permissions; + +	first = TRUE; +	first_permissions = 0; +	for (l = window->details->target_files; l != NULL; l = l->next) { +		CajaFile *file; +		guint32 permissions; + +		file = l->data; + +		if (!both_folder_and_dir && +		    ((caja_file_is_directory (file) && !is_folder) || +		     (!caja_file_is_directory (file) && is_folder))) { +			continue; +		} + +		permissions = GPOINTER_TO_INT (g_hash_table_lookup (window->details->initial_permissions, +								    file)); + +		if (first) { +			if ((permissions & mask) != mask && +			    (permissions & mask) != 0) { +				/* Not fully on or off -> inconsistent */ +				return FALSE; +			} + +			first_permissions = permissions; +			first = FALSE; + +		} else if ((permissions & mask) != first_permissions) { +			/* Not same permissions as first -> inconsistent */ +			return FALSE; +		} +	} +	return TRUE; +} + +static void +permission_button_toggled (GtkToggleButton *button, +			   FMPropertiesWindow *window) +{ +	gboolean is_folder, is_special; +	guint32 permission_mask; +	gboolean inconsistent; +	gboolean on; + +	permission_mask = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), +							      "permission")); +	is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), +							"is-folder")); +	is_special = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), +							"is-special")); + +	if (gtk_toggle_button_get_active (button) +	    && !gtk_toggle_button_get_inconsistent (button)) { +		/* Go to the initial state unless the initial state was +		   consistent, or we support recursive apply */ +		inconsistent = TRUE; +		on = TRUE; + +		if (!window->details->has_recursive_apply && +		    initial_permission_state_consistent (window, permission_mask, is_folder, is_special)) { +			inconsistent = FALSE; +			on = TRUE; +		} +	} else if (gtk_toggle_button_get_inconsistent (button) +		   && !gtk_toggle_button_get_active (button)) { +		inconsistent = FALSE; +		on = TRUE; +	} else { +		inconsistent = FALSE; +		on = FALSE; +	} + +	g_signal_handlers_block_by_func (G_OBJECT (button), +					 G_CALLBACK (permission_button_toggled), +					 window); + +	gtk_toggle_button_set_active (button, on); +	gtk_toggle_button_set_inconsistent (button, inconsistent); + +	g_signal_handlers_unblock_by_func (G_OBJECT (button), +					   G_CALLBACK (permission_button_toggled), +					   window); + +	update_permissions (window, +			    on?permission_mask:0, +			    permission_mask, +			    is_folder, +			    is_special, +			    inconsistent); +} + +static void +permission_button_update (FMPropertiesWindow *window, +			  GtkToggleButton *button) +{ +	GList *l; +	gboolean all_set; +	gboolean all_unset; +	gboolean all_cannot_set; +	gboolean is_folder, is_special; +	gboolean no_match; +	gboolean sensitive; +	guint32 button_permission; + +	if (gtk_toggle_button_get_inconsistent (button) && +	    window->details->has_recursive_apply) { +		/* Never change from an inconsistent state if we have dirs, even +		 * if the current state is now consistent, because its a useful +		 * state for recursive apply. +		 */ +		return; +	} + +	button_permission = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), +								"permission")); +	is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), +							"is-folder")); +	is_special = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), +							 "is-special")); + +	all_set = TRUE; +	all_unset = TRUE; +	all_cannot_set = TRUE; +	no_match = TRUE; +	for (l = window->details->target_files; l != NULL; l = l->next) { +		CajaFile *file; +		guint32 file_permissions; + +		file = CAJA_FILE (l->data); + +		if (!caja_file_can_get_permissions (file)) { +			continue; +		} + +		if (!is_special && +		    ((caja_file_is_directory (file) && !is_folder) || +		     (!caja_file_is_directory (file) && is_folder))) { +			continue; +		} + +		no_match = FALSE; + +		file_permissions = caja_file_get_permissions (file); + +		if ((file_permissions & button_permission) == button_permission) { +			all_unset = FALSE; +		} else if ((file_permissions & button_permission) == 0) { +			all_set = FALSE; +		} else { +			all_unset = FALSE; +			all_set = FALSE; +		} + +		if (caja_file_can_set_permissions (file)) { +			all_cannot_set = FALSE; +		} +	} + +	sensitive = !all_cannot_set; +	if (!is_folder) { +		/* Don't insitive files when we have recursive apply */ +		sensitive |= window->details->has_recursive_apply; +	} + + +	g_signal_handlers_block_by_func (G_OBJECT (button), +					 G_CALLBACK (permission_button_toggled), +					 window); + +	gtk_toggle_button_set_active (button, !all_unset); +	/* if actually inconsistent, or default value for file buttons +	   if no files are selected. (useful for recursive apply) */ +	gtk_toggle_button_set_inconsistent (button, +					    (!all_unset && !all_set) || +					    (!is_folder && no_match)); +	gtk_widget_set_sensitive (GTK_WIDGET (button), sensitive); + +	g_signal_handlers_unblock_by_func (G_OBJECT (button), +					   G_CALLBACK (permission_button_toggled), +					   window); +} + +static void +set_up_permissions_checkbox (FMPropertiesWindow *window, +			     GtkWidget *check_button, +			     guint32 permission, +			     gboolean is_folder) +{ +	/* Load up the check_button with data we'll need when updating its state. */ +        g_object_set_data (G_OBJECT (check_button), "permission", +			   GINT_TO_POINTER (permission)); +        g_object_set_data (G_OBJECT (check_button), "properties_window", +			   window); +	g_object_set_data (G_OBJECT (check_button), "is-folder", +			   GINT_TO_POINTER (is_folder)); + +	window->details->permission_buttons = +		g_list_prepend (window->details->permission_buttons, +				check_button); + +	g_signal_connect_object (check_button, "toggled", +				 G_CALLBACK (permission_button_toggled), +				 window, +				 0); +} + +static void +add_permissions_checkbox_with_label (FMPropertiesWindow *window, +				     GtkTable *table, +				     int row, int column, +				     const char *label, +				     guint32 permission_to_check, +				     GtkLabel *label_for, +				     gboolean is_folder) +{ +	GtkWidget *check_button; +	gboolean a11y_enabled; + +	check_button = gtk_check_button_new_with_mnemonic (label); +	gtk_widget_show (check_button); +	gtk_table_attach (table, check_button, +			  column, column + 1, +			  row, row + 1, +			  GTK_FILL, 0, +			  0, 0); + +	set_up_permissions_checkbox (window, +				     check_button, +				     permission_to_check, +				     is_folder); + +	a11y_enabled = GTK_IS_ACCESSIBLE (gtk_widget_get_accessible (check_button)); +	if (a11y_enabled && label_for != NULL) { +		eel_accessibility_set_up_label_widget_relation (GTK_WIDGET (label_for), +								check_button); +	} +} + +static void +add_permissions_checkbox (FMPropertiesWindow *window, +			  GtkTable *table, +			  int row, int column, +			  guint32 permission_to_check, +			  GtkLabel *label_for, +			  gboolean is_folder) +{ +	gchar *label; + +	if (column == PERMISSIONS_CHECKBOXES_READ_COLUMN) { +		label = _("_Read"); +	} else if (column == PERMISSIONS_CHECKBOXES_WRITE_COLUMN) { +		label = _("_Write"); +	} else { +		label = _("E_xecute"); +	} + +	add_permissions_checkbox_with_label (window, table, +					     row, column, +					     label, +					     permission_to_check, +					     label_for, +					     is_folder); +} + +enum { +	UNIX_PERM_SUID = S_ISUID, +	UNIX_PERM_SGID = S_ISGID, +	UNIX_PERM_STICKY = 01000,	/* S_ISVTX not defined on all systems */ +	UNIX_PERM_USER_READ = S_IRUSR, +	UNIX_PERM_USER_WRITE = S_IWUSR, +	UNIX_PERM_USER_EXEC = S_IXUSR, +	UNIX_PERM_USER_ALL = S_IRUSR | S_IWUSR | S_IXUSR, +	UNIX_PERM_GROUP_READ = S_IRGRP, +	UNIX_PERM_GROUP_WRITE = S_IWGRP, +	UNIX_PERM_GROUP_EXEC = S_IXGRP, +	UNIX_PERM_GROUP_ALL = S_IRGRP | S_IWGRP | S_IXGRP, +	UNIX_PERM_OTHER_READ = S_IROTH, +	UNIX_PERM_OTHER_WRITE = S_IWOTH, +	UNIX_PERM_OTHER_EXEC = S_IXOTH, +	UNIX_PERM_OTHER_ALL = S_IROTH | S_IWOTH | S_IXOTH +}; + +typedef enum { +	PERMISSION_READ  = (1<<0), +	PERMISSION_WRITE = (1<<1), +	PERMISSION_EXEC  = (1<<2) +} PermissionValue; + +typedef enum { +	PERMISSION_USER, +	PERMISSION_GROUP, +	PERMISSION_OTHER +} PermissionType; + +static guint32 vfs_perms[3][3] = { +	{UNIX_PERM_USER_READ, UNIX_PERM_USER_WRITE, UNIX_PERM_USER_EXEC}, +	{UNIX_PERM_GROUP_READ, UNIX_PERM_GROUP_WRITE, UNIX_PERM_GROUP_EXEC}, +	{UNIX_PERM_OTHER_READ, UNIX_PERM_OTHER_WRITE, UNIX_PERM_OTHER_EXEC}, +}; + +static guint32 +permission_to_vfs (PermissionType type, PermissionValue perm) +{ +	guint32 vfs_perm; +	g_assert (type >= 0 && type < 3); + +	vfs_perm = 0; +	if (perm & PERMISSION_READ) { +		vfs_perm |= vfs_perms[type][0]; +	} +	if (perm & PERMISSION_WRITE) { +		vfs_perm |= vfs_perms[type][1]; +	} +	if (perm & PERMISSION_EXEC) { +		vfs_perm |= vfs_perms[type][2]; +	} + +	return vfs_perm; +} + + +static PermissionValue +permission_from_vfs (PermissionType type, guint32 vfs_perm) +{ +	PermissionValue perm; +	g_assert (type >= 0 && type < 3); + +	perm = 0; +	if (vfs_perm & vfs_perms[type][0]) { +		perm |= PERMISSION_READ; +	} +	if (vfs_perm & vfs_perms[type][1]) { +		perm |= PERMISSION_WRITE; +	} +	if (vfs_perm & vfs_perms[type][2]) { +		perm |= PERMISSION_EXEC; +	} + +	return perm; +} + +static void +permission_combo_changed (GtkWidget *combo, FMPropertiesWindow *window) +{ +	GtkTreeIter iter; +	GtkTreeModel *model; +	gboolean is_folder, use_original; +	PermissionType type; +	int new_perm, mask; +	guint32 vfs_new_perm, vfs_mask; + +	is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "is-folder")); +	type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "permission-type")); + +	if (is_folder) { +		mask = PERMISSION_READ|PERMISSION_WRITE|PERMISSION_EXEC; +	} else { +		mask = PERMISSION_READ|PERMISSION_WRITE; +	} + +	vfs_mask = permission_to_vfs (type, mask); + +	model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo)); + +	if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo),  &iter)) { +		return; +	} +	gtk_tree_model_get (model, &iter, 1, &new_perm, 2, &use_original, -1); +	vfs_new_perm = permission_to_vfs (type, new_perm); + +	update_permissions (window, vfs_new_perm, vfs_mask, +			    is_folder, FALSE, use_original); +} + +static void +permission_combo_add_multiple_choice (GtkComboBox *combo, GtkTreeIter *iter) +{ +	GtkTreeModel *model; +	GtkListStore *store; +	gboolean found; + +	model = gtk_combo_box_get_model (combo); +	store = GTK_LIST_STORE (model); + +	found = FALSE; +	gtk_tree_model_get_iter_first (model, iter); +	do { +		gboolean multi; +		gtk_tree_model_get (model, iter, 2, &multi, -1); + +		if (multi) { +			found = TRUE; +			break; +		} +	} while (gtk_tree_model_iter_next (model, iter)); + +	if (!found) { +		gtk_list_store_append (store, iter); +		gtk_list_store_set (store, iter, 0, "---", 1, 0, 2, TRUE, -1); +	} +} + +static void +permission_combo_update (FMPropertiesWindow *window, +			 GtkComboBox *combo) +{ +	PermissionType type; +	PermissionValue perm, all_dir_perm, all_file_perm, all_perm; +	gboolean is_folder, no_files, no_dirs, all_file_same, all_dir_same, all_same; +	gboolean all_dir_cannot_set, all_file_cannot_set, sensitive; +	GtkTreeIter iter; +	int mask; +	GtkTreeModel *model; +	GtkListStore *store; +	GList *l; +	gboolean is_multi; + +	model = gtk_combo_box_get_model (combo); + +	is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "is-folder")); +	type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "permission-type")); + +	is_multi = FALSE; +	if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo),  &iter)) { +		gtk_tree_model_get (model, &iter, 2, &is_multi, -1); +	} + +	if (is_multi && window->details->has_recursive_apply) { +		/* Never change from an inconsistent state if we have dirs, even +		 * if the current state is now consistent, because its a useful +		 * state for recursive apply. +		 */ +		return; +	} + +	no_files = TRUE; +	no_dirs = TRUE; +	all_dir_same = TRUE; +	all_file_same = TRUE; +	all_dir_perm = 0; +	all_file_perm = 0; +	all_dir_cannot_set = TRUE; +	all_file_cannot_set = TRUE; + +	for (l = window->details->target_files; l != NULL; l = l->next) { +		CajaFile *file; +		guint32 file_permissions; + +		file = CAJA_FILE (l->data); + +		if (!caja_file_can_get_permissions (file)) { +			continue; +		} + +		if (caja_file_is_directory (file)) { +			mask = PERMISSION_READ|PERMISSION_WRITE|PERMISSION_EXEC; +		} else { +			mask = PERMISSION_READ|PERMISSION_WRITE; +		} + +		file_permissions = caja_file_get_permissions (file); + +		perm = permission_from_vfs (type, file_permissions) & mask; + +		if (caja_file_is_directory (file)) { +			if (no_dirs) { +				all_dir_perm = perm; +				no_dirs = FALSE; +			} else if (perm != all_dir_perm) { +				all_dir_same = FALSE; +			} + +			if (caja_file_can_set_permissions (file)) { +				all_dir_cannot_set = FALSE; +			} +		} else { +			if (no_files) { +				all_file_perm = perm; +				no_files = FALSE; +			} else if (perm != all_file_perm) { +				all_file_same = FALSE; +			} + +			if (caja_file_can_set_permissions (file)) { +				all_file_cannot_set = FALSE; +			} +		} +	} + +	if (is_folder) { +		all_same = all_dir_same; +		all_perm = all_dir_perm; +	} else { +		all_same = all_file_same && !no_files; +		all_perm = all_file_perm; +	} + +	store = GTK_LIST_STORE (model); +	if (all_same) { +		gboolean found; + +		found = FALSE; +		gtk_tree_model_get_iter_first (model, &iter); +		do { +			int current_perm; +			gtk_tree_model_get (model, &iter, 1, ¤t_perm, -1); + +			if (current_perm == all_perm) { +				found = TRUE; +				break; +			} +		} while (gtk_tree_model_iter_next (model, &iter)); + +		if (!found) { +			GString *str; +			str = g_string_new (""); + +			if (!(all_perm & PERMISSION_READ)) { +				/* translators: this gets concatenated to "no read", +				 * "no access", etc. (see following strings) +				 */ +				g_string_append (str, _("no ")); +			} +			if (is_folder) { +				g_string_append (str, _("list")); +			} else { +				g_string_append (str, _("read")); +			} + +			g_string_append (str, ", "); + +			if (!(all_perm & PERMISSION_WRITE)) { +				g_string_append (str, _("no ")); +			} +			if (is_folder) { +				g_string_append (str, _("create/delete")); +			} else { +				g_string_append (str, _("write")); +			} + +			if (is_folder) { +				g_string_append (str, ", "); + +				if (!(all_perm & PERMISSION_EXEC)) { +					g_string_append (str, _("no ")); +				} +				g_string_append (str, _("access")); +			} + +			gtk_list_store_append (store, &iter); +			gtk_list_store_set (store, &iter, +					    0, str->str, +					    1, all_perm, -1); + +			g_string_free (str, TRUE); +		} +	} else { +		permission_combo_add_multiple_choice (combo, &iter); +	} + +	g_signal_handlers_block_by_func (G_OBJECT (combo), +					 G_CALLBACK (permission_combo_changed), +					 window); + +	gtk_combo_box_set_active_iter (combo, &iter); + +	/* Also enable if no files found (for recursive +	   file changes when only selecting folders) */ +	if (is_folder) { +		sensitive = !all_dir_cannot_set; +	} else { +		sensitive = !all_file_cannot_set || +			window->details->has_recursive_apply; +	} +	gtk_widget_set_sensitive (GTK_WIDGET (combo), sensitive); + +	g_signal_handlers_unblock_by_func (G_OBJECT (combo), +					   G_CALLBACK (permission_combo_changed), +					   window); + +} + +static void +add_permissions_combo_box (FMPropertiesWindow *window, GtkTable *table, +			   PermissionType type, gboolean is_folder, +			   gboolean short_label) +{ +	GtkWidget *combo; +	GtkLabel *label; +	GtkListStore *store; +	GtkCellRenderer *cell; +	GtkTreeIter iter; +	int row; + +	if (short_label) { +		row = append_title_field (table, _("Access:"), &label); +	} else if (is_folder) { +		row = append_title_field (table, _("Folder access:"), &label); +	} else { +		row = append_title_field (table, _("File access:"), &label); +	} + +	store = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN); +	combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL (store)); + +	g_object_set_data (G_OBJECT (combo), "is-folder", GINT_TO_POINTER (is_folder)); +	g_object_set_data (G_OBJECT (combo), "permission-type", GINT_TO_POINTER (type)); + +	if (is_folder) { +		if (type != PERMISSION_USER) { +			gtk_list_store_append (store, &iter); +			/* Translators: this is referred to the permissions +			 * the user has in a directory. +			 */ +			gtk_list_store_set (store, &iter, 0, _("None"), 1, 0, -1); +		} +		gtk_list_store_append (store, &iter); +		gtk_list_store_set (store, &iter, 0, _("List files only"), 1, PERMISSION_READ, -1); +		gtk_list_store_append (store, &iter); +		gtk_list_store_set (store, &iter, 0, _("Access files"), 1, PERMISSION_READ|PERMISSION_EXEC, -1); +		gtk_list_store_append (store, &iter); +		gtk_list_store_set (store, &iter, 0, _("Create and delete files"), 1, PERMISSION_READ|PERMISSION_EXEC|PERMISSION_WRITE, -1); +	} else { +		if (type != PERMISSION_USER) { +			gtk_list_store_append (store, &iter); +			gtk_list_store_set (store, &iter, 0, _("None"), 1, 0, -1); +		} +		gtk_list_store_append (store, &iter); +		gtk_list_store_set (store, &iter, 0, _("Read-only"), 1, PERMISSION_READ, -1); +		gtk_list_store_append (store, &iter); +		gtk_list_store_set (store, &iter, 0, _("Read and write"), 1, PERMISSION_READ|PERMISSION_WRITE, -1); +	} +	if (window->details->has_recursive_apply) { +		permission_combo_add_multiple_choice (GTK_COMBO_BOX (combo), &iter); +	} + +	g_object_unref (store); + +	window->details->permission_combos = +		g_list_prepend (window->details->permission_combos, +				combo); + +	g_signal_connect (combo, "changed", G_CALLBACK (permission_combo_changed), window); + +	cell = gtk_cell_renderer_text_new (); +	gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), cell, TRUE); +	gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), cell, +					"text", 0, +					NULL); + +	gtk_label_set_mnemonic_widget (label, combo); +	gtk_widget_show (combo); + +	gtk_table_attach (table, combo, +			  VALUE_COLUMN, VALUE_COLUMN + 1, +			  row, row + 1, +			  GTK_FILL, 0, +			  0, 0); +} + + +static GtkWidget * +append_special_execution_checkbox (FMPropertiesWindow *window, +				   GtkTable *table, +				   const char *label_text, +				   guint32 permission_to_check) +{ +	GtkWidget *check_button; +	guint last_row; + +	last_row = append_row (table); + +	check_button = gtk_check_button_new_with_mnemonic (label_text); +	gtk_widget_show (check_button); + +	gtk_table_attach (table, check_button, +			  VALUE_COLUMN, VALUE_COLUMN + 1, +			  last_row, last_row + 1, +			  GTK_FILL, 0, +			  0, 0); + +	set_up_permissions_checkbox (window, +				     check_button, +				     permission_to_check, +				     FALSE); +	g_object_set_data (G_OBJECT (check_button), "is-special", +			   GINT_TO_POINTER (TRUE)); + +	return check_button; +} + +static void +append_special_execution_flags (FMPropertiesWindow *window, GtkTable *table) +{ +	gint nrows; + +	append_special_execution_checkbox +		(window, table, _("Set _user ID"), UNIX_PERM_SUID); + +	g_object_get (table, "n-rows", &nrows, NULL); +	attach_title_field (table, nrows - 1, _("Special flags:")); + +	append_special_execution_checkbox (window, table, _("Set gro_up ID"), UNIX_PERM_SGID); +	append_special_execution_checkbox (window, table, _("_Sticky"), UNIX_PERM_STICKY); + +	g_object_get (table, "n-rows", &nrows, NULL); +	gtk_table_set_row_spacing (table, nrows - 1, 18); +} + +static gboolean +all_can_get_permissions (GList *file_list) +{ +	GList *l; +	for (l = file_list; l != NULL; l = l->next) { +		CajaFile *file; + +		file = CAJA_FILE (l->data); + +		if (!caja_file_can_get_permissions (file)) { +			return FALSE; +		} +	} + +	return TRUE; +} + +static gboolean +all_can_set_permissions (GList *file_list) +{ +	GList *l; +	for (l = file_list; l != NULL; l = l->next) { +		CajaFile *file; + +		file = CAJA_FILE (l->data); + +		if (!caja_file_can_set_permissions (file)) { +			return FALSE; +		} +	} + +	return TRUE; +} + +static GHashTable * +get_initial_permissions (GList *file_list) +{ +	GHashTable *ret; +	GList *l; + +	ret = g_hash_table_new (g_direct_hash, +				g_direct_equal); + +	for (l = file_list; l != NULL; l = l->next) { +		guint32 permissions; +		CajaFile *file; + +		file = CAJA_FILE (l->data); + +		permissions = caja_file_get_permissions (file); +		g_hash_table_insert (ret, file, +				     GINT_TO_POINTER (permissions)); +	} + +	return ret; +} + +static void +create_simple_permissions (FMPropertiesWindow *window, GtkTable *page_table) +{ +	gboolean has_file, has_directory; +	GtkLabel *group_label; +	GtkLabel *owner_label; +	GtkLabel *execute_label; +	GtkWidget *value; +	GtkComboBox *group_combo_box; +	GtkComboBox *owner_combo_box; +	guint last_row; +	gint nrows; + +	last_row = 0; + +	has_file = files_has_file (window); +	has_directory = files_has_directory (window); + +	if (!is_multi_file_window (window) && caja_file_can_set_owner (get_target_file (window))) { +		owner_label = attach_title_field (page_table, last_row, _("_Owner:")); +		/* Combo box in this case. */ +		owner_combo_box = attach_owner_combo_box (page_table, last_row, get_target_file (window)); +		gtk_label_set_mnemonic_widget (owner_label, +					       GTK_WIDGET (owner_combo_box)); +	} else { +		owner_label = attach_title_field (page_table, last_row, _("Owner:")); +		/* Static text in this case. */ +		value = attach_value_field (window, +				    page_table, last_row, VALUE_COLUMN, +				    "owner", +				    INCONSISTENT_STATE_STRING, +				    FALSE); +		gtk_label_set_mnemonic_widget (owner_label, value); +	} + +	if (has_directory) { +		add_permissions_combo_box (window, page_table, +					   PERMISSION_USER, TRUE, FALSE); +	} +	if (has_file || window->details->has_recursive_apply) { +		add_permissions_combo_box (window, page_table, +					   PERMISSION_USER, FALSE, !has_directory); +	} + +	g_object_get (page_table, "n-rows", &nrows, NULL); +	gtk_table_set_row_spacing (page_table, nrows - 1, 18); + +	if (!is_multi_file_window (window) && caja_file_can_set_group (get_target_file (window))) { +		last_row = append_title_field (page_table, +					       _("_Group:"), +					       &group_label); +		/* Combo box in this case. */ +		group_combo_box = attach_group_combo_box (page_table, last_row, +							  get_target_file (window)); +		gtk_label_set_mnemonic_widget (group_label, +					       GTK_WIDGET (group_combo_box)); +	} else { +		last_row = append_title_field (page_table, +					       _("Group:"), +					       &group_label); +		/* Static text in this case. */ +		value = attach_value_field (window, page_table, last_row, +				    VALUE_COLUMN, +				    "group", +				    INCONSISTENT_STATE_STRING, +				    FALSE); +		gtk_label_set_mnemonic_widget (group_label, value); +	} + +	if (has_directory) { +		add_permissions_combo_box (window, page_table, +					   PERMISSION_GROUP, TRUE, +					   FALSE); +	} +	if (has_file || window->details->has_recursive_apply) { +		add_permissions_combo_box (window, page_table, +					   PERMISSION_GROUP, FALSE, +					   !has_directory); +	} + +	g_object_get (page_table, "n-rows", &nrows, NULL); +	gtk_table_set_row_spacing (page_table, nrows - 1, 18); + +	append_title_field (page_table, +			    _("Others"), +			    &group_label); + +	if (has_directory) { +		add_permissions_combo_box (window, page_table, +					   PERMISSION_OTHER, TRUE, +					   FALSE); +	} +	if (has_file || window->details->has_recursive_apply) { +		add_permissions_combo_box (window, page_table, +					   PERMISSION_OTHER, FALSE, +					   !has_directory); +	} + +	g_object_get (page_table, "n-rows", &nrows, NULL); +	gtk_table_set_row_spacing (page_table, nrows - 1, 18); + +	last_row = append_title_field (page_table, +				       _("Execute:"), +				       &execute_label); +	add_permissions_checkbox_with_label (window, page_table, +					     last_row, 1, +					     _("Allow _executing file as program"), +					     UNIX_PERM_USER_EXEC|UNIX_PERM_GROUP_EXEC|UNIX_PERM_OTHER_EXEC, +					     execute_label, FALSE); + +} + +static void +create_permission_checkboxes (FMPropertiesWindow *window, +			      GtkTable *page_table, +			      gboolean is_folder) +{ +	guint checkbox_titles_row; +	GtkLabel *owner_perm_label; +	GtkLabel *group_perm_label; +	GtkLabel *other_perm_label; +	GtkTable *check_button_table; + +	checkbox_titles_row = append_title_field (page_table, _("Owner:"), &owner_perm_label); +	append_title_field (page_table, _("Group:"), &group_perm_label); +	append_title_field (page_table, _("Others:"), &other_perm_label); + +	check_button_table = GTK_TABLE (gtk_table_new +					(PERMISSIONS_CHECKBOXES_ROW_COUNT, +					 PERMISSIONS_CHECKBOXES_COLUMN_COUNT, +					 FALSE)); +	apply_standard_table_padding (check_button_table); +	gtk_widget_show (GTK_WIDGET (check_button_table)); +	gtk_table_attach (page_table, GTK_WIDGET (check_button_table), +			  VALUE_COLUMN, VALUE_COLUMN + 1, +			  checkbox_titles_row, checkbox_titles_row + PERMISSIONS_CHECKBOXES_ROW_COUNT, +			  0, 0, +			  0, 0); + +	add_permissions_checkbox (window, +				  check_button_table, +				  PERMISSIONS_CHECKBOXES_OWNER_ROW, +				  PERMISSIONS_CHECKBOXES_READ_COLUMN, +				  UNIX_PERM_USER_READ, +				  owner_perm_label, +				  is_folder); + +	add_permissions_checkbox (window, +				  check_button_table, +				  PERMISSIONS_CHECKBOXES_OWNER_ROW, +				  PERMISSIONS_CHECKBOXES_WRITE_COLUMN, +				  UNIX_PERM_USER_WRITE, +				  owner_perm_label, +				  is_folder); + +	add_permissions_checkbox (window, +				  check_button_table, +				  PERMISSIONS_CHECKBOXES_OWNER_ROW, +				  PERMISSIONS_CHECKBOXES_EXECUTE_COLUMN, +				  UNIX_PERM_USER_EXEC, +				  owner_perm_label, +				  is_folder); + +	add_permissions_checkbox (window, +				  check_button_table, +				  PERMISSIONS_CHECKBOXES_GROUP_ROW, +				  PERMISSIONS_CHECKBOXES_READ_COLUMN, +				  UNIX_PERM_GROUP_READ, +				  group_perm_label, +				  is_folder); + +	add_permissions_checkbox (window, +				  check_button_table, +				  PERMISSIONS_CHECKBOXES_GROUP_ROW, +				  PERMISSIONS_CHECKBOXES_WRITE_COLUMN, +				  UNIX_PERM_GROUP_WRITE, +				  group_perm_label, +				  is_folder); + +	add_permissions_checkbox (window, +				  check_button_table, +				  PERMISSIONS_CHECKBOXES_GROUP_ROW, +				  PERMISSIONS_CHECKBOXES_EXECUTE_COLUMN, +				  UNIX_PERM_GROUP_EXEC, +				  group_perm_label, +				  is_folder); + +	add_permissions_checkbox (window, +				  check_button_table, +				  PERMISSIONS_CHECKBOXES_OTHERS_ROW, +				  PERMISSIONS_CHECKBOXES_READ_COLUMN, +				  UNIX_PERM_OTHER_READ, +				  other_perm_label, +				  is_folder); + +	add_permissions_checkbox (window, +				  check_button_table, +				  PERMISSIONS_CHECKBOXES_OTHERS_ROW, +				  PERMISSIONS_CHECKBOXES_WRITE_COLUMN, +				  UNIX_PERM_OTHER_WRITE, +				  other_perm_label, +				  is_folder); + +	add_permissions_checkbox (window, +				  check_button_table, +				  PERMISSIONS_CHECKBOXES_OTHERS_ROW, +				  PERMISSIONS_CHECKBOXES_EXECUTE_COLUMN, +				  UNIX_PERM_OTHER_EXEC, +				  other_perm_label, +				  is_folder); +} + +static void +create_advanced_permissions (FMPropertiesWindow *window, GtkTable *page_table) +{ +	guint last_row; +	GtkLabel *group_label; +	GtkLabel *owner_label; +	GtkComboBox *group_combo_box; +	GtkComboBox *owner_combo_box; +	gboolean has_directory, has_file; +	gint nrows; + +	last_row = 0; + +	if (!is_multi_file_window (window) && caja_file_can_set_owner (get_target_file (window))) { + +		owner_label  = attach_title_field (page_table, last_row, _("_Owner:")); +		/* Combo box in this case. */ +		owner_combo_box = attach_owner_combo_box (page_table, last_row, get_target_file (window)); +		gtk_label_set_mnemonic_widget (owner_label, +					       GTK_WIDGET (owner_combo_box)); +	} else { +		GtkWidget *value; + +		owner_label = attach_title_field (page_table, last_row, _("Owner:")); +		/* Static text in this case. */ +		value = attach_value_field (window, +				    page_table, last_row, VALUE_COLUMN, +				    "owner", +				    INCONSISTENT_STATE_STRING, +				    FALSE); +		gtk_label_set_mnemonic_widget (owner_label, value); +	} + +	if (!is_multi_file_window (window) && caja_file_can_set_group (get_target_file (window))) { +		last_row = append_title_field (page_table, +					       _("_Group:"), +					       &group_label); +		/* Combo box in this case. */ +		group_combo_box = attach_group_combo_box (page_table, last_row, +							  get_target_file (window)); +		gtk_label_set_mnemonic_widget (group_label, +					       GTK_WIDGET (group_combo_box)); +	} else { +		last_row = append_title_field (page_table, +					       _("Group:"), +					       NULL); +		/* Static text in this case. */ +		attach_value_field (window, page_table, last_row, +				    VALUE_COLUMN, +				    "group", +				    INCONSISTENT_STATE_STRING, +				    FALSE); +	} + +	g_object_get (page_table, "n-rows", &nrows, NULL); +	gtk_table_set_row_spacing (page_table, nrows - 1, 18); + +	has_directory = files_has_directory (window); +	has_file = files_has_file (window); + +	if (has_directory) { +		if (has_file || window->details->has_recursive_apply) { +			append_title_field (page_table, +					    _("Folder Permissions:"), +					    NULL); +		} +		create_permission_checkboxes (window, page_table, TRUE); +		g_object_get (page_table, "n-rows", &nrows, NULL); +		gtk_table_set_row_spacing (page_table, nrows - 1, 18); + +	} + + +	if (has_file || window->details->has_recursive_apply) { +		if (has_directory) { +			append_title_field (page_table, +					    _("File Permissions:"), +					    NULL); +		} +		create_permission_checkboxes (window, page_table, FALSE); +		g_object_get (page_table, "n-rows", &nrows, NULL); +		gtk_table_set_row_spacing (page_table, nrows - 1, 18); +	} + +	append_special_execution_flags (window, page_table); + +	append_title_value_pair +		(window, page_table, _("Text view:"), +		 "permissions", INCONSISTENT_STATE_STRING, +		 FALSE); +} + +static void +set_recursive_permissions_done (gpointer callback_data) +{ +	FMPropertiesWindow *window; + +	window = FM_PROPERTIES_WINDOW (callback_data); +	end_long_operation (window); + +	g_object_unref (window); +} + + +static void +apply_recursive_clicked (GtkWidget *recursive_button, +			 FMPropertiesWindow *window) +{ +	guint32 file_permission, file_permission_mask; +	guint32 dir_permission, dir_permission_mask; +	guint32 vfs_mask, vfs_new_perm, p; +	GtkWidget *button, *combo; +	gboolean active, is_folder, is_special, use_original; +	GList *l; +	GtkTreeModel *model; +	GtkTreeIter iter; +	PermissionType type; +	int new_perm, mask; + +	file_permission = 0; +	file_permission_mask = 0; +	dir_permission = 0; +	dir_permission_mask = 0; + +	/* Advanced mode and execute checkbox: */ +	for (l = window->details->permission_buttons; l != NULL; l = l->next) { +		button = l->data; + +		if (gtk_toggle_button_get_inconsistent (GTK_TOGGLE_BUTTON (button))) { +			continue; +		} + +		active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)); +		p = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), +							"permission")); +		is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), +								"is-folder")); +		is_special = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), +								 "is-special")); + +		if (is_folder || is_special) { +			dir_permission_mask |= p; +			if (active) { +				dir_permission |= p; +			} +		} +		if (!is_folder || is_special) { +			file_permission_mask |= p; +			if (active) { +				file_permission |= p; +			} +		} +	} +	/* Simple mode, minus exec checkbox */ +	for (l = window->details->permission_combos; l != NULL; l = l->next) { +		combo = l->data; + +		if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo),  &iter)) { +			continue; +		} + +		type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "permission-type")); +		is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), +								"is-folder")); + +		model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo)); +		gtk_tree_model_get (model, &iter, 1, &new_perm, 2, &use_original, -1); +		if (use_original) { +			continue; +		} +		vfs_new_perm = permission_to_vfs (type, new_perm); + +		if (is_folder) { +			mask = PERMISSION_READ|PERMISSION_WRITE|PERMISSION_EXEC; +		} else { +			mask = PERMISSION_READ|PERMISSION_WRITE; +		} +		vfs_mask = permission_to_vfs (type, mask); + +		if (is_folder) { +			dir_permission_mask |= vfs_mask; +			dir_permission |= vfs_new_perm; +		} else { +			file_permission_mask |= vfs_mask; +			file_permission |= vfs_new_perm; +		} +	} + +	for (l = window->details->target_files; l != NULL; l = l->next) { +		CajaFile *file; +		char *uri; + +		file = CAJA_FILE (l->data); + +		if (caja_file_is_directory (file) && +		    caja_file_can_set_permissions (file)) { +			uri = caja_file_get_uri (file); +			start_long_operation (window); +			g_object_ref (window); +			caja_file_set_permissions_recursive (uri, +								 file_permission, +								 file_permission_mask, +								 dir_permission, +								 dir_permission_mask, +								 set_recursive_permissions_done, +								 window); +			g_free (uri); +		} +	} +} + +static void +create_permissions_page (FMPropertiesWindow *window) +{ +	GtkWidget *vbox, *button, *hbox; +	GtkTable *page_table; +	char *file_name, *prompt_text; +	GList *file_list; +	guint last_row; +	gint nrows; + +	vbox = create_page_with_vbox (window->details->notebook, +				      _("Permissions")); + +	file_list = window->details->original_files; + +	window->details->initial_permissions = NULL; + +	if (all_can_get_permissions (file_list) && all_can_get_permissions (window->details->target_files)) { +		window->details->initial_permissions = get_initial_permissions (window->details->target_files); +		window->details->has_recursive_apply = files_has_changable_permissions_directory (window); + +		if (!all_can_set_permissions (file_list)) { +			add_prompt_and_separator ( +				GTK_VBOX (vbox), +				_("You are not the owner, so you cannot change these permissions.")); +		} + +		page_table = GTK_TABLE (gtk_table_new (1, COLUMN_COUNT, FALSE)); +		window->details->permissions_table = page_table; + +		apply_standard_table_padding (page_table); +		gtk_widget_show (GTK_WIDGET (page_table)); +		gtk_box_pack_start (GTK_BOX (vbox), +				    GTK_WIDGET (page_table), +				    TRUE, TRUE, 0); + +		if (eel_preferences_get_boolean (CAJA_PREFERENCES_SHOW_ADVANCED_PERMISSIONS)) { +			window->details->advanced_permissions = TRUE; +			create_advanced_permissions (window, page_table); +		} else { +			window->details->advanced_permissions = FALSE; +			create_simple_permissions (window, page_table); +		} + +		g_object_get (page_table, "n-rows", &nrows, NULL); +		gtk_table_set_row_spacing (page_table, nrows - 1, 18); + +#ifdef HAVE_SELINUX +		append_title_value_pair +			(window, page_table, _("SELinux context:"), +			 "selinux_context", INCONSISTENT_STATE_STRING, +			 FALSE); +#endif +		append_title_value_pair +			(window, page_table, _("Last changed:"), +			 "date_permissions", INCONSISTENT_STATE_STRING, +			 FALSE); + +		if (window->details->has_recursive_apply) { +			last_row = append_row (page_table); +			hbox = gtk_hbox_new (FALSE, 0); +			gtk_widget_show (hbox); +			gtk_table_attach (page_table, hbox, +					  0, 2, +					  last_row, last_row+1, +					  GTK_FILL, 0, +					  0, 0); + +			button = gtk_button_new_with_mnemonic (_("Apply Permissions to Enclosed Files")); +			gtk_widget_show (button); +			gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0); +			g_signal_connect (button, "clicked", +					  G_CALLBACK (apply_recursive_clicked), +					  window); +		} +	} else { +		if (!is_multi_file_window (window)) { +			file_name = caja_file_get_display_name (get_target_file (window)); +			prompt_text = g_strdup_printf (_("The permissions of \"%s\" could not be determined."), file_name); +			g_free (file_name); +		} else { +			prompt_text = g_strdup (_("The permissions of the selected file could not be determined.")); +		} + +		add_prompt (GTK_VBOX (vbox), prompt_text, TRUE); +		g_free (prompt_text); +	} +} + +static void +append_extension_pages (FMPropertiesWindow *window) +{ +	GList *providers; +	GList *p; + + 	providers = caja_module_get_extensions_for_type (CAJA_TYPE_PROPERTY_PAGE_PROVIDER); + +	for (p = providers; p != NULL; p = p->next) { +		CajaPropertyPageProvider *provider; +		GList *pages; +		GList *l; + +		provider = CAJA_PROPERTY_PAGE_PROVIDER (p->data); + +		pages = caja_property_page_provider_get_pages +			(provider, window->details->original_files); + +		for (l = pages; l != NULL; l = l->next) { +			CajaPropertyPage *page; +			GtkWidget *page_widget; +			GtkWidget *label; + +			page = CAJA_PROPERTY_PAGE (l->data); + +			g_object_get (G_OBJECT (page), +				      "page", &page_widget, "label", &label, +				      NULL); + +			gtk_notebook_append_page (window->details->notebook, +						  page_widget, label); + +			g_object_set_data (G_OBJECT (page_widget), +					   "is-extension-page", +					   page); + +			g_object_unref (page_widget); +			g_object_unref (label); + +			g_object_unref (page); +		} + +		g_list_free (pages); +	} + +	caja_module_extension_list_free (providers); +} + +static gboolean +should_show_emblems (FMPropertiesWindow *window) +{ +	/* FIXME bugzilla.gnome.org 45643: +	 * Emblems aren't displayed on the the desktop Trash icon, so +	 * we shouldn't pretend that they work by showing them here. +	 * When bug 5643 is fixed we can remove this case. +	 */ +	if (!is_multi_file_window (window) +	    && is_merged_trash_directory (get_target_file (window))) { +		return FALSE; +	} + +	return TRUE; +} + +static gboolean +should_show_permissions (FMPropertiesWindow *window) +{ +	CajaFile *file; + +	file = get_target_file (window); + +	/* Don't show permissions for Trash and Computer since they're not +	 * really file system objects. +	 */ +	if (!is_multi_file_window (window) +	    && (is_merged_trash_directory (file) || +		is_computer_directory (file))) { +		return FALSE; +	} + +	return TRUE; +} + +static char * +get_pending_key (GList *file_list) +{ +	GList *l; +	GList *uris; +	GString *key; +	char *ret; + +	uris = NULL; +	for (l = file_list; l != NULL; l = l->next) { +		uris = g_list_prepend (uris, caja_file_get_uri (CAJA_FILE (l->data))); +	} +	uris = g_list_sort (uris, (GCompareFunc)strcmp); + +	key = g_string_new (""); +	for (l = uris; l != NULL; l = l->next) { +		g_string_append (key, l->data); +		g_string_append (key, ";"); +	} + +	eel_g_list_free_deep (uris); + +	ret = key->str; +	g_string_free (key, FALSE); + +	return ret; +} + +static StartupData * +startup_data_new (GList *original_files, +		  GList *target_files, +		  const char *pending_key, +		  GtkWidget *parent_widget) +{ +	StartupData *data; +	GList *l; + +	data = g_new0 (StartupData, 1); +	data->original_files = caja_file_list_copy (original_files); +	data->target_files = caja_file_list_copy (target_files); +	data->parent_widget = parent_widget; +	data->pending_key = g_strdup (pending_key); +	data->pending_files = g_hash_table_new (g_direct_hash, +						g_direct_equal); + +	for (l = data->target_files; l != NULL; l = l->next) { +		g_hash_table_insert (data->pending_files, l->data, l->data); +	} + +	return data; +} + +static void +startup_data_free (StartupData *data) +{ +	caja_file_list_free (data->original_files); +	caja_file_list_free (data->target_files); +	g_hash_table_destroy (data->pending_files); +	g_free (data->pending_key); +	g_free (data); +} + +static void +file_changed_callback (CajaFile *file, gpointer user_data) +{ +	FMPropertiesWindow *window = FM_PROPERTIES_WINDOW (user_data); + +	if (!g_list_find (window->details->changed_files, file)) { +		caja_file_ref (file); +		window->details->changed_files = g_list_prepend (window->details->changed_files, file); + +		schedule_files_update (window); +	} +} + +static gboolean +is_a_special_file (CajaFile *file) +{ +	if (file == NULL || +	    CAJA_IS_DESKTOP_ICON_FILE (file) || +	    caja_file_is_caja_link (file) || +	    is_merged_trash_directory (file) || +	    is_computer_directory (file)) { +		return TRUE; +	} +	return FALSE; +} + +static gboolean +should_show_open_with (FMPropertiesWindow *window) +{ +	CajaFile *file; + +	/* Don't show open with tab for desktop special icons (trash, etc) +	 * or desktop files. We don't get the open-with menu for these anyway. +	 * +	 * Also don't show it for folders. Changing the default app for folders +	 * leads to all sort of hard to understand errors. +	 */ + +	if (is_multi_file_window (window)) { +		if (!file_list_attributes_identical (window->details->original_files, +						     "mime_type")) { +			return FALSE; +		} else { + +			GList *l; + +			for (l = window->details->original_files; l; l = l->next) { +				file = CAJA_FILE (l->data); +				if (caja_file_is_directory (file) || +				    is_a_special_file (file)) { +					return FALSE; +				} +			} +		} +	} else { +		file = get_original_file (window); +		if (caja_file_is_directory (file) || +		    is_a_special_file (file)) { +			return FALSE; +		} +	} +	return TRUE; +} + +static void +create_open_with_page (FMPropertiesWindow *window) +{ +	GtkWidget *vbox; +	char *mime_type; +	char *uri; + +	mime_type = caja_file_get_mime_type (get_target_file (window)); + +	if (!is_multi_file_window (window)) { +		uri = caja_file_get_uri (get_target_file (window)); +		if (uri == NULL) { +			return; +		} +		vbox = caja_mime_application_chooser_new (uri, mime_type); + +		g_free (uri); +	} else { +		GList *uris; + +		uris = window->details->original_files; +		if (uris == NULL) { +			return; +		} +		vbox = caja_mime_application_chooser_new_for_multiple_files (uris, mime_type); +	} + +	gtk_widget_show (vbox); +	g_free (mime_type); + +	gtk_notebook_append_page (window->details->notebook, +				  vbox, gtk_label_new (_("Open With"))); +} + + +static FMPropertiesWindow * +create_properties_window (StartupData *startup_data) +{ +	FMPropertiesWindow *window; +	GList *l; + +	window = FM_PROPERTIES_WINDOW (gtk_widget_new (fm_properties_window_get_type (), NULL)); + +	window->details->original_files = caja_file_list_copy (startup_data->original_files); + +	window->details->target_files = caja_file_list_copy (startup_data->target_files); + +	gtk_window_set_wmclass (GTK_WINDOW (window), "file_properties", "Caja"); +	gtk_window_set_screen (GTK_WINDOW (window), +			       gtk_widget_get_screen (startup_data->parent_widget)); + +	gtk_window_set_type_hint (GTK_WINDOW (window), GDK_WINDOW_TYPE_HINT_DIALOG); + +	/* Set initial window title */ +	update_properties_window_title (window); + +	/* Start monitoring the file attributes we display. Note that some +	 * of the attributes are for the original file, and some for the +	 * target files. +	 */ + +	for (l = window->details->original_files; l != NULL; l = l->next) { +		CajaFile *file; +		CajaFileAttributes attributes; + +		file = CAJA_FILE (l->data); + +		attributes = +			CAJA_FILE_ATTRIBUTES_FOR_ICON | +			CAJA_FILE_ATTRIBUTE_INFO | +			CAJA_FILE_ATTRIBUTE_LINK_INFO; + +		caja_file_monitor_add (CAJA_FILE (l->data), +					   &window->details->original_files, +					   attributes); +	} + +	for (l = window->details->target_files; l != NULL; l = l->next) { +		CajaFile *file; +		CajaFileAttributes attributes; + +		file = CAJA_FILE (l->data); + +		attributes = 0; +		if (caja_file_is_directory (file)) { +			attributes |= CAJA_FILE_ATTRIBUTE_DEEP_COUNTS; +		} + +		attributes |= CAJA_FILE_ATTRIBUTE_INFO; +		caja_file_monitor_add (file, &window->details->target_files, attributes); +	} + +	for (l = window->details->target_files; l != NULL; l = l->next) { +		g_signal_connect_object (CAJA_FILE (l->data), +					 "changed", +					 G_CALLBACK (file_changed_callback), +					 G_OBJECT (window), +					 0); +	} + +	for (l = window->details->original_files; l != NULL; l = l->next) { +		g_signal_connect_object (CAJA_FILE (l->data), +					 "changed", +					 G_CALLBACK (file_changed_callback), +					 G_OBJECT (window), +					 0); +	} + +	/* Create the notebook tabs. */ +	window->details->notebook = GTK_NOTEBOOK (gtk_notebook_new ()); +	gtk_widget_show (GTK_WIDGET (window->details->notebook)); +	gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (window))), +			    GTK_WIDGET (window->details->notebook), +			    TRUE, TRUE, 0); + +	/* Create the pages. */ +	create_basic_page (window); + +	if (should_show_emblems (window)) { +		create_emblems_page (window); +	} + +	if (should_show_permissions (window)) { +		create_permissions_page (window); +	} + +	if (should_show_open_with (window)) { +		create_open_with_page (window); +	} + +	/* append pages from available views */ +	append_extension_pages (window); + +	gtk_dialog_add_buttons (GTK_DIALOG (window), +				GTK_STOCK_HELP, GTK_RESPONSE_HELP, +				GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, +				NULL); + +	/* FIXME - HIGificiation, should be done inside GTK+ */ +	gtk_widget_ensure_style (GTK_WIDGET (window)); +	gtk_container_set_border_width (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (window))), 12); +	gtk_container_set_border_width (GTK_CONTAINER (gtk_dialog_get_action_area (GTK_DIALOG (window))), 0); +	gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (window))), 12); +	gtk_dialog_set_has_separator (GTK_DIALOG (window), FALSE); + +	/* Update from initial state */ +	properties_window_update (window, NULL); + +	return window; +} + +static GList * +get_target_file_list (GList *original_files) +{ +	GList *ret; +	GList *l; + +	ret = NULL; + +	for (l = original_files; l != NULL; l = l->next) { +		CajaFile *target; + +		target = get_target_file_for_original_file (CAJA_FILE (l->data)); + +		ret = g_list_prepend (ret, target); +	} + +	ret = g_list_reverse (ret); + +	return ret; +} + +static void +add_window (FMPropertiesWindow *window) +{ +	if (!is_multi_file_window (window)) { +		g_hash_table_insert (windows, +				     get_original_file (window), +				     window); +		g_object_set_data (G_OBJECT (window), "window_key", +				   get_original_file (window)); +	} +} + +static void +remove_window (FMPropertiesWindow *window) +{ +	gpointer key; + +	key = g_object_get_data (G_OBJECT (window), "window_key"); +	if (key) { +		g_hash_table_remove (windows, key); +	} +} + +static GtkWindow * +get_existing_window (GList *file_list) +{ +	if (!file_list->next) { +		return g_hash_table_lookup (windows, file_list->data); +	} + +	return NULL; +} + +static void +cancel_create_properties_window_callback (gpointer callback_data) +{ +	remove_pending ((StartupData *)callback_data, TRUE, FALSE, TRUE); +} + +static void +parent_widget_destroyed_callback (GtkWidget *widget, gpointer callback_data) +{ +	g_assert (widget == ((StartupData *)callback_data)->parent_widget); + +	remove_pending ((StartupData *)callback_data, TRUE, TRUE, FALSE); +} + +static void +cancel_call_when_ready_callback (gpointer key, +				 gpointer value, +				 gpointer user_data) +{ +	caja_file_cancel_call_when_ready +		(CAJA_FILE (key), +		 is_directory_ready_callback, +		 user_data); +} + +static void +remove_pending (StartupData *startup_data, +		gboolean cancel_call_when_ready, +		gboolean cancel_timed_wait, +		gboolean cancel_destroy_handler) +{ +	if (cancel_call_when_ready) { +		g_hash_table_foreach (startup_data->pending_files, +				      cancel_call_when_ready_callback, +				      startup_data); + +	} +	if (cancel_timed_wait) { +		eel_timed_wait_stop +			(cancel_create_properties_window_callback, startup_data); +	} +	if (cancel_destroy_handler) { +		g_signal_handlers_disconnect_by_func (startup_data->parent_widget, +						      G_CALLBACK (parent_widget_destroyed_callback), +						      startup_data); +	} + +	g_hash_table_remove (pending_lists, startup_data->pending_key); + +	startup_data_free (startup_data); +} + +static void +is_directory_ready_callback (CajaFile *file, +			     gpointer data) +{ +	StartupData *startup_data; + +	startup_data = data; + +	g_hash_table_remove (startup_data->pending_files, file); + +	if (g_hash_table_size (startup_data->pending_files) == 0) { +		FMPropertiesWindow *new_window; + +		new_window = create_properties_window (startup_data); + +		add_window (new_window); + +		remove_pending (startup_data, FALSE, TRUE, TRUE); + +/* FIXME bugzilla.gnome.org 42151: + * See comment elsewhere in this file about bug 2151. + */ +#ifdef UNDO_ENABLED +		caja_undo_share_undo_manager (GTK_OBJECT (new_window), +						  GTK_OBJECT (callback_data)); +#endif +		gtk_window_present (GTK_WINDOW (new_window)); +	} +} + + +void +fm_properties_window_present (GList *original_files, +			      GtkWidget *parent_widget) +{ +	GList *l, *next; +	GtkWidget *parent_window; +	StartupData *startup_data; +	GList *target_files; +	GtkWindow *existing_window; +	char *pending_key; + +	g_return_if_fail (original_files != NULL); +	g_return_if_fail (GTK_IS_WIDGET (parent_widget)); + +	/* Create the hash tables first time through. */ +	if (windows == NULL) { +		windows = eel_g_hash_table_new_free_at_exit +			(NULL, NULL, "property windows"); +	} + +	if (pending_lists == NULL) { +		pending_lists = eel_g_hash_table_new_free_at_exit +			(g_str_hash, g_str_equal, "pending property window files"); +	} + +	/* Look to see if there's already a window for this file. */ +	existing_window = get_existing_window (original_files); +	if (existing_window != NULL) { +		gtk_window_set_screen (existing_window, +				       gtk_widget_get_screen (parent_widget)); +		gtk_window_present (existing_window); +		return; +	} + + +	pending_key = get_pending_key (original_files); + +	/* Look to see if we're already waiting for a window for this file. */ +	if (g_hash_table_lookup (pending_lists, pending_key) != NULL) { +		return; +	} + +	target_files = get_target_file_list (original_files); + +	startup_data = startup_data_new (original_files, +					 target_files, +					 pending_key, +					 parent_widget); + +	caja_file_list_free (target_files); +	g_free(pending_key); + +	/* Wait until we can tell whether it's a directory before showing, since +	 * some one-time layout decisions depend on that info. +	 */ + +	g_hash_table_insert (pending_lists, startup_data->pending_key, startup_data->pending_key); +	g_signal_connect (parent_widget, "destroy", +			  G_CALLBACK (parent_widget_destroyed_callback), startup_data); + +	parent_window = gtk_widget_get_ancestor (parent_widget, GTK_TYPE_WINDOW); + +	eel_timed_wait_start +		(cancel_create_properties_window_callback, +		 startup_data, +		 _("Creating Properties window."), +		 parent_window == NULL ? NULL : GTK_WINDOW (parent_window)); + + +	for (l = startup_data->target_files; l != NULL; l = next) { +		next = l->next; +		caja_file_call_when_ready +			(CAJA_FILE (l->data), +			 CAJA_FILE_ATTRIBUTE_INFO, +			 is_directory_ready_callback, +			 startup_data); +	} +} + +static void +real_response (GtkDialog *dialog, +	       int        response) +{ +	GError *error = NULL; + +	switch (response) { +	case GTK_RESPONSE_HELP: +		gtk_show_uri (gtk_window_get_screen (GTK_WINDOW (dialog)), +			      "ghelp:user-guide#goscaja-51", +			      gtk_get_current_event_time (), +			      &error); +		if (error != NULL) { +			eel_show_error_dialog (_("There was an error displaying help."), error->message, +					       GTK_WINDOW (dialog)); +			g_error_free (error); +		} +		break; + +	case GTK_RESPONSE_NONE: +	case GTK_RESPONSE_CLOSE: +	case GTK_RESPONSE_DELETE_EVENT: +		gtk_widget_destroy (GTK_WIDGET (dialog)); +		break; + +	default: +		g_assert_not_reached (); +		break; +	} +} + +static void +real_destroy (GtkObject *object) +{ +	FMPropertiesWindow *window; +	GList *l; + +	window = FM_PROPERTIES_WINDOW (object); + +	remove_window (window); + +	for (l = window->details->original_files; l != NULL; l = l->next) { +		caja_file_monitor_remove (CAJA_FILE (l->data), &window->details->original_files); +	} +	caja_file_list_free (window->details->original_files); +	window->details->original_files = NULL; + +	for (l = window->details->target_files; l != NULL; l = l->next) { +		caja_file_monitor_remove (CAJA_FILE (l->data), &window->details->target_files); +	} +	caja_file_list_free (window->details->target_files); +	window->details->target_files = NULL; + +	caja_file_list_free (window->details->changed_files); +	window->details->changed_files = NULL; + +	window->details->name_field = NULL; + +	g_list_free (window->details->emblem_buttons); +	window->details->emblem_buttons = NULL; + +	if (window->details->initial_emblems) { +		g_hash_table_destroy (window->details->initial_emblems); +		window->details->initial_emblems = NULL; +	} + +	g_list_free (window->details->permission_buttons); +	window->details->permission_buttons = NULL; + +	g_list_free (window->details->permission_combos); +	window->details->permission_combos = NULL; + +	if (window->details->initial_permissions) { +		g_hash_table_destroy (window->details->initial_permissions); +		window->details->initial_permissions = NULL; +	} + +	g_list_free (window->details->value_fields); +	window->details->value_fields = NULL; + +	if (window->details->update_directory_contents_timeout_id != 0) { +		g_source_remove (window->details->update_directory_contents_timeout_id); +		window->details->update_directory_contents_timeout_id = 0; +	} + +	if (window->details->update_files_timeout_id != 0) { +		g_source_remove (window->details->update_files_timeout_id); +		window->details->update_files_timeout_id = 0; +	} + +	GTK_OBJECT_CLASS (parent_class)->destroy (object); +} + +static void +real_finalize (GObject *object) +{ +	FMPropertiesWindow *window; + +	window = FM_PROPERTIES_WINDOW (object); + +	eel_g_list_free_deep (window->details->mime_list); + +	g_free (window->details->pending_name); +	g_free (window->details); + +	G_OBJECT_CLASS (parent_class)->finalize (object); +} + +/* converts + *  file://foo/foobar/foofoo/bar + * to + *  foofoo/bar + * if + *  file://foo/foobar + * is the parent + * + * It does not resolve any symlinks. + * */ +static char * +make_relative_uri_from_full (const char *uri, +			     const char *base_uri) +{ +	g_assert (uri != NULL); +	g_assert (base_uri != NULL); + +	if (g_str_has_prefix (uri, base_uri)) { +		uri += strlen (base_uri); +		if (*uri != '/') { +			return NULL; +		} + +		while (*uri == '/') { +			uri++; +		} + +		if (*uri != '\0') { +			return g_strdup (uri); +		} +	} + +	return NULL; +} + +/* icon selection callback to set the image of the file object to the selected file */ +static void +set_icon (const char* icon_uri, FMPropertiesWindow *properties_window) +{ +	CajaFile *file; +	char *file_uri; +	char *icon_path; +	char *real_icon_uri; + +	g_assert (icon_uri != NULL); +	g_assert (FM_IS_PROPERTIES_WINDOW (properties_window)); + +	icon_path = g_filename_from_uri (icon_uri, NULL, NULL); +	/* we don't allow remote URIs */ +	if (icon_path != NULL) { +		GList *l; + +		for (l = properties_window->details->original_files; l != NULL; l = l->next) { +			file = CAJA_FILE (l->data); + +			file_uri = caja_file_get_uri (file); + +			if (caja_file_is_mime_type (file, "application/x-desktop")) { +				if (caja_link_local_set_icon (file_uri, icon_path)) { +					caja_file_invalidate_attributes (file, +									     CAJA_FILE_ATTRIBUTE_INFO | +									     CAJA_FILE_ATTRIBUTE_LINK_INFO); +				} +			} else { +				real_icon_uri = make_relative_uri_from_full (icon_uri, file_uri); +				if (real_icon_uri == NULL) { +					real_icon_uri = g_strdup (icon_uri); +				} + +				caja_file_set_metadata (file, CAJA_METADATA_KEY_CUSTOM_ICON, NULL, real_icon_uri); +				caja_file_set_metadata (file, CAJA_METADATA_KEY_ICON_SCALE, NULL, NULL); + +				g_free (real_icon_uri); +			} + +			g_free (file_uri); +		} + +		g_free (icon_path); +	} +} + +static void +update_preview_callback (GtkFileChooser *icon_chooser, +			 FMPropertiesWindow *window) +{ +	GtkWidget *preview_widget; +	GdkPixbuf *pixbuf, *scaled_pixbuf; +	char *filename; +	double scale; + +	pixbuf = NULL; + +	filename = gtk_file_chooser_get_filename (icon_chooser); +	if (filename != NULL) { +		pixbuf = gdk_pixbuf_new_from_file (filename, NULL); +	} + +	if (pixbuf != NULL) { +		preview_widget = gtk_file_chooser_get_preview_widget (icon_chooser); +		gtk_file_chooser_set_preview_widget_active (icon_chooser, TRUE); + +		if (gdk_pixbuf_get_width (pixbuf) > PREVIEW_IMAGE_WIDTH) { +			scale = (double)gdk_pixbuf_get_height (pixbuf) / +				gdk_pixbuf_get_width (pixbuf); + +			scaled_pixbuf = mate_desktop_thumbnail_scale_down_pixbuf +				(pixbuf, +				 PREVIEW_IMAGE_WIDTH, +				 scale * PREVIEW_IMAGE_WIDTH); +			g_object_unref (pixbuf); +			pixbuf = scaled_pixbuf; +		} + +		gtk_image_set_from_pixbuf (GTK_IMAGE (preview_widget), pixbuf); +	} else { +		gtk_file_chooser_set_preview_widget_active (icon_chooser, FALSE); +	} + +	g_free (filename); + +	if (pixbuf != NULL) { +		g_object_unref (pixbuf); +	} +} + +static void +custom_icon_file_chooser_response_cb (GtkDialog *dialog, +				      gint response, +				      FMPropertiesWindow *window) +{ +	char *uri; + +	switch (response) { +	case GTK_RESPONSE_NO: +		reset_icon (window); +		break; + +	case GTK_RESPONSE_OK: +		uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog)); +		set_icon (uri, window); +		g_free (uri); +		break; + +	default: +		break; +	} + +	gtk_widget_hide (GTK_WIDGET (dialog)); +} + +static void +select_image_button_callback (GtkWidget *widget, +			      FMPropertiesWindow *window) +{ +	GtkWidget *dialog, *preview; +	GtkFileFilter *filter; +	GList *l; +	CajaFile *file; +	char *uri; +	char *image_path; +	gboolean revert_is_sensitive; + +	g_assert (FM_IS_PROPERTIES_WINDOW (window)); + +	dialog = window->details->icon_chooser; + +	if (dialog == NULL) { +		dialog = gtk_file_chooser_dialog_new (_("Select Custom Icon"), GTK_WINDOW (window), +						      GTK_FILE_CHOOSER_ACTION_OPEN, +						      GTK_STOCK_REVERT_TO_SAVED, GTK_RESPONSE_NO, +						      GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, +						      GTK_STOCK_OPEN, GTK_RESPONSE_OK, +						      NULL); +		gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (dialog), "/usr/share/pixmaps", NULL); +		gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE); + +		filter = gtk_file_filter_new (); +		gtk_file_filter_add_pixbuf_formats (filter); +		gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), filter); + +		preview = gtk_image_new (); +		gtk_widget_set_size_request (preview, PREVIEW_IMAGE_WIDTH, -1); +		gtk_file_chooser_set_preview_widget (GTK_FILE_CHOOSER (dialog), preview); +		gtk_file_chooser_set_use_preview_label (GTK_FILE_CHOOSER (dialog), FALSE); +		gtk_file_chooser_set_preview_widget_active (GTK_FILE_CHOOSER (dialog), FALSE); + +		g_signal_connect (dialog, "update-preview", +				  G_CALLBACK (update_preview_callback), window); + +		window->details->icon_chooser = dialog; + +		g_object_add_weak_pointer (G_OBJECT (dialog), +					   (gpointer *) &window->details->icon_chooser); +	} + +	/* it's likely that the user wants to pick an icon that is inside a local directory */ +	if (g_list_length (window->details->original_files) == 1) { +		file = CAJA_FILE (window->details->original_files->data); + +		if (caja_file_is_directory (file)) { +			uri = caja_file_get_uri (file); + +			image_path = g_filename_from_uri (uri, NULL, NULL); +			if (image_path != NULL) { +				gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), image_path); +				g_free (image_path); +			} + +			g_free (uri); +		} +	} + +	revert_is_sensitive = FALSE; +	for (l = window->details->original_files; l != NULL; l = l->next) { +		file = CAJA_FILE (l->data); +		image_path = caja_file_get_metadata (file, CAJA_METADATA_KEY_CUSTOM_ICON, NULL); +		revert_is_sensitive = (image_path != NULL); +		g_free (image_path); + +		if (revert_is_sensitive) { +			break; +		} +	} +	gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_NO, revert_is_sensitive); + +	g_signal_connect (dialog, "response", +			  G_CALLBACK (custom_icon_file_chooser_response_cb), window); +	gtk_widget_show (dialog); +} + +static void +fm_properties_window_class_init (FMPropertiesWindowClass *class) +{ +	GtkBindingSet *binding_set; + +	G_OBJECT_CLASS (class)->finalize = real_finalize; +	GTK_OBJECT_CLASS (class)->destroy = real_destroy; +	GTK_DIALOG_CLASS (class)->response = real_response; + +	binding_set = gtk_binding_set_by_class (class); +	gtk_binding_entry_add_signal (binding_set, GDK_Escape, 0, +				      "close", 0); +} + +static void +fm_properties_window_init (FMPropertiesWindow *window) +{ +	window->details = g_new0 (FMPropertiesWindowDetails, 1); +} diff --git a/src/file-manager/fm-properties-window.h b/src/file-manager/fm-properties-window.h new file mode 100644 index 00000000..6044e8b9 --- /dev/null +++ b/src/file-manager/fm-properties-window.h @@ -0,0 +1,69 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-properties-window.h - interface for window that lets user modify +                            icon properties + +   Copyright (C) 2000 Eazel, Inc. + +   The Mate Library is free software; you can redistribute it and/or +   modify it under the terms of the GNU Library General Public License as +   published by the Free Software Foundation; either version 2 of the +   License, or (at your option) any later version. + +   The Mate Library is distributed in the hope that it will be useful, +   but WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +   Library General Public License for more details. + +   You should have received a copy of the GNU Library General Public +   License along with the Mate Library; see the file COPYING.LIB.  If not, +   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +   Boston, MA 02111-1307, USA. + +   Authors: Darin Adler <[email protected]> +*/ + +#ifndef FM_PROPERTIES_WINDOW_H +#define FM_PROPERTIES_WINDOW_H + +#include <gtk/gtk.h> +#include <libcaja-private/caja-file.h> + +typedef struct FMPropertiesWindow FMPropertiesWindow; + +#define FM_TYPE_PROPERTIES_WINDOW fm_properties_window_get_type() +#define FM_PROPERTIES_WINDOW(obj) \ +  (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_PROPERTIES_WINDOW, FMPropertiesWindow)) +#define FM_PROPERTIES_WINDOW_CLASS(klass) \ +  (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_PROPERTIES_WINDOW, FMPropertiesWindowClass)) +#define FM_IS_PROPERTIES_WINDOW(obj) \ +  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_PROPERTIES_WINDOW)) +#define FM_IS_PROPERTIES_WINDOW_CLASS(klass) \ +  (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_PROPERTIES_WINDOW)) +#define FM_PROPERTIES_WINDOW_GET_CLASS(obj) \ +  (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_PROPERTIES_WINDOW, FMPropertiesWindowClass)) + +typedef struct FMPropertiesWindowDetails FMPropertiesWindowDetails; + +struct FMPropertiesWindow +{ +    GtkDialog window; +    FMPropertiesWindowDetails *details; +}; + +struct FMPropertiesWindowClass +{ +    GtkDialogClass parent_class; + +    /* Keybinding signals */ +    void (* close)    (FMPropertiesWindow *window); +}; + +typedef struct FMPropertiesWindowClass FMPropertiesWindowClass; + +GType   fm_properties_window_get_type   (void); + +void 	fm_properties_window_present 	(GList *files, +        GtkWidget *parent_widget); + +#endif /* FM_PROPERTIES_WINDOW_H */ diff --git a/src/file-manager/fm-tree-model.c b/src/file-manager/fm-tree-model.c new file mode 100644 index 00000000..6356db90 --- /dev/null +++ b/src/file-manager/fm-tree-model.c @@ -0,0 +1,2152 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* + * Copyright C) 2000, 2001 Eazel, Inc + * Copyright (C) 2002 Anders Carlsson + * Copyright (C) 2002 Bent Spoon Software + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Anders Carlsson <[email protected]> + *          Darin Adler <[email protected]> + */ + +/* fm-tree-model.c - model for the tree view */ + +#include <config.h> +#include "fm-tree-model.h" + +#include <eel/eel-glib-extensions.h> +#include <eel/eel-gdk-pixbuf-extensions.h> +#include <glib/gi18n.h> +#include <libcaja-private/caja-directory.h> +#include <libcaja-private/caja-file-attributes.h> +#include <libcaja-private/caja-file.h> +#include <gtk/gtk.h> +#include <string.h> + +enum +{ +    ROW_LOADED, +    LAST_SIGNAL +}; + +static guint tree_model_signals[LAST_SIGNAL] = { 0 }; + +typedef gboolean (* FilePredicate) (CajaFile *); + +/* The user_data of the GtkTreeIter is the TreeNode pointer. + * It's NULL for the dummy node. If it's NULL, then user_data2 + * is the TreeNode pointer to the parent. + */ + +typedef struct TreeNode TreeNode; +typedef struct FMTreeModelRoot FMTreeModelRoot; + +struct TreeNode +{ +    /* part of this node for the file itself */ +    int ref_count; + +    CajaFile *file; +    char *display_name; +    GIcon *icon; +    GMount *mount; +    GdkPixbuf *closed_pixbuf; +    GdkPixbuf *open_pixbuf; +    GdkPixbuf *emblem_pixbuf; + +    FMTreeModelRoot *root; + +    TreeNode *parent; +    TreeNode *next; +    TreeNode *prev; + +    /* part of the node used only for directories */ +    int dummy_child_ref_count; +    int all_children_ref_count; + +    CajaDirectory *directory; +    guint done_loading_id; +    guint files_added_id; +    guint files_changed_id; + +    TreeNode *first_child; + +    /* misc. flags */ +    guint done_loading : 1; +    guint force_has_dummy : 1; +    guint inserted : 1; +}; + +struct FMTreeModelDetails +{ +    int stamp; + +    TreeNode *root_node; + +    guint monitoring_update_idle_id; + +    gboolean show_hidden_files; +    gboolean show_backup_files; +    gboolean show_only_directories; + +    GList *highlighted_files; +}; + +struct FMTreeModelRoot +{ +    FMTreeModel *model; + +    /* separate hash table for each root node needed */ +    GHashTable *file_to_node_map; + +    TreeNode *root_node; +}; + +typedef struct +{ +    CajaDirectory *directory; +    FMTreeModel *model; +} DoneLoadingParameters; + +static void fm_tree_model_tree_model_init (GtkTreeModelIface *iface); +static void schedule_monitoring_update     (FMTreeModel *model); +static void destroy_node_without_reporting (FMTreeModel *model, +        TreeNode          *node); +static void report_node_contents_changed   (FMTreeModel *model, +        TreeNode          *node); + +G_DEFINE_TYPE_WITH_CODE (FMTreeModel, fm_tree_model, G_TYPE_OBJECT, +                         G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL, +                                 fm_tree_model_tree_model_init)); + +static GtkTreeModelFlags +fm_tree_model_get_flags (GtkTreeModel *tree_model) +{ +    return GTK_TREE_MODEL_ITERS_PERSIST; +} + +static void +object_unref_if_not_NULL (gpointer object) +{ +    if (object == NULL) +    { +        return; +    } +    g_object_unref (object); +} + +static FMTreeModelRoot * +tree_model_root_new (FMTreeModel *model) +{ +    FMTreeModelRoot *root; + +    root = g_new0 (FMTreeModelRoot, 1); +    root->model = model; +    root->file_to_node_map = g_hash_table_new (NULL, NULL); + +    return root; +} + +static TreeNode * +tree_node_new (CajaFile *file, FMTreeModelRoot *root) +{ +    TreeNode *node; + +    node = g_new0 (TreeNode, 1); +    node->file = caja_file_ref (file); +    node->root = root; +    return node; +} + +static void +tree_node_unparent (FMTreeModel *model, TreeNode *node) +{ +    TreeNode *parent, *next, *prev; + +    parent = node->parent; +    next = node->next; +    prev = node->prev; + +    if (parent == NULL && +            node == model->details->root_node) +    { +        /* it's the first root node -> if there is a next then let it be the first root node */ +        model->details->root_node = next; +    } + +    if (next != NULL) +    { +        next->prev = prev; +    } +    if (prev == NULL && parent != NULL) +    { +        g_assert (parent->first_child == node); +        parent->first_child = next; +    } +    else if (prev != NULL) +    { +        prev->next = next; +    } + +    node->parent = NULL; +    node->next = NULL; +    node->prev = NULL; +    node->root = NULL; +} + +static void +tree_node_destroy (FMTreeModel *model, TreeNode *node) +{ +    g_assert (node->first_child == NULL); +    g_assert (node->ref_count == 0); + +    tree_node_unparent (model, node); + +    g_object_unref (node->file); +    g_free (node->display_name); +    object_unref_if_not_NULL (node->icon); +    object_unref_if_not_NULL (node->closed_pixbuf); +    object_unref_if_not_NULL (node->open_pixbuf); +    object_unref_if_not_NULL (node->emblem_pixbuf); + +    g_assert (node->done_loading_id == 0); +    g_assert (node->files_added_id == 0); +    g_assert (node->files_changed_id == 0); +    caja_directory_unref (node->directory); + +    g_free (node); +} + +static void +tree_node_parent (TreeNode *node, TreeNode *parent) +{ +    TreeNode *first_child; + +    g_assert (parent != NULL); +    g_assert (node->parent == NULL); +    g_assert (node->prev == NULL); +    g_assert (node->next == NULL); + +    first_child = parent->first_child; + +    node->parent = parent; +    node->root = parent->root; +    node->next = first_child; + +    if (first_child != NULL) +    { +        g_assert (first_child->prev == NULL); +        first_child->prev = node; +    } + +    parent->first_child = node; +} + +static GdkPixbuf * +get_menu_icon (GIcon *icon) +{ +    CajaIconInfo *info; +    GdkPixbuf *pixbuf; +    int size; + +    size = caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU); + +    info = caja_icon_info_lookup (icon, size); +    pixbuf = caja_icon_info_get_pixbuf_nodefault_at_size (info, size); +    g_object_unref (info); + +    return pixbuf; +} + +static GdkPixbuf * +get_menu_icon_for_file (TreeNode *node, +                        CajaFile *file, +                        CajaFileIconFlags flags) +{ +    CajaIconInfo *info; +    GdkPixbuf *pixbuf, *retval; +    gboolean highlight; +    int size; +    FMTreeModel *model; + +    size = caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU); + +    info = caja_file_get_icon (file, size, flags); +    retval = caja_icon_info_get_pixbuf_nodefault_at_size (info, size); +    model = node->root->model; + +    highlight = (g_list_find_custom (model->details->highlighted_files, +                                     file, (GCompareFunc) caja_file_compare_location) != NULL); + +    if (highlight) +    { +        pixbuf = eel_gdk_pixbuf_render (retval, 1, 255, 255, 0, 0); + +        if (pixbuf != NULL) +        { +            g_object_unref (retval); +            retval = pixbuf; +        } +    } + +    g_object_unref (info); + +    return retval; +} + +static GdkPixbuf * +tree_node_get_pixbuf (TreeNode *node, +                      CajaFileIconFlags flags) +{ +    if (node->parent == NULL) +    { +        return get_menu_icon (node->icon); +    } +    return get_menu_icon_for_file (node, node->file, flags); +} + +static gboolean +tree_node_update_pixbuf (TreeNode *node, +                         GdkPixbuf **pixbuf_storage, +                         CajaFileIconFlags flags) +{ +    GdkPixbuf *pixbuf; + +    if (*pixbuf_storage == NULL) +    { +        return FALSE; +    } +    pixbuf = tree_node_get_pixbuf (node, flags); +    if (pixbuf == *pixbuf_storage) +    { +        g_object_unref (pixbuf); +        return FALSE; +    } +    g_object_unref (*pixbuf_storage); +    *pixbuf_storage = pixbuf; +    return TRUE; +} + +static gboolean +tree_node_update_closed_pixbuf (TreeNode *node) +{ +    return tree_node_update_pixbuf (node, &node->closed_pixbuf, 0); +} + +static gboolean +tree_node_update_open_pixbuf (TreeNode *node) +{ +    return tree_node_update_pixbuf (node, &node->open_pixbuf, CAJA_FILE_ICON_FLAGS_FOR_OPEN_FOLDER); +} + +static GdkPixbuf * +tree_node_get_emblem_pixbuf_internal (TreeNode *node) +{ +    GdkPixbuf *pixbuf; +    GList *emblem_pixbufs; +    char *emblems_to_ignore[3]; +    int i; + +    i = 0; +    emblems_to_ignore[i++] = CAJA_FILE_EMBLEM_NAME_TRASH; + +    if (node->parent && node->parent->file) +    { +        if (!caja_file_can_write (node->parent->file)) +        { +            emblems_to_ignore[i++] = CAJA_FILE_EMBLEM_NAME_CANT_WRITE; +        } +    } + +    emblems_to_ignore[i++] = NULL; + +    emblem_pixbufs = caja_file_get_emblem_pixbufs (node->file, +                     caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU), +                     TRUE, +                     emblems_to_ignore); + + +    if (emblem_pixbufs != NULL) +    { +        pixbuf = g_object_ref (emblem_pixbufs->data); +    } +    else +    { +        pixbuf = NULL; +    } + +    eel_gdk_pixbuf_list_free (emblem_pixbufs); + +    return pixbuf; +} + +static gboolean +tree_node_update_emblem_pixbuf (TreeNode *node) +{ +    GdkPixbuf *pixbuf; + +    if (node->emblem_pixbuf == NULL) +    { +        return FALSE; +    } +    pixbuf = tree_node_get_emblem_pixbuf_internal (node); +    if (pixbuf == node->emblem_pixbuf) +    { +        g_object_unref (pixbuf); +        return FALSE; +    } +    g_object_unref (node->emblem_pixbuf); +    node->emblem_pixbuf = pixbuf; +    return TRUE; +} + +static gboolean +tree_node_update_display_name (TreeNode *node) +{ +    char *display_name; + +    if (node->display_name == NULL) +    { +        return FALSE; +    } +    /* don't update root node display names */ +    if (node->parent == NULL) +    { +        return FALSE; +    } +    display_name = caja_file_get_display_name (node->file); +    if (strcmp (display_name, node->display_name) == 0) +    { +        g_free (display_name); +        return FALSE; +    } +    g_free (node->display_name); +    node->display_name = NULL; +    return TRUE; +} + +static GdkPixbuf * +tree_node_get_closed_pixbuf (TreeNode *node) +{ +    if (node->closed_pixbuf == NULL) +    { +        node->closed_pixbuf = tree_node_get_pixbuf (node, 0); +    } +    return node->closed_pixbuf; +} + +static GdkPixbuf * +tree_node_get_open_pixbuf (TreeNode *node) +{ +    if (node->open_pixbuf == NULL) +    { +        node->open_pixbuf = tree_node_get_pixbuf (node, CAJA_FILE_ICON_FLAGS_FOR_OPEN_FOLDER); +    } +    return node->open_pixbuf; +} + +static GdkPixbuf * +tree_node_get_emblem_pixbuf (TreeNode *node) +{ +    if (node->emblem_pixbuf == NULL) +    { +        node->emblem_pixbuf = tree_node_get_emblem_pixbuf_internal (node); +    } +    return node->emblem_pixbuf; +} + +static const char * +tree_node_get_display_name (TreeNode *node) +{ +    if (node->display_name == NULL) +    { +        node->display_name = caja_file_get_display_name (node->file); +    } +    return node->display_name; +} + +static gboolean +tree_node_has_dummy_child (TreeNode *node) +{ +    return (node->directory != NULL +            && (!node->done_loading +                || node->first_child == NULL +                || node->force_has_dummy)) || +           /* Roots always have dummy nodes if directory isn't loaded yet */ +           (node->directory == NULL && node->parent == NULL); +} + +static int +tree_node_get_child_index (TreeNode *parent, TreeNode *child) +{ +    int i; +    TreeNode *node; + +    if (child == NULL) +    { +        g_assert (tree_node_has_dummy_child (parent)); +        return 0; +    } + +    i = tree_node_has_dummy_child (parent) ? 1 : 0; +    for (node = parent->first_child; node != NULL; node = node->next, i++) +    { +        if (child == node) +        { +            return i; +        } +    } + +    g_assert_not_reached (); +    return 0; +} + +static gboolean +make_iter_invalid (GtkTreeIter *iter) +{ +    iter->stamp = 0; +    iter->user_data = NULL; +    iter->user_data2 = NULL; +    iter->user_data3 = NULL; +    return FALSE; +} + +static gboolean +make_iter_for_node (TreeNode *node, GtkTreeIter *iter, int stamp) +{ +    if (node == NULL) +    { +        return make_iter_invalid (iter); +    } +    iter->stamp = stamp; +    iter->user_data = node; +    iter->user_data2 = NULL; +    iter->user_data3 = NULL; +    return TRUE; +} + +static gboolean +make_iter_for_dummy_row (TreeNode *parent, GtkTreeIter *iter, int stamp) +{ +    g_assert (tree_node_has_dummy_child (parent)); +    g_assert (parent != NULL); +    iter->stamp = stamp; +    iter->user_data = NULL; +    iter->user_data2 = parent; +    iter->user_data3 = NULL; +    return TRUE; +} + +static TreeNode * +get_node_from_file (FMTreeModelRoot *root, CajaFile *file) +{ +    return g_hash_table_lookup (root->file_to_node_map, file); +} + +static TreeNode * +get_parent_node_from_file (FMTreeModelRoot *root, CajaFile *file) +{ +    CajaFile *parent_file; +    TreeNode *parent_node; + +    parent_file = caja_file_get_parent (file); +    parent_node = get_node_from_file (root, parent_file); +    caja_file_unref (parent_file); +    return parent_node; +} + +static TreeNode * +create_node_for_file (FMTreeModelRoot *root, CajaFile *file) +{ +    TreeNode *node; + +    g_assert (get_node_from_file (root, file) == NULL); +    node = tree_node_new (file, root); +    g_hash_table_insert (root->file_to_node_map, node->file, node); +    return node; +} + +#ifdef LOG_REF_COUNTS + +static char * +get_node_uri (GtkTreeIter *iter) +{ +    TreeNode *node, *parent; +    char *parent_uri, *node_uri; + +    node = iter->user_data; +    if (node != NULL) +    { +        return caja_file_get_uri (node->file); +    } + +    parent = iter->user_data2; +    parent_uri = caja_file_get_uri (parent->file); +    node_uri = g_strconcat (parent_uri, " -- DUMMY", NULL); +    g_free (parent_uri); +    return node_uri; +} + +#endif + +static void +decrement_ref_count (FMTreeModel *model, TreeNode *node, int count) +{ +    node->all_children_ref_count -= count; +    if (node->all_children_ref_count == 0) +    { +        schedule_monitoring_update (model); +    } +} + +static void +abandon_node_ref_count (FMTreeModel *model, TreeNode *node) +{ +    if (node->parent != NULL) +    { +        decrement_ref_count (model, node->parent, node->ref_count); +#ifdef LOG_REF_COUNTS +        if (node->ref_count != 0) +        { +            char *uri; + +            uri = caja_file_get_uri (node->file); +            g_message ("abandoning %d ref of %s, count is now %d", +                       node->ref_count, uri, node->parent->all_children_ref_count); +            g_free (uri); +        } +#endif +    } +    node->ref_count = 0; +} + +static void +abandon_dummy_row_ref_count (FMTreeModel *model, TreeNode *node) +{ +    decrement_ref_count (model, node, node->dummy_child_ref_count); +    if (node->dummy_child_ref_count != 0) +    { +#ifdef LOG_REF_COUNTS +        char *uri; + +        uri = caja_file_get_uri (node->file); +        g_message ("abandoning %d ref of %s -- DUMMY, count is now %d", +                   node->dummy_child_ref_count, uri, node->all_children_ref_count); +        g_free (uri); +#endif +    } +    node->dummy_child_ref_count = 0; +} + +static void +report_row_inserted (FMTreeModel *model, GtkTreeIter *iter) +{ +    GtkTreePath *path; + +    path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter); +    gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, iter); +    gtk_tree_path_free (path); +} + +static void +report_row_contents_changed (FMTreeModel *model, GtkTreeIter *iter) +{ +    GtkTreePath *path; + +    path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter); +    gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, iter); +    gtk_tree_path_free (path); +} + +static void +report_row_has_child_toggled (FMTreeModel *model, GtkTreeIter *iter) +{ +    GtkTreePath *path; + +    path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter); +    gtk_tree_model_row_has_child_toggled (GTK_TREE_MODEL (model), path, iter); +    gtk_tree_path_free (path); +} + +static GtkTreePath * +get_node_path (FMTreeModel *model, TreeNode *node) +{ +    GtkTreeIter iter; + +    make_iter_for_node (node, &iter, model->details->stamp); +    return gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); +} + +static void +report_dummy_row_inserted (FMTreeModel *model, TreeNode *parent) +{ +    GtkTreeIter iter; + +    if (!parent->inserted) +    { +        return; +    } +    make_iter_for_dummy_row (parent, &iter, model->details->stamp); +    report_row_inserted (model, &iter); +} + +static void +report_dummy_row_deleted (FMTreeModel *model, TreeNode *parent) +{ +    GtkTreeIter iter; +    GtkTreePath *path; + +    if (parent->inserted) +    { +        make_iter_for_node (parent, &iter, model->details->stamp); +        path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); +        gtk_tree_path_append_index (path, 0); +        gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path); +        gtk_tree_path_free (path); +    } +    abandon_dummy_row_ref_count (model, parent); +} + +static void +report_node_inserted (FMTreeModel *model, TreeNode *node) +{ +    GtkTreeIter iter; + +    make_iter_for_node (node, &iter, model->details->stamp); +    report_row_inserted (model, &iter); +    node->inserted = TRUE; + +    if (tree_node_has_dummy_child (node)) +    { +        report_dummy_row_inserted (model, node); +    } + +    if (node->directory != NULL || +            node->parent == NULL) +    { +        report_row_has_child_toggled (model, &iter); +    } +} + +static void +report_node_contents_changed (FMTreeModel *model, TreeNode *node) +{ +    GtkTreeIter iter; + +    if (!node->inserted) +    { +        return; +    } +    make_iter_for_node (node, &iter, model->details->stamp); +    report_row_contents_changed (model, &iter); +} + +static void +report_node_has_child_toggled (FMTreeModel *model, TreeNode *node) +{ +    GtkTreeIter iter; + +    if (!node->inserted) +    { +        return; +    } +    make_iter_for_node (node, &iter, model->details->stamp); +    report_row_has_child_toggled (model, &iter); +} + +static void +report_dummy_row_contents_changed (FMTreeModel *model, TreeNode *parent) +{ +    GtkTreeIter iter; + +    if (!parent->inserted) +    { +        return; +    } +    make_iter_for_dummy_row (parent, &iter, model->details->stamp); +    report_row_contents_changed (model, &iter); +} + +static void +stop_monitoring_directory (FMTreeModel *model, TreeNode *node) +{ +    CajaDirectory *directory; + +    if (node->done_loading_id == 0) +    { +        g_assert (node->files_added_id == 0); +        g_assert (node->files_changed_id == 0); +        return; +    } + +    directory = node->directory; + +    g_signal_handler_disconnect (node->directory, node->done_loading_id); +    g_signal_handler_disconnect (node->directory, node->files_added_id); +    g_signal_handler_disconnect (node->directory, node->files_changed_id); + +    node->done_loading_id = 0; +    node->files_added_id = 0; +    node->files_changed_id = 0; + +    caja_directory_file_monitor_remove (node->directory, model); +} + +static void +destroy_children_without_reporting (FMTreeModel *model, TreeNode *parent) +{ +    while (parent->first_child != NULL) +    { +        destroy_node_without_reporting (model, parent->first_child); +    } +} + +static void +destroy_node_without_reporting (FMTreeModel *model, TreeNode *node) +{ +    abandon_node_ref_count (model, node); +    stop_monitoring_directory (model, node); +    node->inserted = FALSE; +    destroy_children_without_reporting (model, node); +    g_hash_table_remove (node->root->file_to_node_map, node->file); +    tree_node_destroy (model, node); +} + +static void +destroy_node (FMTreeModel *model, TreeNode *node) +{ +    TreeNode *parent; +    gboolean parent_had_dummy_child; +    GtkTreePath *path; + +    parent = node->parent; +    parent_had_dummy_child = tree_node_has_dummy_child (parent); + +    path = get_node_path (model, node); + +    /* Report row_deleted before actually deleting */ +    gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path); +    gtk_tree_path_free (path); + +    destroy_node_without_reporting (model, node); + +    if (tree_node_has_dummy_child (parent)) +    { +        if (!parent_had_dummy_child) +        { +            report_dummy_row_inserted (model, parent); +        } +    } +    else +    { +        g_assert (!parent_had_dummy_child); +    } +} + +static void +destroy_children (FMTreeModel *model, TreeNode *parent) +{ +    while (parent->first_child != NULL) +    { +        destroy_node (model, parent->first_child); +    } +} + +static void +destroy_children_by_function (FMTreeModel *model, TreeNode *parent, FilePredicate f) +{ +    TreeNode *child, *next; + +    for (child = parent->first_child; child != NULL; child = next) +    { +        next = child->next; +        if (f (child->file)) +        { +            destroy_node (model, child); +        } +        else +        { +            destroy_children_by_function (model, child, f); +        } +    } +} + +static void +destroy_by_function (FMTreeModel *model, FilePredicate f) +{ +    TreeNode *node; +    for (node = model->details->root_node; node != NULL; node = node->next) +    { +        destroy_children_by_function (model, node, f); +    } +} + +static gboolean +update_node_without_reporting (FMTreeModel *model, TreeNode *node) +{ +    gboolean changed; + +    changed = FALSE; + +    if (node->directory == NULL && +            (caja_file_is_directory (node->file) || node->parent == NULL)) +    { +        node->directory = caja_directory_get_for_file (node->file); +    } +    else if (node->directory != NULL && +             !(caja_file_is_directory (node->file) || node->parent == NULL)) +    { +        stop_monitoring_directory (model, node); +        destroy_children (model, node); +        caja_directory_unref (node->directory); +        node->directory = NULL; +    } + +    changed |= tree_node_update_display_name (node); +    changed |= tree_node_update_closed_pixbuf (node); +    changed |= tree_node_update_open_pixbuf (node); +    changed |= tree_node_update_emblem_pixbuf (node); + +    return changed; +} + +static void +insert_node (FMTreeModel *model, TreeNode *parent, TreeNode *node) +{ +    gboolean parent_empty; + +    parent_empty = parent->first_child == NULL; +    if (parent_empty) +    { +        /* Make sure the dummy lives as we insert the new row */ +        parent->force_has_dummy = TRUE; +    } + +    tree_node_parent (node, parent); + +    update_node_without_reporting (model, node); +    report_node_inserted (model, node); + +    if (parent_empty) +    { +        parent->force_has_dummy = FALSE; +        if (!tree_node_has_dummy_child (parent)) +        { +            /* Temporarily set this back so that row_deleted is +             * sent before actually removing the dummy child */ +            parent->force_has_dummy = TRUE; +            report_dummy_row_deleted (model, parent); +            parent->force_has_dummy = FALSE; +        } +    } +} + +static void +reparent_node (FMTreeModel *model, TreeNode *node) +{ +    GtkTreePath *path; +    TreeNode *new_parent; + +    new_parent = get_parent_node_from_file (node->root, node->file); +    if (new_parent == NULL || new_parent->directory == NULL) +    { +        destroy_node (model, node); +        return; +    } + +    path = get_node_path (model, node); + +    /* Report row_deleted before actually deleting */ +    gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path); +    gtk_tree_path_free (path); + +    abandon_node_ref_count (model, node); +    tree_node_unparent (model, node); + +    insert_node (model, new_parent, node); +} + +static gboolean +should_show_file (FMTreeModel *model, CajaFile *file) +{ +    gboolean should; +    TreeNode *node; + +    should = caja_file_should_show (file, +                                    model->details->show_hidden_files, +                                    model->details->show_backup_files, +                                    TRUE); + +    if (should +            && model->details->show_only_directories +            &&! caja_file_is_directory (file)) +    { +        should = FALSE; +    } + +    if (should && caja_file_is_gone (file)) +    { +        should = FALSE; +    } + +    for (node = model->details->root_node; node != NULL; node = node->next) +    { +        if (!should && node != NULL && file == node->file) +        { +            should = TRUE; +        } +    } + +    return should; +} + +static void +update_node (FMTreeModel *model, TreeNode *node) +{ +    gboolean had_dummy_child, has_dummy_child; +    gboolean had_directory, has_directory; +    gboolean changed; + +    if (!should_show_file (model, node->file)) +    { +        destroy_node (model, node); +        return; +    } + +    if (node->parent != NULL && node->parent->directory != NULL +            && !caja_directory_contains_file (node->parent->directory, node->file)) +    { +        reparent_node (model, node); +        return; +    } + +    had_dummy_child = tree_node_has_dummy_child (node); +    had_directory = node->directory != NULL; + +    changed = update_node_without_reporting (model, node); + +    has_dummy_child = tree_node_has_dummy_child (node); +    has_directory = node->directory != NULL; + +    if (had_dummy_child != has_dummy_child) +    { +        if (has_dummy_child) +        { +            report_dummy_row_inserted (model, node); +        } +        else +        { +            /* Temporarily set this back so that row_deleted is +             * sent before actually removing the dummy child */ +            node->force_has_dummy = TRUE; +            report_dummy_row_deleted (model, node); +            node->force_has_dummy = FALSE; +        } +    } +    if (had_directory != has_directory) +    { +        report_node_has_child_toggled (model, node); +    } + +    if (changed) +    { +        report_node_contents_changed (model, node); +    } +} + +static void +process_file_change (FMTreeModelRoot *root, +                     CajaFile *file) +{ +    TreeNode *node, *parent; + +    node = get_node_from_file (root, file); +    if (node != NULL) +    { +        update_node (root->model, node); +        return; +    } + +    if (!should_show_file (root->model, file)) +    { +        return; +    } + +    parent = get_parent_node_from_file (root, file); +    if (parent == NULL) +    { +        return; +    } + +    insert_node (root->model, parent, create_node_for_file (root, file)); +} + +static void +files_changed_callback (CajaDirectory *directory, +                        GList *changed_files, +                        gpointer callback_data) +{ +    FMTreeModelRoot *root; +    GList *node; + +    root = (FMTreeModelRoot *) (callback_data); + +    for (node = changed_files; node != NULL; node = node->next) +    { +        process_file_change (root, CAJA_FILE (node->data)); +    } +} + +static void +set_done_loading (FMTreeModel *model, TreeNode *node, gboolean done_loading) +{ +    gboolean had_dummy; + +    if (node == NULL || node->done_loading == done_loading) +    { +        return; +    } + +    had_dummy = tree_node_has_dummy_child (node); + +    node->done_loading = done_loading; + +    if (tree_node_has_dummy_child (node)) +    { +        if (had_dummy) +        { +            report_dummy_row_contents_changed (model, node); +        } +        else +        { +            report_dummy_row_inserted (model, node); +        } +    } +    else +    { +        if (had_dummy) +        { +            /* Temporarily set this back so that row_deleted is +             * sent before actually removing the dummy child */ +            node->force_has_dummy = TRUE; +            report_dummy_row_deleted (model, node); +            node->force_has_dummy = FALSE; +        } +        else +        { +            g_assert_not_reached (); +        } +    } +} + +static void +done_loading_callback (CajaDirectory *directory, +                       FMTreeModelRoot *root) +{ +    CajaFile *file; +    TreeNode *node; +    GtkTreeIter iter; + +    file = caja_directory_get_corresponding_file (directory); +    node = get_node_from_file (root, file); +    if (node == NULL) +    { +        /* This can happen for non-existing files as tree roots, +         * since the directory <-> file object relation gets +         * broken due to caja_directory_remove_file() +         * getting called when i/o fails. +         */ +        return; +    } +    set_done_loading (root->model, node, TRUE); +    caja_file_unref (file); + +    make_iter_for_node (node, &iter, root->model->details->stamp); +    g_signal_emit (root->model, +                   tree_model_signals[ROW_LOADED], 0, +                   &iter); +} + +static CajaFileAttributes +get_tree_monitor_attributes (void) +{ +    CajaFileAttributes attributes; + +    attributes = +        CAJA_FILE_ATTRIBUTES_FOR_ICON | +        CAJA_FILE_ATTRIBUTE_INFO | +        CAJA_FILE_ATTRIBUTE_LINK_INFO; + +    return attributes; +} + +static void +start_monitoring_directory (FMTreeModel *model, TreeNode *node) +{ +    CajaDirectory *directory; +    CajaFileAttributes attributes; + +    if (node->done_loading_id != 0) +    { +        return; +    } + +    g_assert (node->files_added_id == 0); +    g_assert (node->files_changed_id == 0); + +    directory = node->directory; + +    node->done_loading_id = g_signal_connect +                            (directory, "done_loading", +                             G_CALLBACK (done_loading_callback), node->root); +    node->files_added_id = g_signal_connect +                           (directory, "files_added", +                            G_CALLBACK (files_changed_callback), node->root); +    node->files_changed_id = g_signal_connect +                             (directory, "files_changed", +                              G_CALLBACK (files_changed_callback), node->root); + +    set_done_loading (model, node, caja_directory_are_all_files_seen (directory)); + +    attributes = get_tree_monitor_attributes (); +    caja_directory_file_monitor_add (directory, model, +                                     model->details->show_hidden_files, +                                     model->details->show_backup_files, +                                     attributes, files_changed_callback, node->root); +} + +static int +fm_tree_model_get_n_columns (GtkTreeModel *model) +{ +    return FM_TREE_MODEL_NUM_COLUMNS; +} + +static GType +fm_tree_model_get_column_type (GtkTreeModel *model, int index) +{ +    switch (index) +    { +    case FM_TREE_MODEL_DISPLAY_NAME_COLUMN: +        return G_TYPE_STRING; +    case FM_TREE_MODEL_CLOSED_PIXBUF_COLUMN: +        return GDK_TYPE_PIXBUF; +    case FM_TREE_MODEL_OPEN_PIXBUF_COLUMN: +        return GDK_TYPE_PIXBUF; +    case FM_TREE_MODEL_EMBLEM_PIXBUF_COLUMN: +        return GDK_TYPE_PIXBUF; +    case FM_TREE_MODEL_FONT_STYLE_COLUMN: +        return PANGO_TYPE_STYLE; +    default: +        g_assert_not_reached (); +    } + +    return G_TYPE_INVALID; +} + +static gboolean +iter_is_valid (FMTreeModel *model, const GtkTreeIter *iter) +{ +    TreeNode *node, *parent; + +    if (iter->stamp != model->details->stamp) +    { +        return FALSE; +    } + +    node = iter->user_data; +    parent = iter->user_data2; +    if (node == NULL) +    { +        if (parent != NULL) +        { +            if (!CAJA_IS_FILE (parent->file)) +            { +                return FALSE; +            } +            if (!tree_node_has_dummy_child (parent)) +            { +                return FALSE; +            } +        } +    } +    else +    { +        if (!CAJA_IS_FILE (node->file)) +        { +            return FALSE; +        } +        if (parent != NULL) +        { +            return FALSE; +        } +    } +    if (iter->user_data3 != NULL) +    { +        return FALSE; +    } +    return TRUE; +} + +static gboolean +fm_tree_model_get_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreePath *path) +{ +    int *indices; +    GtkTreeIter parent; +    int depth, i; + +    indices = gtk_tree_path_get_indices (path); +    depth = gtk_tree_path_get_depth (path); + +    if (! gtk_tree_model_iter_nth_child (model, iter, NULL, indices[0])) +    { +        return FALSE; +    } + +    for (i = 1; i < depth; i++) +    { +        parent = *iter; + +        if (! gtk_tree_model_iter_nth_child (model, iter, &parent, indices[i])) +        { +            return FALSE; +        } +    } + +    return TRUE; +} + +static GtkTreePath * +fm_tree_model_get_path (GtkTreeModel *model, GtkTreeIter *iter) +{ +    FMTreeModel *tree_model; +    TreeNode *node, *parent, *cnode; +    GtkTreePath *path; +    GtkTreeIter parent_iter; +    int i; + +    g_return_val_if_fail (FM_IS_TREE_MODEL (model), NULL); +    tree_model = FM_TREE_MODEL (model); +    g_return_val_if_fail (iter_is_valid (tree_model, iter), NULL); + +    node = iter->user_data; +    if (node == NULL) +    { +        parent = iter->user_data2; +        if (parent == NULL) +        { +            return gtk_tree_path_new (); +        } +    } +    else +    { +        parent = node->parent; +        if (parent == NULL) +        { +            i = 0; +            for (cnode = tree_model->details->root_node; cnode != node; cnode = cnode->next) +            { +                i++; +            } +            path = gtk_tree_path_new (); +            gtk_tree_path_append_index (path, i); +            return path; +        } +    } + +    parent_iter.stamp = iter->stamp; +    parent_iter.user_data = parent; +    parent_iter.user_data2 = NULL; +    parent_iter.user_data3 = NULL; + +    path = fm_tree_model_get_path (model, &parent_iter); + +    gtk_tree_path_append_index (path, tree_node_get_child_index (parent, node)); + +    return path; +} + +static void +fm_tree_model_get_value (GtkTreeModel *model, GtkTreeIter *iter, int column, GValue *value) +{ +    TreeNode *node, *parent; +    FMTreeModel *fm_model; + +    g_return_if_fail (FM_IS_TREE_MODEL (model)); +    g_return_if_fail (iter_is_valid (FM_TREE_MODEL (model), iter)); + +    fm_model = FM_TREE_MODEL (model); +    node = iter->user_data; + +    switch (column) +    { +    case FM_TREE_MODEL_DISPLAY_NAME_COLUMN: +        g_value_init (value, G_TYPE_STRING); +        if (node == NULL) +        { +            parent = iter->user_data2; +            g_value_set_static_string (value, parent->done_loading +                                       ? _("(Empty)") : _("Loading...")); +        } +        else +        { +            g_value_set_string (value, tree_node_get_display_name (node)); +        } +        break; +    case FM_TREE_MODEL_CLOSED_PIXBUF_COLUMN: +        g_value_init (value, GDK_TYPE_PIXBUF); +        g_value_set_object (value, node == NULL ? NULL : tree_node_get_closed_pixbuf (node)); +        break; +    case FM_TREE_MODEL_OPEN_PIXBUF_COLUMN: +        g_value_init (value, GDK_TYPE_PIXBUF); +        g_value_set_object (value, node == NULL ? NULL : tree_node_get_open_pixbuf (node)); +        break; +    case FM_TREE_MODEL_EMBLEM_PIXBUF_COLUMN: +        g_value_init (value, GDK_TYPE_PIXBUF); +        g_value_set_object (value, node == NULL ? NULL : tree_node_get_emblem_pixbuf (node)); +        break; +    case FM_TREE_MODEL_FONT_STYLE_COLUMN: +        g_value_init (value, PANGO_TYPE_STYLE); +        if (node == NULL) +        { +            g_value_set_enum (value, PANGO_STYLE_ITALIC); +        } +        else +        { +            g_value_set_enum (value, PANGO_STYLE_NORMAL); +        } +        break; +    default: +        g_assert_not_reached (); +    } +} + +static gboolean +fm_tree_model_iter_next (GtkTreeModel *model, GtkTreeIter *iter) +{ +    TreeNode *node, *parent, *next; + +    g_return_val_if_fail (FM_IS_TREE_MODEL (model), FALSE); +    g_return_val_if_fail (iter_is_valid (FM_TREE_MODEL (model), iter), FALSE); + +    node = iter->user_data; + +    if (node == NULL) +    { +        parent = iter->user_data2; +        next = parent->first_child; +    } +    else +    { +        next = node->next; +    } + +    return make_iter_for_node (next, iter, iter->stamp); +} + +static gboolean +fm_tree_model_iter_children (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *parent_iter) +{ +    TreeNode *parent; + +    g_return_val_if_fail (FM_IS_TREE_MODEL (model), FALSE); +    g_return_val_if_fail (iter_is_valid (FM_TREE_MODEL (model), parent_iter), FALSE); + +    parent = parent_iter->user_data; +    if (parent == NULL) +    { +        return make_iter_invalid (iter); +    } + +    if (tree_node_has_dummy_child (parent)) +    { +        return make_iter_for_dummy_row (parent, iter, parent_iter->stamp); +    } +    return make_iter_for_node (parent->first_child, iter, parent_iter->stamp); +} + +static gboolean +fm_tree_model_iter_parent (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *child_iter) +{ +    TreeNode *child, *parent; + +    g_return_val_if_fail (FM_IS_TREE_MODEL (model), FALSE); +    g_return_val_if_fail (iter_is_valid (FM_TREE_MODEL (model), child_iter), FALSE); + +    child = child_iter->user_data; + +    if (child == NULL) +    { +        parent = child_iter->user_data2; +    } +    else +    { +        parent = child->parent; +    } + +    return make_iter_for_node (parent, iter, child_iter->stamp); +} + +static gboolean +fm_tree_model_iter_has_child (GtkTreeModel *model, GtkTreeIter *iter) +{ +    gboolean has_child; +    TreeNode *node; + +    g_return_val_if_fail (FM_IS_TREE_MODEL (model), FALSE); +    g_return_val_if_fail (iter_is_valid (FM_TREE_MODEL (model), iter), FALSE); + +    node = iter->user_data; + +    has_child = node != NULL && (node->directory != NULL || node->parent == NULL); + +#if 0 +    g_warning ("Node '%s' %s", +               node && node->file ? caja_file_get_uri (node->file) : "no name", +               has_child ? "has child" : "no child"); +#endif + +    return has_child; +} + +static int +fm_tree_model_iter_n_children (GtkTreeModel *model, GtkTreeIter *iter) +{ +    FMTreeModel *tree_model; +    TreeNode *parent, *node; +    int n; + +    g_return_val_if_fail (FM_IS_TREE_MODEL (model), FALSE); +    g_return_val_if_fail (iter == NULL || iter_is_valid (FM_TREE_MODEL (model), iter), FALSE); + +    tree_model = FM_TREE_MODEL (model); + +    if (iter == NULL) +    { +        return 1; +    } + +    parent = iter->user_data; +    if (parent == NULL) +    { +        return 0; +    } + +    n = tree_node_has_dummy_child (parent) ? 1 : 0; +    for (node = parent->first_child; node != NULL; node = node->next) +    { +        n++; +    } + +    return n; +} + +static gboolean +fm_tree_model_iter_nth_child (GtkTreeModel *model, GtkTreeIter *iter, +                              GtkTreeIter *parent_iter, int n) +{ +    FMTreeModel *tree_model; +    TreeNode *parent, *node; +    int i; + +    g_return_val_if_fail (FM_IS_TREE_MODEL (model), FALSE); +    g_return_val_if_fail (parent_iter == NULL +                          || iter_is_valid (FM_TREE_MODEL (model), parent_iter), FALSE); + +    tree_model = FM_TREE_MODEL (model); + +    if (parent_iter == NULL) +    { +        node = tree_model->details->root_node; +        for (i = 0; i < n && node != NULL; i++, node = node->next); +        return make_iter_for_node (node, iter, +                                   tree_model->details->stamp); +    } + +    parent = parent_iter->user_data; +    if (parent == NULL) +    { +        return make_iter_invalid (iter); +    } + +    i = tree_node_has_dummy_child (parent) ? 1 : 0; +    if (n == 0 && i == 1) +    { +        return make_iter_for_dummy_row (parent, iter, parent_iter->stamp); +    } +    for (node = parent->first_child; i != n; i++, node = node->next) +    { +        if (node == NULL) +        { +            return make_iter_invalid (iter); +        } +    } + +    return make_iter_for_node (node, iter, parent_iter->stamp); +} + +static void +update_monitoring (FMTreeModel *model, TreeNode *node) +{ +    TreeNode *child; + +    if (node->all_children_ref_count == 0) +    { +        stop_monitoring_directory (model, node); +        destroy_children (model, node); +    } +    else +    { +        for (child = node->first_child; child != NULL; child = child->next) +        { +            update_monitoring (model, child); +        } +        start_monitoring_directory (model, node); +    } +} + +static gboolean +update_monitoring_idle_callback (gpointer callback_data) +{ +    FMTreeModel *model; +    TreeNode *node; + +    model = FM_TREE_MODEL (callback_data); +    model->details->monitoring_update_idle_id = 0; +    for (node = model->details->root_node; node != NULL; node = node->next) +    { +        update_monitoring (model, node); +    } +    return FALSE; +} + +static void +schedule_monitoring_update (FMTreeModel *model) +{ +    if (model->details->monitoring_update_idle_id == 0) +    { +        model->details->monitoring_update_idle_id = +            g_idle_add (update_monitoring_idle_callback, model); +    } +} + +static void +stop_monitoring_directory_and_children (FMTreeModel *model, TreeNode *node) +{ +    TreeNode *child; + +    stop_monitoring_directory (model, node); +    for (child = node->first_child; child != NULL; child = child->next) +    { +        stop_monitoring_directory_and_children (model, child); +    } +} + +static void +stop_monitoring (FMTreeModel *model) +{ +    TreeNode *node; + +    for (node = model->details->root_node; node != NULL; node = node->next) +    { +        stop_monitoring_directory_and_children (model, node); +    } +} + +static void +fm_tree_model_ref_node (GtkTreeModel *model, GtkTreeIter *iter) +{ +    TreeNode *node, *parent; +#ifdef LOG_REF_COUNTS +    char *uri; +#endif + +    g_return_if_fail (FM_IS_TREE_MODEL (model)); +    g_return_if_fail (iter_is_valid (FM_TREE_MODEL (model), iter)); + +    node = iter->user_data; +    if (node == NULL) +    { +        parent = iter->user_data2; +        g_assert (parent->dummy_child_ref_count >= 0); +        ++parent->dummy_child_ref_count; +    } +    else +    { +        parent = node->parent; +        g_assert (node->ref_count >= 0); +        ++node->ref_count; +    } + +    if (parent != NULL) +    { +        g_assert (parent->all_children_ref_count >= 0); +        if (++parent->all_children_ref_count == 1) +        { +            if (parent->first_child == NULL) +            { +                parent->done_loading = FALSE; +            } +            schedule_monitoring_update (FM_TREE_MODEL (model)); +        } +#ifdef LOG_REF_COUNTS +        uri = get_node_uri (iter); +        g_message ("ref of %s, count is now %d", +                   uri, parent->all_children_ref_count); +        g_free (uri); +#endif +    } +} + +static void +fm_tree_model_unref_node (GtkTreeModel *model, GtkTreeIter *iter) +{ +    TreeNode *node, *parent; +#ifdef LOG_REF_COUNTS +    char *uri; +#endif + +    g_return_if_fail (FM_IS_TREE_MODEL (model)); +    g_return_if_fail (iter_is_valid (FM_TREE_MODEL (model), iter)); + +    node = iter->user_data; +    if (node == NULL) +    { +        parent = iter->user_data2; +        g_assert (parent->dummy_child_ref_count > 0); +        --parent->dummy_child_ref_count; +    } +    else +    { +        parent = node->parent; +        g_assert (node->ref_count > 0); +        --node->ref_count; +    } + +    if (parent != NULL) +    { +        g_assert (parent->all_children_ref_count > 0); +#ifdef LOG_REF_COUNTS +        uri = get_node_uri (iter); +        g_message ("unref of %s, count is now %d", +                   uri, parent->all_children_ref_count - 1); +        g_free (uri); +#endif +        if (--parent->all_children_ref_count == 0) +        { +            schedule_monitoring_update (FM_TREE_MODEL (model)); +        } +    } +} + +void +fm_tree_model_add_root_uri (FMTreeModel *model, const char *root_uri, const char *display_name, GIcon *icon, GMount *mount) +{ +    CajaFile *file; +    TreeNode *node, *cnode; +    FMTreeModelRoot *newroot; + +    file = caja_file_get_by_uri (root_uri); + +    newroot = tree_model_root_new (model); +    node = create_node_for_file (newroot, file); +    node->display_name = g_strdup (display_name); +    node->icon = g_object_ref (icon); +    if (mount) +    { +        node->mount = g_object_ref (mount); +    } +    newroot->root_node = node; +    node->parent = NULL; +    if (model->details->root_node == NULL) +    { +        model->details->root_node = node; +    } +    else +    { +        /* append it */ +        for (cnode = model->details->root_node; cnode->next != NULL; cnode = cnode->next); +        cnode->next = node; +        node->prev = cnode; +    } + +    caja_file_unref (file); + +    update_node_without_reporting (model, node); +    report_node_inserted (model, node); +} + +GMount * +fm_tree_model_get_mount_for_root_node_file (FMTreeModel *model, CajaFile *file) +{ +    TreeNode *node; + +    for (node = model->details->root_node; node != NULL; node = node->next) +    { +        if (file == node->file) +        { +            break; +        } +    } + +    if (node) +    { +        return node->mount; +    } + +    return NULL; +} + +void +fm_tree_model_remove_root_uri (FMTreeModel *model, const char *uri) +{ +    TreeNode *node; +    GtkTreePath *path; +    FMTreeModelRoot *root; +    CajaFile *file; + +    file = caja_file_get_by_uri (uri); +    for (node = model->details->root_node; node != NULL; node = node->next) +    { +        if (file == node->file) +        { +            break; +        } +    } +    caja_file_unref (file); + +    if (node) +    { +        /* remove the node */ + +        if (node->mount) +        { +            g_object_unref (node->mount); +            node->mount = NULL; +        } + +        caja_file_monitor_remove (node->file, model); +        path = get_node_path (model, node); + +        /* Report row_deleted before actually deleting */ +        gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path); +        gtk_tree_path_free (path); + +        if (node->prev) +        { +            node->prev->next = node->next; +        } +        if (node->next) +        { +            node->next->prev = node->prev; +        } +        if (node == model->details->root_node) +        { +            model->details->root_node = node->next; +        } + +        /* destroy the root identifier */ +        root = node->root; +        destroy_node_without_reporting (model, node); +        g_hash_table_destroy (root->file_to_node_map); +        g_free (root); +    } +} + +FMTreeModel * +fm_tree_model_new (void) +{ +    FMTreeModel *model; + +    model = g_object_new (FM_TYPE_TREE_MODEL, NULL); + +    return model; +} + +void +fm_tree_model_set_show_hidden_files (FMTreeModel *model, +                                     gboolean show_hidden_files) +{ +    g_return_if_fail (FM_IS_TREE_MODEL (model)); +    g_return_if_fail (show_hidden_files == FALSE || show_hidden_files == TRUE); + +    show_hidden_files = show_hidden_files != FALSE; +    if (model->details->show_hidden_files == show_hidden_files) +    { +        return; +    } +    model->details->show_hidden_files = show_hidden_files; +    model->details->show_backup_files = show_hidden_files; +    stop_monitoring (model); +    if (!show_hidden_files) +    { +        destroy_by_function (model, caja_file_is_hidden_file); +    } +    schedule_monitoring_update (model); +} + +static gboolean +file_is_not_directory (CajaFile *file) +{ +    return !caja_file_is_directory (file); +} + +void +fm_tree_model_set_show_only_directories (FMTreeModel *model, +        gboolean show_only_directories) +{ +    g_return_if_fail (FM_IS_TREE_MODEL (model)); +    g_return_if_fail (show_only_directories == FALSE || show_only_directories == TRUE); + +    show_only_directories = show_only_directories != FALSE; +    if (model->details->show_only_directories == show_only_directories) +    { +        return; +    } +    model->details->show_only_directories = show_only_directories; +    stop_monitoring (model); +    if (show_only_directories) +    { +        destroy_by_function (model, file_is_not_directory); +    } +    schedule_monitoring_update (model); +} + +CajaFile * +fm_tree_model_iter_get_file (FMTreeModel *model, GtkTreeIter *iter) +{ +    TreeNode *node; + +    g_return_val_if_fail (FM_IS_TREE_MODEL (model), NULL); +    g_return_val_if_fail (iter_is_valid (FM_TREE_MODEL (model), iter), NULL); + +    node = iter->user_data; +    return node == NULL ? NULL : caja_file_ref (node->file); +} + +/* This is used to work around some sort order stability problems +   with gtktreemodelsort */ +int +fm_tree_model_iter_compare_roots (FMTreeModel *model, +                                  GtkTreeIter *iter_a, +                                  GtkTreeIter *iter_b) +{ +    TreeNode *a, *b, *n; + +    g_return_val_if_fail (FM_IS_TREE_MODEL (model), 0); +    g_return_val_if_fail (iter_is_valid (model, iter_a), 0); +    g_return_val_if_fail (iter_is_valid (model, iter_b), 0); + +    a = iter_a->user_data; +    b = iter_b->user_data; + +    g_assert (a != NULL && a->parent == NULL); +    g_assert (b != NULL && b->parent == NULL); + +    if (a == b) +    { +        return 0; +    } + +    for (n = model->details->root_node; n != NULL; n = n->next) +    { +        if (n == a) +        { +            return -1; +        } +        if (n == b) +        { +            return 1; +        } +    } +    g_assert_not_reached (); +} + +gboolean +fm_tree_model_iter_is_root (FMTreeModel *model, GtkTreeIter *iter) +{ +    TreeNode *node; + +    g_return_val_if_fail (FM_IS_TREE_MODEL (model), 0); +    g_return_val_if_fail (iter_is_valid (model, iter), 0); +    node = iter->user_data; +    if (node == NULL) +    { +        return FALSE; +    } +    else +    { +        return (node->parent == NULL); +    } +} + +gboolean +fm_tree_model_file_get_iter (FMTreeModel *model, +                             GtkTreeIter *iter, +                             CajaFile *file, +                             GtkTreeIter *current_iter) +{ +    TreeNode *node, *root_node; + +    if (current_iter != NULL && current_iter->user_data != NULL) +    { +        node = get_node_from_file (((TreeNode *) current_iter->user_data)->root, file); +        return make_iter_for_node (node, iter, model->details->stamp); +    } + +    for (root_node = model->details->root_node; root_node != NULL; root_node = root_node->next) +    { +        node = get_node_from_file (root_node->root, file); +        if (node != NULL) +        { +            return make_iter_for_node (node, iter, model->details->stamp); +        } +    } +    return FALSE; +} + +static void +do_update_node (CajaFile *file, +                FMTreeModel *model) +{ +    TreeNode *root, *node = NULL; + +    for (root = model->details->root_node; root != NULL; root = root->next) +    { +        node = get_node_from_file (root->root, file); + +        if (node != NULL) +        { +            break; +        } +    } + +    if (node == NULL) +    { +        return; +    } + +    update_node (model, node); +} + +void +fm_tree_model_set_highlight_for_files (FMTreeModel *model, +                                       GList *files) +{ +    GList *old_files; + +    if (model->details->highlighted_files != NULL) +    { +        old_files = model->details->highlighted_files; +        model->details->highlighted_files = NULL; + +        g_list_foreach (old_files, +                        (GFunc) do_update_node, model); + +        caja_file_list_free (old_files); +    } + +    if (files != NULL) +    { +        model->details->highlighted_files = +            caja_file_list_copy (files); +        g_list_foreach (model->details->highlighted_files, +                        (GFunc) do_update_node, model); +    } +} + +static void +fm_tree_model_init (FMTreeModel *model) +{ +    model->details = g_new0 (FMTreeModelDetails, 1); + +    do +    { +        model->details->stamp = g_random_int (); +    } +    while (model->details->stamp == 0); +} + +static void +fm_tree_model_finalize (GObject *object) +{ +    FMTreeModel *model; +    TreeNode *root_node, *next_root; +    FMTreeModelRoot *root; + +    model = FM_TREE_MODEL (object); + +    for (root_node = model->details->root_node; root_node != NULL; root_node = next_root) +    { +        next_root = root_node->next; +        root = root_node->root; +        destroy_node_without_reporting (model, root_node); +        g_hash_table_destroy (root->file_to_node_map); +        g_free (root); +    } + +    if (model->details->monitoring_update_idle_id != 0) +    { +        g_source_remove (model->details->monitoring_update_idle_id); +    } + +    if (model->details->highlighted_files != NULL) +    { +        caja_file_list_free (model->details->highlighted_files); +    } + +    g_free (model->details); + +    G_OBJECT_CLASS (fm_tree_model_parent_class)->finalize (object); +} + +static void +fm_tree_model_class_init (FMTreeModelClass *class) +{ +    G_OBJECT_CLASS (class)->finalize = fm_tree_model_finalize; + +    tree_model_signals[ROW_LOADED] = +        g_signal_new ("row_loaded", +                      FM_TYPE_TREE_MODEL, +                      G_SIGNAL_RUN_LAST, +                      G_STRUCT_OFFSET (FMTreeModelClass, row_loaded), +                      NULL, NULL, +                      g_cclosure_marshal_VOID__BOXED, +                      G_TYPE_NONE, 1, +                      GTK_TYPE_TREE_ITER); +} + +static void +fm_tree_model_tree_model_init (GtkTreeModelIface *iface) +{ +    iface->get_flags = fm_tree_model_get_flags; +    iface->get_n_columns = fm_tree_model_get_n_columns; +    iface->get_column_type = fm_tree_model_get_column_type; +    iface->get_iter = fm_tree_model_get_iter; +    iface->get_path = fm_tree_model_get_path; +    iface->get_value = fm_tree_model_get_value; +    iface->iter_next = fm_tree_model_iter_next; +    iface->iter_children = fm_tree_model_iter_children; +    iface->iter_has_child = fm_tree_model_iter_has_child; +    iface->iter_n_children = fm_tree_model_iter_n_children; +    iface->iter_nth_child = fm_tree_model_iter_nth_child; +    iface->iter_parent = fm_tree_model_iter_parent; +    iface->ref_node = fm_tree_model_ref_node; +    iface->unref_node = fm_tree_model_unref_node; +} + + diff --git a/src/file-manager/fm-tree-model.h b/src/file-manager/fm-tree-model.h new file mode 100644 index 00000000..f74ce06c --- /dev/null +++ b/src/file-manager/fm-tree-model.h @@ -0,0 +1,104 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* + * Copyright (C) 2002 Anders Carlsson + * Copyright (C) 2002 Bent Spoon Software + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Anders Carlsson <[email protected]> + */ + +/* fm-tree-model.h - Model for the tree view */ + +#ifndef FM_TREE_MODEL_H +#define FM_TREE_MODEL_H + +#include <glib-object.h> +#include <gtk/gtk.h> +#include <gio/gio.h> +#include <libcaja-private/caja-file.h> + +#define FM_TYPE_TREE_MODEL fm_tree_model_get_type() +#define FM_TREE_MODEL(obj) \ +  (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_TREE_MODEL, FMTreeModel)) +#define FM_TREE_MODEL_CLASS(klass) \ +  (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_TREE_MODEL, FMTreeModelClass)) +#define FM_IS_TREE_MODEL(obj) \ +  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_TREE_MODEL)) +#define FM_IS_TREE_MODEL_CLASS(klass) \ +  (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_TREE_MODEL)) +#define FM_TREE_MODEL_GET_CLASS(obj) \ +  (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_TREE_MODEL, FMTreeModelClass)) + +enum +{ +    FM_TREE_MODEL_DISPLAY_NAME_COLUMN, +    FM_TREE_MODEL_CLOSED_PIXBUF_COLUMN, +    FM_TREE_MODEL_OPEN_PIXBUF_COLUMN, +    FM_TREE_MODEL_EMBLEM_PIXBUF_COLUMN, +    FM_TREE_MODEL_FONT_STYLE_COLUMN, +    FM_TREE_MODEL_NUM_COLUMNS +}; + +typedef struct FMTreeModelDetails FMTreeModelDetails; + +typedef struct +{ +    GObject parent; +    FMTreeModelDetails *details; +} FMTreeModel; + +typedef struct +{ +    GObjectClass parent_class; + +    void         (* row_loaded)      (FMTreeModel *tree_model, +                                      GtkTreeIter       *iter); +} FMTreeModelClass; + +GType              fm_tree_model_get_type                  (void); +FMTreeModel *fm_tree_model_new                       (void); +void               fm_tree_model_set_show_hidden_files     (FMTreeModel *model, +        gboolean           show_hidden_files); +void               fm_tree_model_set_show_only_directories (FMTreeModel *model, +        gboolean           show_only_directories); +CajaFile *     fm_tree_model_iter_get_file             (FMTreeModel *model, +        GtkTreeIter       *iter); +void               fm_tree_model_add_root_uri              (FMTreeModel *model, +        const char        *root_uri, +        const char        *display_name, +        GIcon             *icon, +        GMount            *mount); +void               fm_tree_model_remove_root_uri           (FMTreeModel *model, +        const char        *root_uri); +gboolean           fm_tree_model_iter_is_root              (FMTreeModel *model, +        GtkTreeIter *iter); +int                fm_tree_model_iter_compare_roots        (FMTreeModel *model, +        GtkTreeIter *iter_a, +        GtkTreeIter *iter_b); +gboolean           fm_tree_model_file_get_iter             (FMTreeModel *model, +        GtkTreeIter *iter, +        CajaFile *file, +        GtkTreeIter *currentIter); + +GMount *         fm_tree_model_get_mount_for_root_node_file +(FMTreeModel  *model, + CajaFile *file); +void             fm_tree_model_set_highlight_for_files    (FMTreeModel *model, +        GList *files); + +#endif /* FM_TREE_MODEL_H */ diff --git a/src/file-manager/fm-tree-view.c b/src/file-manager/fm-tree-view.c new file mode 100644 index 00000000..2a6cbd60 --- /dev/null +++ b/src/file-manager/fm-tree-view.c @@ -0,0 +1,1814 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* + * Copyright (C) 2000, 2001 Eazel, Inc + * Copyright (C) 2002 Anders Carlsson + * Copyright (C) 2002 Darin Adler + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: + *       Maciej Stachowiak <[email protected]> + *       Anders Carlsson <[email protected]> + *       Darin Adler <[email protected]> + */ + +/* fm-tree-view.c - tree sidebar panel + */ + +#include <config.h> +#include "fm-tree-view.h" + +#include "fm-tree-model.h" +#include "fm-properties-window.h" +#include <string.h> +#include <eel/eel-alert-dialog.h> +#include <eel/eel-glib-extensions.h> +#include <eel/eel-gtk-extensions.h> +#include <eel/eel-preferences.h> +#include <eel/eel-stock-dialogs.h> +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <gio/gio.h> +#include <libcaja-private/caja-clipboard.h> +#include <libcaja-private/caja-clipboard-monitor.h> +#include <libcaja-private/caja-desktop-icon-file.h> +#include <libcaja-private/caja-debug-log.h> +#include <libcaja-private/caja-file-attributes.h> +#include <libcaja-private/caja-file-operations.h> +#include <libcaja-private/caja-file-utilities.h> +#include <libcaja-private/caja-global-preferences.h> +#include <libcaja-private/caja-icon-names.h> +#include <libcaja-private/caja-program-choosing.h> +#include <libcaja-private/caja-tree-view-drag-dest.h> +#include <libcaja-private/caja-cell-renderer-pixbuf-emblem.h> +#include <libcaja-private/caja-sidebar-provider.h> +#include <libcaja-private/caja-module.h> +#include <libcaja-private/caja-window-info.h> +#include <libcaja-private/caja-window-slot-info.h> + +typedef struct +{ +    GObject parent; +} FMTreeViewProvider; + +typedef struct +{ +    GObjectClass parent; +} FMTreeViewProviderClass; + + +struct FMTreeViewDetails +{ +    CajaWindowInfo *window; +    GtkTreeView *tree_widget; +    GtkTreeModelSort *sort_model; +    FMTreeModel *child_model; + +    GVolumeMonitor *volume_monitor; + +    CajaFile *activation_file; +    CajaWindowOpenFlags activation_flags; + +    CajaTreeViewDragDest *drag_dest; + +    char *selection_location; +    gboolean selecting; + +    guint show_selection_idle_id; +    gulong clipboard_handler_id; + +    GtkWidget *popup; +    GtkWidget *popup_open; +    GtkWidget *popup_open_in_new_window; +    GtkWidget *popup_create_folder; +    GtkWidget *popup_cut; +    GtkWidget *popup_copy; +    GtkWidget *popup_paste; +    GtkWidget *popup_rename; +    GtkWidget *popup_trash; +    GtkWidget *popup_delete; +    GtkWidget *popup_properties; +    GtkWidget *popup_unmount_separator; +    GtkWidget *popup_unmount; +    GtkWidget *popup_eject; +    CajaFile *popup_file; +    guint popup_file_idle_handler; + +    guint selection_changed_timer; +}; + +typedef struct +{ +    GList *uris; +    FMTreeView *view; +} PrependURIParameters; + +static GdkAtom copied_files_atom; +static gboolean show_delete_command_auto_value; + +static void  fm_tree_view_iface_init        (CajaSidebarIface         *iface); +static void  sidebar_provider_iface_init    (CajaSidebarProviderIface *iface); +static void  fm_tree_view_activate_file     (FMTreeView *view, +        CajaFile *file, +        CajaWindowOpenFlags flags); +static GType fm_tree_view_provider_get_type (void); + +static void create_popup_menu (FMTreeView *view); + +G_DEFINE_TYPE_WITH_CODE (FMTreeView, fm_tree_view, GTK_TYPE_SCROLLED_WINDOW, +                         G_IMPLEMENT_INTERFACE (CAJA_TYPE_SIDEBAR, +                                 fm_tree_view_iface_init)); +#define parent_class fm_tree_view_parent_class + +G_DEFINE_TYPE_WITH_CODE (FMTreeViewProvider, fm_tree_view_provider, G_TYPE_OBJECT, +                         G_IMPLEMENT_INTERFACE (CAJA_TYPE_SIDEBAR_PROVIDER, +                                 sidebar_provider_iface_init)); + +static void +notify_clipboard_info (CajaClipboardMonitor *monitor, +                       CajaClipboardInfo *info, +                       FMTreeView *view) +{ +    if (info != NULL && info->cut) +    { +        fm_tree_model_set_highlight_for_files (view->details->child_model, info->files); +    } +    else +    { +        fm_tree_model_set_highlight_for_files (view->details->child_model, NULL); +    } +} + + +static gboolean +show_iter_for_file (FMTreeView *view, CajaFile *file, GtkTreeIter *iter) +{ +    GtkTreeModel *model; +    CajaFile *parent_file; +    GtkTreeIter parent_iter; +    GtkTreePath *path, *sort_path; +    GtkTreeIter cur_iter; + +    if (view->details->child_model == NULL) +    { +        return FALSE; +    } +    model = GTK_TREE_MODEL (view->details->child_model); + +    /* check if file is visible in the same root as the currently selected folder is */ +    gtk_tree_view_get_cursor (view->details->tree_widget, &path, NULL); +    if (path != NULL) +    { +        if (gtk_tree_model_get_iter (model, &cur_iter, path) && +                fm_tree_model_file_get_iter (view->details->child_model, iter, +                                             file, &cur_iter)) +        { +            gtk_tree_path_free (path); +            return TRUE; +        } +        gtk_tree_path_free (path); +    } +    /* check if file is visible at all */ +    if (fm_tree_model_file_get_iter (view->details->child_model, +                                     iter, file, NULL)) +    { +        return TRUE; +    } + +    parent_file = caja_file_get_parent (file); + +    if (parent_file == NULL) +    { +        return FALSE; +    } +    if (!show_iter_for_file (view, parent_file, &parent_iter)) +    { +        caja_file_unref (parent_file); +        return FALSE; +    } +    caja_file_unref (parent_file); + +    if (parent_iter.user_data == NULL || parent_iter.stamp == 0) +    { +        return FALSE; +    } +    path = gtk_tree_model_get_path (model, &parent_iter); +    sort_path = gtk_tree_model_sort_convert_child_path_to_path +                (view->details->sort_model, path); +    gtk_tree_path_free (path); +    gtk_tree_view_expand_row (view->details->tree_widget, sort_path, FALSE); +    gtk_tree_path_free (sort_path); + +    return FALSE; +} + +static void +refresh_highlight (FMTreeView *view) +{ +    CajaClipboardMonitor *monitor; +    CajaClipboardInfo *info; + +    monitor = caja_clipboard_monitor_get (); +    info = caja_clipboard_monitor_get_clipboard_info (monitor); + +    notify_clipboard_info (monitor, info, view); +} + +static gboolean +show_selection_idle_callback (gpointer callback_data) +{ +    FMTreeView *view; +    CajaFile *file, *old_file; +    GtkTreeIter iter; +    GtkTreePath *path, *sort_path; + +    view = FM_TREE_VIEW (callback_data); + +    view->details->show_selection_idle_id = 0; + +    file = caja_file_get_by_uri (view->details->selection_location); +    if (file == NULL) +    { +        return FALSE; +    } + +    if (!caja_file_is_directory (file)) +    { +        old_file = file; +        file = caja_file_get_parent (file); +        caja_file_unref (old_file); +        if (file == NULL) +        { +            return FALSE; +        } +    } + +    view->details->selecting = TRUE; +    if (!show_iter_for_file (view, file, &iter)) +    { +        caja_file_unref (file); +        return FALSE; +    } +    view->details->selecting = FALSE; + +    path = gtk_tree_model_get_path (GTK_TREE_MODEL (view->details->child_model), &iter); +    sort_path = gtk_tree_model_sort_convert_child_path_to_path +                (view->details->sort_model, path); +    gtk_tree_path_free (path); +    gtk_tree_view_set_cursor (view->details->tree_widget, sort_path, NULL, FALSE); +    if (gtk_widget_get_realized (GTK_WIDGET (view->details->tree_widget))) +    { +        gtk_tree_view_scroll_to_cell (view->details->tree_widget, sort_path, NULL, FALSE, 0, 0); +    } +    gtk_tree_path_free (sort_path); + +    caja_file_unref (file); +    refresh_highlight (view); + +    return FALSE; +} + +static void +schedule_show_selection (FMTreeView *view) +{ +    if (view->details->show_selection_idle_id == 0) +    { +        view->details->show_selection_idle_id = g_idle_add (show_selection_idle_callback, view); +    } +} + +static void +schedule_select_and_show_location (FMTreeView *view, char *location) +{ +    if (view->details->selection_location != NULL) +    { +        g_free (view->details->selection_location); +    } +    view->details->selection_location = g_strdup (location); +    schedule_show_selection (view); +} + +static void +row_loaded_callback (GtkTreeModel     *tree_model, +                     GtkTreeIter      *iter, +                     FMTreeView *view) +{ +    CajaFile *file, *tmp_file, *selection_file; + +    if (view->details->selection_location == NULL +            || !view->details->selecting +            || iter->user_data == NULL || iter->stamp == 0) +    { +        return; +    } + +    file = fm_tree_model_iter_get_file (view->details->child_model, iter); +    if (file == NULL) +    { +        return; +    } +    if (!caja_file_is_directory (file)) +    { +        caja_file_unref(file); +        return; +    } + +    /* if iter is ancestor of wanted selection_location then update selection */ +    selection_file = caja_file_get_by_uri (view->details->selection_location); +    while (selection_file != NULL) +    { +        if (file == selection_file) +        { +            caja_file_unref (file); +            caja_file_unref (selection_file); + +            schedule_show_selection (view); +            return; +        } +        tmp_file = caja_file_get_parent (selection_file); +        caja_file_unref (selection_file); +        selection_file = tmp_file; +    } +    caja_file_unref (file); +} + +static CajaFile * +sort_model_iter_to_file (FMTreeView *view, GtkTreeIter *iter) +{ +    GtkTreeIter child_iter; + +    gtk_tree_model_sort_convert_iter_to_child_iter (view->details->sort_model, &child_iter, iter); +    return fm_tree_model_iter_get_file (view->details->child_model, &child_iter); +} + +static CajaFile * +sort_model_path_to_file (FMTreeView *view, GtkTreePath *path) +{ +    GtkTreeIter iter; + +    if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (view->details->sort_model), &iter, path)) +    { +        return NULL; +    } +    return sort_model_iter_to_file (view, &iter); +} + +static void +got_activation_uri_callback (CajaFile *file, gpointer callback_data) +{ +    char *uri, *file_uri; +    FMTreeView *view; +    GdkScreen *screen; +    GFile *location; +    CajaWindowSlotInfo *slot; +    gboolean open_in_same_slot; + +    view = FM_TREE_VIEW (callback_data); + +    screen = gtk_widget_get_screen (GTK_WIDGET (view->details->tree_widget)); + +    g_assert (file == view->details->activation_file); + +    open_in_same_slot = +        (view->details->activation_flags & +         (CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW | +          CAJA_WINDOW_OPEN_FLAG_NEW_TAB)) == 0; + +    slot = caja_window_info_get_active_slot (view->details->window); + +    uri = caja_file_get_activation_uri (file); +    if (caja_file_is_launcher (file)) +    { +        file_uri = caja_file_get_uri (file); +        caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, +                        "tree view launch_desktop_file window=%p: %s", +                        view->details->window, file_uri); +        caja_launch_desktop_file (screen, file_uri, NULL, NULL); +        g_free (file_uri); +    } +    else if (uri != NULL +             && caja_file_is_executable (file) +             && caja_file_can_execute (file) +             && !caja_file_is_directory (file)) +    { + +        file_uri = g_filename_from_uri (uri, NULL, NULL); + +        /* Non-local executables don't get launched. They act like non-executables. */ +        if (file_uri == NULL) +        { +            caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, +                            "tree view window_info_open_location window=%p: %s", +                            view->details->window, uri); +            location = g_file_new_for_uri (uri); +            caja_window_slot_info_open_location +            (slot, +             location, +             CAJA_WINDOW_OPEN_ACCORDING_TO_MODE, +             view->details->activation_flags, +             NULL); +            g_object_unref (location); +        } +        else +        { +            caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, +                            "tree view launch_application_from_command window=%p: %s", +                            view->details->window, file_uri); +            caja_launch_application_from_command (screen, NULL, file_uri, FALSE, NULL); +            g_free (file_uri); +        } + +    } +    else if (uri != NULL) +    { +        if (!open_in_same_slot || +                view->details->selection_location == NULL || +                strcmp (uri, view->details->selection_location) != 0) +        { +            if (open_in_same_slot) +            { +                if (view->details->selection_location != NULL) +                { +                    g_free (view->details->selection_location); +                } +                view->details->selection_location = g_strdup (uri); +            } + +            caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, +                            "tree view window_info_open_location window=%p: %s", +                            view->details->window, uri); +            location = g_file_new_for_uri (uri); +            caja_window_slot_info_open_location +            (slot, +             location, +             CAJA_WINDOW_OPEN_ACCORDING_TO_MODE, +             view->details->activation_flags, +             NULL); +            g_object_unref (location); +        } +    } + +    g_free (uri); +    caja_file_unref (view->details->activation_file); +    view->details->activation_file = NULL; +} + +static void +cancel_activation (FMTreeView *view) +{ +    if (view->details->activation_file == NULL) +    { +        return; +    } + +    caja_file_cancel_call_when_ready +    (view->details->activation_file, +     got_activation_uri_callback, view); +    caja_file_unref (view->details->activation_file); +    view->details->activation_file = NULL; +} + +static void +row_activated_callback (GtkTreeView *treeview, GtkTreePath *path, +                        GtkTreeViewColumn *column, FMTreeView *view) +{ +    if (gtk_tree_view_row_expanded (view->details->tree_widget, path)) +    { +        gtk_tree_view_collapse_row (view->details->tree_widget, path); +    } +    else +    { +        gtk_tree_view_expand_row (view->details->tree_widget, +                                  path, FALSE); +    } +} + +static gboolean +selection_changed_timer_callback(FMTreeView *view) +{ +    CajaFileAttributes attributes; +    GtkTreeIter iter; +    GtkTreeSelection *selection; + +    selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view->details->tree_widget)); + +    /* no activation if popup menu is open */ +    if (view->details->popup_file != NULL) +    { +        return FALSE; +    } + +    cancel_activation (view); + +    if (!gtk_tree_selection_get_selected (selection, NULL, &iter)) +    { +        return FALSE; +    } + +    view->details->activation_file = sort_model_iter_to_file (view, &iter); +    if (view->details->activation_file == NULL) +    { +        return FALSE; +    } +    view->details->activation_flags = 0; + +    attributes = CAJA_FILE_ATTRIBUTE_INFO | CAJA_FILE_ATTRIBUTE_LINK_INFO; +    caja_file_call_when_ready (view->details->activation_file, attributes, +                               got_activation_uri_callback, view); +    return FALSE; /* remove timeout */ +} + +static void +selection_changed_callback (GtkTreeSelection *selection, +                            FMTreeView *view) +{ +    GdkEvent *event; +    gboolean is_keyboard; + +    if (view->details->selection_changed_timer) +    { +        g_source_remove (view->details->selection_changed_timer); +        view->details->selection_changed_timer = 0; +    } + +    event = gtk_get_current_event (); +    if (event) +    { +        is_keyboard = (event->type == GDK_KEY_PRESS || event->type == GDK_KEY_RELEASE); +        gdk_event_free (event); + +        if (is_keyboard) +        { +            /* on keyboard event: delay the change */ +            /* TODO: make dependent on keyboard repeat rate as per Markus Bertheau ? */ +            view->details->selection_changed_timer = g_timeout_add (300, (GSourceFunc) selection_changed_timer_callback, view); +        } +        else +        { +            /* on mouse event: show the change immediately */ +            selection_changed_timer_callback (view); +        } +    } +} + +static int +compare_rows (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer callback_data) +{ +    CajaFile *file_a, *file_b; +    int result; + +    /* Dummy rows are always first */ +    if (a->user_data == NULL) +    { +        return -1; +    } +    else if (b->user_data == NULL) +    { +        return 1; +    } + +    /* don't sort root nodes */ +    if (fm_tree_model_iter_is_root (FM_TREE_MODEL (model), a) && +            fm_tree_model_iter_is_root (FM_TREE_MODEL (model), b)) +    { +        return fm_tree_model_iter_compare_roots (FM_TREE_MODEL (model), a, b); +    } + +    file_a = fm_tree_model_iter_get_file (FM_TREE_MODEL (model), a); +    file_b = fm_tree_model_iter_get_file (FM_TREE_MODEL (model), b); + +    if (file_a == file_b) +    { +        result = 0; +    } +    else if (file_a == NULL) +    { +        result = -1; +    } +    else if (file_b == NULL) +    { +        result = +1; +    } +    else +    { +        result = caja_file_compare_for_sort (file_a, file_b, +                                             CAJA_FILE_SORT_BY_DISPLAY_NAME, +                                             FALSE, FALSE); +    } + +    caja_file_unref (file_a); +    caja_file_unref (file_b); + +    return result; +} + + +static char * +get_root_uri_callback (CajaTreeViewDragDest *dest, +                       gpointer user_data) +{ +    FMTreeView *view; + +    view = FM_TREE_VIEW (user_data); + +    /* Don't allow drops on background */ +    return NULL; +} + +static CajaFile * +get_file_for_path_callback (CajaTreeViewDragDest *dest, +                            GtkTreePath *path, +                            gpointer user_data) +{ +    FMTreeView *view; + +    view = FM_TREE_VIEW (user_data); + +    return sort_model_path_to_file (view, path); +} + +static void +move_copy_items_callback (CajaTreeViewDragDest *dest, +                          const GList *item_uris, +                          const char *target_uri, +                          GdkDragAction action, +                          int x, +                          int y, +                          gpointer user_data) +{ +    FMTreeView *view; + +    view = FM_TREE_VIEW (user_data); + +    caja_clipboard_clear_if_colliding_uris (GTK_WIDGET (view), +                                            item_uris, +                                            copied_files_atom); +    caja_file_operations_copy_move +    (item_uris, +     NULL, +     target_uri, +     action, +     GTK_WIDGET (view->details->tree_widget), +     NULL, NULL); +} + +static void +add_root_for_mount (FMTreeView *view, +                    GMount *mount) +{ +    char *mount_uri, *name; +    GFile *root; +    GIcon *icon; + +    if (g_mount_is_shadowed (mount)) +        return; + +    icon = g_mount_get_icon (mount); +    root = g_mount_get_root (mount); +    mount_uri = g_file_get_uri (root); +    g_object_unref (root); +    name = g_mount_get_name (mount); + +    fm_tree_model_add_root_uri(view->details->child_model, +                               mount_uri, name, icon, mount); + +    g_object_unref (icon); +    g_free (name); +    g_free (mount_uri); + +} + +static void +mount_added_callback (GVolumeMonitor *volume_monitor, +                      GMount *mount, +                      FMTreeView *view) +{ +    add_root_for_mount (view, mount); +} + +static void +mount_removed_callback (GVolumeMonitor *volume_monitor, +                        GMount *mount, +                        FMTreeView *view) +{ +    GFile *root; +    char *mount_uri; + +    root = g_mount_get_root (mount); +    mount_uri = g_file_get_uri (root); +    g_object_unref (root); +    fm_tree_model_remove_root_uri (view->details->child_model, +                                   mount_uri); +    g_free (mount_uri); +} + +static void +clipboard_contents_received_callback (GtkClipboard     *clipboard, +                                      GtkSelectionData *selection_data, +                                      gpointer          data) +{ +    FMTreeView *view; + +    view = FM_TREE_VIEW (data); + +    if (gtk_selection_data_get_data_type (selection_data) == copied_files_atom +            && gtk_selection_data_get_length (selection_data) > 0 && +            view->details->popup != NULL) +    { +        gtk_widget_set_sensitive (view->details->popup_paste, TRUE); +    } + +    g_object_unref (view); +} + +static gboolean +is_parent_writable (CajaFile *file) +{ +    CajaFile *parent; +    gboolean result; + +    parent = caja_file_get_parent (file); + +    /* No parent directory, return FALSE */ +    if (parent == NULL) +    { +        return FALSE; +    } + +    result = caja_file_can_write (parent); +    caja_file_unref (parent); + +    return result; +} + +static gboolean +button_pressed_callback (GtkTreeView *treeview, GdkEventButton *event, +                         FMTreeView *view) +{ +    GtkTreePath *path, *cursor_path; +    gboolean parent_file_is_writable; +    gboolean file_is_home_or_desktop; +    gboolean file_is_special_link; +    gboolean can_move_file_to_trash; +    gboolean can_delete_file; + +    if (event->button == 3) +    { +        gboolean show_unmount = FALSE; +        gboolean show_eject = FALSE; +        GMount *mount = NULL; + +        if (view->details->popup_file != NULL) +        { +            return FALSE; /* Already up, ignore */ +        } + +        if (!gtk_tree_view_get_path_at_pos (treeview, event->x, event->y, +                                            &path, NULL, NULL, NULL)) +        { +            return FALSE; +        } + +        view->details->popup_file = sort_model_path_to_file (view, path); +        if (view->details->popup_file == NULL) +        { +            gtk_tree_path_free (path); +            return FALSE; +        } +        gtk_tree_view_get_cursor (view->details->tree_widget, &cursor_path, NULL); +        gtk_tree_view_set_cursor (view->details->tree_widget, path, NULL, FALSE); +        gtk_tree_path_free (path); + +        create_popup_menu (view); + +        gtk_widget_set_sensitive (view->details->popup_open_in_new_window, +                                  caja_file_is_directory (view->details->popup_file)); +        gtk_widget_set_sensitive (view->details->popup_create_folder, +                                  caja_file_is_directory (view->details->popup_file) && +                                  caja_file_can_write (view->details->popup_file)); +        gtk_widget_set_sensitive (view->details->popup_paste, FALSE); +        if (caja_file_is_directory (view->details->popup_file) && +                caja_file_can_write (view->details->popup_file)) +        { +            gtk_clipboard_request_contents (caja_clipboard_get (GTK_WIDGET (view->details->tree_widget)), +                                            copied_files_atom, +                                            clipboard_contents_received_callback, g_object_ref (view)); +        } +        can_move_file_to_trash = caja_file_can_trash (view->details->popup_file); +        gtk_widget_set_sensitive (view->details->popup_trash, can_move_file_to_trash); + +        if (show_delete_command_auto_value) +        { +            parent_file_is_writable = is_parent_writable (view->details->popup_file); +            file_is_home_or_desktop = caja_file_is_home (view->details->popup_file) +                                      || caja_file_is_desktop_directory (view->details->popup_file); +            file_is_special_link = CAJA_IS_DESKTOP_ICON_FILE (view->details->popup_file); + +            can_delete_file = parent_file_is_writable +                              && !file_is_home_or_desktop +                              && !file_is_special_link; + +            gtk_widget_show (view->details->popup_delete); +            gtk_widget_set_sensitive (view->details->popup_delete, can_delete_file); +        } +        else +        { +            gtk_widget_hide (view->details->popup_delete); +        } + +        mount = fm_tree_model_get_mount_for_root_node_file (view->details->child_model, view->details->popup_file); +        if (mount) +        { +            show_unmount = g_mount_can_unmount (mount); +            show_eject = g_mount_can_eject (mount); +        } + +        if (show_unmount) +        { +            gtk_widget_show (view->details->popup_unmount); +        } +        else +        { +            gtk_widget_hide (view->details->popup_unmount); +        } + +        if (show_eject) +        { +            gtk_widget_show (view->details->popup_eject); +        } +        else +        { +            gtk_widget_hide (view->details->popup_eject); +        } + +        if (show_unmount || show_eject) +        { +            gtk_widget_show (view->details->popup_unmount_separator); +        } +        else +        { +            gtk_widget_hide (view->details->popup_unmount_separator); +        } + +        gtk_menu_popup (GTK_MENU (view->details->popup), +                        NULL, NULL, NULL, NULL, +                        event->button, event->time); + +        gtk_tree_view_set_cursor (view->details->tree_widget, cursor_path, NULL, FALSE); +        gtk_tree_path_free (cursor_path); + +        return TRUE; +    } +    else if (event->button == 2 && event->type == GDK_BUTTON_PRESS) +    { +        CajaFile *file; + +        if (!gtk_tree_view_get_path_at_pos (treeview, event->x, event->y, +                                            &path, NULL, NULL, NULL)) +        { +            return FALSE; +        } + +        file = sort_model_path_to_file (view, path); +        if (file) +        { +            fm_tree_view_activate_file (view, file, +                                        (event->state & GDK_CONTROL_MASK) != 0 ? +                                        CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW : +                                        CAJA_WINDOW_OPEN_FLAG_NEW_TAB); +            caja_file_unref (file); +        } + +        gtk_tree_path_free (path); + +        return TRUE; +    } + +    return FALSE; +} + +static void +fm_tree_view_activate_file (FMTreeView *view, +                            CajaFile *file, +                            CajaWindowOpenFlags flags) +{ +    CajaFileAttributes attributes; + +    cancel_activation (view); + +    view->details->activation_file = caja_file_ref (file); +    view->details->activation_flags = flags; + +    attributes = CAJA_FILE_ATTRIBUTE_INFO | CAJA_FILE_ATTRIBUTE_LINK_INFO; +    caja_file_call_when_ready (view->details->activation_file, attributes, +                               got_activation_uri_callback, view); +} + +static void +fm_tree_view_open_cb (GtkWidget *menu_item, +                      FMTreeView *view) +{ +    fm_tree_view_activate_file (view, view->details->popup_file, 0); +} + +static void +fm_tree_view_open_in_new_tab_cb (GtkWidget *menu_item, +                                 FMTreeView *view) +{ +    fm_tree_view_activate_file (view, view->details->popup_file, CAJA_WINDOW_OPEN_FLAG_NEW_TAB); +} + +static void +fm_tree_view_open_in_new_window_cb (GtkWidget *menu_item, +                                    FMTreeView *view) +{ +    fm_tree_view_activate_file (view, view->details->popup_file, CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW); +} + +static void +new_folder_done (GFile *new_folder, gpointer data) +{ +    GList *list; + +    /* show the properties window for the newly created +     * folder so the user can change its name +     */ +    list = g_list_prepend (NULL, caja_file_get (new_folder)); + +    fm_properties_window_present (list, GTK_WIDGET (data)); + +    caja_file_list_free (list); +} + +static void +fm_tree_view_create_folder_cb (GtkWidget *menu_item, +                               FMTreeView *view) +{ +    char *parent_uri; + +    parent_uri = caja_file_get_uri (view->details->popup_file); +    caja_file_operations_new_folder (GTK_WIDGET (view->details->tree_widget), +                                     NULL, +                                     parent_uri, +                                     new_folder_done, view->details->tree_widget); + +    g_free (parent_uri); +} + +static void +copy_or_cut_files (FMTreeView *view, +                   gboolean cut) +{ +    char *status_string, *name; +    CajaClipboardInfo info; +    GtkTargetList *target_list; +    GtkTargetEntry *targets; +    int n_targets; + +    info.cut = cut; +    info.files = g_list_prepend (NULL, view->details->popup_file); + +    target_list = gtk_target_list_new (NULL, 0); +    gtk_target_list_add (target_list, copied_files_atom, 0, 0); +    gtk_target_list_add_uri_targets (target_list, 0); +    gtk_target_list_add_text_targets (target_list, 0); + +    targets = gtk_target_table_new_from_list (target_list, &n_targets); +    gtk_target_list_unref (target_list); + +    gtk_clipboard_set_with_data (caja_clipboard_get (GTK_WIDGET (view->details->tree_widget)), +                                 targets, n_targets, +                                 caja_get_clipboard_callback, caja_clear_clipboard_callback, +                                 NULL); +    gtk_target_table_free (targets, n_targets); + +    caja_clipboard_monitor_set_clipboard_info (caja_clipboard_monitor_get (), +            &info); +    g_list_free (info.files); + +    name = caja_file_get_display_name (view->details->popup_file); +    if (cut) +    { +        status_string = g_strdup_printf (_("\"%s\" will be moved " +                                           "if you select the Paste command"), +                                         name); +    } +    else +    { +        status_string = g_strdup_printf (_("\"%s\" will be copied " +                                           "if you select the Paste command"), +                                         name); +    } +    g_free (name); + +    caja_window_info_push_status (view->details->window, +                                  status_string); +    g_free (status_string); +} + +static void +fm_tree_view_cut_cb (GtkWidget *menu_item, +                     FMTreeView *view) +{ +    copy_or_cut_files (view, TRUE); +} + +static void +fm_tree_view_copy_cb (GtkWidget *menu_item, +                      FMTreeView *view) +{ +    copy_or_cut_files (view, FALSE); +} + +static void +paste_clipboard_data (FMTreeView *view, +                      GtkSelectionData *selection_data, +                      char *destination_uri) +{ +    gboolean cut; +    GList *item_uris; + +    cut = FALSE; +    item_uris = caja_clipboard_get_uri_list_from_selection_data (selection_data, &cut, +                copied_files_atom); + +    if (item_uris == NULL|| destination_uri == NULL) +    { +        caja_window_info_push_status (view->details->window, +                                      _("There is nothing on the clipboard to paste.")); +    } +    else +    { +        caja_file_operations_copy_move +        (item_uris, NULL, destination_uri, +         cut ? GDK_ACTION_MOVE : GDK_ACTION_COPY, +         GTK_WIDGET (view->details->tree_widget), +         NULL, NULL); + +        /* If items are cut then remove from clipboard */ +        if (cut) +        { +            gtk_clipboard_clear (caja_clipboard_get (GTK_WIDGET (view))); +        } + +        eel_g_list_free_deep (item_uris); +    } +} + +static void +paste_into_clipboard_received_callback (GtkClipboard     *clipboard, +                                        GtkSelectionData *selection_data, +                                        gpointer          data) +{ +    FMTreeView *view; +    char *directory_uri; + +    view = FM_TREE_VIEW (data); + +    directory_uri = caja_file_get_uri (view->details->popup_file); + +    paste_clipboard_data (view, selection_data, directory_uri); + +    g_free (directory_uri); +} + +static void +fm_tree_view_paste_cb (GtkWidget *menu_item, +                       FMTreeView *view) +{ +    gtk_clipboard_request_contents (caja_clipboard_get (GTK_WIDGET (view->details->tree_widget)), +                                    copied_files_atom, +                                    paste_into_clipboard_received_callback, view); +} + +static GtkWindow * +fm_tree_view_get_containing_window (FMTreeView *view) +{ +    GtkWidget *window; + +    g_assert (FM_IS_TREE_VIEW (view)); + +    window = gtk_widget_get_ancestor (GTK_WIDGET (view), GTK_TYPE_WINDOW); +    if (window == NULL) +    { +        return NULL; +    } + +    return GTK_WINDOW (window); +} + +static void +fm_tree_view_trash_cb (GtkWidget *menu_item, +                       FMTreeView *view) +{ +    GList *list; + +    if (!caja_file_can_trash (view->details->popup_file)) +    { +        return; +    } + +    list = g_list_prepend (NULL, +                           caja_file_get_location (view->details->popup_file)); + +    caja_file_operations_trash_or_delete (list, +                                          fm_tree_view_get_containing_window (view), +                                          NULL, NULL); +    eel_g_object_list_free (list); +} + +static void +fm_tree_view_delete_cb (GtkWidget *menu_item, +                        FMTreeView *view) +{ +    GList *location_list; + +    if (!show_delete_command_auto_value) +    { +        return; +    } + +    location_list = g_list_prepend (NULL, +                                    caja_file_get_location (view->details->popup_file)); + +    caja_file_operations_delete (location_list, fm_tree_view_get_containing_window (view), NULL, NULL); +    eel_g_object_list_free (location_list); +} + +static void +fm_tree_view_properties_cb (GtkWidget *menu_item, +                            FMTreeView *view) +{ +    GList *list; + +    list = g_list_prepend (NULL, caja_file_ref (view->details->popup_file)); + +    fm_properties_window_present (list, GTK_WIDGET (view->details->tree_widget)); + +    caja_file_list_free (list); +} + +static void +fm_tree_view_unmount_cb (GtkWidget *menu_item, +                         FMTreeView *view) +{ +    CajaFile *file = view->details->popup_file; +    GMount *mount; + +    if (file == NULL) +    { +        return; +    } + +    mount = fm_tree_model_get_mount_for_root_node_file (view->details->child_model, file); + +    if (mount != NULL) +    { +        caja_file_operations_unmount_mount (fm_tree_view_get_containing_window (view), +                                            mount, FALSE, TRUE); +    } +} + +static void +fm_tree_view_eject_cb (GtkWidget *menu_item, +                       FMTreeView *view) +{ +    CajaFile *file = view->details->popup_file; +    GMount *mount; + +    if (file == NULL) +    { +        return; +    } + +    mount = fm_tree_model_get_mount_for_root_node_file (view->details->child_model, file); + +    if (mount != NULL) +    { +        caja_file_operations_unmount_mount (fm_tree_view_get_containing_window (view), +                                            mount, TRUE, TRUE); +    } +} + +static gboolean +free_popup_file_in_idle_cb (gpointer data) +{ +    FMTreeView *view; + +    view = FM_TREE_VIEW (data); + +    if (view->details->popup_file != NULL) +    { +        caja_file_unref (view->details->popup_file); +        view->details->popup_file = NULL; +    } +    view->details->popup_file_idle_handler = 0; +    return FALSE; +} + +static void +popup_menu_deactivated (GtkMenuShell *menu_shell, gpointer data) +{ +    FMTreeView *view; + +    view = FM_TREE_VIEW (data); + +    /* The popup menu is deactivated. (I.E. hidden) +       We want to free popup_file, but can't right away as it might immediately get +       used if we're deactivation due to activating a menu item. So, we free it in +       idle */ + +    if (view->details->popup_file != NULL && +            view->details->popup_file_idle_handler == 0) +    { +        view->details->popup_file_idle_handler = g_idle_add (free_popup_file_in_idle_cb, view); +    } +} + +static void +create_popup_menu (FMTreeView *view) +{ +    GtkWidget *popup, *menu_item, *menu_image; + +    if (view->details->popup != NULL) +    { +        /* already created */ +        return; +    } + +    popup = gtk_menu_new (); + +    g_signal_connect (popup, "deactivate", +                      G_CALLBACK (popup_menu_deactivated), +                      view); + + +    /* add the "open" menu item */ +    menu_image = gtk_image_new_from_stock (GTK_STOCK_OPEN, +                                           GTK_ICON_SIZE_MENU); +    gtk_widget_show (menu_image); +    menu_item = gtk_image_menu_item_new_with_mnemonic (_("_Open")); +    gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item), +                                   menu_image); +    g_signal_connect (menu_item, "activate", +                      G_CALLBACK (fm_tree_view_open_cb), +                      view); +    gtk_widget_show (menu_item); +    gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); +    view->details->popup_open = menu_item; + +    /* add the "open in new tab" menu item */ +    menu_item = gtk_menu_item_new_with_mnemonic (_("Open in New _Tab")); +    g_signal_connect (menu_item, "activate", +                      G_CALLBACK (fm_tree_view_open_in_new_tab_cb), +                      view); +    gtk_widget_show (menu_item); +    gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); +    view->details->popup_open_in_new_window = menu_item; + +    /* add the "open in new window" menu item */ +    menu_item = gtk_menu_item_new_with_mnemonic (_("Open in New _Window")); +    g_signal_connect (menu_item, "activate", +                      G_CALLBACK (fm_tree_view_open_in_new_window_cb), +                      view); +    gtk_widget_show (menu_item); +    gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); +    view->details->popup_open_in_new_window = menu_item; + +    eel_gtk_menu_append_separator (GTK_MENU (popup)); + +    /* add the "create folder" menu item */ +    menu_item = gtk_image_menu_item_new_with_mnemonic (_("Create _Folder")); +    g_signal_connect (menu_item, "activate", +                      G_CALLBACK (fm_tree_view_create_folder_cb), +                      view); +    gtk_widget_show (menu_item); +    gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); +    view->details->popup_create_folder = menu_item; + +    eel_gtk_menu_append_separator (GTK_MENU (popup)); + +    /* add the "cut folder" menu item */ +    menu_item = gtk_image_menu_item_new_from_stock (GTK_STOCK_CUT, NULL); +    g_signal_connect (menu_item, "activate", +                      G_CALLBACK (fm_tree_view_cut_cb), +                      view); +    gtk_widget_show (menu_item); +    gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); +    view->details->popup_cut = menu_item; + +    /* add the "copy folder" menu item */ +    menu_item = gtk_image_menu_item_new_from_stock (GTK_STOCK_COPY, NULL); +    g_signal_connect (menu_item, "activate", +                      G_CALLBACK (fm_tree_view_copy_cb), +                      view); +    gtk_widget_show (menu_item); +    gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); +    view->details->popup_copy = menu_item; + +    /* add the "paste files into folder" menu item */ +    menu_image = gtk_image_new_from_stock (GTK_STOCK_PASTE, +                                           GTK_ICON_SIZE_MENU); +    gtk_widget_show (menu_image); +    menu_item = gtk_image_menu_item_new_with_mnemonic (_("_Paste Into Folder")); +    gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item), +                                   menu_image); +    g_signal_connect (menu_item, "activate", +                      G_CALLBACK (fm_tree_view_paste_cb), +                      view); +    gtk_widget_show (menu_item); +    gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); +    view->details->popup_paste = menu_item; + +    eel_gtk_menu_append_separator (GTK_MENU (popup)); + +    /* add the "move to trash" menu item */ +    menu_image = gtk_image_new_from_icon_name (CAJA_ICON_TRASH_FULL, +                 GTK_ICON_SIZE_MENU); +    gtk_widget_show (menu_image); +    menu_item = gtk_image_menu_item_new_with_mnemonic (_("Mo_ve to Trash")); +    gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item), +                                   menu_image); +    g_signal_connect (menu_item, "activate", +                      G_CALLBACK (fm_tree_view_trash_cb), +                      view); +    gtk_widget_show (menu_item); +    gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); +    view->details->popup_trash = menu_item; + +    /* add the "delete" menu item */ +    menu_image = gtk_image_new_from_icon_name (CAJA_ICON_DELETE, +                 GTK_ICON_SIZE_MENU); +    gtk_widget_show (menu_image); +    menu_item = gtk_image_menu_item_new_with_mnemonic (_("_Delete")); +    gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item), +                                   menu_image); +    g_signal_connect (menu_item, "activate", +                      G_CALLBACK (fm_tree_view_delete_cb), +                      view); +    gtk_widget_show (menu_item); +    gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); +    view->details->popup_delete = menu_item; + +    eel_gtk_menu_append_separator (GTK_MENU (popup)); + +    /* add the "Unmount" menu item */ +    menu_item = gtk_image_menu_item_new_with_mnemonic (_("_Unmount")); +    g_signal_connect (menu_item, "activate", +                      G_CALLBACK (fm_tree_view_unmount_cb), +                      view); +    gtk_widget_show (menu_item); +    gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); +    view->details->popup_unmount = menu_item; + +    /* add the "Eject" menu item */ +    menu_item = gtk_image_menu_item_new_with_mnemonic (_("_Eject")); +    g_signal_connect (menu_item, "activate", +                      G_CALLBACK (fm_tree_view_eject_cb), +                      view); +    gtk_widget_show (menu_item); +    gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); +    view->details->popup_eject = menu_item; + +    /* add the unmount separator menu item */ +    view->details->popup_unmount_separator = +        GTK_WIDGET (eel_gtk_menu_append_separator (GTK_MENU (popup))); + +    /* add the "properties" menu item */ +    menu_item = gtk_image_menu_item_new_from_stock (GTK_STOCK_PROPERTIES, NULL); +    g_signal_connect (menu_item, "activate", +                      G_CALLBACK (fm_tree_view_properties_cb), +                      view); +    gtk_widget_show (menu_item); +    gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); +    view->details->popup_properties = menu_item; + +    view->details->popup = popup; +} + +static void +create_tree (FMTreeView *view) +{ +    GtkCellRenderer *cell; +    GtkTreeViewColumn *column; +    GVolumeMonitor *volume_monitor; +    char *home_uri; +    GList *mounts, *l; +    char *location; +    GIcon *icon; +    CajaWindowSlotInfo *slot; + +    view->details->child_model = fm_tree_model_new (); +    view->details->sort_model = GTK_TREE_MODEL_SORT +                                (gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (view->details->child_model))); +    view->details->tree_widget = GTK_TREE_VIEW +                                 (gtk_tree_view_new_with_model (GTK_TREE_MODEL (view->details->sort_model))); +    g_object_unref (view->details->sort_model); + +    gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (view->details->sort_model), +            compare_rows, view, NULL); + +    g_signal_connect_object +    (view->details->child_model, "row_loaded", +     G_CALLBACK (row_loaded_callback), +     view, G_CONNECT_AFTER); +    home_uri = caja_get_home_directory_uri (); +    icon = g_themed_icon_new (CAJA_ICON_HOME); +    fm_tree_model_add_root_uri (view->details->child_model, home_uri, _("Home Folder"), icon, NULL); +    g_object_unref (icon); +    g_free (home_uri); +    icon = g_themed_icon_new (CAJA_ICON_FILESYSTEM); +    fm_tree_model_add_root_uri (view->details->child_model, "file:///", _("File System"), icon, NULL); +    g_object_unref (icon); +#ifdef NOT_YET_USABLE /* Do we really want this? */ +    icon = g_themed_icon_new (CAJA_ICON_NETWORK); +    fm_tree_model_add_root_uri (view->details->child_model, "network:///", _("Network Neighbourhood"), icon, NULL); +    g_object_unref (icon); +#endif + +    volume_monitor = g_volume_monitor_get (); +    view->details->volume_monitor = volume_monitor; +    mounts = g_volume_monitor_get_mounts (volume_monitor); +    for (l = mounts; l != NULL; l = l->next) +    { +        add_root_for_mount (view, l->data); +        g_object_unref (l->data); +    } +    g_list_free (mounts); + +    g_signal_connect_object (volume_monitor, "mount_added", +                             G_CALLBACK (mount_added_callback), view, 0); +    g_signal_connect_object (volume_monitor, "mount_removed", +                             G_CALLBACK (mount_removed_callback), view, 0); + +    g_object_unref (view->details->child_model); + +    gtk_tree_view_set_headers_visible (view->details->tree_widget, FALSE); + +    view->details->drag_dest = +        caja_tree_view_drag_dest_new (view->details->tree_widget); +    g_signal_connect_object (view->details->drag_dest, +                             "get_root_uri", +                             G_CALLBACK (get_root_uri_callback), +                             view, 0); +    g_signal_connect_object (view->details->drag_dest, +                             "get_file_for_path", +                             G_CALLBACK (get_file_for_path_callback), +                             view, 0); +    g_signal_connect_object (view->details->drag_dest, +                             "move_copy_items", +                             G_CALLBACK (move_copy_items_callback), +                             view, 0); + +    /* Create column */ +    column = gtk_tree_view_column_new (); + +    cell = caja_cell_renderer_pixbuf_emblem_new (); +    gtk_tree_view_column_pack_start (column, cell, FALSE); +    gtk_tree_view_column_set_attributes (column, cell, +                                         "pixbuf", FM_TREE_MODEL_CLOSED_PIXBUF_COLUMN, +                                         "pixbuf_expander_closed", FM_TREE_MODEL_CLOSED_PIXBUF_COLUMN, +                                         "pixbuf_expander_open", FM_TREE_MODEL_OPEN_PIXBUF_COLUMN, +                                         "pixbuf_emblem", FM_TREE_MODEL_EMBLEM_PIXBUF_COLUMN, +                                         NULL); + +    cell = gtk_cell_renderer_text_new (); +    gtk_tree_view_column_pack_start (column, cell, TRUE); +    gtk_tree_view_column_set_attributes (column, cell, +                                         "text", FM_TREE_MODEL_DISPLAY_NAME_COLUMN, +                                         "style", FM_TREE_MODEL_FONT_STYLE_COLUMN, +                                         NULL); + +    gtk_tree_view_append_column (view->details->tree_widget, column); + +    gtk_widget_show (GTK_WIDGET (view->details->tree_widget)); + +    gtk_container_add (GTK_CONTAINER (view), +                       GTK_WIDGET (view->details->tree_widget)); + +    g_signal_connect_object (gtk_tree_view_get_selection (GTK_TREE_VIEW (view->details->tree_widget)), "changed", +                             G_CALLBACK (selection_changed_callback), view, 0); + +    g_signal_connect (G_OBJECT (view->details->tree_widget), +                      "row-activated", G_CALLBACK (row_activated_callback), +                      view); + +    g_signal_connect (G_OBJECT (view->details->tree_widget), +                      "button_press_event", G_CALLBACK (button_pressed_callback), +                      view); + +    slot = caja_window_info_get_active_slot (view->details->window); +    location = caja_window_slot_info_get_current_location (slot); +    schedule_select_and_show_location (view, location); +    g_free (location); +} + +static void +update_filtering_from_preferences (FMTreeView *view) +{ +    CajaWindowShowHiddenFilesMode mode; + +    if (view->details->child_model == NULL) +    { +        return; +    } + +    mode = caja_window_info_get_hidden_files_mode (view->details->window); + +    if (mode == CAJA_WINDOW_SHOW_HIDDEN_FILES_DEFAULT) +    { +        fm_tree_model_set_show_hidden_files +        (view->details->child_model, +         eel_preferences_get_boolean (CAJA_PREFERENCES_SHOW_HIDDEN_FILES)); +    } +    else +    { +        fm_tree_model_set_show_hidden_files +        (view->details->child_model, +         mode == CAJA_WINDOW_SHOW_HIDDEN_FILES_ENABLE); +    } +    fm_tree_model_set_show_only_directories +    (view->details->child_model, +     eel_preferences_get_boolean (CAJA_PREFERENCES_TREE_SHOW_ONLY_DIRECTORIES)); +} + +static void +parent_set_callback (GtkWidget        *widget, +                     GtkWidget        *previous_parent, +                     gpointer          callback_data) +{ +    FMTreeView *view; + +    view = FM_TREE_VIEW (callback_data); + +    if (gtk_widget_get_parent (widget) != NULL && view->details->tree_widget == NULL) +    { +        create_tree (view); +        update_filtering_from_preferences (view); +    } +} + +static void +filtering_changed_callback (gpointer callback_data) +{ +    update_filtering_from_preferences (FM_TREE_VIEW (callback_data)); +} + +static void +loading_uri_callback (CajaWindowInfo *window, +                      char *location, +                      gpointer callback_data) +{ +    FMTreeView *view; + +    view = FM_TREE_VIEW (callback_data); +    schedule_select_and_show_location (view, location); +} + +static void +fm_tree_view_init (FMTreeView *view) +{ +    view->details = g_new0 (FMTreeViewDetails, 1); + +    gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (view), +                                    GTK_POLICY_AUTOMATIC, +                                    GTK_POLICY_AUTOMATIC); +    gtk_scrolled_window_set_hadjustment (GTK_SCROLLED_WINDOW (view), NULL); +    gtk_scrolled_window_set_vadjustment (GTK_SCROLLED_WINDOW (view), NULL); +    gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (view), GTK_SHADOW_IN); + +    gtk_widget_show (GTK_WIDGET (view)); + +    g_signal_connect_object (view, "parent_set", +                             G_CALLBACK (parent_set_callback), view, 0); + +    view->details->selection_location = NULL; + +    view->details->selecting = FALSE; + +    eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_SHOW_HIDDEN_FILES, +            filtering_changed_callback, view, G_OBJECT (view)); +    eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_SHOW_BACKUP_FILES, +            filtering_changed_callback, view, G_OBJECT (view)); +    eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_TREE_SHOW_ONLY_DIRECTORIES, +            filtering_changed_callback, view, G_OBJECT (view)); + +    view->details->popup_file = NULL; + +    view->details->clipboard_handler_id = +        g_signal_connect (caja_clipboard_monitor_get (), +                          "clipboard_info", +                          G_CALLBACK (notify_clipboard_info), view); +} + +static void +fm_tree_view_dispose (GObject *object) +{ +    FMTreeView *view; + +    view = FM_TREE_VIEW (object); + +    if (view->details->selection_changed_timer) +    { +        g_source_remove (view->details->selection_changed_timer); +        view->details->selection_changed_timer = 0; +    } + +    if (view->details->drag_dest) +    { +        g_object_unref (view->details->drag_dest); +        view->details->drag_dest = NULL; +    } + +    if (view->details->show_selection_idle_id) +    { +        g_source_remove (view->details->show_selection_idle_id); +        view->details->show_selection_idle_id = 0; +    } + +    if (view->details->clipboard_handler_id != 0) +    { +        g_signal_handler_disconnect (caja_clipboard_monitor_get (), +                                     view->details->clipboard_handler_id); +        view->details->clipboard_handler_id = 0; +    } + +    cancel_activation (view); + +    if (view->details->popup != NULL) +    { +        gtk_widget_destroy (view->details->popup); +        view->details->popup = NULL; +    } + +    if (view->details->popup_file_idle_handler != 0) +    { +        g_source_remove (view->details->popup_file_idle_handler); +        view->details->popup_file_idle_handler = 0; +    } + +    if (view->details->popup_file != NULL) +    { +        caja_file_unref (view->details->popup_file); +        view->details->popup_file = NULL; +    } + +    if (view->details->selection_location != NULL) +    { +        g_free (view->details->selection_location); +        view->details->selection_location = NULL; +    } + +    if (view->details->volume_monitor != NULL) +    { +        g_object_unref (view->details->volume_monitor); +        view->details->volume_monitor = NULL; +    } + +    view->details->window = NULL; + +    G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +fm_tree_view_finalize (GObject *object) +{ +    FMTreeView *view; + +    view = FM_TREE_VIEW (object); + +    g_free (view->details); + +    G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +fm_tree_view_class_init (FMTreeViewClass *class) +{ +    G_OBJECT_CLASS (class)->dispose = fm_tree_view_dispose; +    G_OBJECT_CLASS (class)->finalize = fm_tree_view_finalize; + +    copied_files_atom = gdk_atom_intern ("x-special/mate-copied-files", FALSE); + +    eel_preferences_add_auto_boolean (CAJA_PREFERENCES_ENABLE_DELETE, +                                      &show_delete_command_auto_value); +} + +static const char * +fm_tree_view_get_sidebar_id (CajaSidebar *sidebar) +{ +    return TREE_SIDEBAR_ID; +} + +static char * +fm_tree_view_get_tab_label (CajaSidebar *sidebar) +{ +    return g_strdup (_("Tree")); +} + +static char * +fm_tree_view_get_tab_tooltip (CajaSidebar *sidebar) +{ +    return g_strdup (_("Show Tree")); +} + +static GdkPixbuf * +fm_tree_view_get_tab_icon (CajaSidebar *sidebar) +{ +    return NULL; +} + +static void +fm_tree_view_is_visible_changed (CajaSidebar *sidebar, +                                 gboolean         is_visible) +{ +    /* Do nothing */ +} + +static void +hidden_files_mode_changed_callback (CajaWindowInfo *window, +                                    FMTreeView *view) +{ +    update_filtering_from_preferences (view); +} + +static void +fm_tree_view_iface_init (CajaSidebarIface *iface) +{ +    iface->get_sidebar_id = fm_tree_view_get_sidebar_id; +    iface->get_tab_label = fm_tree_view_get_tab_label; +    iface->get_tab_tooltip = fm_tree_view_get_tab_tooltip; +    iface->get_tab_icon = fm_tree_view_get_tab_icon; +    iface->is_visible_changed = fm_tree_view_is_visible_changed; +} + +static void +fm_tree_view_set_parent_window (FMTreeView *sidebar, +                                CajaWindowInfo *window) +{ +    char *location; +    CajaWindowSlotInfo *slot; + +    sidebar->details->window = window; + +    slot = caja_window_info_get_active_slot (window); + +    g_signal_connect_object (window, "loading_uri", +                             G_CALLBACK (loading_uri_callback), sidebar, 0); +    location = caja_window_slot_info_get_current_location (slot); +    loading_uri_callback (window, location, sidebar); +    g_free (location); + +    g_signal_connect_object (window, "hidden_files_mode_changed", +                             G_CALLBACK (hidden_files_mode_changed_callback), sidebar, 0); + +} + +static CajaSidebar * +fm_tree_view_create (CajaSidebarProvider *provider, +                     CajaWindowInfo *window) +{ +    FMTreeView *sidebar; + +    sidebar = g_object_new (fm_tree_view_get_type (), NULL); +    fm_tree_view_set_parent_window (sidebar, window); +    g_object_ref_sink (sidebar); + +    return CAJA_SIDEBAR (sidebar); +} + +static void +sidebar_provider_iface_init (CajaSidebarProviderIface *iface) +{ +    iface->create = fm_tree_view_create; +} + +static void +fm_tree_view_provider_init (FMTreeViewProvider *sidebar) +{ +} + +static void +fm_tree_view_provider_class_init (FMTreeViewProviderClass *class) +{ +} + +void +fm_tree_view_register (void) +{ +    caja_module_add_type (fm_tree_view_provider_get_type ()); +} diff --git a/src/file-manager/fm-tree-view.h b/src/file-manager/fm-tree-view.h new file mode 100644 index 00000000..a1bbe458 --- /dev/null +++ b/src/file-manager/fm-tree-view.h @@ -0,0 +1,65 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* + * Copyright (C) 2000, 2001 Eazel, Inc + * Copyright (C) 2002 Anders Carlsson + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Maciej Stachowiak <[email protected]> + *          Anders Carlsson <[email protected]> + */ + +/* fm-tree-view.h - tree view. */ + + +#ifndef FM_TREE_VIEW_H +#define FM_TREE_VIEW_H + +#include <gtk/gtk.h> + +#define FM_TYPE_TREE_VIEW fm_tree_view_get_type() +#define FM_TREE_VIEW(obj) \ +  (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_TREE_VIEW, FMTreeView)) +#define FM_TREE_VIEW_CLASS(klass) \ +  (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_TREE_VIEW, FMTreeViewClass)) +#define FM_IS_TREE_VIEW(obj) \ +  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_TREE_VIEW)) +#define FM_IS_TREE_VIEW_CLASS(klass) \ +  (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_TREE_VIEW)) +#define FM_TREE_VIEW_GET_CLASS(obj) \ +  (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_TREE_VIEW, FMTreeViewClass)) + +#define TREE_SIDEBAR_ID "CajaTreeSidebar" + +typedef struct FMTreeViewDetails FMTreeViewDetails; + +typedef struct +{ +    GtkScrolledWindow parent; + +    FMTreeViewDetails *details; +} FMTreeView; + +typedef struct +{ +    GtkScrolledWindowClass parent_class; +} FMTreeViewClass; + +GType fm_tree_view_get_type (void); +void fm_tree_view_register (void); + +#endif /* FM_TREE_VIEW_H */  | 
