summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am197
-rw-r--r--src/caja-actions.h56
-rw-r--r--src/caja-application.c2338
-rw-r--r--src/caja-application.h117
-rw-r--r--src/caja-autorun-software.c316
-rw-r--r--src/caja-bookmark-list.c775
-rw-r--r--src/caja-bookmark-list.h87
-rw-r--r--src/caja-bookmarks-window.c1112
-rw-r--r--src/caja-bookmarks-window.h39
-rw-r--r--src/caja-bookmarks-window.ui370
-rw-r--r--src/caja-connect-server-dialog-main.c235
-rw-r--r--src/caja-connect-server-dialog-nonmain.c59
-rw-r--r--src/caja-connect-server-dialog.c1075
-rw-r--r--src/caja-connect-server-dialog.h61
-rw-r--r--src/caja-convert-metadata.c492
-rw-r--r--src/caja-desktop-window.c277
-rw-r--r--src/caja-desktop-window.h66
-rw-r--r--src/caja-emblem-sidebar.c1174
-rw-r--r--src/caja-emblem-sidebar.h63
-rw-r--r--src/caja-file-management-properties-main.c64
-rw-r--r--src/caja-file-management-properties.c891
-rw-r--r--src/caja-file-management-properties.h41
-rw-r--r--src/caja-file-management-properties.ui3080
-rw-r--r--src/caja-history-sidebar.c423
-rw-r--r--src/caja-history-sidebar.h50
-rw-r--r--src/caja-image-properties-page.c745
-rw-r--r--src/caja-image-properties-page.h57
-rw-r--r--src/caja-information-panel.c1271
-rw-r--r--src/caja-information-panel.h66
-rw-r--r--src/caja-location-bar.c617
-rw-r--r--src/caja-location-bar.h70
-rw-r--r--src/caja-location-dialog.c275
-rw-r--r--src/caja-location-dialog.h55
-rw-r--r--src/caja-location-entry.c485
-rw-r--r--src/caja-location-entry.h72
-rw-r--r--src/caja-main.c590
-rw-r--r--src/caja-main.h37
-rw-r--r--src/caja-navigation-action.c381
-rw-r--r--src/caja-navigation-action.h68
-rw-r--r--src/caja-navigation-bar.c170
-rw-r--r--src/caja-navigation-bar.h77
-rw-r--r--src/caja-navigation-window-menus.c1099
-rw-r--r--src/caja-navigation-window-pane.c946
-rw-r--r--src/caja-navigation-window-pane.h92
-rw-r--r--src/caja-navigation-window-slot.c241
-rw-r--r--src/caja-navigation-window-slot.h78
-rw-r--r--src/caja-navigation-window-ui.xml77
-rw-r--r--src/caja-navigation-window.c1426
-rw-r--r--src/caja-navigation-window.h119
-rw-r--r--src/caja-notebook.c570
-rw-r--r--src/caja-notebook.h99
-rw-r--r--src/caja-notes-viewer.c535
-rw-r--r--src/caja-notes-viewer.h50
-rw-r--r--src/caja-pathbar.c2165
-rw-r--r--src/caja-pathbar.h90
-rw-r--r--src/caja-places-sidebar.c3092
-rw-r--r--src/caja-places-sidebar.h49
-rw-r--r--src/caja-property-browser.c2492
-rw-r--r--src/caja-property-browser.h71
-rw-r--r--src/caja-query-editor.c1397
-rw-r--r--src/caja-query-editor.h81
-rw-r--r--src/caja-search-bar.c256
-rw-r--r--src/caja-search-bar.h68
-rw-r--r--src/caja-self-check-functions.c40
-rw-r--r--src/caja-self-check-functions.h47
-rw-r--r--src/caja-shell-ui.xml87
-rw-r--r--src/caja-side-pane.c675
-rw-r--r--src/caja-side-pane.h82
-rw-r--r--src/caja-sidebar-title.c732
-rw-r--r--src/caja-sidebar-title.h76
-rw-r--r--src/caja-spatial-window-ui.xml29
-rw-r--r--src/caja-spatial-window.c1209
-rw-r--r--src/caja-spatial-window.h71
-rw-r--r--src/caja-trash-bar.c236
-rw-r--r--src/caja-trash-bar.h64
-rw-r--r--src/caja-view-as-action.c288
-rw-r--r--src/caja-view-as-action.h57
-rw-r--r--src/caja-window-bookmarks.c295
-rw-r--r--src/caja-window-bookmarks.h37
-rw-r--r--src/caja-window-manage-views.c2337
-rw-r--r--src/caja-window-manage-views.h49
-rw-r--r--src/caja-window-menus.c1163
-rw-r--r--src/caja-window-pane.c305
-rw-r--r--src/caja-window-pane.h97
-rw-r--r--src/caja-window-private.h250
-rw-r--r--src/caja-window-slot.c710
-rw-r--r--src/caja-window-slot.h186
-rw-r--r--src/caja-window-toolbars.c204
-rw-r--r--src/caja-window.c2164
-rw-r--r--src/caja-window.h164
-rw-r--r--src/caja-x-content-bar.c331
-rw-r--r--src/caja-x-content-bar.h71
-rw-r--r--src/caja-zoom-action.c211
-rw-r--r--src/caja-zoom-action.h57
-rw-r--r--src/caja-zoom-control.c996
-rw-r--r--src/caja-zoom-control.h91
-rwxr-xr-xsrc/check-caja2
-rw-r--r--src/file-manager/Makefile.am61
-rw-r--r--src/file-manager/caja-audio-mime-types.h46
-rw-r--r--src/file-manager/caja-desktop-icon-view-ui.xml21
-rw-r--r--src/file-manager/caja-directory-view-ui.xml231
-rw-r--r--src/file-manager/caja-icon-view-ui.xml56
-rw-r--r--src/file-manager/caja-list-view-ui.xml9
-rw-r--r--src/file-manager/fm-actions.h110
-rw-r--r--src/file-manager/fm-desktop-icon-view.c881
-rw-r--r--src/file-manager/fm-desktop-icon-view.h60
-rw-r--r--src/file-manager/fm-directory-view.c10936
-rw-r--r--src/file-manager/fm-directory-view.h495
-rw-r--r--src/file-manager/fm-ditem-page.c563
-rw-r--r--src/file-manager/fm-ditem-page.h51
-rw-r--r--src/file-manager/fm-empty-view.c412
-rw-r--r--src/file-manager/fm-empty-view.h60
-rw-r--r--src/file-manager/fm-error-reporting.c380
-rw-r--r--src/file-manager/fm-error-reporting.h55
-rw-r--r--src/file-manager/fm-icon-container.c625
-rw-r--r--src/file-manager/fm-icon-container.h68
-rw-r--r--src/file-manager/fm-icon-view.c3439
-rw-r--r--src/file-manager/fm-icon-view.h135
-rw-r--r--src/file-manager/fm-list-model.c1882
-rw-r--r--src/file-manager/fm-list-model.h148
-rw-r--r--src/file-manager/fm-list-view-private.h43
-rw-r--r--src/file-manager/fm-list-view.c3415
-rw-r--r--src/file-manager/fm-list-view.h63
-rw-r--r--src/file-manager/fm-properties-window.c5835
-rw-r--r--src/file-manager/fm-properties-window.h69
-rw-r--r--src/file-manager/fm-tree-model.c2152
-rw-r--r--src/file-manager/fm-tree-model.h104
-rw-r--r--src/file-manager/fm-tree-view.c1814
-rw-r--r--src/file-manager/fm-tree-view.h65
-rw-r--r--src/mate-network-scheme.desktop.in11
130 files changed, 81295 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 00000000..c929e6d3
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,197 @@
+include $(top_srcdir)/Makefile.shared
+
+SUBDIRS=file-manager
+
+bin_PROGRAMS = \
+ caja \
+ caja-file-management-properties \
+ caja-autorun-software \
+ caja-connect-server \
+ $(NULL)
+
+libexec_PROGRAMS = \
+ caja-convert-metadata \
+ $(NULL)
+
+INCLUDES = \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/cut-n-paste-code \
+ -I$(top_builddir)/libcaja-private \
+ $(CORE_CFLAGS) \
+ $(WARNING_CFLAGS) \
+ $(EXIF_CFLAGS) \
+ $(EXEMPI_CFLAGS) \
+ -DDATADIR=\""$(datadir)"\" \
+ -DLIBDIR=\""$(libdir)"\" \
+ -DLIBEXECDIR=\""$(libexecdir)"\" \
+ -DCAJA_DATADIR=\""$(datadir)/caja"\" \
+ -DUIDIR=\""$(datadir)/caja/ui"\" \
+ -DCAJA_PIXMAPDIR=\""$(datadir)/pixmaps/caja"\" \
+ -DPREFIX=\""$(prefix)"\" \
+ -DSYSCONFDIR=\""$(sysconfdir)"\" \
+ -DVERSION="\"$(VERSION)\"" \
+ $(DISABLE_DEPRECATED_CFLAGS) \
+ $(UNIQUE_CFLAGS) \
+ $(NULL)
+
+LDADD = \
+ $(top_builddir)/src/file-manager/libcaja-file-manager.la \
+ $(top_builddir)/libcaja-private/libcaja-private.la \
+ $(CORE_LIBS) \
+ $(EXIF_LIBS) \
+ $(EXEMPI_LIBS) \
+ $(POPT_LIBS) \
+ $(NULL)
+
+@INTLTOOL_DESKTOP_RULE@
+
+desktop_in_files=mate-network-scheme.desktop.in
+desktop_files=$(desktop_in_files:.desktop.in=.desktop)
+desktopdir = $(datadir)/mate/network/
+
+schemedir = $(datadir)/applications
+scheme_DATA = mate-network-scheme.desktop
+
+caja_SOURCES = \
+ caja-actions.h \
+ caja-application.c \
+ caja-application.h \
+ caja-bookmark-list.c \
+ caja-bookmark-list.h \
+ caja-bookmarks-window.c \
+ caja-bookmarks-window.h \
+ caja-connect-server-dialog.c \
+ caja-connect-server-dialog.h \
+ caja-connect-server-dialog-nonmain.c \
+ caja-desktop-window.c \
+ caja-desktop-window.h \
+ caja-emblem-sidebar.c \
+ caja-emblem-sidebar.h \
+ caja-file-management-properties.c \
+ caja-file-management-properties.h \
+ caja-history-sidebar.c \
+ caja-history-sidebar.h \
+ caja-image-properties-page.c \
+ caja-image-properties-page.h \
+ caja-information-panel.c \
+ caja-information-panel.h \
+ caja-location-bar.c \
+ caja-location-bar.h \
+ caja-location-dialog.c \
+ caja-location-dialog.h \
+ caja-location-entry.c \
+ caja-location-entry.h \
+ caja-main.c \
+ caja-main.h \
+ caja-navigation-action.c \
+ caja-navigation-action.h \
+ caja-navigation-bar.c \
+ caja-navigation-bar.h \
+ caja-navigation-window-menus.c \
+ caja-navigation-window.c \
+ caja-navigation-window.h \
+ caja-navigation-window-pane.c \
+ caja-navigation-window-pane.h \
+ caja-navigation-window-slot.c \
+ caja-navigation-window-slot.h \
+ caja-notebook.c \
+ caja-notebook.h \
+ caja-notes-viewer.c \
+ caja-notes-viewer.h \
+ caja-pathbar.c \
+ caja-pathbar.h \
+ caja-places-sidebar.c \
+ caja-places-sidebar.h \
+ caja-property-browser.c \
+ caja-property-browser.h \
+ caja-query-editor.c \
+ caja-query-editor.h \
+ caja-search-bar.c \
+ caja-search-bar.h \
+ caja-self-check-functions.c \
+ caja-self-check-functions.h \
+ caja-side-pane.c \
+ caja-side-pane.h \
+ caja-sidebar-title.c \
+ caja-sidebar-title.h \
+ caja-spatial-window.c \
+ caja-spatial-window.h \
+ caja-trash-bar.c \
+ caja-trash-bar.h \
+ caja-view-as-action.c \
+ caja-view-as-action.h \
+ caja-window-bookmarks.c \
+ caja-window-bookmarks.h \
+ caja-window-manage-views.c \
+ caja-window-manage-views.h \
+ caja-window-menus.c \
+ caja-window-pane.c \
+ caja-window-pane.h \
+ caja-window-private.h \
+ caja-window-slot.c \
+ caja-window-slot.h \
+ caja-window-toolbars.c \
+ caja-window.c \
+ caja-window.h \
+ caja-x-content-bar.c \
+ caja-x-content-bar.h \
+ caja-zoom-action.c \
+ caja-zoom-action.h \
+ caja-zoom-control.c \
+ caja-zoom-control.h \
+ $(NULL)
+
+caja_file_management_properties_SOURCES = \
+ caja-file-management-properties.c \
+ caja-file-management-properties.h \
+ caja-file-management-properties-main.c \
+ $(NULL)
+
+caja_autorun_software_SOURCES = \
+ caja-autorun-software.c \
+ $(NULL)
+
+caja_connect_server_SOURCES = \
+ caja-bookmark-list.c \
+ caja-bookmark-list.h \
+ caja-connect-server-dialog.c \
+ caja-connect-server-dialog.h \
+ caja-connect-server-dialog-main.c \
+ caja-location-entry.c \
+ caja-location-entry.h \
+ $(NULL)
+
+caja_convert_metadata_SOURCES = \
+ caja-convert-metadata.c \
+ $(NULL)
+
+TESTS=check-caja
+
+@INTLTOOL_SERVER_RULE@
+
+uidir = $(datadir)/caja/ui
+ui_DATA = \
+ caja-shell-ui.xml \
+ caja-navigation-window-ui.xml \
+ caja-spatial-window-ui.xml \
+ caja-file-management-properties.ui \
+ caja-bookmarks-window.ui \
+ $(NULL)
+
+CLEANFILES = \
+ $(desktop_files) \
+ $(server_DATA) \
+ $(NULL)
+
+EXTRA_DIST = \
+ $(server_in_files) \
+ $(ui_DATA) \
+ check-caja \
+ $(desktop_in_files) \
+ $(NULL)
+
+BUILT_SOURCES = \
+ $(NULL)
+
+dist-hook:
+ cd $(distdir); rm -f $(CLEANFILES)
diff --git a/src/caja-actions.h b/src/caja-actions.h
new file mode 100644
index 00000000..5736d1d4
--- /dev/null
+++ b/src/caja-actions.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2004 Red Hat, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Authors: Alexander Larsson <[email protected]>
+ *
+ */
+
+#ifndef CAJA_ACTIONS_H
+#define CAJA_ACTIONS_H
+
+#define CAJA_ACTION_STOP "Stop"
+#define CAJA_ACTION_RELOAD "Reload"
+#define CAJA_ACTION_BACK "Back"
+#define CAJA_ACTION_UP "Up"
+#define CAJA_ACTION_UP_ACCEL "UpAccel"
+#define CAJA_ACTION_UP_ACCEL "UpAccel"
+#define CAJA_ACTION_FORWARD "Forward"
+#define CAJA_ACTION_SHOW_HIDE_TOOLBAR "Show Hide Toolbar"
+#define CAJA_ACTION_SHOW_HIDE_SIDEBAR "Show Hide Sidebar"
+#define CAJA_ACTION_SHOW_HIDE_STATUSBAR "Show Hide Statusbar"
+#define CAJA_ACTION_SHOW_HIDE_LOCATION_BAR "Show Hide Location Bar"
+#define CAJA_ACTION_SHOW_HIDE_EXTRA_PANE "Show Hide Extra Pane"
+#define CAJA_ACTION_GO_TO_BURN_CD "Go to Burn CD"
+#define CAJA_ACTION_GO_TO_LOCATION "Go to Location"
+#define CAJA_ACTION_GO_HOME "Home"
+#define CAJA_ACTION_ADD_BOOKMARK "Add Bookmark"
+#define CAJA_ACTION_EDIT_BOOKMARKS "Edit Bookmarks"
+#define CAJA_ACTION_HOME "Home"
+#define CAJA_ACTION_ZOOM_IN "Zoom In"
+#define CAJA_ACTION_ZOOM_OUT "Zoom Out"
+#define CAJA_ACTION_ZOOM_NORMAL "Zoom Normal"
+#define CAJA_ACTION_SHOW_HIDDEN_FILES "Show Hidden Files"
+#define CAJA_ACTION_CLOSE "Close"
+#define CAJA_ACTION_SEARCH "Search"
+#define CAJA_ACTION_FOLDER_WINDOW "Folder Window"
+#define CAJA_ACTION_NEW_TAB "New Tab"
+
+#endif /* CAJA_ACTIONS_H */
diff --git a/src/caja-application.c b/src/caja-application.c
new file mode 100644
index 00000000..af2b586d
--- /dev/null
+++ b/src/caja-application.c
@@ -0,0 +1,2338 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 1999, 2000 Red Hat, Inc.
+ * Copyright (C) 2000, 2001 Eazel, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Authors: Elliot Lee <[email protected]>,
+ * Darin Adler <[email protected]>
+ *
+ */
+
+#include <config.h>
+#include "caja-application.h"
+
+#include "file-manager/fm-desktop-icon-view.h"
+#include "file-manager/fm-icon-view.h"
+#include "file-manager/fm-list-view.h"
+#include "file-manager/fm-tree-view.h"
+#if ENABLE_EMPTY_VIEW
+#include "file-manager/fm-empty-view.h"
+#endif /* ENABLE_EMPTY_VIEW */
+#include "caja-information-panel.h"
+#include "caja-history-sidebar.h"
+#include "caja-places-sidebar.h"
+#include "caja-notes-viewer.h"
+#include "caja-emblem-sidebar.h"
+#include "caja-image-properties-page.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+#include "caja-desktop-window.h"
+#include "caja-main.h"
+#include "caja-spatial-window.h"
+#include "caja-navigation-window.h"
+#include "caja-window-slot.h"
+#include "caja-navigation-window-slot.h"
+#include "caja-window-bookmarks.h"
+#include "libcaja-private/caja-file-operations.h"
+#include "caja-window-private.h"
+#include "caja-window-manage-views.h"
+#include <unistd.h>
+#include <libxml/xmlsave.h>
+#include <glib/gstdio.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <eel/eel-gtk-extensions.h>
+#include <eel/eel-gtk-macros.h>
+#include <eel/eel-stock-dialogs.h>
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+#include <libcaja-private/caja-debug-log.h>
+#include <libcaja-private/caja-file-utilities.h>
+#include <libcaja-private/caja-global-preferences.h>
+#include <libcaja-private/caja-module.h>
+#include <libcaja-private/caja-undo-manager.h>
+#include <libcaja-private/caja-desktop-link-monitor.h>
+#include <libcaja-private/caja-directory-private.h>
+#include <libcaja-private/caja-signaller.h>
+#include <libcaja-extension/caja-menu-provider.h>
+#include <libcaja-private/caja-autorun.h>
+
+enum {
+ COMMAND_0, /* unused: 0 is an invalid command */
+
+ COMMAND_START_DESKTOP,
+ COMMAND_STOP_DESKTOP,
+ COMMAND_OPEN_BROWSER,
+};
+
+/* Keep window from shrinking down ridiculously small; numbers are somewhat arbitrary */
+#define APPLICATION_WINDOW_MIN_WIDTH 300
+#define APPLICATION_WINDOW_MIN_HEIGHT 100
+
+#define START_STATE_CONFIG "start-state"
+
+#define CAJA_ACCEL_MAP_SAVE_DELAY 30
+
+/* Keeps track of all the desktop windows. */
+static GList *caja_application_desktop_windows;
+
+/* Keeps track of all the caja windows. */
+static GList *caja_application_window_list;
+
+/* Keeps track of all the object windows */
+static GList *caja_application_spatial_window_list;
+
+/* The saving of the accelerator map was requested */
+static gboolean save_of_accel_map_requested = FALSE;
+
+static void desktop_changed_callback (gpointer user_data);
+static void desktop_location_changed_callback (gpointer user_data);
+static void mount_removed_callback (GVolumeMonitor *monitor,
+ GMount *mount,
+ CajaApplication *application);
+static void mount_added_callback (GVolumeMonitor *monitor,
+ GMount *mount,
+ CajaApplication *application);
+static void volume_added_callback (GVolumeMonitor *monitor,
+ GVolume *volume,
+ CajaApplication *application);
+static void drive_connected_callback (GVolumeMonitor *monitor,
+ GDrive *drive,
+ CajaApplication *application);
+static void drive_listen_for_eject_button (GDrive *drive,
+ CajaApplication *application);
+static void caja_application_load_session (CajaApplication *application);
+static char * caja_application_get_session_data (void);
+
+G_DEFINE_TYPE (CajaApplication, caja_application, G_TYPE_OBJECT);
+
+static gboolean
+_unique_message_data_set_geometry_and_uris (UniqueMessageData *message_data,
+ const char *geometry,
+ char **uris)
+{
+ GString *list;
+ gint i;
+ gchar *result;
+ gsize length;
+
+ list = g_string_new (NULL);
+ if (geometry != NULL)
+ {
+ g_string_append (list, geometry);
+ }
+ g_string_append (list, "\r\n");
+
+ for (i = 0; uris != NULL && uris[i]; i++)
+ {
+ g_string_append (list, uris[i]);
+ g_string_append (list, "\r\n");
+ }
+
+ result = g_convert (list->str, list->len,
+ "ASCII", "UTF-8",
+ NULL, &length, NULL);
+ g_string_free (list, TRUE);
+
+ if (result)
+ {
+ unique_message_data_set (message_data, (guchar *) result, length);
+ g_free (result);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gchar **
+_unique_message_data_get_geometry_and_uris (UniqueMessageData *message_data,
+ char **geometry)
+{
+ gchar **result = NULL;
+
+ *geometry = NULL;
+
+ gchar *text, *newline, *uris;
+ text = unique_message_data_get_text (message_data);
+ if (text)
+ {
+ newline = strchr (text, '\n');
+ if (newline)
+ {
+ *geometry = g_strndup (text, newline-text);
+ uris = newline+1;
+ }
+ else
+ {
+ uris = text;
+ }
+
+ result = g_uri_list_extract_uris (uris);
+ g_free (text);
+ }
+
+ /* if the string is empty, make it NULL */
+ if (*geometry && strlen (*geometry) == 0)
+ {
+ g_free (*geometry);
+ *geometry = NULL;
+ }
+
+ return result;
+}
+
+GList *
+caja_application_get_window_list (void)
+{
+ return caja_application_window_list;
+}
+
+GList *
+caja_application_get_spatial_window_list (void)
+{
+ return caja_application_spatial_window_list;
+}
+
+unsigned int
+caja_application_get_n_windows (void)
+{
+ return g_list_length (caja_application_window_list) +
+ g_list_length (caja_application_desktop_windows);
+}
+
+static void
+startup_volume_mount_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_volume_mount_finish (G_VOLUME (source_object), res, NULL);
+}
+
+static void
+automount_all_volumes (CajaApplication *application)
+{
+ GList *volumes, *l;
+ GMount *mount;
+ GVolume *volume;
+
+ if (eel_preferences_get_boolean (CAJA_PREFERENCES_MEDIA_AUTOMOUNT))
+ {
+ /* automount all mountable volumes at start-up */
+ volumes = g_volume_monitor_get_volumes (application->volume_monitor);
+ for (l = volumes; l != NULL; l = l->next)
+ {
+ volume = l->data;
+
+ if (!g_volume_should_automount (volume) ||
+ !g_volume_can_mount (volume))
+ {
+ continue;
+ }
+
+ mount = g_volume_get_mount (volume);
+ if (mount != NULL)
+ {
+ g_object_unref (mount);
+ continue;
+ }
+
+ /* pass NULL as GMountOperation to avoid user interaction */
+ g_volume_mount (volume, 0, NULL, NULL, startup_volume_mount_cb, NULL);
+ }
+ eel_g_object_list_free (volumes);
+ }
+
+}
+
+static void
+smclient_save_state_cb (EggSMClient *client,
+ GKeyFile *state_file,
+ CajaApplication *application)
+{
+ char *data;
+
+ data = caja_application_get_session_data ();
+ if (data)
+ {
+ g_key_file_set_string (state_file,
+ "Caja",
+ "documents",
+ data);
+ }
+ g_free (data);
+}
+
+static void
+smclient_quit_cb (EggSMClient *client,
+ CajaApplication *application)
+{
+ caja_main_event_loop_quit (TRUE);
+}
+
+static void
+caja_application_init (CajaApplication *application)
+{
+ /* Create an undo manager */
+ application->undo_manager = caja_undo_manager_new ();
+
+ application->unique_app = unique_app_new_with_commands ("org.mate.Caja", NULL,
+ "start_desktop", COMMAND_START_DESKTOP,
+ "stop_desktop", COMMAND_STOP_DESKTOP,
+ "open_browser", COMMAND_OPEN_BROWSER,
+ NULL);
+
+
+ application->smclient = egg_sm_client_get ();
+ g_signal_connect (application->smclient, "save_state",
+ G_CALLBACK (smclient_save_state_cb),
+ application);
+ g_signal_connect (application->smclient, "quit",
+ G_CALLBACK (smclient_quit_cb),
+ application);
+ /* TODO: Should connect to quit_requested and block logout on active transfer? */
+
+ /* register views */
+ fm_icon_view_register ();
+ fm_desktop_icon_view_register ();
+ fm_list_view_register ();
+ fm_compact_view_register ();
+#if ENABLE_EMPTY_VIEW
+ fm_empty_view_register ();
+#endif /* ENABLE_EMPTY_VIEW */
+
+ /* register sidebars */
+ caja_places_sidebar_register ();
+ caja_information_panel_register ();
+ fm_tree_view_register ();
+ caja_history_sidebar_register ();
+ caja_notes_viewer_register (); /* also property page */
+ caja_emblem_sidebar_register ();
+
+ /* register property pages */
+ caja_image_properties_page_register ();
+
+ /* initialize search path for custom icons */
+ gtk_icon_theme_append_search_path (gtk_icon_theme_get_default (),
+ CAJA_DATADIR G_DIR_SEPARATOR_S "icons");
+}
+
+CajaApplication *
+caja_application_new (void)
+{
+ return g_object_new (CAJA_TYPE_APPLICATION, NULL);
+}
+
+static void
+caja_application_finalize (GObject *object)
+{
+ CajaApplication *application;
+
+ application = CAJA_APPLICATION (object);
+
+ caja_bookmarks_exiting ();
+
+ g_object_unref (application->undo_manager);
+
+ if (application->volume_monitor)
+ {
+ g_object_unref (application->volume_monitor);
+ application->volume_monitor = NULL;
+ }
+
+ g_object_unref (application->unique_app);
+
+ if (application->automount_idle_id != 0)
+ {
+ g_source_remove (application->automount_idle_id);
+ application->automount_idle_id = 0;
+ }
+
+ if (application->proxy != NULL)
+ {
+ g_object_unref (application->proxy);
+ application->proxy = NULL;
+ }
+
+ G_OBJECT_CLASS (caja_application_parent_class)->finalize (object);
+}
+
+static gboolean
+check_required_directories (CajaApplication *application)
+{
+ char *user_directory;
+ char *desktop_directory;
+ GSList *directories;
+ gboolean ret;
+
+ g_assert (CAJA_IS_APPLICATION (application));
+
+ ret = TRUE;
+
+ user_directory = caja_get_user_directory ();
+ desktop_directory = caja_get_desktop_directory ();
+
+ directories = NULL;
+
+ if (!g_file_test (user_directory, G_FILE_TEST_IS_DIR))
+ {
+ directories = g_slist_prepend (directories, user_directory);
+ }
+
+ if (!g_file_test (desktop_directory, G_FILE_TEST_IS_DIR))
+ {
+ directories = g_slist_prepend (directories, desktop_directory);
+ }
+
+ if (directories != NULL)
+ {
+ int failed_count;
+ GString *directories_as_string;
+ GSList *l;
+ char *error_string;
+ const char *detail_string;
+ GtkDialog *dialog;
+
+ ret = FALSE;
+
+ failed_count = g_slist_length (directories);
+
+ directories_as_string = g_string_new ((const char *)directories->data);
+ for (l = directories->next; l != NULL; l = l->next)
+ {
+ g_string_append_printf (directories_as_string, ", %s", (const char *)l->data);
+ }
+
+ if (failed_count == 1)
+ {
+ error_string = g_strdup_printf (_("Caja could not create the required folder \"%s\"."),
+ directories_as_string->str);
+ detail_string = _("Before running Caja, please create the following folder, or "
+ "set permissions such that Caja can create it.");
+ }
+ else
+ {
+ error_string = g_strdup_printf (_("Caja could not create the following required folders: "
+ "%s."), directories_as_string->str);
+ detail_string = _("Before running Caja, please create these folders, or "
+ "set permissions such that Caja can create them.");
+ }
+
+ dialog = eel_show_error_dialog (error_string, detail_string, NULL);
+ /* We need the main event loop so the user has a chance to see the dialog. */
+ caja_main_event_loop_register (GTK_OBJECT (dialog));
+
+ g_string_free (directories_as_string, TRUE);
+ g_free (error_string);
+ }
+
+ g_slist_free (directories);
+ g_free (user_directory);
+ g_free (desktop_directory);
+
+ return ret;
+}
+
+static void
+menu_provider_items_updated_handler (CajaMenuProvider *provider, GtkWidget* parent_window, gpointer data)
+{
+
+ g_signal_emit_by_name (caja_signaller_get_current (),
+ "popup_menu_changed");
+}
+
+static void
+menu_provider_init_callback (void)
+{
+ 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 = CAJA_MENU_PROVIDER (l->data);
+
+ g_signal_connect_after (G_OBJECT (provider), "items_updated",
+ (GCallback)menu_provider_items_updated_handler,
+ NULL);
+ }
+
+ caja_module_extension_list_free (providers);
+}
+
+static gboolean
+automount_all_volumes_idle_cb (gpointer data)
+{
+ CajaApplication *application = CAJA_APPLICATION (data);
+
+ automount_all_volumes (application);
+
+ application->automount_idle_id = 0;
+ return FALSE;
+}
+
+static void
+mark_desktop_files_trusted (void)
+{
+ char *do_once_file;
+ GFile *f, *c;
+ GFileEnumerator *e;
+ GFileInfo *info;
+ const char *name;
+ int fd;
+
+ do_once_file = g_build_filename (g_get_user_data_dir (),
+ ".converted-launchers", NULL);
+
+ if (g_file_test (do_once_file, G_FILE_TEST_EXISTS))
+ {
+ goto out;
+ }
+
+ f = caja_get_desktop_location ();
+ e = g_file_enumerate_children (f,
+ G_FILE_ATTRIBUTE_STANDARD_TYPE ","
+ G_FILE_ATTRIBUTE_STANDARD_NAME ","
+ G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE
+ ,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ NULL, NULL);
+ if (e == NULL)
+ {
+ goto out2;
+ }
+
+ while ((info = g_file_enumerator_next_file (e, NULL, NULL)) != NULL)
+ {
+ name = g_file_info_get_name (info);
+
+ if (g_str_has_suffix (name, ".desktop") &&
+ !g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE))
+ {
+ c = g_file_get_child (f, name);
+ caja_file_mark_desktop_file_trusted (c,
+ NULL, FALSE,
+ NULL, NULL);
+ g_object_unref (c);
+ }
+ g_object_unref (info);
+ }
+
+ g_object_unref (e);
+out2:
+ fd = g_creat (do_once_file, 0666);
+ close (fd);
+
+ g_object_unref (f);
+out:
+ g_free (do_once_file);
+}
+
+#define CK_NAME "org.freedesktop.ConsoleKit"
+#define CK_PATH "/org/freedesktop/ConsoleKit"
+#define CK_INTERFACE "org.freedesktop.ConsoleKit"
+
+static void
+ck_session_proxy_signal_cb (GDBusProxy *proxy,
+ const char *sender_name,
+ const char *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ CajaApplication *application = user_data;
+
+ if (g_strcmp0 (signal_name, "ActiveChanged") == 0)
+ {
+ g_variant_get (parameters, "(b)", &application->session_is_active);
+ }
+}
+
+static void
+ck_call_is_active_cb (GDBusProxy *proxy,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ CajaApplication *application = user_data;
+ GVariant *variant;
+ GError *error = NULL;
+
+ variant = g_dbus_proxy_call_finish (proxy, result, &error);
+
+ if (variant == NULL)
+ {
+ g_warning ("Error when calling IsActive(): %s\n", error->message);
+ application->session_is_active = TRUE;
+
+ g_error_free (error);
+ return;
+ }
+
+ g_variant_get (variant, "(b)", &application->session_is_active);
+
+ g_variant_unref (variant);
+}
+
+static void
+session_proxy_appeared (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ CajaApplication *application = user_data;
+ GDBusProxy *proxy;
+ GError *error = NULL;
+
+ proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
+
+ if (error != NULL)
+ {
+ g_warning ("Failed to get the current CK session: %s", error->message);
+ g_error_free (error);
+
+ application->session_is_active = TRUE;
+ return;
+ }
+
+ g_signal_connect (proxy, "g-signal",
+ G_CALLBACK (ck_session_proxy_signal_cb),
+ application);
+
+ g_dbus_proxy_call (proxy,
+ "IsActive",
+ g_variant_new ("()"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ (GAsyncReadyCallback) ck_call_is_active_cb,
+ application);
+
+ application->proxy = proxy;
+}
+
+static void
+ck_get_current_session_cb (GDBusConnection *connection,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ CajaApplication *application = user_data;
+ GVariant *variant;
+ const char *session_path = NULL;
+ GError *error = NULL;
+
+ variant = g_dbus_connection_call_finish (connection, result, &error);
+
+ if (variant == NULL)
+ {
+ g_warning ("Failed to get the current CK session: %s", error->message);
+ g_error_free (error);
+
+ application->session_is_active = TRUE;
+ return;
+ }
+
+ g_variant_get (variant, "(&o)", &session_path);
+
+ g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ CK_NAME,
+ session_path,
+ CK_INTERFACE ".Session",
+ NULL,
+ session_proxy_appeared,
+ application);
+
+ g_variant_unref (variant);
+}
+
+static void
+do_initialize_consolekit (CajaApplication *application)
+{
+ GDBusConnection *connection;
+
+ connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, NULL);
+
+ if (connection == NULL)
+ {
+ application->session_is_active = TRUE;
+ return;
+ }
+
+ g_dbus_connection_call (connection,
+ CK_NAME,
+ CK_PATH "/Manager",
+ CK_INTERFACE ".Manager",
+ "GetCurrentSession",
+ g_variant_new ("()"),
+ G_VARIANT_TYPE ("(o)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ (GAsyncReadyCallback) ck_get_current_session_cb,
+ application);
+
+ g_object_unref (connection);
+}
+
+static void
+do_upgrades_once (CajaApplication *application,
+ gboolean no_desktop)
+{
+ char *metafile_dir, *updated;
+ int fd;
+
+ if (!no_desktop)
+ {
+ mark_desktop_files_trusted ();
+ }
+
+ metafile_dir = g_build_filename(g_get_home_dir(), ".config", "caja", "metafiles", NULL);
+
+ if (g_file_test (metafile_dir, G_FILE_TEST_IS_DIR))
+ {
+ updated = g_build_filename (metafile_dir, "migrated-to-gvfs", NULL);
+ if (!g_file_test (updated, G_FILE_TEST_EXISTS))
+ {
+ g_spawn_command_line_async (LIBEXECDIR "/caja-convert-metadata --quiet", NULL);
+ fd = g_creat (updated, 0600);
+ if (fd != -1)
+ {
+ close (fd);
+ }
+ }
+ g_free (updated);
+ }
+ g_free (metafile_dir);
+}
+
+static void
+finish_startup (CajaApplication *application,
+ gboolean no_desktop)
+{
+ GList *drives;
+
+ do_upgrades_once (application, no_desktop);
+
+ /* initialize caja modules */
+ caja_module_setup ();
+
+ /* attach menu-provider module callback */
+ menu_provider_init_callback ();
+
+ /* Initialize the desktop link monitor singleton */
+ caja_desktop_link_monitor_get ();
+
+ /* Initialize the ConsoleKit listener for active session */
+ do_initialize_consolekit (application);
+
+ /* Watch for mounts so we can restore open windows This used
+ * to be for showing new window on mount, but is not used
+ * anymore */
+
+ /* Watch for unmounts so we can close open windows */
+ /* TODO-gio: This should be using the UNMOUNTED feature of GFileMonitor instead */
+ application->volume_monitor = g_volume_monitor_get ();
+ g_signal_connect_object (application->volume_monitor, "mount_removed",
+ G_CALLBACK (mount_removed_callback), application, 0);
+ g_signal_connect_object (application->volume_monitor, "mount_pre_unmount",
+ G_CALLBACK (mount_removed_callback), application, 0);
+ g_signal_connect_object (application->volume_monitor, "mount_added",
+ G_CALLBACK (mount_added_callback), application, 0);
+ g_signal_connect_object (application->volume_monitor, "volume_added",
+ G_CALLBACK (volume_added_callback), application, 0);
+ g_signal_connect_object (application->volume_monitor, "drive_connected",
+ G_CALLBACK (drive_connected_callback), application, 0);
+
+ /* listen for eject button presses */
+ drives = g_volume_monitor_get_connected_drives (application->volume_monitor);
+ g_list_foreach (drives, (GFunc) drive_listen_for_eject_button, application);
+ g_list_foreach (drives, (GFunc) g_object_unref, NULL);
+ g_list_free (drives);
+
+ application->automount_idle_id =
+ g_idle_add_full (G_PRIORITY_LOW,
+ automount_all_volumes_idle_cb,
+ application, NULL);
+}
+
+static void
+open_window (CajaApplication *application,
+ const char *startup_id,
+ const char *uri, GdkScreen *screen, const char *geometry,
+ gboolean browser_window)
+{
+ GFile *location;
+ CajaWindow *window;
+
+ if (browser_window ||
+ eel_preferences_get_boolean (CAJA_PREFERENCES_ALWAYS_USE_BROWSER))
+ {
+ window = caja_application_create_navigation_window (application,
+ startup_id,
+ screen);
+ if (uri == NULL)
+ {
+ caja_window_go_home (window);
+ }
+ else
+ {
+ location = g_file_new_for_uri (uri);
+ caja_window_go_to (window, location);
+ g_object_unref (location);
+ }
+ }
+ else
+ {
+ if (uri == NULL)
+ {
+ location = g_file_new_for_path (g_get_home_dir ());
+ }
+ else
+ {
+ location = g_file_new_for_uri (uri);
+ }
+
+ window = caja_application_present_spatial_window (application,
+ NULL,
+ startup_id,
+ location,
+ screen);
+ g_object_unref (location);
+ }
+
+ if (geometry != NULL && !gtk_widget_get_visible (GTK_WIDGET (window)))
+ {
+ /* never maximize windows opened from shell if a
+ * custom geometry has been requested.
+ */
+ gtk_window_unmaximize (GTK_WINDOW (window));
+ eel_gtk_window_set_initial_geometry_from_string (GTK_WINDOW (window),
+ geometry,
+ APPLICATION_WINDOW_MIN_WIDTH,
+ APPLICATION_WINDOW_MIN_HEIGHT,
+ FALSE);
+ }
+}
+
+static void
+open_windows (CajaApplication *application,
+ const char *startup_id,
+ char **uris,
+ GdkScreen *screen,
+ const char *geometry,
+ gboolean browser_window)
+{
+ guint i;
+
+ if (uris == NULL || uris[0] == NULL)
+ {
+ /* Open a window pointing at the default location. */
+ open_window (application, startup_id, NULL, screen, geometry, browser_window);
+ }
+ else
+ {
+ /* Open windows at each requested location. */
+ for (i = 0; uris[i] != NULL; i++)
+ {
+ open_window (application, startup_id, uris[i], screen, geometry, browser_window);
+ }
+ }
+}
+
+static UniqueResponse
+message_received_cb (UniqueApp *unique_app,
+ gint command,
+ UniqueMessageData *message,
+ guint time_,
+ gpointer user_data)
+{
+ CajaApplication *application;
+ UniqueResponse res;
+ char **uris;
+ char *geometry;
+ GdkScreen *screen;
+
+ application = user_data;
+ res = UNIQUE_RESPONSE_OK;
+
+ switch (command)
+ {
+ case UNIQUE_CLOSE:
+ res = UNIQUE_RESPONSE_OK;
+ caja_main_event_loop_quit (TRUE);
+
+ break;
+ case UNIQUE_OPEN:
+ case COMMAND_OPEN_BROWSER:
+ uris = _unique_message_data_get_geometry_and_uris (message, &geometry);
+ screen = unique_message_data_get_screen (message);
+ open_windows (application,
+ unique_message_data_get_startup_id (message),
+ uris,
+ screen,
+ geometry,
+ command == COMMAND_OPEN_BROWSER);
+ g_strfreev (uris);
+ g_free (geometry);
+ break;
+ case COMMAND_START_DESKTOP:
+ caja_application_open_desktop (application);
+ break;
+ case COMMAND_STOP_DESKTOP:
+ caja_application_close_desktop ();
+ break;
+ default:
+ res = UNIQUE_RESPONSE_PASSTHROUGH;
+ break;
+ }
+
+ return res;
+}
+
+gboolean
+caja_application_save_accel_map (gpointer data)
+{
+ if (save_of_accel_map_requested)
+ {
+ char *accel_map_filename;
+ accel_map_filename = caja_get_accel_map_file ();
+ if (accel_map_filename)
+ {
+ gtk_accel_map_save (accel_map_filename);
+ g_free (accel_map_filename);
+ }
+ save_of_accel_map_requested = FALSE;
+ }
+
+ return FALSE;
+}
+
+
+static void
+queue_accel_map_save_callback (GtkAccelMap *object, gchar *accel_path,
+ guint accel_key, GdkModifierType accel_mods,
+ gpointer user_data)
+{
+ if (!save_of_accel_map_requested)
+ {
+ save_of_accel_map_requested = TRUE;
+ g_timeout_add_seconds (CAJA_ACCEL_MAP_SAVE_DELAY,
+ caja_application_save_accel_map, NULL);
+ }
+}
+
+void
+caja_application_startup (CajaApplication *application,
+ gboolean kill_shell,
+ gboolean no_default_window,
+ gboolean no_desktop,
+ gboolean browser_window,
+ const char *geometry,
+ char **urls)
+{
+ UniqueMessageData *message;
+
+ /* Check the user's ~/.config/caja directories and post warnings
+ * if there are problems.
+ */
+ if (!kill_shell && !check_required_directories (application))
+ {
+ return;
+ }
+
+ if (kill_shell)
+ {
+ if (unique_app_is_running (application->unique_app))
+ {
+ unique_app_send_message (application->unique_app,
+ UNIQUE_CLOSE, NULL);
+
+ }
+ }
+ else
+ {
+ char *accel_map_filename;
+
+ if (!no_desktop &&
+ !eel_preferences_get_boolean (CAJA_PREFERENCES_SHOW_DESKTOP))
+ {
+ no_desktop = TRUE;
+ }
+
+ if (!no_desktop)
+ {
+ if (unique_app_is_running (application->unique_app))
+ {
+ unique_app_send_message (application->unique_app,
+ COMMAND_START_DESKTOP, NULL);
+ }
+ else
+ {
+ caja_application_open_desktop (application);
+ }
+ }
+
+ if (!unique_app_is_running (application->unique_app))
+ {
+ finish_startup (application, no_desktop);
+ g_signal_connect (application->unique_app, "message-received", G_CALLBACK (message_received_cb), application);
+ }
+
+ /* Monitor the preference to show or hide the desktop */
+ eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_SHOW_DESKTOP,
+ desktop_changed_callback,
+ application,
+ G_OBJECT (application));
+
+ /* Monitor the preference to have the desktop */
+ /* point to the Unix home folder */
+ eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_DESKTOP_IS_HOME_DIR,
+ desktop_location_changed_callback,
+ NULL,
+ G_OBJECT (application));
+
+ /* Create the other windows. */
+ if (urls != NULL || !no_default_window)
+ {
+ if (unique_app_is_running (application->unique_app))
+ {
+ message = unique_message_data_new ();
+ _unique_message_data_set_geometry_and_uris (message, geometry, urls);
+ if (browser_window)
+ {
+ unique_app_send_message (application->unique_app,
+ COMMAND_OPEN_BROWSER, message);
+ }
+ else
+ {
+ unique_app_send_message (application->unique_app,
+ UNIQUE_OPEN, message);
+ }
+ unique_message_data_free (message);
+ }
+ else
+ {
+ open_windows (application, NULL,
+ urls,
+ gdk_screen_get_default (),
+ geometry,
+ browser_window);
+ }
+ }
+
+ /* Load session info if availible */
+ caja_application_load_session (application);
+
+ /* load accelerator map, and register save callback */
+ accel_map_filename = caja_get_accel_map_file ();
+ if (accel_map_filename)
+ {
+ gtk_accel_map_load (accel_map_filename);
+ g_free (accel_map_filename);
+ }
+ g_signal_connect (gtk_accel_map_get (), "changed", G_CALLBACK (queue_accel_map_save_callback), NULL);
+ }
+}
+
+
+static void
+selection_get_cb (GtkWidget *widget,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint time)
+{
+ /* No extra targets atm */
+}
+
+static GtkWidget *
+get_desktop_manager_selection (GdkDisplay *display, int screen)
+{
+ char selection_name[32];
+ GdkAtom selection_atom;
+ Window selection_owner;
+ GtkWidget *selection_widget;
+
+ g_snprintf (selection_name, sizeof (selection_name), "_NET_DESKTOP_MANAGER_S%d", screen);
+ selection_atom = gdk_atom_intern (selection_name, FALSE);
+
+ selection_owner = XGetSelectionOwner (GDK_DISPLAY_XDISPLAY (display),
+ gdk_x11_atom_to_xatom_for_display (display,
+ selection_atom));
+ if (selection_owner != None)
+ {
+ return NULL;
+ }
+
+ selection_widget = gtk_invisible_new_for_screen (gdk_display_get_screen (display, screen));
+ /* We need this for gdk_x11_get_server_time() */
+ gtk_widget_add_events (selection_widget, GDK_PROPERTY_CHANGE_MASK);
+
+ if (gtk_selection_owner_set_for_display (display,
+ selection_widget,
+ selection_atom,
+ gdk_x11_get_server_time (gtk_widget_get_window (selection_widget))))
+ {
+
+ g_signal_connect (selection_widget, "selection_get",
+ G_CALLBACK (selection_get_cb), NULL);
+ return selection_widget;
+ }
+
+ gtk_widget_destroy (selection_widget);
+
+ return NULL;
+}
+
+static void
+desktop_unrealize_cb (GtkWidget *widget,
+ GtkWidget *selection_widget)
+{
+ gtk_widget_destroy (selection_widget);
+}
+
+static gboolean
+selection_clear_event_cb (GtkWidget *widget,
+ GdkEventSelection *event,
+ CajaDesktopWindow *window)
+{
+ gtk_widget_destroy (GTK_WIDGET (window));
+
+ caja_application_desktop_windows =
+ g_list_remove (caja_application_desktop_windows, window);
+
+ return TRUE;
+}
+
+static void
+caja_application_create_desktop_windows (CajaApplication *application)
+{
+ static gboolean create_in_progress = FALSE;
+ GdkDisplay *display;
+ CajaDesktopWindow *window;
+ GtkWidget *selection_widget;
+ int screens, i;
+
+ g_return_if_fail (caja_application_desktop_windows == NULL);
+ g_return_if_fail (CAJA_IS_APPLICATION (application));
+
+ if (create_in_progress)
+ {
+ return;
+ }
+
+ create_in_progress = TRUE;
+
+ display = gdk_display_get_default ();
+ screens = gdk_display_get_n_screens (display);
+
+ for (i = 0; i < screens; i++)
+ {
+ selection_widget = get_desktop_manager_selection (display, i);
+ if (selection_widget != NULL)
+ {
+ window = caja_desktop_window_new (application,
+ gdk_display_get_screen (display, i));
+
+ g_signal_connect (selection_widget, "selection_clear_event",
+ G_CALLBACK (selection_clear_event_cb), window);
+
+ g_signal_connect (window, "unrealize",
+ G_CALLBACK (desktop_unrealize_cb), selection_widget);
+
+ /* We realize it immediately so that the CAJA_DESKTOP_WINDOW_ID
+ property is set so mate-settings-daemon doesn't try to set the
+ background. And we do a gdk_flush() to be sure X gets it. */
+ gtk_widget_realize (GTK_WIDGET (window));
+ gdk_flush ();
+
+
+ caja_application_desktop_windows =
+ g_list_prepend (caja_application_desktop_windows, window);
+ }
+ }
+
+ create_in_progress = FALSE;
+}
+
+void
+caja_application_open_desktop (CajaApplication *application)
+{
+ if (caja_application_desktop_windows == NULL)
+ {
+ caja_application_create_desktop_windows (application);
+ }
+}
+
+void
+caja_application_close_desktop (void)
+{
+ if (caja_application_desktop_windows != NULL)
+ {
+ g_list_foreach (caja_application_desktop_windows,
+ (GFunc) gtk_widget_destroy, NULL);
+ g_list_free (caja_application_desktop_windows);
+ caja_application_desktop_windows = NULL;
+ }
+}
+
+void
+caja_application_close_all_navigation_windows (void)
+{
+ GList *list_copy;
+ GList *l;
+
+ list_copy = g_list_copy (caja_application_window_list);
+ /* First hide all window to get the feeling of quick response */
+ for (l = list_copy; l != NULL; l = l->next)
+ {
+ CajaWindow *window;
+
+ window = CAJA_WINDOW (l->data);
+
+ if (CAJA_IS_NAVIGATION_WINDOW (window))
+ {
+ gtk_widget_hide (GTK_WIDGET (window));
+ }
+ }
+
+ for (l = list_copy; l != NULL; l = l->next)
+ {
+ CajaWindow *window;
+
+ window = CAJA_WINDOW (l->data);
+
+ if (CAJA_IS_NAVIGATION_WINDOW (window))
+ {
+ caja_window_close (window);
+ }
+ }
+ g_list_free (list_copy);
+}
+
+static CajaSpatialWindow *
+caja_application_get_existing_spatial_window (GFile *location)
+{
+ GList *l;
+ CajaWindowSlot *slot;
+
+ for (l = caja_application_get_spatial_window_list ();
+ l != NULL; l = l->next)
+ {
+ GFile *window_location;
+
+ slot = CAJA_WINDOW (l->data)->details->active_pane->active_slot;
+ window_location = slot->location;
+ if (window_location != NULL)
+ {
+ if (g_file_equal (location, window_location))
+ {
+ return CAJA_SPATIAL_WINDOW (l->data);
+ }
+ }
+ }
+ return NULL;
+}
+
+static CajaSpatialWindow *
+find_parent_spatial_window (CajaSpatialWindow *window)
+{
+ CajaFile *file;
+ CajaFile *parent_file;
+ CajaWindowSlot *slot;
+ GFile *location;
+
+ slot = CAJA_WINDOW (window)->details->active_pane->active_slot;
+
+ location = slot->location;
+ if (location == NULL)
+ {
+ return NULL;
+ }
+ file = caja_file_get (location);
+
+ if (!file)
+ {
+ return NULL;
+ }
+
+ parent_file = caja_file_get_parent (file);
+ caja_file_unref (file);
+ while (parent_file)
+ {
+ CajaSpatialWindow *parent_window;
+
+ location = caja_file_get_location (parent_file);
+ parent_window = caja_application_get_existing_spatial_window (location);
+ g_object_unref (location);
+
+ /* Stop at the desktop directory if it's not explicitely opened
+ * in a spatial window of its own.
+ */
+ if (caja_file_is_desktop_directory (parent_file) && !parent_window)
+ {
+ caja_file_unref (parent_file);
+ return NULL;
+ }
+
+ if (parent_window)
+ {
+ caja_file_unref (parent_file);
+ return parent_window;
+ }
+ file = parent_file;
+ parent_file = caja_file_get_parent (file);
+ caja_file_unref (file);
+ }
+
+ return NULL;
+}
+
+void
+caja_application_close_parent_windows (CajaSpatialWindow *window)
+{
+ CajaSpatialWindow *parent_window;
+ CajaSpatialWindow *new_parent_window;
+
+ g_return_if_fail (CAJA_IS_SPATIAL_WINDOW (window));
+
+ parent_window = find_parent_spatial_window (window);
+
+ while (parent_window)
+ {
+
+ new_parent_window = find_parent_spatial_window (parent_window);
+ caja_window_close (CAJA_WINDOW (parent_window));
+ parent_window = new_parent_window;
+ }
+}
+
+void
+caja_application_close_all_spatial_windows (void)
+{
+ GList *list_copy;
+ GList *l;
+
+ list_copy = g_list_copy (caja_application_spatial_window_list);
+ /* First hide all window to get the feeling of quick response */
+ for (l = list_copy; l != NULL; l = l->next)
+ {
+ CajaWindow *window;
+
+ window = CAJA_WINDOW (l->data);
+
+ if (CAJA_IS_SPATIAL_WINDOW (window))
+ {
+ gtk_widget_hide (GTK_WIDGET (window));
+ }
+ }
+
+ for (l = list_copy; l != NULL; l = l->next)
+ {
+ CajaWindow *window;
+
+ window = CAJA_WINDOW (l->data);
+
+ if (CAJA_IS_SPATIAL_WINDOW (window))
+ {
+ caja_window_close (window);
+ }
+ }
+ g_list_free (list_copy);
+}
+
+static void
+caja_application_destroyed_window (GtkObject *object, CajaApplication *application)
+{
+ caja_application_window_list = g_list_remove (caja_application_window_list, object);
+}
+
+static gboolean
+caja_window_delete_event_callback (GtkWidget *widget,
+ GdkEvent *event,
+ gpointer user_data)
+{
+ CajaWindow *window;
+
+ window = CAJA_WINDOW (widget);
+ caja_window_close (window);
+
+ return TRUE;
+}
+
+
+static CajaWindow *
+create_window (CajaApplication *application,
+ GType window_type,
+ const char *startup_id,
+ GdkScreen *screen)
+{
+ CajaWindow *window;
+
+ g_return_val_if_fail (CAJA_IS_APPLICATION (application), NULL);
+
+ window = CAJA_WINDOW (gtk_widget_new (window_type,
+ "app", application,
+ "screen", screen,
+ NULL));
+
+ if (startup_id)
+ {
+ gtk_window_set_startup_id (GTK_WINDOW (window), startup_id);
+ }
+
+ g_signal_connect_data (window, "delete_event",
+ G_CALLBACK (caja_window_delete_event_callback), NULL, NULL,
+ G_CONNECT_AFTER);
+
+ g_signal_connect_object (window, "destroy",
+ G_CALLBACK (caja_application_destroyed_window), application, 0);
+
+ caja_application_window_list = g_list_prepend (caja_application_window_list, window);
+
+ /* Do not yet show the window. It will be shown later on if it can
+ * successfully display its initial URI. Otherwise it will be destroyed
+ * without ever having seen the light of day.
+ */
+
+ return window;
+}
+
+static void
+spatial_window_destroyed_callback (void *user_data, GObject *window)
+{
+ caja_application_spatial_window_list = g_list_remove (caja_application_spatial_window_list, window);
+
+}
+
+CajaWindow *
+caja_application_present_spatial_window (CajaApplication *application,
+ CajaWindow *requesting_window,
+ const char *startup_id,
+ GFile *location,
+ GdkScreen *screen)
+{
+ return caja_application_present_spatial_window_with_selection (application,
+ requesting_window,
+ startup_id,
+ location,
+ NULL,
+ screen);
+}
+
+CajaWindow *
+caja_application_present_spatial_window_with_selection (CajaApplication *application,
+ CajaWindow *requesting_window,
+ const char *startup_id,
+ GFile *location,
+ GList *new_selection,
+ GdkScreen *screen)
+{
+ CajaWindow *window;
+ GList *l;
+ char *uri;
+
+ g_return_val_if_fail (CAJA_IS_APPLICATION (application), NULL);
+
+ for (l = caja_application_get_spatial_window_list ();
+ l != NULL; l = l->next)
+ {
+ CajaWindow *existing_window;
+ CajaWindowSlot *slot;
+ GFile *existing_location;
+
+ existing_window = CAJA_WINDOW (l->data);
+ slot = existing_window->details->active_pane->active_slot;
+ existing_location = slot->pending_location;
+
+ if (existing_location == NULL)
+ {
+ existing_location = slot->location;
+ }
+
+ if (g_file_equal (existing_location, location))
+ {
+ gtk_window_present (GTK_WINDOW (existing_window));
+ if (new_selection &&
+ slot->content_view != NULL)
+ {
+ caja_view_set_selection (slot->content_view, new_selection);
+ }
+
+ uri = g_file_get_uri (location);
+ caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
+ "present EXISTING spatial window=%p: %s",
+ existing_window, uri);
+ g_free (uri);
+ return existing_window;
+ }
+ }
+
+ window = create_window (application, CAJA_TYPE_SPATIAL_WINDOW, startup_id, screen);
+ if (requesting_window)
+ {
+ /* Center the window over the requesting window by default */
+ int orig_x, orig_y, orig_width, orig_height;
+ int new_x, new_y, new_width, new_height;
+
+ gtk_window_get_position (GTK_WINDOW (requesting_window),
+ &orig_x, &orig_y);
+ gtk_window_get_size (GTK_WINDOW (requesting_window),
+ &orig_width, &orig_height);
+ gtk_window_get_default_size (GTK_WINDOW (window),
+ &new_width, &new_height);
+
+ new_x = orig_x + (orig_width - new_width) / 2;
+ new_y = orig_y + (orig_height - new_height) / 2;
+
+ if (orig_width - new_width < 10)
+ {
+ new_x += 10;
+ new_y += 10;
+ }
+
+ gtk_window_move (GTK_WINDOW (window), new_x, new_y);
+ }
+
+ caja_application_spatial_window_list = g_list_prepend (caja_application_spatial_window_list, window);
+ g_object_weak_ref (G_OBJECT (window),
+ spatial_window_destroyed_callback, NULL);
+
+ caja_window_go_to_with_selection (window, location, new_selection);
+
+ uri = g_file_get_uri (location);
+ caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
+ "present NEW spatial window=%p: %s",
+ window, uri);
+ g_free (uri);
+
+ return window;
+}
+
+static gboolean
+another_navigation_window_already_showing (CajaWindow *the_window)
+{
+ GList *list, *item;
+
+ list = caja_application_get_window_list ();
+ for (item = list; item != NULL; item = item->next)
+ {
+ if (item->data != the_window &&
+ CAJA_IS_NAVIGATION_WINDOW (item->data))
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+CajaWindow *
+caja_application_create_navigation_window (CajaApplication *application,
+ const char *startup_id,
+ GdkScreen *screen)
+{
+ CajaWindow *window;
+ char *geometry_string;
+ gboolean maximized;
+
+ g_return_val_if_fail (CAJA_IS_APPLICATION (application), NULL);
+
+ window = create_window (application, CAJA_TYPE_NAVIGATION_WINDOW, startup_id, screen);
+
+ maximized = eel_preferences_get_boolean
+ (CAJA_PREFERENCES_NAVIGATION_WINDOW_MAXIMIZED);
+ if (maximized)
+ {
+ gtk_window_maximize (GTK_WINDOW (window));
+ }
+ else
+ {
+ gtk_window_unmaximize (GTK_WINDOW (window));
+ }
+
+ geometry_string = eel_preferences_get
+ (CAJA_PREFERENCES_NAVIGATION_WINDOW_SAVED_GEOMETRY);
+ if (geometry_string != NULL &&
+ geometry_string[0] != 0)
+ {
+ /* Ignore saved window position if a window with the same
+ * location is already showing. That way the two windows
+ * wont appear at the exact same location on the screen.
+ */
+ eel_gtk_window_set_initial_geometry_from_string
+ (GTK_WINDOW (window),
+ geometry_string,
+ CAJA_NAVIGATION_WINDOW_MIN_WIDTH,
+ CAJA_NAVIGATION_WINDOW_MIN_HEIGHT,
+ another_navigation_window_already_showing (window));
+ }
+ g_free (geometry_string);
+
+ caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
+ "create new navigation window=%p",
+ window);
+
+ return window;
+}
+
+/* callback for changing the directory the desktop points to */
+static void
+desktop_location_changed_callback (gpointer user_data)
+{
+ if (caja_application_desktop_windows != NULL)
+ {
+ g_list_foreach (caja_application_desktop_windows,
+ (GFunc) caja_desktop_window_update_directory, NULL);
+ }
+}
+
+/* callback for showing or hiding the desktop based on the user's preference */
+static void
+desktop_changed_callback (gpointer user_data)
+{
+ CajaApplication *application;
+
+ application = CAJA_APPLICATION (user_data);
+ if ( eel_preferences_get_boolean (CAJA_PREFERENCES_SHOW_DESKTOP))
+ {
+ caja_application_open_desktop (application);
+ }
+ else
+ {
+ caja_application_close_desktop ();
+ }
+}
+
+static gboolean
+window_can_be_closed (CajaWindow *window)
+{
+ if (!CAJA_IS_DESKTOP_WINDOW (window))
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+volume_added_callback (GVolumeMonitor *monitor,
+ GVolume *volume,
+ CajaApplication *application)
+{
+ if (eel_preferences_get_boolean (CAJA_PREFERENCES_MEDIA_AUTOMOUNT) &&
+ g_volume_should_automount (volume) &&
+ g_volume_can_mount (volume))
+ {
+ caja_file_operations_mount_volume (NULL, volume, TRUE);
+ }
+ else
+ {
+ /* Allow caja_autorun() to run. When the mount is later
+ * added programmatically (i.e. for a blank CD),
+ * caja_autorun() will be called by mount_added_callback(). */
+ caja_allow_autorun_for_volume (volume);
+ caja_allow_autorun_for_volume_finish (volume);
+ }
+}
+
+static void
+drive_eject_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GError *error;
+ char *primary;
+ char *name;
+ error = NULL;
+ if (!g_drive_eject_with_operation_finish (G_DRIVE (source_object), res, &error))
+ {
+ if (error->code != G_IO_ERROR_FAILED_HANDLED)
+ {
+ name = g_drive_get_name (G_DRIVE (source_object));
+ primary = g_strdup_printf (_("Unable to eject %s"), name);
+ g_free (name);
+ eel_show_error_dialog (primary,
+ error->message,
+ NULL);
+ g_free (primary);
+ }
+ g_error_free (error);
+ }
+}
+
+static void
+drive_eject_button_pressed (GDrive *drive,
+ CajaApplication *application)
+{
+ GMountOperation *mount_op;
+
+ mount_op = gtk_mount_operation_new (NULL);
+ g_drive_eject_with_operation (drive, 0, mount_op, NULL, drive_eject_cb, NULL);
+ g_object_unref (mount_op);
+}
+
+static void
+drive_listen_for_eject_button (GDrive *drive, CajaApplication *application)
+{
+ g_signal_connect (drive,
+ "eject-button",
+ G_CALLBACK (drive_eject_button_pressed),
+ application);
+}
+
+static void
+drive_connected_callback (GVolumeMonitor *monitor,
+ GDrive *drive,
+ CajaApplication *application)
+{
+ drive_listen_for_eject_button (drive, application);
+}
+
+static void
+autorun_show_window (GMount *mount, gpointer user_data)
+{
+ GFile *location;
+ CajaApplication *application = user_data;
+
+ location = g_mount_get_root (mount);
+
+ /* Ther should probably be an easier way to do this */
+ if (eel_preferences_get_boolean (CAJA_PREFERENCES_ALWAYS_USE_BROWSER))
+ {
+ CajaWindow *window;
+ window = caja_application_create_navigation_window (application,
+ NULL,
+ gdk_screen_get_default ());
+ caja_window_go_to (window, location);
+
+ }
+ else
+ {
+ caja_application_present_spatial_window (application,
+ NULL,
+ NULL,
+ location,
+ gdk_screen_get_default ());
+ }
+ g_object_unref (location);
+}
+
+static void
+mount_added_callback (GVolumeMonitor *monitor,
+ GMount *mount,
+ CajaApplication *application)
+{
+ CajaDirectory *directory;
+ GFile *root;
+
+ if (!application->session_is_active)
+ {
+ return;
+ }
+
+ root = g_mount_get_root (mount);
+ directory = caja_directory_get_existing (root);
+ g_object_unref (root);
+ if (directory != NULL)
+ {
+ caja_directory_force_reload (directory);
+ caja_directory_unref (directory);
+ }
+
+ caja_autorun (mount, autorun_show_window, application);
+}
+
+static CajaWindowSlot *
+get_first_navigation_slot (GList *slot_list)
+{
+ GList *l;
+
+ for (l = slot_list; l != NULL; l = l->next)
+ {
+ if (CAJA_IS_NAVIGATION_WINDOW_SLOT (l->data))
+ {
+ return l->data;
+ }
+ }
+
+ return NULL;
+}
+
+/* We redirect some slots and close others */
+static gboolean
+should_close_slot_with_mount (CajaWindow *window,
+ CajaWindowSlot *slot,
+ GMount *mount)
+{
+ if (CAJA_IS_SPATIAL_WINDOW (window))
+ {
+ return TRUE;
+ }
+ return caja_navigation_window_slot_should_close_with_mount (CAJA_NAVIGATION_WINDOW_SLOT (slot),
+ mount);
+}
+
+/* Called whenever a mount is unmounted. Check and see if there are
+ * any windows open displaying contents on the mount. If there are,
+ * close them. It would also be cool to save open window and position
+ * info.
+ *
+ * This is also called on pre_unmount.
+ */
+static void
+mount_removed_callback (GVolumeMonitor *monitor,
+ GMount *mount,
+ CajaApplication *application)
+{
+ GList *window_list, *node, *close_list;
+ CajaWindow *window;
+ CajaWindowSlot *slot;
+ CajaWindowSlot *force_no_close_slot;
+ GFile *root, *computer;
+ gboolean unclosed_slot;
+
+ close_list = NULL;
+ force_no_close_slot = NULL;
+ unclosed_slot = FALSE;
+
+ /* Check and see if any of the open windows are displaying contents from the unmounted mount */
+ window_list = caja_application_get_window_list ();
+
+ root = g_mount_get_root (mount);
+ /* Construct a list of windows to be closed. Do not add the non-closable windows to the list. */
+ for (node = window_list; node != NULL; node = node->next)
+ {
+ window = CAJA_WINDOW (node->data);
+ if (window != NULL && window_can_be_closed (window))
+ {
+ GList *l;
+ GList *lp;
+ GFile *location;
+
+ for (lp = window->details->panes; lp != NULL; lp = lp->next)
+ {
+ CajaWindowPane *pane;
+ pane = (CajaWindowPane*) lp->data;
+ for (l = pane->slots; l != NULL; l = l->next)
+ {
+ slot = l->data;
+ location = slot->location;
+ if (g_file_has_prefix (location, root) ||
+ g_file_equal (location, root))
+ {
+ close_list = g_list_prepend (close_list, slot);
+
+ if (!should_close_slot_with_mount (window, slot, mount))
+ {
+ /* We'll be redirecting this, not closing */
+ unclosed_slot = TRUE;
+ }
+ }
+ else
+ {
+ unclosed_slot = TRUE;
+ }
+ } /* for all slots */
+ } /* for all panes */
+ }
+ }
+
+ if (caja_application_desktop_windows == NULL &&
+ !unclosed_slot)
+ {
+ /* We are trying to close all open slots. Keep one navigation slot open. */
+ force_no_close_slot = get_first_navigation_slot (close_list);
+ }
+
+ /* Handle the windows in the close list. */
+ for (node = close_list; node != NULL; node = node->next)
+ {
+ slot = node->data;
+ window = slot->pane->window;
+
+ if (should_close_slot_with_mount (window, slot, mount) &&
+ slot != force_no_close_slot)
+ {
+ caja_window_slot_close (slot);
+ }
+ else
+ {
+ computer = g_file_new_for_uri ("computer:///");
+ caja_window_slot_go_to (slot, computer, FALSE);
+ g_object_unref(computer);
+ }
+ }
+
+ g_list_free (close_list);
+}
+
+static char *
+icon_to_string (GIcon *icon)
+{
+ const char * const *names;
+ GFile *file;
+
+ if (icon == NULL)
+ {
+ return NULL;
+ }
+ else if (G_IS_THEMED_ICON (icon))
+ {
+ names = g_themed_icon_get_names (G_THEMED_ICON (icon));
+ return g_strjoinv (":", (char **)names);
+ }
+ else if (G_IS_FILE_ICON (icon))
+ {
+ file = g_file_icon_get_file (G_FILE_ICON (icon));
+ return g_file_get_path (file);
+ }
+ return NULL;
+}
+
+static GIcon *
+icon_from_string (const char *string)
+{
+ GFile *file;
+ GIcon *icon;
+ gchar **names;
+
+ if (g_path_is_absolute (string))
+ {
+ file = g_file_new_for_path (string);
+ icon = g_file_icon_new (file);
+ g_object_unref (file);
+ return icon;
+ }
+ else
+ {
+ names = g_strsplit (string, ":", 0);
+ icon = g_themed_icon_new_from_names (names, -1);
+ g_strfreev (names);
+ return icon;
+ }
+ return NULL;
+}
+
+static char *
+caja_application_get_session_data (void)
+{
+ xmlDocPtr doc;
+ xmlNodePtr root_node, history_node;
+ GList *l;
+ char *data;
+ unsigned n_processed;
+ xmlSaveCtxtPtr ctx;
+ xmlBufferPtr buffer;
+
+ doc = xmlNewDoc ("1.0");
+
+ root_node = xmlNewNode (NULL, "session");
+ xmlDocSetRootElement (doc, root_node);
+
+ history_node = xmlNewChild (root_node, NULL, "history", NULL);
+
+ n_processed = 0;
+ for (l = caja_get_history_list (); l != NULL; l = l->next)
+ {
+ CajaBookmark *bookmark;
+ xmlNodePtr bookmark_node;
+ GIcon *icon;
+ char *tmp;
+
+ bookmark = l->data;
+
+ bookmark_node = xmlNewChild (history_node, NULL, "bookmark", NULL);
+
+ tmp = caja_bookmark_get_name (bookmark);
+ xmlNewProp (bookmark_node, "name", tmp);
+ g_free (tmp);
+
+ icon = caja_bookmark_get_icon (bookmark);
+ tmp = icon_to_string (icon);
+ g_object_unref (icon);
+ if (tmp)
+ {
+ xmlNewProp (bookmark_node, "icon", tmp);
+ g_free (tmp);
+ }
+
+ tmp = caja_bookmark_get_uri (bookmark);
+ xmlNewProp (bookmark_node, "uri", tmp);
+ g_free (tmp);
+
+ if (caja_bookmark_get_has_custom_name (bookmark))
+ {
+ xmlNewProp (bookmark_node, "has_custom_name", "TRUE");
+ }
+
+ if (++n_processed > 50) /* prevent history list from growing arbitrarily large. */
+ {
+ break;
+ }
+ }
+
+ for (l = caja_application_window_list; l != NULL; l = l->next)
+ {
+ xmlNodePtr win_node, slot_node;
+ CajaWindow *window;
+ CajaWindowSlot *slot, *active_slot;
+ GList *slots, *m;
+ char *tmp;
+
+ window = l->data;
+
+ win_node = xmlNewChild (root_node, NULL, "window", NULL);
+
+ xmlNewProp (win_node, "type", CAJA_IS_NAVIGATION_WINDOW (window) ? "navigation" : "spatial");
+
+ if (CAJA_IS_NAVIGATION_WINDOW (window)) /* spatial windows store their state as file metadata */
+ {
+ GdkWindow *gdk_window;
+
+ tmp = eel_gtk_window_get_geometry_string (GTK_WINDOW (window));
+ xmlNewProp (win_node, "geometry", tmp);
+ g_free (tmp);
+
+ gdk_window = gtk_widget_get_window (GTK_WIDGET (window));
+
+ if (gdk_window &&
+ gdk_window_get_state (gdk_window) & GDK_WINDOW_STATE_MAXIMIZED)
+ {
+ xmlNewProp (win_node, "maximized", "TRUE");
+ }
+
+ if (gdk_window &&
+ gdk_window_get_state (gdk_window) & GDK_WINDOW_STATE_STICKY)
+ {
+ xmlNewProp (win_node, "sticky", "TRUE");
+ }
+
+ if (gdk_window &&
+ gdk_window_get_state (gdk_window) & GDK_WINDOW_STATE_ABOVE)
+ {
+ xmlNewProp (win_node, "keep-above", "TRUE");
+ }
+ }
+
+ slots = caja_window_get_slots (window);
+ active_slot = caja_window_get_active_slot (window);
+
+ /* store one slot as window location. Otherwise
+ * older Caja versions will bail when reading the file. */
+ tmp = caja_window_slot_get_location_uri (active_slot);
+ xmlNewProp (win_node, "location", tmp);
+ g_free (tmp);
+
+ for (m = slots; m != NULL; m = m->next)
+ {
+ slot = CAJA_WINDOW_SLOT (m->data);
+
+ slot_node = xmlNewChild (win_node, NULL, "slot", NULL);
+
+ tmp = caja_window_slot_get_location_uri (slot);
+ xmlNewProp (slot_node, "location", tmp);
+ g_free (tmp);
+
+ if (slot == active_slot)
+ {
+ xmlNewProp (slot_node, "active", "TRUE");
+ }
+ }
+
+ g_list_free (slots);
+ }
+
+ buffer = xmlBufferCreate ();
+ xmlIndentTreeOutput = 1;
+ ctx = xmlSaveToBuffer (buffer, "UTF-8", XML_SAVE_FORMAT);
+ if (xmlSaveDoc (ctx, doc) < 0 ||
+ xmlSaveFlush (ctx) < 0)
+ {
+ g_message ("failed to save session");
+ }
+
+ xmlSaveClose(ctx);
+ data = g_strndup (buffer->content, buffer->use);
+ xmlBufferFree (buffer);
+
+ xmlFreeDoc (doc);
+
+ return data;
+}
+
+void
+caja_application_load_session (CajaApplication *application)
+{
+ xmlDocPtr doc;
+ gboolean bail;
+ xmlNodePtr root_node;
+ GKeyFile *state_file;
+ char *data;
+
+ if (!egg_sm_client_is_resumed (application->smclient))
+ {
+ return;
+ }
+
+ state_file = egg_sm_client_get_state_file (application->smclient);
+ if (!state_file)
+ {
+ return;
+ }
+
+ data = g_key_file_get_string (state_file,
+ "Caja",
+ "documents",
+ NULL);
+ if (data == NULL)
+ {
+ return;
+ }
+
+ bail = TRUE;
+
+ doc = xmlReadMemory (data, strlen (data), NULL, "UTF-8", 0);
+ if (doc != NULL && (root_node = xmlDocGetRootElement (doc)) != NULL)
+ {
+ xmlNodePtr node;
+
+ bail = FALSE;
+
+ for (node = root_node->children; node != NULL; node = node->next)
+ {
+
+ if (!strcmp (node->name, "text"))
+ {
+ continue;
+ }
+ else if (!strcmp (node->name, "history"))
+ {
+ xmlNodePtr bookmark_node;
+ gboolean emit_change;
+
+ emit_change = FALSE;
+
+ for (bookmark_node = node->children; bookmark_node != NULL; bookmark_node = bookmark_node->next)
+ {
+ if (!strcmp (bookmark_node->name, "text"))
+ {
+ continue;
+ }
+ else if (!strcmp (bookmark_node->name, "bookmark"))
+ {
+ xmlChar *name, *icon_str, *uri;
+ gboolean has_custom_name;
+ GIcon *icon;
+ GFile *location;
+
+ uri = xmlGetProp (bookmark_node, "uri");
+ name = xmlGetProp (bookmark_node, "name");
+ has_custom_name = xmlHasProp (bookmark_node, "has_custom_name") ? TRUE : FALSE;
+ icon_str = xmlGetProp (bookmark_node, "icon");
+ icon = NULL;
+ if (icon_str)
+ {
+ icon = icon_from_string (icon_str);
+ }
+ location = g_file_new_for_uri (uri);
+
+ emit_change |= caja_add_to_history_list_no_notify (location, name, has_custom_name, icon);
+
+ g_object_unref (location);
+
+ if (icon)
+ {
+ g_object_unref (icon);
+ }
+ xmlFree (name);
+ xmlFree (uri);
+ xmlFree (icon_str);
+ }
+ else
+ {
+ g_message ("unexpected bookmark node %s while parsing session data", bookmark_node->name);
+ bail = TRUE;
+ continue;
+ }
+ }
+
+ if (emit_change)
+ {
+ caja_send_history_list_changed ();
+ }
+ }
+ else if (!strcmp (node->name, "window"))
+ {
+ CajaWindow *window;
+ xmlChar *type, *location_uri, *slot_uri;
+ xmlNodePtr slot_node;
+ GFile *location;
+ int i;
+
+ type = xmlGetProp (node, "type");
+ if (type == NULL)
+ {
+ g_message ("empty type node while parsing session data");
+ bail = TRUE;
+ continue;
+ }
+
+ location_uri = xmlGetProp (node, "location");
+ if (location_uri == NULL)
+ {
+ g_message ("empty location node while parsing session data");
+ bail = TRUE;
+ xmlFree (type);
+ continue;
+ }
+
+ if (!strcmp (type, "navigation"))
+ {
+ xmlChar *geometry;
+
+ window = caja_application_create_navigation_window (application, NULL, gdk_screen_get_default ());
+
+ geometry = xmlGetProp (node, "geometry");
+ if (geometry != NULL)
+ {
+ eel_gtk_window_set_initial_geometry_from_string
+ (GTK_WINDOW (window),
+ geometry,
+ CAJA_NAVIGATION_WINDOW_MIN_WIDTH,
+ CAJA_NAVIGATION_WINDOW_MIN_HEIGHT,
+ FALSE);
+ }
+ xmlFree (geometry);
+
+ if (xmlHasProp (node, "maximized"))
+ {
+ gtk_window_maximize (GTK_WINDOW (window));
+ }
+ else
+ {
+ gtk_window_unmaximize (GTK_WINDOW (window));
+ }
+
+ if (xmlHasProp (node, "sticky"))
+ {
+ gtk_window_stick (GTK_WINDOW (window));
+ }
+ else
+ {
+ gtk_window_unstick (GTK_WINDOW (window));
+ }
+
+ if (xmlHasProp (node, "keep-above"))
+ {
+ gtk_window_set_keep_above (GTK_WINDOW (window), TRUE);
+ }
+ else
+ {
+ gtk_window_set_keep_above (GTK_WINDOW (window), FALSE);
+ }
+
+ for (i = 0, slot_node = node->children; slot_node != NULL; slot_node = slot_node->next)
+ {
+ if (!strcmp (slot_node->name, "slot"))
+ {
+ slot_uri = xmlGetProp (slot_node, "location");
+ if (slot_uri != NULL)
+ {
+ CajaWindowSlot *slot;
+
+ if (i == 0)
+ {
+ slot = window->details->active_pane->active_slot;
+ }
+ else
+ {
+ slot = caja_window_open_slot (window->details->active_pane, CAJA_WINDOW_OPEN_SLOT_APPEND);
+ }
+
+ location = g_file_new_for_uri (slot_uri);
+ caja_window_slot_open_location (slot, location, FALSE);
+
+ if (xmlHasProp (slot_node, "active"))
+ {
+ caja_window_set_active_slot (slot->pane->window, slot);
+ }
+
+ i++;
+ }
+ xmlFree (slot_uri);
+ }
+ }
+
+ if (i == 0)
+ {
+ /* This may be an old session file */
+ location = g_file_new_for_uri (location_uri);
+ caja_window_slot_open_location (window->details->active_pane->active_slot, location, FALSE);
+ g_object_unref (location);
+ }
+ }
+ else if (!strcmp (type, "spatial"))
+ {
+ location = g_file_new_for_uri (location_uri);
+ window = caja_application_present_spatial_window (application, NULL, NULL, location, gdk_screen_get_default ());
+ g_object_unref (location);
+ }
+ else
+ {
+ g_message ("unknown window type \"%s\" while parsing session data", type);
+ bail = TRUE;
+ }
+
+ xmlFree (type);
+ xmlFree (location_uri);
+ }
+ else
+ {
+ g_message ("unexpected node %s while parsing session data", node->name);
+ bail = TRUE;
+ continue;
+ }
+ }
+ }
+
+ if (doc != NULL)
+ {
+ xmlFreeDoc (doc);
+ }
+
+ g_free (data);
+
+ if (bail)
+ {
+ g_message ("failed to load session");
+ }
+}
+
+static void
+caja_application_class_init (CajaApplicationClass *class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = caja_application_finalize;
+}
diff --git a/src/caja-application.h b/src/caja-application.h
new file mode 100644
index 00000000..843c6407
--- /dev/null
+++ b/src/caja-application.h
@@ -0,0 +1,117 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2000 Red Hat, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/* caja-application.h
+ */
+
+#ifndef CAJA_APPLICATION_H
+#define CAJA_APPLICATION_H
+
+#include <gdk/gdk.h>
+#include <gio/gio.h>
+#include <unique/unique.h>
+#include <libegg/eggsmclient.h>
+#include <libcaja-private/caja-undo-manager.h>
+
+#define CAJA_DESKTOP_ICON_VIEW_IID "OAFIID:Caja_File_Manager_Desktop_Icon_View"
+
+#define CAJA_TYPE_APPLICATION \
+ caja_application_get_type()
+#define CAJA_APPLICATION(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), CAJA_TYPE_APPLICATION, CajaApplication))
+#define CAJA_APPLICATION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), CAJA_TYPE_APPLICATION, CajaApplicationClass))
+#define CAJA_IS_APPLICATION(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), CAJA_TYPE_APPLICATION))
+#define CAJA_IS_APPLICATION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), CAJA_TYPE_APPLICATION))
+#define CAJA_APPLICATION_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS((obj), CAJA_TYPE_APPLICATION, CajaApplicationClass))
+
+#ifndef CAJA_WINDOW_DEFINED
+#define CAJA_WINDOW_DEFINED
+typedef struct CajaWindow CajaWindow;
+#endif
+
+#ifndef CAJA_SPATIAL_WINDOW_DEFINED
+#define CAJA_SPATIAL_WINDOW_DEFINED
+typedef struct _CajaSpatialWindow CajaSpatialWindow;
+#endif
+
+typedef struct CajaShell CajaShell;
+
+typedef struct
+{
+ GObject parent;
+ UniqueApp* unique_app;
+ EggSMClient* smclient;
+ CajaUndoManager* undo_manager;
+ GVolumeMonitor* volume_monitor;
+ unsigned int automount_idle_id;
+ GDBusProxy* proxy;
+ gboolean session_is_active;
+} CajaApplication;
+
+typedef struct
+{
+ GObjectClass parent_class;
+} CajaApplicationClass;
+
+GType caja_application_get_type (void);
+CajaApplication *caja_application_new (void);
+void caja_application_startup (CajaApplication *application,
+ gboolean kill_shell,
+ gboolean no_default_window,
+ gboolean no_desktop,
+ gboolean browser_window,
+ const char *default_geometry,
+ char **urls);
+GList * caja_application_get_window_list (void);
+GList * caja_application_get_spatial_window_list (void);
+unsigned int caja_application_get_n_windows (void);
+
+CajaWindow * caja_application_present_spatial_window (CajaApplication *application,
+ CajaWindow *requesting_window,
+ const char *startup_id,
+ GFile *location,
+ GdkScreen *screen);
+CajaWindow * caja_application_present_spatial_window_with_selection (CajaApplication *application,
+ CajaWindow *requesting_window,
+ const char *startup_id,
+ GFile *location,
+ GList *new_selection,
+ GdkScreen *screen);
+
+CajaWindow * caja_application_create_navigation_window (CajaApplication *application,
+ const char *startup_id,
+ GdkScreen *screen);
+
+void caja_application_close_all_navigation_windows (void);
+void caja_application_close_parent_windows (CajaSpatialWindow *window);
+void caja_application_close_all_spatial_windows (void);
+void caja_application_open_desktop (CajaApplication *application);
+void caja_application_close_desktop (void);
+gboolean caja_application_save_accel_map (gpointer data);
+
+
+#endif /* CAJA_APPLICATION_H */
diff --git a/src/caja-autorun-software.c b/src/caja-autorun-software.c
new file mode 100644
index 00000000..502f1eac
--- /dev/null
+++ b/src/caja-autorun-software.c
@@ -0,0 +1,316 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* Caja
+
+ Copyright (C) 2008 Red Hat, Inc.
+
+ The Mate Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Mate Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: David Zeuthen <[email protected]>
+*/
+
+
+#include <config.h>
+
+#include <unistd.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+
+#include <glib/gi18n.h>
+
+#include <libcaja-private/caja-module.h>
+#include <libcaja-private/caja-icon-info.h>
+
+typedef struct
+{
+ GtkWidget *dialog;
+ GMount *mount;
+} AutorunSoftwareDialogData;
+
+static void autorun_software_dialog_mount_unmounted (GMount *mount, AutorunSoftwareDialogData *data);
+
+static void
+autorun_software_dialog_destroy (AutorunSoftwareDialogData *data)
+{
+ g_signal_handlers_disconnect_by_func (G_OBJECT (data->mount),
+ G_CALLBACK (autorun_software_dialog_mount_unmounted),
+ data);
+
+ gtk_widget_destroy (GTK_WIDGET (data->dialog));
+ g_object_unref (data->mount);
+ g_free (data);
+}
+
+static void
+autorun_software_dialog_mount_unmounted (GMount *mount, AutorunSoftwareDialogData *data)
+{
+ autorun_software_dialog_destroy (data);
+}
+
+static gboolean
+_check_file (GFile *mount_root, const char *file_path, gboolean must_be_executable)
+{
+ GFile *file;
+ GFileInfo *file_info;
+ gboolean ret;
+
+ ret = FALSE;
+
+ file = g_file_get_child (mount_root, file_path);
+ file_info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ NULL);
+ if (file_info != NULL)
+ {
+ if (must_be_executable)
+ {
+ if (g_file_info_get_attribute_boolean (file_info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE))
+ {
+ ret = TRUE;
+ }
+ }
+ else
+ {
+ ret = TRUE;
+ }
+ g_object_unref (file_info);
+ }
+ g_object_unref (file);
+
+ return ret;
+}
+
+static void
+autorun (GMount *mount)
+{
+ char *error_string;
+ GFile *root;
+ GFile *program_to_spawn;
+ char *path_to_spawn;
+ char *cwd_for_program;
+
+ root = g_mount_get_root (mount);
+
+ /* Careful here, according to
+ *
+ * http://standards.freedesktop.org/autostart-spec/autostart-spec-latest.html
+ *
+ * the ordering does matter.
+ */
+
+ program_to_spawn = NULL;
+ path_to_spawn = NULL;
+
+ if (_check_file (root, ".autorun", TRUE))
+ {
+ program_to_spawn = g_file_get_child (root, ".autorun");
+ }
+ else if (_check_file (root, "autorun", TRUE))
+ {
+ program_to_spawn = g_file_get_child (root, "autorun");
+ }
+ else if (_check_file (root, "autorun.sh", TRUE))
+ {
+ program_to_spawn = g_file_get_child (root, "autorun.sh");
+ }
+ else if (_check_file (root, "autorun.exe", TRUE))
+ {
+ /* TODO */
+ }
+ else if (_check_file (root, "AUTORUN.EXE", TRUE))
+ {
+ /* TODO */
+ }
+ else if (_check_file (root, "autorun.inf", FALSE))
+ {
+ /* TODO */
+ }
+ else if (_check_file (root, "AUTORUN.INF", FALSE))
+ {
+ /* TODO */
+ }
+
+ if (program_to_spawn != NULL)
+ {
+ path_to_spawn = g_file_get_path (program_to_spawn);
+ }
+
+ cwd_for_program = g_file_get_path (root);
+
+ error_string = NULL;
+ if (path_to_spawn != NULL && cwd_for_program != NULL)
+ {
+ if (chdir (cwd_for_program) == 0)
+ {
+ execl (path_to_spawn, path_to_spawn, NULL);
+ error_string = g_strdup_printf (_("Error starting autorun program: %s"), strerror (errno));
+ goto out;
+ }
+ error_string = g_strdup_printf (_("Error starting autorun program: %s"), strerror (errno));
+ goto out;
+ }
+ error_string = g_strdup_printf (_("Cannot find the autorun program"));
+
+out:
+ if (program_to_spawn != NULL)
+ {
+ g_object_unref (program_to_spawn);
+ }
+ if (root != NULL)
+ {
+ g_object_unref (root);
+ }
+ g_free (path_to_spawn);
+ g_free (cwd_for_program);
+
+ if (error_string != NULL)
+ {
+ GtkWidget *dialog;
+ dialog = gtk_message_dialog_new_with_markup (NULL, /* TODO: parent window? */
+ 0,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_OK,
+ _("<big><b>Error autorunning software</b></big>"));
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), "%s", error_string);
+ gtk_dialog_run (GTK_DIALOG (dialog));
+ gtk_widget_destroy (dialog);
+ g_free (error_string);
+ }
+}
+
+static void
+present_autorun_for_software_dialog (GMount *mount)
+{
+ GIcon *icon;
+ int icon_size;
+ CajaIconInfo *icon_info;
+ GdkPixbuf *pixbuf;
+ GtkWidget *image;
+ char *mount_name;
+ GtkWidget *dialog;
+ AutorunSoftwareDialogData *data;
+
+ mount_name = g_mount_get_name (mount);
+
+ dialog = gtk_message_dialog_new_with_markup (NULL, /* TODO: parent window? */
+ 0,
+ GTK_MESSAGE_OTHER,
+ GTK_BUTTONS_CANCEL,
+ _("<big><b>This medium contains software intended to be automatically started. Would you like to run it?</b></big>"));
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ _("The software will run directly from the medium \"%s\". "
+ "You should never run software that you don't trust.\n"
+ "\n"
+ "If in doubt, press Cancel."),
+ mount_name);
+
+ /* TODO: in a star trek future add support for verifying
+ * software on media (e.g. if it has a certificate, check it
+ * etc.)
+ */
+
+
+ icon = g_mount_get_icon (mount);
+ icon_size = caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_DIALOG);
+ icon_info = caja_icon_info_lookup (icon, icon_size);
+ pixbuf = caja_icon_info_get_pixbuf_at_size (icon_info, icon_size);
+ image = gtk_image_new_from_pixbuf (pixbuf);
+ gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0.0);
+
+ gtk_message_dialog_set_image (GTK_MESSAGE_DIALOG (dialog), image);
+
+ gtk_window_set_title (GTK_WINDOW (dialog), mount_name);
+ gtk_window_set_icon (GTK_WINDOW (dialog), pixbuf);
+
+ data = g_new0 (AutorunSoftwareDialogData, 1);
+ data->dialog = dialog;
+ data->mount = g_object_ref (mount);
+
+ g_signal_connect (G_OBJECT (mount),
+ "unmounted",
+ G_CALLBACK (autorun_software_dialog_mount_unmounted),
+ data);
+
+ gtk_dialog_add_button (GTK_DIALOG (dialog),
+ _("_Run"),
+ GTK_RESPONSE_OK);
+
+ gtk_widget_show_all (dialog);
+
+ if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK)
+ {
+ gtk_widget_destroy (dialog);
+ autorun (mount);
+ }
+
+ g_object_unref (icon_info);
+ g_object_unref (pixbuf);
+ g_free (mount_name);
+}
+
+int
+main (int argc, char *argv[])
+{
+ GVolumeMonitor *monitor;
+ GFile *file;
+ GMount *mount;
+
+ bindtextdomain (GETTEXT_PACKAGE, MATELOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ textdomain (GETTEXT_PACKAGE);
+
+ gtk_init (&argc, &argv);
+
+ if (argc != 2)
+ {
+ goto out;
+ }
+
+ /* instantiate monitor so we get the "unmounted" signal properly */
+ monitor = g_volume_monitor_get ();
+ if (monitor == NULL)
+ {
+ goto out;
+ }
+
+ file = g_file_new_for_commandline_arg (argv[1]);
+ if (file == NULL)
+ {
+ g_object_unref (monitor);
+ goto out;
+ }
+
+ mount = g_file_find_enclosing_mount (file, NULL, NULL);
+ if (mount == NULL)
+ {
+ g_object_unref (file);
+ g_object_unref (monitor);
+ goto out;
+ }
+
+ present_autorun_for_software_dialog (mount);
+ g_object_unref (file);
+ g_object_unref (monitor);
+ g_object_unref (mount);
+
+out:
+ return 0;
+}
diff --git a/src/caja-bookmark-list.c b/src/caja-bookmark-list.c
new file mode 100644
index 00000000..17f9b961
--- /dev/null
+++ b/src/caja-bookmark-list.c
@@ -0,0 +1,775 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 1999, 2000 Eazel, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Authors: John Sullivan <[email protected]>
+ */
+
+/* caja-bookmark-list.c - implementation of centralized list of bookmarks.
+ */
+
+#include <config.h>
+#include "caja-bookmark-list.h"
+
+#include <libcaja-private/caja-file-utilities.h>
+#include <libcaja-private/caja-file.h>
+#include <libcaja-private/caja-icon-names.h>
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-string.h>
+#include <gio/gio.h>
+
+#define MAX_BOOKMARK_LENGTH 80
+#define LOAD_JOB 1
+#define SAVE_JOB 2
+
+enum
+{
+ CONTENTS_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+static char *window_geometry;
+static CajaBookmarkList *singleton = NULL;
+
+/* forward declarations */
+
+static void caja_bookmark_list_load_file (CajaBookmarkList *bookmarks);
+static void caja_bookmark_list_save_file (CajaBookmarkList *bookmarks);
+
+G_DEFINE_TYPE(CajaBookmarkList, caja_bookmark_list, G_TYPE_OBJECT)
+
+static CajaBookmark *
+new_bookmark_from_uri (const char *uri, const char *label)
+{
+ CajaBookmark *new_bookmark;
+ CajaFile *file;
+ char *name;
+ GIcon *icon;
+ gboolean has_label;
+ GFile *location;
+ gboolean native;
+
+ location = NULL;
+ if (uri)
+ {
+ location = g_file_new_for_uri (uri);
+ }
+
+ has_label = FALSE;
+ if (!label)
+ {
+ name = caja_compute_title_for_location (location);
+ }
+ else
+ {
+ name = g_strdup (label);
+ has_label = TRUE;
+ }
+
+ new_bookmark = NULL;
+
+ if (uri)
+ {
+ native = g_file_is_native (location);
+ file = caja_file_get (location);
+
+ icon = NULL;
+ if (caja_file_check_if_ready (file,
+ CAJA_FILE_ATTRIBUTES_FOR_ICON))
+ {
+ icon = caja_file_get_gicon (file, 0);
+ }
+ caja_file_unref (file);
+
+ if (icon == NULL)
+ {
+ icon = native ? g_themed_icon_new (CAJA_ICON_FOLDER) :
+ g_themed_icon_new (CAJA_ICON_FOLDER_REMOTE);
+ }
+
+ new_bookmark = caja_bookmark_new (location, name, has_label, icon);
+
+ g_object_unref (icon);
+
+ }
+ g_free (name);
+ g_object_unref (location);
+ return new_bookmark;
+}
+
+static GFile *
+caja_bookmark_list_get_file (void)
+{
+ char *filename;
+ GFile *file;
+
+ filename = g_build_filename (g_get_home_dir (),
+ ".gtk-bookmarks",
+ NULL);
+ file = g_file_new_for_path (filename);
+
+ g_free (filename);
+
+ return file;
+}
+
+/* Initialization. */
+
+static void
+bookmark_in_list_changed_callback (CajaBookmark *bookmark,
+ CajaBookmarkList *bookmarks)
+{
+ g_assert (CAJA_IS_BOOKMARK (bookmark));
+ g_assert (CAJA_IS_BOOKMARK_LIST (bookmarks));
+
+ /* Save changes so we'll have the good icon next time. */
+ caja_bookmark_list_save_file (bookmarks);
+}
+
+static void
+stop_monitoring_bookmark (CajaBookmarkList *bookmarks,
+ CajaBookmark *bookmark)
+{
+ g_signal_handlers_disconnect_by_func (bookmark,
+ bookmark_in_list_changed_callback,
+ bookmarks);
+}
+
+static void
+stop_monitoring_one (gpointer data, gpointer user_data)
+{
+ g_assert (CAJA_IS_BOOKMARK (data));
+ g_assert (CAJA_IS_BOOKMARK_LIST (user_data));
+
+ stop_monitoring_bookmark (CAJA_BOOKMARK_LIST (user_data),
+ CAJA_BOOKMARK (data));
+}
+
+static void
+clear (CajaBookmarkList *bookmarks)
+{
+ g_list_foreach (bookmarks->list, stop_monitoring_one, bookmarks);
+ eel_g_object_list_free (bookmarks->list);
+ bookmarks->list = NULL;
+}
+
+static void
+do_finalize (GObject *object)
+{
+ if (CAJA_BOOKMARK_LIST (object)->monitor != NULL)
+ {
+ g_file_monitor_cancel (CAJA_BOOKMARK_LIST (object)->monitor);
+ CAJA_BOOKMARK_LIST (object)->monitor = NULL;
+ }
+
+ g_queue_free (CAJA_BOOKMARK_LIST (object)->pending_ops);
+
+ clear (CAJA_BOOKMARK_LIST (object));
+
+ G_OBJECT_CLASS (caja_bookmark_list_parent_class)->finalize (object);
+}
+
+static GObject *
+do_constructor (GType type,
+ guint n_construct_params,
+ GObjectConstructParam *construct_params)
+{
+ GObject *retval;
+
+ if (singleton != NULL)
+ {
+ return g_object_ref (singleton);
+ }
+
+ retval = G_OBJECT_CLASS (caja_bookmark_list_parent_class)->constructor
+ (type, n_construct_params, construct_params);
+
+ singleton = CAJA_BOOKMARK_LIST (retval);
+ g_object_add_weak_pointer (retval, (gpointer) &singleton);
+
+ return retval;
+}
+
+
+static void
+caja_bookmark_list_class_init (CajaBookmarkListClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+ object_class->finalize = do_finalize;
+ object_class->constructor = do_constructor;
+
+ signals[CONTENTS_CHANGED] =
+ g_signal_new ("contents_changed",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaBookmarkListClass,
+ contents_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+static void
+bookmark_monitor_changed_cb (GFileMonitor *monitor,
+ GFile *child,
+ GFile *other_file,
+ GFileMonitorEvent eflags,
+ gpointer user_data)
+{
+ if (eflags == G_FILE_MONITOR_EVENT_CHANGED ||
+ eflags == G_FILE_MONITOR_EVENT_CREATED)
+ {
+ g_return_if_fail (CAJA_IS_BOOKMARK_LIST (CAJA_BOOKMARK_LIST (user_data)));
+ caja_bookmark_list_load_file (CAJA_BOOKMARK_LIST (user_data));
+ }
+}
+
+static void
+caja_bookmark_list_init (CajaBookmarkList *bookmarks)
+{
+ GFile *file;
+
+ bookmarks->pending_ops = g_queue_new ();
+
+ caja_bookmark_list_load_file (bookmarks);
+
+ file = caja_bookmark_list_get_file ();
+ bookmarks->monitor = g_file_monitor_file (file, 0, NULL, NULL);
+ g_file_monitor_set_rate_limit (bookmarks->monitor, 1000);
+
+ g_signal_connect (bookmarks->monitor, "changed",
+ G_CALLBACK (bookmark_monitor_changed_cb), bookmarks);
+
+ g_object_unref (file);
+}
+
+static void
+insert_bookmark_internal (CajaBookmarkList *bookmarks,
+ CajaBookmark *bookmark,
+ int index)
+{
+ bookmarks->list = g_list_insert (bookmarks->list, bookmark, index);
+
+ g_signal_connect_object (bookmark, "contents_changed",
+ G_CALLBACK (bookmark_in_list_changed_callback), bookmarks, 0);
+}
+
+/**
+ * caja_bookmark_list_append:
+ *
+ * Append a bookmark to a bookmark list.
+ * @bookmarks: CajaBookmarkList to append to.
+ * @bookmark: Bookmark to append a copy of.
+ **/
+void
+caja_bookmark_list_append (CajaBookmarkList *bookmarks,
+ CajaBookmark *bookmark)
+{
+ g_return_if_fail (CAJA_IS_BOOKMARK_LIST (bookmarks));
+ g_return_if_fail (CAJA_IS_BOOKMARK (bookmark));
+
+ insert_bookmark_internal (bookmarks,
+ caja_bookmark_copy (bookmark),
+ -1);
+
+ caja_bookmark_list_save_file (bookmarks);
+}
+
+/**
+ * caja_bookmark_list_contains:
+ *
+ * Check whether a bookmark with matching name and url is already in the list.
+ * @bookmarks: CajaBookmarkList to check contents of.
+ * @bookmark: CajaBookmark to match against.
+ *
+ * Return value: TRUE if matching bookmark is in list, FALSE otherwise
+ **/
+gboolean
+caja_bookmark_list_contains (CajaBookmarkList *bookmarks,
+ CajaBookmark *bookmark)
+{
+ g_return_val_if_fail (CAJA_IS_BOOKMARK_LIST (bookmarks), FALSE);
+ g_return_val_if_fail (CAJA_IS_BOOKMARK (bookmark), FALSE);
+
+ return g_list_find_custom (bookmarks->list,
+ (gpointer)bookmark,
+ caja_bookmark_compare_with)
+ != NULL;
+}
+
+/**
+ * caja_bookmark_list_delete_item_at:
+ *
+ * Delete the bookmark at the specified position.
+ * @bookmarks: the list of bookmarks.
+ * @index: index, must be less than length of list.
+ **/
+void
+caja_bookmark_list_delete_item_at (CajaBookmarkList *bookmarks,
+ guint index)
+{
+ GList *doomed;
+
+ g_return_if_fail (CAJA_IS_BOOKMARK_LIST (bookmarks));
+ g_return_if_fail (index < g_list_length (bookmarks->list));
+
+ doomed = g_list_nth (bookmarks->list, index);
+ bookmarks->list = g_list_remove_link (bookmarks->list, doomed);
+
+ g_assert (CAJA_IS_BOOKMARK (doomed->data));
+ stop_monitoring_bookmark (bookmarks, CAJA_BOOKMARK (doomed->data));
+ g_object_unref (doomed->data);
+
+ g_list_free_1 (doomed);
+
+ caja_bookmark_list_save_file (bookmarks);
+}
+
+/**
+ * caja_bookmark_list_move_item:
+ *
+ * Move the item from the given position to the destination.
+ * @index: the index of the first bookmark.
+ * @destination: the index of the second bookmark.
+ **/
+void
+caja_bookmark_list_move_item (CajaBookmarkList *bookmarks,
+ guint index,
+ guint destination)
+{
+ GList *bookmark_item;
+
+ if (index == destination)
+ {
+ return;
+ }
+
+ bookmark_item = g_list_nth (bookmarks->list, index);
+ bookmarks->list = g_list_remove_link (bookmarks->list,
+ bookmark_item);
+
+ if (index < destination)
+ {
+ bookmarks->list = g_list_insert (bookmarks->list,
+ bookmark_item->data,
+ destination - 1);
+ }
+ else
+ {
+ bookmarks->list = g_list_insert (bookmarks->list,
+ bookmark_item->data,
+ destination);
+ }
+
+ caja_bookmark_list_save_file (bookmarks);
+}
+
+/**
+ * caja_bookmark_list_delete_items_with_uri:
+ *
+ * Delete all bookmarks with the given uri.
+ * @bookmarks: the list of bookmarks.
+ * @uri: The uri to match.
+ **/
+void
+caja_bookmark_list_delete_items_with_uri (CajaBookmarkList *bookmarks,
+ const char *uri)
+{
+ GList *node, *next;
+ gboolean list_changed;
+ char *bookmark_uri;
+
+ g_return_if_fail (CAJA_IS_BOOKMARK_LIST (bookmarks));
+ g_return_if_fail (uri != NULL);
+
+ list_changed = FALSE;
+ for (node = bookmarks->list; node != NULL; node = next)
+ {
+ next = node->next;
+
+ bookmark_uri = caja_bookmark_get_uri (CAJA_BOOKMARK (node->data));
+ if (eel_strcmp (bookmark_uri, uri) == 0)
+ {
+ bookmarks->list = g_list_remove_link (bookmarks->list, node);
+ stop_monitoring_bookmark (bookmarks, CAJA_BOOKMARK (node->data));
+ g_object_unref (node->data);
+ g_list_free_1 (node);
+ list_changed = TRUE;
+ }
+ g_free (bookmark_uri);
+ }
+
+ if (list_changed)
+ {
+ caja_bookmark_list_save_file (bookmarks);
+ }
+}
+
+/**
+ * caja_bookmark_list_get_window_geometry:
+ *
+ * Get a string representing the bookmark_list's window's geometry.
+ * This is the value set earlier by caja_bookmark_list_set_window_geometry.
+ * @bookmarks: the list of bookmarks associated with the window.
+ * Return value: string representation of window's geometry, suitable for
+ * passing to mate_parse_geometry(), or NULL if
+ * no window geometry has yet been saved for this bookmark list.
+ **/
+const char *
+caja_bookmark_list_get_window_geometry (CajaBookmarkList *bookmarks)
+{
+ return window_geometry;
+}
+
+/**
+ * caja_bookmark_list_insert_item:
+ *
+ * Insert a bookmark at a specified position.
+ * @bookmarks: the list of bookmarks.
+ * @index: the position to insert the bookmark at.
+ * @new_bookmark: the bookmark to insert a copy of.
+ **/
+void
+caja_bookmark_list_insert_item (CajaBookmarkList *bookmarks,
+ CajaBookmark *new_bookmark,
+ guint index)
+{
+ g_return_if_fail (CAJA_IS_BOOKMARK_LIST (bookmarks));
+ g_return_if_fail (index <= g_list_length (bookmarks->list));
+
+ insert_bookmark_internal (bookmarks,
+ caja_bookmark_copy (new_bookmark),
+ index);
+
+ caja_bookmark_list_save_file (bookmarks);
+}
+
+/**
+ * caja_bookmark_list_item_at:
+ *
+ * Get the bookmark at the specified position.
+ * @bookmarks: the list of bookmarks.
+ * @index: index, must be less than length of list.
+ *
+ * Return value: the bookmark at position @index in @bookmarks.
+ **/
+CajaBookmark *
+caja_bookmark_list_item_at (CajaBookmarkList *bookmarks, guint index)
+{
+ g_return_val_if_fail (CAJA_IS_BOOKMARK_LIST (bookmarks), NULL);
+ g_return_val_if_fail (index < g_list_length (bookmarks->list), NULL);
+
+ return CAJA_BOOKMARK (g_list_nth_data (bookmarks->list, index));
+}
+
+/**
+ * caja_bookmark_list_length:
+ *
+ * Get the number of bookmarks in the list.
+ * @bookmarks: the list of bookmarks.
+ *
+ * Return value: the length of the bookmark list.
+ **/
+guint
+caja_bookmark_list_length (CajaBookmarkList *bookmarks)
+{
+ g_return_val_if_fail (CAJA_IS_BOOKMARK_LIST(bookmarks), 0);
+
+ return g_list_length (bookmarks->list);
+}
+
+static void
+load_file_finish (CajaBookmarkList *bookmarks,
+ GObject *source,
+ GAsyncResult *res)
+{
+ GError *error = NULL;
+ gchar *contents = NULL;
+
+ g_file_load_contents_finish (G_FILE (source),
+ res, &contents, NULL, NULL, &error);
+
+ if (error == NULL)
+ {
+ char **lines;
+ int i;
+
+ lines = g_strsplit (contents, "\n", -1);
+ for (i = 0; lines[i]; i++)
+ {
+ /* Ignore empty or invalid lines that cannot be parsed properly */
+ if (lines[i][0] != '\0' && lines[i][0] != ' ')
+ {
+ /* gtk 2.7/2.8 might have labels appended to bookmarks which are separated by a space */
+ /* we must seperate the bookmark uri and the potential label */
+ char *space, *label;
+
+ label = NULL;
+ space = strchr (lines[i], ' ');
+ if (space)
+ {
+ *space = '\0';
+ label = g_strdup (space + 1);
+ }
+ insert_bookmark_internal (bookmarks,
+ new_bookmark_from_uri (lines[i], label),
+ -1);
+
+ g_free (label);
+ }
+ }
+ g_free (contents);
+ g_strfreev (lines);
+
+ g_signal_emit (bookmarks, signals[CONTENTS_CHANGED], 0);
+ }
+ else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+ {
+ g_warning ("Could not load bookmark file: %s\n", error->message);
+ g_error_free (error);
+ }
+}
+
+static void
+load_file_async (CajaBookmarkList *self,
+ GAsyncReadyCallback callback)
+{
+ GFile *file;
+
+ file = caja_bookmark_list_get_file ();
+
+ /* Wipe out old list. */
+ clear (self);
+
+ /* keep the bookmark list alive */
+ g_object_ref (self);
+ g_file_load_contents_async (file, NULL, callback, self);
+
+ g_object_unref (file);
+}
+
+static void
+save_file_finish (CajaBookmarkList *bookmarks,
+ GObject *source,
+ GAsyncResult *res)
+{
+ GError *error = NULL;
+ GFile *file;
+
+ g_file_replace_contents_finish (G_FILE (source),
+ res, NULL, &error);
+
+ if (error != NULL)
+ {
+ g_warning ("Unable to replace contents of the bookmarks file: %s",
+ error->message);
+ g_error_free (error);
+ }
+
+ file = caja_bookmark_list_get_file ();
+
+ /* re-enable bookmark file monitoring */
+ bookmarks->monitor = g_file_monitor_file (file, 0, NULL, NULL);
+ g_file_monitor_set_rate_limit (bookmarks->monitor, 1000);
+ g_signal_connect (bookmarks->monitor, "changed",
+ G_CALLBACK (bookmark_monitor_changed_cb), bookmarks);
+
+ g_object_unref (file);
+}
+
+static void
+save_file_async (CajaBookmarkList *bookmarks,
+ GAsyncReadyCallback callback)
+{
+ GFile *file;
+ GList *l;
+ GString *bookmark_string;
+
+ /* temporarily disable bookmark file monitoring when writing file */
+ if (bookmarks->monitor != NULL)
+ {
+ g_file_monitor_cancel (bookmarks->monitor);
+ bookmarks->monitor = NULL;
+ }
+
+ file = caja_bookmark_list_get_file ();
+ bookmark_string = g_string_new (NULL);
+
+ for (l = bookmarks->list; l; l = l->next)
+ {
+ CajaBookmark *bookmark;
+
+ bookmark = CAJA_BOOKMARK (l->data);
+
+ /* make sure we save label if it has one for compatibility with GTK 2.7 and 2.8 */
+ if (caja_bookmark_get_has_custom_name (bookmark))
+ {
+ char *label, *uri;
+ label = caja_bookmark_get_name (bookmark);
+ uri = caja_bookmark_get_uri (bookmark);
+ g_string_append_printf (bookmark_string,
+ "%s %s\n", uri, label);
+ g_free (uri);
+ g_free (label);
+ }
+ else
+ {
+ char *uri;
+ uri = caja_bookmark_get_uri (bookmark);
+ g_string_append_printf (bookmark_string, "%s\n", uri);
+ g_free (uri);
+ }
+ }
+
+ /* keep the bookmark list alive */
+ g_object_ref (bookmarks);
+ g_file_replace_contents_async (file, bookmark_string->str,
+ bookmark_string->len, NULL,
+ FALSE, 0, NULL, callback,
+ bookmarks);
+
+ g_object_unref (file);
+}
+
+static void
+process_next_op (CajaBookmarkList *bookmarks);
+
+static void
+op_processed_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ CajaBookmarkList *self = user_data;
+ int op;
+
+ op = GPOINTER_TO_INT (g_queue_pop_tail (self->pending_ops));
+
+ if (op == LOAD_JOB)
+ {
+ load_file_finish (self, source, res);
+ }
+ else
+ {
+ save_file_finish (self, source, res);
+ }
+
+ if (!g_queue_is_empty (self->pending_ops))
+ {
+ process_next_op (self);
+ }
+
+ /* release the reference acquired during the _async method */
+ g_object_unref (self);
+}
+
+static void
+process_next_op (CajaBookmarkList *bookmarks)
+{
+ gint op;
+
+ op = GPOINTER_TO_INT (g_queue_peek_tail (bookmarks->pending_ops));
+
+ if (op == LOAD_JOB)
+ {
+ load_file_async (bookmarks, op_processed_cb);
+ }
+ else
+ {
+ save_file_async (bookmarks, op_processed_cb);
+ }
+}
+
+/**
+ * caja_bookmark_list_load_file:
+ *
+ * Reads bookmarks from file, clobbering contents in memory.
+ * @bookmarks: the list of bookmarks to fill with file contents.
+ **/
+static void
+caja_bookmark_list_load_file (CajaBookmarkList *bookmarks)
+{
+ g_queue_push_head (bookmarks->pending_ops, GINT_TO_POINTER (LOAD_JOB));
+
+ if (g_queue_get_length (bookmarks->pending_ops) == 1)
+ {
+ process_next_op (bookmarks);
+ }
+}
+
+/**
+ * caja_bookmark_list_save_file:
+ *
+ * Save bookmarks to disk.
+ * @bookmarks: the list of bookmarks to save.
+ **/
+static void
+caja_bookmark_list_save_file (CajaBookmarkList *bookmarks)
+{
+ g_signal_emit (bookmarks, signals[CONTENTS_CHANGED], 0);
+
+ g_queue_push_head (bookmarks->pending_ops, GINT_TO_POINTER (SAVE_JOB));
+
+ if (g_queue_get_length (bookmarks->pending_ops) == 1)
+ {
+ process_next_op (bookmarks);
+ }
+}
+
+/**
+ * caja_bookmark_list_new:
+ *
+ * Create a new bookmark_list, with contents read from disk.
+ *
+ * Return value: A pointer to the new widget.
+ **/
+CajaBookmarkList *
+caja_bookmark_list_new (void)
+{
+ CajaBookmarkList *list;
+
+ list = CAJA_BOOKMARK_LIST (g_object_new (CAJA_TYPE_BOOKMARK_LIST, NULL));
+
+ return list;
+}
+
+/**
+ * caja_bookmark_list_set_window_geometry:
+ *
+ * Set a bookmarks window's geometry (position & size), in string form. This is
+ * stored to disk by this class, and can be retrieved later in
+ * the same session or in a future session.
+ * @bookmarks: the list of bookmarks associated with the window.
+ * @geometry: the new window geometry string.
+ **/
+void
+caja_bookmark_list_set_window_geometry (CajaBookmarkList *bookmarks,
+ const char *geometry)
+{
+ g_return_if_fail (CAJA_IS_BOOKMARK_LIST (bookmarks));
+ g_return_if_fail (geometry != NULL);
+
+ g_free (window_geometry);
+ window_geometry = g_strdup (geometry);
+
+ caja_bookmark_list_save_file (bookmarks);
+}
+
diff --git a/src/caja-bookmark-list.h b/src/caja-bookmark-list.h
new file mode 100644
index 00000000..d5a584cc
--- /dev/null
+++ b/src/caja-bookmark-list.h
@@ -0,0 +1,87 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 1999, 2000 Eazel, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Authors: John Sullivan <[email protected]>
+ */
+
+/* caja-bookmark-list.h - interface for centralized list of bookmarks.
+ */
+
+#ifndef CAJA_BOOKMARK_LIST_H
+#define CAJA_BOOKMARK_LIST_H
+
+#include <libcaja-private/caja-bookmark.h>
+#include <gio/gio.h>
+
+typedef struct CajaBookmarkList CajaBookmarkList;
+typedef struct CajaBookmarkListClass CajaBookmarkListClass;
+
+#define CAJA_TYPE_BOOKMARK_LIST caja_bookmark_list_get_type()
+#define CAJA_BOOKMARK_LIST(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_BOOKMARK_LIST, CajaBookmarkList))
+#define CAJA_BOOKMARK_LIST_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_BOOKMARK_LIST, CajaBookmarkListClass))
+#define CAJA_IS_BOOKMARK_LIST(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_BOOKMARK_LIST))
+#define CAJA_IS_BOOKMARK_LIST_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_BOOKMARK_LIST))
+#define CAJA_BOOKMARK_LIST_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_BOOKMARK_LIST, CajaBookmarkListClass))
+
+struct CajaBookmarkList
+{
+ GObject object;
+
+ GList *list;
+ GFileMonitor *monitor;
+ GQueue *pending_ops;
+};
+
+struct CajaBookmarkListClass
+{
+ GObjectClass parent_class;
+ void (* contents_changed) (CajaBookmarkList *bookmarks);
+};
+
+GType caja_bookmark_list_get_type (void);
+CajaBookmarkList * caja_bookmark_list_new (void);
+void caja_bookmark_list_append (CajaBookmarkList *bookmarks,
+ CajaBookmark *bookmark);
+gboolean caja_bookmark_list_contains (CajaBookmarkList *bookmarks,
+ CajaBookmark *bookmark);
+void caja_bookmark_list_delete_item_at (CajaBookmarkList *bookmarks,
+ guint index);
+void caja_bookmark_list_delete_items_with_uri (CajaBookmarkList *bookmarks,
+ const char *uri);
+void caja_bookmark_list_insert_item (CajaBookmarkList *bookmarks,
+ CajaBookmark *bookmark,
+ guint index);
+guint caja_bookmark_list_length (CajaBookmarkList *bookmarks);
+CajaBookmark * caja_bookmark_list_item_at (CajaBookmarkList *bookmarks,
+ guint index);
+void caja_bookmark_list_move_item (CajaBookmarkList *bookmarks,
+ guint index,
+ guint destination);
+void caja_bookmark_list_set_window_geometry (CajaBookmarkList *bookmarks,
+ const char *geometry);
+const char * caja_bookmark_list_get_window_geometry (CajaBookmarkList *bookmarks);
+
+#endif /* CAJA_BOOKMARK_LIST_H */
diff --git a/src/caja-bookmarks-window.c b/src/caja-bookmarks-window.c
new file mode 100644
index 00000000..afd87425
--- /dev/null
+++ b/src/caja-bookmarks-window.c
@@ -0,0 +1,1112 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 1999, 2000 Eazel, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Authors: John Sullivan <[email protected]>
+ */
+
+/* caja-bookmarks-window.c - implementation of bookmark-editing window.
+ */
+
+#include <config.h>
+#include "caja-bookmarks-window.h"
+#include "caja-window.h"
+#include "caja-navigation-window.h"
+#include "caja-spatial-window.h"
+#include <libcaja-private/caja-undo.h>
+#include <libcaja-private/caja-global-preferences.h>
+#include <eel/eel-gtk-extensions.h>
+#include <eel/eel-mate-extensions.h>
+#include <libcaja-private/caja-undo-signal-handlers.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+/* Static variables to keep track of window state. If there were
+ * more than one bookmark-editing window, these would be struct or
+ * class fields.
+ */
+static int bookmark_list_changed_signal_id;
+static CajaBookmarkList *bookmarks = NULL;
+static GtkTreeView *bookmark_list_widget = NULL; /* awkward name to distinguish from CajaBookmarkList */
+static GtkListStore *bookmark_list_store = NULL;
+static GtkListStore *bookmark_empty_list_store = NULL;
+static GtkTreeSelection *bookmark_selection = NULL;
+static int selection_changed_id = 0;
+static GtkWidget *name_field = NULL;
+static int name_field_changed_signal_id;
+static GtkWidget *remove_button = NULL;
+static GtkWidget *jump_button = NULL;
+static gboolean text_changed = FALSE;
+static gboolean name_text_changed = FALSE;
+static GtkWidget *uri_field = NULL;
+static int uri_field_changed_signal_id;
+static int row_changed_signal_id;
+static int row_deleted_signal_id;
+static int row_activated_signal_id;
+static int button_pressed_signal_id;
+static int key_pressed_signal_id;
+static int jump_button_signal_id;
+static CajaApplication *application;
+static gboolean parent_is_browser_window;
+
+/* forward declarations */
+static guint get_selected_row (void);
+static gboolean get_selection_exists (void);
+static void name_or_uri_field_activate (CajaEntry *entry);
+static void caja_bookmarks_window_restore_geometry (GtkWidget *window);
+static void on_bookmark_list_changed (CajaBookmarkList *list,
+ gpointer user_data);
+static void on_name_field_changed (GtkEditable *editable,
+ gpointer user_data);
+static void on_remove_button_clicked (GtkButton *button,
+ gpointer user_data);
+static void on_jump_button_clicked (GtkButton *button,
+ gpointer user_data);
+static void on_row_changed (GtkListStore *store,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer user_data);
+static void on_row_deleted (GtkListStore *store,
+ GtkTreePath *path,
+ gpointer user_data);
+static void on_row_activated (GtkTreeView *view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ gpointer user_data);
+static gboolean on_button_pressed (GtkTreeView *view,
+ GdkEventButton *event,
+ gpointer user_data);
+static gboolean on_key_pressed (GtkTreeView *view,
+ GdkEventKey *event,
+ gpointer user_data);
+static void on_selection_changed (GtkTreeSelection *treeselection,
+ gpointer user_data);
+
+static gboolean on_text_field_focus_out_event (GtkWidget *widget,
+ GdkEventFocus *event,
+ gpointer user_data);
+static void on_uri_field_changed (GtkEditable *editable,
+ gpointer user_data);
+static gboolean on_window_delete_event (GtkWidget *widget,
+ GdkEvent *event,
+ gpointer user_data);
+static void on_window_hide_event (GtkWidget *widget,
+ gpointer user_data);
+static void on_window_destroy_event (GtkWidget *widget,
+ gpointer user_data);
+static void repopulate (void);
+static void set_up_close_accelerator (GtkWidget *window);
+static void open_selected_bookmark (gpointer user_data, GdkScreen *screen);
+static void update_bookmark_from_text (void);
+
+/* We store a pointer to the bookmark in a column so when an item is moved
+ with DnD we know which item it is. However we have to be careful to keep
+ this in sync with the actual bookmark. Note that
+ caja_bookmark_list_insert_item() makes a copy of the bookmark, so we
+ have to fetch the new copy and update our pointer. */
+#define BOOKMARK_LIST_COLUMN_ICON 0
+#define BOOKMARK_LIST_COLUMN_NAME 1
+#define BOOKMARK_LIST_COLUMN_BOOKMARK 2
+#define BOOKMARK_LIST_COLUMN_STYLE 3
+#define BOOKMARK_LIST_COLUMN_COUNT 4
+
+/* layout constants */
+
+/* Keep window from shrinking down ridiculously small; numbers are somewhat arbitrary */
+#define BOOKMARKS_WINDOW_MIN_WIDTH 300
+#define BOOKMARKS_WINDOW_MIN_HEIGHT 100
+
+/* Larger size initially; user can stretch or shrink (but not shrink below min) */
+#define BOOKMARKS_WINDOW_INITIAL_WIDTH 500
+#define BOOKMARKS_WINDOW_INITIAL_HEIGHT 200
+
+static void
+caja_bookmarks_window_response_callback (GtkDialog *dialog,
+ int response_id,
+ gpointer callback_data)
+{
+ if (response_id == GTK_RESPONSE_HELP)
+ {
+ GError *error = NULL;
+
+ gtk_show_uri (gtk_window_get_screen (GTK_WINDOW (dialog)),
+ "ghelp:user-guide#goscaja-36",
+ gtk_get_current_event_time (), &error);
+
+ if (error)
+ {
+ GtkWidget *err_dialog;
+ err_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_OK,
+ _("There was an error displaying help: \n%s"),
+ error->message);
+
+ g_signal_connect (G_OBJECT (err_dialog),
+ "response", G_CALLBACK (gtk_widget_destroy),
+ NULL);
+ gtk_window_set_resizable (GTK_WINDOW (err_dialog), FALSE);
+ gtk_widget_show (err_dialog);
+ g_error_free (error);
+ }
+ }
+ else if (response_id == GTK_RESPONSE_CLOSE)
+ {
+ gtk_widget_hide (GTK_WIDGET (dialog));
+ }
+}
+
+static GtkListStore *
+create_bookmark_store (void)
+{
+ return gtk_list_store_new (BOOKMARK_LIST_COLUMN_COUNT,
+ GDK_TYPE_PIXBUF,
+ G_TYPE_STRING,
+ G_TYPE_OBJECT,
+ PANGO_TYPE_STYLE);
+}
+
+static void
+setup_empty_list (void)
+{
+ GtkTreeIter iter;
+
+ bookmark_empty_list_store = create_bookmark_store ();
+ gtk_list_store_append (bookmark_empty_list_store, &iter);
+
+ gtk_list_store_set (bookmark_empty_list_store, &iter,
+ BOOKMARK_LIST_COLUMN_NAME, _("No bookmarks defined"),
+ BOOKMARK_LIST_COLUMN_STYLE, PANGO_STYLE_ITALIC,
+ -1);
+}
+
+static void
+bookmarks_set_empty (gboolean empty)
+{
+ GtkTreeIter iter;
+
+ if (empty)
+ {
+ gtk_tree_view_set_model (bookmark_list_widget,
+ GTK_TREE_MODEL (bookmark_empty_list_store));
+ gtk_widget_set_sensitive (GTK_WIDGET (bookmark_list_widget), FALSE);
+ }
+ else
+ {
+ gtk_tree_view_set_model (bookmark_list_widget,
+ GTK_TREE_MODEL (bookmark_list_store));
+ gtk_widget_set_sensitive (GTK_WIDGET (bookmark_list_widget), TRUE);
+
+ if (caja_bookmark_list_length (bookmarks) > 0 &&
+ !get_selection_exists ())
+ {
+ gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (bookmark_list_store),
+ &iter, NULL, 0);
+ gtk_tree_selection_select_iter (bookmark_selection, &iter);
+ }
+ }
+
+ on_selection_changed (bookmark_selection, NULL);
+}
+
+static void
+edit_bookmarks_dialog_reset_signals (gpointer data,
+ GObject *obj)
+{
+ g_signal_handler_disconnect (GTK_OBJECT (jump_button),
+ jump_button_signal_id);
+ g_signal_handler_disconnect (GTK_OBJECT (bookmark_list_widget),
+ row_activated_signal_id);
+ jump_button_signal_id =
+ g_signal_connect (jump_button, "clicked",
+ G_CALLBACK (on_jump_button_clicked), NULL);
+ row_activated_signal_id =
+ g_signal_connect (bookmark_list_widget, "row_activated",
+ G_CALLBACK (on_row_activated), NULL);
+}
+
+/**
+ * create_bookmarks_window:
+ *
+ * Create a new bookmark-editing window.
+ * @list: The CajaBookmarkList that this window will edit.
+ *
+ * Return value: A pointer to the new window.
+ **/
+GtkWindow *
+create_bookmarks_window (CajaBookmarkList *list, GObject *undo_manager_source)
+{
+ GtkWidget *window;
+ GtkTreeViewColumn *col;
+ GtkCellRenderer *rend;
+ GtkBuilder *builder;
+
+ bookmarks = list;
+
+ builder = gtk_builder_new ();
+ if (!gtk_builder_add_from_file (builder,
+ UIDIR "/caja-bookmarks-window.ui",
+ NULL))
+ {
+ return NULL;
+ }
+
+ window = (GtkWidget *)gtk_builder_get_object (builder, "bookmarks_dialog");
+ bookmark_list_widget = (GtkTreeView *)gtk_builder_get_object (builder, "bookmark_tree_view");
+ remove_button = (GtkWidget *)gtk_builder_get_object (builder, "bookmark_delete_button");
+ jump_button = (GtkWidget *)gtk_builder_get_object (builder, "bookmark_jump_button");
+
+ application = CAJA_WINDOW (undo_manager_source)->application;
+
+ if (CAJA_IS_NAVIGATION_WINDOW (undo_manager_source))
+ {
+ parent_is_browser_window = TRUE;
+ }
+ else
+ {
+ parent_is_browser_window = FALSE;
+ }
+
+ set_up_close_accelerator (window);
+ caja_undo_share_undo_manager (G_OBJECT (window), undo_manager_source);
+
+ gtk_window_set_wmclass (GTK_WINDOW (window), "bookmarks", "Caja");
+ caja_bookmarks_window_restore_geometry (window);
+
+ g_object_weak_ref (G_OBJECT (undo_manager_source), edit_bookmarks_dialog_reset_signals,
+ undo_manager_source);
+
+ bookmark_list_widget = GTK_TREE_VIEW (gtk_builder_get_object (builder, "bookmark_tree_view"));
+
+ rend = gtk_cell_renderer_pixbuf_new ();
+ col = gtk_tree_view_column_new_with_attributes ("Icon",
+ rend,
+ "pixbuf",
+ BOOKMARK_LIST_COLUMN_ICON,
+ NULL);
+ gtk_tree_view_append_column (bookmark_list_widget,
+ GTK_TREE_VIEW_COLUMN (col));
+ gtk_tree_view_column_set_fixed_width (GTK_TREE_VIEW_COLUMN (col),
+ CAJA_ICON_SIZE_SMALLER);
+
+ rend = gtk_cell_renderer_text_new ();
+ g_object_set (rend,
+ "ellipsize", PANGO_ELLIPSIZE_END,
+ "ellipsize-set", TRUE,
+ NULL);
+
+ col = gtk_tree_view_column_new_with_attributes ("Icon",
+ rend,
+ "text",
+ BOOKMARK_LIST_COLUMN_NAME,
+ "style",
+ BOOKMARK_LIST_COLUMN_STYLE,
+ NULL);
+ gtk_tree_view_append_column (bookmark_list_widget,
+ GTK_TREE_VIEW_COLUMN (col));
+
+ bookmark_list_store = create_bookmark_store ();
+ setup_empty_list ();
+ gtk_tree_view_set_model (bookmark_list_widget,
+ GTK_TREE_MODEL (bookmark_empty_list_store));
+
+ bookmark_selection =
+ GTK_TREE_SELECTION (gtk_tree_view_get_selection (bookmark_list_widget));
+
+ name_field = caja_entry_new ();
+
+ gtk_widget_show (name_field);
+ gtk_box_pack_start (GTK_BOX (gtk_builder_get_object (builder, "bookmark_name_placeholder")),
+ name_field, TRUE, TRUE, 0);
+ caja_undo_editable_set_undo_key (GTK_EDITABLE (name_field), TRUE);
+
+ gtk_label_set_mnemonic_widget (
+ GTK_LABEL (gtk_builder_get_object (builder, "bookmark_name_label")),
+ name_field);
+
+ uri_field = caja_entry_new ();
+ gtk_widget_show (uri_field);
+ gtk_box_pack_start (GTK_BOX (gtk_builder_get_object (builder, "bookmark_location_placeholder")),
+ uri_field, TRUE, TRUE, 0);
+ caja_undo_editable_set_undo_key (GTK_EDITABLE (uri_field), TRUE);
+
+ gtk_label_set_mnemonic_widget (
+ GTK_LABEL (gtk_builder_get_object (builder, "bookmark_location_label")),
+ uri_field);
+
+ bookmark_list_changed_signal_id =
+ g_signal_connect (bookmarks, "contents_changed",
+ G_CALLBACK (on_bookmark_list_changed), NULL);
+ row_changed_signal_id =
+ g_signal_connect (bookmark_list_store, "row_changed",
+ G_CALLBACK (on_row_changed), NULL);
+ row_deleted_signal_id =
+ g_signal_connect (bookmark_list_store, "row_deleted",
+ G_CALLBACK (on_row_deleted), NULL);
+ row_activated_signal_id =
+ g_signal_connect (bookmark_list_widget, "row_activated",
+ G_CALLBACK (on_row_activated), undo_manager_source);
+ button_pressed_signal_id =
+ g_signal_connect (bookmark_list_widget, "button_press_event",
+ G_CALLBACK (on_button_pressed), NULL);
+ key_pressed_signal_id =
+ g_signal_connect (bookmark_list_widget, "key_press_event",
+ G_CALLBACK (on_key_pressed), NULL);
+ selection_changed_id =
+ g_signal_connect (bookmark_selection, "changed",
+ G_CALLBACK (on_selection_changed), NULL);
+
+ g_signal_connect (window, "delete_event",
+ G_CALLBACK (on_window_delete_event), NULL);
+ g_signal_connect (window, "hide",
+ G_CALLBACK (on_window_hide_event), NULL);
+ g_signal_connect (window, "destroy",
+ G_CALLBACK (on_window_destroy_event), NULL);
+ g_signal_connect (window, "response",
+ G_CALLBACK (caja_bookmarks_window_response_callback), NULL);
+
+ name_field_changed_signal_id =
+ g_signal_connect (name_field, "changed",
+ G_CALLBACK (on_name_field_changed), NULL);
+
+ g_signal_connect (name_field, "focus_out_event",
+ G_CALLBACK (on_text_field_focus_out_event), NULL);
+ g_signal_connect (name_field, "activate",
+ G_CALLBACK (name_or_uri_field_activate), NULL);
+
+ uri_field_changed_signal_id =
+ g_signal_connect (uri_field, "changed",
+ G_CALLBACK (on_uri_field_changed), NULL);
+
+ g_signal_connect (uri_field, "focus_out_event",
+ G_CALLBACK (on_text_field_focus_out_event), NULL);
+ g_signal_connect (uri_field, "activate",
+ G_CALLBACK (name_or_uri_field_activate), NULL);
+ g_signal_connect (remove_button, "clicked",
+ G_CALLBACK (on_remove_button_clicked), NULL);
+ jump_button_signal_id =
+ g_signal_connect (jump_button, "clicked",
+ G_CALLBACK (on_jump_button_clicked), undo_manager_source);
+
+ gtk_tree_selection_set_mode (bookmark_selection, GTK_SELECTION_BROWSE);
+
+ /* Fill in list widget with bookmarks, must be after signals are wired up. */
+ repopulate();
+
+ g_object_unref (builder);
+
+ return GTK_WINDOW (window);
+}
+
+void
+edit_bookmarks_dialog_set_signals (GObject *undo_manager_source)
+{
+
+ g_signal_handler_disconnect (GTK_OBJECT (jump_button),
+ jump_button_signal_id);
+ g_signal_handler_disconnect (GTK_OBJECT (bookmark_list_widget),
+ row_activated_signal_id);
+
+ jump_button_signal_id =
+ g_signal_connect (jump_button, "clicked",
+ G_CALLBACK (on_jump_button_clicked), undo_manager_source);
+ row_activated_signal_id =
+ g_signal_connect (bookmark_list_widget, "row_activated",
+ G_CALLBACK (on_row_activated), undo_manager_source);
+
+ g_object_weak_ref (G_OBJECT (undo_manager_source), edit_bookmarks_dialog_reset_signals,
+ undo_manager_source);
+}
+
+static CajaBookmark *
+get_selected_bookmark (void)
+{
+ g_return_val_if_fail(CAJA_IS_BOOKMARK_LIST(bookmarks), NULL);
+
+ if (!get_selection_exists())
+ return NULL;
+
+ if (caja_bookmark_list_length (bookmarks) < 1)
+ return NULL;
+
+ return caja_bookmark_list_item_at(bookmarks, get_selected_row ());
+}
+
+static guint
+get_selected_row (void)
+{
+ GtkTreeIter iter;
+ GtkTreePath *path;
+ GtkTreeModel *model;
+ gint *indices, row;
+
+ g_assert (get_selection_exists());
+
+ model = GTK_TREE_MODEL (bookmark_list_store);
+ gtk_tree_selection_get_selected (bookmark_selection,
+ &model,
+ &iter);
+
+ path = gtk_tree_model_get_path (model, &iter);
+ indices = gtk_tree_path_get_indices (path);
+ row = indices[0];
+ gtk_tree_path_free (path);
+ return row;
+}
+
+static gboolean
+get_selection_exists (void)
+{
+ return gtk_tree_selection_get_selected (bookmark_selection, NULL, NULL);
+}
+
+static void
+caja_bookmarks_window_restore_geometry (GtkWidget *window)
+{
+ const char *window_geometry;
+
+ g_return_if_fail (GTK_IS_WINDOW (window));
+ g_return_if_fail (CAJA_IS_BOOKMARK_LIST (bookmarks));
+
+ window_geometry = caja_bookmark_list_get_window_geometry (bookmarks);
+
+ if (window_geometry != NULL)
+ {
+ eel_gtk_window_set_initial_geometry_from_string
+ (GTK_WINDOW (window), window_geometry,
+ BOOKMARKS_WINDOW_MIN_WIDTH, BOOKMARKS_WINDOW_MIN_HEIGHT, FALSE);
+
+ }
+ else
+ {
+ /* use default since there was no stored geometry */
+ gtk_window_set_default_size (GTK_WINDOW (window),
+ BOOKMARKS_WINDOW_INITIAL_WIDTH,
+ BOOKMARKS_WINDOW_INITIAL_HEIGHT);
+
+ /* Let window manager handle default position if no position stored */
+ }
+}
+
+/**
+ * caja_bookmarks_window_save_geometry:
+ *
+ * Save window size & position to disk.
+ * @window: The bookmarks window whose geometry should be saved.
+ **/
+void
+caja_bookmarks_window_save_geometry (GtkWindow *window)
+{
+ g_return_if_fail (GTK_IS_WINDOW (window));
+ g_return_if_fail (CAJA_IS_BOOKMARK_LIST (bookmarks));
+
+ /* Don't bother if window is already closed */
+ if (gtk_widget_get_visible (GTK_WIDGET (window)))
+ {
+ char *geometry_string;
+
+ geometry_string = eel_gtk_window_get_geometry_string (window);
+
+ caja_bookmark_list_set_window_geometry (bookmarks, geometry_string);
+ g_free (geometry_string);
+ }
+}
+
+static void
+on_bookmark_list_changed (CajaBookmarkList *bookmarks, gpointer data)
+{
+ g_return_if_fail (CAJA_IS_BOOKMARK_LIST (bookmarks));
+
+ /* maybe add logic here or in repopulate to save/restore selection */
+ repopulate ();
+}
+
+static void
+on_name_field_changed (GtkEditable *editable,
+ gpointer user_data)
+{
+ GtkTreeIter iter;
+ g_return_if_fail(GTK_IS_TREE_VIEW(bookmark_list_widget));
+ g_return_if_fail(GTK_IS_ENTRY(name_field));
+
+ if (!get_selection_exists())
+ return;
+
+ /* Update text displayed in list instantly. Also remember that
+ * user has changed text so we update real bookmark later.
+ */
+ gtk_tree_selection_get_selected (bookmark_selection,
+ NULL,
+ &iter);
+
+ gtk_list_store_set (bookmark_list_store,
+ &iter, BOOKMARK_LIST_COLUMN_NAME,
+ gtk_entry_get_text (GTK_ENTRY (name_field)),
+ -1);
+ text_changed = TRUE;
+ name_text_changed = TRUE;
+}
+
+static void
+open_selected_bookmark (gpointer user_data, GdkScreen *screen)
+{
+ CajaBookmark *selected;
+ CajaWindow *window;
+ GFile *location;
+
+ selected = get_selected_bookmark ();
+
+ if (!selected)
+ {
+ return;
+ }
+
+ location = caja_bookmark_get_location (selected);
+ if (location == NULL)
+ {
+ return;
+ }
+
+ if (CAJA_IS_NAVIGATION_WINDOW (user_data))
+ {
+ caja_window_go_to (CAJA_WINDOW (user_data), location);
+ }
+ else if (CAJA_IS_SPATIAL_WINDOW (user_data))
+ {
+ window = caja_application_present_spatial_window (application,
+ NULL,
+ NULL,
+ location,
+ screen);
+ }
+ else /* window that opened bookmarks window has been closed */
+ {
+ if (parent_is_browser_window || eel_preferences_get_boolean (CAJA_PREFERENCES_ALWAYS_USE_BROWSER))
+ {
+ window = caja_application_create_navigation_window (application,
+ NULL,
+ screen);
+ caja_window_go_to (window, location);
+ }
+ else
+ {
+ window = caja_application_present_spatial_window (application,
+ NULL,
+ NULL,
+ location,
+ screen);
+ }
+ }
+
+ g_object_unref (location);
+}
+
+static void
+on_jump_button_clicked (GtkButton *button,
+ gpointer user_data)
+{
+ GdkScreen *screen;
+
+ screen = gtk_widget_get_screen (GTK_WIDGET (button));
+ open_selected_bookmark (user_data, screen);
+}
+
+static void
+bookmarks_delete_bookmark (void)
+{
+ GtkTreeIter iter;
+ GtkTreePath *path;
+ gint *indices, row, rows;
+
+ g_assert (GTK_IS_TREE_VIEW (bookmark_list_widget));
+
+ if (!gtk_tree_selection_get_selected (bookmark_selection, NULL, &iter))
+ return;
+
+ /* Remove the selected item from the list store. on_row_deleted() will
+ remove it from the bookmark list. */
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (bookmark_list_store),
+ &iter);
+ indices = gtk_tree_path_get_indices (path);
+ row = indices[0];
+ gtk_tree_path_free (path);
+
+ gtk_list_store_remove (bookmark_list_store, &iter);
+
+ /* Try to select the same row, or the last one in the list. */
+ rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (bookmark_list_store), NULL);
+ if (row >= rows)
+ row = rows - 1;
+
+ if (row < 0)
+ {
+ bookmarks_set_empty (TRUE);
+ }
+ else
+ {
+ gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (bookmark_list_store),
+ &iter, NULL, row);
+ gtk_tree_selection_select_iter (bookmark_selection, &iter);
+ }
+}
+
+static void
+on_remove_button_clicked (GtkButton *button,
+ gpointer user_data)
+{
+ bookmarks_delete_bookmark ();
+}
+
+
+/* This is a bit of a kludge to get DnD to work. We check if the row in the
+ GtkListStore matches the one in the bookmark list. If it doesn't, we assume
+ the bookmark has just been dragged here and we insert it into the bookmark
+ list. */
+static void
+on_row_changed (GtkListStore *store,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer user_data)
+{
+ CajaBookmark *bookmark = NULL, *bookmark_in_list;
+ gint *indices, row;
+ gboolean insert_bookmark = TRUE;
+
+ store = bookmark_list_store;
+
+ indices = gtk_tree_path_get_indices (path);
+ row = indices[0];
+ gtk_tree_model_get (GTK_TREE_MODEL (store), iter,
+ BOOKMARK_LIST_COLUMN_BOOKMARK, &bookmark,
+ -1);
+
+ /* If the bookmark in the list doesn't match the changed one, it must
+ have been dragged here, so we insert it into the list. */
+ if (row < (gint) caja_bookmark_list_length (bookmarks))
+ {
+ bookmark_in_list = caja_bookmark_list_item_at (bookmarks,
+ row);
+ if (bookmark_in_list == bookmark)
+ insert_bookmark = FALSE;
+ }
+
+ if (insert_bookmark)
+ {
+ g_signal_handler_block (bookmarks,
+ bookmark_list_changed_signal_id);
+ caja_bookmark_list_insert_item (bookmarks, bookmark, row);
+ g_signal_handler_unblock (bookmarks,
+ bookmark_list_changed_signal_id);
+
+ /* The bookmark will be copied when inserted into the list, so
+ we have to update the pointer in the list store. */
+ bookmark = caja_bookmark_list_item_at (bookmarks, row);
+ g_signal_handler_block (store, row_changed_signal_id);
+ gtk_list_store_set (store, iter,
+ BOOKMARK_LIST_COLUMN_BOOKMARK, bookmark,
+ -1);
+ g_signal_handler_unblock (store, row_changed_signal_id);
+ }
+}
+
+/* The update_bookmark_from_text() calls in the
+ * on_button_pressed() and on_key_pressed() handlers
+ * of the tree view are a hack.
+ *
+ * The purpose is to track selection changes to the view
+ * and write the text fields back before the selection
+ * actually changed.
+ *
+ * Note that the focus-out event of the text entries is emitted
+ * after the selection changed, else this would not not be neccessary.
+ */
+
+static gboolean
+on_button_pressed (GtkTreeView *view,
+ GdkEventButton *event,
+ gpointer user_data)
+{
+ update_bookmark_from_text ();
+
+ return FALSE;
+}
+
+static gboolean
+on_key_pressed (GtkTreeView *view,
+ GdkEventKey *event,
+ gpointer user_data)
+{
+ if (event->keyval == GDK_Delete || event->keyval == GDK_KP_Delete)
+ {
+ bookmarks_delete_bookmark ();
+ return TRUE;
+ }
+
+ update_bookmark_from_text ();
+
+ return FALSE;
+}
+
+static void
+on_row_activated (GtkTreeView *view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ gpointer user_data)
+{
+ GdkScreen *screen;
+
+ screen = gtk_widget_get_screen (GTK_WIDGET (view));
+ open_selected_bookmark (user_data, screen);
+}
+
+static void
+on_row_deleted (GtkListStore *store,
+ GtkTreePath *path,
+ gpointer user_data)
+{
+ gint *indices, row;
+
+ indices = gtk_tree_path_get_indices (path);
+ row = indices[0];
+
+ g_signal_handler_block (bookmarks, bookmark_list_changed_signal_id);
+ caja_bookmark_list_delete_item_at (bookmarks, row);
+ g_signal_handler_unblock (bookmarks, bookmark_list_changed_signal_id);
+}
+
+static void
+on_selection_changed (GtkTreeSelection *treeselection,
+ gpointer user_data)
+{
+ CajaBookmark *selected;
+ char *name = NULL, *entry_text = NULL;
+ GFile *location;
+
+ g_assert (GTK_IS_ENTRY (name_field));
+ g_assert (GTK_IS_ENTRY (uri_field));
+
+ selected = get_selected_bookmark ();
+
+ if (selected)
+ {
+ name = caja_bookmark_get_name (selected);
+ location = caja_bookmark_get_location (selected);
+ entry_text = g_file_get_parse_name (location);
+
+ g_object_unref (location);
+ }
+
+ /* Set the sensitivity of widgets that require a selection */
+ gtk_widget_set_sensitive (remove_button, selected != NULL);
+ gtk_widget_set_sensitive (jump_button, selected != NULL);
+ gtk_widget_set_sensitive (name_field, selected != NULL);
+ gtk_widget_set_sensitive (uri_field, selected != NULL);
+
+ g_signal_handler_block (name_field, name_field_changed_signal_id);
+ caja_entry_set_text (CAJA_ENTRY (name_field),
+ name ? name : "");
+ g_signal_handler_unblock (name_field, name_field_changed_signal_id);
+
+ g_signal_handler_block (uri_field, uri_field_changed_signal_id);
+ caja_entry_set_text (CAJA_ENTRY (uri_field),
+ entry_text ? entry_text : "");
+ g_signal_handler_unblock (uri_field, uri_field_changed_signal_id);
+
+ text_changed = FALSE;
+ name_text_changed = FALSE;
+
+ g_free (name);
+ g_free (entry_text);
+}
+
+
+static void
+update_bookmark_from_text (void)
+{
+ if (text_changed)
+ {
+ CajaBookmark *bookmark, *bookmark_in_list;
+ char *name;
+ GdkPixbuf *pixbuf;
+ guint selected_row;
+ GtkTreeIter iter;
+ GFile *location;
+
+ g_assert (GTK_IS_ENTRY (name_field));
+ g_assert (GTK_IS_ENTRY (uri_field));
+
+ if (gtk_entry_get_text_length (GTK_ENTRY (uri_field)) == 0)
+ {
+ return;
+ }
+
+ location = g_file_parse_name
+ (gtk_entry_get_text (GTK_ENTRY (uri_field)));
+
+ bookmark = caja_bookmark_new (location, gtk_entry_get_text (GTK_ENTRY (name_field)),
+ name_text_changed, NULL);
+
+ g_object_unref (location);
+
+ selected_row = get_selected_row ();
+
+ /* turn off list updating 'cuz otherwise the list-reordering code runs
+ * after repopulate(), thus reordering the correctly-ordered list.
+ */
+ g_signal_handler_block (bookmarks,
+ bookmark_list_changed_signal_id);
+ caja_bookmark_list_delete_item_at (bookmarks, selected_row);
+ caja_bookmark_list_insert_item (bookmarks, bookmark, selected_row);
+ g_signal_handler_unblock (bookmarks,
+ bookmark_list_changed_signal_id);
+ g_object_unref (bookmark);
+
+ /* We also have to update the bookmark pointer in the list
+ store. */
+ gtk_tree_selection_get_selected (bookmark_selection,
+ NULL, &iter);
+ g_signal_handler_block (bookmark_list_store,
+ row_changed_signal_id);
+
+ bookmark_in_list = caja_bookmark_list_item_at (bookmarks,
+ selected_row);
+
+ name = caja_bookmark_get_name (bookmark_in_list);
+
+ pixbuf = caja_bookmark_get_pixbuf (bookmark_in_list, GTK_ICON_SIZE_MENU);
+
+ gtk_list_store_set (bookmark_list_store, &iter,
+ BOOKMARK_LIST_COLUMN_BOOKMARK, bookmark_in_list,
+ BOOKMARK_LIST_COLUMN_NAME, name,
+ BOOKMARK_LIST_COLUMN_ICON, pixbuf,
+ -1);
+ g_signal_handler_unblock (bookmark_list_store,
+ row_changed_signal_id);
+
+ g_object_unref (pixbuf);
+ g_free (name);
+ }
+}
+
+static gboolean
+on_text_field_focus_out_event (GtkWidget *widget,
+ GdkEventFocus *event,
+ gpointer user_data)
+{
+ g_assert (CAJA_IS_ENTRY (widget));
+
+ update_bookmark_from_text ();
+ return FALSE;
+}
+
+static void
+name_or_uri_field_activate (CajaEntry *entry)
+{
+ g_assert (CAJA_IS_ENTRY (entry));
+
+ update_bookmark_from_text ();
+ caja_entry_select_all_at_idle (entry);
+}
+
+static void
+on_uri_field_changed (GtkEditable *editable,
+ gpointer user_data)
+{
+ /* Remember that user has changed text so we
+ * update real bookmark later.
+ */
+ text_changed = TRUE;
+}
+
+static gboolean
+on_window_delete_event (GtkWidget *widget,
+ GdkEvent *event,
+ gpointer user_data)
+{
+ gtk_widget_hide (widget);
+ return TRUE;
+}
+
+static gboolean
+restore_geometry (gpointer data)
+{
+ g_assert (GTK_IS_WINDOW (data));
+
+ caja_bookmarks_window_restore_geometry (GTK_WIDGET (data));
+
+ /* Don't call this again */
+ return FALSE;
+}
+
+static void
+on_window_hide_event (GtkWidget *widget,
+ gpointer user_data)
+{
+ caja_bookmarks_window_save_geometry (GTK_WINDOW (widget));
+
+ /* Disable undo for entry widgets */
+ caja_undo_unregister (G_OBJECT (name_field));
+ caja_undo_unregister (G_OBJECT (uri_field));
+
+ /* restore_geometry only works after window is hidden */
+ g_idle_add (restore_geometry, widget);
+}
+
+static void
+on_window_destroy_event (GtkWidget *widget,
+ gpointer user_data)
+{
+ g_object_unref (bookmark_list_store);
+ g_object_unref (bookmark_empty_list_store);
+ g_source_remove_by_user_data (widget);
+}
+
+static void
+repopulate (void)
+{
+ CajaBookmark *selected;
+ GtkListStore *store;
+ GtkTreePath *path;
+ GtkTreeRowReference *reference;
+ guint index;
+
+ g_assert (GTK_IS_TREE_VIEW (bookmark_list_widget));
+ g_assert (CAJA_IS_BOOKMARK_LIST (bookmarks));
+
+ store = GTK_LIST_STORE (bookmark_list_store);
+
+ selected = get_selected_bookmark ();
+
+ g_signal_handler_block (bookmark_selection,
+ selection_changed_id);
+ g_signal_handler_block (bookmark_list_store,
+ row_deleted_signal_id);
+ g_signal_handler_block (bookmark_list_widget,
+ row_activated_signal_id);
+ g_signal_handler_block (bookmark_list_widget,
+ key_pressed_signal_id);
+ g_signal_handler_block (bookmark_list_widget,
+ button_pressed_signal_id);
+
+ gtk_list_store_clear (store);
+
+ g_signal_handler_unblock (bookmark_list_widget,
+ row_activated_signal_id);
+ g_signal_handler_unblock (bookmark_list_widget,
+ key_pressed_signal_id);
+ g_signal_handler_unblock (bookmark_list_widget,
+ button_pressed_signal_id);
+ g_signal_handler_unblock (bookmark_list_store,
+ row_deleted_signal_id);
+ g_signal_handler_unblock (bookmark_selection,
+ selection_changed_id);
+
+ /* Fill the list in with the bookmark names. */
+ g_signal_handler_block (store, row_changed_signal_id);
+
+ reference = NULL;
+
+ for (index = 0; index < caja_bookmark_list_length (bookmarks); ++index)
+ {
+ CajaBookmark *bookmark;
+ char *bookmark_name;
+ GdkPixbuf *bookmark_pixbuf;
+ GtkTreeIter iter;
+
+ bookmark = caja_bookmark_list_item_at (bookmarks, index);
+ bookmark_name = caja_bookmark_get_name (bookmark);
+ bookmark_pixbuf = caja_bookmark_get_pixbuf (bookmark, GTK_ICON_SIZE_MENU);
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ BOOKMARK_LIST_COLUMN_ICON, bookmark_pixbuf,
+ BOOKMARK_LIST_COLUMN_NAME, bookmark_name,
+ BOOKMARK_LIST_COLUMN_BOOKMARK, bookmark,
+ BOOKMARK_LIST_COLUMN_STYLE, PANGO_STYLE_NORMAL,
+ -1);
+
+ if (bookmark == selected)
+ {
+ /* save old selection */
+ GtkTreePath *path;
+
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
+ reference = gtk_tree_row_reference_new (GTK_TREE_MODEL (store), path);
+ gtk_tree_path_free (path);
+ }
+
+ g_free (bookmark_name);
+ g_object_unref (bookmark_pixbuf);
+
+ }
+ g_signal_handler_unblock (store, row_changed_signal_id);
+
+ if (reference != NULL)
+ {
+ /* restore old selection */
+
+ /* bookmarks_set_empty() will call the selection change handler,
+ * so we block it here in case of selection change.
+ */
+ g_signal_handler_block (bookmark_selection, selection_changed_id);
+
+ g_assert (index != 0);
+ g_assert (gtk_tree_row_reference_valid (reference));
+
+ path = gtk_tree_row_reference_get_path (reference);
+ gtk_tree_selection_select_path (bookmark_selection, path);
+ gtk_tree_row_reference_free (reference);
+ gtk_tree_path_free (path);
+
+ g_signal_handler_unblock (bookmark_selection, selection_changed_id);
+ }
+
+ bookmarks_set_empty (index == 0);
+}
+
+static int
+handle_close_accelerator (GtkWindow *window,
+ GdkEventKey *event,
+ gpointer user_data)
+{
+ g_assert (GTK_IS_WINDOW (window));
+ g_assert (event != NULL);
+ g_assert (user_data == NULL);
+
+ if (eel_gtk_window_event_is_close_accelerator (window, event))
+ {
+ gtk_widget_hide (GTK_WIDGET (window));
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+set_up_close_accelerator (GtkWidget *window)
+{
+ /* Note that we don't call eel_gtk_window_set_up_close_accelerator
+ * here because we have to handle saving geometry before hiding the
+ * window.
+ */
+ g_signal_connect (window, "key_press_event",
+ G_CALLBACK (handle_close_accelerator), NULL);
+}
diff --git a/src/caja-bookmarks-window.h b/src/caja-bookmarks-window.h
new file mode 100644
index 00000000..4a8a8c6b
--- /dev/null
+++ b/src/caja-bookmarks-window.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 1999, 2000 Eazel, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Authors: John Sullivan <[email protected]>
+ */
+
+/* caja-bookmarks-window.h - interface for bookmark-editing window.
+ */
+
+#ifndef CAJA_BOOKMARKS_WINDOW_H
+#define CAJA_BOOKMARKS_WINDOW_H
+
+#include <gtk/gtk.h>
+#include "caja-bookmark-list.h"
+
+GtkWindow *create_bookmarks_window (CajaBookmarkList *bookmarks,
+ GObject *undo_manager_source);
+void caja_bookmarks_window_save_geometry (GtkWindow *window);
+void edit_bookmarks_dialog_set_signals (GObject *undo_manager_source);
+
+#endif /* CAJA_BOOKMARKS_WINDOW_H */
diff --git a/src/caja-bookmarks-window.ui b/src/caja-bookmarks-window.ui
new file mode 100644
index 00000000..3c5e47e6
--- /dev/null
+++ b/src/caja-bookmarks-window.ui
@@ -0,0 +1,370 @@
+<?xml version="1.0"?>
+<!--*- mode: xml -*-->
+<interface>
+ <object class="GtkDialog" id="bookmarks_dialog">
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Edit Bookmarks</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_CENTER</property>
+ <property name="modal">False</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <object class="GtkButton" id="helpbutton1">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-help</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="bookmark_jump_button">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-jump-to</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="bookmark_delete_button">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-remove</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="button2">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-close</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox1">
+ <property name="border_width">5</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">True</property>
+ <property name="spacing">18</property>
+ <child>
+ <object class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <child>
+ <object class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;_Bookmarks&lt;/b&gt;</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">bookmark_tree_view</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox4">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+ <child>
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="label" translatable="no"> </property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="bookmark_list_window">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+ <child>
+ <object class="GtkTreeView" id="bookmark_tree_view">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">False</property>
+ <property name="rules_hint">False</property>
+ <property name="reorderable">True</property>
+ <property name="enable_search">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment2">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <child>
+ <object class="GtkVBox" id="vbox2">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">18</property>
+ <child>
+ <object class="GtkVBox" id="vbox4">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="bookmark_name_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;_Name&lt;/b&gt;</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox3">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+ <child>
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="label" translatable="no"> </property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="bookmark_name_placeholder">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox3">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="bookmark_location_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;_Location&lt;/b&gt;</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">2</property>
+ <property name="ypad">2</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox2">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+ <child>
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="label" translatable="no"> </property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="bookmark_location_placeholder">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-11">helpbutton1</action-widget>
+ <action-widget response="-10">bookmark_jump_button</action-widget>
+ <action-widget response="-2">bookmark_delete_button</action-widget>
+ <action-widget response="-7">button2</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/src/caja-connect-server-dialog-main.c b/src/caja-connect-server-dialog-main.c
new file mode 100644
index 00000000..a543fc80
--- /dev/null
+++ b/src/caja-connect-server-dialog-main.c
@@ -0,0 +1,235 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-connect-server-main.c - Start the "Connect to Server" dialog.
+ * Caja
+ *
+ * Copyright (C) 2005 Vincent Untz
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors:
+ * Vincent Untz <[email protected]>
+ */
+
+#include <config.h>
+
+#include <glib/gi18n.h>
+
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+
+#include <stdlib.h>
+
+#include <eel/eel-preferences.h>
+#include <eel/eel-stock-dialogs.h>
+
+#include <libcaja-private/caja-icon-names.h>
+
+#include "caja-window.h"
+#include "caja-connect-server-dialog.h"
+
+static int open_dialogs;
+
+static void
+main_dialog_destroyed (GtkWidget *widget,
+ gpointer user_data)
+{
+ /* this only happens when user clicks "cancel"
+ * on the main dialog or when we are all done.
+ */
+ gtk_main_quit ();
+}
+
+static void
+error_dialog_destroyed (GtkWidget *widget,
+ GtkWidget *main_dialog)
+{
+ if (--open_dialogs <= 0)
+ gtk_widget_destroy (main_dialog);
+}
+
+static void
+display_error_dialog (GError *error,
+ const char *uri,
+ GtkWidget *parent)
+{
+ GtkDialog *error_dialog;
+ char *error_message;
+
+ error_message = g_strdup_printf (_("Cannot display location \"%s\""),
+ uri);
+ error_dialog = eel_show_error_dialog (error_message,
+ error->message,
+ NULL);
+
+ open_dialogs++;
+
+ g_signal_connect (error_dialog, "destroy",
+ G_CALLBACK (error_dialog_destroyed), parent);
+
+ gtk_window_set_screen (GTK_WINDOW (error_dialog),
+ gtk_widget_get_screen (parent));
+
+ g_free (error_message);
+}
+
+static void
+show_uri (const char *uri,
+ GtkWidget *widget)
+{
+ GError *error;
+ GdkAppLaunchContext *launch_context;
+
+ launch_context = gdk_app_launch_context_new ();
+ gdk_app_launch_context_set_screen (launch_context,
+ gtk_widget_get_screen (widget));
+
+ error = NULL;
+ g_app_info_launch_default_for_uri (uri,
+ G_APP_LAUNCH_CONTEXT (launch_context),
+ &error);
+
+ g_object_unref (launch_context);
+
+ if (error)
+ {
+ display_error_dialog (error, uri, widget);
+ g_error_free (error);
+ }
+ else
+ {
+ /* everything is OK, destroy the main dialog and quit */
+ gtk_widget_destroy (widget);
+ }
+}
+
+static void
+mount_enclosing_ready_cb (GFile *location,
+ GAsyncResult *res,
+ GtkWidget *widget)
+{
+ char *uri;
+ gboolean success;
+ GError *error = NULL;
+
+ uri = g_file_get_uri (location);
+ success = g_file_mount_enclosing_volume_finish (location,
+ res, &error);
+
+ if (success ||
+ g_error_matches (error, G_IO_ERROR, G_IO_ERROR_ALREADY_MOUNTED))
+ {
+ /* volume is mounted, show it */
+ show_uri (uri, widget);
+ }
+ else
+ {
+ display_error_dialog (error, uri, widget);
+ }
+
+ if (error)
+ {
+ g_error_free (error);
+ }
+
+ g_object_unref (location);
+ g_free (uri);
+}
+
+void
+caja_connect_server_dialog_present_uri (CajaApplication *application,
+ GFile *location,
+ GtkWidget *widget)
+{
+ GMountOperation *op;
+
+ op = gtk_mount_operation_new (GTK_WINDOW (widget));
+ g_mount_operation_set_password_save (op, G_PASSWORD_SAVE_FOR_SESSION);
+ g_file_mount_enclosing_volume (location,
+ 0, op,
+ NULL,
+ (GAsyncReadyCallback) mount_enclosing_ready_cb,
+ widget);
+ g_object_unref (op);
+}
+
+int
+main (int argc, char *argv[])
+{
+ GtkWidget *dialog;
+ GOptionContext *context;
+ const char **args;
+ GFile *location;
+ GError *error;
+ const GOptionEntry options[] =
+ {
+ { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &args, NULL, N_("[URI]") },
+ { NULL }
+ };
+
+ bindtextdomain (GETTEXT_PACKAGE, MATELOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ textdomain (GETTEXT_PACKAGE);
+
+ args = NULL;
+ error = NULL;
+ /* Translators: This is the --help description for the connect to server app,
+ the initial newlines are between the command line arg and the description */
+ context = g_option_context_new (N_("\n\nAdd connect to server mount"));
+ g_option_context_add_main_entries (context, options, GETTEXT_PACKAGE);
+
+ g_option_context_set_translation_domain (context, GETTEXT_PACKAGE);
+ g_option_context_add_group (context, gtk_get_option_group (TRUE));
+
+ if (!g_option_context_parse (context, &argc, &argv, &error))
+ {
+ g_critical ("Failed to parse arguments: %s", error->message);
+ g_error_free (error);
+ g_option_context_free (context);
+ exit (1);
+ }
+
+ g_option_context_free (context);
+
+ eel_preferences_init ("/apps/caja");
+
+ gtk_window_set_default_icon_name (CAJA_ICON_FOLDER);
+
+
+ /* command line arguments, null terminated array */
+ location = NULL;
+ if (args)
+ {
+ location = g_file_new_for_commandline_arg (*args);
+ }
+
+ dialog = caja_connect_server_dialog_new (NULL, location);
+
+ if (location)
+ {
+ g_object_unref (location);
+ }
+
+ open_dialogs = 0;
+ g_signal_connect (dialog, "destroy",
+ G_CALLBACK (main_dialog_destroyed), NULL);
+
+ gtk_widget_show (dialog);
+
+ gtk_main ();
+
+ return 0;
+}
diff --git a/src/caja-connect-server-dialog-nonmain.c b/src/caja-connect-server-dialog-nonmain.c
new file mode 100644
index 00000000..94aa40f6
--- /dev/null
+++ b/src/caja-connect-server-dialog-nonmain.c
@@ -0,0 +1,59 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2005 Red Hat, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <gio/gio.h>
+#include "caja-connect-server-dialog.h"
+#include <libcaja-private/caja-global-preferences.h>
+
+/* This file contains the glue for the calls from the connect to server dialog
+ * to the main caja binary. A different version of this glue is in
+ * caja-connect-server-dialog-main.c for the standalone version.
+ */
+
+void
+caja_connect_server_dialog_present_uri (CajaApplication *application,
+ GFile *location,
+ GtkWidget *widget)
+{
+ CajaWindow *window;
+
+ if (eel_preferences_get_boolean (CAJA_PREFERENCES_ALWAYS_USE_BROWSER))
+ {
+ window = caja_application_create_navigation_window (application,
+ NULL,
+ gtk_widget_get_screen (widget));
+ caja_window_go_to (window, location);
+ }
+ else
+ {
+ caja_application_present_spatial_window (application,
+ NULL,
+ NULL,
+ location,
+ gtk_widget_get_screen (widget));
+ }
+
+ gtk_widget_destroy (widget);
+ g_object_unref (location);
+}
diff --git a/src/caja-connect-server-dialog.c b/src/caja-connect-server-dialog.c
new file mode 100644
index 00000000..90364764
--- /dev/null
+++ b/src/caja-connect-server-dialog.c
@@ -0,0 +1,1075 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2003 Red Hat, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "caja-connect-server-dialog.h"
+
+#include <string.h>
+#include <eel/eel-gtk-macros.h>
+#include <eel/eel-stock-dialogs.h>
+#include <eel/eel-vfs-extensions.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+#include "caja-location-entry.h"
+#include <libcaja-private/caja-global-preferences.h>
+#include <libcaja-private/caja-icon-names.h>
+
+/* TODO:
+ * - dns-sd fill out servers
+ * - pre-fill user?
+ * - name entry + pre-fill
+ * - folder browse function
+ */
+
+/* TODO gio port:
+ * - see FIXME here
+ * - see FIXME in caja-connect-server-dialog-main.c
+ */
+
+struct _CajaConnectServerDialogDetails
+{
+ CajaApplication *application;
+
+ GtkWidget *table;
+
+ GtkWidget *type_combo;
+ GtkWidget *uri_entry;
+ GtkWidget *server_entry;
+ GtkWidget *share_entry;
+ GtkWidget *port_entry;
+ GtkWidget *folder_entry;
+ GtkWidget *domain_entry;
+ GtkWidget *user_entry;
+
+ GtkWidget *bookmark_check;
+ GtkWidget *name_entry;
+};
+
+static void caja_connect_server_dialog_class_init (CajaConnectServerDialogClass *class);
+static void caja_connect_server_dialog_init (CajaConnectServerDialog *dialog);
+
+EEL_CLASS_BOILERPLATE (CajaConnectServerDialog,
+ caja_connect_server_dialog,
+ GTK_TYPE_DIALOG)
+enum
+{
+ RESPONSE_CONNECT
+};
+
+struct MethodInfo
+{
+ const char *scheme;
+ guint flags;
+};
+
+/* A collection of flags for MethodInfo.flags */
+enum
+{
+ DEFAULT_METHOD = 0x00000001,
+
+ /* Widgets to display in setup_for_type */
+ SHOW_SHARE = 0x00000010,
+ SHOW_PORT = 0x00000020,
+ SHOW_USER = 0x00000040,
+ SHOW_DOMAIN = 0x00000080,
+
+ IS_ANONYMOUS = 0x00001000
+};
+
+/* Remember to fill in descriptions below */
+static struct MethodInfo methods[] =
+{
+ /* FIXME: we need to alias ssh to sftp */
+ { "sftp", SHOW_PORT | SHOW_USER },
+ { "ftp", SHOW_PORT | SHOW_USER },
+ { "ftp", DEFAULT_METHOD | IS_ANONYMOUS | SHOW_PORT},
+ { "smb", SHOW_SHARE | SHOW_USER | SHOW_DOMAIN },
+ { "dav", SHOW_PORT | SHOW_USER },
+ /* FIXME: hrm, shouldn't it work? */
+ { "davs", SHOW_PORT | SHOW_USER },
+ { NULL, 0 }, /* Custom URI method */
+};
+
+/* To get around non constant gettext strings */
+static const char*
+get_method_description (struct MethodInfo *meth)
+{
+ if (!meth->scheme)
+ {
+ return _("Custom Location");
+ }
+ else if (strcmp (meth->scheme, "sftp") == 0)
+ {
+ return _("SSH");
+ }
+ else if (strcmp (meth->scheme, "ftp") == 0)
+ {
+ if (meth->flags & IS_ANONYMOUS)
+ {
+ return _("Public FTP");
+ }
+ else
+ {
+ return _("FTP (with login)");
+ }
+ }
+ else if (strcmp (meth->scheme, "smb") == 0)
+ {
+ return _("Windows share");
+ }
+ else if (strcmp (meth->scheme, "dav") == 0)
+ {
+ return _("WebDAV (HTTP)");
+ }
+ else if (strcmp (meth->scheme, "davs") == 0)
+ {
+ return _("Secure WebDAV (HTTPS)");
+
+ /* No descriptive text */
+ }
+ else
+ {
+ return meth->scheme;
+ }
+}
+
+static void
+caja_connect_server_dialog_finalize (GObject *object)
+{
+ CajaConnectServerDialog *dialog;
+
+ dialog = CAJA_CONNECT_SERVER_DIALOG (object);
+
+ g_object_unref (dialog->details->uri_entry);
+ g_object_unref (dialog->details->server_entry);
+ g_object_unref (dialog->details->share_entry);
+ g_object_unref (dialog->details->port_entry);
+ g_object_unref (dialog->details->folder_entry);
+ g_object_unref (dialog->details->domain_entry);
+ g_object_unref (dialog->details->user_entry);
+ g_object_unref (dialog->details->bookmark_check);
+ g_object_unref (dialog->details->name_entry);
+
+ g_free (dialog->details);
+
+ EEL_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
+}
+
+static void
+caja_connect_server_dialog_destroy (GtkObject *object)
+{
+ CajaConnectServerDialog *dialog;
+
+ dialog = CAJA_CONNECT_SERVER_DIALOG (object);
+
+ EEL_CALL_PARENT (GTK_OBJECT_CLASS, destroy, (object));
+}
+
+static void
+connect_to_server (CajaConnectServerDialog *dialog)
+{
+ struct MethodInfo *meth;
+ char *uri;
+ GFile *location;
+ int index;
+ GtkTreeIter iter;
+
+ /* Get our method info */
+ gtk_combo_box_get_active_iter (GTK_COMBO_BOX (dialog->details->type_combo), &iter);
+ gtk_tree_model_get (gtk_combo_box_get_model (GTK_COMBO_BOX (dialog->details->type_combo)),
+ &iter, 0, &index, -1);
+ g_assert (index < G_N_ELEMENTS (methods) && index >= 0);
+ meth = &(methods[index]);
+
+ uri = gtk_editable_get_chars (GTK_EDITABLE (dialog->details->server_entry), 0, -1);
+ if (strlen (uri) == 0)
+ {
+ eel_show_error_dialog (_("Cannot Connect to Server. You must enter a name for the server."),
+ _("Please enter a name and try again."),
+ GTK_WINDOW (dialog));
+ g_free (uri);
+ return;
+ }
+
+ if (meth->scheme == NULL)
+ {
+ uri = gtk_editable_get_chars (GTK_EDITABLE (dialog->details->uri_entry), 0, -1);
+ /* FIXME: we should validate it in some way? */
+ }
+ else
+ {
+ char *user, *port, *initial_path, *server, *folder, *domain;
+ char *t, *join;
+ gboolean free_initial_path, free_user, free_domain, free_port;
+
+ server = uri;
+ uri = NULL;
+
+ user = "";
+ port = "";
+ initial_path = "";
+ domain = "";
+ free_initial_path = FALSE;
+ free_user = FALSE;
+ free_domain = FALSE;
+ free_port = FALSE;
+
+ /* FTP special case */
+ if (meth->flags & IS_ANONYMOUS)
+ {
+ user = "anonymous";
+
+ /* SMB special case */
+ }
+ else if (strcmp (meth->scheme, "smb") == 0)
+ {
+ t = gtk_editable_get_chars (GTK_EDITABLE (dialog->details->share_entry), 0, -1);
+ initial_path = g_strconcat ("/", t, NULL);
+ free_initial_path = TRUE;
+ g_free (t);
+ }
+
+ if (gtk_widget_get_parent (dialog->details->port_entry) != NULL)
+ {
+ free_port = TRUE;
+ port = gtk_editable_get_chars (GTK_EDITABLE (dialog->details->port_entry), 0, -1);
+ }
+ folder = gtk_editable_get_chars (GTK_EDITABLE (dialog->details->folder_entry), 0, -1);
+ if (gtk_widget_get_parent (dialog->details->user_entry) != NULL)
+ {
+ free_user = TRUE;
+
+ t = gtk_editable_get_chars (GTK_EDITABLE (dialog->details->user_entry), 0, -1);
+
+ user = g_uri_escape_string (t, G_URI_RESERVED_CHARS_ALLOWED_IN_USERINFO, FALSE);
+
+ g_free (t);
+ }
+ if (gtk_widget_get_parent (dialog->details->domain_entry) != NULL)
+ {
+ free_domain = TRUE;
+
+ domain = gtk_editable_get_chars (GTK_EDITABLE (dialog->details->domain_entry), 0, -1);
+
+ if (strlen (domain) != 0)
+ {
+ t = user;
+
+ user = g_strconcat (domain , ";" , t, NULL);
+
+ if (free_user)
+ {
+ g_free (t);
+ }
+
+ free_user = TRUE;
+ }
+ }
+
+ if (folder[0] != 0 &&
+ folder[0] != '/')
+ {
+ join = "/";
+ }
+ else
+ {
+ join = "";
+ }
+
+ t = folder;
+ folder = g_strconcat (initial_path, join, t, NULL);
+ g_free (t);
+
+ t = folder;
+ folder = g_uri_escape_string (t, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, FALSE);
+ g_free (t);
+
+ uri = g_strdup_printf ("%s://%s%s%s%s%s%s",
+ meth->scheme,
+ user, (user[0] != 0) ? "@" : "",
+ server,
+ (port[0] != 0) ? ":" : "", port,
+ folder);
+
+ if (free_initial_path)
+ {
+ g_free (initial_path);
+ }
+ g_free (server);
+ if (free_port)
+ {
+ g_free (port);
+ }
+ g_free (folder);
+ if (free_user)
+ {
+ g_free (user);
+ }
+ if (free_domain)
+ {
+ g_free (domain);
+ }
+ }
+
+ gtk_widget_hide (GTK_WIDGET (dialog));
+
+ location = g_file_new_for_uri (uri);
+ g_free (uri);
+
+ /* FIXME: sensitivity */
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->details->bookmark_check)))
+ {
+ char *name;
+ CajaBookmark *bookmark;
+ CajaBookmarkList *list;
+ GIcon *icon;
+
+ name = gtk_editable_get_chars (GTK_EDITABLE (dialog->details->name_entry), 0, -1);
+ icon = g_themed_icon_new (CAJA_ICON_FOLDER_REMOTE);
+ bookmark = caja_bookmark_new (location, strlen (name) ? name : NULL,
+ TRUE, icon);
+ list = caja_bookmark_list_new ();
+ if (!caja_bookmark_list_contains (list, bookmark))
+ {
+ caja_bookmark_list_append (list, bookmark);
+ }
+
+ g_object_unref (bookmark);
+ g_object_unref (list);
+ g_object_unref (icon);
+ g_free (name);
+ }
+
+ caja_connect_server_dialog_present_uri (dialog->details->application,
+ location,
+ GTK_WIDGET (dialog));
+}
+
+static void
+response_callback (CajaConnectServerDialog *dialog,
+ int response_id,
+ gpointer data)
+{
+ GError *error;
+
+ switch (response_id)
+ {
+ case RESPONSE_CONNECT:
+ connect_to_server (dialog);
+ break;
+ 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-server-connect",
+ 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
+caja_connect_server_dialog_class_init (CajaConnectServerDialogClass *class)
+{
+ GObjectClass *gobject_class;
+ GtkObjectClass *object_class;
+
+ gobject_class = G_OBJECT_CLASS (class);
+ gobject_class->finalize = caja_connect_server_dialog_finalize;
+
+ object_class = GTK_OBJECT_CLASS (class);
+ object_class->destroy = caja_connect_server_dialog_destroy;
+}
+
+static void
+setup_for_type (CajaConnectServerDialog *dialog)
+{
+ struct MethodInfo *meth;
+ int index, i;
+ GtkWidget *label, *table;
+ GtkTreeIter iter;
+
+ /* Get our method info */
+ gtk_combo_box_get_active_iter (GTK_COMBO_BOX (dialog->details->type_combo), &iter);
+ gtk_tree_model_get (gtk_combo_box_get_model (GTK_COMBO_BOX (dialog->details->type_combo)),
+ &iter, 0, &index, -1);
+ g_assert (index < G_N_ELEMENTS (methods) && index >= 0);
+ meth = &(methods[index]);
+
+ if (gtk_widget_get_parent (dialog->details->uri_entry) != NULL)
+ {
+ gtk_container_remove (GTK_CONTAINER (dialog->details->table),
+ dialog->details->uri_entry);
+ }
+ if (gtk_widget_get_parent (dialog->details->server_entry) != NULL)
+ {
+ gtk_container_remove (GTK_CONTAINER (dialog->details->table),
+ dialog->details->server_entry);
+ }
+ if (gtk_widget_get_parent (dialog->details->share_entry) != NULL)
+ {
+ gtk_container_remove (GTK_CONTAINER (dialog->details->table),
+ dialog->details->share_entry);
+ }
+ if (gtk_widget_get_parent (dialog->details->port_entry) != NULL)
+ {
+ gtk_container_remove (GTK_CONTAINER (dialog->details->table),
+ dialog->details->port_entry);
+ }
+ if (gtk_widget_get_parent (dialog->details->folder_entry) != NULL)
+ {
+ gtk_container_remove (GTK_CONTAINER (dialog->details->table),
+ dialog->details->folder_entry);
+ }
+ if (gtk_widget_get_parent (dialog->details->user_entry) != NULL)
+ {
+ gtk_container_remove (GTK_CONTAINER (dialog->details->table),
+ dialog->details->user_entry);
+ }
+ if (gtk_widget_get_parent (dialog->details->domain_entry) != NULL)
+ {
+ gtk_container_remove (GTK_CONTAINER (dialog->details->table),
+ dialog->details->domain_entry);
+ }
+ if (gtk_widget_get_parent (dialog->details->bookmark_check) != NULL)
+ {
+ gtk_container_remove (GTK_CONTAINER (dialog->details->table),
+ dialog->details->bookmark_check);
+ }
+ if (gtk_widget_get_parent (dialog->details->name_entry) != NULL)
+ {
+ gtk_container_remove (GTK_CONTAINER (dialog->details->table),
+ dialog->details->name_entry);
+ }
+ /* Destroy all labels */
+ gtk_container_foreach (GTK_CONTAINER (dialog->details->table),
+ (GtkCallback) gtk_widget_destroy, NULL);
+
+
+ i = 1;
+ table = dialog->details->table;
+
+ if (meth->scheme == NULL)
+ {
+ label = gtk_label_new_with_mnemonic (_("_Location (URI):"));
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_widget_show (label);
+ gtk_table_attach (GTK_TABLE (table), label,
+ 0, 1,
+ i, i+1,
+ GTK_FILL, GTK_FILL,
+ 0, 0);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), dialog->details->uri_entry);
+ gtk_widget_show (dialog->details->uri_entry);
+ gtk_table_attach (GTK_TABLE (table), dialog->details->uri_entry,
+ 1, 2,
+ i, i+1,
+ GTK_FILL | GTK_EXPAND, GTK_FILL,
+ 0, 0);
+
+ i++;
+
+ goto connection_name;
+ }
+
+ label = gtk_label_new_with_mnemonic (_("_Server:"));
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_widget_show (label);
+ gtk_table_attach (GTK_TABLE (table), label,
+ 0, 1,
+ i, i+1,
+ GTK_FILL, GTK_FILL,
+ 0, 0);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), dialog->details->server_entry);
+ gtk_widget_show (dialog->details->server_entry);
+ gtk_table_attach (GTK_TABLE (table), dialog->details->server_entry,
+ 1, 2,
+ i, i+1,
+ GTK_FILL | GTK_EXPAND, GTK_FILL,
+ 0, 0);
+
+ i++;
+
+ label = gtk_label_new (_("Optional information:"));
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_widget_show (label);
+ gtk_table_attach (GTK_TABLE (table), label,
+ 0, 2,
+ i, i+1,
+ GTK_FILL, GTK_FILL,
+ 0, 0);
+
+ i++;
+
+ if (meth->flags & SHOW_SHARE)
+ {
+ label = gtk_label_new_with_mnemonic (_("_Share:"));
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_widget_show (label);
+ gtk_table_attach (GTK_TABLE (table), label,
+ 0, 1,
+ i, i+1,
+ GTK_FILL, GTK_FILL,
+ 0, 0);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), dialog->details->share_entry);
+ gtk_widget_show (dialog->details->share_entry);
+ gtk_table_attach (GTK_TABLE (table), dialog->details->share_entry,
+ 1, 2,
+ i, i+1,
+ GTK_FILL | GTK_EXPAND, GTK_FILL,
+ 0, 0);
+
+ i++;
+ }
+
+ if (meth->flags & SHOW_PORT)
+ {
+ label = gtk_label_new_with_mnemonic (_("_Port:"));
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_widget_show (label);
+ gtk_table_attach (GTK_TABLE (table), label,
+ 0, 1,
+ i, i+1,
+ GTK_FILL, GTK_FILL,
+ 0, 0);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), dialog->details->port_entry);
+ gtk_widget_show (dialog->details->port_entry);
+ gtk_table_attach (GTK_TABLE (table), dialog->details->port_entry,
+ 1, 2,
+ i, i+1,
+ GTK_FILL | GTK_EXPAND, GTK_FILL,
+ 0, 0);
+
+ i++;
+ }
+
+ label = gtk_label_new_with_mnemonic (_("_Folder:"));
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_widget_show (label);
+ gtk_table_attach (GTK_TABLE (table), label,
+ 0, 1,
+ i, i+1,
+ GTK_FILL, GTK_FILL,
+ 0, 0);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), dialog->details->folder_entry);
+ gtk_widget_show (dialog->details->folder_entry);
+ gtk_table_attach (GTK_TABLE (table), dialog->details->folder_entry,
+ 1, 2,
+ i, i+1,
+ GTK_FILL | GTK_EXPAND, GTK_FILL,
+ 0, 0);
+
+ i++;
+
+ if (meth->flags & SHOW_USER)
+ {
+ label = gtk_label_new_with_mnemonic (_("_User Name:"));
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_widget_show (label);
+ gtk_table_attach (GTK_TABLE (table), label,
+ 0, 1,
+ i, i+1,
+ GTK_FILL, GTK_FILL,
+ 0, 0);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), dialog->details->user_entry);
+ gtk_widget_show (dialog->details->user_entry);
+ gtk_table_attach (GTK_TABLE (table), dialog->details->user_entry,
+ 1, 2,
+ i, i+1,
+ GTK_FILL | GTK_EXPAND, GTK_FILL,
+ 0, 0);
+
+ i++;
+ }
+
+ if (meth->flags & SHOW_DOMAIN)
+ {
+ label = gtk_label_new_with_mnemonic (_("_Domain Name:"));
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_widget_show (label);
+ gtk_table_attach (GTK_TABLE (table), label,
+ 0, 1,
+ i, i+1,
+ GTK_FILL, GTK_FILL,
+ 0, 0);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), dialog->details->domain_entry);
+ gtk_widget_show (dialog->details->domain_entry);
+ gtk_table_attach (GTK_TABLE (table), dialog->details->domain_entry,
+ 1, 2,
+ i, i+1,
+ GTK_FILL | GTK_EXPAND, GTK_FILL,
+ 0, 0);
+
+ i++;
+ }
+
+
+
+connection_name:
+
+ gtk_widget_show (dialog->details->bookmark_check);
+ gtk_table_attach (GTK_TABLE (table), dialog->details->bookmark_check,
+ 0, 1,
+ i, i+1,
+ GTK_FILL, GTK_FILL,
+ 0, 0);
+ i++;
+
+ label = gtk_label_new_with_mnemonic (_("Bookmark _name:"));
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_widget_show (label);
+ gtk_table_attach (GTK_TABLE (table), label,
+ 0, 1,
+ i, i+1,
+ GTK_FILL, GTK_FILL,
+ 0, 0);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), dialog->details->name_entry);
+ gtk_widget_show (dialog->details->name_entry);
+ gtk_table_attach (GTK_TABLE (table), dialog->details->name_entry,
+ 1, 2,
+ i, i+1,
+ GTK_FILL | GTK_EXPAND, GTK_FILL,
+ 0, 0);
+
+ i++;
+
+}
+
+static void
+display_server_location (CajaConnectServerDialog *dialog, GFile *location)
+{
+#if 0 /*FIXME */
+ struct MethodInfo *meth = NULL;
+ char *scheme;
+ int i, index = 0;
+ char *folder;
+ const char *t;
+
+ /* Find an appropriate method */
+ scheme = g_file_get_uri_scheme (location);
+ g_return_if_fail (scheme != NULL);
+
+ for (i = 0; i < G_N_ELEMENTS (methods); i++)
+ {
+
+ /* The default is 'Custom URI' */
+ if (methods[i].scheme == NULL)
+ {
+ meth = &(methods[i]);
+ index = i;
+
+ }
+ else if (strcmp (methods[i].scheme, scheme) == 0)
+ {
+
+ /* FTP Special case: If no user keep searching for public ftp */
+ if (strcmp (scheme, "ftp") == 0)
+ {
+ t = mate_vfs_uri_get_user_name (uri);
+ if ((!t || !t[0] || strcmp (t, "anonymous") == 0) &&
+ (!(methods[i].flags & IS_ANONYMOUS)))
+ {
+ continue;
+ }
+ }
+
+ meth = &(methods[i]);
+ index = i;
+ break;
+ }
+ }
+
+ g_free (scheme);
+ g_assert (meth);
+
+ gtk_combo_box_set_active (GTK_COMBO_BOX (dialog->details->type_combo), index);
+ setup_for_type (dialog);
+
+ /* Custom URI */
+ if (meth->scheme == NULL)
+ {
+ gchar *uri;
+
+ /* FIXME: with mate-vfs, we had MATE_VFS_URI_HIDE_PASSWORD |
+ * MATE_VFS_URI_HIDE_FRAGMENT_IDENTIFIER */
+ uri = g_file_get_uri (location)
+ gtk_entry_set_text (GTK_ENTRY (dialog->details->uri_entry), uri);
+ g_free (uri);
+
+ }
+ else
+ {
+
+ folder = g_file_get_path (location);
+ if (!folder)
+ {
+ folder = "";
+ }
+ else if (folder[0] == '/')
+ {
+ folder++;
+ }
+
+ /* Server */
+ t = mate_vfs_uri_get_host_name (uri);
+ gtk_entry_set_text (GTK_ENTRY (dialog->details->server_entry),
+ t ? t : "");
+
+ /* Share */
+ if (meth->flags & SHOW_SHARE)
+ {
+ t = strchr (folder, '/');
+ if (t)
+ {
+ char *share = g_strndup (folder, t - folder);
+ gtk_entry_set_text (GTK_ENTRY (dialog->details->share_entry), share);
+ g_free (share);
+ folder = t + 1;
+ }
+
+ }
+
+ /* Port */
+ if (meth->flags & SHOW_PORT)
+ {
+ guint port = mate_vfs_uri_get_host_port (uri);
+ if (port != 0)
+ {
+ char sport[32];
+ g_snprintf (sport, sizeof (sport), "%d", port);
+ gtk_entry_set_text (GTK_ENTRY (dialog->details->port_entry), sport);
+ }
+ }
+
+ /* Folder */
+ gtk_entry_set_text (GTK_ENTRY (dialog->details->folder_entry), folder);
+ g_free (folder);
+
+ /* User */
+ if (meth->flags & SHOW_USER)
+ {
+ const char *user = mate_vfs_uri_get_user_name (uri);
+ if (user)
+ {
+ t = strchr (user, ';');
+ if (t)
+ {
+ user = t + 1;
+ }
+ gtk_entry_set_text (GTK_ENTRY (dialog->details->user_entry), user);
+ }
+ }
+
+ /* Domain */
+ if (meth->flags & SHOW_DOMAIN)
+ {
+ const char *user = mate_vfs_uri_get_user_name (uri);
+ if (user)
+ {
+ t = strchr (user, ';');
+ if (t)
+ {
+ char *domain = g_strndup (user, t - user);
+ gtk_entry_set_text (GTK_ENTRY (dialog->details->domain_entry), domain);
+ g_free (domain);
+ }
+ }
+ }
+ }
+#endif
+}
+
+static void
+combo_changed_callback (GtkComboBox *combo_box,
+ CajaConnectServerDialog *dialog)
+{
+ setup_for_type (dialog);
+}
+
+static void
+port_insert_text (GtkEditable *editable,
+ const gchar *new_text,
+ gint new_text_length,
+ gint *position)
+{
+ int pos;
+
+ if (new_text_length < 0)
+ {
+ new_text_length = strlen (new_text);
+ }
+
+ /* Only allow digits to be inserted as port number */
+ for (pos = 0; pos < new_text_length; pos++)
+ {
+ if (!g_ascii_isdigit (new_text[pos]))
+ {
+ GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (editable));
+ if (toplevel != NULL)
+ {
+ gdk_window_beep (gtk_widget_get_window (toplevel));
+ }
+ g_signal_stop_emission_by_name (editable, "insert_text");
+ return;
+ }
+ }
+}
+
+static void
+bookmark_checkmark_toggled (GtkToggleButton *toggle, CajaConnectServerDialog *dialog)
+{
+ gtk_widget_set_sensitive (GTK_WIDGET(dialog->details->name_entry),
+ gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON (toggle)));
+}
+
+static void
+caja_connect_server_dialog_init (CajaConnectServerDialog *dialog)
+{
+ GtkWidget *label;
+ GtkWidget *table;
+ GtkWidget *combo;
+ GtkWidget *hbox;
+ GtkWidget *vbox;
+ GtkListStore *store;
+ GtkCellRenderer *renderer;
+ int i;
+
+ dialog->details = g_new0 (CajaConnectServerDialogDetails, 1);
+
+ gtk_window_set_title (GTK_WINDOW (dialog), _("Connect to Server"));
+ 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);
+ gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+
+ vbox = gtk_vbox_new (FALSE, 6);
+ gtk_container_set_border_width (GTK_CONTAINER (vbox), 5);
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
+ vbox, FALSE, TRUE, 0);
+ gtk_widget_show (vbox);
+
+ hbox = gtk_hbox_new (FALSE, 6);
+ gtk_box_pack_start (GTK_BOX (vbox),
+ hbox, FALSE, TRUE, 0);
+ gtk_widget_show (hbox);
+
+ label = gtk_label_new_with_mnemonic (_("Service _type:"));
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_widget_show (label);
+ gtk_box_pack_start (GTK_BOX (hbox),
+ label, FALSE, FALSE, 0);
+
+ dialog->details->type_combo = combo = gtk_combo_box_new ();
+
+ /* each row contains: method index, textual description */
+ store = gtk_list_store_new (2, G_TYPE_INT, G_TYPE_STRING);
+ gtk_combo_box_set_model (GTK_COMBO_BOX (combo), GTK_TREE_MODEL (store));
+ g_object_unref (G_OBJECT (store));
+
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), renderer, TRUE);
+ gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (combo), renderer, "text", 1);
+
+ for (i = 0; i < G_N_ELEMENTS (methods); i++)
+ {
+ GtkTreeIter iter;
+ const gchar * const *supported;
+ int j;
+
+ /* skip methods that don't have corresponding MateVFSMethods */
+ supported = g_vfs_get_supported_uri_schemes (g_vfs_get_default ());
+
+ if (methods[i].scheme != NULL)
+ {
+ gboolean found;
+
+ found = FALSE;
+ for (j = 0; supported[j] != NULL; j++)
+ {
+ if (strcmp (methods[i].scheme, supported[j]) == 0)
+ {
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (!found)
+ {
+ continue;
+ }
+ }
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ 0, i,
+ 1, get_method_description (&(methods[i])),
+ -1);
+
+
+ if (methods[i].flags & DEFAULT_METHOD)
+ {
+ gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo), &iter);
+ }
+ }
+
+ if (gtk_combo_box_get_active (GTK_COMBO_BOX (combo)) < 0)
+ {
+ /* default method not available, use any other */
+ gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 0);
+ }
+
+ gtk_widget_show (combo);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo);
+ gtk_box_pack_start (GTK_BOX (hbox),
+ combo, TRUE, TRUE, 0);
+ g_signal_connect (combo, "changed",
+ G_CALLBACK (combo_changed_callback),
+ dialog);
+
+
+ hbox = gtk_hbox_new (FALSE, 6);
+ gtk_box_pack_start (GTK_BOX (vbox),
+ hbox, FALSE, TRUE, 0);
+ gtk_widget_show (hbox);
+
+ label = gtk_label_new_with_mnemonic (" ");
+ gtk_widget_show (label);
+ gtk_box_pack_start (GTK_BOX (hbox),
+ label, FALSE, FALSE, 0);
+
+
+ dialog->details->table = table = gtk_table_new (5, 2, FALSE);
+ gtk_table_set_row_spacings (GTK_TABLE (table), 6);
+ gtk_table_set_col_spacings (GTK_TABLE (table), 12);
+ gtk_widget_show (table);
+ gtk_box_pack_start (GTK_BOX (hbox),
+ table, TRUE, TRUE, 0);
+
+ dialog->details->uri_entry = caja_location_entry_new ();
+ /* hide the clean icon, as it doesn't make sense here */
+ g_object_set (dialog->details->uri_entry, "secondary-icon-name", NULL, NULL);
+ dialog->details->server_entry = gtk_entry_new ();
+ dialog->details->share_entry = gtk_entry_new ();
+ dialog->details->port_entry = gtk_entry_new ();
+ g_signal_connect (dialog->details->port_entry, "insert_text", G_CALLBACK (port_insert_text),
+ NULL);
+ dialog->details->folder_entry = gtk_entry_new ();
+ dialog->details->domain_entry = gtk_entry_new ();
+ dialog->details->user_entry = gtk_entry_new ();
+ dialog->details->bookmark_check = gtk_check_button_new_with_mnemonic (_("Add _bookmark"));
+ dialog->details->name_entry = gtk_entry_new ();
+
+ g_signal_connect (dialog->details->bookmark_check, "toggled",
+ G_CALLBACK (bookmark_checkmark_toggled), dialog);
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->details->bookmark_check), FALSE);
+ gtk_widget_set_sensitive (GTK_WIDGET(dialog->details->name_entry), FALSE);
+
+ gtk_entry_set_activates_default (GTK_ENTRY (dialog->details->uri_entry), TRUE);
+ gtk_entry_set_activates_default (GTK_ENTRY (dialog->details->server_entry), TRUE);
+ gtk_entry_set_activates_default (GTK_ENTRY (dialog->details->share_entry), TRUE);
+ gtk_entry_set_activates_default (GTK_ENTRY (dialog->details->port_entry), TRUE);
+ gtk_entry_set_activates_default (GTK_ENTRY (dialog->details->folder_entry), TRUE);
+ gtk_entry_set_activates_default (GTK_ENTRY (dialog->details->domain_entry), TRUE);
+ gtk_entry_set_activates_default (GTK_ENTRY (dialog->details->user_entry), TRUE);
+ gtk_entry_set_activates_default (GTK_ENTRY (dialog->details->name_entry), TRUE);
+
+ /* We need an extra ref so we can remove them from the table */
+ g_object_ref (dialog->details->uri_entry);
+ g_object_ref (dialog->details->server_entry);
+ g_object_ref (dialog->details->share_entry);
+ g_object_ref (dialog->details->port_entry);
+ g_object_ref (dialog->details->folder_entry);
+ g_object_ref (dialog->details->domain_entry);
+ g_object_ref (dialog->details->user_entry);
+ g_object_ref (dialog->details->bookmark_check);
+ g_object_ref (dialog->details->name_entry);
+
+ setup_for_type (dialog);
+
+ gtk_dialog_add_button (GTK_DIALOG (dialog),
+ GTK_STOCK_HELP,
+ GTK_RESPONSE_HELP);
+ gtk_dialog_add_button (GTK_DIALOG (dialog),
+ GTK_STOCK_CANCEL,
+ GTK_RESPONSE_CANCEL);
+ gtk_dialog_add_button (GTK_DIALOG (dialog),
+ _("C_onnect"),
+ RESPONSE_CONNECT);
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog),
+ RESPONSE_CONNECT);
+
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (response_callback),
+ dialog);
+
+
+}
+
+GtkWidget *
+caja_connect_server_dialog_new (CajaWindow *window, GFile *location)
+{
+ CajaConnectServerDialog *conndlg;
+ GtkWidget *dialog;
+
+ dialog = gtk_widget_new (CAJA_TYPE_CONNECT_SERVER_DIALOG, NULL);
+ conndlg = CAJA_CONNECT_SERVER_DIALOG (dialog);
+
+ if (window)
+ {
+ gtk_window_set_screen (GTK_WINDOW (dialog),
+ gtk_window_get_screen (GTK_WINDOW (window)));
+ conndlg->details->application = window->application;
+ }
+
+ if (location)
+ {
+ /* If it's a remote URI, then load as the default */
+ if (!g_file_is_native (location))
+ {
+ display_server_location (conndlg, location);
+ }
+ }
+
+ return dialog;
+}
diff --git a/src/caja-connect-server-dialog.h b/src/caja-connect-server-dialog.h
new file mode 100644
index 00000000..6313e36a
--- /dev/null
+++ b/src/caja-connect-server-dialog.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2003 Red Hat, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef CAJA_CONNECT_SERVER_DIALOG_H
+#define CAJA_CONNECT_SERVER_DIALOG_H
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+#include "caja-window.h"
+
+#define CAJA_TYPE_CONNECT_SERVER_DIALOG (caja_connect_server_dialog_get_type ())
+#define CAJA_CONNECT_SERVER_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_CONNECT_SERVER_DIALOG, CajaConnectServerDialog))
+#define CAJA_CONNECT_SERVER_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_CONNECT_SERVER_DIALOG, CajaConnectServerDialogClass))
+#define CAJA_IS_CONNECT_SERVER_DIALOG(obj) (G_TYPE_INSTANCE_CHECK_TYPE ((obj), CAJA_TYPE_CONNECT_SERVER_DIALOG)
+
+typedef struct _CajaConnectServerDialog CajaConnectServerDialog;
+typedef struct _CajaConnectServerDialogClass CajaConnectServerDialogClass;
+typedef struct _CajaConnectServerDialogDetails CajaConnectServerDialogDetails;
+
+struct _CajaConnectServerDialog
+{
+ GtkDialog parent;
+ CajaConnectServerDialogDetails *details;
+};
+
+struct _CajaConnectServerDialogClass
+{
+ GtkDialogClass parent_class;
+};
+
+GType caja_connect_server_dialog_get_type (void);
+GtkWidget* caja_connect_server_dialog_new (CajaWindow *window,
+ GFile *location);
+
+/* Private internal calls */
+
+void caja_connect_server_dialog_present_uri (CajaApplication *application,
+ GFile *location,
+ GtkWidget *widget);
+
+#endif /* CAJA_CONNECT_SERVER_DIALOG_H */
diff --git a/src/caja-convert-metadata.c b/src/caja-convert-metadata.c
new file mode 100644
index 00000000..7571173d
--- /dev/null
+++ b/src/caja-convert-metadata.c
@@ -0,0 +1,492 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-convert-metadata.c - Convert old metadata format to gvfs metadata.
+ *
+ * Copyright (C) 2009 Alexander Larsson
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors:
+ * Alexander Larsson <[email protected]>
+ */
+
+#include <config.h>
+
+#include <glib.h>
+
+#include <gio/gio.h>
+#include <glib/gi18n.h>
+#include <string.h>
+#include <libxml/tree.h>
+#include <mateconf/mateconf-client.h>
+
+#include <libcaja-private/caja-metadata.h>
+
+#define CAJA_DESKTOP_METADATA_MATECONF_PATH "/apps/caja/desktop-metadata"
+
+static gboolean quiet = FALSE;
+static char *
+get_metadata_mateconf_path (const char *name,
+ const char *key)
+{
+ char *res, *escaped_name;
+
+ escaped_name = mateconf_escape_key (name, -1);
+ res = g_build_filename (CAJA_DESKTOP_METADATA_MATECONF_PATH, escaped_name, key + strlen ("metadata::"), NULL);
+ g_free (escaped_name);
+
+ return res;
+}
+
+static void
+desktop_set_metadata_string (GFile *file,
+ const char *key,
+ const char *string)
+{
+ MateConfClient *client;
+ char *mateconf_key;
+ GFile *parent;
+ char *name;
+
+ parent = g_file_get_parent (file);
+ if (parent == NULL)
+ {
+ name = g_strdup ("directory");
+ }
+ else
+ {
+ g_object_unref (parent);
+ name = g_file_get_basename (file);
+ }
+
+ client = mateconf_client_get_default ();
+ mateconf_key = get_metadata_mateconf_path (name, key);
+
+ mateconf_client_set_string (client, mateconf_key, string, NULL);
+
+ g_free (mateconf_key);
+ g_free (name);
+ g_object_unref (client);
+}
+
+static void
+desktop_set_metadata_stringv (GFile *file,
+ const char *key,
+ char **stringv)
+{
+ MateConfClient *client;
+ char *mateconf_key;
+ GSList *list;
+ int i;
+ GFile *parent;
+ char *name;
+
+ parent = g_file_get_parent (file);
+ if (parent == NULL)
+ {
+ name = g_strdup ("directory");
+ }
+ else
+ {
+ g_object_unref (parent);
+ name = g_file_get_basename (file);
+ }
+
+ client = mateconf_client_get_default ();
+ mateconf_key = get_metadata_mateconf_path (name, key);
+
+ list = NULL;
+ for (i = 0; stringv[i] != NULL; i++)
+ {
+ list = g_slist_prepend (list, stringv[i]);
+ }
+ list = g_slist_reverse (list);
+
+ mateconf_client_set_list (client, mateconf_key,
+ MATECONF_VALUE_STRING,
+ list, NULL);
+
+ g_slist_free (list);
+ g_free (mateconf_key);
+ g_free (name);
+ g_object_unref (client);
+}
+
+static xmlNodePtr
+xml_get_children (xmlNodePtr parent)
+{
+ if (parent == NULL)
+ {
+ return NULL;
+ }
+ return parent->children;
+}
+
+static xmlNodePtr
+xml_get_root_children (xmlDocPtr document)
+{
+ return xml_get_children (xmlDocGetRootElement (document));
+}
+
+
+static char *
+get_uri_from_caja_metafile_name (const char *filename)
+{
+ GString *s;
+ char c;
+ char *base_name, *p;
+ int len;
+
+ base_name = g_path_get_basename (filename);
+ len = strlen (base_name);
+ if (len <= 4 ||
+ strcmp (base_name + len - 4, ".xml") != 0)
+ {
+ g_free (base_name);
+ return NULL;
+ }
+ base_name[len-4] = 0;
+
+ s = g_string_new (NULL);
+
+ p = base_name;
+ while (*p)
+ {
+ c = *p++;
+ if (c == '%')
+ {
+ c = g_ascii_xdigit_value (p[0]) << 4 |
+ g_ascii_xdigit_value (p[1]);
+ p += 2;
+ }
+ g_string_append_c (s, c);
+ }
+ g_free (base_name);
+
+ return g_string_free (s, FALSE);
+}
+
+static struct
+{
+ const char *old_key;
+ const char *new_key;
+} metadata_keys[] =
+{
+ {"default_component", "metadata::" CAJA_METADATA_KEY_DEFAULT_VIEW},
+ {"background_color", "metadata::" CAJA_METADATA_KEY_LOCATION_BACKGROUND_COLOR},
+ {"background_tile_image", "metadata::" CAJA_METADATA_KEY_LOCATION_BACKGROUND_IMAGE},
+ {"icon_view_zoom_level", "metadata::" CAJA_METADATA_KEY_ICON_VIEW_ZOOM_LEVEL},
+ {"icon_view_auto_layout", "metadata::" CAJA_METADATA_KEY_ICON_VIEW_AUTO_LAYOUT},
+ {"icon_view_tighter_layout", "metadata::" CAJA_METADATA_KEY_ICON_VIEW_TIGHTER_LAYOUT},
+ {"icon_view_sort_by", "metadata::" CAJA_METADATA_KEY_ICON_VIEW_SORT_BY},
+ {"icon_view_sort_reversed", "metadata::" CAJA_METADATA_KEY_ICON_VIEW_SORT_REVERSED},
+ {"icon_view_keep_aligned", "metadata::" CAJA_METADATA_KEY_ICON_VIEW_KEEP_ALIGNED},
+ {"icon_view_layout_timestamp", "metadata::" CAJA_METADATA_KEY_ICON_VIEW_LAYOUT_TIMESTAMP},
+ {"list_view_zoom_level", "metadata::" CAJA_METADATA_KEY_LIST_VIEW_ZOOM_LEVEL},
+ {"list_view_sort_column", "metadata::" CAJA_METADATA_KEY_LIST_VIEW_SORT_COLUMN},
+ {"list_view_sort_reversed", "metadata::" CAJA_METADATA_KEY_LIST_VIEW_SORT_REVERSED},
+ {"list_view_visible_columns", "metadata::" CAJA_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS},
+ {"list_view_column_order", "metadata::" CAJA_METADATA_KEY_LIST_VIEW_COLUMN_ORDER},
+ {"compact_view_zoom_level", "metadata::" CAJA_METADATA_KEY_COMPACT_VIEW_ZOOM_LEVEL},
+ {"window_geometry", "metadata::" CAJA_METADATA_KEY_WINDOW_GEOMETRY},
+ {"window_scroll_position", "metadata::" CAJA_METADATA_KEY_WINDOW_SCROLL_POSITION},
+ {"window_show_hidden_files", "metadata::" CAJA_METADATA_KEY_WINDOW_SHOW_HIDDEN_FILES},
+ {"window_maximized", "metadata::" CAJA_METADATA_KEY_WINDOW_MAXIMIZED},
+ {"window_sticky", "metadata::" CAJA_METADATA_KEY_WINDOW_STICKY},
+ {"window_keep_above", "metadata::" CAJA_METADATA_KEY_WINDOW_KEEP_ABOVE},
+ {"sidebar_background_color", "metadata::" CAJA_METADATA_KEY_SIDEBAR_BACKGROUND_COLOR},
+ {"sidebar_background_tile_image", "metadata::" CAJA_METADATA_KEY_SIDEBAR_BACKGROUND_IMAGE},
+ {"sidebar_buttons", "metadata::" CAJA_METADATA_KEY_SIDEBAR_BUTTONS},
+ {"annotation", "metadata::" CAJA_METADATA_KEY_ANNOTATION},
+ {"icon_position", "metadata::" CAJA_METADATA_KEY_ICON_POSITION},
+ {"icon_position_timestamp", "metadata::" CAJA_METADATA_KEY_ICON_POSITION_TIMESTAMP},
+ {"icon_scale", "metadata::" CAJA_METADATA_KEY_ICON_SCALE},
+ {"custom_icon", "metadata::" CAJA_METADATA_KEY_CUSTOM_ICON},
+ {"screen", "metadata::" CAJA_METADATA_KEY_SCREEN},
+ {"keyword", "metadata::" CAJA_METADATA_KEY_EMBLEMS},
+};
+
+static const char *
+convert_key_name (const char *old_key)
+{
+ int i;
+
+ for (i = 0; i < G_N_ELEMENTS (metadata_keys); i++)
+ {
+ if (strcmp (metadata_keys[i].old_key, old_key) == 0)
+ {
+ return metadata_keys[i].new_key;
+ }
+ }
+
+ return NULL;
+}
+
+static void
+parse_xml_node (GFile *file,
+ xmlNodePtr filenode)
+{
+ xmlNodePtr node;
+ xmlAttrPtr attr;
+ xmlChar *property;
+ const char *new_key;
+ GHashTable *list_keys;
+ GList *keys, *l;
+ GHashTableIter iter;
+ GFileInfo *info;
+ int i;
+ char **strv;
+ GError *error;
+
+ info = NULL;
+ if (!g_file_has_uri_scheme (file, "x-caja-desktop"))
+ {
+ info = g_file_info_new ();
+ }
+
+ for (attr = filenode->properties; attr != NULL; attr = attr->next)
+ {
+ if (strcmp ((char *)attr->name, "name") == 0 ||
+ strcmp ((char *)attr->name, "timestamp") == 0)
+ {
+ continue;
+ }
+
+ new_key = convert_key_name (attr->name);
+ if (new_key)
+ {
+ property = xmlGetProp (filenode, attr->name);
+ if (property)
+ {
+ if (info)
+ {
+ g_file_info_set_attribute_string (info,
+ new_key,
+ property);
+ }
+ else
+ {
+ desktop_set_metadata_string (file, new_key, property);
+ }
+ xmlFree (property);
+ }
+ }
+ }
+
+ list_keys = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
+ for (node = filenode->children; node != NULL; node = node->next)
+ {
+ for (attr = node->properties; attr != NULL; attr = attr->next)
+ {
+ new_key = convert_key_name (node->name);
+ if (new_key)
+ {
+ property = xmlGetProp (node, attr->name);
+ if (property)
+ {
+ keys = g_hash_table_lookup (list_keys, new_key);
+ keys = g_list_append (keys, property);
+ g_hash_table_replace (list_keys, (char *)new_key, keys);
+ }
+ }
+ }
+ }
+
+ g_hash_table_iter_init (&iter, list_keys);
+ while (g_hash_table_iter_next (&iter, (void **)&new_key, (void **)&keys))
+ {
+ strv = g_new0 (char *, g_list_length (keys) + 1);
+
+ for (l = keys, i = 0; l != NULL; l = l->next, i++)
+ {
+ strv[i] = l->data;
+ }
+ if (info)
+ {
+ g_file_info_set_attribute_stringv (info,
+ new_key,
+ strv);
+ }
+ else
+ {
+ desktop_set_metadata_stringv (file, new_key, strv);
+ }
+ g_free (strv);
+ g_list_foreach (keys, (GFunc)xmlFree, NULL);
+ g_list_free (keys);
+ }
+ g_hash_table_destroy (list_keys);
+
+ if (info)
+ {
+ error = NULL;
+ if (!g_file_set_attributes_from_info (file,
+ info,
+ 0, NULL, &error))
+ {
+ char *uri;
+
+ uri = g_file_get_uri (file);
+ if (!quiet)
+ {
+ g_print ("error setting info for %s: %s\n", uri, error->message);
+ }
+ g_free (uri);
+ g_error_free (error);
+ }
+ g_object_unref (info);
+ }
+}
+
+static void
+convert_xml_file (xmlDocPtr xml,
+ GFile *dir)
+{
+ xmlNodePtr node;
+ xmlChar *name;
+ char *unescaped_name;
+ GFile *file;
+
+ for (node = xml_get_root_children (xml);
+ node != NULL; node = node->next)
+ {
+ if (strcmp ((char *)node->name, "file") == 0)
+ {
+ name = xmlGetProp (node, (xmlChar *)"name");
+ unescaped_name = g_uri_unescape_string ((char *)name, "/");
+ xmlFree (name);
+
+ if (unescaped_name == NULL)
+ {
+ continue;
+ }
+
+ if (strcmp (unescaped_name, ".") == 0)
+ {
+ file = g_object_ref (dir);
+ }
+ else
+ {
+ file = g_file_get_child (dir, unescaped_name);
+ }
+
+ parse_xml_node (file, node);
+ g_object_unref (file);
+ g_free (unescaped_name);
+ }
+ }
+}
+
+static void
+convert_caja_file (char *file)
+{
+ GFile *dir;
+ char *uri;
+ gchar *contents;
+ gsize length;
+ xmlDocPtr xml;
+
+ if (!g_file_get_contents (file, &contents, &length, NULL))
+ {
+ if (!quiet)
+ {
+ g_print ("failed to load %s\n", file);
+ }
+ return;
+ }
+
+ uri = get_uri_from_caja_metafile_name (file);
+ if (uri == NULL)
+ {
+ g_free (contents);
+ return;
+ }
+
+ dir = g_file_new_for_uri (uri);
+ g_free (uri);
+
+ xml = xmlParseMemory (contents, length);
+ g_free (contents);
+ if (xml == NULL)
+ {
+ return;
+ }
+
+ convert_xml_file (xml, dir);
+ xmlFreeDoc (xml);
+}
+
+static GOptionEntry entries[] =
+{
+ {
+ "quiet", 'q', 0, G_OPTION_ARG_NONE, &quiet,
+ "Don't show errors", NULL
+ },
+ { NULL }
+};
+
+int
+main (int argc, char *argv[])
+{
+ GOptionContext *context;
+ GError *error = NULL;
+ int i;
+
+ g_type_init ();
+
+ context = g_option_context_new ("<caja metadata files> - convert caja metadata");
+ g_option_context_add_main_entries (context, entries, NULL);
+ if (!g_option_context_parse (context, &argc, &argv, &error))
+ {
+ g_printerr ("option parsing failed: %s\n", error->message);
+ return 1;
+ }
+
+ if (argc < 2)
+ {
+ GDir *dir;
+ char *metafile_dir;
+ char *file;
+ const char *entry;
+
+ /* Convert all metafiles */
+
+ metafile_dir = g_build_filename (g_get_home_dir (), ".config", "caja", "metafiles", NULL);
+
+ dir = g_dir_open (metafile_dir, 0, NULL);
+ if (dir)
+ {
+ while ((entry = g_dir_read_name (dir)) != NULL)
+ {
+ file = g_build_filename (metafile_dir, entry, NULL);
+ if (g_str_has_suffix (file, ".xml"))
+ convert_caja_file (file);
+ g_free (file);
+ }
+ g_dir_close (dir);
+ }
+ g_free (metafile_dir);
+ }
+ else
+ {
+ for (i = 1; i < argc; i++)
+ {
+ convert_caja_file (argv[i]);
+ }
+ }
+
+ return 0;
+}
diff --git a/src/caja-desktop-window.c b/src/caja-desktop-window.c
new file mode 100644
index 00000000..807db291
--- /dev/null
+++ b/src/caja-desktop-window.c
@@ -0,0 +1,277 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Authors: Darin Adler <[email protected]>
+ */
+
+#include <config.h>
+#include "caja-desktop-window.h"
+#include "caja-window-private.h"
+#include "caja-actions.h"
+
+#include <X11/Xatom.h>
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+#include <eel/eel-gtk-macros.h>
+#include <eel/eel-vfs-extensions.h>
+#include <libcaja-private/caja-file-utilities.h>
+#include <libcaja-private/caja-icon-names.h>
+#include <gio/gio.h>
+#include <glib/gi18n.h>
+
+struct CajaDesktopWindowDetails
+{
+ int dummy;
+};
+
+static void set_wmspec_desktop_hint (GdkWindow *window);
+
+G_DEFINE_TYPE (CajaDesktopWindow, caja_desktop_window,
+ CAJA_TYPE_SPATIAL_WINDOW);
+
+static void
+caja_desktop_window_init (CajaDesktopWindow *window)
+{
+ GtkAction *action;
+ AtkObject *accessible;
+
+ window->details = g_new0 (CajaDesktopWindowDetails, 1);
+
+ gtk_window_move (GTK_WINDOW (window), 0, 0);
+
+ /* shouldn't really be needed given our semantic type
+ * of _NET_WM_TYPE_DESKTOP, but why not
+ */
+ gtk_window_set_resizable (GTK_WINDOW (window),
+ FALSE);
+
+ g_object_set_data (G_OBJECT (window), "is_desktop_window",
+ GINT_TO_POINTER (1));
+
+ gtk_widget_hide (CAJA_WINDOW (window)->details->statusbar);
+ gtk_widget_hide (CAJA_WINDOW (window)->details->menubar);
+
+ /* Don't allow close action on desktop */
+ action = gtk_action_group_get_action (CAJA_WINDOW (window)->details->main_action_group,
+ CAJA_ACTION_CLOSE);
+ gtk_action_set_sensitive (action, FALSE);
+
+ /* Set the accessible name so that it doesn't inherit the cryptic desktop URI. */
+ accessible = gtk_widget_get_accessible (GTK_WIDGET (window));
+ if (accessible)
+ atk_object_set_name (accessible, _("Desktop"));
+}
+
+static gint
+caja_desktop_window_delete_event (CajaDesktopWindow *window)
+{
+ /* Returning true tells GTK+ not to delete the window. */
+ return TRUE;
+}
+
+void
+caja_desktop_window_update_directory (CajaDesktopWindow *window)
+{
+ GFile *location;
+
+ g_assert (CAJA_IS_DESKTOP_WINDOW (window));
+
+ CAJA_SPATIAL_WINDOW (window)->affect_spatial_window_on_next_location_change = TRUE;
+ location = g_file_new_for_uri (EEL_DESKTOP_URI);
+ caja_window_go_to (CAJA_WINDOW (window), location);
+ g_object_unref (location);
+}
+
+static void
+caja_desktop_window_screen_size_changed (GdkScreen *screen,
+ CajaDesktopWindow *window)
+{
+ int width_request, height_request;
+
+ width_request = gdk_screen_get_width (screen);
+ height_request = gdk_screen_get_height (screen);
+
+ g_object_set (window,
+ "width_request", width_request,
+ "height_request", height_request,
+ NULL);
+}
+
+CajaDesktopWindow *
+caja_desktop_window_new (CajaApplication *application,
+ GdkScreen *screen)
+{
+ CajaDesktopWindow *window;
+ int width_request, height_request;
+
+ width_request = gdk_screen_get_width (screen);
+ height_request = gdk_screen_get_height (screen);
+
+ window = CAJA_DESKTOP_WINDOW
+ (gtk_widget_new (caja_desktop_window_get_type(),
+ "app", application,
+ "width_request", width_request,
+ "height_request", height_request,
+ "screen", screen,
+ NULL));
+
+ /* Special sawmill setting*/
+ gtk_window_set_wmclass (GTK_WINDOW (window), "desktop_window", "Caja");
+
+ g_signal_connect (window, "delete_event", G_CALLBACK (caja_desktop_window_delete_event), NULL);
+
+ /* Point window at the desktop folder.
+ * Note that caja_desktop_window_init is too early to do this.
+ */
+ caja_desktop_window_update_directory (window);
+
+ return window;
+}
+
+static void
+finalize (GObject *object)
+{
+ CajaDesktopWindow *window;
+
+ window = CAJA_DESKTOP_WINDOW (object);
+
+ g_free (window->details);
+
+ G_OBJECT_CLASS (caja_desktop_window_parent_class)->finalize (object);
+}
+
+static void
+map (GtkWidget *widget)
+{
+ /* Chain up to realize our children */
+ GTK_WIDGET_CLASS (caja_desktop_window_parent_class)->map (widget);
+ gdk_window_lower (gtk_widget_get_window (widget));
+}
+
+
+static void
+unrealize (GtkWidget *widget)
+{
+ CajaDesktopWindow *window;
+ GdkWindow *root_window;
+
+ window = CAJA_DESKTOP_WINDOW (widget);
+
+ root_window = gdk_screen_get_root_window (
+ gtk_window_get_screen (GTK_WINDOW (window)));
+
+ gdk_property_delete (root_window,
+ gdk_atom_intern ("CAJA_DESKTOP_WINDOW_ID", TRUE));
+
+ g_signal_handlers_disconnect_by_func (gtk_window_get_screen (GTK_WINDOW (window)),
+ G_CALLBACK (caja_desktop_window_screen_size_changed),
+ window);
+
+ GTK_WIDGET_CLASS (caja_desktop_window_parent_class)->unrealize (widget);
+}
+
+static void
+set_wmspec_desktop_hint (GdkWindow *window)
+{
+ GdkAtom atom;
+
+ atom = gdk_atom_intern ("_NET_WM_WINDOW_TYPE_DESKTOP", FALSE);
+
+ gdk_property_change (window,
+ gdk_atom_intern ("_NET_WM_WINDOW_TYPE", FALSE),
+ gdk_x11_xatom_to_atom (XA_ATOM), 32,
+ GDK_PROP_MODE_REPLACE, (guchar *) &atom, 1);
+}
+
+static void
+set_desktop_window_id (CajaDesktopWindow *window,
+ GdkWindow *gdkwindow)
+{
+ /* Tuck the desktop windows xid in the root to indicate we own the desktop.
+ */
+ Window window_xid;
+ GdkWindow *root_window;
+
+ root_window = gdk_screen_get_root_window (
+ gtk_window_get_screen (GTK_WINDOW (window)));
+
+ window_xid = GDK_WINDOW_XWINDOW (gdkwindow);
+
+ gdk_property_change (root_window,
+ gdk_atom_intern ("CAJA_DESKTOP_WINDOW_ID", FALSE),
+ gdk_x11_xatom_to_atom (XA_WINDOW), 32,
+ GDK_PROP_MODE_REPLACE, (guchar *) &window_xid, 1);
+}
+
+static void
+realize (GtkWidget *widget)
+{
+ CajaDesktopWindow *window;
+
+ window = CAJA_DESKTOP_WINDOW (widget);
+
+ /* Make sure we get keyboard events */
+ gtk_widget_set_events (widget, gtk_widget_get_events (widget)
+ | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK);
+
+ /* Do the work of realizing. */
+ GTK_WIDGET_CLASS (caja_desktop_window_parent_class)->realize (widget);
+
+ /* This is the new way to set up the desktop window */
+ set_wmspec_desktop_hint (gtk_widget_get_window (widget));
+
+ set_desktop_window_id (window, gtk_widget_get_window (widget));
+
+ g_signal_connect (gtk_window_get_screen (GTK_WINDOW (window)), "size_changed",
+ G_CALLBACK (caja_desktop_window_screen_size_changed), window);
+}
+
+static char *
+real_get_title (CajaWindow *window)
+{
+ return g_strdup (_("Desktop"));
+}
+
+static CajaIconInfo *
+real_get_icon (CajaWindow *window,
+ CajaWindowSlot *slot)
+{
+ return caja_icon_info_lookup_from_name (CAJA_ICON_DESKTOP, 48);
+}
+
+static void
+caja_desktop_window_class_init (CajaDesktopWindowClass *class)
+{
+ G_OBJECT_CLASS (class)->finalize = finalize;
+ GTK_WIDGET_CLASS (class)->realize = realize;
+ GTK_WIDGET_CLASS (class)->unrealize = unrealize;
+
+
+ GTK_WIDGET_CLASS (class)->map = map;
+
+ CAJA_WINDOW_CLASS (class)->window_type = CAJA_WINDOW_DESKTOP;
+
+ CAJA_WINDOW_CLASS (class)->get_title
+ = real_get_title;
+ CAJA_WINDOW_CLASS (class)->get_icon
+ = real_get_icon;
+
+}
diff --git a/src/caja-desktop-window.h b/src/caja-desktop-window.h
new file mode 100644
index 00000000..9268414b
--- /dev/null
+++ b/src/caja-desktop-window.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Authors: Darin Adler <[email protected]>
+ */
+
+/* caja-desktop-window.h
+ */
+
+#ifndef CAJA_DESKTOP_WINDOW_H
+#define CAJA_DESKTOP_WINDOW_H
+
+#include "caja-window.h"
+#include "caja-application.h"
+#include "caja-spatial-window.h"
+
+#define CAJA_TYPE_DESKTOP_WINDOW caja_desktop_window_get_type()
+#define CAJA_DESKTOP_WINDOW(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_DESKTOP_WINDOW, CajaDesktopWindow))
+#define CAJA_DESKTOP_WINDOW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_DESKTOP_WINDOW, CajaDesktopWindowClass))
+#define CAJA_IS_DESKTOP_WINDOW(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_DESKTOP_WINDOW))
+#define CAJA_IS_DESKTOP_WINDOW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_DESKTOP_WINDOW))
+#define CAJA_DESKTOP_WINDOW_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_DESKTOP_WINDOW, CajaDesktopWindowClass))
+
+typedef struct CajaDesktopWindowDetails CajaDesktopWindowDetails;
+
+typedef struct
+{
+ CajaSpatialWindow parent_spot;
+ CajaDesktopWindowDetails *details;
+ gboolean affect_desktop_on_next_location_change;
+} CajaDesktopWindow;
+
+typedef struct
+{
+ CajaSpatialWindowClass parent_spot;
+} CajaDesktopWindowClass;
+
+GType caja_desktop_window_get_type (void);
+CajaDesktopWindow *caja_desktop_window_new (CajaApplication *application,
+ GdkScreen *screen);
+void caja_desktop_window_update_directory (CajaDesktopWindow *window);
+
+#endif /* CAJA_DESKTOP_WINDOW_H */
diff --git a/src/caja-emblem-sidebar.c b/src/caja-emblem-sidebar.c
new file mode 100644
index 00000000..79de5299
--- /dev/null
+++ b/src/caja-emblem-sidebar.c
@@ -0,0 +1,1174 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 1999, 2000, 2001 Eazel, Inc.
+ * Copyright (C) 2001 Red Hat, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Authors: James Willcox <[email protected]>
+ * Alexander Larsson <[email protected]>
+ *
+ * This is a sidebar displaying emblems which can be dragged onto files to
+ * set/unset the chosen emblem.
+ *
+ */
+
+#include <config.h>
+#include "caja-emblem-sidebar.h"
+
+#include <stdio.h>
+#include <eel/eel-gtk-macros.h>
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-string.h>
+#include <eel/eel-wrap-table.h>
+#include <eel/eel-labeled-image.h>
+#include <eel/eel-graphic-effects.h>
+#include <eel/eel-gdk-pixbuf-extensions.h>
+#include <eel/eel-stock-dialogs.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <mateconf/mateconf-client.h>
+#include <libcaja-private/caja-icon-dnd.h>
+#include <libcaja-private/caja-emblem-utils.h>
+#include <libcaja-private/caja-file-utilities.h>
+#include <libcaja-private/caja-sidebar-provider.h>
+#include <libcaja-private/caja-module.h>
+#include <libcaja-private/caja-signaller.h>
+
+struct CajaEmblemSidebarDetails
+{
+ CajaWindowInfo *window;
+ MateConfClient *client;
+ GtkWidget *emblems_table;
+ GtkWidget *popup;
+ GtkWidget *popup_remove;
+ GtkWidget *popup_rename;
+
+ char *popup_emblem_keyword;
+ char *popup_emblem_display_name;
+ GdkPixbuf *popup_emblem_pixbuf;
+};
+
+#define ERASE_EMBLEM_KEYWORD "erase"
+#define STANDARD_EMBLEM_HEIGHT 52
+#define EMBLEM_LABEL_SPACING 2
+
+static void caja_emblem_sidebar_iface_init (CajaSidebarIface *iface);
+static void caja_emblem_sidebar_finalize (GObject *object);
+static void caja_emblem_sidebar_populate (CajaEmblemSidebar *emblem_sidebar);
+static void caja_emblem_sidebar_refresh (CajaEmblemSidebar *emblem_sidebar);
+static void caja_emblem_sidebar_iface_init (CajaSidebarIface *iface);
+static void sidebar_provider_iface_init (CajaSidebarProviderIface *iface);
+static GType caja_emblem_sidebar_provider_get_type (void);
+
+static const GtkTargetEntry drag_types[] =
+{
+ {"property/keyword", 0, 0 }
+};
+
+enum
+{
+ TARGET_URI_LIST,
+ TARGET_URI,
+ TARGET_NETSCAPE_URL
+};
+
+static const GtkTargetEntry dest_types[] =
+{
+ {"text/uri-list", 0, TARGET_URI_LIST},
+ {"text/plain", 0, TARGET_URI},
+ {"_NETSCAPE_URL", 0, TARGET_NETSCAPE_URL}
+};
+
+typedef struct _Emblem
+{
+ GdkPixbuf *pixbuf;
+ char *uri;
+ char *name;
+ char *keyword;
+} Emblem;
+
+typedef struct
+{
+ GObject parent;
+} CajaEmblemSidebarProvider;
+
+typedef struct
+{
+ GObjectClass parent;
+} CajaEmblemSidebarProviderClass;
+
+
+
+
+G_DEFINE_TYPE_WITH_CODE (CajaEmblemSidebar, caja_emblem_sidebar, GTK_TYPE_VBOX,
+ G_IMPLEMENT_INTERFACE (CAJA_TYPE_SIDEBAR,
+ caja_emblem_sidebar_iface_init));
+
+G_DEFINE_TYPE_WITH_CODE (CajaEmblemSidebarProvider, caja_emblem_sidebar_provider, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (CAJA_TYPE_SIDEBAR_PROVIDER,
+ sidebar_provider_iface_init));
+
+static void
+caja_emblem_sidebar_drag_data_get_cb (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *data,
+ guint info,
+ guint time,
+ CajaEmblemSidebar *emblem_sidebar)
+{
+ char *keyword;
+
+ keyword = g_object_get_data (G_OBJECT (widget), "emblem-keyword");
+
+ g_return_if_fail (keyword != NULL);
+
+ gtk_selection_data_set (data, gtk_selection_data_get_target (data), 8,
+ keyword,
+ strlen (keyword));
+}
+
+static void
+caja_emblem_sidebar_enter_notify_cb (GtkWidget *widget,
+ CajaEmblemSidebar *emblem_sidebar)
+{
+ GdkPixbuf *pixbuf;
+ EelLabeledImage *image;
+
+ pixbuf = g_object_get_data (G_OBJECT (widget), "prelight-pixbuf");
+ image = g_object_get_data (G_OBJECT (widget), "labeled-image");
+
+ eel_labeled_image_set_pixbuf (EEL_LABELED_IMAGE (image), pixbuf);
+}
+
+static void
+caja_emblem_sidebar_leave_notify_cb (GtkWidget *widget,
+ CajaEmblemSidebar *emblem_sidebar)
+{
+ GdkPixbuf *pixbuf;
+ EelLabeledImage *image;
+
+ pixbuf = g_object_get_data (G_OBJECT (widget), "original-pixbuf");
+ image = g_object_get_data (G_OBJECT (widget), "labeled-image");
+
+ eel_labeled_image_set_pixbuf (EEL_LABELED_IMAGE (image), pixbuf);
+}
+
+static gboolean
+caja_emblem_sidebar_button_press_cb (GtkWidget *widget,
+ GdkEventButton *event,
+ CajaEmblemSidebar *emblem_sidebar)
+{
+ char *keyword, *name;
+ GdkPixbuf *pixbuf;
+
+ if (event->button == 3)
+ {
+ keyword = g_object_get_data (G_OBJECT (widget),
+ "emblem-keyword");
+ name = g_object_get_data (G_OBJECT (widget),
+ "emblem-display-name");
+ pixbuf = g_object_get_data (G_OBJECT (widget),
+ "original-pixbuf");
+
+ emblem_sidebar->details->popup_emblem_keyword = keyword;
+ emblem_sidebar->details->popup_emblem_display_name = name;
+ emblem_sidebar->details->popup_emblem_pixbuf = pixbuf;
+
+ gtk_widget_set_sensitive (emblem_sidebar->details->popup_remove,
+ caja_emblem_can_remove_emblem (keyword));
+ gtk_widget_set_sensitive (emblem_sidebar->details->popup_rename,
+ caja_emblem_can_rename_emblem (keyword));
+
+
+ gtk_menu_popup (GTK_MENU (emblem_sidebar->details->popup),
+ NULL, NULL, NULL, NULL, event->button,
+ event->time);
+ }
+
+ return TRUE;
+}
+
+static void
+send_emblems_changed (void)
+{
+ g_signal_emit_by_name (caja_signaller_get_current (),
+ "emblems_changed");
+}
+
+static void
+emblems_changed_callback (GObject *signaller,
+ CajaEmblemSidebar *emblem_sidebar)
+{
+ caja_emblem_sidebar_refresh (emblem_sidebar);
+}
+
+static void
+caja_emblem_sidebar_delete_cb (GtkWidget *menu_item,
+ CajaEmblemSidebar *emblem_sidebar)
+{
+ char *error;
+
+ if (caja_emblem_remove_emblem (emblem_sidebar->details->popup_emblem_keyword))
+ {
+ send_emblems_changed ();
+ }
+ else
+ {
+ error = g_strdup_printf (_("Could not remove emblem with name '%s'."), emblem_sidebar->details->popup_emblem_display_name);
+ eel_show_error_dialog (error, _("This is probably because the emblem is a permanent one, and not one that you added yourself."),
+ NULL);
+ g_free (error);
+ }
+}
+
+static void
+rename_dialog_response_cb (GtkWidget *dialog, int response,
+ CajaEmblemSidebar *emblem_sidebar)
+{
+ GtkWidget *entry;
+ char *keyword, *name, *error;
+
+ keyword = g_object_get_data (G_OBJECT (dialog), "emblem-keyword");
+
+ if (response == GTK_RESPONSE_CANCEL)
+ {
+ g_free (keyword);
+ gtk_widget_destroy (dialog);
+ return;
+ }
+ else if (response == GTK_RESPONSE_HELP)
+ {
+ g_message ("Implement me!");
+ return;
+ }
+
+ entry = g_object_get_data (G_OBJECT (dialog), "entry");
+
+ name = g_strdup (gtk_entry_get_text (GTK_ENTRY (entry)));
+
+ gtk_widget_destroy (dialog);
+
+ if (caja_emblem_rename_emblem (keyword, name))
+ {
+ send_emblems_changed ();
+ }
+ else
+ {
+ error = g_strdup_printf (_("Could not rename emblem with name '%s'."), name);
+ eel_show_error_dialog (error, _("This is probably because the emblem is a permanent one, and not one that you added yourself."),
+ NULL);
+ g_free (error);
+ }
+
+ g_free (keyword);
+ g_free (name);
+}
+
+static GtkWidget *
+create_rename_emblem_dialog (CajaEmblemSidebar *emblem_sidebar,
+ const char *keyword, const char *orig_name,
+ GdkPixbuf *pixbuf)
+{
+ GtkWidget *dialog, *label, *image, *entry, *hbox;
+
+ image = gtk_image_new_from_pixbuf (pixbuf);
+ entry = gtk_entry_new ();
+
+ dialog = gtk_dialog_new_with_buttons (_("Rename Emblem"),
+ NULL,
+ 0,
+ GTK_STOCK_CANCEL,
+ GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OK,
+ GTK_RESPONSE_OK,
+ GTK_STOCK_HELP,
+ GTK_RESPONSE_HELP,
+ NULL);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog),
+ GTK_RESPONSE_OK);
+
+ g_object_set_data (G_OBJECT (dialog), "emblem-keyword",
+ g_strdup (keyword));
+ g_object_set_data (G_OBJECT (dialog), "entry",
+ entry);
+
+ label = gtk_label_new (_("Enter a new name for the displayed emblem:"));
+ gtk_widget_show (label);
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), label,
+ FALSE, FALSE, 8);
+
+
+ hbox = gtk_hbox_new (FALSE, 8);
+ gtk_box_pack_start (GTK_BOX (hbox), image, TRUE, TRUE, 8);
+
+ gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE);
+
+ gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, FALSE, 8);
+ gtk_widget_show_all (hbox);
+
+ /* it would be nice to have the text selected, ready to be overwritten
+ * by the user, but that doesn't seem possible.
+ */
+ gtk_widget_grab_focus (entry);
+ gtk_entry_set_text (GTK_ENTRY (entry), orig_name);
+
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), hbox,
+ TRUE, TRUE, 8);
+
+
+ return dialog;
+}
+
+static void
+caja_emblem_sidebar_rename_cb (GtkWidget *menu_item,
+ CajaEmblemSidebar *emblem_sidebar)
+{
+ GtkWidget *dialog;
+
+ dialog = create_rename_emblem_dialog (emblem_sidebar,
+ emblem_sidebar->details->popup_emblem_keyword,
+ emblem_sidebar->details->popup_emblem_display_name,
+ emblem_sidebar->details->popup_emblem_pixbuf);
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (rename_dialog_response_cb),
+ emblem_sidebar);
+ gtk_widget_show (dialog);
+}
+
+static void
+create_popup_menu (CajaEmblemSidebar *emblem_sidebar)
+{
+ GtkWidget *popup, *menu_item, *menu_image;
+
+ popup = gtk_menu_new ();
+
+ /* add the "rename" menu item */
+ menu_image = gtk_image_new_from_stock (GTK_STOCK_PROPERTIES,
+ GTK_ICON_SIZE_MENU);
+ gtk_widget_show (menu_image);
+ menu_item = gtk_image_menu_item_new_with_label (_("Rename"));
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item),
+ menu_image);
+
+ g_signal_connect (menu_item, "activate",
+ G_CALLBACK (caja_emblem_sidebar_rename_cb),
+ emblem_sidebar);
+ gtk_widget_show (menu_item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item);
+ emblem_sidebar->details->popup_rename = menu_item;
+
+ /* add "delete" menu item */
+ menu_item = gtk_image_menu_item_new_from_stock (GTK_STOCK_DELETE,
+ NULL);
+ g_signal_connect (menu_item, "activate",
+ G_CALLBACK (caja_emblem_sidebar_delete_cb),
+ emblem_sidebar);
+ gtk_widget_show (menu_item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item);
+ emblem_sidebar->details->popup_remove = menu_item;
+
+ emblem_sidebar->details->popup = popup;
+}
+
+static GtkWidget *
+create_emblem_widget_with_pixbuf (CajaEmblemSidebar *emblem_sidebar,
+ const char *keyword,
+ const char *display_name,
+ GdkPixbuf *pixbuf)
+{
+ GtkWidget *image, *event_box;
+ GdkPixbuf *prelight_pixbuf;
+
+
+ image = eel_labeled_image_new (display_name, pixbuf);
+
+ eel_labeled_image_set_fixed_image_height (EEL_LABELED_IMAGE (image),
+ STANDARD_EMBLEM_HEIGHT);
+ eel_labeled_image_set_spacing (EEL_LABELED_IMAGE (image),
+ EMBLEM_LABEL_SPACING);
+ event_box = gtk_event_box_new ();
+ gtk_container_add (GTK_CONTAINER (event_box), image);
+
+ prelight_pixbuf = eel_create_spotlight_pixbuf (pixbuf);
+
+
+ gtk_drag_source_set (event_box, GDK_BUTTON1_MASK, drag_types,
+ G_N_ELEMENTS (drag_types),
+ GDK_ACTION_COPY | GDK_ACTION_MOVE);
+
+ gtk_drag_source_set_icon_pixbuf (event_box, pixbuf);
+
+
+
+ g_signal_connect (event_box, "button_press_event",
+ G_CALLBACK (caja_emblem_sidebar_button_press_cb),
+ emblem_sidebar);
+ g_signal_connect (event_box, "drag-data-get",
+ G_CALLBACK (caja_emblem_sidebar_drag_data_get_cb),
+ emblem_sidebar);
+ g_signal_connect (event_box, "enter-notify-event",
+ G_CALLBACK (caja_emblem_sidebar_enter_notify_cb),
+ emblem_sidebar);
+ g_signal_connect (event_box, "leave-notify-event",
+ G_CALLBACK (caja_emblem_sidebar_leave_notify_cb),
+ emblem_sidebar);
+
+ g_object_set_data_full (G_OBJECT (event_box),
+ "emblem-keyword",
+ g_strdup (keyword), g_free);
+ g_object_set_data_full (G_OBJECT (event_box),
+ "emblem-display-name",
+ g_strdup (display_name), g_free);
+ g_object_set_data_full (G_OBJECT (event_box),
+ "original-pixbuf",
+ pixbuf, g_object_unref);
+ g_object_set_data_full (G_OBJECT (event_box),
+ "prelight-pixbuf",
+ prelight_pixbuf, g_object_unref);
+ g_object_set_data (G_OBJECT (event_box),
+ "labeled-image", image);
+
+ return event_box;
+
+}
+
+static GtkWidget *
+create_emblem_widget (CajaEmblemSidebar *emblem_sidebar,
+ const char *name)
+{
+ GtkWidget *ret;
+ const char *display_name;
+ char *keyword;
+ GdkPixbuf *pixbuf;
+ CajaIconInfo *info;
+
+ info = caja_icon_info_lookup_from_name (name, CAJA_ICON_SIZE_STANDARD);
+
+ pixbuf = caja_icon_info_get_pixbuf_at_size (info, CAJA_ICON_SIZE_STANDARD);
+
+ display_name = caja_icon_info_get_display_name (info);
+
+ keyword = caja_emblem_get_keyword_from_icon_name (name);
+ if (display_name == NULL)
+ {
+ display_name = keyword;
+ }
+
+ ret = create_emblem_widget_with_pixbuf (emblem_sidebar, keyword,
+ display_name, pixbuf);
+ g_free (keyword);
+ g_object_unref (info);
+ return ret;
+}
+
+static void
+emblem_name_entry_changed_cb (GtkWidget *entry, Emblem *emblem)
+{
+ char *text;
+
+ g_free (emblem->name);
+
+ text = gtk_editable_get_chars (GTK_EDITABLE (entry), 0, -1);
+
+ emblem->name = g_strdup (text);
+}
+
+
+static void
+destroy_emblem (Emblem *emblem, gpointer user_data)
+{
+ g_return_if_fail (emblem != NULL);
+
+
+ if (emblem->pixbuf != NULL)
+ {
+ g_object_unref (emblem->pixbuf);
+ emblem->pixbuf = NULL;
+ }
+
+ if (emblem->name != NULL)
+ {
+ g_free (emblem->name);
+ emblem->name = NULL;
+ }
+
+ if (emblem->uri != NULL)
+ {
+ g_free (emblem->uri);
+ emblem->uri = NULL;
+ }
+
+ if (emblem->keyword != NULL)
+ {
+ g_free (emblem->keyword);
+ emblem->keyword = NULL;
+ }
+
+ g_free (emblem);
+}
+
+static void
+destroy_emblem_list (GSList *list)
+{
+ g_slist_foreach (list, (GFunc)destroy_emblem, NULL);
+ g_slist_free (list);
+}
+
+static GtkWidget *
+create_add_emblems_dialog (CajaEmblemSidebar *emblem_sidebar,
+ GSList *emblems)
+{
+ GtkWidget *dialog, *label, *table, *image;
+ GtkWidget *first_entry, *entry, *scroller, *hbox;
+ Emblem *emblem;
+ GSList *list;
+ int num_emblems;
+
+ first_entry = NULL;
+
+ dialog = gtk_dialog_new_with_buttons (_("Add Emblems..."),
+ NULL,
+ 0,
+ GTK_STOCK_CANCEL,
+ GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OK,
+ GTK_RESPONSE_OK,
+ GTK_STOCK_HELP,
+ GTK_RESPONSE_HELP,
+ NULL);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog),
+ GTK_RESPONSE_OK);
+
+ /* FIXME: make a better message */
+ if (g_slist_length (emblems) > 1)
+ {
+ label = gtk_label_new (_("Enter a descriptive name next to each emblem. This name will be used in other places to identify the emblem."));
+ }
+ else
+ {
+ label = gtk_label_new (_("Enter a descriptive name next to the emblem. This name will be used in other places to identify the emblem."));
+ }
+
+ gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
+ label, FALSE, FALSE, 8);
+ gtk_widget_show (label);
+
+ scroller = eel_scrolled_wrap_table_new (TRUE, GTK_SHADOW_NONE, &table);
+ eel_wrap_table_set_x_spacing (EEL_WRAP_TABLE (table), 8);
+ eel_wrap_table_set_y_spacing (EEL_WRAP_TABLE (table), 8);
+
+ num_emblems=0;
+ list = emblems;
+ while (list != NULL)
+ {
+ /* walk through the list of emblems, and create an image
+ * and entry for each one
+ */
+
+ emblem = (Emblem *)list->data;
+ list = list->next;
+
+ image = gtk_image_new_from_pixbuf (emblem->pixbuf);
+
+ hbox = gtk_hbox_new (TRUE, 0);
+ gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
+
+ entry = gtk_entry_new ();
+ gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE);
+ g_signal_connect (entry, "changed",
+ G_CALLBACK (emblem_name_entry_changed_cb),
+ emblem);
+
+ gtk_box_pack_start (GTK_BOX (hbox), entry, FALSE, FALSE, 0);
+ gtk_container_add (GTK_CONTAINER (table), hbox);
+
+ if (num_emblems == 0)
+ {
+ first_entry = entry;
+ }
+
+ num_emblems++;
+ }
+
+ gtk_container_set_border_width (GTK_CONTAINER (dialog), 8);
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
+ scroller, TRUE, TRUE, 8);
+ gtk_widget_show_all (scroller);
+
+ gtk_widget_grab_focus (first_entry);
+
+ /* we expand the window to hold up to about 4 emblems, but after that
+ * let the scroller do its thing. Is there a better way to do this?
+ */
+ gtk_window_set_default_size (GTK_WINDOW (dialog), 400,
+ MIN (120+(60*num_emblems), 350));
+
+ g_object_set_data_full (G_OBJECT (dialog), "emblems-to-add",
+ emblems, (GDestroyNotify)destroy_emblem_list);
+
+ return dialog;
+}
+
+static void
+remove_widget (GtkWidget *widget, GtkContainer *container)
+{
+ gtk_container_remove (container, widget);
+}
+
+static void
+caja_emblem_sidebar_refresh (CajaEmblemSidebar *emblem_sidebar)
+{
+ caja_emblem_refresh_list ();
+
+ gtk_container_foreach (GTK_CONTAINER (emblem_sidebar->details->emblems_table),
+ (GtkCallback)remove_widget,
+ emblem_sidebar->details->emblems_table);
+
+ caja_emblem_sidebar_populate (emblem_sidebar);
+}
+
+static void
+add_emblems_dialog_response_cb (GtkWidget *dialog, int response,
+ CajaEmblemSidebar *emblem_sidebar)
+{
+ Emblem *emblem;
+ GSList *emblems;
+ GSList *l;
+
+ switch (response)
+ {
+ case GTK_RESPONSE_CANCEL:
+ gtk_widget_destroy (dialog);
+ break;
+
+ case GTK_RESPONSE_HELP:
+ g_message ("Implement me!");
+ break;
+
+ case GTK_RESPONSE_OK:
+ emblems = g_object_get_data (G_OBJECT (dialog),
+ "emblems-to-add");
+
+ for (l = emblems; l; l = l->next)
+ {
+ char *keyword;
+
+ emblem = (Emblem *)l->data;
+ if (emblem->keyword != NULL)
+ {
+ /* this one has already been verified */
+ continue;
+ }
+
+ keyword = caja_emblem_create_unique_keyword (emblem->name);
+ if (!caja_emblem_verify_keyword
+ (GTK_WINDOW (dialog), keyword, emblem->name))
+ {
+ g_free (keyword);
+ return;
+ }
+ else
+ {
+ emblem->keyword = keyword;
+ }
+
+ }
+
+ for (l = emblems; l; l = l->next)
+ {
+ emblem = (Emblem *)l->data;
+
+ caja_emblem_install_custom_emblem (emblem->pixbuf,
+ emblem->keyword,
+ emblem->name,
+ GTK_WINDOW (dialog));
+ }
+
+ gtk_widget_destroy (dialog);
+
+ send_emblems_changed ();
+ break;
+ }
+}
+
+static void
+show_add_emblems_dialog (CajaEmblemSidebar *emblem_sidebar,
+ GSList *emblems)
+{
+ GtkWidget *dialog;
+
+ g_return_if_fail (emblems != NULL);
+
+ dialog = create_add_emblems_dialog (emblem_sidebar, emblems);
+
+ if (dialog == NULL)
+ {
+ return;
+ }
+
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (add_emblems_dialog_response_cb),
+ emblem_sidebar);
+
+ gtk_window_present (GTK_WINDOW (dialog));
+}
+
+static void
+caja_emblem_sidebar_drag_received_cb (GtkWidget *widget,
+ GdkDragContext *drag_context,
+ gint x,
+ gint y,
+ GtkSelectionData *data,
+ guint info,
+ guint time,
+ CajaEmblemSidebar *emblem_sidebar)
+{
+ GSList *emblems;
+ Emblem *emblem;
+ GdkPixbuf *pixbuf;
+ char *uri, *error, *uri_utf8;
+ char **uris;
+ GFile *f;
+ int i;
+ gboolean had_failure;
+ gint data_format, data_length;
+ const guchar *data_data;
+
+ had_failure = FALSE;
+ emblems = NULL;
+ data_format = gtk_selection_data_get_format (data);
+ data_length = gtk_selection_data_get_length (data);
+ data_data = gtk_selection_data_get_data (data);
+
+ switch (info)
+ {
+ case TARGET_URI_LIST:
+ if (data_format != 8 ||
+ data_length == 0)
+ {
+ g_message ("URI list had wrong format (%d) or length (%d)\n",
+ data_format, data_length);
+ return;
+ }
+
+ uris = g_uri_list_extract_uris (data_data);
+ if (uris == NULL)
+ {
+ break;
+ }
+
+ for (i = 0; uris[i] != NULL; ++i)
+ {
+ f = g_file_new_for_uri (uris[i]);
+ pixbuf = caja_emblem_load_pixbuf_for_emblem (f);
+
+ if (pixbuf == NULL)
+ {
+ /* this one apparently isn't an image, or
+ * at least not one that we know how to read
+ */
+ had_failure = TRUE;
+ g_object_unref (f);
+ continue;
+ }
+
+ emblem = g_new (Emblem, 1);
+ emblem->uri = g_file_get_uri (f);
+ emblem->name = NULL; /* created later on by the user */
+ emblem->keyword = NULL;
+ emblem->pixbuf = pixbuf;
+
+ g_object_unref (f);
+
+ emblems = g_slist_prepend (emblems, emblem);
+ }
+
+ g_strfreev (uris);
+
+ if (had_failure && emblems != NULL)
+ {
+ eel_show_error_dialog (_("Some of the files could not be added as emblems."), _("The emblems do not appear to be valid images."), NULL);
+ }
+ else if (had_failure && emblems == NULL)
+ {
+ eel_show_error_dialog (_("None of the files could be added as emblems."), _("The emblems do not appear to be valid images."), NULL);
+
+ }
+
+ if (emblems != NULL)
+ {
+ show_add_emblems_dialog (emblem_sidebar, emblems);
+ }
+
+ break;
+
+ case TARGET_URI:
+ if (data_format != 8 ||
+ data_length == 0)
+ {
+ g_warning ("URI had wrong format (%d) or length (%d)\n",
+ data_format, data_length);
+ return;
+ }
+
+ uri = g_strndup (data_data, data_length);
+
+ f = g_file_new_for_uri (uri);
+ pixbuf = caja_emblem_load_pixbuf_for_emblem (f);
+
+ if (pixbuf != NULL)
+ {
+ emblem = g_new (Emblem, 1);
+ emblem->uri = uri;
+ emblem->name = NULL;
+ emblem->keyword = NULL;
+ emblem->pixbuf = pixbuf;
+
+ emblems = g_slist_prepend (NULL, emblem);
+
+ show_add_emblems_dialog (emblem_sidebar, emblems);
+ }
+ else
+ {
+ uri_utf8 = g_file_get_parse_name (f);
+
+ if (uri_utf8)
+ {
+ error = g_strdup_printf (_("The file '%s' does not appear to be a valid image."), uri_utf8);
+ g_free (uri_utf8);
+ }
+ else
+ {
+ error = g_strdup (_("The dragged file does not appear to be a valid image."));
+ }
+ eel_show_error_dialog (_("The emblem cannot be added."), error, NULL);
+ g_free (error);
+ g_free (uri_utf8);
+ }
+
+ g_object_unref (f);
+ g_free (uri);
+
+ break;
+
+ case TARGET_NETSCAPE_URL:
+ if (data_format != 8 ||
+ data_length == 0)
+ {
+ g_message ("URI had wrong format (%d) or length (%d)\n",
+ data_format, data_length);
+ return;
+ }
+
+ /* apparently, this is a URI/title pair? or just a pair
+ * of identical URIs? Regardless, this seems to work...
+ */
+
+ uris = g_uri_list_extract_uris (data_data);
+ if (uris == NULL)
+ {
+ break;
+ }
+
+ uri = uris[0];
+ if (uri == NULL)
+ {
+ g_strfreev (uris);
+ break;
+ }
+
+ f = g_file_new_for_uri (uri);
+ pixbuf = caja_emblem_load_pixbuf_for_emblem (f);
+ g_object_unref (f);
+
+ if (pixbuf != NULL)
+ {
+ emblem = g_new (Emblem, 1);
+ emblem->uri = g_strdup (uri);
+ emblem->name = NULL;
+ emblem->keyword = NULL;
+ emblem->pixbuf = pixbuf;
+
+ emblems = g_slist_prepend (NULL, emblem);
+
+ show_add_emblems_dialog (emblem_sidebar, emblems);
+ }
+ else
+ {
+ g_warning ("Tried to load '%s', but failed.\n",
+ uri);
+ error = g_strdup_printf (_("The file '%s' does not appear to be a valid image."), uri);
+ eel_show_error_dialog (_("The emblem cannot be added."), error, NULL);
+ g_free (error);
+ }
+
+ g_strfreev (uris);
+
+ break;
+ }
+}
+
+static GtkWidget *
+caja_emblem_sidebar_create_container (CajaEmblemSidebar *emblem_sidebar)
+{
+ GtkWidget *emblems_table, *scroller;
+
+ /* The emblems wrapped table */
+ scroller = eel_scrolled_wrap_table_new (TRUE, GTK_SHADOW_IN, &emblems_table);
+
+ gtk_container_set_border_width (GTK_CONTAINER (emblems_table), 8);
+
+ /* set up dnd for adding emblems */
+ gtk_drag_dest_set (scroller,
+ GTK_DEST_DEFAULT_ALL,
+ dest_types, G_N_ELEMENTS (dest_types),
+ GDK_ACTION_COPY | GDK_ACTION_MOVE);
+
+ g_signal_connect (scroller, "drag-data-received",
+ G_CALLBACK (caja_emblem_sidebar_drag_received_cb),
+ emblem_sidebar);
+
+ gtk_widget_show (scroller);
+
+ emblem_sidebar->details->emblems_table = emblems_table;
+
+ return scroller;
+}
+
+static gint
+emblem_widget_sort_func (gconstpointer a, gconstpointer b)
+{
+ GObject *obj_a, *obj_b;
+
+ obj_a = G_OBJECT (a);
+ obj_b = G_OBJECT (b);
+
+ return strcmp (g_object_get_data (obj_a, "emblem-display-name"),
+ g_object_get_data (obj_b, "emblem-display-name"));
+}
+
+static void
+caja_emblem_sidebar_populate (CajaEmblemSidebar *emblem_sidebar)
+{
+ GList *icons, *l, *widgets;
+ GtkWidget *emblem_widget;
+ char *name;
+ char *path;
+ GdkPixbuf *erase_pixbuf;
+
+ erase_pixbuf = NULL;
+
+ path = caja_pixmap_file ("erase.png");
+ if (path != NULL)
+ {
+ erase_pixbuf = gdk_pixbuf_new_from_file (path, NULL);
+ }
+ g_free (path);
+
+ if (erase_pixbuf != NULL)
+ {
+ emblem_widget = create_emblem_widget_with_pixbuf (emblem_sidebar,
+ ERASE_EMBLEM_KEYWORD,
+ _("Erase"),
+ erase_pixbuf);
+ gtk_container_add (GTK_CONTAINER
+ (emblem_sidebar->details->emblems_table),
+ emblem_widget);
+ }
+
+
+ icons = caja_emblem_list_available ();
+
+ l = icons;
+ widgets = NULL;
+ while (l != NULL)
+ {
+ name = (char *)l->data;
+ l = l->next;
+
+ if (!caja_emblem_should_show_in_list (name))
+ {
+ continue;
+ }
+
+ emblem_widget = create_emblem_widget (emblem_sidebar, name);
+
+ widgets = g_list_prepend (widgets, emblem_widget);
+ }
+ eel_g_list_free_deep (icons);
+
+ /* sort the emblems by display name */
+ widgets = g_list_sort (widgets, emblem_widget_sort_func);
+
+ l = widgets;
+ while (l != NULL)
+ {
+ gtk_container_add
+ (GTK_CONTAINER (emblem_sidebar->details->emblems_table),
+ l->data);
+ l = l->next;
+ }
+ g_list_free (widgets);
+
+ gtk_widget_show_all (emblem_sidebar->details->emblems_table);
+}
+
+static void
+caja_emblem_sidebar_init (CajaEmblemSidebar *emblem_sidebar)
+{
+ GtkWidget *widget;
+
+ emblem_sidebar->details = g_new0 (CajaEmblemSidebarDetails, 1);
+
+ emblem_sidebar->details->client = mateconf_client_get_default ();
+
+ create_popup_menu (emblem_sidebar);
+
+ widget = caja_emblem_sidebar_create_container (emblem_sidebar);
+ caja_emblem_sidebar_populate (emblem_sidebar);
+
+ g_signal_connect_object (caja_signaller_get_current (),
+ "emblems_changed",
+ G_CALLBACK (emblems_changed_callback), emblem_sidebar, 0);
+
+ gtk_box_pack_start (GTK_BOX (emblem_sidebar), widget,
+ TRUE, TRUE, 0);
+}
+
+static void
+caja_emblem_sidebar_finalize (GObject *object)
+{
+ CajaEmblemSidebar *emblem_sidebar;
+
+ g_assert (CAJA_IS_EMBLEM_SIDEBAR (object));
+ emblem_sidebar = CAJA_EMBLEM_SIDEBAR (object);
+
+ if (emblem_sidebar->details != NULL)
+ {
+ if (emblem_sidebar->details->client != NULL)
+ {
+ g_object_unref (emblem_sidebar->details->client);
+ }
+
+ g_free (emblem_sidebar->details);
+ }
+
+ G_OBJECT_CLASS (caja_emblem_sidebar_parent_class)->finalize (object);
+}
+
+static void
+caja_emblem_sidebar_class_init (CajaEmblemSidebarClass *object_klass)
+{
+ GObjectClass *gobject_class;
+
+ CajaEmblemSidebarClass *klass;
+
+ klass = CAJA_EMBLEM_SIDEBAR_CLASS (object_klass);
+ gobject_class = G_OBJECT_CLASS (object_klass);
+
+ gobject_class->finalize = caja_emblem_sidebar_finalize;
+}
+
+static const char *
+caja_emblem_sidebar_get_sidebar_id (CajaSidebar *sidebar)
+{
+ return CAJA_EMBLEM_SIDEBAR_ID;
+}
+
+static char *
+caja_emblem_sidebar_get_tab_label (CajaSidebar *sidebar)
+{
+ return g_strdup (_("Emblems"));
+}
+
+static char *
+caja_emblem_sidebar_get_tab_tooltip (CajaSidebar *sidebar)
+{
+ return g_strdup (_("Show Emblems"));
+}
+
+static GdkPixbuf *
+caja_emblem_sidebar_get_tab_icon (CajaSidebar *sidebar)
+{
+ return NULL;
+}
+
+static void
+caja_emblem_sidebar_is_visible_changed (CajaSidebar *sidebar,
+ gboolean is_visible)
+{
+ /* Do nothing */
+}
+
+static void
+caja_emblem_sidebar_iface_init (CajaSidebarIface *iface)
+{
+ iface->get_sidebar_id = caja_emblem_sidebar_get_sidebar_id;
+ iface->get_tab_label = caja_emblem_sidebar_get_tab_label;
+ iface->get_tab_tooltip = caja_emblem_sidebar_get_tab_tooltip;
+ iface->get_tab_icon = caja_emblem_sidebar_get_tab_icon;
+ iface->is_visible_changed = caja_emblem_sidebar_is_visible_changed;
+}
+
+static void
+caja_emblem_sidebar_set_parent_window (CajaEmblemSidebar *sidebar,
+ CajaWindowInfo *window)
+{
+ sidebar->details->window = window;
+}
+
+static CajaSidebar *
+caja_emblem_sidebar_create (CajaSidebarProvider *provider,
+ CajaWindowInfo *window)
+{
+ CajaEmblemSidebar *sidebar;
+
+ sidebar = g_object_new (caja_emblem_sidebar_get_type (), NULL);
+ caja_emblem_sidebar_set_parent_window (sidebar, window);
+ g_object_ref_sink (sidebar);
+
+ return CAJA_SIDEBAR (sidebar);
+}
+
+static void
+sidebar_provider_iface_init (CajaSidebarProviderIface *iface)
+{
+ iface->create = caja_emblem_sidebar_create;
+}
+
+static void
+caja_emblem_sidebar_provider_init (CajaEmblemSidebarProvider *sidebar)
+{
+}
+
+static void
+caja_emblem_sidebar_provider_class_init (CajaEmblemSidebarProviderClass *class)
+{
+}
+
+void
+caja_emblem_sidebar_register (void)
+{
+ caja_module_add_type (caja_emblem_sidebar_provider_get_type ());
+}
+
diff --git a/src/caja-emblem-sidebar.h b/src/caja-emblem-sidebar.h
new file mode 100644
index 00000000..9c62934e
--- /dev/null
+++ b/src/caja-emblem-sidebar.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 1999, 2000 Eazel, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *
+ * This is the header file for the index panel widget, which displays oversidebar information
+ * in a vertical panel and hosts the meta-sidebars.
+ */
+
+#ifndef CAJA_EMBLEM_SIDEBAR_H
+#define CAJA_EMBLEM_SIDEBAR_H
+
+#include <gtk/gtk.h>
+
+#define CAJA_TYPE_EMBLEM_SIDEBAR caja_emblem_sidebar_get_type()
+#define CAJA_EMBLEM_SIDEBAR(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_EMBLEM_SIDEBAR, CajaEmblemSidebar))
+#define CAJA_EMBLEM_SIDEBAR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_EMBLEM_SIDEBAR, CajaEmblemSidebarClass))
+#define CAJA_IS_EMBLEM_SIDEBAR(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_EMBLEM_SIDEBAR))
+#define CAJA_IS_EMBLEM_SIDEBAR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_EMBLEM_SIDEBAR))
+#define CAJA_EMBLEM_SIDEBAR_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_EMBLEM_SIDEBAR, CajaEmblemSidebarClass))
+
+#define CAJA_EMBLEM_SIDEBAR_ID "CajaEmblemSidebar"
+
+typedef struct CajaEmblemSidebarDetails CajaEmblemSidebarDetails;
+
+typedef struct
+{
+ GtkVBox parent_slot;
+ CajaEmblemSidebarDetails *details;
+} CajaEmblemSidebar;
+
+typedef struct
+{
+ GtkVBoxClass parent_slot;
+
+} CajaEmblemSidebarClass;
+
+GType caja_emblem_sidebar_get_type (void);
+void caja_emblem_sidebar_register (void);
+
+#endif /* CAJA_EMBLEM_SIDEBAR_H */
diff --git a/src/caja-file-management-properties-main.c b/src/caja-file-management-properties-main.c
new file mode 100644
index 00000000..78f375e4
--- /dev/null
+++ b/src/caja-file-management-properties-main.c
@@ -0,0 +1,64 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-file-management-properties-main.c - Start the caja-file-management preference dialog.
+
+ Copyright (C) 2002 Jan Arne Petersen
+
+ 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: Jan Arne Petersen <[email protected]>
+*/
+
+#include <config.h>
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+
+#include <libcaja-private/caja-module.h>
+
+#include <eel/eel-preferences.h>
+
+#include "caja-file-management-properties.h"
+
+static void
+caja_file_management_properties_main_close_callback (GtkDialog *dialog,
+ int response_id)
+{
+ if (response_id == GTK_RESPONSE_CLOSE)
+ {
+ gtk_main_quit ();
+ }
+}
+
+int
+main (int argc, char *argv[])
+{
+ bindtextdomain (GETTEXT_PACKAGE, MATELOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ textdomain (GETTEXT_PACKAGE);
+
+ gtk_init (&argc, &argv);
+
+ eel_preferences_init ("/apps/caja");
+
+ caja_module_setup ();
+
+ caja_file_management_properties_dialog_show (G_CALLBACK (caja_file_management_properties_main_close_callback), NULL);
+
+ gtk_main ();
+
+ return 0;
+}
diff --git a/src/caja-file-management-properties.c b/src/caja-file-management-properties.c
new file mode 100644
index 00000000..fcfbbbf4
--- /dev/null
+++ b/src/caja-file-management-properties.c
@@ -0,0 +1,891 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-file-management-properties.c - Functions to create and show the caja preference dialog.
+
+ Copyright (C) 2002 Jan Arne Petersen
+
+ 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: Jan Arne Petersen <[email protected]>
+*/
+
+#include <config.h>
+
+#include "caja-file-management-properties.h"
+
+#include <string.h>
+#include <time.h>
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+
+#include <glib/gi18n.h>
+
+#include <eel/eel-mateconf-extensions.h>
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-preferences.h>
+
+#include <libcaja-private/caja-column-chooser.h>
+#include <libcaja-private/caja-column-utilities.h>
+#include <libcaja-private/caja-global-preferences.h>
+#include <libcaja-private/caja-module.h>
+
+#include <libcaja-private/caja-autorun.h>
+
+/* string enum preferences */
+#define CAJA_FILE_MANAGEMENT_PROPERTIES_DEFAULT_VIEW_WIDGET "default_view_combobox"
+#define CAJA_FILE_MANAGEMENT_PROPERTIES_ICON_VIEW_ZOOM_WIDGET "icon_view_zoom_combobox"
+#define CAJA_FILE_MANAGEMENT_PROPERTIES_COMPACT_VIEW_ZOOM_WIDGET "compact_view_zoom_combobox"
+#define CAJA_FILE_MANAGEMENT_PROPERTIES_LIST_VIEW_ZOOM_WIDGET "list_view_zoom_combobox"
+#define CAJA_FILE_MANAGEMENT_PROPERTIES_SORT_ORDER_WIDGET "sort_order_combobox"
+#define CAJA_FILE_MANAGEMENT_PROPERTIES_DATE_FORMAT_WIDGET "date_format_combobox"
+#define CAJA_FILE_MANAGEMENT_PROPERTIES_PREVIEW_TEXT_WIDGET "preview_text_combobox"
+#define CAJA_FILE_MANAGEMENT_PROPERTIES_PREVIEW_IMAGE_WIDGET "preview_image_combobox"
+#define CAJA_FILE_MANAGEMENT_PROPERTIES_PREVIEW_SOUND_WIDGET "preview_sound_combobox"
+#define CAJA_FILE_MANAGEMENT_PROPERTIES_PREVIEW_FOLDER_WIDGET "preview_folder_combobox"
+
+/* bool preferences */
+#define CAJA_FILE_MANAGEMENT_PROPERTIES_FOLDERS_FIRST_WIDGET "sort_folders_first_checkbutton"
+#define CAJA_FILE_MANAGEMENT_PROPERTIES_COMPACT_LAYOUT_WIDGET "compact_layout_checkbutton"
+#define CAJA_FILE_MANAGEMENT_PROPERTIES_LABELS_BESIDE_ICONS_WIDGET "labels_beside_icons_checkbutton"
+#define CAJA_FILE_MANAGEMENT_PROPERTIES_ALL_COLUMNS_SAME_WIDTH "all_columns_same_width_checkbutton"
+#define CAJA_FILE_MANAGEMENT_PROPERTIES_ALWAYS_USE_BROWSER_WIDGET "always_use_browser_checkbutton"
+#define CAJA_FILE_MANAGEMENT_PROPERTIES_ALWAYS_USE_LOCATION_ENTRY_WIDGET "always_use_location_entry_checkbutton"
+#define CAJA_FILE_MANAGEMENT_PROPERTIES_TRASH_CONFIRM_WIDGET "trash_confirm_checkbutton"
+#define CAJA_FILE_MANAGEMENT_PROPERTIES_TRASH_DELETE_WIDGET "trash_delete_checkbutton"
+#define CAJA_FILE_MANAGEMENT_PROPERTIES_OPEN_NEW_WINDOW_WIDGET "new_window_checkbutton"
+#define CAJA_FILE_MANAGEMENT_PROPERTIES_SHOW_HIDDEN_WIDGET "hidden_files_checkbutton"
+#define CAJA_FILE_MANAGEMENT_PROPERTIES_TREE_VIEW_FOLDERS_WIDGET "treeview_folders_checkbutton"
+#define CAJA_FILE_MANAGEMENT_PROPERTIES_MEDIA_AUTOMOUNT_OPEN "media_automount_open_checkbutton"
+#define CAJA_FILE_MANAGEMENT_PROPERTIES_MEDIA_AUTORUN_NEVER "media_autorun_never_checkbutton"
+
+/* int enums */
+#define CAJA_FILE_MANAGEMENT_PROPERTIES_THUMBNAIL_LIMIT_WIDGET "preview_image_size_combobox"
+
+static const char * const default_view_values[] =
+{
+ "icon_view",
+ "list_view",
+ "compact_view",
+ NULL
+};
+
+static const char * const zoom_values[] =
+{
+ "smallest",
+ "smaller",
+ "small",
+ "standard",
+ "large",
+ "larger",
+ "largest",
+ NULL
+};
+
+static const char * const sort_order_values[] =
+{
+ "name",
+ "size",
+ "type",
+ "modification_date",
+ "emblems",
+ NULL
+};
+
+static const char * const date_format_values[] =
+{
+ "locale",
+ "iso",
+ "informal",
+ NULL
+};
+
+static const char * const preview_values[] =
+{
+ "always",
+ "local_only",
+ "never",
+ NULL
+};
+
+static const char * const click_behavior_components[] =
+{
+ "single_click_radiobutton",
+ "double_click_radiobutton",
+ NULL
+};
+
+static const char * const click_behavior_values[] =
+{
+ "single",
+ "double",
+ NULL
+};
+
+static const char * const executable_text_components[] =
+{
+ "scripts_execute_radiobutton",
+ "scripts_view_radiobutton",
+ "scripts_confirm_radiobutton",
+ NULL
+};
+
+static const char * const executable_text_values[] =
+{
+ "launch",
+ "display",
+ "ask",
+ NULL
+};
+
+static const guint thumbnail_limit_values[] =
+{
+ 102400,
+ 512000,
+ 1048576,
+ 3145728,
+ 5242880,
+ 10485760,
+ 104857600,
+ 1073741824,
+ 2147483648U,
+ 4294967295U
+};
+
+static const char * const icon_captions_components[] =
+{
+ "captions_0_combobox",
+ "captions_1_combobox",
+ "captions_2_combobox",
+ NULL
+};
+
+static void
+caja_file_management_properties_size_group_create (GtkBuilder *builder,
+ char *prefix,
+ int items)
+{
+ GtkSizeGroup *size_group;
+ int i;
+ char *item_name;
+ GtkWidget *widget;
+
+ size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+
+ for (i = 0; i < items; i++)
+ {
+ item_name = g_strdup_printf ("%s_%d", prefix, i);
+ widget = GTK_WIDGET (gtk_builder_get_object (builder, item_name));
+ gtk_size_group_add_widget (size_group, widget);
+ g_free (item_name);
+ }
+ g_object_unref (G_OBJECT (size_group));
+}
+
+static void
+preferences_show_help (GtkWindow *parent,
+ char const *helpfile,
+ char const *sect_id)
+{
+ GError *error = NULL;
+ GtkWidget *dialog;
+ char *help_string;
+
+ g_assert (helpfile != NULL);
+ g_assert (sect_id != NULL);
+
+ help_string = g_strdup_printf ("ghelp:%s#%s", helpfile, sect_id);
+
+ gtk_show_uri (gtk_window_get_screen (parent),
+ help_string, gtk_get_current_event_time (),
+ &error);
+ g_free (help_string);
+
+ if (error)
+ {
+ dialog = gtk_message_dialog_new (GTK_WINDOW (parent),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_OK,
+ _("There was an error displaying help: \n%s"),
+ error->message);
+
+ g_signal_connect (G_OBJECT (dialog),
+ "response", G_CALLBACK (gtk_widget_destroy),
+ NULL);
+ gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+ gtk_widget_show (dialog);
+ g_error_free (error);
+ }
+}
+
+
+static void
+caja_file_management_properties_dialog_response_cb (GtkDialog *parent,
+ int response_id,
+ GtkBuilder *builder)
+{
+ char *section;
+
+ if (response_id == GTK_RESPONSE_HELP)
+ {
+ switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (gtk_builder_get_object (builder, "notebook1"))))
+ {
+ default:
+ case 0:
+ section = "goscaja-438";
+ break;
+ case 1:
+ section = "goscaja-56";
+ break;
+ case 2:
+ section = "goscaja-439";
+ break;
+ case 3:
+ section = "goscaja-490";
+ break;
+ case 4:
+ section = "goscaja-60";
+ }
+ preferences_show_help (GTK_WINDOW (parent), "user-guide", section);
+ }
+ else if (response_id == GTK_RESPONSE_CLOSE)
+ {
+ /* remove mateconf monitors */
+ eel_mateconf_monitor_remove ("/apps/caja/icon_view");
+ eel_mateconf_monitor_remove ("/apps/caja/list_view");
+ eel_mateconf_monitor_remove ("/apps/caja/preferences");
+ eel_mateconf_monitor_remove ("/desktop/mate/file_views");
+ }
+}
+
+static void
+columns_changed_callback (CajaColumnChooser *chooser,
+ gpointer callback_data)
+{
+ char **visible_columns;
+ char **column_order;
+
+ caja_column_chooser_get_settings (CAJA_COLUMN_CHOOSER (chooser),
+ &visible_columns,
+ &column_order);
+
+ eel_preferences_set_string_array (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS, visible_columns);
+ eel_preferences_set_string_array (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_COLUMN_ORDER, column_order);
+
+ g_strfreev (visible_columns);
+ g_strfreev (column_order);
+}
+
+static void
+free_column_names_array (GPtrArray *column_names)
+{
+ g_ptr_array_foreach (column_names, (GFunc) g_free, NULL);
+ g_ptr_array_free (column_names, TRUE);
+}
+
+static void
+create_icon_caption_combo_box_items (GtkComboBox *combo_box,
+ GList *columns)
+{
+ GList *l;
+ GPtrArray *column_names;
+
+ column_names = g_ptr_array_new ();
+
+ /* Translators: this is referred to captions under icons. */
+ gtk_combo_box_append_text (combo_box, _("None"));
+ g_ptr_array_add (column_names, g_strdup ("none"));
+
+ for (l = columns; l != NULL; l = l->next)
+ {
+ CajaColumn *column;
+ char *name;
+ char *label;
+
+ column = CAJA_COLUMN (l->data);
+
+ g_object_get (G_OBJECT (column),
+ "name", &name, "label", &label,
+ NULL);
+
+ /* Don't show name here, it doesn't make sense */
+ if (!strcmp (name, "name"))
+ {
+ g_free (name);
+ g_free (label);
+ continue;
+ }
+
+ gtk_combo_box_append_text (combo_box, label);
+ g_ptr_array_add (column_names, name);
+
+ g_free (label);
+ }
+ g_object_set_data_full (G_OBJECT (combo_box), "column_names",
+ column_names,
+ (GDestroyNotify) free_column_names_array);
+}
+
+static void
+icon_captions_changed_callback (GtkComboBox *combo_box,
+ gpointer user_data)
+{
+ GPtrArray *captions;
+ GtkBuilder *builder;
+ int i;
+
+ builder = GTK_BUILDER (user_data);
+
+ captions = g_ptr_array_new ();
+
+ for (i = 0; icon_captions_components[i] != NULL; i++)
+ {
+ GtkWidget *combo_box;
+ int active;
+ GPtrArray *column_names;
+ char *name;
+
+ combo_box = GTK_WIDGET (gtk_builder_get_object
+ (builder, icon_captions_components[i]));
+ active = gtk_combo_box_get_active (GTK_COMBO_BOX (combo_box));
+
+ column_names = g_object_get_data (G_OBJECT (combo_box),
+ "column_names");
+
+ name = g_ptr_array_index (column_names, active);
+ g_ptr_array_add (captions, name);
+ }
+ g_ptr_array_add (captions, NULL);
+
+ eel_preferences_set_string_array (CAJA_PREFERENCES_ICON_VIEW_CAPTIONS,
+ (char **)captions->pdata);
+ g_ptr_array_free (captions, TRUE);
+}
+
+static void
+update_caption_combo_box (GtkBuilder *builder,
+ const char *combo_box_name,
+ const char *name)
+{
+ GtkWidget *combo_box;
+ int i;
+ GPtrArray *column_names;
+
+ combo_box = GTK_WIDGET (gtk_builder_get_object (builder, combo_box_name));
+
+ g_signal_handlers_block_by_func
+ (combo_box,
+ G_CALLBACK (icon_captions_changed_callback),
+ builder);
+
+ column_names = g_object_get_data (G_OBJECT (combo_box),
+ "column_names");
+
+ for (i = 0; i < column_names->len; ++i)
+ {
+ if (!strcmp (name, g_ptr_array_index (column_names, i)))
+ {
+ gtk_combo_box_set_active (GTK_COMBO_BOX (combo_box), i);
+ break;
+ }
+ }
+
+ g_signal_handlers_unblock_by_func
+ (combo_box,
+ G_CALLBACK (icon_captions_changed_callback),
+ builder);
+}
+
+static void
+update_icon_captions_from_mateconf (GtkBuilder *builder)
+{
+ char **captions;
+ int i, j;
+
+ captions = eel_preferences_get_string_array (CAJA_PREFERENCES_ICON_VIEW_CAPTIONS);
+ if (captions == NULL)
+ return;
+
+ for (i = 0, j = 0;
+ icon_captions_components[i] != NULL;
+ i++)
+ {
+ char *data;
+
+ if (captions[j])
+ {
+ data = captions[j];
+ ++j;
+ }
+ else
+ {
+ data = "none";
+ }
+
+ update_caption_combo_box (builder,
+ icon_captions_components[i],
+ data);
+ }
+
+ g_strfreev (captions);
+}
+
+static void
+caja_file_management_properties_dialog_setup_icon_caption_page (GtkBuilder *builder)
+{
+ GList *columns;
+ int i;
+ gboolean writable;
+
+ writable = eel_preferences_key_is_writable (CAJA_PREFERENCES_ICON_VIEW_CAPTIONS);
+
+ columns = caja_get_common_columns ();
+
+ for (i = 0; icon_captions_components[i] != NULL; i++)
+ {
+ GtkWidget *combo_box;
+
+ combo_box = GTK_WIDGET (gtk_builder_get_object (builder,
+ icon_captions_components[i]));
+
+ create_icon_caption_combo_box_items (GTK_COMBO_BOX (combo_box), columns);
+ gtk_widget_set_sensitive (combo_box, writable);
+
+ g_signal_connect (combo_box, "changed",
+ G_CALLBACK (icon_captions_changed_callback),
+ builder);
+ }
+
+ caja_column_list_free (columns);
+
+ update_icon_captions_from_mateconf (builder);
+}
+
+static void
+create_date_format_menu (GtkBuilder *builder)
+{
+ GtkWidget *combo_box;
+ gchar *date_string;
+ time_t now_raw;
+ struct tm* now;
+
+ combo_box = GTK_WIDGET (gtk_builder_get_object (builder,
+ CAJA_FILE_MANAGEMENT_PROPERTIES_DATE_FORMAT_WIDGET));
+
+ now_raw = time (NULL);
+ now = localtime (&now_raw);
+
+ date_string = eel_strdup_strftime ("%c", now);
+ gtk_combo_box_append_text (GTK_COMBO_BOX (combo_box), date_string);
+ g_free (date_string);
+
+ date_string = eel_strdup_strftime ("%Y-%m-%d %H:%M:%S", now);
+ gtk_combo_box_append_text (GTK_COMBO_BOX (combo_box), date_string);
+ g_free (date_string);
+
+ date_string = eel_strdup_strftime (_("today at %-I:%M:%S %p"), now);
+ gtk_combo_box_append_text (GTK_COMBO_BOX (combo_box), date_string);
+ g_free (date_string);
+}
+
+static void
+set_columns_from_mateconf (CajaColumnChooser *chooser)
+{
+ char **visible_columns;
+ char **column_order;
+
+ visible_columns = eel_preferences_get_string_array (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS);
+ column_order = eel_preferences_get_string_array (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_COLUMN_ORDER);
+
+ caja_column_chooser_set_settings (CAJA_COLUMN_CHOOSER (chooser),
+ visible_columns,
+ column_order);
+
+ g_strfreev (visible_columns);
+ g_strfreev (column_order);
+}
+
+static void
+use_default_callback (CajaColumnChooser *chooser,
+ gpointer user_data)
+{
+ eel_preferences_unset (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS);
+ eel_preferences_unset (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_COLUMN_ORDER);
+ set_columns_from_mateconf (chooser);
+}
+
+static void
+caja_file_management_properties_dialog_setup_list_column_page (GtkBuilder *builder)
+{
+ GtkWidget *chooser;
+ GtkWidget *box;
+
+ chooser = caja_column_chooser_new (NULL);
+ g_signal_connect (chooser, "changed",
+ G_CALLBACK (columns_changed_callback), chooser);
+ g_signal_connect (chooser, "use_default",
+ G_CALLBACK (use_default_callback), chooser);
+
+ set_columns_from_mateconf (CAJA_COLUMN_CHOOSER (chooser));
+
+ gtk_widget_show (chooser);
+ box = GTK_WIDGET (gtk_builder_get_object (builder, "list_columns_vbox"));
+
+ gtk_box_pack_start (GTK_BOX (box), chooser, TRUE, TRUE, 0);
+}
+
+static void
+caja_file_management_properties_dialog_update_media_sensitivity (GtkBuilder *builder)
+{
+ gtk_widget_set_sensitive (GTK_WIDGET (gtk_builder_get_object (builder, "media_handling_vbox")),
+ ! eel_preferences_get_boolean (CAJA_PREFERENCES_MEDIA_AUTORUN_NEVER));
+}
+
+static void
+other_type_combo_box_changed (GtkComboBox *combo_box, GtkComboBox *action_combo_box)
+{
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ char *x_content_type;
+
+ x_content_type = NULL;
+
+ if (!gtk_combo_box_get_active_iter (combo_box, &iter))
+ {
+ goto out;
+ }
+
+ model = gtk_combo_box_get_model (combo_box);
+ if (model == NULL)
+ {
+ goto out;
+ }
+
+ gtk_tree_model_get (model, &iter,
+ 2, &x_content_type,
+ -1);
+
+ caja_autorun_prepare_combo_box (GTK_WIDGET (action_combo_box),
+ x_content_type,
+ TRUE,
+ TRUE,
+ TRUE,
+ NULL, NULL);
+out:
+ g_free (x_content_type);
+}
+
+
+static void
+caja_file_management_properties_dialog_setup_media_page (GtkBuilder *builder)
+{
+ unsigned int n;
+ GList *l;
+ GList *content_types;
+ GtkWidget *other_type_combo_box;
+ GtkListStore *other_type_list_store;
+ GtkCellRenderer *renderer;
+ GtkTreeIter iter;
+ const char *s[] = {"media_audio_cdda_combobox", "x-content/audio-cdda",
+ "media_video_dvd_combobox", "x-content/video-dvd",
+ "media_music_player_combobox", "x-content/audio-player",
+ "media_dcf_combobox", "x-content/image-dcf",
+ "media_software_combobox", "x-content/software",
+ NULL
+ };
+
+ for (n = 0; s[n*2] != NULL; n++)
+ {
+ caja_autorun_prepare_combo_box (GTK_WIDGET (gtk_builder_get_object (builder, s[n*2])), s[n*2 + 1],
+ TRUE, TRUE, TRUE, NULL, NULL);
+ }
+
+ other_type_combo_box = GTK_WIDGET (gtk_builder_get_object (builder, "media_other_type_combobox"));
+
+ other_type_list_store = gtk_list_store_new (3,
+ GDK_TYPE_PIXBUF,
+ G_TYPE_STRING,
+ G_TYPE_STRING);
+
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (other_type_list_store),
+ 1, GTK_SORT_ASCENDING);
+
+
+ content_types = g_content_types_get_registered ();
+
+ for (l = content_types; l != NULL; l = l->next)
+ {
+ char *content_type = l->data;
+ char *description;
+ GIcon *icon;
+ CajaIconInfo *icon_info;
+ GdkPixbuf *pixbuf;
+ int icon_size;
+
+ if (!g_str_has_prefix (content_type, "x-content/"))
+ continue;
+ for (n = 0; s[n*2] != NULL; n++)
+ {
+ if (strcmp (content_type, s[n*2 + 1]) == 0)
+ {
+ goto skip;
+ }
+ }
+
+ icon_size = caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU);
+
+ description = g_content_type_get_description (content_type);
+ gtk_list_store_append (other_type_list_store, &iter);
+ icon = g_content_type_get_icon (content_type);
+ if (icon != NULL)
+ {
+ icon_info = caja_icon_info_lookup (icon, icon_size);
+ g_object_unref (icon);
+ pixbuf = caja_icon_info_get_pixbuf_nodefault_at_size (icon_info, icon_size);
+ g_object_unref (icon_info);
+ }
+ else
+ {
+ pixbuf = NULL;
+ }
+
+ gtk_list_store_set (other_type_list_store, &iter,
+ 0, pixbuf,
+ 1, description,
+ 2, content_type,
+ -1);
+ if (pixbuf != NULL)
+ g_object_unref (pixbuf);
+ g_free (description);
+skip:
+ ;
+ }
+ g_list_foreach (content_types, (GFunc) g_free, NULL);
+ g_list_free (content_types);
+
+ gtk_combo_box_set_model (GTK_COMBO_BOX (other_type_combo_box), GTK_TREE_MODEL (other_type_list_store));
+
+ renderer = gtk_cell_renderer_pixbuf_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (other_type_combo_box), renderer, FALSE);
+ gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (other_type_combo_box), renderer,
+ "pixbuf", 0,
+ NULL);
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (other_type_combo_box), renderer, TRUE);
+ gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (other_type_combo_box), renderer,
+ "text", 1,
+ NULL);
+
+ g_signal_connect (G_OBJECT (other_type_combo_box),
+ "changed",
+ G_CALLBACK (other_type_combo_box_changed),
+ gtk_builder_get_object (builder, "media_other_action_combobox"));
+
+ gtk_combo_box_set_active (GTK_COMBO_BOX (other_type_combo_box), 0);
+
+ caja_file_management_properties_dialog_update_media_sensitivity (builder);
+}
+
+static void
+caja_file_management_properties_dialog_setup (GtkBuilder *builder, GtkWindow *window)
+{
+ GtkWidget *dialog;
+
+ /* setup mateconf stuff */
+ eel_mateconf_monitor_add ("/apps/caja/icon_view");
+ eel_mateconf_preload_cache ("/apps/caja/icon_view", MATECONF_CLIENT_PRELOAD_ONELEVEL);
+ eel_mateconf_monitor_add ("/apps/caja/compact_view");
+ eel_mateconf_preload_cache ("/apps/caja/compact_view", MATECONF_CLIENT_PRELOAD_ONELEVEL);
+ eel_mateconf_monitor_add ("/apps/caja/list_view");
+ eel_mateconf_preload_cache ("/apps/caja/list_view", MATECONF_CLIENT_PRELOAD_ONELEVEL);
+ eel_mateconf_monitor_add ("/apps/caja/preferences");
+ eel_mateconf_preload_cache ("/apps/caja/preferences", MATECONF_CLIENT_PRELOAD_ONELEVEL);
+ eel_mateconf_monitor_add ("/desktop/mate/file_views");
+ eel_mateconf_preload_cache ("/desktop/mate/file_views", MATECONF_CLIENT_PRELOAD_ONELEVEL);
+
+ /* setup UI */
+ caja_file_management_properties_size_group_create (builder,
+ "views_label",
+ 5);
+ caja_file_management_properties_size_group_create (builder,
+ "captions_label",
+ 3);
+ caja_file_management_properties_size_group_create (builder,
+ "preview_label",
+ 5);
+ create_date_format_menu (builder);
+
+ /* setup preferences */
+ eel_preferences_builder_connect_bool (builder,
+ CAJA_FILE_MANAGEMENT_PROPERTIES_COMPACT_LAYOUT_WIDGET,
+ CAJA_PREFERENCES_ICON_VIEW_DEFAULT_USE_TIGHTER_LAYOUT);
+ eel_preferences_builder_connect_bool (builder,
+ CAJA_FILE_MANAGEMENT_PROPERTIES_LABELS_BESIDE_ICONS_WIDGET,
+ CAJA_PREFERENCES_ICON_VIEW_LABELS_BESIDE_ICONS);
+ eel_preferences_builder_connect_bool (builder,
+ CAJA_FILE_MANAGEMENT_PROPERTIES_ALL_COLUMNS_SAME_WIDTH,
+ CAJA_PREFERENCES_COMPACT_VIEW_ALL_COLUMNS_SAME_WIDTH);
+ eel_preferences_builder_connect_bool (builder,
+ CAJA_FILE_MANAGEMENT_PROPERTIES_FOLDERS_FIRST_WIDGET,
+ CAJA_PREFERENCES_SORT_DIRECTORIES_FIRST);
+ eel_preferences_builder_connect_inverted_bool (builder,
+ CAJA_FILE_MANAGEMENT_PROPERTIES_ALWAYS_USE_BROWSER_WIDGET,
+ CAJA_PREFERENCES_ALWAYS_USE_BROWSER);
+
+ eel_preferences_builder_connect_bool (builder,
+ CAJA_FILE_MANAGEMENT_PROPERTIES_MEDIA_AUTOMOUNT_OPEN,
+ CAJA_PREFERENCES_MEDIA_AUTOMOUNT_OPEN);
+ eel_preferences_builder_connect_bool (builder,
+ CAJA_FILE_MANAGEMENT_PROPERTIES_MEDIA_AUTORUN_NEVER,
+ CAJA_PREFERENCES_MEDIA_AUTORUN_NEVER);
+
+ eel_preferences_builder_connect_bool (builder,
+ CAJA_FILE_MANAGEMENT_PROPERTIES_TRASH_CONFIRM_WIDGET,
+ CAJA_PREFERENCES_CONFIRM_TRASH);
+ eel_preferences_builder_connect_bool (builder,
+ CAJA_FILE_MANAGEMENT_PROPERTIES_TRASH_DELETE_WIDGET,
+ CAJA_PREFERENCES_ENABLE_DELETE);
+ eel_preferences_builder_connect_bool (builder,
+ CAJA_FILE_MANAGEMENT_PROPERTIES_SHOW_HIDDEN_WIDGET,
+ CAJA_PREFERENCES_SHOW_HIDDEN_FILES);
+ eel_preferences_builder_connect_bool_slave (builder,
+ CAJA_FILE_MANAGEMENT_PROPERTIES_SHOW_HIDDEN_WIDGET,
+ CAJA_PREFERENCES_SHOW_BACKUP_FILES);
+ eel_preferences_builder_connect_bool (builder,
+ CAJA_FILE_MANAGEMENT_PROPERTIES_TREE_VIEW_FOLDERS_WIDGET,
+ CAJA_PREFERENCES_TREE_SHOW_ONLY_DIRECTORIES);
+
+ eel_preferences_builder_connect_string_enum_combo_box (builder,
+ CAJA_FILE_MANAGEMENT_PROPERTIES_DEFAULT_VIEW_WIDGET,
+ CAJA_PREFERENCES_DEFAULT_FOLDER_VIEWER,
+ (const char **) default_view_values);
+ eel_preferences_builder_connect_string_enum_combo_box (builder,
+ CAJA_FILE_MANAGEMENT_PROPERTIES_ICON_VIEW_ZOOM_WIDGET,
+ CAJA_PREFERENCES_ICON_VIEW_DEFAULT_ZOOM_LEVEL,
+ (const char **) zoom_values);
+ eel_preferences_builder_connect_string_enum_combo_box (builder,
+ CAJA_FILE_MANAGEMENT_PROPERTIES_COMPACT_VIEW_ZOOM_WIDGET,
+ CAJA_PREFERENCES_COMPACT_VIEW_DEFAULT_ZOOM_LEVEL,
+ (const char **) zoom_values);
+ eel_preferences_builder_connect_string_enum_combo_box (builder,
+ CAJA_FILE_MANAGEMENT_PROPERTIES_LIST_VIEW_ZOOM_WIDGET,
+ CAJA_PREFERENCES_LIST_VIEW_DEFAULT_ZOOM_LEVEL,
+ (const char **) zoom_values);
+ eel_preferences_builder_connect_string_enum_combo_box (builder,
+ CAJA_FILE_MANAGEMENT_PROPERTIES_SORT_ORDER_WIDGET,
+ CAJA_PREFERENCES_ICON_VIEW_DEFAULT_SORT_ORDER,
+ (const char **) sort_order_values);
+ eel_preferences_builder_connect_string_enum_combo_box_slave (builder,
+ CAJA_FILE_MANAGEMENT_PROPERTIES_SORT_ORDER_WIDGET,
+ CAJA_PREFERENCES_LIST_VIEW_DEFAULT_SORT_ORDER);
+ eel_preferences_builder_connect_string_enum_combo_box (builder,
+ CAJA_FILE_MANAGEMENT_PROPERTIES_PREVIEW_TEXT_WIDGET,
+ CAJA_PREFERENCES_SHOW_TEXT_IN_ICONS,
+ (const char **) preview_values);
+ eel_preferences_builder_connect_string_enum_combo_box (builder,
+ CAJA_FILE_MANAGEMENT_PROPERTIES_PREVIEW_IMAGE_WIDGET,
+ CAJA_PREFERENCES_SHOW_IMAGE_FILE_THUMBNAILS,
+ (const char **) preview_values);
+ eel_preferences_builder_connect_string_enum_combo_box (builder,
+ CAJA_FILE_MANAGEMENT_PROPERTIES_PREVIEW_SOUND_WIDGET,
+ CAJA_PREFERENCES_PREVIEW_SOUND,
+ (const char **) preview_values);
+ eel_preferences_builder_connect_string_enum_combo_box (builder,
+ CAJA_FILE_MANAGEMENT_PROPERTIES_PREVIEW_FOLDER_WIDGET,
+ CAJA_PREFERENCES_SHOW_DIRECTORY_ITEM_COUNTS,
+ (const char **) preview_values);
+ eel_preferences_builder_connect_string_enum_combo_box (builder,
+ CAJA_FILE_MANAGEMENT_PROPERTIES_DATE_FORMAT_WIDGET,
+ CAJA_PREFERENCES_DATE_FORMAT,
+ (const char **) date_format_values);
+
+ eel_preferences_builder_connect_string_enum_radio_button (builder,
+ (const char **) click_behavior_components,
+ CAJA_PREFERENCES_CLICK_POLICY,
+ (const char **) click_behavior_values);
+ eel_preferences_builder_connect_string_enum_radio_button (builder,
+ (const char **) executable_text_components,
+ CAJA_PREFERENCES_EXECUTABLE_TEXT_ACTIVATION,
+ (const char **) executable_text_values);
+
+ eel_preferences_builder_connect_uint_enum (builder,
+ CAJA_FILE_MANAGEMENT_PROPERTIES_THUMBNAIL_LIMIT_WIDGET,
+ CAJA_PREFERENCES_IMAGE_FILE_THUMBNAIL_LIMIT,
+ (const guint *) thumbnail_limit_values,
+ G_N_ELEMENTS (thumbnail_limit_values));
+
+ caja_file_management_properties_dialog_setup_icon_caption_page (builder);
+ caja_file_management_properties_dialog_setup_list_column_page (builder);
+ caja_file_management_properties_dialog_setup_media_page (builder);
+
+ eel_preferences_add_callback (CAJA_PREFERENCES_MEDIA_AUTORUN_NEVER,
+ (EelPreferencesCallback ) caja_file_management_properties_dialog_update_media_sensitivity,
+ g_object_ref (builder));
+
+
+ /* UI callbacks */
+ dialog = GTK_WIDGET (gtk_builder_get_object (builder, "file_management_dialog"));
+ g_signal_connect_data (G_OBJECT (dialog), "response",
+ G_CALLBACK (caja_file_management_properties_dialog_response_cb),
+ g_object_ref (builder),
+ (GClosureNotify)g_object_unref,
+ 0);
+
+ gtk_window_set_icon_name (GTK_WINDOW (dialog), "system-file-manager");
+
+ if (window)
+ {
+ gtk_window_set_screen (GTK_WINDOW (dialog), gtk_window_get_screen(window));
+ }
+
+ gtk_widget_show (dialog);
+}
+
+static gboolean
+delete_event_callback (GtkWidget *widget,
+ GdkEventAny *event,
+ gpointer data)
+{
+ void (*response_callback) (GtkDialog *dialog,
+ gint response_id);
+
+ response_callback = data;
+
+ response_callback (GTK_DIALOG (widget), GTK_RESPONSE_CLOSE);
+
+ return TRUE;
+}
+
+void
+caja_file_management_properties_dialog_show (GCallback close_callback, GtkWindow *window)
+{
+ GtkBuilder *builder;
+
+ builder = gtk_builder_new ();
+
+ gtk_builder_add_from_file (builder,
+ UIDIR "/caja-file-management-properties.ui",
+ NULL);
+
+ g_signal_connect (G_OBJECT (gtk_builder_get_object (builder, "file_management_dialog")),
+ "response", close_callback, NULL);
+ g_signal_connect (G_OBJECT (gtk_builder_get_object (builder, "file_management_dialog")),
+ "delete_event", G_CALLBACK (delete_event_callback), close_callback);
+
+ caja_file_management_properties_dialog_setup (builder, window);
+
+ g_object_unref (builder);
+}
diff --git a/src/caja-file-management-properties.h b/src/caja-file-management-properties.h
new file mode 100644
index 00000000..db2bf485
--- /dev/null
+++ b/src/caja-file-management-properties.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-file-management-properties.h - Function to show the caja preference dialog.
+
+ Copyright (C) 2002 Jan Arne Petersen
+
+ 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: Jan Arne Petersen <[email protected]>
+*/
+
+#ifndef CAJA_FILE_MANAGEMENT_PROPERTIES_H
+#define CAJA_FILE_MANAGEMENT_PROPERTIES_H
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+ void caja_file_management_properties_dialog_show (GCallback close_callback, GtkWindow *window);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CAJA_FILE_MANAGEMENT_PROPERTIES_H */
diff --git a/src/caja-file-management-properties.ui b/src/caja-file-management-properties.ui
new file mode 100644
index 00000000..eefd82ac
--- /dev/null
+++ b/src/caja-file-management-properties.ui
@@ -0,0 +1,3080 @@
+<?xml version="1.0"?>
+<!--*- mode: xml -*-->
+<interface>
+ <object class="GtkListStore" id="model1">
+ <columns>
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Icon View</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">List View</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Compact View</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model2">
+ <columns>
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">By Name</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">By Size</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">By Type</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">By Modification Date</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">By Emblems</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model3">
+ <columns>
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">33%</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">50%</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">66%</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">100%</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">150%</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">200%</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">400%</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model4">
+ <columns>
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">33%</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">50%</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">66%</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">100%</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">150%</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">200%</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">400%</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model5">
+ <columns>
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">33%</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">50%</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">66%</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">100%</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">150%</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">200%</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">400%</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model6">
+ <columns>
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Always</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Local Files Only</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Never</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model7">
+ <columns>
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Always</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Local Files Only</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Never</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model8">
+ <columns>
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">100 KB</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">500 KB</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">1 MB</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">3 MB</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">5 MB</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">10 MB</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">100 MB</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">1 GB</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">2 GB</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">4 GB</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model9">
+ <columns>
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Always</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Local Files Only</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Never</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model10">
+ <columns>
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Always</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Local Files Only</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Never</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model11">
+ <columns>
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model12">
+ <columns>
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model13">
+ <columns>
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model14">
+ <columns>
+ <column type="gchararray"/>
+ </columns>
+ </object>
+ <object class="GtkDialog" id="file_management_dialog">
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">File Management Preferences</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_CENTER</property>
+ <property name="modal">False</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="focus_on_map">True</property>
+ <property name="urgency_hint">False</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <object class="GtkButton" id="helpbutton1">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-help</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="closebutton1">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-close</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkNotebook" id="notebook1">
+ <property name="border_width">5</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="show_tabs">True</property>
+ <property name="show_border">True</property>
+ <property name="tab_pos">GTK_POS_TOP</property>
+ <property name="scrollable">False</property>
+ <property name="enable_popup">False</property>
+ <child>
+ <object class="GtkVBox" id="vbox1">
+ <property name="border_width">12</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">18</property>
+ <child>
+ <object class="GtkVBox" id="vbox2">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;Default View&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment2">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <property name="top_padding">0</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">0</property>
+ <child>
+ <object class="GtkVBox" id="vbox14">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkHBox" id="hbox34">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="views_label_0">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">View _new folders using:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">default_view_combobox</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="default_view_combobox">
+ <property name="visible">True</property>
+ <property name="add_tearoffs">False</property>
+ <property name="focus_on_click">True</property>
+ <property name="model">model1</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer1"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox11">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="views_label_1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Arrange items:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">sort_order_combobox</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="sort_order_combobox">
+ <property name="visible">True</property>
+ <property name="add_tearoffs">False</property>
+ <property name="focus_on_click">True</property>
+ <property name="model">model2</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer2"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="sort_folders_first_checkbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Sort _folders before files</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="hidden_files_checkbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Show hidden and _backup files</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox3">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;Icon View Defaults&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <property name="top_padding">0</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">0</property>
+ <child>
+ <object class="GtkVBox" id="vbox16">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkHBox" id="hbox35">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="views_label_2">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Default _zoom level:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">icon_view_zoom_combobox</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="icon_view_zoom_combobox">
+ <property name="visible">True</property>
+ <property name="add_tearoffs">False</property>
+ <property name="focus_on_click">True</property>
+ <property name="model">model3</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer3"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="compact_layout_checkbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Use compact layout</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="labels_beside_icons_checkbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Text beside icons</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;Compact View Defaults&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <property name="top_padding">0</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">0</property>
+ <child>
+ <object class="GtkVBox" id="vbox42">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkHBox" id="hbox">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="views_label_4">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Default zoom level:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">compact_view_zoom_combobox</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="compact_view_zoom_combobox">
+ <property name="visible">True</property>
+ <property name="add_tearoffs">False</property>
+ <property name="focus_on_click">True</property>
+ <property name="model">model4</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer4"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="all_columns_same_width_checkbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">A_ll columns have the same width</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox4">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label6">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;List View Defaults&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment3">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <property name="top_padding">0</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">0</property>
+ <child>
+ <object class="GtkVBox" id="vbox15">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkHBox" id="hbox36">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="views_label_3">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">D_efault zoom level:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">list_view_zoom_combobox</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="list_view_zoom_combobox">
+ <property name="visible">True</property>
+ <property name="add_tearoffs">False</property>
+ <property name="focus_on_click">True</property>
+ <property name="model">model5</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer5"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox24">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label25">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;Tree View Defaults&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment4">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <property name="top_padding">0</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">0</property>
+ <child>
+ <object class="GtkVBox" id="vbox25">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkCheckButton" id="treeview_folders_checkbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Show _only folders</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Views</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox5">
+ <property name="border_width">12</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">18</property>
+ <child>
+ <object class="GtkVBox" id="vbox6">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label10">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;Behavior&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment5">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <property name="top_padding">0</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">0</property>
+ <child>
+ <object class="GtkVBox" id="vbox17">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+ <child>
+ <object class="GtkRadioButton" id="single_click_radiobutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Single click to open items</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="double_click_radiobutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Double click to open items</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">single_click_radiobutton</property>
+ </object>
+ <packing>
+ <property name="padding">6</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label99">
+ <property name="height_request">6</property>
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"/>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="always_use_browser_checkbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Open each _folder in its own window</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox7">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label12">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;Executable Text Files&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment6">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <property name="top_padding">0</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">0</property>
+ <child>
+ <object class="GtkVBox" id="vbox18">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkRadioButton" id="scripts_execute_radiobutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Run executable text files when they are opened</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="scripts_view_radiobutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_View executable text files when they are opened</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">scripts_execute_radiobutton</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="scripts_confirm_radiobutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Ask each time</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">scripts_execute_radiobutton</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox8">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label14">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;Trash&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment7">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <property name="top_padding">0</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">0</property>
+ <child>
+ <object class="GtkVBox" id="vbox19">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkCheckButton" id="trash_confirm_checkbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Ask before _emptying the Trash or deleting files</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="trash_delete_checkbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">I_nclude a Delete command that bypasses Trash</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Behavior</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox26">
+ <property name="border_width">12</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">18</property>
+ <child>
+ <object class="GtkVBox" id="vbox27">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label28">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;Icon Captions&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment8">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <property name="top_padding">0</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">0</property>
+ <child>
+ <object class="GtkVBox" id="vbox28">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label29">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Choose the order of information to appear beneath icon names. More information will appear when zooming in closer.</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">True</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox28">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+ <child>
+ <object class="GtkLabel" id="captions_label_0">
+ <property name="visible">True</property>
+ <property name="label"/>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="captions_0_combobox">
+ <property name="visible">True</property>
+ <property name="add_tearoffs">False</property>
+ <property name="focus_on_click">True</property>
+ <property name="model">model11</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox29">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+ <child>
+ <object class="GtkLabel" id="captions_label_1">
+ <property name="visible">True</property>
+ <property name="label"/>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="captions_1_combobox">
+ <property name="visible">True</property>
+ <property name="add_tearoffs">False</property>
+ <property name="focus_on_click">True</property>
+ <property name="model">model12</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer12"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox30">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+ <child>
+ <object class="GtkLabel" id="captions_label_2">
+ <property name="visible">True</property>
+ <property name="label"/>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="captions_2_combobox">
+ <property name="visible">True</property>
+ <property name="add_tearoffs">False</property>
+ <property name="focus_on_click">True</property>
+ <property name="model">model13</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer13"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox31">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label34">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;Date&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment9">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <property name="top_padding">0</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">0</property>
+ <child>
+ <object class="GtkHBox" id="hbox33">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="label36">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Format:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">date_format_combobox</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="date_format_combobox">
+ <property name="visible">True</property>
+ <property name="add_tearoffs">False</property>
+ <property name="focus_on_click">True</property>
+ <property name="model">model14</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer14"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label24">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Display</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox29">
+ <property name="border_width">12</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">18</property>
+ <child>
+ <object class="GtkVBox" id="vbox30">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label31">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;List Columns&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment21">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <property name="top_padding">0</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">0</property>
+ <child>
+ <object class="GtkVBox" id="list_columns_vbox">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label33">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Choose the order of information to appear in the list view.</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">True</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label30">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">List Columns</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox9">
+ <property name="border_width">12</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">18</property>
+ <child>
+ <object class="GtkVBox" id="vbox10">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label16">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;Text Files&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment10">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <property name="top_padding">0</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">0</property>
+ <child>
+ <object class="GtkVBox" id="vbox20">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkHBox" id="hbox24">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="preview_label_0">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Show te_xt in icons:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">preview_text_combobox</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="preview_text_combobox">
+ <property name="visible">True</property>
+ <property name="add_tearoffs">False</property>
+ <property name="focus_on_click">True</property>
+ <property name="model">model6</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer6"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox11">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label18">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;Other Previewable Files&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment11">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <property name="top_padding">0</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">0</property>
+ <child>
+ <object class="GtkVBox" id="vbox21">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkHBox" id="hbox20">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="preview_label_1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Show _thumbnails:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">preview_image_combobox</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="preview_image_combobox">
+ <property name="visible">True</property>
+ <property name="add_tearoffs">False</property>
+ <property name="focus_on_click">True</property>
+ <property name="model">model7</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer7"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox21">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="preview_label_2">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Only for files smaller than:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">preview_image_size_combobox</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="preview_image_size_combobox">
+ <property name="visible">True</property>
+ <property name="add_tearoffs">False</property>
+ <property name="focus_on_click">True</property>
+ <property name="model">model8</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer8"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox12">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label20">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;Sound Files&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment12">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <property name="top_padding">0</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">0</property>
+ <child>
+ <object class="GtkVBox" id="vbox22">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkHBox" id="hbox22">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="preview_label_3">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Preview _sound files:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">preview_sound_combobox</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="preview_sound_combobox">
+ <property name="visible">True</property>
+ <property name="add_tearoffs">False</property>
+ <property name="focus_on_click">True</property>
+ <property name="model">model9</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer9"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox13">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label22">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;Folders&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment13">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <property name="top_padding">0</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">0</property>
+ <child>
+ <object class="GtkVBox" id="vbox23">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkHBox" id="hbox23">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="preview_label_4">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Count _number of items:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">preview_folder_combobox</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="preview_folder_combobox">
+ <property name="visible">True</property>
+ <property name="add_tearoffs">False</property>
+ <property name="focus_on_click">True</property>
+ <property name="model">model10</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer10"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Preview</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox34">
+ <property name="border_width">12</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkVBox" id="media_handling_vbox">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkVBox" id="vbox44">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label42">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;Media Handling&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment18">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <property name="top_padding">0</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">0</property>
+ <child>
+ <object class="GtkVBox" id="vbox52">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label60">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Choose what happens when inserting media or connecting devices to the system</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">True</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkTable" id="table4">
+ <property name="visible">True</property>
+ <property name="n_rows">5</property>
+ <property name="n_columns">2</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label44">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">CD _Audio:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">media_audio_cdda_combobox</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label50">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_DVD Video:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">media_video_dvd_combobox</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="media_audio_cdda_combobox">
+ <property name="visible">True</property>
+ <property name="add_tearoffs">False</property>
+ <property name="focus_on_click">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="y_options">fill</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="media_video_dvd_combobox">
+ <property name="visible">True</property>
+ <property name="add_tearoffs">False</property>
+ <property name="focus_on_click">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options">fill</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label54">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Music Player:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">media_music_player_combobox</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="media_music_player_combobox">
+ <property name="visible">True</property>
+ <property name="add_tearoffs">False</property>
+ <property name="focus_on_click">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">fill</property>
+ <property name="y_options">fill</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label59">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Photos:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">media_dcf_combobox</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="media_dcf_combobox">
+ <property name="visible">True</property>
+ <property name="add_tearoffs">False</property>
+ <property name="focus_on_click">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">fill</property>
+ <property name="y_options">fill</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label57">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Software:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">media_software_combobox</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="media_software_combobox">
+ <property name="visible">True</property>
+ <property name="add_tearoffs">False</property>
+ <property name="focus_on_click">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="x_options">fill</property>
+ <property name="y_options">fill</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox50">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label61">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;Other Media&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment20">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <property name="top_padding">0</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">0</property>
+ <child>
+ <object class="GtkVBox" id="vbox51">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label65">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Less common media formats can be configured here</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">True</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkTable" id="table5">
+ <property name="visible">True</property>
+ <property name="n_rows">2</property>
+ <property name="n_columns">2</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">6</property>
+ <child>
+ <object class="GtkComboBox" id="media_other_type_combobox">
+ <property name="visible">True</property>
+ <property name="add_tearoffs">False</property>
+ <property name="focus_on_click">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="y_options">fill</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label64">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Acti_on:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">media_other_action_combobox</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="media_other_action_combobox">
+ <property name="visible">True</property>
+ <property name="add_tearoffs">False</property>
+ <property name="focus_on_click">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options">fill</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label63">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Type:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">media_other_type_combobox</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="media_autorun_never_checkbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Never prompt or start programs on media insertion</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="media_automount_open_checkbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">B_rowse media when inserted</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label38">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Media</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-11">helpbutton1</action-widget>
+ <action-widget response="-7">closebutton1</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/src/caja-history-sidebar.c b/src/caja-history-sidebar.c
new file mode 100644
index 00000000..3f256ee9
--- /dev/null
+++ b/src/caja-history-sidebar.c
@@ -0,0 +1,423 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 1999, 2000 Red Hat, Inc.
+ * Copyright (C) 2000, 2001 Eazel, Inc.
+ *
+ * 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
+ * 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: Elliot Lee <[email protected]>
+ * Darin Adler <[email protected]>
+ *
+ */
+
+#include <config.h>
+
+#include <eel/eel-debug.h>
+#include <eel/eel-gtk-extensions.h>
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-preferences.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <libcaja-private/caja-bookmark.h>
+#include <libcaja-private/caja-global-preferences.h>
+#include <libcaja-private/caja-sidebar-provider.h>
+#include <libcaja-private/caja-module.h>
+#include <libcaja-private/caja-signaller.h>
+#include <libcaja-private/caja-window-info.h>
+#include <libcaja-private/caja-window-slot-info.h>
+
+#include "caja-history-sidebar.h"
+
+#define CAJA_HISTORY_SIDEBAR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_HISTORY_SIDEBAR, CajaHistorySidebarClass))
+#define CAJA_IS_HISTORY_SIDEBAR(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_HISTORY_SIDEBAR))
+#define CAJA_IS_HISTORY_SIDEBAR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_HISTORY_SIDEBAR))
+
+typedef struct
+{
+ GtkScrolledWindowClass parent;
+} CajaHistorySidebarClass;
+
+typedef struct
+{
+ GObject parent;
+} CajaHistorySidebarProvider;
+
+typedef struct
+{
+ GObjectClass parent;
+} CajaHistorySidebarProviderClass;
+
+
+enum
+{
+ HISTORY_SIDEBAR_COLUMN_ICON,
+ HISTORY_SIDEBAR_COLUMN_NAME,
+ HISTORY_SIDEBAR_COLUMN_BOOKMARK,
+ HISTORY_SIDEBAR_COLUMN_COUNT
+};
+
+static void caja_history_sidebar_iface_init (CajaSidebarIface *iface);
+static void sidebar_provider_iface_init (CajaSidebarProviderIface *iface);
+static GType caja_history_sidebar_provider_get_type (void);
+static void caja_history_sidebar_style_set (GtkWidget *widget,
+ GtkStyle *previous_style);
+
+G_DEFINE_TYPE_WITH_CODE (CajaHistorySidebar, caja_history_sidebar, GTK_TYPE_SCROLLED_WINDOW,
+ G_IMPLEMENT_INTERFACE (CAJA_TYPE_SIDEBAR,
+ caja_history_sidebar_iface_init));
+
+G_DEFINE_TYPE_WITH_CODE (CajaHistorySidebarProvider, caja_history_sidebar_provider, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (CAJA_TYPE_SIDEBAR_PROVIDER,
+ sidebar_provider_iface_init));
+
+static void
+update_history (CajaHistorySidebar *sidebar)
+{
+ GtkListStore *store;
+ GtkTreeSelection *selection;
+ CajaBookmark *bookmark;
+ GdkPixbuf *pixbuf;
+ GtkTreeIter iter;
+ char *name;
+ GList *l, *history;
+
+ store = GTK_LIST_STORE (gtk_tree_view_get_model (sidebar->tree_view));
+
+ gtk_list_store_clear (store);
+
+ history = caja_window_info_get_history (sidebar->window);
+ for (l = history; l != NULL; l = l->next)
+ {
+ bookmark = caja_bookmark_copy (l->data);
+
+ pixbuf = caja_bookmark_get_pixbuf (bookmark, GTK_ICON_SIZE_MENU);
+ name = caja_bookmark_get_name (bookmark);
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ HISTORY_SIDEBAR_COLUMN_ICON, pixbuf,
+ HISTORY_SIDEBAR_COLUMN_NAME, name,
+ HISTORY_SIDEBAR_COLUMN_BOOKMARK, bookmark,
+ -1);
+ g_object_unref (bookmark);
+
+ if (pixbuf != NULL)
+ {
+ g_object_unref (pixbuf);
+ }
+ g_free (name);
+ }
+ eel_g_object_list_free (history);
+
+ selection = GTK_TREE_SELECTION (gtk_tree_view_get_selection (sidebar->tree_view));
+
+ if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter))
+ {
+ gtk_tree_selection_select_iter (selection, &iter);
+ }
+}
+
+static void
+history_changed_callback (GObject *signaller,
+ CajaHistorySidebar *sidebar)
+{
+ update_history (sidebar);
+}
+
+static void
+open_selected_item (CajaHistorySidebar *sidebar,
+ GtkTreePath *path,
+ CajaWindowOpenFlags flags)
+{
+ CajaWindowSlotInfo *slot;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ CajaBookmark *bookmark;
+ GFile *location;
+
+ model = gtk_tree_view_get_model (sidebar->tree_view);
+
+ if (!gtk_tree_model_get_iter (model, &iter, path))
+ {
+ return;
+ }
+
+ gtk_tree_model_get
+ (model, &iter, HISTORY_SIDEBAR_COLUMN_BOOKMARK, &bookmark, -1);
+
+ /* Navigate to the clicked location. */
+ location = caja_bookmark_get_location (CAJA_BOOKMARK (bookmark));
+ slot = caja_window_info_get_active_slot (sidebar->window);
+ caja_window_slot_info_open_location
+ (slot,
+ location, CAJA_WINDOW_OPEN_ACCORDING_TO_MODE,
+ flags, NULL);
+ g_object_unref (location);
+}
+
+static void
+row_activated_callback (GtkTreeView *tree_view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ gpointer user_data)
+{
+ CajaHistorySidebar *sidebar;
+
+ sidebar = CAJA_HISTORY_SIDEBAR (user_data);
+ g_assert (sidebar->tree_view == tree_view);
+
+ open_selected_item (sidebar, path, 0);
+}
+
+static gboolean
+button_press_event_callback (GtkWidget *widget,
+ GdkEventButton *event,
+ gpointer user_data)
+{
+ if (event->button == 2 && event->type == GDK_BUTTON_PRESS)
+ {
+ /* Open new tab on middle click. */
+ CajaHistorySidebar *sidebar;
+ GtkTreePath *path;
+
+ sidebar = CAJA_HISTORY_SIDEBAR (user_data);
+ g_assert (sidebar->tree_view == GTK_TREE_VIEW (widget));
+
+ if (gtk_tree_view_get_path_at_pos (sidebar->tree_view,
+ event->x, event->y,
+ &path, NULL, NULL, NULL))
+ {
+ open_selected_item (sidebar,
+ path,
+ CAJA_WINDOW_OPEN_FLAG_NEW_TAB);
+ gtk_tree_path_free (path);
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+update_click_policy (CajaHistorySidebar *sidebar)
+{
+ int policy;
+
+ policy = eel_preferences_get_enum (CAJA_PREFERENCES_CLICK_POLICY);
+
+ eel_gtk_tree_view_set_activate_on_single_click
+ (sidebar->tree_view, policy == CAJA_CLICK_POLICY_SINGLE);
+}
+
+static void
+click_policy_changed_callback (gpointer user_data)
+{
+ CajaHistorySidebar *sidebar;
+
+ sidebar = CAJA_HISTORY_SIDEBAR (user_data);
+
+ update_click_policy (sidebar);
+}
+
+static void
+caja_history_sidebar_init (CajaHistorySidebar *sidebar)
+{
+ GtkTreeView *tree_view;
+ GtkTreeViewColumn *col;
+ GtkCellRenderer *cell;
+ GtkListStore *store;
+ GtkTreeSelection *selection;
+
+ tree_view = GTK_TREE_VIEW (gtk_tree_view_new ());
+ gtk_tree_view_set_headers_visible (tree_view, FALSE);
+ gtk_widget_show (GTK_WIDGET (tree_view));
+
+ col = GTK_TREE_VIEW_COLUMN (gtk_tree_view_column_new ());
+
+ cell = gtk_cell_renderer_pixbuf_new ();
+ gtk_tree_view_column_pack_start (col, cell, FALSE);
+ gtk_tree_view_column_set_attributes (col, cell,
+ "pixbuf", HISTORY_SIDEBAR_COLUMN_ICON,
+ NULL);
+
+ cell = gtk_cell_renderer_text_new ();
+ gtk_tree_view_column_pack_start (col, cell, TRUE);
+ gtk_tree_view_column_set_attributes (col, cell,
+ "text", HISTORY_SIDEBAR_COLUMN_NAME,
+ NULL);
+
+ gtk_tree_view_column_set_fixed_width (col, CAJA_ICON_SIZE_SMALLER);
+ gtk_tree_view_append_column (tree_view, col);
+
+ store = gtk_list_store_new (HISTORY_SIDEBAR_COLUMN_COUNT,
+ GDK_TYPE_PIXBUF,
+ G_TYPE_STRING,
+ CAJA_TYPE_BOOKMARK);
+
+ gtk_tree_view_set_model (tree_view, GTK_TREE_MODEL (store));
+ g_object_unref (store);
+
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sidebar),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_hadjustment (GTK_SCROLLED_WINDOW (sidebar), NULL);
+ gtk_scrolled_window_set_vadjustment (GTK_SCROLLED_WINDOW (sidebar), NULL);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sidebar), GTK_SHADOW_IN);
+ gtk_container_add (GTK_CONTAINER (sidebar), GTK_WIDGET (tree_view));
+ gtk_widget_show (GTK_WIDGET (sidebar));
+
+ sidebar->tree_view = tree_view;
+
+ selection = gtk_tree_view_get_selection (tree_view);
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
+
+ g_signal_connect_object
+ (tree_view, "row_activated",
+ G_CALLBACK (row_activated_callback), sidebar, 0);
+
+ g_signal_connect_object (caja_signaller_get_current (),
+ "history_list_changed",
+ G_CALLBACK (history_changed_callback), sidebar, 0);
+
+ g_signal_connect (tree_view, "button-press-event",
+ G_CALLBACK (button_press_event_callback), sidebar);
+
+ eel_preferences_add_callback (CAJA_PREFERENCES_CLICK_POLICY,
+ click_policy_changed_callback,
+ sidebar);
+ update_click_policy (sidebar);
+}
+
+static void
+caja_history_sidebar_finalize (GObject *object)
+{
+ CajaHistorySidebar *sidebar;
+
+ sidebar = CAJA_HISTORY_SIDEBAR (object);
+
+ eel_preferences_remove_callback (CAJA_PREFERENCES_CLICK_POLICY,
+ click_policy_changed_callback,
+ sidebar);
+
+ G_OBJECT_CLASS (caja_history_sidebar_parent_class)->finalize (object);
+}
+
+static void
+caja_history_sidebar_class_init (CajaHistorySidebarClass *class)
+{
+ G_OBJECT_CLASS (class)->finalize = caja_history_sidebar_finalize;
+
+ GTK_WIDGET_CLASS (class)->style_set = caja_history_sidebar_style_set;
+}
+
+static const char *
+caja_history_sidebar_get_sidebar_id (CajaSidebar *sidebar)
+{
+ return CAJA_HISTORY_SIDEBAR_ID;
+}
+
+static char *
+caja_history_sidebar_get_tab_label (CajaSidebar *sidebar)
+{
+ return g_strdup (_("History"));
+}
+
+static char *
+caja_history_sidebar_get_tab_tooltip (CajaSidebar *sidebar)
+{
+ return g_strdup (_("Show History"));
+}
+
+static GdkPixbuf *
+caja_history_sidebar_get_tab_icon (CajaSidebar *sidebar)
+{
+ return NULL;
+}
+
+static void
+caja_history_sidebar_is_visible_changed (CajaSidebar *sidebar,
+ gboolean is_visible)
+{
+ /* Do nothing */
+}
+
+static void
+caja_history_sidebar_iface_init (CajaSidebarIface *iface)
+{
+ iface->get_sidebar_id = caja_history_sidebar_get_sidebar_id;
+ iface->get_tab_label = caja_history_sidebar_get_tab_label;
+ iface->get_tab_tooltip = caja_history_sidebar_get_tab_tooltip;
+ iface->get_tab_icon = caja_history_sidebar_get_tab_icon;
+ iface->is_visible_changed = caja_history_sidebar_is_visible_changed;
+}
+
+static void
+caja_history_sidebar_set_parent_window (CajaHistorySidebar *sidebar,
+ CajaWindowInfo *window)
+{
+ sidebar->window = window;
+ update_history (sidebar);
+}
+
+static void
+caja_history_sidebar_style_set (GtkWidget *widget,
+ GtkStyle *previous_style)
+{
+ CajaHistorySidebar *sidebar;
+
+ sidebar = CAJA_HISTORY_SIDEBAR (widget);
+
+ update_history (sidebar);
+}
+
+static CajaSidebar *
+caja_history_sidebar_create (CajaSidebarProvider *provider,
+ CajaWindowInfo *window)
+{
+ CajaHistorySidebar *sidebar;
+
+ sidebar = g_object_new (caja_history_sidebar_get_type (), NULL);
+ caja_history_sidebar_set_parent_window (sidebar, window);
+ g_object_ref_sink (sidebar);
+
+ return CAJA_SIDEBAR (sidebar);
+}
+
+static void
+sidebar_provider_iface_init (CajaSidebarProviderIface *iface)
+{
+ iface->create = caja_history_sidebar_create;
+}
+
+static void
+caja_history_sidebar_provider_init (CajaHistorySidebarProvider *sidebar)
+{
+}
+
+static void
+caja_history_sidebar_provider_class_init (CajaHistorySidebarProviderClass *class)
+{
+}
+
+void
+caja_history_sidebar_register (void)
+{
+ caja_module_add_type (caja_history_sidebar_provider_get_type ());
+}
+
diff --git a/src/caja-history-sidebar.h b/src/caja-history-sidebar.h
new file mode 100644
index 00000000..4bb8109d
--- /dev/null
+++ b/src/caja-history-sidebar.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 1999, 2000 Red Hat, Inc.
+ * Copyright (C) 2000, 2001 Eazel, Inc.
+ *
+ * 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
+ * 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: Elliot Lee <[email protected]>
+ * Darin Adler <[email protected]>
+ *
+ */
+#ifndef _CAJA_HISTORY_SIDEBAR_H
+#define _CAJA_HISTORY_SIDEBAR_H
+
+#include <gtk/gtk.h>
+#include <libcaja-private/caja-view.h>
+#include <libcaja-private/caja-window-info.h>
+
+#define CAJA_HISTORY_SIDEBAR_ID "CajaHistorySidebar"
+
+#define CAJA_TYPE_HISTORY_SIDEBAR caja_history_sidebar_get_type()
+#define CAJA_HISTORY_SIDEBAR(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_HISTORY_SIDEBAR, CajaHistorySidebar))
+
+typedef struct
+{
+ GtkScrolledWindow parent;
+ GtkTreeView *tree_view;
+ CajaWindowInfo *window;
+} CajaHistorySidebar;
+
+GType caja_history_sidebar_get_type (void);
+void caja_history_sidebar_register (void);
+
+#endif
diff --git a/src/caja-image-properties-page.c b/src/caja-image-properties-page.c
new file mode 100644
index 00000000..119f262b
--- /dev/null
+++ b/src/caja-image-properties-page.c
@@ -0,0 +1,745 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Copyright (C) 2004 Red Hat, Inc
+ * Copyright (c) 2007 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Alexander Larsson <[email protected]>
+ * XMP support by Hubert Figuiere <[email protected]>
+ */
+
+#include <config.h>
+#include "caja-image-properties-page.h"
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <eel/eel-vfs-extensions.h>
+#include <libcaja-extension/caja-property-page-provider.h>
+#include <libcaja-private/caja-module.h>
+#include <string.h>
+
+#ifdef HAVE_EXIF
+#include <libexif/exif-data.h>
+#include <libexif/exif-ifd.h>
+#include <libexif/exif-loader.h>
+#endif
+#ifdef HAVE_EXEMPI
+#include <exempi/xmp.h>
+#include <exempi/xmpconsts.h>
+#endif
+
+#define LOAD_BUFFER_SIZE 8192
+
+struct CajaImagePropertiesPageDetails
+{
+ GCancellable *cancellable;
+ GtkWidget *vbox;
+ GtkWidget *loading_label;
+ GdkPixbufLoader *loader;
+ gboolean got_size;
+ gboolean pixbuf_still_loading;
+ char buffer[LOAD_BUFFER_SIZE];
+ int width;
+ int height;
+#ifdef HAVE_EXIF
+ ExifLoader *exifldr;
+#endif /*HAVE_EXIF*/
+#ifdef HAVE_EXEMPI
+ XmpPtr xmp;
+#endif
+};
+
+#ifdef HAVE_EXIF
+struct ExifAttribute
+{
+ ExifTag tag;
+ char *value;
+ gboolean found;
+};
+#endif /*HAVE_EXIF*/
+
+enum
+{
+ PROP_URI
+};
+
+typedef struct
+{
+ GObject parent;
+} CajaImagePropertiesPageProvider;
+
+typedef struct
+{
+ GObjectClass parent;
+} CajaImagePropertiesPageProviderClass;
+
+
+static GType caja_image_properties_page_provider_get_type (void);
+static void property_page_provider_iface_init (CajaPropertyPageProviderIface *iface);
+
+
+G_DEFINE_TYPE (CajaImagePropertiesPage, caja_image_properties_page, GTK_TYPE_VBOX);
+
+G_DEFINE_TYPE_WITH_CODE (CajaImagePropertiesPageProvider, caja_image_properties_page_provider, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (CAJA_TYPE_PROPERTY_PAGE_PROVIDER,
+ property_page_provider_iface_init));
+
+static void
+caja_image_properties_page_finalize (GObject *object)
+{
+ CajaImagePropertiesPage *page;
+
+ page = CAJA_IMAGE_PROPERTIES_PAGE (object);
+
+ if (page->details->cancellable)
+ {
+ g_cancellable_cancel (page->details->cancellable);
+ g_object_unref (page->details->cancellable);
+ page->details->cancellable = NULL;
+ }
+
+ G_OBJECT_CLASS (caja_image_properties_page_parent_class)->finalize (object);
+}
+
+static void
+file_close_callback (GObject *object,
+ GAsyncResult *res,
+ gpointer data)
+{
+ CajaImagePropertiesPage *page;
+ GInputStream *stream;
+
+ page = CAJA_IMAGE_PROPERTIES_PAGE (data);
+ stream = G_INPUT_STREAM (object);
+
+ g_input_stream_close_finish (stream, res, NULL);
+
+ g_object_unref (page->details->cancellable);
+ page->details->cancellable = NULL;
+}
+
+static GtkWidget *
+append_label (GtkWidget *vbox,
+ const char *str)
+{
+ GtkWidget *label;
+
+ label = gtk_label_new (NULL);
+ gtk_label_set_markup (GTK_LABEL (label), str);
+ gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
+ gtk_label_set_selectable (GTK_LABEL (label), TRUE);
+
+ /* setting can_focus to FALSE will allow to make the label
+ * selectable but without the cursor showing.
+ */
+ gtk_widget_set_can_focus (label, FALSE);
+
+ gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+
+ return label;
+}
+
+static GtkWidget *
+append_label_take_str (GtkWidget *vbox,
+ char *str)
+{
+ GtkWidget *retval;
+
+ retval = append_label (vbox, str);
+ g_free (str);
+
+ return retval;
+}
+
+#ifdef HAVE_EXIF
+static char *
+exif_string_to_utf8 (const char *exif_str)
+{
+ char *utf8_str;
+
+ if (g_utf8_validate (exif_str, -1, NULL))
+ {
+ return g_strdup (exif_str);
+ }
+
+ utf8_str = g_locale_to_utf8 (exif_str, -1, NULL, NULL, NULL);
+ if (utf8_str != NULL)
+ {
+ return utf8_str;
+ }
+
+ return eel_make_valid_utf8 (exif_str);
+}
+
+static void
+exif_content_callback (ExifContent *content, gpointer data)
+{
+ struct ExifAttribute *attribute;
+#ifndef HAVE_OLD_EXIF
+ char b[1024];
+#endif
+
+ attribute = (struct ExifAttribute *)data;
+ if (attribute->found)
+ {
+ return;
+ }
+
+#ifdef HAVE_OLD_EXIF
+ attribute->value = g_strdup (exif_content_get_value (content, attribute->tag));
+#else
+ attribute->value = g_strdup (exif_content_get_value (content, attribute->tag, b, sizeof(b)));
+#endif
+ if (attribute->value != NULL)
+ {
+ attribute->found = TRUE;
+ }
+}
+
+static char *
+exifdata_get_tag_name_utf8 (ExifTag tag)
+{
+ return exif_string_to_utf8 (exif_tag_get_name (tag));
+}
+
+static char *
+exifdata_get_tag_value_utf8 (ExifData *data, ExifTag tag)
+{
+ struct ExifAttribute attribute;
+ char *utf8_value;
+
+ attribute.tag = tag;
+ attribute.value = NULL;
+ attribute.found = FALSE;
+
+ exif_data_foreach_content (data, exif_content_callback, &attribute);
+
+ if (attribute.found)
+ {
+ utf8_value = exif_string_to_utf8 (attribute.value);
+ g_free (attribute.value);
+ }
+ else
+ {
+ utf8_value = NULL;
+ }
+
+ return utf8_value;
+}
+
+static gboolean
+append_tag_value_pair (CajaImagePropertiesPage *page,
+ ExifData *data,
+ ExifTag tag,
+ char *description)
+{
+ char *utf_attribute;
+ char *utf_value;
+
+ utf_attribute = exifdata_get_tag_name_utf8 (tag);
+ utf_value = exifdata_get_tag_value_utf8 (data, tag);
+
+ if ((utf_attribute == NULL) || (utf_value == NULL))
+ {
+ g_free (utf_attribute);
+ g_free (utf_value);
+ return FALSE;
+ }
+
+ append_label_take_str
+ (page->details->vbox,
+ g_strdup_printf ("<b>%s:</b> %s",
+ description ? description : utf_attribute,
+ utf_value));
+
+ g_free (utf_attribute);
+ g_free (utf_value);
+ return TRUE;
+}
+
+static void
+append_exifdata_string (ExifData *exifdata, CajaImagePropertiesPage *page)
+{
+ if (exifdata && exifdata->ifd[0] && exifdata->ifd[0]->count)
+ {
+ append_tag_value_pair (page, exifdata, EXIF_TAG_MAKE, _("Camera Brand"));
+ append_tag_value_pair (page, exifdata, EXIF_TAG_MODEL, _("Camera Model"));
+
+ /* Choose which date to show in order of relevance */
+ if (!append_tag_value_pair (page, exifdata, EXIF_TAG_DATE_TIME_ORIGINAL, _("Date Taken")))
+ {
+ if (!append_tag_value_pair (page, exifdata, EXIF_TAG_DATE_TIME_DIGITIZED, _("Date Digitized")))
+ {
+ append_tag_value_pair (page, exifdata, EXIF_TAG_DATE_TIME, _("Date Modified"));
+ }
+ }
+
+ append_tag_value_pair (page, exifdata, EXIF_TAG_EXPOSURE_TIME, _("Exposure Time"));
+ append_tag_value_pair (page, exifdata, EXIF_TAG_APERTURE_VALUE, _("Aperture Value"));
+ append_tag_value_pair (page, exifdata, EXIF_TAG_ISO_SPEED_RATINGS, _("ISO Speed Rating"));
+ append_tag_value_pair (page, exifdata, EXIF_TAG_FLASH,_("Flash Fired"));
+ append_tag_value_pair (page, exifdata, EXIF_TAG_METERING_MODE, _("Metering Mode"));
+ append_tag_value_pair (page, exifdata, EXIF_TAG_EXPOSURE_PROGRAM, _("Exposure Program"));
+ append_tag_value_pair (page, exifdata, EXIF_TAG_FOCAL_LENGTH,_("Focal Length"));
+ append_tag_value_pair (page, exifdata, EXIF_TAG_SOFTWARE, _("Software"));
+ }
+}
+#endif /*HAVE_EXIF*/
+
+#ifdef HAVE_EXEMPI
+static void
+append_xmp_value_pair (CajaImagePropertiesPage *page,
+ XmpPtr xmp,
+ const char *ns,
+ const char *propname,
+ char *descr)
+{
+ uint32_t options;
+ XmpStringPtr value;
+
+ value = xmp_string_new();
+#ifdef HAVE_EXEMPI_NEW_API
+ if (xmp_get_property (xmp, ns, propname, value, &options))
+ {
+#else
+ if (xmp_get_property_and_bits (xmp, ns, propname, value, &options))
+ {
+#endif
+ if (XMP_IS_PROP_SIMPLE (options))
+ {
+ append_label_take_str
+ (page->details->vbox,
+ g_strdup_printf ("<b>%s:</b> %s",
+ descr, xmp_string_cstr (value)));
+ }
+ else if (XMP_IS_PROP_ARRAY (options))
+ {
+ XmpIteratorPtr iter;
+
+ iter = xmp_iterator_new (xmp, ns, propname, XMP_ITER_JUSTLEAFNODES);
+ if (iter)
+ {
+ GString *str;
+ gboolean first = TRUE;
+
+ str = g_string_new (NULL);
+
+ g_string_append_printf (str, "<b>%s:</b> ",
+ descr);
+ while (xmp_iterator_next (iter, NULL, NULL, value, &options)
+ && !XMP_IS_PROP_QUALIFIER(options))
+ {
+ if (!first)
+ {
+ g_string_append_printf (str, ", ");
+ }
+ else
+ {
+ first = FALSE;
+ }
+ g_string_append_printf (str,
+ "%s",
+ xmp_string_cstr(value));
+ }
+ xmp_iterator_free(iter);
+ append_label_take_str (page->details->vbox,
+ g_string_free (str, FALSE));
+ }
+ }
+ }
+ xmp_string_free(value);
+}
+
+static void
+append_xmpdata_string (XmpPtr xmp, CajaImagePropertiesPage *page)
+{
+ if (xmp != NULL)
+ {
+ append_xmp_value_pair (page, xmp, NS_IPTC4XMP, "Location", _("Location"));
+ append_xmp_value_pair (page, xmp, NS_DC, "description", _("Description"));
+ append_xmp_value_pair (page, xmp, NS_DC, "subject", _("Keywords"));
+ append_xmp_value_pair (page, xmp, NS_DC, "creator", _("Creator"));
+ append_xmp_value_pair (page, xmp, NS_DC, "rights", _("Copyright"));
+ append_xmp_value_pair (page, xmp, NS_XAP,"Rating", _("Rating"));
+ /* TODO add CC licenses */
+ }
+}
+#endif
+
+static void
+load_finished (CajaImagePropertiesPage *page)
+{
+ GdkPixbufFormat *format;
+ char *name, *desc;
+
+ gtk_widget_destroy (page->details->loading_label);
+
+ if (page->details->got_size)
+ {
+#ifdef HAVE_EXIF
+ ExifData *exif_data;
+#endif
+
+ format = gdk_pixbuf_loader_get_format (page->details->loader);
+
+ name = gdk_pixbuf_format_get_name (format);
+ desc = gdk_pixbuf_format_get_description (format);
+ append_label_take_str
+ (page->details->vbox,
+ g_strdup_printf ("<b>%s</b> %s (%s)",
+ _("Image Type:"), name, desc));
+ append_label_take_str
+ (page->details->vbox,
+ g_strdup_printf (ngettext ("<b>Width:</b> %d pixel",
+ "<b>Width:</b> %d pixels",
+ page->details->width),
+ page->details->width));
+ append_label_take_str
+ (page->details->vbox,
+ g_strdup_printf (ngettext ("<b>Height:</b> %d pixel",
+ "<b>Height:</b> %d pixels",
+ page->details->height),
+ page->details->height));
+ g_free (name);
+ g_free (desc);
+
+#ifdef HAVE_EXIF
+ exif_data = exif_loader_get_data (page->details->exifldr);
+ append_exifdata_string (exif_data, page);
+ exif_data_unref (exif_data);
+#endif /*HAVE_EXIF*/
+#ifdef HAVE_EXEMPI
+ append_xmpdata_string (page->details->xmp, page);
+#endif /*HAVE EXEMPI*/
+ }
+ else
+ {
+ append_label (page->details->vbox,
+ _("Failed to load image information"));
+ }
+
+ if (page->details->loader != NULL)
+ {
+ gdk_pixbuf_loader_close (page->details->loader, NULL);
+ g_object_unref (page->details->loader);
+ page->details->loader = NULL;
+ }
+#ifdef HAVE_EXIF
+ if (page->details->exifldr != NULL)
+ {
+ exif_loader_unref (page->details->exifldr);
+ page->details->exifldr = NULL;
+ }
+#endif /*HAVE_EXIF*/
+#ifdef HAVE_EXEMPI
+ if (page->details->xmp != NULL)
+ {
+ xmp_free(page->details->xmp);
+ page->details->xmp = NULL;
+ }
+#endif
+}
+
+static void
+file_read_callback (GObject *object,
+ GAsyncResult *res,
+ gpointer data)
+{
+ CajaImagePropertiesPage *page;
+ GInputStream *stream;
+ gssize count_read;
+ GError *error;
+ int exif_still_loading;
+ gboolean done_reading;
+
+ page = CAJA_IMAGE_PROPERTIES_PAGE (data);
+ stream = G_INPUT_STREAM (object);
+
+ error = NULL;
+ done_reading = FALSE;
+ count_read = g_input_stream_read_finish (stream, res, &error);
+
+ if (count_read > 0)
+ {
+
+ g_assert (count_read <= sizeof(page->details->buffer));
+
+#ifdef HAVE_EXIF
+ exif_still_loading = exif_loader_write (page->details->exifldr,
+ page->details->buffer,
+ count_read);
+#else
+ exif_still_loading = 0;
+#endif
+
+ if (page->details->pixbuf_still_loading)
+ {
+ if (!gdk_pixbuf_loader_write (page->details->loader,
+ page->details->buffer,
+ count_read,
+ NULL))
+ {
+ page->details->pixbuf_still_loading = FALSE;
+ }
+ }
+
+ if (page->details->pixbuf_still_loading ||
+ (exif_still_loading == 1))
+ {
+ g_input_stream_read_async (G_INPUT_STREAM (stream),
+ page->details->buffer,
+ sizeof (page->details->buffer),
+ 0,
+ page->details->cancellable,
+ file_read_callback,
+ page);
+ }
+ else
+ {
+ done_reading = TRUE;
+ }
+ }
+ else
+ {
+ /* either EOF, cancelled or an error occurred */
+ done_reading = TRUE;
+ }
+
+ if (done_reading)
+ {
+ load_finished (page);
+ g_input_stream_close_async (stream,
+ 0,
+ page->details->cancellable,
+ file_close_callback,
+ page);
+ }
+}
+
+static void
+size_prepared_callback (GdkPixbufLoader *loader,
+ int width,
+ int height,
+ gpointer callback_data)
+{
+ CajaImagePropertiesPage *page;
+
+ page = CAJA_IMAGE_PROPERTIES_PAGE (callback_data);
+
+ page->details->height = height;
+ page->details->width = width;
+ page->details->got_size = TRUE;
+ page->details->pixbuf_still_loading = FALSE;
+}
+
+static void
+file_open_callback (GObject *object,
+ GAsyncResult *res,
+ gpointer data)
+{
+ CajaImagePropertiesPage *page;
+ GFile *file;
+ GFileInputStream *stream;
+ GError *error;
+
+ page = CAJA_IMAGE_PROPERTIES_PAGE (data);
+ file = G_FILE (object);
+
+ error = NULL;
+ stream = g_file_read_finish (file, res, &error);
+ if (stream)
+ {
+ page->details->loader = gdk_pixbuf_loader_new ();
+ page->details->pixbuf_still_loading = TRUE;
+ page->details->width = 0;
+ page->details->height = 0;
+#ifdef HAVE_EXIF
+ page->details->exifldr = exif_loader_new ();
+#endif /*HAVE_EXIF*/
+
+ g_signal_connect (page->details->loader,
+ "size_prepared",
+ G_CALLBACK (size_prepared_callback),
+ page);
+
+ g_input_stream_read_async (G_INPUT_STREAM (stream),
+ page->details->buffer,
+ sizeof (page->details->buffer),
+ 0,
+ page->details->cancellable,
+ file_read_callback,
+ page);
+
+ g_object_unref (stream);
+ }
+}
+
+static void
+load_location (CajaImagePropertiesPage *page,
+ const char *location)
+{
+ GFile *file;
+
+ g_assert (CAJA_IS_IMAGE_PROPERTIES_PAGE (page));
+ g_assert (location != NULL);
+
+ page->details->cancellable = g_cancellable_new ();
+ file = g_file_new_for_uri (location);
+
+#ifdef HAVE_EXEMPI
+ {
+ /* Current Exempi does not support setting custom IO to be able to use Mate-vfs */
+ /* So it will only work with local files. Future version might remove this limitation */
+ XmpFilePtr xf;
+ char *localname;
+
+ localname = g_filename_from_uri (location, NULL, NULL);
+ if (localname)
+ {
+ xf = xmp_files_open_new (localname, 0);
+ page->details->xmp = xmp_files_get_new_xmp (xf); /* only load when loading */
+ xmp_files_close (xf, 0);
+ g_free (localname);
+ }
+ else
+ {
+ page->details->xmp = NULL;
+ }
+ }
+#endif /*HAVE_EXEMPI*/
+
+ g_file_read_async (file,
+ 0,
+ page->details->cancellable,
+ file_open_callback,
+ page);
+
+ g_object_unref (file);
+}
+
+static void
+caja_image_properties_page_class_init (CajaImagePropertiesPageClass *class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (class);
+
+ object_class->finalize = caja_image_properties_page_finalize;
+
+ g_type_class_add_private (object_class, sizeof(CajaImagePropertiesPageDetails));
+}
+
+static void
+caja_image_properties_page_init (CajaImagePropertiesPage *page)
+{
+ page->details = G_TYPE_INSTANCE_GET_PRIVATE (page,
+ CAJA_TYPE_IMAGE_PROPERTIES_PAGE,
+ CajaImagePropertiesPageDetails);
+
+ gtk_box_set_homogeneous (GTK_BOX (page), FALSE);
+ gtk_box_set_spacing (GTK_BOX (page), 2);
+ gtk_container_set_border_width (GTK_CONTAINER (page), 6);
+
+ page->details->vbox = gtk_vbox_new (FALSE, 6);
+ page->details->loading_label =
+ append_label (page->details->vbox,_("loading..."));
+ gtk_box_pack_start (GTK_BOX (page),
+ page->details->vbox,
+ FALSE, TRUE, 2);
+
+ gtk_widget_show_all (GTK_WIDGET (page));
+}
+
+static GList *
+get_property_pages (CajaPropertyPageProvider *provider,
+ GList *files)
+{
+ GList *pages;
+ CajaPropertyPage *real_page;
+ CajaFileInfo *file;
+ char *uri;
+ CajaImagePropertiesPage *page;
+
+ /* Only show the property page if 1 file is selected */
+ if (!files || files->next != NULL)
+ {
+ return NULL;
+ }
+
+ file = CAJA_FILE_INFO (files->data);
+
+ if (!
+ (caja_file_info_is_mime_type (file, "image/x-bmp") ||
+ caja_file_info_is_mime_type (file, "image/x-ico") ||
+ caja_file_info_is_mime_type (file, "image/jpeg") ||
+ caja_file_info_is_mime_type (file, "image/gif") ||
+ caja_file_info_is_mime_type (file, "image/png") ||
+ caja_file_info_is_mime_type (file, "image/pnm") ||
+ caja_file_info_is_mime_type (file, "image/ras") ||
+ caja_file_info_is_mime_type (file, "image/tga") ||
+ caja_file_info_is_mime_type (file, "image/tiff") ||
+ caja_file_info_is_mime_type (file, "image/wbmp") ||
+ caja_file_info_is_mime_type (file, "image/x-xbitmap") ||
+ caja_file_info_is_mime_type (file, "image/x-xpixmap")))
+ {
+ return NULL;
+ }
+
+ pages = NULL;
+
+ uri = caja_file_info_get_uri (file);
+
+ page = g_object_new (caja_image_properties_page_get_type (), NULL);
+ load_location (page, uri);
+
+ g_free (uri);
+
+ real_page = caja_property_page_new
+ ("CajaImagePropertiesPage::property_page",
+ gtk_label_new (_("Image")),
+ GTK_WIDGET (page));
+ pages = g_list_append (pages, real_page);
+
+ return pages;
+}
+
+static void
+property_page_provider_iface_init (CajaPropertyPageProviderIface *iface)
+{
+ iface->get_pages = get_property_pages;
+}
+
+
+static void
+caja_image_properties_page_provider_init (CajaImagePropertiesPageProvider *sidebar)
+{
+}
+
+static void
+caja_image_properties_page_provider_class_init (CajaImagePropertiesPageProviderClass *class)
+{
+}
+
+void
+caja_image_properties_page_register (void)
+{
+ caja_module_add_type (caja_image_properties_page_provider_get_type ());
+}
+
diff --git a/src/caja-image-properties-page.h b/src/caja-image-properties-page.h
new file mode 100644
index 00000000..ec9ce5f0
--- /dev/null
+++ b/src/caja-image-properties-page.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Copyright (C) 2004 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Alexander Larsson <[email protected]>
+ */
+
+#ifndef CAJA_IMAGE_PROPERTIES_PAGE_H
+#define CAJA_IMAGE_PROPERTIES_PAGE_H
+
+#include <gtk/gtk.h>
+
+#define CAJA_TYPE_IMAGE_PROPERTIES_PAGE caja_image_properties_page_get_type()
+#define CAJA_IMAGE_PROPERTIES_PAGE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_IMAGE_PROPERTIES_PAGE, CajaImagePropertiesPage))
+#define CAJA_IMAGE_PROPERTIES_PAGE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_IMAGE_PROPERTIES_PAGE, CajaImagePropertiesPageClass))
+#define CAJA_IS_IMAGE_PROPERTIES_PAGE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_IMAGE_PROPERTIES_PAGE))
+#define CAJA_IS_IMAGE_PROPERTIES_PAGE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_IMAGE_PROPERTIES_PAGE))
+#define CAJA_IMAGE_PROPERTIES_PAGE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_IMAGE_PROPERTIES_PAGE, CajaImagePropertiesPageClass))
+
+typedef struct CajaImagePropertiesPageDetails CajaImagePropertiesPageDetails;
+
+typedef struct
+{
+ GtkVBox parent;
+ CajaImagePropertiesPageDetails *details;
+} CajaImagePropertiesPage;
+
+typedef struct
+{
+ GtkVBoxClass parent;
+} CajaImagePropertiesPageClass;
+
+GType caja_image_properties_page_get_type (void);
+void caja_image_properties_page_register (void);
+
+#endif /* CAJA_IMAGE_PROPERTIES_PAGE_H */
diff --git a/src/caja-information-panel.c b/src/caja-information-panel.c
new file mode 100644
index 00000000..e31173aa
--- /dev/null
+++ b/src/caja-information-panel.c
@@ -0,0 +1,1271 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 1999, 2000, 2001 Eazel, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Author: Andy Hertzfeld <[email protected]>
+ *
+ */
+
+#include <config.h>
+#include "caja-information-panel.h"
+
+#include "caja-sidebar-title.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 <gdk-pixbuf/gdk-pixbuf.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <libcaja-private/caja-dnd.h>
+#include <libcaja-private/caja-directory.h>
+#include <libcaja-private/caja-file-dnd.h>
+#include <libcaja-private/caja-file.h>
+#include <libcaja-private/caja-global-preferences.h>
+#include <libcaja-private/caja-keep-last-vertical-box.h>
+#include <libcaja-private/caja-metadata.h>
+#include <libcaja-private/caja-mime-actions.h>
+#include <libcaja-private/caja-program-choosing.h>
+#include <libcaja-private/caja-sidebar-provider.h>
+#include <libcaja-private/caja-module.h>
+
+struct CajaInformationPanelDetails
+{
+ GtkVBox *container;
+ CajaWindowInfo *window;
+ CajaSidebarTitle *title;
+ GtkHBox *button_box_centerer;
+ GtkVBox *button_box;
+ gboolean has_buttons;
+ CajaFile *file;
+ guint file_changed_connection;
+ gboolean background_connected;
+
+ char *default_background_color;
+ char *default_background_image;
+ char *current_background_color;
+ char *current_background_image;
+};
+
+/* button assignments */
+#define CONTEXTUAL_MENU_BUTTON 3
+
+static gboolean caja_information_panel_press_event (GtkWidget *widget,
+ GdkEventButton *event);
+static void caja_information_panel_finalize (GObject *object);
+static void caja_information_panel_drag_data_received (GtkWidget *widget,
+ GdkDragContext *context,
+ int x,
+ int y,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint time);
+static void caja_information_panel_read_defaults (CajaInformationPanel *information_panel);
+static void caja_information_panel_style_set (GtkWidget *widget,
+ GtkStyle *previous_style);
+static void caja_information_panel_theme_changed (gpointer user_data);
+static void caja_information_panel_update_appearance (CajaInformationPanel *information_panel);
+static void caja_information_panel_update_buttons (CajaInformationPanel *information_panel);
+static void background_metadata_changed_callback (CajaInformationPanel *information_panel);
+static void caja_information_panel_iface_init (CajaSidebarIface *iface);
+static void caja_information_panel_iface_init (CajaSidebarIface *iface);
+static void sidebar_provider_iface_init (CajaSidebarProviderIface *iface);
+static GType caja_information_panel_provider_get_type (void);
+
+enum
+{
+ LOCATION_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+/* drag and drop definitions */
+
+enum
+{
+ TARGET_URI_LIST,
+ TARGET_COLOR,
+ TARGET_BGIMAGE,
+ TARGET_KEYWORD,
+ TARGET_BACKGROUND_RESET,
+ TARGET_MATE_URI_LIST
+};
+
+static const GtkTargetEntry target_table[] =
+{
+ { "text/uri-list", 0, TARGET_URI_LIST },
+ { "application/x-color", 0, TARGET_COLOR },
+ { "property/bgimage", 0, TARGET_BGIMAGE },
+ { "property/keyword", 0, TARGET_KEYWORD },
+ { "x-special/mate-reset-background", 0, TARGET_BACKGROUND_RESET },
+ { "x-special/mate-icon-list", 0, TARGET_MATE_URI_LIST }
+};
+
+typedef enum
+{
+ NO_PART,
+ BACKGROUND_PART,
+ ICON_PART
+} InformationPanelPart;
+
+typedef struct
+{
+ GObject parent;
+} CajaInformationPanelProvider;
+
+typedef struct
+{
+ GObjectClass parent;
+} CajaInformationPanelProviderClass;
+
+
+G_DEFINE_TYPE_WITH_CODE (CajaInformationPanel, caja_information_panel, EEL_TYPE_BACKGROUND_BOX,
+ G_IMPLEMENT_INTERFACE (CAJA_TYPE_SIDEBAR,
+ caja_information_panel_iface_init));
+/* for EEL_CALL_PARENT */
+#define parent_class caja_information_panel_parent_class
+
+G_DEFINE_TYPE_WITH_CODE (CajaInformationPanelProvider, caja_information_panel_provider, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (CAJA_TYPE_SIDEBAR_PROVIDER,
+ sidebar_provider_iface_init));
+
+
+static const char *
+caja_information_panel_get_sidebar_id (CajaSidebar *sidebar)
+{
+ return CAJA_INFORMATION_PANEL_ID;
+}
+
+static char *
+caja_information_panel_get_tab_label (CajaSidebar *sidebar)
+{
+ return g_strdup (_("Information"));
+}
+
+static char *
+caja_information_panel_get_tab_tooltip (CajaSidebar *sidebar)
+{
+ return g_strdup (_("Show Information"));
+}
+
+static GdkPixbuf *
+caja_information_panel_get_tab_icon (CajaSidebar *sidebar)
+{
+ return NULL;
+}
+
+static void
+caja_information_panel_is_visible_changed (CajaSidebar *sidebar,
+ gboolean is_visible)
+{
+ /* Do nothing */
+}
+
+static void
+caja_information_panel_iface_init (CajaSidebarIface *iface)
+{
+ iface->get_sidebar_id = caja_information_panel_get_sidebar_id;
+ iface->get_tab_label = caja_information_panel_get_tab_label;
+ iface->get_tab_tooltip = caja_information_panel_get_tab_tooltip;
+ iface->get_tab_icon = caja_information_panel_get_tab_icon;
+ iface->is_visible_changed = caja_information_panel_is_visible_changed;
+}
+
+/* initializing the class object by installing the operations we override */
+static void
+caja_information_panel_class_init (CajaInformationPanelClass *klass)
+{
+ GtkWidgetClass *widget_class;
+ GObjectClass *gobject_class;
+
+ gobject_class = G_OBJECT_CLASS (klass);
+ widget_class = GTK_WIDGET_CLASS (klass);
+
+ gobject_class->finalize = caja_information_panel_finalize;
+
+ widget_class->drag_data_received = caja_information_panel_drag_data_received;
+ widget_class->button_press_event = caja_information_panel_press_event;
+ widget_class->style_set = caja_information_panel_style_set;
+
+ /* add the "location changed" signal */
+ signals[LOCATION_CHANGED] = g_signal_new
+ ("location_changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaInformationPanelClass,
+ location_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE, 1, G_TYPE_STRING);
+}
+
+/* utility routine to allocate the box the holds the command buttons */
+static void
+make_button_box (CajaInformationPanel *information_panel)
+{
+ information_panel->details->button_box_centerer = GTK_HBOX (gtk_hbox_new (FALSE, 0));
+ gtk_box_pack_start (GTK_BOX (information_panel->details->container),
+ GTK_WIDGET (information_panel->details->button_box_centerer), TRUE, TRUE, 0);
+
+ information_panel->details->button_box = GTK_VBOX (caja_keep_last_vertical_box_new (4));
+ gtk_container_set_border_width (GTK_CONTAINER (information_panel->details->button_box), 8);
+ gtk_widget_show (GTK_WIDGET (information_panel->details->button_box));
+ gtk_box_pack_start (GTK_BOX (information_panel->details->button_box_centerer),
+ GTK_WIDGET (information_panel->details->button_box),
+ TRUE, TRUE, 0);
+ information_panel->details->has_buttons = FALSE;
+}
+
+/* initialize the instance's fields, create the necessary subviews, etc. */
+
+static void
+caja_information_panel_init (CajaInformationPanel *information_panel)
+{
+ GtkWidget *widget;
+
+ widget = GTK_WIDGET (information_panel);
+
+ information_panel->details = g_new0 (CajaInformationPanelDetails, 1);
+
+ /* load the default background */
+ caja_information_panel_read_defaults (information_panel);
+
+ /* enable mouse tracking */
+ gtk_widget_add_events (GTK_WIDGET (information_panel), GDK_POINTER_MOTION_MASK);
+
+ /* create the container box */
+ information_panel->details->container = GTK_VBOX (gtk_vbox_new (FALSE, 0));
+ gtk_container_set_border_width (GTK_CONTAINER (information_panel->details->container), 0);
+ gtk_widget_show (GTK_WIDGET (information_panel->details->container));
+ gtk_container_add (GTK_CONTAINER (information_panel),
+ GTK_WIDGET (information_panel->details->container));
+
+ /* allocate and install the index title widget */
+ information_panel->details->title = CAJA_SIDEBAR_TITLE (caja_sidebar_title_new ());
+ gtk_widget_show (GTK_WIDGET (information_panel->details->title));
+ gtk_box_pack_start (GTK_BOX (information_panel->details->container),
+ GTK_WIDGET (information_panel->details->title),
+ FALSE, FALSE, 8);
+
+ /* allocate and install the command button container */
+ make_button_box (information_panel);
+
+ /* add a callback for when the theme changes */
+ eel_preferences_add_callback (CAJA_PREFERENCES_SIDE_PANE_BACKGROUND_SET, caja_information_panel_theme_changed, information_panel);
+ eel_preferences_add_callback (CAJA_PREFERENCES_SIDE_PANE_BACKGROUND_COLOR, caja_information_panel_theme_changed, information_panel);
+ eel_preferences_add_callback (CAJA_PREFERENCES_SIDE_PANE_BACKGROUND_FILENAME, caja_information_panel_theme_changed, information_panel);
+
+ /* prepare ourselves to receive dropped objects */
+ gtk_drag_dest_set (GTK_WIDGET (information_panel),
+ 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 | GDK_ACTION_ASK);
+}
+
+static void
+caja_information_panel_finalize (GObject *object)
+{
+ CajaInformationPanel *information_panel;
+
+ information_panel = CAJA_INFORMATION_PANEL (object);
+
+ if (information_panel->details->file != NULL)
+ {
+ caja_file_monitor_remove (information_panel->details->file, information_panel);
+ caja_file_unref (information_panel->details->file);
+ }
+
+ g_free (information_panel->details->default_background_color);
+ g_free (information_panel->details->default_background_image);
+ g_free (information_panel->details->current_background_color);
+ g_free (information_panel->details->current_background_image);
+ g_free (information_panel->details);
+
+ eel_preferences_remove_callback (CAJA_PREFERENCES_SIDE_PANE_BACKGROUND_SET,
+ caja_information_panel_theme_changed,
+ information_panel);
+ eel_preferences_remove_callback (CAJA_PREFERENCES_SIDE_PANE_BACKGROUND_COLOR,
+ caja_information_panel_theme_changed,
+ information_panel);
+ eel_preferences_remove_callback (CAJA_PREFERENCES_SIDE_PANE_BACKGROUND_FILENAME,
+ caja_information_panel_theme_changed,
+ information_panel);
+
+ EEL_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
+}
+
+/* callback to handle resetting the background */
+static void
+reset_background_callback (GtkWidget *menu_item, GtkWidget *information_panel)
+{
+ EelBackground *background;
+
+ background = eel_get_widget_background (information_panel);
+ if (background != NULL)
+ {
+ eel_background_reset (background);
+ }
+}
+
+static gboolean
+information_panel_has_background (CajaInformationPanel *information_panel)
+{
+ EelBackground *background;
+ gboolean has_background;
+ char *color;
+ char *image;
+
+ background = eel_get_widget_background (GTK_WIDGET(information_panel));
+
+ color = eel_background_get_color (background);
+ image = eel_background_get_image_uri (background);
+
+ has_background = (color || image);
+
+ return has_background;
+}
+
+/* create the context menu */
+static GtkWidget *
+caja_information_panel_create_context_menu (CajaInformationPanel *information_panel)
+{
+ GtkWidget *menu, *menu_item;
+
+ menu = gtk_menu_new ();
+ gtk_menu_set_screen (GTK_MENU (menu),
+ gtk_widget_get_screen (GTK_WIDGET (information_panel)));
+
+ /* add the reset background item, possibly disabled */
+ menu_item = gtk_menu_item_new_with_mnemonic (_("Use _Default Background"));
+ gtk_widget_show (menu_item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
+ gtk_widget_set_sensitive (menu_item, information_panel_has_background (information_panel));
+ g_signal_connect_object (menu_item, "activate",
+ G_CALLBACK (reset_background_callback), information_panel, 0);
+
+ return menu;
+}
+
+/* set up the default backgrounds and images */
+static void
+caja_information_panel_read_defaults (CajaInformationPanel *information_panel)
+{
+ gboolean background_set;
+ char *background_color, *background_image;
+
+ background_set = eel_preferences_get_boolean (CAJA_PREFERENCES_SIDE_PANE_BACKGROUND_SET);
+
+ background_color = NULL;
+ background_image = NULL;
+ if (background_set)
+ {
+ background_color = eel_preferences_get (CAJA_PREFERENCES_SIDE_PANE_BACKGROUND_COLOR);
+ background_image = eel_preferences_get (CAJA_PREFERENCES_SIDE_PANE_BACKGROUND_FILENAME);
+ }
+
+ g_free (information_panel->details->default_background_color);
+ information_panel->details->default_background_color = NULL;
+ g_free (information_panel->details->default_background_image);
+ information_panel->details->default_background_image = NULL;
+
+ if (background_color && strlen (background_color))
+ {
+ information_panel->details->default_background_color = g_strdup (background_color);
+ }
+
+ /* set up the default background image */
+
+ if (background_image && strlen (background_image))
+ {
+ information_panel->details->default_background_image = g_strdup (background_image);
+ }
+
+ g_free (background_color);
+ g_free (background_image);
+}
+
+/* handler for handling theme changes */
+
+static void
+caja_information_panel_theme_changed (gpointer user_data)
+{
+ CajaInformationPanel *information_panel;
+
+ information_panel = CAJA_INFORMATION_PANEL (user_data);
+ caja_information_panel_read_defaults (information_panel);
+ caja_information_panel_update_appearance (information_panel);
+ gtk_widget_queue_draw (GTK_WIDGET (information_panel)) ;
+}
+
+/* hit testing */
+
+static InformationPanelPart
+hit_test (CajaInformationPanel *information_panel,
+ int x, int y)
+{
+ if (caja_sidebar_title_hit_test_icon (information_panel->details->title, x, y))
+ {
+ return ICON_PART;
+ }
+
+ if (eel_point_in_widget (GTK_WIDGET (information_panel), x, y))
+ {
+ return BACKGROUND_PART;
+ }
+
+ return NO_PART;
+}
+
+/* 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
+receive_dropped_uri_list (CajaInformationPanel *information_panel,
+ GdkDragAction action,
+ int x, int y,
+ GtkSelectionData *selection_data)
+{
+ char **uris;
+ gboolean exactly_one;
+ GtkWindow *window;
+
+ uris = g_uri_list_extract_uris ((gchar *) gtk_selection_data_get_data (selection_data));
+ exactly_one = uris[0] != NULL && (uris[1] == NULL || uris[1][0] == '\0');
+ window = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (information_panel)));
+
+ switch (hit_test (information_panel, x, y))
+ {
+ case NO_PART:
+ case BACKGROUND_PART:
+ /* FIXME bugzilla.gnome.org 42507: Does this work for all images, or only background images?
+ * Other views handle background images differently from other URIs.
+ */
+ if (exactly_one && uri_is_local_image (uris[0]))
+ {
+ if (action == GDK_ACTION_ASK)
+ {
+ action = caja_drag_drop_background_ask (GTK_WIDGET (information_panel), CAJA_DND_ACTION_SET_AS_BACKGROUND | CAJA_DND_ACTION_SET_AS_GLOBAL_BACKGROUND);
+ }
+
+ if (action > 0)
+ {
+ eel_background_receive_dropped_background_image
+ (eel_get_widget_background (GTK_WIDGET (information_panel)),
+ action,
+ uris[0]);
+ }
+ }
+ else if (exactly_one)
+ {
+ g_signal_emit (information_panel,
+ signals[LOCATION_CHANGED], 0,
+ uris[0]);
+ }
+ break;
+ case ICON_PART:
+ /* handle images dropped on the logo specially */
+
+ 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);
+ break;
+ }
+
+ if (uri_is_local_image (uris[0]))
+ {
+ if (information_panel->details->file != NULL)
+ {
+ caja_file_set_metadata (information_panel->details->file,
+ CAJA_METADATA_KEY_CUSTOM_ICON,
+ NULL,
+ uris[0]);
+ caja_file_set_metadata (information_panel->details->file,
+ CAJA_METADATA_KEY_ICON_SCALE,
+ NULL,
+ NULL);
+ }
+ }
+ 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 images as custom icons."),
+ window);
+ }
+ g_object_unref (f);
+ }
+ break;
+ }
+
+ g_strfreev (uris);
+}
+
+static void
+receive_dropped_color (CajaInformationPanel *information_panel,
+ GdkDragAction action,
+ int x, int y,
+ GtkSelectionData *selection_data)
+{
+ guint16 *channels;
+ char color_spec[8];
+
+ if (gtk_selection_data_get_length (selection_data) != 8 ||
+ gtk_selection_data_get_format (selection_data) != 16)
+ {
+ g_warning ("received invalid color data");
+ return;
+ }
+
+ channels = (guint16 *) gtk_selection_data_get_data (selection_data);
+ g_snprintf (color_spec, sizeof (color_spec),
+ "#%02X%02X%02X", channels[0] >> 8, channels[1] >> 8, channels[2] >> 8);
+
+ switch (hit_test (information_panel, x, y))
+ {
+ case NO_PART:
+ g_warning ("dropped color, but not on any part of information_panel");
+ break;
+ case ICON_PART:
+ case BACKGROUND_PART:
+ if (action == GDK_ACTION_ASK)
+ {
+ action = caja_drag_drop_background_ask (GTK_WIDGET (information_panel), CAJA_DND_ACTION_SET_AS_BACKGROUND | CAJA_DND_ACTION_SET_AS_GLOBAL_BACKGROUND);
+ }
+
+ if (action > 0)
+ {
+ /* Let the background change based on the dropped color. */
+ eel_background_receive_dropped_color
+ (eel_get_widget_background (GTK_WIDGET (information_panel)),
+ GTK_WIDGET (information_panel),
+ action, x, y, selection_data);
+ }
+
+ break;
+ }
+}
+
+/* handle receiving a dropped keyword */
+
+static void
+receive_dropped_keyword (CajaInformationPanel *information_panel,
+ int x, int y,
+ GtkSelectionData *selection_data)
+{
+ caja_drag_file_receive_dropped_keyword (information_panel->details->file,
+ gtk_selection_data_get_data (selection_data));
+
+ /* regenerate the display */
+ caja_information_panel_update_appearance (information_panel);
+}
+
+static void
+caja_information_panel_drag_data_received (GtkWidget *widget, GdkDragContext *context,
+ int x, int y,
+ GtkSelectionData *selection_data,
+ guint info, guint time)
+{
+ CajaInformationPanel *information_panel;
+ EelBackground *background;
+
+ g_return_if_fail (CAJA_IS_INFORMATION_PANEL (widget));
+
+ information_panel = CAJA_INFORMATION_PANEL (widget);
+
+ switch (info)
+ {
+ case TARGET_MATE_URI_LIST:
+ case TARGET_URI_LIST:
+ receive_dropped_uri_list (information_panel,
+ gdk_drag_context_get_selected_action (context), x, y, selection_data);
+ break;
+ case TARGET_COLOR:
+ receive_dropped_color (information_panel,
+ gdk_drag_context_get_selected_action (context), x, y, selection_data);
+ break;
+ case TARGET_BGIMAGE:
+ if (hit_test (information_panel, x, y) == BACKGROUND_PART)
+ receive_dropped_uri_list (information_panel,
+ gdk_drag_context_get_selected_action (context), x, y, selection_data);
+ break;
+ case TARGET_BACKGROUND_RESET:
+ background = eel_get_widget_background ( GTK_WIDGET (information_panel));
+ if (background != NULL)
+ {
+ eel_background_reset (background);
+ }
+ break;
+ case TARGET_KEYWORD:
+ receive_dropped_keyword (information_panel, x, y, selection_data);
+ break;
+ default:
+ g_warning ("unknown drop type");
+ }
+}
+
+/* handle the context menu if necessary */
+static gboolean
+caja_information_panel_press_event (GtkWidget *widget, GdkEventButton *event)
+{
+ CajaInformationPanel *information_panel;
+ GtkWidget *menu;
+
+ if (gtk_widget_get_window (widget) != event->window)
+ {
+ return FALSE;
+ }
+
+ information_panel = CAJA_INFORMATION_PANEL (widget);
+
+ /* handle the context menu */
+ if (event->button == CONTEXTUAL_MENU_BUTTON)
+ {
+ menu = caja_information_panel_create_context_menu (information_panel);
+ eel_pop_up_context_menu (GTK_MENU(menu),
+ EEL_DEFAULT_POPUP_MENU_DISPLACEMENT,
+ EEL_DEFAULT_POPUP_MENU_DISPLACEMENT,
+ event);
+ }
+ return TRUE;
+}
+
+static gboolean
+value_different (const char *a, const char *b)
+{
+ if (!a && !b)
+ return FALSE;
+
+ if (!a || !b)
+ return TRUE;
+
+ return strcmp (a, b);
+}
+
+/* Handle the background changed signal by writing out the settings to metadata.
+ */
+static void
+background_settings_changed_callback (EelBackground *background, GdkDragAction action, CajaInformationPanel *information_panel)
+{
+ char *image;
+ char *color;
+
+ g_assert (EEL_IS_BACKGROUND (background));
+ g_assert (CAJA_IS_INFORMATION_PANEL (information_panel));
+
+ if (information_panel->details->file == NULL)
+ {
+ return;
+ }
+
+ /* Block so we don't respond to our own metadata changes.
+ */
+ g_signal_handlers_block_by_func (information_panel->details->file,
+ G_CALLBACK (background_metadata_changed_callback),
+ information_panel);
+
+ color = eel_background_get_color (background);
+ image = eel_background_get_image_uri (background);
+
+ if (action != (GdkDragAction) CAJA_DND_ACTION_SET_AS_BACKGROUND)
+ {
+ caja_file_set_metadata (information_panel->details->file,
+ CAJA_METADATA_KEY_SIDEBAR_BACKGROUND_COLOR,
+ NULL,
+ NULL);
+
+ caja_file_set_metadata (information_panel->details->file,
+ CAJA_METADATA_KEY_SIDEBAR_BACKGROUND_IMAGE,
+ NULL,
+ NULL);
+
+ eel_preferences_set
+ (CAJA_PREFERENCES_SIDE_PANE_BACKGROUND_COLOR, color ? color : "");
+ eel_preferences_set
+ (CAJA_PREFERENCES_SIDE_PANE_BACKGROUND_FILENAME, image ? image : "");
+ eel_preferences_set_boolean
+ (CAJA_PREFERENCES_SIDE_PANE_BACKGROUND_SET, TRUE);
+ }
+ else
+ {
+ caja_file_set_metadata (information_panel->details->file,
+ CAJA_METADATA_KEY_SIDEBAR_BACKGROUND_COLOR,
+ NULL,
+ color);
+
+ caja_file_set_metadata (information_panel->details->file,
+ CAJA_METADATA_KEY_SIDEBAR_BACKGROUND_IMAGE,
+ NULL,
+ image);
+ }
+
+ if (value_different (information_panel->details->current_background_color, color))
+ {
+ g_free (information_panel->details->current_background_color);
+ information_panel->details->current_background_color = g_strdup (color);
+ }
+
+ if (value_different (information_panel->details->current_background_image, image))
+ {
+ g_free (information_panel->details->current_background_image);
+ information_panel->details->current_background_image = g_strdup (image);
+ }
+
+ g_free (color);
+ g_free (image);
+
+ g_signal_handlers_unblock_by_func (information_panel->details->file,
+ G_CALLBACK (background_metadata_changed_callback),
+ information_panel);
+}
+
+/* handle the background reset signal by writing out NULL to metadata and setting the backgrounds
+ fields to their default values */
+static void
+background_reset_callback (EelBackground *background, CajaInformationPanel *information_panel)
+{
+ char *color;
+ char *image;
+ g_assert (EEL_IS_BACKGROUND (background));
+ g_assert (CAJA_IS_INFORMATION_PANEL (information_panel));
+
+ if (information_panel->details->file == NULL)
+ {
+ return;
+ }
+
+ /* Block so we don't respond to our own metadata changes.
+ */
+ g_signal_handlers_block_by_func (information_panel->details->file,
+ G_CALLBACK (background_metadata_changed_callback),
+ information_panel);
+
+ color = caja_file_get_metadata (information_panel->details->file,
+ CAJA_METADATA_KEY_SIDEBAR_BACKGROUND_COLOR,
+ NULL);
+
+ image = caja_file_get_metadata (information_panel->details->file,
+ CAJA_METADATA_KEY_SIDEBAR_BACKGROUND_IMAGE,
+ NULL);
+ if (color || image)
+ {
+ caja_file_set_metadata (information_panel->details->file,
+ CAJA_METADATA_KEY_SIDEBAR_BACKGROUND_COLOR,
+ NULL,
+ NULL);
+
+ caja_file_set_metadata (information_panel->details->file,
+ CAJA_METADATA_KEY_SIDEBAR_BACKGROUND_IMAGE,
+ NULL,
+ NULL);
+ }
+ else
+ {
+ eel_preferences_set_boolean (CAJA_PREFERENCES_SIDE_PANE_BACKGROUND_SET, FALSE);
+ }
+
+ g_signal_handlers_unblock_by_func (information_panel->details->file,
+ G_CALLBACK (background_metadata_changed_callback),
+ information_panel);
+
+ /* Force a read from the metadata to set the defaults
+ */
+ background_metadata_changed_callback (information_panel);
+}
+
+static GtkWindow *
+caja_information_panel_get_window (CajaInformationPanel *information_panel)
+{
+ GtkWidget *result;
+
+ result = gtk_widget_get_ancestor (GTK_WIDGET (information_panel), GTK_TYPE_WINDOW);
+
+ return result == NULL ? NULL : GTK_WINDOW (result);
+}
+
+static void
+command_button_callback (GtkWidget *button, GAppInfo *application)
+{
+ CajaInformationPanel *information_panel;
+ GList files;
+
+ information_panel = CAJA_INFORMATION_PANEL (g_object_get_data (G_OBJECT (button), "user_data"));
+
+ files.next = NULL;
+ files.prev = NULL;
+ files.data = information_panel->details->file;
+ caja_launch_application (application, &files,
+ caja_information_panel_get_window (information_panel));
+}
+
+/* interpret commands for buttons specified by metadata. Handle some built-in ones explicitly, or fork
+ a shell to handle general ones */
+/* for now, we don't have any of these */
+static void
+metadata_button_callback (GtkWidget *button, const char *command_str)
+{
+ CajaInformationPanel *information_panel;
+
+ information_panel = CAJA_INFORMATION_PANEL (g_object_get_data (G_OBJECT (button), "user_data"));
+}
+
+/* utility routine that allocates the command buttons from the command list */
+
+static void
+add_command_button (CajaInformationPanel *information_panel, GAppInfo *application)
+{
+ char *temp_str;
+ GtkWidget *temp_button, *label;
+
+ /* There's always at least the "Open with..." button */
+ information_panel->details->has_buttons = TRUE;
+
+ temp_str = g_strdup_printf (_("Open With %s"), g_app_info_get_display_name (application));
+ temp_button = gtk_button_new_with_label (temp_str);
+ label = gtk_bin_get_child (GTK_BIN (temp_button));
+ gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_START);
+ g_free (temp_str);
+ gtk_box_pack_start (GTK_BOX (information_panel->details->button_box),
+ temp_button,
+ FALSE, FALSE,
+ 0);
+
+ g_signal_connect_data (temp_button,
+ "clicked",
+ G_CALLBACK (command_button_callback),
+ g_object_ref (application),
+ (GClosureNotify)g_object_unref,
+ 0);
+
+ g_object_set_data (G_OBJECT (temp_button), "user_data", information_panel);
+
+ gtk_widget_show (temp_button);
+}
+
+/* utility to construct command buttons for the information_panel from the passed in metadata string */
+
+static void
+add_buttons_from_metadata (CajaInformationPanel *information_panel, const char *button_data)
+{
+ char **terms;
+ char *current_term, *temp_str;
+ char *button_name, *command_string;
+ const char *term;
+ int index;
+ GtkWidget *temp_button;
+
+ /* split the button specification into a set of terms */
+ button_name = NULL;
+ terms = g_strsplit (button_data, ";", 0);
+
+ /* for each term, either create a button or attach a property to one */
+ for (index = 0; (term = terms[index]) != NULL; index++)
+ {
+ current_term = g_strdup (term);
+ temp_str = strchr (current_term, '=');
+ if (temp_str)
+ {
+ *temp_str = '\0';
+ if (!g_ascii_strcasecmp (current_term, "button"))
+ {
+ button_name = g_strdup (temp_str + 1);
+ }
+ else if (!g_ascii_strcasecmp (current_term, "script"))
+ {
+ if (button_name != NULL)
+ {
+ temp_button = gtk_button_new_with_label (button_name);
+ gtk_box_pack_start (GTK_BOX (information_panel->details->button_box),
+ temp_button,
+ FALSE, FALSE,
+ 0);
+ information_panel->details->has_buttons = TRUE;
+ command_string = g_strdup (temp_str + 1);
+ g_free (button_name);
+
+ g_signal_connect_data (temp_button,
+ "clicked",
+ G_CALLBACK (metadata_button_callback),
+ command_string,
+ (GClosureNotify)g_free,
+ 0);
+
+ g_object_set_data (G_OBJECT (temp_button), "user_data", information_panel);
+
+ gtk_widget_show (temp_button);
+ }
+ }
+ }
+ g_free(current_term);
+ }
+ g_strfreev (terms);
+}
+
+/*
+ * caja_information_panel_update_buttons:
+ *
+ * Update the list of program-launching buttons based on the current uri.
+ */
+static void
+caja_information_panel_update_buttons (CajaInformationPanel *information_panel)
+{
+ char *button_data;
+ GAppInfo *default_app;
+
+ /* dispose of any existing buttons */
+ if (information_panel->details->has_buttons)
+ {
+ gtk_container_remove (GTK_CONTAINER (information_panel->details->container),
+ GTK_WIDGET (information_panel->details->button_box_centerer));
+ make_button_box (information_panel);
+ }
+
+ /* create buttons from file metadata if necessary */
+ button_data = caja_file_get_metadata (information_panel->details->file,
+ CAJA_METADATA_KEY_SIDEBAR_BUTTONS,
+ NULL);
+ if (button_data)
+ {
+ add_buttons_from_metadata (information_panel, button_data);
+ g_free(button_data);
+ }
+
+ /* Make a button for the default application */
+ if (caja_mime_has_any_applications_for_file (information_panel->details->file) &&
+ !caja_file_is_directory (information_panel->details->file))
+ {
+ default_app =
+ caja_mime_get_default_application_for_file (information_panel->details->file);
+ add_command_button (information_panel, default_app);
+ g_object_unref (default_app);
+ }
+
+ gtk_widget_show (GTK_WIDGET (information_panel->details->button_box_centerer));
+}
+
+static void
+caja_information_panel_update_appearance (CajaInformationPanel *information_panel)
+{
+ EelBackground *background;
+ char *background_color;
+ char *background_image;
+
+ g_return_if_fail (CAJA_IS_INFORMATION_PANEL (information_panel));
+
+ /* Connect the background changed signal to code that writes the color. */
+ background = eel_get_widget_background (GTK_WIDGET (information_panel));
+ if (!information_panel->details->background_connected)
+ {
+ information_panel->details->background_connected = TRUE;
+ g_signal_connect_object (background,"settings_changed",
+ G_CALLBACK (background_settings_changed_callback), information_panel, 0);
+ g_signal_connect_object (background, "reset",
+ G_CALLBACK (background_reset_callback), information_panel, 0);
+ }
+
+ /* Set up the background color and image from the metadata. */
+ background_color = caja_file_get_metadata (information_panel->details->file,
+ CAJA_METADATA_KEY_SIDEBAR_BACKGROUND_COLOR,
+ NULL);
+ background_image = caja_file_get_metadata (information_panel->details->file,
+ CAJA_METADATA_KEY_SIDEBAR_BACKGROUND_IMAGE,
+ NULL);
+
+ if (background_color == NULL && background_image == NULL)
+ {
+ background_color = g_strdup (information_panel->details->default_background_color);
+ background_image = g_strdup (information_panel->details->default_background_image);
+ }
+
+ /* Block so we don't write these settings out in response to our set calls below */
+ g_signal_handlers_block_by_func (background,
+ G_CALLBACK (background_settings_changed_callback),
+ information_panel);
+
+ if (value_different (information_panel->details->current_background_color, background_color) ||
+ value_different (information_panel->details->current_background_image, background_image))
+ {
+
+ g_free (information_panel->details->current_background_color);
+ information_panel->details->current_background_color = g_strdup (background_color);
+ g_free (information_panel->details->current_background_image);
+ information_panel->details->current_background_image = g_strdup (background_image);
+
+ eel_background_set_image_uri (background, background_image);
+ eel_background_set_color (background, background_color);
+
+ caja_sidebar_title_select_text_color
+ (information_panel->details->title, background,
+ !information_panel_has_background (information_panel));
+ }
+
+ g_free (background_color);
+ g_free (background_image);
+
+ g_signal_handlers_unblock_by_func (background,
+ G_CALLBACK (background_settings_changed_callback),
+ information_panel);
+}
+
+static void
+background_metadata_changed_callback (CajaInformationPanel *information_panel)
+{
+ CajaFileAttributes attributes;
+ gboolean ready;
+
+ attributes = caja_mime_actions_get_required_file_attributes ();
+ ready = caja_file_check_if_ready (information_panel->details->file, attributes);
+
+ if (ready)
+ {
+ caja_information_panel_update_appearance (information_panel);
+
+ /* set up the command buttons */
+ caja_information_panel_update_buttons (information_panel);
+ }
+}
+
+/* here is the key routine that populates the information_panel with the appropriate information when the uri changes */
+
+static void
+caja_information_panel_set_uri (CajaInformationPanel *information_panel,
+ const char* new_uri,
+ const char* initial_title)
+{
+ CajaFile *file;
+ CajaFileAttributes attributes;
+
+ g_return_if_fail (CAJA_IS_INFORMATION_PANEL (information_panel));
+ g_return_if_fail (new_uri != NULL);
+ g_return_if_fail (initial_title != NULL);
+
+ /* there's nothing to do if the uri is the same as the current one */
+ if (information_panel->details->file != NULL &&
+ caja_file_matches_uri (information_panel->details->file, new_uri))
+ {
+ return;
+ }
+
+ if (information_panel->details->file != NULL)
+ {
+ g_signal_handler_disconnect (information_panel->details->file,
+ information_panel->details->file_changed_connection);
+ caja_file_monitor_remove (information_panel->details->file, information_panel);
+ }
+
+ file = caja_file_get_by_uri (new_uri);
+
+ caja_file_unref (information_panel->details->file);
+ information_panel->details->file = file;
+
+ information_panel->details->file_changed_connection =
+ g_signal_connect_object (information_panel->details->file, "changed",
+ G_CALLBACK (background_metadata_changed_callback),
+ information_panel, G_CONNECT_SWAPPED);
+
+ attributes = caja_mime_actions_get_required_file_attributes ();
+ caja_file_monitor_add (information_panel->details->file, information_panel, attributes);
+
+ background_metadata_changed_callback (information_panel);
+
+ /* tell the title widget about it */
+ caja_sidebar_title_set_file (information_panel->details->title,
+ information_panel->details->file,
+ initial_title);
+}
+
+static void
+title_changed_callback (CajaWindowInfo *window,
+ char *new_title,
+ CajaInformationPanel *panel)
+{
+ caja_sidebar_title_set_text (panel->details->title,
+ new_title);
+}
+
+/* ::style_set handler for the information_panel */
+static void
+caja_information_panel_style_set (GtkWidget *widget, GtkStyle *previous_style)
+{
+ CajaInformationPanel *information_panel;
+
+ information_panel = CAJA_INFORMATION_PANEL (widget);
+
+ caja_information_panel_theme_changed (information_panel);
+}
+
+static void
+loading_uri_callback (CajaWindowInfo *window,
+ char *uri,
+ CajaInformationPanel *panel)
+{
+ CajaWindowSlotInfo *slot;
+ char *title;
+
+ slot = caja_window_info_get_active_slot (window);
+
+ title = caja_window_slot_info_get_title (slot);
+ caja_information_panel_set_uri (panel,
+ uri,
+ title);
+ g_free (title);
+}
+
+static void
+selection_changed_callback (CajaWindowInfo *window,
+ CajaInformationPanel *panel)
+{
+ int selection_count;
+ GList *selection;
+ GFile *selected;
+ CajaFile *file;
+ char *uri, *name;
+
+ selection = caja_window_info_get_selection (window);
+ selection_count = g_list_length (selection);
+
+ if (selection_count == 1)
+ {
+ selection = caja_window_info_get_selection (window);
+ selected = selection->data;
+
+ /* this should never fail here, as we're displaying the file */
+ file = caja_file_get_existing (selected);
+ uri = caja_file_get_uri (file);
+ name = caja_file_get_display_name (file);
+
+ caja_file_unref (file);
+ }
+ else
+ {
+ uri = caja_window_info_get_current_location (window);
+ name = caja_window_info_get_title (window);
+ }
+
+ caja_information_panel_set_uri (panel, uri, name);
+
+ eel_g_object_list_unref (selection);
+ g_free (uri);
+ g_free (name);
+}
+
+static void
+caja_information_panel_set_parent_window (CajaInformationPanel *panel,
+ CajaWindowInfo *window)
+{
+ gpointer slot;
+ char *title, *location;
+
+ panel->details->window = window;
+
+ g_signal_connect_object (window, "loading_uri",
+ G_CALLBACK (loading_uri_callback), panel, 0);
+ g_signal_connect_object (window, "title_changed",
+ G_CALLBACK (title_changed_callback), panel, 0);
+ g_signal_connect_object (window, "selection-changed",
+ G_CALLBACK (selection_changed_callback), panel, 0);
+
+ slot = caja_window_info_get_active_slot (window);
+
+ title = caja_window_slot_info_get_title (slot);
+ location = caja_window_slot_info_get_current_location (slot);
+ caja_information_panel_set_uri (panel,
+ location,
+ title);
+ g_free (location);
+ g_free (title);
+}
+
+static CajaSidebar *
+caja_information_panel_create (CajaSidebarProvider *provider,
+ CajaWindowInfo *window)
+{
+ CajaInformationPanel *panel;
+
+ panel = g_object_new (caja_information_panel_get_type (), NULL);
+ caja_information_panel_set_parent_window (panel, window);
+ g_object_ref_sink (panel);
+
+ return CAJA_SIDEBAR (panel);
+}
+
+static void
+sidebar_provider_iface_init (CajaSidebarProviderIface *iface)
+{
+ iface->create = caja_information_panel_create;
+}
+
+static void
+caja_information_panel_provider_init (CajaInformationPanelProvider *sidebar)
+{
+}
+
+static void
+caja_information_panel_provider_class_init (CajaInformationPanelProviderClass *class)
+{
+}
+
+void
+caja_information_panel_register (void)
+{
+ caja_module_add_type (caja_information_panel_provider_get_type ());
+}
+
diff --git a/src/caja-information-panel.h b/src/caja-information-panel.h
new file mode 100644
index 00000000..020342f9
--- /dev/null
+++ b/src/caja-information-panel.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 1999, 2000 Eazel, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Author: Andy Hertzfeld <[email protected]>
+ *
+ * This is the header file for the index panel widget, which displays overview information
+ * in a vertical panel and hosts the meta-views.
+ */
+
+#ifndef CAJA_INFORMATION_PANEL_H
+#define CAJA_INFORMATION_PANEL_H
+
+#include <eel/eel-background-box.h>
+
+#define CAJA_TYPE_INFORMATION_PANEL caja_information_panel_get_type()
+#define CAJA_INFORMATION_PANEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_INFORMATION_PANEL, CajaInformationPanel))
+#define CAJA_INFORMATION_PANEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_INFORMATION_PANEL, CajaInformationPanelClass))
+#define CAJA_IS_INFORMATION_PANEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_INFORMATION_PANEL))
+#define CAJA_IS_INFORMATION_PANEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_INFORMATION_PANEL))
+#define CAJA_INFORMATION_PANEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_INFORMATION_PANEL, CajaInformationPanelClass))
+
+typedef struct CajaInformationPanelDetails CajaInformationPanelDetails;
+
+#define CAJA_INFORMATION_PANEL_ID "CajaInformationPanel"
+
+typedef struct
+{
+ EelBackgroundBox parent_slot;
+ CajaInformationPanelDetails *details;
+} CajaInformationPanel;
+
+typedef struct
+{
+ EelBackgroundBoxClass parent_slot;
+
+ void (*location_changed) (CajaInformationPanel *information_panel,
+ const char *location);
+} CajaInformationPanelClass;
+
+GType caja_information_panel_get_type (void);
+void caja_information_panel_register (void);
+
+#endif /* CAJA_INFORMATION_PANEL_H */
diff --git a/src/caja-location-bar.c b/src/caja-location-bar.c
new file mode 100644
index 00000000..2e35d50d
--- /dev/null
+++ b/src/caja-location-bar.c
@@ -0,0 +1,617 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Maciej Stachowiak <[email protected]>
+ * Ettore Perazzoli <[email protected]>
+ * Michael Meeks <[email protected]>
+ * Andy Hertzfeld <[email protected]>
+ *
+ */
+
+/* caja-location-bar.c - Location bar for Caja
+ */
+
+#include <config.h>
+#include "caja-location-bar.h"
+
+#include "caja-location-entry.h"
+#include "caja-window-private.h"
+#include "caja-window.h"
+#include "caja-navigation-window-pane.h"
+#include <eel/eel-accessibility.h>
+#include <eel/eel-glib-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 <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <libcaja-private/caja-icon-dnd.h>
+#include <libcaja-private/caja-clipboard.h>
+#include <stdio.h>
+#include <string.h>
+
+#define CAJA_DND_URI_LIST_TYPE "text/uri-list"
+#define CAJA_DND_TEXT_PLAIN_TYPE "text/plain"
+
+static const char untranslated_location_label[] = N_("Location:");
+static const char untranslated_go_to_label[] = N_("Go To:");
+#define LOCATION_LABEL _(untranslated_location_label)
+#define GO_TO_LABEL _(untranslated_go_to_label)
+
+struct CajaLocationBarDetails
+{
+ GtkLabel *label;
+ CajaEntry *entry;
+
+ char *last_location;
+
+ guint idle_id;
+};
+
+enum
+{
+ CAJA_DND_MC_DESKTOP_ICON,
+ CAJA_DND_URI_LIST,
+ CAJA_DND_TEXT_PLAIN,
+ CAJA_DND_NTARGETS
+};
+
+static const GtkTargetEntry drag_types [] =
+{
+ { CAJA_DND_URI_LIST_TYPE, 0, CAJA_DND_URI_LIST },
+ { CAJA_DND_TEXT_PLAIN_TYPE, 0, CAJA_DND_TEXT_PLAIN },
+};
+
+static const GtkTargetEntry drop_types [] =
+{
+ { CAJA_DND_URI_LIST_TYPE, 0, CAJA_DND_URI_LIST },
+ { CAJA_DND_TEXT_PLAIN_TYPE, 0, CAJA_DND_TEXT_PLAIN },
+};
+
+static char *caja_location_bar_get_location (CajaNavigationBar *navigation_bar);
+static void caja_location_bar_set_location (CajaNavigationBar *navigation_bar,
+ const char *location);
+static void caja_location_bar_class_init (CajaLocationBarClass *class);
+static void caja_location_bar_init (CajaLocationBar *bar);
+static void caja_location_bar_update_label (CajaLocationBar *bar);
+
+EEL_CLASS_BOILERPLATE (CajaLocationBar,
+ caja_location_bar,
+ CAJA_TYPE_NAVIGATION_BAR)
+
+static CajaNavigationWindow *
+caja_location_bar_get_window (GtkWidget *bar)
+{
+ return CAJA_NAVIGATION_WINDOW (gtk_widget_get_ancestor (bar, CAJA_TYPE_WINDOW));
+}
+
+static void
+drag_data_received_callback (GtkWidget *widget,
+ GdkDragContext *context,
+ int x,
+ int y,
+ GtkSelectionData *data,
+ guint info,
+ guint32 time,
+ gpointer callback_data)
+{
+ char **names;
+ CajaApplication *application;
+ int name_count;
+ CajaWindow *new_window;
+ CajaNavigationWindow *window;
+ GdkScreen *screen;
+ gboolean new_windows_for_extras;
+ char *prompt;
+ char *detail;
+ GFile *location;
+
+ g_assert (CAJA_IS_LOCATION_BAR (widget));
+ g_assert (data != NULL);
+ g_assert (callback_data == NULL);
+
+ names = g_uri_list_extract_uris (gtk_selection_data_get_data (data));
+
+ if (names == NULL || *names == NULL)
+ {
+ g_warning ("No D&D URI's");
+ gtk_drag_finish (context, FALSE, FALSE, time);
+ return;
+ }
+
+ window = caja_location_bar_get_window (widget);
+ new_windows_for_extras = FALSE;
+ /* Ask user if they really want to open multiple windows
+ * for multiple dropped URIs. This is likely to have been
+ * a mistake.
+ */
+ name_count = g_strv_length (names);
+ if (name_count > 1)
+ {
+ prompt = g_strdup_printf (ngettext("Do you want to view %d location?",
+ "Do you want to view %d locations?",
+ name_count),
+ name_count);
+ detail = g_strdup_printf (ngettext("This will open %d separate window.",
+ "This will open %d separate windows.",
+ name_count),
+ name_count);
+ /* eel_run_simple_dialog should really take in pairs
+ * like gtk_dialog_new_with_buttons() does. */
+ new_windows_for_extras = eel_run_simple_dialog
+ (GTK_WIDGET (window),
+ TRUE,
+ GTK_MESSAGE_QUESTION,
+ prompt,
+ detail,
+ GTK_STOCK_CANCEL, GTK_STOCK_OK,
+ NULL) != 0 /* MATE_OK */;
+
+ g_free (prompt);
+ g_free (detail);
+
+ if (!new_windows_for_extras)
+ {
+ gtk_drag_finish (context, FALSE, FALSE, time);
+ return;
+ }
+ }
+
+ caja_navigation_bar_set_location (CAJA_NAVIGATION_BAR (widget),
+ names[0]);
+ caja_navigation_bar_location_changed (CAJA_NAVIGATION_BAR (widget));
+
+ if (new_windows_for_extras)
+ {
+ int i;
+
+ application = CAJA_WINDOW (window)->application;
+ screen = gtk_window_get_screen (GTK_WINDOW (window));
+
+ for (i = 1; names[i] != NULL; ++i)
+ {
+ new_window = caja_application_create_navigation_window (application, NULL, screen);
+ location = g_file_new_for_uri (names[i]);
+ caja_window_go_to (new_window, location);
+ g_object_unref (location);
+ }
+ }
+
+ g_strfreev (names);
+
+ gtk_drag_finish (context, TRUE, FALSE, time);
+}
+
+static void
+drag_data_get_callback (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint32 time,
+ gpointer callback_data)
+{
+ CajaNavigationBar *bar;
+ char *entry_text;
+
+ g_assert (selection_data != NULL);
+ bar = CAJA_NAVIGATION_BAR (callback_data);
+
+ entry_text = caja_navigation_bar_get_location (bar);
+
+ switch (info)
+ {
+ case CAJA_DND_URI_LIST:
+ case CAJA_DND_TEXT_PLAIN:
+ gtk_selection_data_set (selection_data,
+ gtk_selection_data_get_target (selection_data),
+ 8, (guchar *) entry_text,
+ eel_strlen (entry_text));
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+ g_free (entry_text);
+}
+
+/* routine that determines the usize for the label widget as larger
+ then the size of the largest string and then sets it to that so
+ that we don't have localization problems. see
+ gtk_label_finalize_lines in gtklabel.c (line 618) for the code that
+ we are imitating here. */
+
+static void
+style_set_handler (GtkWidget *widget, GtkStyle *previous_style)
+{
+ PangoLayout *layout;
+ int width, width2;
+ int xpad;
+
+ layout = gtk_label_get_layout (GTK_LABEL(widget));
+
+ layout = pango_layout_copy (layout);
+
+ pango_layout_set_text (layout, LOCATION_LABEL, -1);
+ pango_layout_get_pixel_size (layout, &width, NULL);
+
+ pango_layout_set_text (layout, GO_TO_LABEL, -1);
+ pango_layout_get_pixel_size (layout, &width2, NULL);
+ width = MAX (width, width2);
+
+ gtk_misc_get_padding (GTK_MISC (widget),
+ &xpad, NULL);
+
+ width += 2 * xpad;
+
+ gtk_widget_set_size_request (widget, width, -1);
+
+ g_object_unref (layout);
+}
+
+static gboolean
+label_button_pressed_callback (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ CajaNavigationWindow *window;
+ CajaWindowSlot *slot;
+ CajaView *view;
+ GtkWidget *label;
+
+ if (event->button != 3)
+ {
+ return FALSE;
+ }
+
+ window = caja_location_bar_get_window (gtk_widget_get_parent (widget));
+ slot = CAJA_WINDOW (window)->details->active_pane->active_slot;
+ view = slot->content_view;
+ label = gtk_bin_get_child (GTK_BIN (widget));
+ /* only pop-up if the URI in the entry matches the displayed location */
+ if (view == NULL ||
+ strcmp (gtk_label_get_text (GTK_LABEL (label)), LOCATION_LABEL))
+ {
+ return FALSE;
+ }
+
+ caja_view_pop_up_location_context_menu (view, event, NULL);
+
+ return FALSE;
+}
+
+static void
+editable_activate_callback (GtkEntry *entry,
+ gpointer user_data)
+{
+ CajaNavigationBar *bar;
+ const char *entry_text;
+
+ bar = CAJA_NAVIGATION_BAR (user_data);
+
+ entry_text = gtk_entry_get_text (entry);
+ if (entry_text != NULL && *entry_text != '\0')
+ {
+ caja_navigation_bar_location_changed (bar);
+ }
+}
+
+static void
+editable_changed_callback (GtkEntry *entry,
+ gpointer user_data)
+{
+ caja_location_bar_update_label (CAJA_LOCATION_BAR (user_data));
+}
+
+static void
+real_activate (CajaNavigationBar *navigation_bar)
+{
+ CajaLocationBar *bar;
+
+ bar = CAJA_LOCATION_BAR (navigation_bar);
+
+ /* Put the keyboard focus in the text field when switching to this mode,
+ * and select all text for easy overtyping
+ */
+ gtk_widget_grab_focus (GTK_WIDGET (bar->details->entry));
+ caja_entry_select_all (bar->details->entry);
+}
+
+static void
+real_cancel (CajaNavigationBar *navigation_bar)
+{
+ char *last_location;
+
+ last_location = CAJA_LOCATION_BAR (navigation_bar)->details->last_location;
+ caja_navigation_bar_set_location (navigation_bar, last_location);
+}
+
+static void
+finalize (GObject *object)
+{
+ CajaLocationBar *bar;
+
+ bar = CAJA_LOCATION_BAR (object);
+
+ g_free (bar->details);
+
+ EEL_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
+}
+
+static void
+destroy (GtkObject *object)
+{
+ CajaLocationBar *bar;
+
+ bar = CAJA_LOCATION_BAR (object);
+
+ /* cancel the pending idle call, if any */
+ if (bar->details->idle_id != 0)
+ {
+ g_source_remove (bar->details->idle_id);
+ bar->details->idle_id = 0;
+ }
+
+ g_free (bar->details->last_location);
+ bar->details->last_location = NULL;
+
+ EEL_CALL_PARENT (GTK_OBJECT_CLASS, destroy, (object));
+}
+
+static void
+caja_location_bar_class_init (CajaLocationBarClass *class)
+{
+ GObjectClass *gobject_class;
+ GtkObjectClass *object_class;
+ CajaNavigationBarClass *navigation_bar_class;
+
+ gobject_class = G_OBJECT_CLASS (class);
+ gobject_class->finalize = finalize;
+
+ object_class = GTK_OBJECT_CLASS (class);
+ object_class->destroy = destroy;
+
+ navigation_bar_class = CAJA_NAVIGATION_BAR_CLASS (class);
+
+ navigation_bar_class->activate = real_activate;
+ navigation_bar_class->cancel = real_cancel;
+ navigation_bar_class->get_location = caja_location_bar_get_location;
+ navigation_bar_class->set_location = caja_location_bar_set_location;
+}
+
+static void
+caja_location_bar_init (CajaLocationBar *bar)
+{
+ GtkWidget *label;
+ GtkWidget *entry;
+ GtkWidget *event_box;
+ GtkWidget *hbox;
+
+ bar->details = g_new0 (CajaLocationBarDetails, 1);
+
+ hbox = gtk_hbox_new (0, FALSE);
+
+ event_box = gtk_event_box_new ();
+ gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
+
+ gtk_container_set_border_width (GTK_CONTAINER (event_box), 4);
+ label = gtk_label_new (LOCATION_LABEL);
+ gtk_container_add (GTK_CONTAINER (event_box), label);
+ gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_RIGHT);
+ gtk_misc_set_alignment (GTK_MISC (label), 1, 0.5);
+ g_signal_connect (label, "style_set",
+ G_CALLBACK (style_set_handler), NULL);
+
+ gtk_box_pack_start (GTK_BOX (hbox), event_box, FALSE, TRUE, 4);
+
+ entry = caja_location_entry_new ();
+
+ g_signal_connect_object (entry, "activate",
+ G_CALLBACK (editable_activate_callback), bar, G_CONNECT_AFTER);
+ g_signal_connect_object (entry, "changed",
+ G_CALLBACK (editable_changed_callback), bar, 0);
+
+ gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
+
+ eel_accessibility_set_up_label_widget_relation (label, entry);
+
+ gtk_container_add (GTK_CONTAINER (bar), hbox);
+
+
+ /* Label context menu */
+ g_signal_connect (event_box, "button-press-event",
+ G_CALLBACK (label_button_pressed_callback), NULL);
+
+ /* Drag source */
+ gtk_drag_source_set (GTK_WIDGET (event_box),
+ GDK_BUTTON1_MASK | GDK_BUTTON3_MASK,
+ drag_types, G_N_ELEMENTS (drag_types),
+ GDK_ACTION_COPY | GDK_ACTION_LINK);
+ g_signal_connect_object (event_box, "drag_data_get",
+ G_CALLBACK (drag_data_get_callback), bar, 0);
+
+ /* Drag dest. */
+ gtk_drag_dest_set (GTK_WIDGET (bar),
+ GTK_DEST_DEFAULT_ALL,
+ drop_types, G_N_ELEMENTS (drop_types),
+ GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
+ g_signal_connect (bar, "drag_data_received",
+ G_CALLBACK (drag_data_received_callback), NULL);
+
+ gtk_widget_show_all (hbox);
+
+ bar->details->label = GTK_LABEL (label);
+ bar->details->entry = CAJA_ENTRY (entry);
+}
+
+GtkWidget *
+caja_location_bar_new (CajaNavigationWindowPane *pane)
+{
+ GtkWidget *bar;
+ CajaLocationBar *location_bar;
+
+ bar = gtk_widget_new (CAJA_TYPE_LOCATION_BAR, NULL);
+ location_bar = CAJA_LOCATION_BAR (bar);
+
+ /* Clipboard */
+ caja_clipboard_set_up_editable
+ (GTK_EDITABLE (location_bar->details->entry),
+ caja_window_get_ui_manager (CAJA_WINDOW (CAJA_WINDOW_PANE(pane)->window)),
+ TRUE);
+
+ return bar;
+}
+
+static void
+caja_location_bar_set_location (CajaNavigationBar *navigation_bar,
+ const char *location)
+{
+ CajaLocationBar *bar;
+ char *formatted_location;
+ GFile *file;
+
+ g_assert (location != NULL);
+
+ bar = CAJA_LOCATION_BAR (navigation_bar);
+
+ /* Note: This is called in reaction to external changes, and
+ * thus should not emit the LOCATION_CHANGED signal. */
+
+ if (eel_uri_is_search (location))
+ {
+ caja_location_entry_set_special_text (CAJA_LOCATION_ENTRY (bar->details->entry),
+ "");
+ }
+ else
+ {
+ file = g_file_new_for_uri (location);
+ formatted_location = g_file_get_parse_name (file);
+ g_object_unref (file);
+ caja_location_entry_update_current_location (CAJA_LOCATION_ENTRY (bar->details->entry),
+ formatted_location);
+ g_free (formatted_location);
+ }
+
+ /* remember the original location for later comparison */
+
+ if (bar->details->last_location != location)
+ {
+ g_free (bar->details->last_location);
+ bar->details->last_location = g_strdup (location);
+ }
+
+ caja_location_bar_update_label (bar);
+}
+
+/**
+ * caja_location_bar_get_location
+ *
+ * Get the "URI" represented by the text in the location bar.
+ *
+ * @bar: A CajaLocationBar.
+ *
+ * returns a newly allocated "string" containing the mangled
+ * (by g_file_parse_name) text that the user typed in...maybe a URI
+ * but not guaranteed.
+ *
+ **/
+static char *
+caja_location_bar_get_location (CajaNavigationBar *navigation_bar)
+{
+ CajaLocationBar *bar;
+ char *user_location, *uri;
+ GFile *location;
+
+ bar = CAJA_LOCATION_BAR (navigation_bar);
+
+ user_location = gtk_editable_get_chars (GTK_EDITABLE (bar->details->entry), 0, -1);
+ location = g_file_parse_name (user_location);
+ g_free (user_location);
+ uri = g_file_get_uri (location);
+ g_object_unref (location);
+ return uri;
+}
+
+/**
+ * caja_location_bar_update_label
+ *
+ * if the text in the entry matches the uri, set the label to "location", otherwise use "goto"
+ *
+ **/
+static void
+caja_location_bar_update_label (CajaLocationBar *bar)
+{
+ const char *current_text;
+ GFile *location;
+ GFile *last_location;
+
+ if (bar->details->last_location == NULL)
+ {
+ gtk_label_set_text (GTK_LABEL (bar->details->label), GO_TO_LABEL);
+ caja_location_entry_set_secondary_action (CAJA_LOCATION_ENTRY (bar->details->entry),
+ CAJA_LOCATION_ENTRY_ACTION_GOTO);
+ return;
+ }
+
+ current_text = gtk_entry_get_text (GTK_ENTRY (bar->details->entry));
+ location = g_file_parse_name (current_text);
+ last_location = g_file_parse_name (bar->details->last_location);
+
+ if (g_file_equal (last_location, location))
+ {
+ gtk_label_set_text (GTK_LABEL (bar->details->label), LOCATION_LABEL);
+ caja_location_entry_set_secondary_action (CAJA_LOCATION_ENTRY (bar->details->entry),
+ CAJA_LOCATION_ENTRY_ACTION_CLEAR);
+ }
+ else
+ {
+ gtk_label_set_text (GTK_LABEL (bar->details->label), GO_TO_LABEL);
+ caja_location_entry_set_secondary_action (CAJA_LOCATION_ENTRY (bar->details->entry),
+ CAJA_LOCATION_ENTRY_ACTION_GOTO);
+ }
+
+ g_object_unref (location);
+ g_object_unref (last_location);
+}
+
+/* change background color based on activity state */
+void
+caja_location_bar_set_active(CajaLocationBar *location_bar, gboolean is_active)
+{
+ if(is_active)
+ {
+ /* reset style to default */
+ gtk_widget_modify_base (GTK_WIDGET (location_bar->details->entry), GTK_STATE_NORMAL, NULL);
+ }
+ else
+ {
+ GtkStyle *style;
+ GdkColor color;
+ style = gtk_widget_get_style (GTK_WIDGET (location_bar->details->entry));
+ color = style->base[GTK_STATE_INSENSITIVE];
+ gtk_widget_modify_base(GTK_WIDGET (location_bar->details->entry), GTK_STATE_NORMAL, &color);
+ }
+}
+
+CajaEntry *
+caja_location_bar_get_entry (CajaLocationBar *location_bar)
+{
+ return location_bar->details->entry;
+}
diff --git a/src/caja-location-bar.h b/src/caja-location-bar.h
new file mode 100644
index 00000000..48606733
--- /dev/null
+++ b/src/caja-location-bar.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Maciej Stachowiak <[email protected]>
+ * Ettore Perazzoli <[email protected]>
+ */
+
+/* caja-location-bar.h - Location bar for Caja
+ */
+
+#ifndef CAJA_LOCATION_BAR_H
+#define CAJA_LOCATION_BAR_H
+
+#include "caja-navigation-bar.h"
+#include "caja-navigation-window.h"
+#include "caja-navigation-window-pane.h"
+#include <libcaja-private/caja-entry.h>
+#include <gtk/gtk.h>
+
+#define CAJA_TYPE_LOCATION_BAR caja_location_bar_get_type()
+#define CAJA_LOCATION_BAR(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_LOCATION_BAR, CajaLocationBar))
+#define CAJA_LOCATION_BAR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_LOCATION_BAR, CajaLocationBarClass))
+#define CAJA_IS_LOCATION_BAR(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_LOCATION_BAR))
+#define CAJA_IS_LOCATION_BAR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_LOCATION_BAR))
+#define CAJA_LOCATION_BAR_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_LOCATION_BAR, CajaLocationBarClass))
+
+typedef struct CajaLocationBarDetails CajaLocationBarDetails;
+
+typedef struct CajaLocationBar
+{
+ CajaNavigationBar parent;
+ CajaLocationBarDetails *details;
+} CajaLocationBar;
+
+typedef struct
+{
+ CajaNavigationBarClass parent_class;
+} CajaLocationBarClass;
+
+GType caja_location_bar_get_type (void);
+GtkWidget* caja_location_bar_new (CajaNavigationWindowPane *pane);
+void caja_location_bar_set_active (CajaLocationBar *location_bar,
+ gboolean is_active);
+CajaEntry * caja_location_bar_get_entry (CajaLocationBar *location_bar);
+
+#endif /* CAJA_LOCATION_BAR_H */
diff --git a/src/caja-location-dialog.c b/src/caja-location-dialog.c
new file mode 100644
index 00000000..d0dcaf5f
--- /dev/null
+++ b/src/caja-location-dialog.c
@@ -0,0 +1,275 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2003 Ximian, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "caja-location-dialog.h"
+
+#include <eel/eel-gtk-macros.h>
+#include <eel/eel-stock-dialogs.h>
+#include <gtk/gtk.h>
+#include <libcaja-private/caja-file-utilities.h>
+#include "caja-location-entry.h"
+#include "caja-desktop-window.h"
+#include <glib/gi18n.h>
+
+struct _CajaLocationDialogDetails
+{
+ GtkWidget *entry;
+ CajaWindow *window;
+};
+
+static void caja_location_dialog_class_init (CajaLocationDialogClass *class);
+static void caja_location_dialog_init (CajaLocationDialog *dialog);
+
+EEL_CLASS_BOILERPLATE (CajaLocationDialog,
+ caja_location_dialog,
+ GTK_TYPE_DIALOG)
+enum
+{
+ RESPONSE_OPEN
+};
+
+static void
+caja_location_dialog_finalize (GObject *object)
+{
+ CajaLocationDialog *dialog;
+
+ dialog = CAJA_LOCATION_DIALOG (object);
+
+ g_free (dialog->details);
+
+ EEL_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
+}
+
+static void
+caja_location_dialog_destroy (GtkObject *object)
+{
+ CajaLocationDialog *dialog;
+
+ dialog = CAJA_LOCATION_DIALOG (object);
+
+ EEL_CALL_PARENT (GTK_OBJECT_CLASS, destroy, (object));
+}
+
+static void
+open_current_location (CajaLocationDialog *dialog)
+{
+ GFile *location;
+ char *user_location;
+
+ user_location = gtk_editable_get_chars (GTK_EDITABLE (dialog->details->entry), 0, -1);
+ location = g_file_parse_name (user_location);
+ caja_window_go_to (dialog->details->window, location);
+ g_object_unref (location);
+ g_free (user_location);
+}
+
+static void
+response_callback (CajaLocationDialog *dialog,
+ int response_id,
+ gpointer data)
+{
+ GError *error;
+
+ switch (response_id)
+ {
+ case RESPONSE_OPEN :
+ open_current_location (dialog);
+ 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;
+ case GTK_RESPONSE_HELP :
+ error = NULL;
+ gtk_show_uri (gtk_window_get_screen (GTK_WINDOW (dialog)),
+ "ghelp:user-guide#caja-open-location",
+ 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
+entry_activate_callback (GtkEntry *entry,
+ gpointer user_data)
+{
+ CajaLocationDialog *dialog;
+
+ dialog = CAJA_LOCATION_DIALOG (user_data);
+
+ if (gtk_entry_get_text_length (GTK_ENTRY (dialog->details->entry)) != 0)
+ {
+ gtk_dialog_response (GTK_DIALOG (dialog), RESPONSE_OPEN);
+ }
+}
+
+static void
+caja_location_dialog_class_init (CajaLocationDialogClass *class)
+{
+ GObjectClass *gobject_class;
+ GtkObjectClass *object_class;
+
+ gobject_class = G_OBJECT_CLASS (class);
+ gobject_class->finalize = caja_location_dialog_finalize;
+
+ object_class = GTK_OBJECT_CLASS (class);
+ object_class->destroy = caja_location_dialog_destroy;
+}
+
+static void
+entry_text_changed (GObject *object, GParamSpec *spec, gpointer user_data)
+{
+ CajaLocationDialog *dialog;
+
+ dialog = CAJA_LOCATION_DIALOG (user_data);
+
+ if (gtk_entry_get_text_length (GTK_ENTRY (dialog->details->entry)) != 0)
+ {
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), RESPONSE_OPEN, TRUE);
+ }
+ else
+ {
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), RESPONSE_OPEN, FALSE);
+ }
+}
+
+static void
+caja_location_dialog_init (CajaLocationDialog *dialog)
+{
+ GtkWidget *box;
+ GtkWidget *label;
+
+ dialog->details = g_new0 (CajaLocationDialogDetails, 1);
+
+ gtk_window_set_title (GTK_WINDOW (dialog), _("Open Location"));
+ gtk_window_set_default_size (GTK_WINDOW (dialog), 300, -1);
+ gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE);
+ 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);
+
+ box = gtk_hbox_new (FALSE, 12);
+ gtk_container_set_border_width (GTK_CONTAINER (box), 5);
+ gtk_widget_show (box);
+
+ label = gtk_label_new_with_mnemonic (_("_Location:"));
+ gtk_widget_show (label);
+ gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
+
+ dialog->details->entry = caja_location_entry_new ();
+ gtk_entry_set_width_chars (GTK_ENTRY (dialog->details->entry), 30);
+ g_signal_connect_after (dialog->details->entry,
+ "activate",
+ G_CALLBACK (entry_activate_callback),
+ dialog);
+
+ gtk_widget_show (dialog->details->entry);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), dialog->details->entry);
+
+ gtk_box_pack_start (GTK_BOX (box), dialog->details->entry,
+ TRUE, TRUE, 0);
+
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
+ box, FALSE, TRUE, 0);
+
+ gtk_dialog_add_button (GTK_DIALOG (dialog),
+ GTK_STOCK_HELP,
+ GTK_RESPONSE_HELP);
+ gtk_dialog_add_button (GTK_DIALOG (dialog),
+ GTK_STOCK_CANCEL,
+ GTK_RESPONSE_CANCEL);
+ gtk_dialog_add_button (GTK_DIALOG (dialog),
+ GTK_STOCK_OPEN,
+ RESPONSE_OPEN);
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog),
+ RESPONSE_OPEN);
+
+ g_signal_connect (dialog->details->entry, "notify::text",
+ G_CALLBACK (entry_text_changed), dialog);
+
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (response_callback),
+ dialog);
+}
+
+GtkWidget *
+caja_location_dialog_new (CajaWindow *window)
+{
+ CajaWindowSlot *slot;
+ CajaLocationDialog *loc_dialog;
+ GtkWidget *dialog;
+ GFile *location;
+ char *formatted_location;
+
+ dialog = gtk_widget_new (CAJA_TYPE_LOCATION_DIALOG, NULL);
+ loc_dialog = CAJA_LOCATION_DIALOG (dialog);
+
+ if (window)
+ {
+ gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (window));
+ gtk_window_set_screen (GTK_WINDOW (dialog),
+ gtk_window_get_screen (GTK_WINDOW (window)));
+ loc_dialog->details->window = window;
+ }
+
+ slot = window->details->active_pane->active_slot;
+
+ location = slot->location;
+ if (location != NULL)
+ {
+ if (CAJA_IS_DESKTOP_WINDOW (window))
+ {
+ formatted_location = g_strdup_printf ("%s/", g_get_home_dir ());
+ }
+ else
+ {
+ formatted_location = g_file_get_parse_name (location);
+ }
+ caja_location_entry_update_current_location (CAJA_LOCATION_ENTRY (loc_dialog->details->entry),
+ formatted_location);
+ g_free (formatted_location);
+ }
+
+ gtk_widget_grab_focus (loc_dialog->details->entry);
+
+ return dialog;
+}
+
+void
+caja_location_dialog_set_location (CajaLocationDialog *dialog,
+ const char *location)
+{
+ caja_location_entry_update_current_location (CAJA_LOCATION_ENTRY (dialog->details->entry),
+ location);
+}
diff --git a/src/caja-location-dialog.h b/src/caja-location-dialog.h
new file mode 100644
index 00000000..08a302fc
--- /dev/null
+++ b/src/caja-location-dialog.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2003 Ximian, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef CAJA_LOCATION_DIALOG_H
+#define CAJA_LOCATION_DIALOG_H
+
+#include <gtk/gtk.h>
+#include "caja-window.h"
+
+#define CAJA_TYPE_LOCATION_DIALOG (caja_location_dialog_get_type ())
+#define CAJA_LOCATION_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_LOCATION_DIALOG, CajaLocationDialog))
+#define CAJA_LOCATION_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_LOCATION_DIALOG, CajaLocationDialogClass))
+#define CAJA_IS_LOCATION_DIALOG(obj) (G_TYPE_INSTANCE_CHECK_TYPE ((obj), CAJA_TYPE_LOCATION_DIALOG)
+
+typedef struct _CajaLocationDialog CajaLocationDialog;
+typedef struct _CajaLocationDialogClass CajaLocationDialogClass;
+typedef struct _CajaLocationDialogDetails CajaLocationDialogDetails;
+
+struct _CajaLocationDialog
+{
+ GtkDialog parent;
+ CajaLocationDialogDetails *details;
+};
+
+struct _CajaLocationDialogClass
+{
+ GtkDialogClass parent_class;
+};
+
+GType caja_location_dialog_get_type (void);
+GtkWidget* caja_location_dialog_new (CajaWindow *window);
+void caja_location_dialog_set_location (CajaLocationDialog *dialog,
+ const char *location);
+
+#endif /* CAJA_LOCATION_DIALOG_H */
diff --git a/src/caja-location-entry.c b/src/caja-location-entry.c
new file mode 100644
index 00000000..03557337
--- /dev/null
+++ b/src/caja-location-entry.c
@@ -0,0 +1,485 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Maciej Stachowiak <[email protected]>
+ * Ettore Perazzoli <[email protected]>
+ * Michael Meeks <[email protected]>
+ * Andy Hertzfeld <[email protected]>
+ *
+ */
+
+/* caja-location-bar.c - Location bar for Caja
+ */
+
+#include <config.h>
+#include "caja-location-entry.h"
+
+#include "caja-window-private.h"
+#include "caja-window.h"
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-gtk-macros.h>
+#include <eel/eel-stock-dialogs.h>
+#include <eel/eel-string.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <libcaja-private/caja-file-utilities.h>
+#include <libcaja-private/caja-entry.h>
+#include <libcaja-private/caja-icon-dnd.h>
+#include <libcaja-private/caja-clipboard.h>
+#include <stdio.h>
+#include <string.h>
+
+struct CajaLocationEntryDetails
+{
+ GtkLabel *label;
+
+ char *current_directory;
+ GFilenameCompleter *completer;
+
+ guint idle_id;
+
+ gboolean has_special_text;
+ gboolean setting_special_text;
+ gchar *special_text;
+ CajaLocationEntryAction secondary_action;
+};
+
+static void caja_location_entry_class_init (CajaLocationEntryClass *class);
+static void caja_location_entry_init (CajaLocationEntry *entry);
+
+EEL_CLASS_BOILERPLATE (CajaLocationEntry,
+ caja_location_entry,
+ CAJA_TYPE_ENTRY)
+
+/* routine that performs the tab expansion. Extract the directory name and
+ incomplete basename, then iterate through the directory trying to complete it. If we
+ find something, add it to the entry */
+
+static gboolean
+try_to_expand_path (gpointer callback_data)
+{
+ CajaLocationEntry *entry;
+ GtkEditable *editable;
+ char *suffix, *user_location, *absolute_location;
+ int user_location_length, pos;
+
+ entry = CAJA_LOCATION_ENTRY (callback_data);
+ editable = GTK_EDITABLE (entry);
+ user_location = gtk_editable_get_chars (editable, 0, -1);
+ user_location_length = g_utf8_strlen (user_location, -1);
+ entry->details->idle_id = 0;
+
+ if (!g_path_is_absolute (user_location))
+ {
+ absolute_location = g_build_filename (entry->details->current_directory, user_location, NULL);
+ suffix = g_filename_completer_get_completion_suffix (entry->details->completer,
+ absolute_location);
+ g_free (absolute_location);
+ }
+ else
+ {
+ suffix = g_filename_completer_get_completion_suffix (entry->details->completer,
+ user_location);
+ g_free (user_location);
+ }
+
+ /* if we've got something, add it to the entry */
+ if (suffix != NULL)
+ {
+ pos = user_location_length;
+ gtk_editable_insert_text (editable,
+ suffix, -1, &pos);
+ pos = user_location_length;
+ gtk_editable_select_region (editable, pos, -1);
+
+ g_free (suffix);
+ }
+
+ return FALSE;
+}
+
+/* Until we have a more elegant solution, this is how we figure out if
+ * the GtkEntry inserted characters, assuming that the return value is
+ * TRUE indicating that the GtkEntry consumed the key event for some
+ * reason. This is a clone of code from GtkEntry.
+ */
+static gboolean
+entry_would_have_inserted_characters (const GdkEventKey *event)
+{
+ switch (event->keyval)
+ {
+ case GDK_BackSpace:
+ case GDK_Clear:
+ case GDK_Insert:
+ case GDK_Delete:
+ case GDK_Home:
+ case GDK_End:
+ case GDK_KP_Home:
+ case GDK_KP_End:
+ case GDK_Left:
+ case GDK_Right:
+ case GDK_KP_Left:
+ case GDK_KP_Right:
+ case GDK_Return:
+ return FALSE;
+ default:
+ if (event->keyval >= 0x20 && event->keyval <= 0xFF)
+ {
+ if ((event->state & GDK_CONTROL_MASK) != 0)
+ {
+ return FALSE;
+ }
+ if ((event->state & GDK_MOD1_MASK) != 0)
+ {
+ return FALSE;
+ }
+ }
+ return event->length > 0;
+ }
+}
+
+static int
+get_editable_number_of_chars (GtkEditable *editable)
+{
+ char *text;
+ int length;
+
+ text = gtk_editable_get_chars (editable, 0, -1);
+ length = g_utf8_strlen (text, -1);
+ g_free (text);
+ return length;
+}
+
+static void
+set_position_and_selection_to_end (GtkEditable *editable)
+{
+ int end;
+
+ end = get_editable_number_of_chars (editable);
+ gtk_editable_select_region (editable, end, end);
+ gtk_editable_set_position (editable, end);
+}
+
+static gboolean
+position_and_selection_are_at_end (GtkEditable *editable)
+{
+ int end;
+ int start_sel, end_sel;
+
+ end = get_editable_number_of_chars (editable);
+ if (gtk_editable_get_selection_bounds (editable, &start_sel, &end_sel))
+ {
+ if (start_sel != end || end_sel != end)
+ {
+ return FALSE;
+ }
+ }
+ return gtk_editable_get_position (editable) == end;
+}
+
+static void
+got_completion_data_callback (GFilenameCompleter *completer,
+ CajaLocationEntry *entry)
+{
+ if (entry->details->idle_id)
+ {
+ g_source_remove (entry->details->idle_id);
+ entry->details->idle_id = 0;
+ }
+ try_to_expand_path (entry);
+}
+
+static void
+editable_event_after_callback (GtkEntry *entry,
+ GdkEvent *event,
+ CajaLocationEntry *location_entry)
+{
+ GtkEditable *editable;
+ GdkEventKey *keyevent;
+
+ if (event->type != GDK_KEY_PRESS)
+ {
+ return;
+ }
+
+ editable = GTK_EDITABLE (entry);
+ keyevent = (GdkEventKey *)event;
+
+ /* After typing the right arrow key we move the selection to
+ * the end, if we have a valid selection - since this is most
+ * likely an auto-completion. We ignore shift / control since
+ * they can validly be used to extend the selection.
+ */
+ if ((keyevent->keyval == GDK_Right || keyevent->keyval == GDK_End) &&
+ !(keyevent->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) &&
+ gtk_editable_get_selection_bounds (editable, NULL, NULL))
+ {
+ set_position_and_selection_to_end (editable);
+ }
+
+ /* Only do expanding when we are typing at the end of the
+ * text. Do the expand at idle time to avoid slowing down
+ * typing when the directory is large. Only trigger the expand
+ * when we type a key that would have inserted characters.
+ */
+ if (position_and_selection_are_at_end (editable))
+ {
+ if (entry_would_have_inserted_characters (keyevent))
+ {
+ if (location_entry->details->idle_id == 0)
+ {
+ location_entry->details->idle_id = g_idle_add (try_to_expand_path, location_entry);
+ }
+ }
+ }
+ else
+ {
+ /* FIXME: Also might be good to do this when you click
+ * to change the position or selection.
+ */
+ if (location_entry->details->idle_id != 0)
+ {
+ g_source_remove (location_entry->details->idle_id);
+ location_entry->details->idle_id = 0;
+ }
+ }
+}
+
+static void
+finalize (GObject *object)
+{
+ CajaLocationEntry *entry;
+
+ entry = CAJA_LOCATION_ENTRY (object);
+
+ g_object_unref (entry->details->completer);
+ g_free (entry->details->special_text);
+ g_free (entry->details);
+
+ EEL_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
+}
+
+static void
+destroy (GtkObject *object)
+{
+ CajaLocationEntry *entry;
+
+ entry = CAJA_LOCATION_ENTRY (object);
+
+ /* cancel the pending idle call, if any */
+ if (entry->details->idle_id != 0)
+ {
+ g_source_remove (entry->details->idle_id);
+ entry->details->idle_id = 0;
+ }
+
+ g_free (entry->details->current_directory);
+ entry->details->current_directory = NULL;
+
+ EEL_CALL_PARENT (GTK_OBJECT_CLASS, destroy, (object));
+}
+
+static void
+caja_location_entry_text_changed (CajaLocationEntry *entry,
+ GParamSpec *pspec)
+{
+ if (entry->details->setting_special_text)
+ {
+ return;
+ }
+
+ entry->details->has_special_text = FALSE;
+}
+
+static void
+caja_location_entry_icon_release (GtkEntry *gentry,
+ GtkEntryIconPosition position,
+ GdkEvent *event,
+ gpointer unused)
+{
+ switch (CAJA_LOCATION_ENTRY (gentry)->details->secondary_action)
+ {
+ case CAJA_LOCATION_ENTRY_ACTION_GOTO:
+ g_signal_emit_by_name (gentry, "activate", gentry);
+ break;
+ case CAJA_LOCATION_ENTRY_ACTION_CLEAR:
+ gtk_entry_set_text (gentry, "");
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static gboolean
+caja_location_entry_focus_in (GtkWidget *widget,
+ GdkEventFocus *event)
+{
+ CajaLocationEntry *entry = CAJA_LOCATION_ENTRY (widget);
+
+ if (entry->details->has_special_text)
+ {
+ entry->details->setting_special_text = TRUE;
+ gtk_entry_set_text (GTK_ENTRY (entry), "");
+ entry->details->setting_special_text = FALSE;
+ }
+
+ return EEL_CALL_PARENT_WITH_RETURN_VALUE (GTK_WIDGET_CLASS, focus_in_event, (widget, event));
+}
+
+static void
+caja_location_entry_activate (GtkEntry *entry)
+{
+ CajaLocationEntry *loc_entry;
+ const gchar *entry_text;
+ gchar *full_path, *uri_scheme = NULL;
+
+ loc_entry = CAJA_LOCATION_ENTRY (entry);
+ entry_text = gtk_entry_get_text (entry);
+
+ if (entry_text != NULL && *entry_text != '\0')
+ {
+ uri_scheme = g_uri_parse_scheme (entry_text);
+
+ if (!g_path_is_absolute (entry_text) && uri_scheme == NULL)
+ {
+ /* Fix non absolute paths */
+ full_path = g_build_filename (loc_entry->details->current_directory, entry_text, NULL);
+ gtk_entry_set_text (entry, full_path);
+ g_free (full_path);
+ }
+
+ g_free (uri_scheme);
+ }
+
+ EEL_CALL_PARENT (GTK_ENTRY_CLASS, activate, (entry));
+}
+
+static void
+caja_location_entry_class_init (CajaLocationEntryClass *class)
+{
+ GtkWidgetClass *widget_class;
+ GObjectClass *gobject_class;
+ GtkObjectClass *object_class;
+ GtkEntryClass *entry_class;
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->focus_in_event = caja_location_entry_focus_in;
+
+ gobject_class = G_OBJECT_CLASS (class);
+ gobject_class->finalize = finalize;
+
+ object_class = GTK_OBJECT_CLASS (class);
+ object_class->destroy = destroy;
+
+ entry_class = GTK_ENTRY_CLASS (class);
+ entry_class->activate = caja_location_entry_activate;
+}
+
+void
+caja_location_entry_update_current_location (CajaLocationEntry *entry,
+ const char *location)
+{
+ g_free (entry->details->current_directory);
+ entry->details->current_directory = g_strdup (location);
+
+ caja_entry_set_text (CAJA_ENTRY (entry), location);
+ set_position_and_selection_to_end (GTK_EDITABLE (entry));
+}
+
+void
+caja_location_entry_set_secondary_action (CajaLocationEntry *entry,
+ CajaLocationEntryAction secondary_action)
+{
+ if (entry->details->secondary_action == secondary_action)
+ {
+ return;
+ }
+ switch (secondary_action)
+ {
+ case CAJA_LOCATION_ENTRY_ACTION_CLEAR:
+ gtk_entry_set_icon_from_stock (GTK_ENTRY (entry),
+ GTK_ENTRY_ICON_SECONDARY,
+ GTK_STOCK_CLEAR);
+ break;
+ case CAJA_LOCATION_ENTRY_ACTION_GOTO:
+ gtk_entry_set_icon_from_stock (GTK_ENTRY (entry),
+ GTK_ENTRY_ICON_SECONDARY,
+ GTK_STOCK_GO_FORWARD);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+ entry->details->secondary_action = secondary_action;
+}
+
+static void
+caja_location_entry_init (CajaLocationEntry *entry)
+{
+ entry->details = g_new0 (CajaLocationEntryDetails, 1);
+
+ entry->details->completer = g_filename_completer_new ();
+ g_filename_completer_set_dirs_only (entry->details->completer, TRUE);
+
+ caja_location_entry_set_secondary_action (entry,
+ CAJA_LOCATION_ENTRY_ACTION_CLEAR);
+
+ caja_entry_set_special_tab_handling (CAJA_ENTRY (entry), TRUE);
+
+ g_signal_connect (entry, "event_after",
+ G_CALLBACK (editable_event_after_callback), entry);
+
+ g_signal_connect (entry, "notify::text",
+ G_CALLBACK (caja_location_entry_text_changed), NULL);
+
+ g_signal_connect (entry, "icon-release",
+ G_CALLBACK (caja_location_entry_icon_release), NULL);
+
+ g_signal_connect (entry->details->completer, "got_completion_data",
+ G_CALLBACK (got_completion_data_callback), entry);
+}
+
+GtkWidget *
+caja_location_entry_new (void)
+{
+ GtkWidget *entry;
+
+ entry = gtk_widget_new (CAJA_TYPE_LOCATION_ENTRY, NULL);
+
+ return entry;
+}
+
+void
+caja_location_entry_set_special_text (CajaLocationEntry *entry,
+ const char *special_text)
+{
+ entry->details->has_special_text = TRUE;
+
+ g_free (entry->details->special_text);
+ entry->details->special_text = g_strdup (special_text);
+
+ entry->details->setting_special_text = TRUE;
+ gtk_entry_set_text (GTK_ENTRY (entry), special_text);
+ entry->details->setting_special_text = FALSE;
+}
+
diff --git a/src/caja-location-entry.h b/src/caja-location-entry.h
new file mode 100644
index 00000000..c115a18f
--- /dev/null
+++ b/src/caja-location-entry.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Maciej Stachowiak <[email protected]>
+ * Ettore Perazzoli <[email protected]>
+ */
+
+#ifndef CAJA_LOCATION_ENTRY_H
+#define CAJA_LOCATION_ENTRY_H
+
+#include <libcaja-private/caja-entry.h>
+
+#define CAJA_TYPE_LOCATION_ENTRY caja_location_entry_get_type()
+#define CAJA_LOCATION_ENTRY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_LOCATION_ENTRY, CajaLocationEntry))
+#define CAJA_LOCATION_ENTRY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_LOCATION_ENTRY, CajaLocationEntryClass))
+#define CAJA_IS_LOCATION_ENTRY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_LOCATION_ENTRY))
+#define CAJA_IS_LOCATION_ENTRY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_LOCATION_ENTRY))
+#define CAJA_LOCATION_ENTRY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_LOCATION_ENTRY, CajaLocationEntryClass))
+
+typedef struct CajaLocationEntryDetails CajaLocationEntryDetails;
+
+typedef struct CajaLocationEntry
+{
+ CajaEntry parent;
+ CajaLocationEntryDetails *details;
+} CajaLocationEntry;
+
+typedef struct
+{
+ CajaEntryClass parent_class;
+} CajaLocationEntryClass;
+
+typedef enum
+{
+ CAJA_LOCATION_ENTRY_ACTION_GOTO,
+ CAJA_LOCATION_ENTRY_ACTION_CLEAR
+} CajaLocationEntryAction;
+
+GType caja_location_entry_get_type (void);
+GtkWidget* caja_location_entry_new (void);
+void caja_location_entry_set_special_text (CajaLocationEntry *entry,
+ const char *special_text);
+void caja_location_entry_set_secondary_action (CajaLocationEntry *entry,
+ CajaLocationEntryAction secondary_action);
+void caja_location_entry_update_current_location (CajaLocationEntry *entry,
+ const char *path);
+
+#endif /* CAJA_LOCATION_ENTRY_H */
diff --git a/src/caja-main.c b/src/caja-main.c
new file mode 100644
index 00000000..0a414ef5
--- /dev/null
+++ b/src/caja-main.c
@@ -0,0 +1,590 @@
+/*
+ * Caja
+ *
+ * Copyright (C) 1999, 2000 Red Hat, Inc.
+ * Copyright (C) 1999, 2000 Eazel, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Authors: Elliot Lee <[email protected]>,
+ * Darin Adler <[email protected]>,
+ * John Sullivan <[email protected]>
+ *
+ */
+
+/* caja-main.c: Implementation of the routines that drive program lifecycle and main window creation/destruction. */
+
+#include <config.h>
+#include "caja-main.h"
+
+#include "caja-application.h"
+#include "caja-self-check-functions.h"
+#include "caja-window.h"
+#include <dlfcn.h>
+#include <signal.h>
+#include <eel/eel-debug.h>
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-self-checks.h>
+#include <libegg/eggsmclient.h>
+#include <libegg/eggdesktopfile.h>
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <gio/gdesktopappinfo.h>
+#include <libcaja-private/caja-debug-log.h>
+#include <libcaja-private/caja-global-preferences.h>
+#include <libcaja-private/caja-lib-self-check-functions.h>
+#include <libcaja-private/caja-icon-names.h>
+#include <libxml/parser.h>
+#ifdef HAVE_LOCALE_H
+ #include <locale.h>
+#endif
+#ifdef HAVE_MALLOC_H
+ #include <malloc.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef HAVE_EXEMPI
+ #include <exempi/xmp.h>
+#endif
+
+/* Keeps track of everyone who wants the main event loop kept active */
+static GSList* event_loop_registrants;
+
+static gboolean exit_with_last_window = TRUE;
+
+static gboolean is_event_loop_needed(void)
+{
+ return event_loop_registrants != NULL || !exit_with_last_window;
+}
+
+static int quit_if_in_main_loop (gpointer callback_data)
+{
+ guint level;
+
+ g_assert (callback_data == NULL);
+
+ level = gtk_main_level ();
+
+ /* We can be called even outside the main loop,
+ * so check that we are in a loop before calling quit.
+ */
+ if (level != 0)
+ {
+ gtk_main_quit ();
+ }
+
+ /* We need to be called again if we quit a nested loop. */
+ return level > 1;
+}
+
+static void eel_gtk_main_quit_all (void)
+{
+ /* Calling gtk_main_quit directly only kills the current/top event loop.
+ * This idler will be run by the current event loop, killing it, and then
+ * by the next event loop, ...
+ */
+ g_idle_add (quit_if_in_main_loop, NULL);
+}
+
+static void event_loop_unregister (GtkObject *object)
+{
+ event_loop_registrants = g_slist_remove (event_loop_registrants, object);
+
+ if (!is_event_loop_needed ())
+ {
+ eel_gtk_main_quit_all ();
+ }
+}
+
+void caja_main_event_loop_register (GtkObject *object)
+{
+ g_signal_connect (object, "destroy", G_CALLBACK (event_loop_unregister), NULL);
+ event_loop_registrants = g_slist_prepend (event_loop_registrants, object);
+}
+
+gboolean caja_main_is_event_loop_mainstay (GtkObject *object)
+{
+ return g_slist_length (event_loop_registrants) == 1
+ && event_loop_registrants->data == object;
+}
+
+void caja_main_event_loop_quit (gboolean explicit)
+{
+ if (explicit)
+ {
+ /* Explicit --quit, make sure we don't restart */
+
+ /* To quit all instances, reset exit_with_last_window */
+ exit_with_last_window = TRUE;
+
+ if (event_loop_registrants == NULL)
+ {
+ /* If this is reached, caja must run in "daemon" mode
+ * (i.e. !exit_with_last_window) with no windows open.
+ * We need to quit_all here because the below loop won't
+ * trigger a quit.
+ */
+ eel_gtk_main_quit_all();
+ }
+
+ /* TODO: With the old session we needed to set restart
+ style to MATE_RESTART_IF_RUNNING here, but i don't think we need
+ that now since mate-session doesn't restart apps except on startup. */
+ }
+ while (event_loop_registrants != NULL)
+ {
+ gtk_object_destroy (event_loop_registrants->data);
+ }
+}
+
+static void dump_debug_log (void)
+{
+ char *filename;
+
+ filename = g_build_filename (g_get_home_dir (), "caja-debug-log.txt", NULL);
+ caja_debug_log_dump (filename, NULL); /* NULL GError */
+ g_free (filename);
+}
+
+static int debug_log_pipes[2];
+
+static gboolean debug_log_io_cb (GIOChannel *io, GIOCondition condition, gpointer data)
+{
+ char a;
+
+ while (read (debug_log_pipes[0], &a, 1) != 1)
+ ;
+
+ caja_debug_log (TRUE, CAJA_DEBUG_LOG_DOMAIN_USER,
+ "user requested dump of debug log");
+
+ dump_debug_log ();
+ return FALSE;
+}
+
+static void sigusr1_handler (int sig)
+{
+ while (write (debug_log_pipes[1], "a", 1) != 1)
+ ;
+}
+
+/* This is totally broken as we're using non-signal safe
+ * calls in sigfatal_handler. Disable by default. */
+#ifdef USE_SEGV_HANDLER
+
+/* sigaction structures for the old handlers of these signals */
+static struct sigaction old_segv_sa;
+static struct sigaction old_abrt_sa;
+static struct sigaction old_trap_sa;
+static struct sigaction old_fpe_sa;
+static struct sigaction old_bus_sa;
+
+static void
+sigfatal_handler (int sig)
+{
+ void (* func) (int);
+
+ /* FIXME: is this totally busted? We do malloc() inside these functions,
+ * and yet we are inside a signal handler...
+ */
+ caja_debug_log (TRUE, CAJA_DEBUG_LOG_DOMAIN_USER,
+ "debug log dumped due to signal %d", sig);
+ dump_debug_log ();
+
+ switch (sig)
+ {
+ case SIGSEGV:
+ func = old_segv_sa.sa_handler;
+ break;
+
+ case SIGABRT:
+ func = old_abrt_sa.sa_handler;
+ break;
+
+ case SIGTRAP:
+ func = old_trap_sa.sa_handler;
+ break;
+
+ case SIGFPE:
+ func = old_fpe_sa.sa_handler;
+ break;
+
+ case SIGBUS:
+ func = old_bus_sa.sa_handler;
+ break;
+
+ default:
+ func = NULL;
+ break;
+ }
+
+ /* this scares me */
+ if (func != NULL && func != SIG_IGN && func != SIG_DFL)
+ (* func) (sig);
+}
+#endif
+
+static void
+setup_debug_log_signals (void)
+{
+ struct sigaction sa;
+ GIOChannel *io;
+
+ if (pipe (debug_log_pipes) == -1)
+ g_error ("Could not create pipe() for debug log");
+
+ io = g_io_channel_unix_new (debug_log_pipes[0]);
+ g_io_add_watch (io, G_IO_IN, debug_log_io_cb, NULL);
+
+ sa.sa_handler = sigusr1_handler;
+ sigemptyset (&sa.sa_mask);
+ sa.sa_flags = 0;
+ sigaction (SIGUSR1, &sa, NULL);
+
+ /* This is totally broken as we're using non-signal safe
+ * calls in sigfatal_handler. Disable by default. */
+#ifdef USE_SEGV_HANDLER
+ sa.sa_handler = sigfatal_handler;
+ sigemptyset (&sa.sa_mask);
+ sa.sa_flags = 0;
+
+ sigaction(SIGSEGV, &sa, &old_segv_sa);
+ sigaction(SIGABRT, &sa, &old_abrt_sa);
+ sigaction(SIGTRAP, &sa, &old_trap_sa);
+ sigaction(SIGFPE, &sa, &old_fpe_sa);
+ sigaction(SIGBUS, &sa, &old_bus_sa);
+#endif
+}
+
+static GLogFunc default_log_handler;
+
+static void
+log_override_cb (const gchar *log_domain,
+ GLogLevelFlags log_level,
+ const gchar *message,
+ gpointer user_data)
+{
+ gboolean is_debug;
+ gboolean is_milestone;
+
+ is_debug = ((log_level & G_LOG_LEVEL_DEBUG) != 0);
+ is_milestone = !is_debug;
+
+ caja_debug_log (is_milestone, CAJA_DEBUG_LOG_DOMAIN_GLOG, "%s", message);
+
+ if (!is_debug)
+ (* default_log_handler) (log_domain, log_level, message, user_data);
+}
+
+static void
+setup_debug_log_glog (void)
+{
+ default_log_handler = g_log_set_default_handler (log_override_cb, NULL);
+}
+
+static void
+setup_debug_log (void)
+{
+ char *config_filename;
+
+ config_filename = g_build_filename (g_get_home_dir (), "caja-debug-log.conf", NULL);
+ caja_debug_log_load_configuration (config_filename, NULL); /* NULL GError */
+ g_free (config_filename);
+
+ setup_debug_log_signals ();
+ setup_debug_log_glog ();
+}
+
+int
+main (int argc, char *argv[])
+{
+ gboolean kill_shell;
+ gboolean no_default_window;
+ gboolean browser_window;
+ gboolean no_desktop;
+ gboolean version;
+ gboolean autostart_mode;
+ const char *autostart_id;
+ gchar *geometry;
+ gchar **remaining;
+ gboolean perform_self_check;
+ CajaApplication *application;
+ GOptionContext *context;
+ GFile *file;
+ char *uri;
+ char **uris;
+ GPtrArray *uris_array;
+ GError *error;
+ int i;
+
+ const GOptionEntry options[] =
+ {
+#ifndef CAJA_OMIT_SELF_CHECK
+ {
+ "check", 'c', 0, G_OPTION_ARG_NONE, &perform_self_check,
+ N_("Perform a quick set of self-check tests."), NULL
+ },
+#endif
+ {
+ "version", '\0', 0, G_OPTION_ARG_NONE, &version,
+ N_("Show the version of the program."), NULL
+ },
+ {
+ "geometry", 'g', 0, G_OPTION_ARG_STRING, &geometry,
+ N_("Create the initial window with the given geometry."), N_("GEOMETRY")
+ },
+ {
+ "no-default-window", 'n', 0, G_OPTION_ARG_NONE, &no_default_window,
+ N_("Only create windows for explicitly specified URIs."), NULL
+ },
+ {
+ "no-desktop", '\0', 0, G_OPTION_ARG_NONE, &no_desktop,
+ N_("Do not manage the desktop (ignore the preference set in the preferences dialog)."), NULL
+ },
+ {
+ "browser", '\0', 0, G_OPTION_ARG_NONE, &browser_window,
+ N_("open a browser window."), NULL
+ },
+ {
+ "quit", 'q', 0, G_OPTION_ARG_NONE, &kill_shell,
+ N_("Quit Caja."), NULL
+ },
+ { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &remaining, NULL, N_("[URI...]") },
+
+ { NULL }
+ };
+
+#if defined (HAVE_MALLOPT) && defined(M_MMAP_THRESHOLD)
+ /* Caja uses lots and lots of small and medium size allocations,
+ * and then a few large ones for the desktop background. By default
+ * glibc uses a dynamic treshold for how large allocations should
+ * be mmaped. Unfortunately this triggers quickly for caja when
+ * it does the desktop background allocations, raising the limit
+ * such that a lot of temporary large allocations end up on the
+ * heap and are thus not returned to the OS. To fix this we set
+ * a hardcoded limit. I don't know what a good value is, but 128K
+ * was the old glibc static limit, lets use that.
+ */
+ mallopt (M_MMAP_THRESHOLD, 128 *1024);
+#endif
+
+ g_thread_init (NULL);
+
+ /* This will be done by gtk+ later, but for now, force it to MATE */
+ g_desktop_app_info_set_desktop_env ("MATE");
+
+ if (g_getenv ("CAJA_DEBUG") != NULL)
+ {
+ eel_make_warnings_and_criticals_stop_in_debugger ();
+ }
+
+ /* Initialize gettext support */
+ bindtextdomain (GETTEXT_PACKAGE, MATELOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ textdomain (GETTEXT_PACKAGE);
+
+ autostart_mode = FALSE;
+
+ autostart_id = g_getenv ("DESKTOP_AUTOSTART_ID");
+ if (autostart_id != NULL && *autostart_id != '\0')
+ {
+ autostart_mode = TRUE;
+ }
+
+ /* Get parameters. */
+ remaining = NULL;
+ geometry = NULL;
+ version = FALSE;
+ kill_shell = FALSE;
+ no_default_window = FALSE;
+ no_desktop = FALSE;
+ perform_self_check = FALSE;
+ browser_window = FALSE;
+
+ g_set_prgname ("caja");
+
+ if (g_file_test (DATADIR "/applications/caja.desktop", G_FILE_TEST_EXISTS))
+ {
+ egg_set_desktop_file (DATADIR "/applications/caja.desktop");
+ }
+
+ context = g_option_context_new (_("\n\nBrowse the file system with the file manager"));
+ g_option_context_add_main_entries (context, options, NULL);
+
+ g_option_context_add_group (context, gtk_get_option_group (TRUE));
+ g_option_context_add_group (context, egg_sm_client_get_option_group ());
+
+ error = NULL;
+ if (!g_option_context_parse (context, &argc, &argv, &error))
+ {
+ g_printerr ("Could not parse arguments: %s\n", error->message);
+ g_error_free (error);
+ return 1;
+ }
+
+ g_option_context_free (context);
+
+ if (version)
+ {
+ g_print ("MATE caja " PACKAGE_VERSION "\n");
+ return 0;
+ }
+
+#ifdef HAVE_EXEMPI
+ xmp_init();
+#endif
+
+ setup_debug_log ();
+
+ /* If in autostart mode (aka started by mate-session), we need to ensure
+ * caja starts with the correct options.
+ */
+ if (autostart_mode)
+ {
+ no_default_window = TRUE;
+ no_desktop = FALSE;
+ }
+
+ if (perform_self_check && remaining != NULL)
+ {
+ /* translators: %s is an option (e.g. --check) */
+ fprintf (stderr, _("caja: %s cannot be used with URIs.\n"),
+ "--check");
+ return EXIT_FAILURE;
+ }
+ if (perform_self_check && kill_shell)
+ {
+ fprintf (stderr, _("caja: --check cannot be used with other options.\n"));
+ return EXIT_FAILURE;
+ }
+ if (kill_shell && remaining != NULL)
+ {
+ fprintf (stderr, _("caja: %s cannot be used with URIs.\n"),
+ "--quit");
+ return EXIT_FAILURE;
+ }
+ if (geometry != NULL && remaining != NULL && remaining[0] != NULL && remaining[1] != NULL)
+ {
+ fprintf (stderr, _("caja: --geometry cannot be used with more than one URI.\n"));
+ return EXIT_FAILURE;
+ }
+
+ /* Initialize the services that we use. */
+ LIBXML_TEST_VERSION
+
+ /* Initialize preferences. This is needed so that proper
+ * defaults are available before any preference peeking
+ * happens.
+ */
+ caja_global_preferences_init ();
+
+ /* exit_with_last_window being FALSE, caja can run without window. */
+ exit_with_last_window = eel_preferences_get_boolean (CAJA_PREFERENCES_EXIT_WITH_LAST_WINDOW);
+
+ if (no_desktop)
+ {
+ eel_preferences_set_is_invisible
+ (CAJA_PREFERENCES_SHOW_DESKTOP, TRUE);
+ eel_preferences_set_is_invisible
+ (CAJA_PREFERENCES_DESKTOP_IS_HOME_DIR, TRUE);
+ }
+
+ application = NULL;
+
+ /* Do either the self-check or the real work. */
+ if (perform_self_check)
+ {
+ #ifndef CAJA_OMIT_SELF_CHECK
+ /* Run the checks (each twice) for caja and libcaja-private. */
+
+ caja_run_self_checks ();
+ caja_run_lib_self_checks ();
+ eel_exit_if_self_checks_failed ();
+
+ caja_run_self_checks ();
+ caja_run_lib_self_checks ();
+ eel_exit_if_self_checks_failed ();
+ #endif
+ }
+ else
+ {
+ /* Convert args to URIs */
+ uris = NULL;
+ if (remaining != NULL)
+ {
+ uris_array = g_ptr_array_new ();
+ for (i = 0; remaining[i] != NULL; i++)
+ {
+ file = g_file_new_for_commandline_arg (remaining[i]);
+ if (file != NULL)
+ {
+ uri = g_file_get_uri (file);
+ g_object_unref (file);
+ if (uri)
+ {
+ g_ptr_array_add (uris_array, uri);
+ }
+ }
+ }
+ g_ptr_array_add (uris_array, NULL);
+ uris = (char**) g_ptr_array_free (uris_array, FALSE);
+ g_strfreev (remaining);
+ }
+
+
+ /* Run the caja application. */
+ application = caja_application_new ();
+
+ if (egg_sm_client_is_resumed (application->smclient))
+ {
+ no_default_window = TRUE;
+ }
+
+ caja_application_startup
+ (application,
+ kill_shell, no_default_window, no_desktop,
+ browser_window,
+ geometry,
+ uris);
+ g_strfreev (uris);
+
+ if (unique_app_is_running (application->unique_app) ||
+ kill_shell)
+ {
+ exit_with_last_window = TRUE;
+ }
+
+ if (is_event_loop_needed ())
+ {
+ gtk_main ();
+ }
+ }
+
+ caja_icon_info_clear_caches ();
+
+ if (application != NULL)
+ {
+ g_object_unref (application);
+ }
+
+ eel_debug_shut_down ();
+
+ caja_application_save_accel_map (NULL);
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/caja-main.h b/src/caja-main.h
new file mode 100644
index 00000000..fa93357e
--- /dev/null
+++ b/src/caja-main.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: 8; c-basic-offset: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 1999, 2000 Red Hat, Inc.
+ * Copyright (C) 1999, 2000 Eazel, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* caja-main.c
+ */
+
+#ifndef CAJA_MAIN_H
+#define CAJA_MAIN_H
+
+#include <gtk/gtk.h>
+
+void caja_main_event_loop_register (GtkObject *object);
+gboolean caja_main_is_event_loop_mainstay (GtkObject *object);
+void caja_main_event_loop_quit (gboolean explicit);
+
+#endif /* CAJA_MAIN_H */
+
diff --git a/src/caja-navigation-action.c b/src/caja-navigation-action.c
new file mode 100644
index 00000000..93a43435
--- /dev/null
+++ b/src/caja-navigation-action.c
@@ -0,0 +1,381 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2004 Red Hat, Inc.
+ * Copyright (C) 2003 Marco Pesenti Gritti
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Based on ephy-navigation-action.h from Epiphany
+ *
+ * Authors: Alexander Larsson <[email protected]>
+ * Marco Pesenti Gritti
+ *
+ */
+
+#include <config.h>
+
+#include "caja-navigation-action.h"
+#include "caja-navigation-window.h"
+#include "caja-window-private.h"
+#include "caja-navigation-window-slot.h"
+#include <gtk/gtk.h>
+#include <eel/eel-gtk-extensions.h>
+
+static void caja_navigation_action_init (CajaNavigationAction *action);
+static void caja_navigation_action_class_init (CajaNavigationActionClass *class);
+
+static GObjectClass *parent_class = NULL;
+
+#define CAJA_NAVIGATION_ACTION_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), CAJA_TYPE_NAVIGATION_ACTION, CajaNavigationActionPrivate))
+
+struct CajaNavigationActionPrivate
+{
+ CajaNavigationWindow *window;
+ CajaNavigationDirection direction;
+ char *arrow_tooltip;
+};
+
+enum
+{
+ PROP_0,
+ PROP_ARROW_TOOLTIP,
+ PROP_DIRECTION,
+ PROP_WINDOW
+};
+
+GType
+caja_navigation_action_get_type (void)
+{
+ static GType type = 0;
+
+ if (type == 0)
+ {
+ const GTypeInfo type_info =
+ {
+ sizeof (CajaNavigationActionClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) caja_navigation_action_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ sizeof (CajaNavigationAction),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) caja_navigation_action_init,
+ };
+
+ type = g_type_register_static (GTK_TYPE_ACTION,
+ "CajaNavigationAction",
+ &type_info, 0);
+ }
+
+ return type;
+}
+
+static gboolean
+should_open_in_new_tab (void)
+{
+ /* FIXME this is duplicated */
+ GdkEvent *event;
+
+ event = gtk_get_current_event ();
+ if (event->type == GDK_BUTTON_PRESS || event->type == GDK_BUTTON_RELEASE)
+ {
+ return event->button.button == 2;
+ }
+
+ gdk_event_free (event);
+
+ return FALSE;
+}
+
+static void
+activate_back_or_forward_menu_item (GtkMenuItem *menu_item,
+ CajaNavigationWindow *window,
+ gboolean back)
+{
+ int index;
+
+ g_assert (GTK_IS_MENU_ITEM (menu_item));
+ g_assert (CAJA_IS_NAVIGATION_WINDOW (window));
+
+ index = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (menu_item), "user_data"));
+
+ caja_navigation_window_back_or_forward (window, back, index, should_open_in_new_tab ());
+}
+
+static void
+activate_back_menu_item_callback (GtkMenuItem *menu_item, CajaNavigationWindow *window)
+{
+ activate_back_or_forward_menu_item (menu_item, window, TRUE);
+}
+
+static void
+activate_forward_menu_item_callback (GtkMenuItem *menu_item, CajaNavigationWindow *window)
+{
+ activate_back_or_forward_menu_item (menu_item, window, FALSE);
+}
+
+static void
+fill_menu (CajaNavigationWindow *window,
+ GtkWidget *menu,
+ gboolean back)
+{
+ CajaNavigationWindowSlot *slot;
+ GtkWidget *menu_item;
+ int index;
+ GList *list;
+
+ g_assert (CAJA_IS_NAVIGATION_WINDOW (window));
+
+ slot = CAJA_NAVIGATION_WINDOW_SLOT (CAJA_WINDOW (window)->details->active_pane->active_slot);
+
+ list = back ? slot->back_list : slot->forward_list;
+ index = 0;
+ while (list != NULL)
+ {
+ menu_item = caja_bookmark_menu_item_new (CAJA_BOOKMARK (list->data));
+ g_object_set_data (G_OBJECT (menu_item), "user_data", GINT_TO_POINTER (index));
+ gtk_widget_show (GTK_WIDGET (menu_item));
+ g_signal_connect_object (menu_item, "activate",
+ back
+ ? G_CALLBACK (activate_back_menu_item_callback)
+ : G_CALLBACK (activate_forward_menu_item_callback),
+ window, 0);
+
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
+ list = g_list_next (list);
+ ++index;
+ }
+}
+
+static void
+show_menu_callback (GtkMenuToolButton *button,
+ CajaNavigationAction *action)
+{
+ CajaNavigationActionPrivate *p;
+ CajaNavigationWindow *window;
+ GtkWidget *menu;
+ GList *children;
+ GList *li;
+
+ p = action->priv;
+ window = action->priv->window;
+
+ menu = gtk_menu_tool_button_get_menu (button);
+
+ children = gtk_container_get_children (GTK_CONTAINER (menu));
+ for (li = children; li; li = li->next)
+ {
+ gtk_container_remove (GTK_CONTAINER (menu), li->data);
+ }
+ g_list_free (children);
+
+ switch (p->direction)
+ {
+ case CAJA_NAVIGATION_DIRECTION_FORWARD:
+ fill_menu (window, menu, FALSE);
+ break;
+ case CAJA_NAVIGATION_DIRECTION_BACK:
+ fill_menu (window, menu, TRUE);
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+static gboolean
+proxy_button_press_event_cb (GtkButton *button,
+ GdkEventButton *event,
+ gpointer user_data)
+{
+ if (event->button == 2)
+ {
+ g_signal_emit_by_name (button, "pressed", 0);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+proxy_button_release_event_cb (GtkButton *button,
+ GdkEventButton *event,
+ gpointer user_data)
+{
+ if (event->button == 2)
+ {
+ g_signal_emit_by_name (button, "released", 0);
+ }
+
+ return FALSE;
+}
+
+static void
+connect_proxy (GtkAction *action, GtkWidget *proxy)
+{
+ if (GTK_IS_MENU_TOOL_BUTTON (proxy))
+ {
+ CajaNavigationAction *naction = CAJA_NAVIGATION_ACTION (action);
+ GtkMenuToolButton *button = GTK_MENU_TOOL_BUTTON (proxy);
+ GtkWidget *menu;
+ GtkWidget *child;
+
+ /* set an empty menu, so the arrow button becomes sensitive */
+ menu = gtk_menu_new ();
+ gtk_menu_tool_button_set_menu (button, menu);
+
+ gtk_menu_tool_button_set_arrow_tooltip_text (button,
+ naction->priv->arrow_tooltip);
+
+ g_signal_connect (proxy, "show-menu",
+ G_CALLBACK (show_menu_callback), action);
+
+ /* Make sure that middle click works. Note that there is some code duplication
+ * between here and caja-window-menus.c */
+ child = eel_gtk_menu_tool_button_get_button (button);
+ g_signal_connect (child, "button-press-event", G_CALLBACK (proxy_button_press_event_cb), NULL);
+ g_signal_connect (child, "button-release-event", G_CALLBACK (proxy_button_release_event_cb), NULL);
+ }
+
+ (* GTK_ACTION_CLASS (parent_class)->connect_proxy) (action, proxy);
+}
+
+static void
+disconnect_proxy (GtkAction *action, GtkWidget *proxy)
+{
+ if (GTK_IS_MENU_TOOL_BUTTON (proxy))
+ {
+ GtkWidget *child;
+
+ g_signal_handlers_disconnect_by_func (proxy, G_CALLBACK (show_menu_callback), action);
+
+ child = eel_gtk_menu_tool_button_get_button (GTK_MENU_TOOL_BUTTON (proxy));
+ g_signal_handlers_disconnect_by_func (child, G_CALLBACK (proxy_button_press_event_cb), NULL);
+ g_signal_handlers_disconnect_by_func (child, G_CALLBACK (proxy_button_release_event_cb), NULL);
+ }
+
+ (* GTK_ACTION_CLASS (parent_class)->disconnect_proxy) (action, proxy);
+}
+
+static void
+caja_navigation_action_finalize (GObject *object)
+{
+ CajaNavigationAction *action = CAJA_NAVIGATION_ACTION (object);
+
+ g_free (action->priv->arrow_tooltip);
+
+ (* G_OBJECT_CLASS (parent_class)->finalize) (object);
+}
+
+static void
+caja_navigation_action_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CajaNavigationAction *nav;
+
+ nav = CAJA_NAVIGATION_ACTION (object);
+
+ switch (prop_id)
+ {
+ case PROP_ARROW_TOOLTIP:
+ g_free (nav->priv->arrow_tooltip);
+ nav->priv->arrow_tooltip = g_value_dup_string (value);
+ break;
+ case PROP_DIRECTION:
+ nav->priv->direction = g_value_get_int (value);
+ break;
+ case PROP_WINDOW:
+ nav->priv->window = CAJA_NAVIGATION_WINDOW (g_value_get_object (value));
+ break;
+ }
+}
+
+static void
+caja_navigation_action_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ CajaNavigationAction *nav;
+
+ nav = CAJA_NAVIGATION_ACTION (object);
+
+ switch (prop_id)
+ {
+ case PROP_ARROW_TOOLTIP:
+ g_value_set_string (value, nav->priv->arrow_tooltip);
+ break;
+ case PROP_DIRECTION:
+ g_value_set_int (value, nav->priv->direction);
+ break;
+ case PROP_WINDOW:
+ g_value_set_object (value, nav->priv->window);
+ break;
+ }
+}
+
+static void
+caja_navigation_action_class_init (CajaNavigationActionClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+ GtkActionClass *action_class = GTK_ACTION_CLASS (class);
+
+ object_class->finalize = caja_navigation_action_finalize;
+ object_class->set_property = caja_navigation_action_set_property;
+ object_class->get_property = caja_navigation_action_get_property;
+
+ parent_class = g_type_class_peek_parent (class);
+
+ action_class->toolbar_item_type = GTK_TYPE_MENU_TOOL_BUTTON;
+ action_class->connect_proxy = connect_proxy;
+ action_class->disconnect_proxy = disconnect_proxy;
+
+ g_object_class_install_property (object_class,
+ PROP_ARROW_TOOLTIP,
+ g_param_spec_string ("arrow-tooltip",
+ "Arrow Tooltip",
+ "Arrow Tooltip",
+ NULL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_DIRECTION,
+ g_param_spec_int ("direction",
+ "Direction",
+ "Direction",
+ 0,
+ G_MAXINT,
+ 0,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_WINDOW,
+ g_param_spec_object ("window",
+ "Window",
+ "The navigation window",
+ G_TYPE_OBJECT,
+ G_PARAM_READWRITE));
+
+ g_type_class_add_private (object_class, sizeof(CajaNavigationActionPrivate));
+}
+
+static void
+caja_navigation_action_init (CajaNavigationAction *action)
+{
+ action->priv = CAJA_NAVIGATION_ACTION_GET_PRIVATE (action);
+}
diff --git a/src/caja-navigation-action.h b/src/caja-navigation-action.h
new file mode 100644
index 00000000..59927585
--- /dev/null
+++ b/src/caja-navigation-action.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2004 Red Hat, Inc.
+ * Copyright (C) 2003 Marco Pesenti Gritti
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ *
+ * Based on ephy-navigation-action.h from Epiphany
+ *
+ * Authors: Alexander Larsson <[email protected]>
+ * Marco Pesenti Gritti
+ *
+ */
+
+#ifndef CAJA_NAVIGATION_ACTION_H
+#define CAJA_NAVIGATION_ACTION_H
+
+#include <gtk/gtk.h>
+
+#define CAJA_TYPE_NAVIGATION_ACTION (caja_navigation_action_get_type ())
+#define CAJA_NAVIGATION_ACTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_NAVIGATION_ACTION, CajaNavigationAction))
+#define CAJA_NAVIGATION_ACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_NAVIGATION_ACTION, CajaNavigationActionClass))
+#define CAJA_IS_NAVIGATION_ACTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_NAVIGATION_ACTION))
+#define CAJA_IS_NAVIGATION_ACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), CAJA_TYPE_NAVIGATION_ACTION))
+#define CAJA_NAVIGATION_ACTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), CAJA_TYPE_NAVIGATION_ACTION, CajaNavigationActionClass))
+
+typedef struct _CajaNavigationAction CajaNavigationAction;
+typedef struct _CajaNavigationActionClass CajaNavigationActionClass;
+typedef struct CajaNavigationActionPrivate CajaNavigationActionPrivate;
+
+typedef enum
+{
+ CAJA_NAVIGATION_DIRECTION_BACK,
+ CAJA_NAVIGATION_DIRECTION_FORWARD
+} CajaNavigationDirection;
+
+struct _CajaNavigationAction
+{
+ GtkAction parent;
+
+ /*< private >*/
+ CajaNavigationActionPrivate *priv;
+};
+
+struct _CajaNavigationActionClass
+{
+ GtkActionClass parent_class;
+};
+
+GType caja_navigation_action_get_type (void);
+
+#endif
diff --git a/src/caja-navigation-bar.c b/src/caja-navigation-bar.c
new file mode 100644
index 00000000..3f0c9945
--- /dev/null
+++ b/src/caja-navigation-bar.c
@@ -0,0 +1,170 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Maciej Stachowiak <[email protected]>
+ */
+
+/* caja-navigation-bar.c - Abstract navigation bar class
+ */
+
+#include <config.h>
+#include "caja-navigation-bar.h"
+
+#include <eel/eel-gtk-macros.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+#include <string.h>
+
+enum
+{
+ ACTIVATE,
+ CANCEL,
+ LOCATION_CHANGED,
+ LAST_SIGNAL
+};
+static guint signals[LAST_SIGNAL];
+
+static void caja_navigation_bar_class_init (CajaNavigationBarClass *class);
+static void caja_navigation_bar_init (CajaNavigationBar *bar);
+
+EEL_CLASS_BOILERPLATE (CajaNavigationBar, caja_navigation_bar, GTK_TYPE_HBOX)
+
+EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (caja_navigation_bar, get_location)
+EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (caja_navigation_bar, set_location)
+
+static void
+caja_navigation_bar_class_init (CajaNavigationBarClass *klass)
+{
+ GtkObjectClass *object_class;
+ GtkBindingSet *binding_set;
+
+ object_class = GTK_OBJECT_CLASS (klass);
+
+ signals[ACTIVATE] = g_signal_new
+ ("activate",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaNavigationBarClass,
+ activate),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[CANCEL] = g_signal_new
+ ("cancel",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (CajaNavigationBarClass,
+ cancel),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[LOCATION_CHANGED] = g_signal_new
+ ("location_changed",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaNavigationBarClass,
+ location_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE, 1, G_TYPE_STRING);
+
+ klass->activate = NULL;
+ klass->cancel = NULL;
+
+ binding_set = gtk_binding_set_by_class (klass);
+ gtk_binding_entry_add_signal (binding_set, GDK_Escape, 0, "cancel", 0);
+
+ EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, caja_navigation_bar, get_location);
+ EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, caja_navigation_bar, set_location);
+}
+
+static void
+caja_navigation_bar_init (CajaNavigationBar *bar)
+{
+}
+
+/**
+ * caja_navigation_bar_activate
+ *
+ * Change the navigation bar to an active state.
+ *
+ * @bar: A CajaNavigationBar.
+ */
+void
+caja_navigation_bar_activate (CajaNavigationBar *bar)
+{
+ g_return_if_fail (CAJA_IS_NAVIGATION_BAR (bar));
+
+ g_signal_emit (bar, signals[ACTIVATE], 0);
+}
+
+/**
+ * caja_navigation_bar_get_location
+ *
+ * Return the location displayed in the navigation bar.
+ *
+ * @bar: A CajaNavigationBar.
+ * @location: The uri that should be displayed.
+ */
+char *
+caja_navigation_bar_get_location (CajaNavigationBar *bar)
+{
+ g_return_val_if_fail (CAJA_IS_NAVIGATION_BAR (bar), NULL);
+
+ return EEL_CALL_METHOD_WITH_RETURN_VALUE
+ (CAJA_NAVIGATION_BAR_CLASS, bar,
+ get_location, (bar));
+}
+
+/**
+ * caja_navigation_bar_set_location
+ *
+ * Change the location displayed in the navigation bar.
+ *
+ * @bar: A CajaNavigationBar.
+ * @location: The uri that should be displayed.
+ */
+void
+caja_navigation_bar_set_location (CajaNavigationBar *bar,
+ const char *location)
+{
+ g_return_if_fail (CAJA_IS_NAVIGATION_BAR (bar));
+
+ EEL_CALL_METHOD (CAJA_NAVIGATION_BAR_CLASS, bar,
+ set_location, (bar, location));
+}
+
+void
+caja_navigation_bar_location_changed (CajaNavigationBar *bar)
+{
+ char *location;
+
+ g_return_if_fail (CAJA_IS_NAVIGATION_BAR (bar));
+
+ location = caja_navigation_bar_get_location (bar);
+ g_signal_emit (bar,
+ signals[LOCATION_CHANGED], 0,
+ location);
+ g_free (location);
+}
diff --git a/src/caja-navigation-bar.h b/src/caja-navigation-bar.h
new file mode 100644
index 00000000..ee369b6a
--- /dev/null
+++ b/src/caja-navigation-bar.h
@@ -0,0 +1,77 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Maciej Stachowiak <[email protected]>
+ */
+
+/* caja-navigation-bar.h - Abstract navigation bar class
+ */
+
+#ifndef CAJA_NAVIGATION_BAR_H
+#define CAJA_NAVIGATION_BAR_H
+
+#include <gtk/gtk.h>
+
+#define CAJA_TYPE_NAVIGATION_BAR caja_navigation_bar_get_type()
+#define CAJA_NAVIGATION_BAR(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_NAVIGATION_BAR, CajaNavigationBar))
+#define CAJA_NAVIGATION_BAR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_NAVIGATION_BAR, CajaNavigationBarClass))
+#define CAJA_IS_NAVIGATION_BAR(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_NAVIGATION_BAR))
+#define CAJA_IS_NAVIGATION_BAR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_NAVIGATION_BAR))
+#define CAJA_NAVIGATION_BAR_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_NAVIGATION_BAR, CajaNavigationBarClass))
+
+typedef struct
+{
+ GtkHBox parent;
+} CajaNavigationBar;
+
+typedef struct
+{
+ GtkHBoxClass parent_class;
+
+ /* signals */
+ void (* location_changed) (CajaNavigationBar *bar,
+ const char *location);
+ void (* cancel) (CajaNavigationBar *bar);
+
+ /* virtual methods */
+ void (* activate) (CajaNavigationBar *bar);
+ char * (* get_location) (CajaNavigationBar *bar);
+ void (* set_location) (CajaNavigationBar *bar,
+ const char *location);
+
+} CajaNavigationBarClass;
+
+GType caja_navigation_bar_get_type (void);
+void caja_navigation_bar_activate (CajaNavigationBar *bar);
+char * caja_navigation_bar_get_location (CajaNavigationBar *bar);
+void caja_navigation_bar_set_location (CajaNavigationBar *bar,
+ const char *location);
+
+/* `protected' function meant to be used by subclasses to emit the `location_changed' signal */
+void caja_navigation_bar_location_changed (CajaNavigationBar *bar);
+
+#endif /* CAJA_NAVIGATION_BAR_H */
diff --git a/src/caja-navigation-window-menus.c b/src/caja-navigation-window-menus.c
new file mode 100644
index 00000000..9e395bef
--- /dev/null
+++ b/src/caja-navigation-window-menus.c
@@ -0,0 +1,1099 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2000, 2001 Eazel, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Author: John Sullivan <[email protected]>
+ */
+
+/* caja-window-menus.h - implementation of caja window menu operations,
+ * split into separate file just for convenience.
+ */
+#include <config.h>
+
+#include <locale.h>
+
+#include "caja-actions.h"
+#include "caja-notebook.h"
+#include "caja-navigation-action.h"
+#include "caja-zoom-action.h"
+#include "caja-view-as-action.h"
+#include "caja-application.h"
+#include "caja-bookmark-list.h"
+#include "caja-bookmarks-window.h"
+#include "caja-file-management-properties.h"
+#include "caja-property-browser.h"
+#include "caja-window-manage-views.h"
+#include "caja-window-private.h"
+#include "caja-window-bookmarks.h"
+#include "caja-navigation-window-pane.h"
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-mate-extensions.h>
+#include <eel/eel-stock-dialogs.h>
+#include <eel/eel-string.h>
+#include <eel/eel-xml-extensions.h>
+#include <libxml/parser.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <libcaja-private/caja-file-utilities.h>
+#include <libcaja-private/caja-global-preferences.h>
+#include <libcaja-private/caja-ui-utilities.h>
+#include <libcaja-private/caja-undo-manager.h>
+#include <libcaja-private/caja-search-engine.h>
+#include <libcaja-private/caja-signaller.h>
+
+#define MENU_PATH_HISTORY_PLACEHOLDER "/MenuBar/Other Menus/Go/History Placeholder"
+
+#define RESPONSE_FORGET 1000
+#define MENU_ITEM_MAX_WIDTH_CHARS 32
+
+static void schedule_refresh_go_menu (CajaNavigationWindow *window);
+
+static void
+action_close_all_windows_callback (GtkAction *action,
+ gpointer user_data)
+{
+ caja_application_close_all_navigation_windows ();
+}
+
+static gboolean
+should_open_in_new_tab (void)
+{
+ /* FIXME this is duplicated */
+ GdkEvent *event;
+
+ event = gtk_get_current_event ();
+
+ if (event == NULL)
+ {
+ return FALSE;
+ }
+
+ if (event->type == GDK_BUTTON_PRESS || event->type == GDK_BUTTON_RELEASE)
+ {
+ return event->button.button == 2;
+ }
+
+ gdk_event_free (event);
+
+ return FALSE;
+}
+
+static void
+action_back_callback (GtkAction *action,
+ gpointer user_data)
+{
+ caja_navigation_window_back_or_forward (CAJA_NAVIGATION_WINDOW (user_data),
+ TRUE, 0, should_open_in_new_tab ());
+}
+
+static void
+action_forward_callback (GtkAction *action,
+ gpointer user_data)
+{
+ caja_navigation_window_back_or_forward (CAJA_NAVIGATION_WINDOW (user_data),
+ FALSE, 0, should_open_in_new_tab ());
+}
+
+static void
+forget_history_if_yes (GtkDialog *dialog, int response, gpointer callback_data)
+{
+ if (response == RESPONSE_FORGET)
+ {
+ caja_forget_history ();
+ }
+ gtk_object_destroy (GTK_OBJECT (dialog));
+}
+
+static void
+forget_history_if_confirmed (CajaWindow *window)
+{
+ GtkDialog *dialog;
+
+ dialog = eel_create_question_dialog (_("Are you sure you want to clear the list "
+ "of locations you have visited?"),
+ NULL,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_CLEAR, RESPONSE_FORGET,
+ GTK_WINDOW (window));
+
+ gtk_widget_show (GTK_WIDGET (dialog));
+
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (forget_history_if_yes), NULL);
+
+ gtk_dialog_set_default_response (dialog, GTK_RESPONSE_CANCEL);
+}
+
+static void
+action_clear_history_callback (GtkAction *action,
+ gpointer user_data)
+{
+ forget_history_if_confirmed (CAJA_WINDOW (user_data));
+}
+
+static void
+action_split_view_switch_next_pane_callback(GtkAction *action,
+ gpointer user_data)
+{
+ caja_window_pane_switch_to (caja_window_get_next_pane (CAJA_WINDOW (user_data)));
+}
+
+static void
+action_split_view_same_location_callback (GtkAction *action,
+ gpointer user_data)
+{
+ CajaWindow *window;
+ CajaWindowPane *next_pane;
+ GFile *location;
+
+ window = CAJA_WINDOW (user_data);
+ next_pane = caja_window_get_next_pane (window);
+
+ if (!next_pane)
+ {
+ return;
+ }
+ location = caja_window_slot_get_location (next_pane->active_slot);
+ if (location)
+ {
+ caja_window_slot_go_to (window->details->active_pane->active_slot, location, FALSE);
+ g_object_unref (location);
+ }
+}
+
+static void
+action_show_hide_toolbar_callback (GtkAction *action,
+ gpointer user_data)
+{
+ CajaNavigationWindow *window;
+
+ window = CAJA_NAVIGATION_WINDOW (user_data);
+
+ if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
+ {
+ caja_navigation_window_show_toolbar (window);
+ }
+ else
+ {
+ caja_navigation_window_hide_toolbar (window);
+ }
+}
+
+
+
+static void
+action_show_hide_sidebar_callback (GtkAction *action,
+ gpointer user_data)
+{
+ CajaNavigationWindow *window;
+
+ window = CAJA_NAVIGATION_WINDOW (user_data);
+
+ if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
+ {
+ caja_navigation_window_show_sidebar (window);
+ }
+ else
+ {
+ caja_navigation_window_hide_sidebar (window);
+ }
+}
+
+static void
+pane_show_hide_location_bar (CajaNavigationWindowPane *pane, gboolean is_active)
+{
+ if (caja_navigation_window_pane_location_bar_showing (pane) != is_active)
+ {
+ if (is_active)
+ {
+ caja_navigation_window_pane_show_location_bar (pane, TRUE);
+ }
+ else
+ {
+ caja_navigation_window_pane_hide_location_bar (pane, TRUE);
+ }
+ }
+}
+
+static void
+action_show_hide_location_bar_callback (GtkAction *action,
+ gpointer user_data)
+{
+ CajaWindow *window;
+ GList *walk;
+ gboolean is_active;
+
+ window = CAJA_WINDOW (user_data);
+
+ is_active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
+
+ /* Do the active pane first, because this will trigger an update of the menu items,
+ * which in turn relies on the active pane. */
+ pane_show_hide_location_bar (CAJA_NAVIGATION_WINDOW_PANE (window->details->active_pane), is_active);
+
+ for (walk = window->details->panes; walk; walk = walk->next)
+ {
+ pane_show_hide_location_bar (CAJA_NAVIGATION_WINDOW_PANE (walk->data), is_active);
+ }
+}
+
+static void
+action_show_hide_statusbar_callback (GtkAction *action,
+ gpointer user_data)
+{
+ CajaNavigationWindow *window;
+
+ window = CAJA_NAVIGATION_WINDOW (user_data);
+
+ if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
+ {
+ caja_navigation_window_show_status_bar (window);
+ }
+ else
+ {
+ caja_navigation_window_hide_status_bar (window);
+ }
+}
+
+static void
+action_split_view_callback (GtkAction *action,
+ gpointer user_data)
+{
+ CajaNavigationWindow *window;
+ gboolean is_active;
+
+ window = CAJA_NAVIGATION_WINDOW (user_data);
+
+ is_active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
+ if (is_active != caja_navigation_window_split_view_showing (window))
+ {
+ CajaWindow *caja_window;
+
+ if (is_active)
+ {
+ caja_navigation_window_split_view_on (window);
+ }
+ else
+ {
+ caja_navigation_window_split_view_off (window);
+ }
+ caja_window = CAJA_WINDOW (window);
+ if (caja_window->details->active_pane && caja_window->details->active_pane->active_slot)
+ {
+ caja_view_update_menus (caja_window->details->active_pane->active_slot->content_view);
+ }
+ }
+}
+
+void
+caja_navigation_window_update_show_hide_menu_items (CajaNavigationWindow *window)
+{
+ GtkAction *action;
+
+ g_assert (CAJA_IS_NAVIGATION_WINDOW (window));
+
+ action = gtk_action_group_get_action (window->details->navigation_action_group,
+ CAJA_ACTION_SHOW_HIDE_TOOLBAR);
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
+ caja_navigation_window_toolbar_showing (window));
+
+ action = gtk_action_group_get_action (window->details->navigation_action_group,
+ CAJA_ACTION_SHOW_HIDE_SIDEBAR);
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
+ caja_navigation_window_sidebar_showing (window));
+
+ action = gtk_action_group_get_action (window->details->navigation_action_group,
+ CAJA_ACTION_SHOW_HIDE_LOCATION_BAR);
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
+ caja_navigation_window_pane_location_bar_showing (CAJA_NAVIGATION_WINDOW_PANE (CAJA_WINDOW (window)->details->active_pane)));
+
+ action = gtk_action_group_get_action (window->details->navigation_action_group,
+ CAJA_ACTION_SHOW_HIDE_STATUSBAR);
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
+ caja_navigation_window_status_bar_showing (window));
+
+ action = gtk_action_group_get_action (window->details->navigation_action_group,
+ CAJA_ACTION_SHOW_HIDE_EXTRA_PANE);
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
+ caja_navigation_window_split_view_showing (window));
+}
+
+void
+caja_navigation_window_update_spatial_menu_item (CajaNavigationWindow *window)
+{
+ GtkAction *action;
+
+ g_assert (CAJA_IS_NAVIGATION_WINDOW (window));
+
+ action = gtk_action_group_get_action (window->details->navigation_action_group,
+ CAJA_ACTION_FOLDER_WINDOW);
+ gtk_action_set_visible (action,
+ !eel_preferences_get_boolean (CAJA_PREFERENCES_ALWAYS_USE_BROWSER));
+}
+
+static void
+action_add_bookmark_callback (GtkAction *action,
+ gpointer user_data)
+{
+ caja_window_add_bookmark_for_current_location (CAJA_WINDOW (user_data));
+}
+
+static void
+action_edit_bookmarks_callback (GtkAction *action,
+ gpointer user_data)
+{
+ caja_window_edit_bookmarks (CAJA_WINDOW (user_data));
+}
+
+void
+caja_navigation_window_remove_go_menu_callback (CajaNavigationWindow *window)
+{
+ if (window->details->refresh_go_menu_idle_id != 0)
+ {
+ g_source_remove (window->details->refresh_go_menu_idle_id);
+ window->details->refresh_go_menu_idle_id = 0;
+ }
+}
+
+void
+caja_navigation_window_remove_go_menu_items (CajaNavigationWindow *window)
+{
+ GtkUIManager *ui_manager;
+
+ ui_manager = caja_window_get_ui_manager (CAJA_WINDOW (window));
+ if (window->details->go_menu_merge_id != 0)
+ {
+ gtk_ui_manager_remove_ui (ui_manager,
+ window->details->go_menu_merge_id);
+ window->details->go_menu_merge_id = 0;
+ }
+ if (window->details->go_menu_action_group != NULL)
+ {
+ gtk_ui_manager_remove_action_group (ui_manager,
+ window->details->go_menu_action_group);
+ window->details->go_menu_action_group = NULL;
+ }
+}
+
+static void
+show_bogus_history_window (CajaWindow *window,
+ CajaBookmark *bookmark)
+{
+ GFile *file;
+ char *uri_for_display;
+ char *detail;
+
+ file = caja_bookmark_get_location (bookmark);
+ uri_for_display = g_file_get_parse_name (file);
+
+ detail = g_strdup_printf (_("The location \"%s\" does not exist."), uri_for_display);
+
+ eel_show_warning_dialog (_("The history location doesn't exist."),
+ detail,
+ GTK_WINDOW (window));
+
+ g_object_unref (file);
+ g_free (uri_for_display);
+ g_free (detail);
+}
+
+static void
+connect_proxy_cb (GtkActionGroup *action_group,
+ GtkAction *action,
+ GtkWidget *proxy,
+ gpointer dummy)
+{
+ GtkLabel *label;
+
+ if (!GTK_IS_MENU_ITEM (proxy))
+ return;
+
+ label = GTK_LABEL (gtk_bin_get_child (GTK_BIN (proxy)));
+
+ gtk_label_set_use_underline (label, FALSE);
+ gtk_label_set_ellipsize (label, PANGO_ELLIPSIZE_END);
+ gtk_label_set_max_width_chars (label, MENU_ITEM_MAX_WIDTH_CHARS);
+}
+
+static const char* icon_entries[] =
+{
+ "/MenuBar/Other Menus/Go/Home",
+ "/MenuBar/Other Menus/Go/Computer",
+ "/MenuBar/Other Menus/Go/Go to Templates",
+ "/MenuBar/Other Menus/Go/Go to Trash",
+ "/MenuBar/Other Menus/Go/Go to Network",
+ "/MenuBar/Other Menus/Go/Go to Location"
+};
+
+/**
+ * refresh_go_menu:
+ *
+ * Refresh list of bookmarks at end of Go menu to match centralized history list.
+ * @window: The CajaWindow whose Go menu will be refreshed.
+ **/
+static void
+refresh_go_menu (CajaNavigationWindow *window)
+{
+ GtkUIManager *ui_manager;
+ GList *node;
+ GtkWidget *menuitem;
+ int index;
+ int i;
+
+ g_assert (CAJA_IS_NAVIGATION_WINDOW (window));
+
+ /* Unregister any pending call to this function. */
+ caja_navigation_window_remove_go_menu_callback (window);
+
+ /* Remove old set of history items. */
+ caja_navigation_window_remove_go_menu_items (window);
+
+ ui_manager = caja_window_get_ui_manager (CAJA_WINDOW (window));
+
+ window->details->go_menu_merge_id = gtk_ui_manager_new_merge_id (ui_manager);
+ window->details->go_menu_action_group = gtk_action_group_new ("GoMenuGroup");
+ g_signal_connect (window->details->go_menu_action_group, "connect-proxy",
+ G_CALLBACK (connect_proxy_cb), NULL);
+
+ gtk_ui_manager_insert_action_group (ui_manager,
+ window->details->go_menu_action_group,
+ -1);
+ g_object_unref (window->details->go_menu_action_group);
+
+ for (i = 0; i < G_N_ELEMENTS (icon_entries); i++)
+ {
+ menuitem = gtk_ui_manager_get_widget (
+ ui_manager,
+ icon_entries[i]);
+
+ gtk_image_menu_item_set_always_show_image (
+ GTK_IMAGE_MENU_ITEM (menuitem), TRUE);
+ }
+
+ /* Add in a new set of history items. */
+ for (node = caja_get_history_list (), index = 0;
+ node != NULL && index < 10;
+ node = node->next, index++)
+ {
+ caja_menus_append_bookmark_to_menu
+ (CAJA_WINDOW (window),
+ CAJA_BOOKMARK (node->data),
+ MENU_PATH_HISTORY_PLACEHOLDER,
+ "history",
+ index,
+ window->details->go_menu_action_group,
+ window->details->go_menu_merge_id,
+ G_CALLBACK (schedule_refresh_go_menu),
+ show_bogus_history_window);
+ }
+}
+
+static gboolean
+refresh_go_menu_idle_callback (gpointer data)
+{
+ g_assert (CAJA_IS_NAVIGATION_WINDOW (data));
+
+ refresh_go_menu (CAJA_NAVIGATION_WINDOW (data));
+
+ /* Don't call this again (unless rescheduled) */
+ return FALSE;
+}
+
+static void
+schedule_refresh_go_menu (CajaNavigationWindow *window)
+{
+ g_assert (CAJA_IS_NAVIGATION_WINDOW (window));
+
+ if (window->details->refresh_go_menu_idle_id == 0)
+ {
+ window->details->refresh_go_menu_idle_id
+ = g_idle_add (refresh_go_menu_idle_callback,
+ window);
+ }
+}
+
+/**
+ * caja_navigation_window_initialize_go_menu
+ *
+ * Wire up signals so we'll be notified when history list changes.
+ */
+static void
+caja_navigation_window_initialize_go_menu (CajaNavigationWindow *window)
+{
+ /* Recreate bookmarks part of menu if history list changes
+ */
+ g_signal_connect_object (caja_signaller_get_current (), "history_list_changed",
+ G_CALLBACK (schedule_refresh_go_menu), window, G_CONNECT_SWAPPED);
+}
+
+void
+caja_navigation_window_update_split_view_actions_sensitivity (CajaNavigationWindow *window)
+{
+ CajaWindow *win;
+ GtkActionGroup *action_group;
+ GtkAction *action;
+ gboolean have_multiple_panes;
+ gboolean next_pane_is_in_same_location;
+ GFile *active_pane_location;
+ GFile *next_pane_location;
+ CajaWindowPane *next_pane;
+
+ g_assert (CAJA_IS_NAVIGATION_WINDOW (window));
+
+ action_group = window->details->navigation_action_group;
+ win = CAJA_WINDOW (window);
+
+ /* collect information */
+ have_multiple_panes = (win->details->panes && win->details->panes->next);
+ if (win->details->active_pane->active_slot)
+ {
+ active_pane_location = caja_window_slot_get_location (win->details->active_pane->active_slot);
+ }
+ else
+ {
+ active_pane_location = NULL;
+ }
+ next_pane = caja_window_get_next_pane (win);
+ if (next_pane && next_pane->active_slot)
+ {
+ next_pane_location = caja_window_slot_get_location (next_pane->active_slot);
+ next_pane_is_in_same_location = (active_pane_location && next_pane_location &&
+ g_file_equal (active_pane_location, next_pane_location));
+ }
+ else
+ {
+ next_pane_location = NULL;
+ next_pane_is_in_same_location = FALSE;
+ }
+
+ /* switch to next pane */
+ action = gtk_action_group_get_action (action_group, "SplitViewNextPane");
+ gtk_action_set_sensitive (action, have_multiple_panes);
+
+ /* same location */
+ action = gtk_action_group_get_action (action_group, "SplitViewSameLocation");
+ gtk_action_set_sensitive (action, have_multiple_panes && !next_pane_is_in_same_location);
+
+ /* clean up */
+ if (active_pane_location)
+ {
+ g_object_unref (active_pane_location);
+ }
+ if (next_pane_location)
+ {
+ g_object_unref (next_pane_location);
+ }
+}
+
+static void
+action_new_window_callback (GtkAction *action,
+ gpointer user_data)
+{
+ CajaWindow *current_window;
+ CajaWindow *new_window;
+
+ current_window = CAJA_WINDOW (user_data);
+ new_window = caja_application_create_navigation_window (
+ current_window->application,
+ NULL,
+ gtk_window_get_screen (GTK_WINDOW (current_window)));
+ caja_window_go_home (new_window);
+}
+
+static void
+action_new_tab_callback (GtkAction *action,
+ gpointer user_data)
+{
+ CajaWindow *window;
+ CajaWindowSlot *current_slot;
+ CajaWindowSlot *new_slot;
+ CajaWindowOpenFlags flags;
+ GFile *location;
+ int new_slot_position;
+ char *scheme;
+
+ window = CAJA_WINDOW (user_data);
+ current_slot = window->details->active_pane->active_slot;
+ location = caja_window_slot_get_location (current_slot);
+
+ if (location != NULL)
+ {
+ flags = 0;
+
+ new_slot_position = eel_preferences_get_enum (CAJA_PREFERENCES_NEW_TAB_POSITION);
+ if (new_slot_position == CAJA_NEW_TAB_POSITION_END)
+ {
+ flags = CAJA_WINDOW_OPEN_SLOT_APPEND;
+ }
+
+ scheme = g_file_get_uri_scheme (location);
+ if (!strcmp (scheme, "x-caja-search"))
+ {
+ g_object_unref (location);
+ location = g_file_new_for_path (g_get_home_dir ());
+ }
+ g_free (scheme);
+
+ new_slot = caja_window_open_slot (current_slot->pane, flags);
+ caja_window_set_active_slot (window, new_slot);
+ caja_window_slot_go_to (new_slot, location, FALSE);
+ g_object_unref (location);
+ }
+}
+
+static void
+action_folder_window_callback (GtkAction *action,
+ gpointer user_data)
+{
+ CajaWindow *current_window;
+ CajaWindowSlot *slot;
+ GFile *current_location;
+
+ current_window = CAJA_WINDOW (user_data);
+ slot = current_window->details->active_pane->active_slot;
+ current_location = caja_window_slot_get_location (slot);
+ caja_application_present_spatial_window (
+ current_window->application,
+ current_window,
+ NULL,
+ current_location,
+ gtk_window_get_screen (GTK_WINDOW (current_window)));
+ if (current_location != NULL)
+ {
+ g_object_unref (current_location);
+ }
+}
+
+static void
+action_go_to_location_callback (GtkAction *action,
+ gpointer user_data)
+{
+ CajaWindow *window;
+
+ window = CAJA_WINDOW (user_data);
+
+ caja_window_prompt_for_location (window, NULL);
+}
+
+/* The ctrl-f Keyboard shortcut always enables, rather than toggles
+ the search mode */
+static void
+action_show_search_callback (GtkAction *action,
+ gpointer user_data)
+{
+ GtkAction *search_action;
+ CajaNavigationWindow *window;
+
+ window = CAJA_NAVIGATION_WINDOW (user_data);
+
+ search_action =
+ gtk_action_group_get_action (window->details->navigation_action_group,
+ CAJA_ACTION_SEARCH);
+
+ if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (search_action)))
+ {
+ /* Already visible, just show it */
+ caja_navigation_window_show_search (window);
+ }
+ else
+ {
+ /* Otherwise, enable */
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (search_action),
+ TRUE);
+ }
+}
+
+static void
+action_show_hide_search_callback (GtkAction *action,
+ gpointer user_data)
+{
+ CajaNavigationWindow *window;
+
+ /* This is used when toggling the action for updating the UI
+ state only, not actually activating the action */
+ if (g_object_get_data (G_OBJECT (action), "blocked") != NULL)
+ {
+ return;
+ }
+
+ window = CAJA_NAVIGATION_WINDOW (user_data);
+
+ if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
+ {
+ caja_navigation_window_show_search (window);
+ }
+ else
+ {
+ CajaWindowSlot *slot;
+ GFile *location = NULL;
+
+ slot = CAJA_WINDOW (window)->details->active_pane->active_slot;
+
+ /* Use the location bar as the return location */
+ if (slot->query_editor == NULL)
+ {
+ location = caja_window_slot_get_location (slot);
+ /* Use the search location as the return location */
+ }
+ else
+ {
+ CajaQuery *query;
+ char *uri;
+
+ query = caja_query_editor_get_query (slot->query_editor);
+ if (query != NULL)
+ {
+ uri = caja_query_get_location (query);
+ if (uri != NULL)
+ {
+ location = g_file_new_for_uri (uri);
+ g_free (uri);
+ }
+ g_object_unref (query);
+ }
+ }
+
+ /* Last try: use the home directory as the return location */
+ if (location == NULL)
+ {
+ location = g_file_new_for_path (g_get_home_dir ());
+ }
+
+ caja_window_go_to (CAJA_WINDOW (window), location);
+ g_object_unref (location);
+
+ caja_navigation_window_hide_search (window);
+ }
+}
+
+static void
+action_tabs_previous_callback (GtkAction *action,
+ gpointer user_data)
+{
+ CajaNavigationWindowPane *pane;
+
+ pane = CAJA_NAVIGATION_WINDOW_PANE (CAJA_WINDOW (user_data)->details->active_pane);
+ caja_notebook_set_current_page_relative (CAJA_NOTEBOOK (pane->notebook), -1);
+}
+
+static void
+action_tabs_next_callback (GtkAction *action,
+ gpointer user_data)
+{
+ CajaNavigationWindowPane *pane;
+
+ pane = CAJA_NAVIGATION_WINDOW_PANE (CAJA_WINDOW (user_data)->details->active_pane);
+ caja_notebook_set_current_page_relative (CAJA_NOTEBOOK (pane->notebook), 1);
+}
+
+static void
+action_tabs_move_left_callback (GtkAction *action,
+ gpointer user_data)
+{
+ CajaNavigationWindowPane *pane;
+
+ pane = CAJA_NAVIGATION_WINDOW_PANE (CAJA_WINDOW (user_data)->details->active_pane);
+ caja_notebook_reorder_current_child_relative (CAJA_NOTEBOOK (pane->notebook), -1);
+}
+
+static void
+action_tabs_move_right_callback (GtkAction *action,
+ gpointer user_data)
+{
+ CajaNavigationWindowPane *pane;
+
+ pane = CAJA_NAVIGATION_WINDOW_PANE (CAJA_WINDOW (user_data)->details->active_pane);
+ caja_notebook_reorder_current_child_relative (CAJA_NOTEBOOK (pane->notebook), 1);
+}
+
+static void
+action_tab_change_action_activate_callback (GtkAction *action, gpointer user_data)
+{
+ CajaWindow *window;
+
+ window = CAJA_WINDOW (user_data);
+ if (window && window->details->active_pane)
+ {
+ GtkNotebook *notebook;
+ notebook = GTK_NOTEBOOK (CAJA_NAVIGATION_WINDOW_PANE (window->details->active_pane)->notebook);
+ if (notebook)
+ {
+ int num;
+ num = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (action), "num"));
+ if (num < gtk_notebook_get_n_pages (notebook))
+ {
+ gtk_notebook_set_current_page (notebook, num);
+ }
+ }
+ }
+}
+
+static const GtkActionEntry navigation_entries[] =
+{
+ /* name, stock id, label */ { "Go", NULL, N_("_Go") },
+ /* name, stock id, label */ { "Bookmarks", NULL, N_("_Bookmarks") },
+ /* name, stock id, label */ { "Tabs", NULL, N_("_Tabs") },
+ /* name, stock id, label */ { "New Window", "window-new", N_("New _Window"),
+ "<control>N", N_("Open another Caja window for the displayed location"),
+ G_CALLBACK (action_new_window_callback)
+ },
+ /* name, stock id, label */ { "New Tab", "tab-new", N_("New _Tab"),
+ "<control>T", N_("Open another tab for the displayed location"),
+ G_CALLBACK (action_new_tab_callback)
+ },
+ /* name, stock id, label */ { "Folder Window", "folder", N_("Open Folder W_indow"),
+ NULL, N_("Open a folder window for the displayed location"),
+ G_CALLBACK (action_folder_window_callback)
+ },
+ /* name, stock id, label */ { "Close All Windows", NULL, N_("Close _All Windows"),
+ "<control>Q", N_("Close all Navigation windows"),
+ G_CALLBACK (action_close_all_windows_callback)
+ },
+ /* name, stock id, label */ { "Go to Location", NULL, N_("_Location..."),
+ "<control>L", N_("Specify a location to open"),
+ G_CALLBACK (action_go_to_location_callback)
+ },
+ /* name, stock id, label */ { "Clear History", NULL, N_("Clea_r History"),
+ NULL, N_("Clear contents of Go menu and Back/Forward lists"),
+ G_CALLBACK (action_clear_history_callback)
+ },
+ /* name, stock id, label */ { "SplitViewNextPane", NULL, N_("S_witch to Other Pane"),
+ "F6", N_("Move focus to the other pane in a split view window"),
+ G_CALLBACK (action_split_view_switch_next_pane_callback)
+ },
+ /* name, stock id, label */ { "SplitViewSameLocation", NULL, N_("Sa_me Location as Other Pane"),
+ NULL, N_("Go to the same location as in the extra pane"),
+ G_CALLBACK (action_split_view_same_location_callback)
+ },
+ /* name, stock id, label */ { "Add Bookmark", GTK_STOCK_ADD, N_("_Add Bookmark"),
+ "<control>d", N_("Add a bookmark for the current location to this menu"),
+ G_CALLBACK (action_add_bookmark_callback)
+ },
+ /* name, stock id, label */ { "Edit Bookmarks", NULL, N_("_Edit Bookmarks..."),
+ "<control>b", N_("Display a window that allows editing the bookmarks in this menu"),
+ G_CALLBACK (action_edit_bookmarks_callback)
+ },
+ {
+ "TabsPrevious", NULL, N_("_Previous Tab"), "<control>Page_Up",
+ N_("Activate previous tab"),
+ G_CALLBACK (action_tabs_previous_callback)
+ },
+ {
+ "TabsNext", NULL, N_("_Next Tab"), "<control>Page_Down",
+ N_("Activate next tab"),
+ G_CALLBACK (action_tabs_next_callback)
+ },
+ {
+ "TabsMoveLeft", NULL, N_("Move Tab _Left"), "<shift><control>Page_Up",
+ N_("Move current tab to left"),
+ G_CALLBACK (action_tabs_move_left_callback)
+ },
+ {
+ "TabsMoveRight", NULL, N_("Move Tab _Right"), "<shift><control>Page_Down",
+ N_("Move current tab to right"),
+ G_CALLBACK (action_tabs_move_right_callback)
+ },
+ {
+ "ShowSearch", NULL, N_("S_how Search"), "<control>f",
+ N_("Show search"),
+ G_CALLBACK (action_show_search_callback)
+ }
+};
+
+static const GtkToggleActionEntry navigation_toggle_entries[] =
+{
+ /* name, stock id */ { "Show Hide Toolbar", NULL,
+ /* label, accelerator */ N_("_Main Toolbar"), NULL,
+ /* tooltip */ N_("Change the visibility of this window's main toolbar"),
+ G_CALLBACK (action_show_hide_toolbar_callback),
+ /* is_active */ TRUE
+ },
+ /* name, stock id */ { "Show Hide Sidebar", NULL,
+ /* label, accelerator */ N_("_Side Pane"), "F9",
+ /* tooltip */ N_("Change the visibility of this window's side pane"),
+ G_CALLBACK (action_show_hide_sidebar_callback),
+ /* is_active */ TRUE
+ },
+ /* name, stock id */ { "Show Hide Location Bar", NULL,
+ /* label, accelerator */ N_("Location _Bar"), NULL,
+ /* tooltip */ N_("Change the visibility of this window's location bar"),
+ G_CALLBACK (action_show_hide_location_bar_callback),
+ /* is_active */ TRUE
+ },
+ /* name, stock id */ { "Show Hide Statusbar", NULL,
+ /* label, accelerator */ N_("St_atusbar"), NULL,
+ /* tooltip */ N_("Change the visibility of this window's statusbar"),
+ G_CALLBACK (action_show_hide_statusbar_callback),
+ /* is_active */ TRUE
+ },
+ /* name, stock id */ { "Search", "gtk-find",
+ /* label, accelerator */ N_("_Search for Files..."),
+ /* Accelerator is in ShowSearch */"",
+ /* tooltip */ N_("Search documents and folders by name"),
+ G_CALLBACK (action_show_hide_search_callback),
+ /* is_active */ FALSE
+ },
+ /* name, stock id */ {
+ CAJA_ACTION_SHOW_HIDE_EXTRA_PANE, NULL,
+ /* label, accelerator */ N_("E_xtra Pane"), "F3",
+ /* tooltip */ N_("Open an extra folder view side-by-side"),
+ G_CALLBACK (action_split_view_callback),
+ /* is_active */ FALSE
+ },
+};
+
+void
+caja_navigation_window_initialize_actions (CajaNavigationWindow *window)
+{
+ GtkActionGroup *action_group;
+ GtkUIManager *ui_manager;
+ GtkAction *action;
+ int i;
+
+ action_group = gtk_action_group_new ("NavigationActions");
+ gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
+ window->details->navigation_action_group = action_group;
+ gtk_action_group_add_actions (action_group,
+ navigation_entries, G_N_ELEMENTS (navigation_entries),
+ window);
+ gtk_action_group_add_toggle_actions (action_group,
+ navigation_toggle_entries, G_N_ELEMENTS (navigation_toggle_entries),
+ window);
+
+ action = g_object_new (CAJA_TYPE_NAVIGATION_ACTION,
+ "name", "Back",
+ "label", _("_Back"),
+ "stock_id", GTK_STOCK_GO_BACK,
+ "tooltip", _("Go to the previous visited location"),
+ "arrow-tooltip", _("Back history"),
+ "window", window,
+ "direction", CAJA_NAVIGATION_DIRECTION_BACK,
+ "is_important", TRUE,
+ NULL);
+ g_signal_connect (action, "activate",
+ G_CALLBACK (action_back_callback), window);
+ gtk_action_group_add_action_with_accel (action_group,
+ action,
+ "<alt>Left");
+ g_object_unref (action);
+
+ action = g_object_new (CAJA_TYPE_NAVIGATION_ACTION,
+ "name", "Forward",
+ "label", _("_Forward"),
+ "stock_id", GTK_STOCK_GO_FORWARD,
+ "tooltip", _("Go to the next visited location"),
+ "arrow-tooltip", _("Forward history"),
+ "window", window,
+ "direction", CAJA_NAVIGATION_DIRECTION_FORWARD,
+ "is_important", TRUE,
+ NULL);
+ g_signal_connect (action, "activate",
+ G_CALLBACK (action_forward_callback), window);
+ gtk_action_group_add_action_with_accel (action_group,
+ action,
+ "<alt>Right");
+
+ g_object_unref (action);
+
+ action = g_object_new (CAJA_TYPE_ZOOM_ACTION,
+ "name", "Zoom",
+ "label", _("_Zoom"),
+ "window", window,
+ "is_important", FALSE,
+ NULL);
+ gtk_action_group_add_action (action_group,
+ action);
+ g_object_unref (action);
+
+ action = g_object_new (CAJA_TYPE_VIEW_AS_ACTION,
+ "name", "ViewAs",
+ "label", _("_View As"),
+ "window", window,
+ "is_important", FALSE,
+ NULL);
+ gtk_action_group_add_action (action_group,
+ action);
+ g_object_unref (action);
+
+ ui_manager = caja_window_get_ui_manager (CAJA_WINDOW (window));
+
+ /* Alt+N for the first 10 tabs */
+ for (i = 0; i < 10; ++i)
+ {
+ gchar action_name[80];
+ gchar accelerator[80];
+
+ snprintf(action_name, sizeof (action_name), "Tab%d", i);
+ action = gtk_action_new (action_name, NULL, NULL, NULL);
+ g_object_set_data (G_OBJECT (action), "num", GINT_TO_POINTER (i));
+ g_signal_connect (action, "activate",
+ G_CALLBACK (action_tab_change_action_activate_callback), window);
+ snprintf(accelerator, sizeof (accelerator), "<alt>%d", (i+1)%10);
+ gtk_action_group_add_action_with_accel (action_group, action, accelerator);
+ g_object_unref (action);
+ gtk_ui_manager_add_ui (ui_manager,
+ gtk_ui_manager_new_merge_id (ui_manager),
+ "/",
+ action_name,
+ action_name,
+ GTK_UI_MANAGER_ACCELERATOR,
+ FALSE);
+
+ }
+
+ action = gtk_action_group_get_action (action_group, CAJA_ACTION_SEARCH);
+ g_object_set (action, "short_label", _("_Search"), NULL);
+
+ action = gtk_action_group_get_action (action_group, "ShowSearch");
+ gtk_action_set_sensitive (action, TRUE);
+
+ gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
+ g_object_unref (action_group); /* owned by ui_manager */
+
+ g_signal_connect (window, "loading_uri",
+ G_CALLBACK (caja_navigation_window_update_split_view_actions_sensitivity),
+ NULL);
+
+ caja_navigation_window_update_split_view_actions_sensitivity (window);
+}
+
+
+/**
+ * caja_window_initialize_menus
+ *
+ * Create and install the set of menus for this window.
+ * @window: A recently-created CajaWindow.
+ */
+void
+caja_navigation_window_initialize_menus (CajaNavigationWindow *window)
+{
+ GtkUIManager *ui_manager;
+ const char *ui;
+
+ ui_manager = caja_window_get_ui_manager (CAJA_WINDOW (window));
+
+ ui = caja_ui_string_get ("caja-navigation-window-ui.xml");
+ gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, NULL);
+
+ caja_navigation_window_update_show_hide_menu_items (window);
+ caja_navigation_window_update_spatial_menu_item (window);
+
+ caja_navigation_window_initialize_go_menu (window);
+}
diff --git a/src/caja-navigation-window-pane.c b/src/caja-navigation-window-pane.c
new file mode 100644
index 00000000..021a1798
--- /dev/null
+++ b/src/caja-navigation-window-pane.c
@@ -0,0 +1,946 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-navigation-window-pane.c: Caja navigation window pane
+
+ Copyright (C) 2008 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with this program; if not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Holger Berndt <[email protected]>
+*/
+
+#include "caja-navigation-window-pane.h"
+#include "caja-window-private.h"
+#include "caja-window-manage-views.h"
+#include "caja-navigation-bar.h"
+#include "caja-pathbar.h"
+#include "caja-location-bar.h"
+#include "caja-notebook.h"
+
+#include <libcaja-private/caja-global-preferences.h>
+#include <libcaja-private/caja-window-slot-info.h>
+#include <libcaja-private/caja-view-factory.h>
+#include <libcaja-private/caja-entry.h>
+
+#include <eel/eel-preferences.h>
+
+
+static void caja_navigation_window_pane_init (CajaNavigationWindowPane *pane);
+static void caja_navigation_window_pane_class_init (CajaNavigationWindowPaneClass *class);
+static void caja_navigation_window_pane_dispose (GObject *object);
+
+G_DEFINE_TYPE (CajaNavigationWindowPane,
+ caja_navigation_window_pane,
+ CAJA_TYPE_WINDOW_PANE)
+#define parent_class caja_navigation_window_pane_parent_class
+
+
+static void
+real_set_active (CajaWindowPane *pane, gboolean is_active)
+{
+ CajaNavigationWindowPane *nav_pane;
+ GList *l;
+
+ nav_pane = CAJA_NAVIGATION_WINDOW_PANE (pane);
+
+ /* path bar */
+ for (l = CAJA_PATH_BAR (nav_pane->path_bar)->button_list; l; l = l->next)
+ {
+ gtk_widget_set_sensitive (gtk_bin_get_child (GTK_BIN (caja_path_bar_get_button_from_button_list_entry (l->data))), is_active);
+ }
+
+ /* navigation bar (manual entry) */
+ caja_location_bar_set_active (CAJA_LOCATION_BAR (nav_pane->navigation_bar), is_active);
+}
+
+static gboolean
+navigation_bar_focus_in_callback (GtkWidget *widget, GdkEventFocus *event, gpointer user_data)
+{
+ CajaWindowPane *pane;
+ pane = CAJA_WINDOW_PANE (user_data);
+ caja_window_set_active_pane (pane->window, pane);
+ return FALSE;
+}
+
+static int
+bookmark_list_get_uri_index (GList *list, GFile *location)
+{
+ CajaBookmark *bookmark;
+ GList *l;
+ GFile *tmp;
+ int i;
+
+ g_return_val_if_fail (location != NULL, -1);
+
+ for (i = 0, l = list; l != NULL; i++, l = l->next)
+ {
+ bookmark = CAJA_BOOKMARK (l->data);
+
+ tmp = caja_bookmark_get_location (bookmark);
+ if (g_file_equal (location, tmp))
+ {
+ g_object_unref (tmp);
+ return i;
+ }
+ g_object_unref (tmp);
+ }
+
+ return -1;
+}
+
+static void
+search_bar_focus_in_callback (CajaSearchBar *bar,
+ CajaWindowPane *pane)
+{
+ caja_window_set_active_pane (pane->window, pane);
+}
+
+
+static void
+search_bar_activate_callback (CajaSearchBar *bar,
+ CajaNavigationWindowPane *pane)
+{
+ char *uri, *current_uri;
+ CajaDirectory *directory;
+ CajaSearchDirectory *search_directory;
+ CajaQuery *query;
+ GFile *location;
+
+ uri = caja_search_directory_generate_new_uri ();
+ location = g_file_new_for_uri (uri);
+ g_free (uri);
+
+ directory = caja_directory_get (location);
+
+ g_assert (CAJA_IS_SEARCH_DIRECTORY (directory));
+
+ search_directory = CAJA_SEARCH_DIRECTORY (directory);
+
+ query = caja_search_bar_get_query (CAJA_SEARCH_BAR (pane->search_bar));
+ if (query != NULL)
+ {
+ CajaWindowSlot *slot = CAJA_WINDOW_PANE (pane)->active_slot;
+ if (!caja_search_directory_is_indexed (search_directory))
+ {
+ current_uri = caja_window_slot_get_location_uri (slot);
+ caja_query_set_location (query, current_uri);
+ g_free (current_uri);
+ }
+ caja_search_directory_set_query (search_directory, query);
+ g_object_unref (query);
+ }
+
+ caja_window_slot_go_to (CAJA_WINDOW_PANE (pane)->active_slot, location, FALSE);
+
+ caja_directory_unref (directory);
+ g_object_unref (location);
+}
+
+static void
+search_bar_cancel_callback (GtkWidget *widget,
+ CajaNavigationWindowPane *pane)
+{
+ if (caja_navigation_window_pane_hide_temporary_bars (pane))
+ {
+ caja_navigation_window_restore_focus_widget (CAJA_NAVIGATION_WINDOW (CAJA_WINDOW_PANE (pane)->window));
+ }
+}
+
+static void
+navigation_bar_cancel_callback (GtkWidget *widget,
+ CajaNavigationWindowPane *pane)
+{
+ if (caja_navigation_window_pane_hide_temporary_bars (pane))
+ {
+ caja_navigation_window_restore_focus_widget (CAJA_NAVIGATION_WINDOW (CAJA_WINDOW_PANE (pane)->window));
+ }
+}
+
+static void
+navigation_bar_location_changed_callback (GtkWidget *widget,
+ const char *uri,
+ CajaNavigationWindowPane *pane)
+{
+ GFile *location;
+
+ if (caja_navigation_window_pane_hide_temporary_bars (pane))
+ {
+ caja_navigation_window_restore_focus_widget (CAJA_NAVIGATION_WINDOW (CAJA_WINDOW_PANE (pane)->window));
+ }
+
+ location = g_file_new_for_uri (uri);
+ caja_window_slot_go_to (CAJA_WINDOW_PANE (pane)->active_slot, location, FALSE);
+ g_object_unref (location);
+}
+
+static void
+path_bar_location_changed_callback (GtkWidget *widget,
+ GFile *location,
+ CajaNavigationWindowPane *pane)
+{
+ CajaNavigationWindowSlot *slot;
+ CajaWindowPane *win_pane;
+ int i;
+
+ g_assert (CAJA_IS_NAVIGATION_WINDOW_PANE (pane));
+
+ win_pane = CAJA_WINDOW_PANE(pane);
+
+ slot = CAJA_NAVIGATION_WINDOW_SLOT (win_pane->active_slot);
+
+ /* check whether we already visited the target location */
+ i = bookmark_list_get_uri_index (slot->back_list, location);
+ if (i >= 0)
+ {
+ caja_navigation_window_back_or_forward (CAJA_NAVIGATION_WINDOW (win_pane->window), TRUE, i, FALSE);
+ }
+ else
+ {
+ caja_window_slot_go_to (win_pane->active_slot, location, FALSE);
+ }
+}
+
+static gboolean
+path_bar_button_pressed_callback (GtkWidget *widget,
+ GdkEventButton *event,
+ CajaNavigationWindowPane *pane)
+{
+ CajaWindowSlot *slot;
+ CajaView *view;
+ GFile *location;
+ char *uri;
+
+ caja_window_set_active_pane (CAJA_WINDOW_PANE (pane)->window, CAJA_WINDOW_PANE (pane));
+
+ g_object_set_data (G_OBJECT (widget), "handle-button-release",
+ GINT_TO_POINTER (TRUE));
+
+ if (event->button == 3)
+ {
+ slot = caja_window_get_active_slot (CAJA_WINDOW_PANE (pane)->window);
+ view = slot->content_view;
+ if (view != NULL)
+ {
+ location = caja_path_bar_get_path_for_button (
+ CAJA_PATH_BAR (pane->path_bar), widget);
+ if (location != NULL)
+ {
+ uri = g_file_get_uri (location);
+ caja_view_pop_up_location_context_menu (
+ view, event, uri);
+ g_object_unref (G_OBJECT (location));
+ g_free (uri);
+ return TRUE;
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+static gboolean
+path_bar_button_released_callback (GtkWidget *widget,
+ GdkEventButton *event,
+ CajaNavigationWindowPane *pane)
+{
+ CajaWindowSlot *slot;
+ CajaWindowOpenFlags flags;
+ GFile *location;
+ int mask;
+ gboolean handle_button_release;
+
+ mask = event->state & gtk_accelerator_get_default_mod_mask ();
+ flags = 0;
+
+ handle_button_release = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (widget),
+ "handle-button-release"));
+
+ if (event->type == GDK_BUTTON_RELEASE && handle_button_release)
+ {
+ location = caja_path_bar_get_path_for_button (CAJA_PATH_BAR (pane->path_bar), widget);
+
+ if (event->button == 2 && mask == 0)
+ {
+ flags = CAJA_WINDOW_OPEN_FLAG_NEW_TAB;
+ }
+ else if (event->button == 1 && mask == GDK_CONTROL_MASK)
+ {
+ flags = CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW;
+ }
+
+ if (flags != 0)
+ {
+ slot = caja_window_get_active_slot (CAJA_WINDOW_PANE (pane)->window);
+ caja_window_slot_info_open_location (slot, location,
+ CAJA_WINDOW_OPEN_ACCORDING_TO_MODE,
+ flags, NULL);
+ g_object_unref (location);
+ return TRUE;
+ }
+
+ g_object_unref (location);
+ }
+
+ return FALSE;
+}
+
+static void
+path_bar_button_drag_begin_callback (GtkWidget *widget,
+ GdkEventButton *event,
+ gpointer user_data)
+{
+ g_object_set_data (G_OBJECT (widget), "handle-button-release",
+ GINT_TO_POINTER (FALSE));
+}
+
+static void
+path_bar_path_set_callback (GtkWidget *widget,
+ GFile *location,
+ CajaNavigationWindowPane *pane)
+{
+ GList *children, *l;
+ GtkWidget *child;
+
+ children = gtk_container_get_children (GTK_CONTAINER (widget));
+
+ for (l = children; l != NULL; l = l->next)
+ {
+ child = GTK_WIDGET (l->data);
+
+ if (!GTK_IS_TOGGLE_BUTTON (child))
+ {
+ continue;
+ }
+
+ if (!g_signal_handler_find (child,
+ G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL,
+ path_bar_button_pressed_callback,
+ pane))
+ {
+ g_signal_connect (child, "button-press-event",
+ G_CALLBACK (path_bar_button_pressed_callback),
+ pane);
+ g_signal_connect (child, "button-release-event",
+ G_CALLBACK (path_bar_button_released_callback),
+ pane);
+ g_signal_connect (child, "drag-begin",
+ G_CALLBACK (path_bar_button_drag_begin_callback),
+ pane);
+ }
+ }
+
+ g_list_free (children);
+}
+
+static void
+notebook_popup_menu_move_left_cb (GtkMenuItem *menuitem,
+ gpointer user_data)
+{
+ CajaNavigationWindowPane *pane;
+
+ pane = CAJA_NAVIGATION_WINDOW_PANE (user_data);
+ caja_notebook_reorder_current_child_relative (CAJA_NOTEBOOK (pane->notebook), -1);
+}
+
+static void
+notebook_popup_menu_move_right_cb (GtkMenuItem *menuitem,
+ gpointer user_data)
+{
+ CajaNavigationWindowPane *pane;
+
+ pane = CAJA_NAVIGATION_WINDOW_PANE (user_data);
+ caja_notebook_reorder_current_child_relative (CAJA_NOTEBOOK (pane->notebook), 1);
+}
+
+static void
+notebook_popup_menu_close_cb (GtkMenuItem *menuitem,
+ gpointer user_data)
+{
+ CajaWindowPane *pane;
+ CajaWindowSlot *slot;
+
+ pane = CAJA_WINDOW_PANE (user_data);
+ slot = pane->active_slot;
+ caja_window_slot_close (slot);
+}
+
+static void
+notebook_popup_menu_show (CajaNavigationWindowPane *pane,
+ GdkEventButton *event)
+{
+ GtkWidget *popup;
+ GtkWidget *item;
+ GtkWidget *image;
+ int button, event_time;
+ gboolean can_move_left, can_move_right;
+ CajaNotebook *notebook;
+
+ notebook = CAJA_NOTEBOOK (pane->notebook);
+
+ can_move_left = caja_notebook_can_reorder_current_child_relative (notebook, -1);
+ can_move_right = caja_notebook_can_reorder_current_child_relative (notebook, 1);
+
+ popup = gtk_menu_new();
+
+ item = gtk_menu_item_new_with_mnemonic (_("Move Tab _Left"));
+ g_signal_connect (item, "activate",
+ G_CALLBACK (notebook_popup_menu_move_left_cb),
+ pane);
+ gtk_menu_shell_append (GTK_MENU_SHELL (popup),
+ item);
+ gtk_widget_set_sensitive (item, can_move_left);
+
+ item = gtk_menu_item_new_with_mnemonic (_("Move Tab _Right"));
+ g_signal_connect (item, "activate",
+ G_CALLBACK (notebook_popup_menu_move_right_cb),
+ pane);
+ gtk_menu_shell_append (GTK_MENU_SHELL (popup),
+ item);
+ gtk_widget_set_sensitive (item, can_move_right);
+
+ gtk_menu_shell_append (GTK_MENU_SHELL (popup),
+ gtk_separator_menu_item_new ());
+
+ item = gtk_image_menu_item_new_with_mnemonic (_("_Close Tab"));
+ image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU);
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
+ g_signal_connect (item, "activate",
+ G_CALLBACK (notebook_popup_menu_close_cb), pane);
+ gtk_menu_shell_append (GTK_MENU_SHELL (popup),
+ item);
+
+ gtk_widget_show_all (popup);
+
+ if (event)
+ {
+ button = event->button;
+ event_time = event->time;
+ }
+ else
+ {
+ button = 0;
+ event_time = gtk_get_current_event_time ();
+ }
+
+ /* TODO is this correct? */
+ gtk_menu_attach_to_widget (GTK_MENU (popup),
+ pane->notebook,
+ NULL);
+
+ gtk_menu_popup (GTK_MENU (popup), NULL, NULL, NULL, NULL,
+ button, event_time);
+}
+
+/* emitted when the user clicks the "close" button of tabs */
+static void
+notebook_tab_close_requested (CajaNotebook *notebook,
+ CajaWindowSlot *slot,
+ CajaWindowPane *pane)
+{
+ caja_window_pane_slot_close (pane, slot);
+}
+
+static gboolean
+notebook_button_press_cb (GtkWidget *widget,
+ GdkEventButton *event,
+ gpointer user_data)
+{
+ CajaNavigationWindowPane *pane;
+
+ pane = CAJA_NAVIGATION_WINDOW_PANE (user_data);
+ if (GDK_BUTTON_PRESS == event->type && 3 == event->button)
+ {
+ notebook_popup_menu_show (pane, event);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+notebook_popup_menu_cb (GtkWidget *widget,
+ gpointer user_data)
+{
+ CajaNavigationWindowPane *pane;
+
+ pane = CAJA_NAVIGATION_WINDOW_PANE (user_data);
+ notebook_popup_menu_show (pane, NULL);
+ return TRUE;
+}
+
+static gboolean
+notebook_switch_page_cb (GtkNotebook *notebook,
+ GtkWidget *page,
+ unsigned int page_num,
+ CajaNavigationWindowPane *pane)
+{
+ CajaWindowSlot *slot;
+ GtkWidget *widget;
+
+ widget = gtk_notebook_get_nth_page (GTK_NOTEBOOK (pane->notebook), page_num);
+ g_assert (widget != NULL);
+
+ /* find slot corresponding to the target page */
+ slot = caja_window_pane_get_slot_for_content_box (CAJA_WINDOW_PANE (pane), widget);
+ g_assert (slot != NULL);
+
+ caja_window_set_active_slot (slot->pane->window, slot);
+
+ return FALSE;
+}
+
+void
+caja_navigation_window_pane_remove_page (CajaNavigationWindowPane *pane, int page_num)
+{
+ GtkNotebook *notebook;
+ notebook = GTK_NOTEBOOK (pane->notebook);
+
+ g_signal_handlers_block_by_func (notebook,
+ G_CALLBACK (notebook_switch_page_cb),
+ pane);
+ gtk_notebook_remove_page (notebook, page_num);
+ g_signal_handlers_unblock_by_func (notebook,
+ G_CALLBACK (notebook_switch_page_cb),
+ pane);
+}
+
+void
+caja_navigation_window_pane_add_slot_in_tab (CajaNavigationWindowPane *pane, CajaWindowSlot *slot, CajaWindowOpenSlotFlags flags)
+{
+ CajaNotebook *notebook;
+
+ notebook = CAJA_NOTEBOOK (pane->notebook);
+ g_signal_handlers_block_by_func (notebook,
+ G_CALLBACK (notebook_switch_page_cb),
+ pane);
+ caja_notebook_add_tab (notebook,
+ slot,
+ (flags & CAJA_WINDOW_OPEN_SLOT_APPEND) != 0 ?
+ -1 :
+ gtk_notebook_get_current_page (GTK_NOTEBOOK (notebook)) + 1,
+ FALSE);
+ g_signal_handlers_unblock_by_func (notebook,
+ G_CALLBACK (notebook_switch_page_cb),
+ pane);
+}
+
+static void
+real_sync_location_widgets (CajaWindowPane *pane)
+{
+ CajaNavigationWindowSlot *navigation_slot;
+ CajaNavigationWindowPane *navigation_pane;
+ CajaWindowSlot *slot;
+
+ slot = pane->active_slot;
+ navigation_pane = CAJA_NAVIGATION_WINDOW_PANE (pane);
+
+ /* Change the location bar and path bar to match the current location. */
+ if (slot->location != NULL)
+ {
+ char *uri;
+
+ /* this may be NULL if we just created the slot */
+ uri = caja_window_slot_get_location_uri (slot);
+ caja_navigation_bar_set_location (CAJA_NAVIGATION_BAR (navigation_pane->navigation_bar), uri);
+ g_free (uri);
+ caja_path_bar_set_path (CAJA_PATH_BAR (navigation_pane->path_bar), slot->location);
+ }
+
+ /* Update window global UI if this is the active pane */
+ if (pane == pane->window->details->active_pane)
+ {
+ caja_window_update_up_button (pane->window);
+
+ /* Check if the back and forward buttons need enabling or disabling. */
+ navigation_slot = CAJA_NAVIGATION_WINDOW_SLOT (pane->window->details->active_pane->active_slot);
+ caja_navigation_window_allow_back (CAJA_NAVIGATION_WINDOW (pane->window),
+ navigation_slot->back_list != NULL);
+ caja_navigation_window_allow_forward (CAJA_NAVIGATION_WINDOW (pane->window),
+ navigation_slot->forward_list != NULL);
+ }
+}
+
+gboolean
+caja_navigation_window_pane_hide_temporary_bars (CajaNavigationWindowPane *pane)
+{
+ CajaWindowSlot *slot;
+ CajaDirectory *directory;
+ gboolean success;
+
+ g_assert (CAJA_IS_NAVIGATION_WINDOW_PANE (pane));
+
+ slot = CAJA_WINDOW_PANE(pane)->active_slot;
+ success = FALSE;
+
+ if (pane->temporary_location_bar)
+ {
+ if (caja_navigation_window_pane_location_bar_showing (pane))
+ {
+ caja_navigation_window_pane_hide_location_bar (pane, FALSE);
+ }
+ pane->temporary_location_bar = FALSE;
+ success = TRUE;
+ }
+ if (pane->temporary_navigation_bar)
+ {
+ directory = caja_directory_get (slot->location);
+
+ if (CAJA_IS_SEARCH_DIRECTORY (directory))
+ {
+ caja_navigation_window_pane_set_bar_mode (pane, CAJA_BAR_SEARCH);
+ }
+ else
+ {
+ if (!eel_preferences_get_boolean (CAJA_PREFERENCES_ALWAYS_USE_LOCATION_ENTRY))
+ {
+ caja_navigation_window_pane_set_bar_mode (pane, CAJA_BAR_PATH);
+ }
+ }
+ pane->temporary_navigation_bar = FALSE;
+ success = TRUE;
+
+ caja_directory_unref (directory);
+ }
+ if (pane->temporary_search_bar)
+ {
+ CajaNavigationWindow *window;
+
+ if (!eel_preferences_get_boolean (CAJA_PREFERENCES_ALWAYS_USE_LOCATION_ENTRY))
+ {
+ caja_navigation_window_pane_set_bar_mode (pane, CAJA_BAR_PATH);
+ }
+ else
+ {
+ caja_navigation_window_pane_set_bar_mode (pane, CAJA_BAR_NAVIGATION);
+ }
+ window = CAJA_NAVIGATION_WINDOW (CAJA_WINDOW_PANE (pane)->window);
+ caja_navigation_window_set_search_button (window, FALSE);
+ pane->temporary_search_bar = FALSE;
+ success = TRUE;
+ }
+
+ return success;
+}
+
+void
+caja_navigation_window_pane_always_use_location_entry (CajaNavigationWindowPane *pane, gboolean use_entry)
+{
+ if (use_entry)
+ {
+ caja_navigation_window_pane_set_bar_mode (pane, CAJA_BAR_NAVIGATION);
+ }
+ else
+ {
+ caja_navigation_window_pane_set_bar_mode (pane, CAJA_BAR_PATH);
+ }
+}
+
+void
+caja_navigation_window_pane_setup (CajaNavigationWindowPane *pane)
+{
+ GtkWidget *hbox;
+ CajaEntry *entry;
+ GtkSizeGroup *header_size_group;
+
+ pane->widget = gtk_vbox_new (FALSE, 0);
+
+ hbox = gtk_hbox_new (FALSE, 12);
+ pane->location_bar = hbox;
+ gtk_container_set_border_width (GTK_CONTAINER (hbox), 4);
+ gtk_box_pack_start (GTK_BOX (pane->widget), hbox,
+ FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ header_size_group = CAJA_NAVIGATION_WINDOW (CAJA_WINDOW_PANE (pane)->window)->details->header_size_group;
+
+ pane->path_bar = g_object_new (CAJA_TYPE_PATH_BAR, NULL);
+ gtk_size_group_add_widget (header_size_group, pane->path_bar);
+ gtk_widget_show (pane->path_bar);
+
+ g_signal_connect_object (pane->path_bar, "path_clicked",
+ G_CALLBACK (path_bar_location_changed_callback), pane, 0);
+ g_signal_connect_object (pane->path_bar, "path_set",
+ G_CALLBACK (path_bar_path_set_callback), pane, 0);
+
+ gtk_box_pack_start (GTK_BOX (hbox),
+ pane->path_bar,
+ TRUE, TRUE, 0);
+
+ pane->navigation_bar = caja_location_bar_new (pane);
+ gtk_size_group_add_widget (header_size_group, pane->navigation_bar);
+ g_signal_connect_object (pane->navigation_bar, "location_changed",
+ G_CALLBACK (navigation_bar_location_changed_callback), pane, 0);
+ g_signal_connect_object (pane->navigation_bar, "cancel",
+ G_CALLBACK (navigation_bar_cancel_callback), pane, 0);
+ entry = caja_location_bar_get_entry (CAJA_LOCATION_BAR (pane->navigation_bar));
+ g_signal_connect (entry, "focus-in-event",
+ G_CALLBACK (navigation_bar_focus_in_callback), pane);
+
+ gtk_box_pack_start (GTK_BOX (hbox),
+ pane->navigation_bar,
+ TRUE, TRUE, 0);
+
+ pane->search_bar = caja_search_bar_new ();
+ gtk_size_group_add_widget (header_size_group, pane->search_bar);
+ g_signal_connect_object (pane->search_bar, "activate",
+ G_CALLBACK (search_bar_activate_callback), pane, 0);
+ g_signal_connect_object (pane->search_bar, "cancel",
+ G_CALLBACK (search_bar_cancel_callback), pane, 0);
+ g_signal_connect_object (pane->search_bar, "focus-in",
+ G_CALLBACK (search_bar_focus_in_callback), pane, 0);
+ gtk_box_pack_start (GTK_BOX (hbox),
+ pane->search_bar,
+ TRUE, TRUE, 0);
+
+ pane->notebook = g_object_new (CAJA_TYPE_NOTEBOOK, NULL);
+ gtk_box_pack_start (GTK_BOX (pane->widget), pane->notebook,
+ TRUE, TRUE, 0);
+ g_signal_connect (pane->notebook,
+ "tab-close-request",
+ G_CALLBACK (notebook_tab_close_requested),
+ pane);
+ g_signal_connect_after (pane->notebook,
+ "button_press_event",
+ G_CALLBACK (notebook_button_press_cb),
+ pane);
+ g_signal_connect (pane->notebook, "popup-menu",
+ G_CALLBACK (notebook_popup_menu_cb),
+ pane);
+ g_signal_connect (pane->notebook,
+ "switch-page",
+ G_CALLBACK (notebook_switch_page_cb),
+ pane);
+
+ gtk_notebook_set_show_tabs (GTK_NOTEBOOK (pane->notebook), FALSE);
+ gtk_notebook_set_show_border (GTK_NOTEBOOK (pane->notebook), FALSE);
+ gtk_widget_show (pane->notebook);
+ gtk_container_set_border_width (GTK_CONTAINER (pane->notebook), 0);
+
+ /* Ensure that the view has some minimal size and that other parts
+ * of the UI (like location bar and tabs) don't request more and
+ * thus affect the default position of the split view paned.
+ */
+ gtk_widget_set_size_request (pane->widget, 60, 60);
+}
+
+
+void
+caja_navigation_window_pane_show_location_bar_temporarily (CajaNavigationWindowPane *pane)
+{
+ if (!caja_navigation_window_pane_location_bar_showing (pane))
+ {
+ caja_navigation_window_pane_show_location_bar (pane, FALSE);
+ pane->temporary_location_bar = TRUE;
+ }
+}
+
+void
+caja_navigation_window_pane_show_navigation_bar_temporarily (CajaNavigationWindowPane *pane)
+{
+ if (caja_navigation_window_pane_path_bar_showing (pane)
+ || caja_navigation_window_pane_search_bar_showing (pane))
+ {
+ caja_navigation_window_pane_set_bar_mode (pane, CAJA_BAR_NAVIGATION);
+ pane->temporary_navigation_bar = TRUE;
+ }
+ caja_navigation_bar_activate
+ (CAJA_NAVIGATION_BAR (pane->navigation_bar));
+}
+
+gboolean
+caja_navigation_window_pane_path_bar_showing (CajaNavigationWindowPane *pane)
+{
+ if (pane->path_bar != NULL)
+ {
+ return gtk_widget_get_visible (pane->path_bar);
+ }
+ /* If we're not visible yet we haven't changed visibility, so its TRUE */
+ return TRUE;
+}
+
+void
+caja_navigation_window_pane_set_bar_mode (CajaNavigationWindowPane *pane,
+ CajaBarMode mode)
+{
+ GtkWidget *focus_widget;
+ CajaNavigationWindow *window;
+
+ switch (mode)
+ {
+
+ case CAJA_BAR_PATH:
+ gtk_widget_show (pane->path_bar);
+ gtk_widget_hide (pane->navigation_bar);
+ gtk_widget_hide (pane->search_bar);
+ break;
+
+ case CAJA_BAR_NAVIGATION:
+ gtk_widget_show (pane->navigation_bar);
+ gtk_widget_hide (pane->path_bar);
+ gtk_widget_hide (pane->search_bar);
+ break;
+
+ case CAJA_BAR_SEARCH:
+ gtk_widget_show (pane->search_bar);
+ gtk_widget_hide (pane->path_bar);
+ gtk_widget_hide (pane->navigation_bar);
+ break;
+ }
+
+ window = CAJA_NAVIGATION_WINDOW (CAJA_WINDOW_PANE (pane)->window);
+ focus_widget = gtk_window_get_focus (GTK_WINDOW (window));
+ if (focus_widget != NULL && !caja_navigation_window_is_in_temporary_navigation_bar (focus_widget, window) &&
+ !caja_navigation_window_is_in_temporary_search_bar (focus_widget, window))
+ {
+ if (mode == CAJA_BAR_NAVIGATION || mode == CAJA_BAR_PATH)
+ {
+ caja_navigation_window_set_search_button (window, FALSE);
+ }
+ else
+ {
+ caja_navigation_window_set_search_button (window, TRUE);
+ }
+ }
+}
+
+gboolean
+caja_navigation_window_pane_search_bar_showing (CajaNavigationWindowPane *pane)
+{
+ if (pane->search_bar != NULL)
+ {
+ return gtk_widget_get_visible (pane->search_bar);
+ }
+ /* If we're not visible yet we haven't changed visibility, so its TRUE */
+ return TRUE;
+}
+
+void
+caja_navigation_window_pane_hide_location_bar (CajaNavigationWindowPane *pane, gboolean save_preference)
+{
+ pane->temporary_location_bar = FALSE;
+ gtk_widget_hide(pane->location_bar);
+ caja_navigation_window_update_show_hide_menu_items(
+ CAJA_NAVIGATION_WINDOW (CAJA_WINDOW_PANE (pane)->window));
+ if (save_preference && eel_preferences_key_is_writable(CAJA_PREFERENCES_START_WITH_LOCATION_BAR))
+ {
+ eel_preferences_set_boolean(CAJA_PREFERENCES_START_WITH_LOCATION_BAR, FALSE);
+ }
+}
+
+void
+caja_navigation_window_pane_show_location_bar (CajaNavigationWindowPane *pane, gboolean save_preference)
+{
+ gtk_widget_show(pane->location_bar);
+ caja_navigation_window_update_show_hide_menu_items(CAJA_NAVIGATION_WINDOW (CAJA_WINDOW_PANE (pane)->window));
+ if (save_preference && eel_preferences_key_is_writable(CAJA_PREFERENCES_START_WITH_LOCATION_BAR))
+ {
+ eel_preferences_set_boolean(CAJA_PREFERENCES_START_WITH_LOCATION_BAR, TRUE);
+ }
+}
+
+gboolean
+caja_navigation_window_pane_location_bar_showing (CajaNavigationWindowPane *pane)
+{
+ if (!CAJA_IS_NAVIGATION_WINDOW_PANE (pane))
+ {
+ return FALSE;
+ }
+ if (pane->location_bar != NULL)
+ {
+ return gtk_widget_get_visible (pane->location_bar);
+ }
+ /* If we're not visible yet we haven't changed visibility, so its TRUE */
+ return TRUE;
+}
+
+static void
+caja_navigation_window_pane_init (CajaNavigationWindowPane *pane)
+{
+}
+
+static void
+caja_navigation_window_pane_show (CajaWindowPane *pane)
+{
+ CajaNavigationWindowPane *npane = CAJA_NAVIGATION_WINDOW_PANE (pane);
+
+ gtk_widget_show (npane->widget);
+}
+
+/* either called due to slot change, or due to location change in the current slot. */
+static void
+real_sync_search_widgets (CajaWindowPane *window_pane)
+{
+ CajaWindowSlot *slot;
+ CajaDirectory *directory;
+ CajaSearchDirectory *search_directory;
+ CajaNavigationWindowPane *pane;
+
+ pane = CAJA_NAVIGATION_WINDOW_PANE (window_pane);
+ slot = window_pane->active_slot;
+ search_directory = NULL;
+
+ directory = caja_directory_get (slot->location);
+ if (CAJA_IS_SEARCH_DIRECTORY (directory))
+ {
+ search_directory = CAJA_SEARCH_DIRECTORY (directory);
+ }
+
+ if (search_directory != NULL &&
+ !caja_search_directory_is_saved_search (search_directory))
+ {
+ caja_navigation_window_pane_show_location_bar_temporarily (pane);
+ caja_navigation_window_pane_set_bar_mode (pane, CAJA_BAR_SEARCH);
+ pane->temporary_search_bar = FALSE;
+ }
+ else
+ {
+ pane->temporary_search_bar = TRUE;
+ caja_navigation_window_pane_hide_temporary_bars (pane);
+ }
+ caja_directory_unref (directory);
+}
+
+static void
+caja_navigation_window_pane_class_init (CajaNavigationWindowPaneClass *class)
+{
+ G_OBJECT_CLASS (class)->dispose = caja_navigation_window_pane_dispose;
+ CAJA_WINDOW_PANE_CLASS (class)->show = caja_navigation_window_pane_show;
+ CAJA_WINDOW_PANE_CLASS (class)->set_active = real_set_active;
+ CAJA_WINDOW_PANE_CLASS (class)->sync_search_widgets = real_sync_search_widgets;
+ CAJA_WINDOW_PANE_CLASS (class)->sync_location_widgets = real_sync_location_widgets;
+}
+
+static void
+caja_navigation_window_pane_dispose (GObject *object)
+{
+ CajaNavigationWindowPane *pane = CAJA_NAVIGATION_WINDOW_PANE (object);
+
+ gtk_widget_destroy (pane->widget);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+CajaNavigationWindowPane *
+caja_navigation_window_pane_new (CajaWindow *window)
+{
+ CajaNavigationWindowPane *pane;
+
+ pane = g_object_new (CAJA_TYPE_NAVIGATION_WINDOW_PANE, NULL);
+ CAJA_WINDOW_PANE(pane)->window = window;
+
+ return pane;
+}
diff --git a/src/caja-navigation-window-pane.h b/src/caja-navigation-window-pane.h
new file mode 100644
index 00000000..87e4ae5d
--- /dev/null
+++ b/src/caja-navigation-window-pane.h
@@ -0,0 +1,92 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-navigation-window-pane.h: Caja navigation window pane
+
+ Copyright (C) 2008 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with this program; if not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Holger Berndt <[email protected]>
+*/
+
+#ifndef CAJA_NAVIGATION_WINDOW_PANE_H
+#define CAJA_NAVIGATION_WINDOW_PANE_H
+
+#include "caja-window-pane.h"
+#include "caja-navigation-window-slot.h"
+
+#define CAJA_TYPE_NAVIGATION_WINDOW_PANE (caja_navigation_window_pane_get_type())
+#define CAJA_NAVIGATION_WINDOW_PANE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), CAJA_TYPE_NAVIGATION_WINDOW_PANE, CajaNavigationWindowPaneClass))
+#define CAJA_NAVIGATION_WINDOW_PANE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_NAVIGATION_WINDOW_PANE, CajaNavigationWindowPane))
+#define CAJA_IS_NAVIGATION_WINDOW_PANE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_NAVIGATION_WINDOW_PANE))
+#define CAJA_IS_NAVIGATION_WINDOW_PANE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), CAJA_TYPE_NAVIGATION_WINDOW_PANE))
+#define CAJA_NAVIGATION_WINDOW_PANE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), CAJA_TYPE_NAVIGATION_WINDOW_PANE, CajaNavigationWindowPaneClass))
+
+typedef struct _CajaNavigationWindowPaneClass CajaNavigationWindowPaneClass;
+typedef struct _CajaNavigationWindowPane CajaNavigationWindowPane;
+
+struct _CajaNavigationWindowPaneClass
+{
+ CajaWindowPaneClass parent_class;
+};
+
+struct _CajaNavigationWindowPane
+{
+ CajaWindowPane parent;
+
+ GtkWidget *widget;
+
+ /* location bar */
+ GtkWidget *location_bar;
+ GtkWidget *navigation_bar;
+ GtkWidget *path_bar;
+ GtkWidget *search_bar;
+
+ gboolean temporary_navigation_bar;
+ gboolean temporary_location_bar;
+ gboolean temporary_search_bar;
+
+ /* notebook */
+ GtkWidget *notebook;
+
+ /* split view */
+ GtkWidget *split_view_hpane;
+};
+
+GType caja_navigation_window_pane_get_type (void);
+
+CajaNavigationWindowPane* caja_navigation_window_pane_new (CajaWindow *window);
+
+/* location bar */
+void caja_navigation_window_pane_setup (CajaNavigationWindowPane *pane);
+
+void caja_navigation_window_pane_hide_location_bar (CajaNavigationWindowPane *pane, gboolean save_preference);
+void caja_navigation_window_pane_show_location_bar (CajaNavigationWindowPane *pane, gboolean save_preference);
+gboolean caja_navigation_window_pane_location_bar_showing (CajaNavigationWindowPane *pane);
+void caja_navigation_window_pane_hide_path_bar (CajaNavigationWindowPane *pane);
+void caja_navigation_window_pane_show_path_bar (CajaNavigationWindowPane *pane);
+gboolean caja_navigation_window_pane_path_bar_showing (CajaNavigationWindowPane *pane);
+gboolean caja_navigation_window_pane_search_bar_showing (CajaNavigationWindowPane *pane);
+void caja_navigation_window_pane_set_bar_mode (CajaNavigationWindowPane *pane, CajaBarMode mode);
+void caja_navigation_window_pane_show_location_bar_temporarily (CajaNavigationWindowPane *pane);
+void caja_navigation_window_pane_show_navigation_bar_temporarily (CajaNavigationWindowPane *pane);
+void caja_navigation_window_pane_always_use_location_entry (CajaNavigationWindowPane *pane, gboolean use_entry);
+gboolean caja_navigation_window_pane_hide_temporary_bars (CajaNavigationWindowPane *pane);
+/* notebook */
+void caja_navigation_window_pane_add_slot_in_tab (CajaNavigationWindowPane *pane, CajaWindowSlot *slot, CajaWindowOpenSlotFlags flags);
+void caja_navigation_window_pane_remove_page (CajaNavigationWindowPane *pane, int page_num);
+
+#endif /* CAJA_NAVIGATION_WINDOW_PANE_H */
diff --git a/src/caja-navigation-window-slot.c b/src/caja-navigation-window-slot.c
new file mode 100644
index 00000000..4bf28447
--- /dev/null
+++ b/src/caja-navigation-window-slot.c
@@ -0,0 +1,241 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-navigation-window-slot.c: Caja navigation window slot
+
+ Copyright (C) 2008 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with this program; if not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Christian Neumair <[email protected]>
+*/
+
+#include "caja-window-slot.h"
+#include "caja-navigation-window-slot.h"
+#include "caja-window-private.h"
+#include "caja-search-bar.h"
+#include "caja-navigation-window-pane.h"
+#include <libcaja-private/caja-window-slot-info.h>
+#include <libcaja-private/caja-file.h>
+#include <eel/eel-gtk-macros.h>
+
+static void caja_navigation_window_slot_init (CajaNavigationWindowSlot *slot);
+static void caja_navigation_window_slot_class_init (CajaNavigationWindowSlotClass *class);
+
+G_DEFINE_TYPE (CajaNavigationWindowSlot, caja_navigation_window_slot, CAJA_TYPE_WINDOW_SLOT)
+#define parent_class caja_navigation_window_slot_parent_class
+
+gboolean
+caja_navigation_window_slot_should_close_with_mount (CajaNavigationWindowSlot *slot,
+ GMount *mount)
+{
+ CajaBookmark *bookmark;
+ GFile *mount_location, *bookmark_location;
+ GList *l;
+ gboolean close_with_mount;
+
+ if (slot->parent.pane->window->details->initiated_unmount)
+ {
+ return FALSE;
+ }
+
+ mount_location = g_mount_get_root (mount);
+
+ close_with_mount = TRUE;
+
+ for (l = slot->back_list; l != NULL; l = l->next)
+ {
+ bookmark = CAJA_BOOKMARK (l->data);
+
+ bookmark_location = caja_bookmark_get_location (bookmark);
+ close_with_mount &= g_file_has_prefix (bookmark_location, mount_location) ||
+ g_file_equal (bookmark_location, mount_location);
+ g_object_unref (bookmark_location);
+
+ if (!close_with_mount)
+ {
+ break;
+ }
+ }
+
+ close_with_mount &= g_file_has_prefix (CAJA_WINDOW_SLOT (slot)->location, mount_location) ||
+ g_file_equal (CAJA_WINDOW_SLOT (slot)->location, mount_location);
+
+ /* we could also consider the forward list here, but since the “go home” request
+ * in caja-window-manager-views.c:mount_removed_callback() would discard those
+ * anyway, we don't consider them.
+ */
+
+ g_object_unref (mount_location);
+
+ return close_with_mount;
+}
+
+void
+caja_navigation_window_slot_clear_forward_list (CajaNavigationWindowSlot *slot)
+{
+ g_assert (CAJA_IS_NAVIGATION_WINDOW_SLOT (slot));
+
+ eel_g_object_list_free (slot->forward_list);
+ slot->forward_list = NULL;
+}
+
+void
+caja_navigation_window_slot_clear_back_list (CajaNavigationWindowSlot *slot)
+{
+ g_assert (CAJA_IS_NAVIGATION_WINDOW_SLOT (slot));
+
+ eel_g_object_list_free (slot->back_list);
+ slot->back_list = NULL;
+}
+
+static void
+query_editor_changed_callback (CajaSearchBar *bar,
+ CajaQuery *query,
+ gboolean reload,
+ CajaWindowSlot *slot)
+{
+ CajaDirectory *directory;
+
+ g_assert (CAJA_IS_FILE (slot->viewed_file));
+
+ directory = caja_directory_get_for_file (slot->viewed_file);
+ g_assert (CAJA_IS_SEARCH_DIRECTORY (directory));
+
+ caja_search_directory_set_query (CAJA_SEARCH_DIRECTORY (directory),
+ query);
+ if (reload)
+ {
+ caja_window_slot_reload (slot);
+ }
+
+ caja_directory_unref (directory);
+}
+
+
+static void
+caja_navigation_window_slot_update_query_editor (CajaWindowSlot *slot)
+{
+ CajaDirectory *directory;
+ CajaSearchDirectory *search_directory;
+ CajaQuery *query;
+ CajaNavigationWindow *navigation_window;
+ GtkWidget *query_editor;
+
+ g_assert (slot->pane->window != NULL);
+ navigation_window = CAJA_NAVIGATION_WINDOW (slot->pane->window);
+
+ query_editor = NULL;
+
+ directory = caja_directory_get (slot->location);
+ if (CAJA_IS_SEARCH_DIRECTORY (directory))
+ {
+ search_directory = CAJA_SEARCH_DIRECTORY (directory);
+
+ if (caja_search_directory_is_saved_search (search_directory))
+ {
+ query_editor = caja_query_editor_new (TRUE,
+ caja_search_directory_is_indexed (search_directory));
+ }
+ else
+ {
+ query_editor = caja_query_editor_new_with_bar (FALSE,
+ caja_search_directory_is_indexed (search_directory),
+ slot->pane->window->details->active_pane->active_slot == slot,
+ CAJA_SEARCH_BAR (CAJA_NAVIGATION_WINDOW_PANE (slot->pane)->search_bar),
+ slot);
+ }
+ }
+
+ slot->query_editor = CAJA_QUERY_EDITOR (query_editor);
+
+ if (query_editor != NULL)
+ {
+ g_signal_connect_object (query_editor, "changed",
+ G_CALLBACK (query_editor_changed_callback), slot, 0);
+
+ query = caja_search_directory_get_query (search_directory);
+ if (query != NULL)
+ {
+ caja_query_editor_set_query (CAJA_QUERY_EDITOR (query_editor),
+ query);
+ g_object_unref (query);
+ }
+ else
+ {
+ caja_query_editor_set_default_query (CAJA_QUERY_EDITOR (query_editor));
+ }
+
+ caja_window_slot_add_extra_location_widget (slot, query_editor);
+ gtk_widget_show (query_editor);
+ caja_query_editor_grab_focus (CAJA_QUERY_EDITOR (query_editor));
+ }
+
+ caja_directory_unref (directory);
+}
+
+static void
+caja_navigation_window_slot_active (CajaWindowSlot *slot)
+{
+ CajaNavigationWindow *window;
+ CajaNavigationWindowSlot *navigation_slot;
+ CajaNavigationWindowPane *pane;
+ int page_num;
+
+ navigation_slot = CAJA_NAVIGATION_WINDOW_SLOT (slot);
+ pane = CAJA_NAVIGATION_WINDOW_PANE (slot->pane);
+ window = CAJA_NAVIGATION_WINDOW (slot->pane->window);
+
+ page_num = gtk_notebook_page_num (GTK_NOTEBOOK (pane->notebook),
+ slot->content_box);
+ g_assert (page_num >= 0);
+
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (pane->notebook), page_num);
+
+ EEL_CALL_PARENT (CAJA_WINDOW_SLOT_CLASS, active, (slot));
+
+ if (slot->viewed_file != NULL)
+ {
+ caja_navigation_window_load_extension_toolbar_items (window);
+ }
+}
+
+static void
+caja_navigation_window_slot_dispose (GObject *object)
+{
+ CajaNavigationWindowSlot *slot;
+
+ slot = CAJA_NAVIGATION_WINDOW_SLOT (object);
+
+ caja_navigation_window_slot_clear_forward_list (slot);
+ caja_navigation_window_slot_clear_back_list (slot);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+caja_navigation_window_slot_init (CajaNavigationWindowSlot *slot)
+{
+}
+
+static void
+caja_navigation_window_slot_class_init (CajaNavigationWindowSlotClass *class)
+{
+ CAJA_WINDOW_SLOT_CLASS (class)->active = caja_navigation_window_slot_active;
+ CAJA_WINDOW_SLOT_CLASS (class)->update_query_editor = caja_navigation_window_slot_update_query_editor;
+
+ G_OBJECT_CLASS (class)->dispose = caja_navigation_window_slot_dispose;
+}
+
diff --git a/src/caja-navigation-window-slot.h b/src/caja-navigation-window-slot.h
new file mode 100644
index 00000000..f8cfbec8
--- /dev/null
+++ b/src/caja-navigation-window-slot.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-navigation-window-slot.h: Caja navigation window slot
+
+ Copyright (C) 2008 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with this program; if not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Christian Neumair <[email protected]>
+*/
+
+#ifndef CAJA_NAVIGATION_WINDOW_SLOT_H
+#define CAJA_NAVIGATION_WINDOW_SLOT_H
+
+#include "caja-window-slot.h"
+
+typedef struct CajaNavigationWindowSlot CajaNavigationWindowSlot;
+typedef struct CajaNavigationWindowSlotClass CajaNavigationWindowSlotClass;
+
+
+#define CAJA_TYPE_NAVIGATION_WINDOW_SLOT (caja_navigation_window_slot_get_type())
+#define CAJA_NAVIGATION_WINDOW_SLOT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), CAJA_NAVIGATION_WINDOW_SLOT_CLASS, CajaNavigationWindowSlotClass))
+#define CAJA_NAVIGATION_WINDOW_SLOT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_NAVIGATION_WINDOW_SLOT, CajaNavigationWindowSlot))
+#define CAJA_IS_NAVIGATION_WINDOW_SLOT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_NAVIGATION_WINDOW_SLOT))
+#define CAJA_IS_NAVIGATION_WINDOW_SLOT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), CAJA_TYPE_NAVIGATION_WINDOW_SLOT))
+#define CAJA_NAVIGATION_WINDOW_SLOT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), CAJA_TYPE_NAVIGATION_WINDOW_SLOT, CajaNavigationWindowSlotClass))
+
+typedef enum
+{
+ CAJA_BAR_PATH,
+ CAJA_BAR_NAVIGATION,
+ CAJA_BAR_SEARCH
+} CajaBarMode;
+
+struct CajaNavigationWindowSlot
+{
+ CajaWindowSlot parent;
+
+ CajaBarMode bar_mode;
+ GtkTreeModel *viewer_model;
+ int num_viewers;
+
+ /* Back/Forward chain, and history list.
+ * The data in these lists are CajaBookmark pointers.
+ */
+ GList *back_list, *forward_list;
+
+ /* Current views stuff */
+ GList *sidebar_panels;
+};
+
+struct CajaNavigationWindowSlotClass
+{
+ CajaWindowSlotClass parent;
+};
+
+GType caja_navigation_window_slot_get_type (void);
+
+gboolean caja_navigation_window_slot_should_close_with_mount (CajaNavigationWindowSlot *slot,
+ GMount *mount);
+
+void caja_navigation_window_slot_clear_forward_list (CajaNavigationWindowSlot *slot);
+void caja_navigation_window_slot_clear_back_list (CajaNavigationWindowSlot *slot);
+
+#endif /* CAJA_NAVIGATION_WINDOW_SLOT_H */
diff --git a/src/caja-navigation-window-ui.xml b/src/caja-navigation-window-ui.xml
new file mode 100644
index 00000000..76b34cb8
--- /dev/null
+++ b/src/caja-navigation-window-ui.xml
@@ -0,0 +1,77 @@
+<ui>
+<accelerator action="ShowSearch"/>
+<accelerator action="SplitViewNextPane"/>
+<accelerator action="TabsPrevious"/>
+<accelerator action="TabsNext"/>
+<accelerator action="TabsMoveLeft"/>
+<accelerator action="TabsMoveRight"/>
+<menubar name="MenuBar">
+ <menu action="File">
+ <placeholder name="New Items Placeholder">
+ <menuitem name="New Tab" action="New Tab"/>
+ <menuitem name="New Window" action="New Window"/>
+ <menuitem name="Folder Window" action="Folder Window"/>
+ <separator/>
+ </placeholder>
+
+ <placeholder name="Close Items Placeholder">
+ <menuitem name="Close All Windows" action="Close All Windows"/>
+ </placeholder>
+ </menu>
+ <menu action="View">
+ <placeholder name="Show Hide Placeholder">
+ <menuitem name="Show Hide Toolbar" action="Show Hide Toolbar"/>
+ <menuitem name="Show Hide Sidebar" action="Show Hide Sidebar"/>
+ <menuitem name="Show Hide Location Bar" action="Show Hide Location Bar"/>
+ <menuitem name="Show Hide Statusbar" action="Show Hide Statusbar"/>
+ <menuitem name="Show Hide Extra Pane" action="Show Hide Extra Pane"/>
+ </placeholder>
+ </menu>
+ <placeholder name="Other Menus">
+ <menu action="Go">
+ <placeholder name="Navigation Items">
+ <menuitem name="Up" action="Up"/>
+ <menuitem name="Back" action="Back"/>
+ <menuitem name="Forward" action="Forward"/>
+ <menuitem name="SplitViewSameLocationMenu" action="SplitViewSameLocation"/>
+ </placeholder>
+ <separator/>
+ <menuitem name="Home" action="Home"/>
+ <menuitem name="Computer" action="Go to Computer"/>
+ <menuitem name="Go to Templates" action="Go to Templates"/>
+ <menuitem name="Go to Trash" action="Go to Trash"/>
+ <menuitem name="Go to Network" action="Go to Network"/>
+ <menuitem name="Go to Location" action="Go to Location"/>
+ <menuitem name="Search" action="Search"/>
+ <separator/>
+ <menuitem name="Clear History" action="Clear History"/>
+ <separator/>
+ <placeholder name="History Placeholder"/>
+ </menu>
+ <menu action="Bookmarks">
+ <menuitem name="Add Bookmark" action="Add Bookmark"/>
+ <menuitem name="Edit Bookmark" action="Edit Bookmarks"/>
+ <separator/>
+ <placeholder name="Bookmarks Placeholder"/>
+ </menu>
+ </placeholder>
+</menubar>
+<toolbar name="Toolbar">
+ <toolitem name="Back" action="Back"/>
+ <toolitem name="Forward" action="Forward"/>
+
+ <toolitem name="Up" action="Up"/>
+ <toolitem name="Stop" action="Stop"/>
+ <toolitem name="Reload" action="Reload"/>
+ <separator/>
+ <toolitem name="Home" action="Home"/>
+ <toolitem name="Computer" action="Go to Computer"/>
+ <separator/>
+ <toolitem name="Zoom" action="Zoom"/>
+ <toolitem name="ViewAs" action="ViewAs"/>
+ <toolitem name="Search" action="Search"/>
+ <placeholder name="Extra Buttons Placeholder">
+ <placeholder name="Extension Actions"/>
+ </placeholder>
+</toolbar>
+</ui>
diff --git a/src/caja-navigation-window.c b/src/caja-navigation-window.c
new file mode 100644
index 00000000..bbbd4320
--- /dev/null
+++ b/src/caja-navigation-window.c
@@ -0,0 +1,1426 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 1999, 2000 Red Hat, Inc.
+ * Copyright (C) 1999, 2000, 2001 Eazel, Inc.
+ * Copyright (C) 2003 Ximian, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Authors: Elliot Lee <[email protected]>
+ * John Sullivan <[email protected]>
+ *
+ */
+
+/* caja-window.c: Implementation of the main window object */
+
+#include <config.h>
+#include "caja-window-private.h"
+
+#include "caja-actions.h"
+#include "caja-application.h"
+#include "caja-bookmarks-window.h"
+#include "caja-main.h"
+#include "caja-location-bar.h"
+#include "caja-query-editor.h"
+#include "caja-search-bar.h"
+#include "caja-navigation-window-slot.h"
+#include "caja-notebook.h"
+#include "caja-window-manage-views.h"
+#include "caja-navigation-window-pane.h"
+#include <eel/eel-gtk-extensions.h>
+#include <eel/eel-gtk-macros.h>
+#include <eel/eel-string.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#ifdef HAVE_X11_XF86KEYSYM_H
+#include <X11/XF86keysym.h>
+#endif
+#include <libcaja-private/caja-file-utilities.h>
+#include <libcaja-private/caja-file-attributes.h>
+#include <libcaja-private/caja-global-preferences.h>
+#include <libcaja-private/caja-horizontal-splitter.h>
+#include <libcaja-private/caja-icon-info.h>
+#include <libcaja-private/caja-metadata.h>
+#include <libcaja-private/caja-mime-actions.h>
+#include <libcaja-private/caja-program-choosing.h>
+#include <libcaja-private/caja-sidebar.h>
+#include <libcaja-private/caja-view-factory.h>
+#include <libcaja-private/caja-clipboard.h>
+#include <libcaja-private/caja-undo.h>
+#include <libcaja-private/caja-module.h>
+#include <libcaja-private/caja-sidebar-provider.h>
+#include <libcaja-private/caja-search-directory.h>
+#include <libcaja-private/caja-signaller.h>
+#include <math.h>
+#include <sys/time.h>
+
+
+/* FIXME bugzilla.gnome.org 41243:
+ * We should use inheritance instead of these special cases
+ * for the desktop window.
+ */
+#include "caja-desktop-window.h"
+
+#define MAX_TITLE_LENGTH 180
+
+#define MENU_PATH_BOOKMARKS_PLACEHOLDER "/MenuBar/Other Menus/Bookmarks/Bookmarks Placeholder"
+
+enum
+{
+ ARG_0,
+ ARG_APP_ID,
+ ARG_APP
+};
+
+static int side_pane_width_auto_value = 0;
+
+
+/* Forward and back buttons on the mouse */
+static gboolean mouse_extra_buttons = TRUE;
+static int mouse_forward_button = 9;
+static int mouse_back_button = 8;
+
+static void add_sidebar_panels (CajaNavigationWindow *window);
+static void side_panel_image_changed_callback (CajaSidebar *side_panel,
+ gpointer callback_data);
+static void always_use_location_entry_changed (gpointer callback_data);
+static void always_use_browser_changed (gpointer callback_data);
+static void mouse_back_button_changed (gpointer callback_data);
+static void mouse_forward_button_changed (gpointer callback_data);
+static void use_extra_mouse_buttons_changed (gpointer callback_data);
+static CajaWindowSlot *create_extra_pane (CajaNavigationWindow *window);
+
+
+G_DEFINE_TYPE (CajaNavigationWindow, caja_navigation_window, CAJA_TYPE_WINDOW)
+#define parent_class caja_navigation_window_parent_class
+
+static const struct
+{
+ unsigned int keyval;
+ const char *action;
+} extra_navigation_window_keybindings [] =
+{
+#ifdef HAVE_X11_XF86KEYSYM_H
+ { XF86XK_Back, CAJA_ACTION_BACK },
+ { XF86XK_Forward, CAJA_ACTION_FORWARD }
+#endif
+};
+
+static void
+caja_navigation_window_init (CajaNavigationWindow *window)
+{
+ GtkUIManager *ui_manager;
+ GtkWidget *toolbar;
+ CajaWindow *win;
+ CajaNavigationWindowPane *pane;
+ GtkWidget *hpaned;
+ GtkWidget *vbox;
+
+ win = CAJA_WINDOW (window);
+
+ window->details = G_TYPE_INSTANCE_GET_PRIVATE (window, CAJA_TYPE_NAVIGATION_WINDOW, CajaNavigationWindowDetails);
+
+ pane = caja_navigation_window_pane_new (win);
+ win->details->panes = g_list_prepend (win->details->panes, pane);
+
+ window->details->header_size_group = gtk_size_group_new (GTK_SIZE_GROUP_VERTICAL);
+ gtk_size_group_set_ignore_hidden (window->details->header_size_group, FALSE);
+
+ window->details->content_paned = caja_horizontal_splitter_new ();
+ gtk_table_attach (GTK_TABLE (CAJA_WINDOW (window)->details->table),
+ window->details->content_paned,
+ /* X direction */ /* Y direction */
+ 0, 1, 3, 4,
+ GTK_EXPAND | GTK_FILL | GTK_SHRINK, GTK_EXPAND | GTK_FILL | GTK_SHRINK,
+ 0, 0);
+ gtk_widget_show (window->details->content_paned);
+
+ vbox = gtk_vbox_new (FALSE, 0);
+ caja_horizontal_splitter_pack2 (CAJA_HORIZONTAL_SPLITTER (window->details->content_paned), vbox);
+ gtk_widget_show (vbox);
+
+ hpaned = gtk_hpaned_new ();
+ gtk_box_pack_start (GTK_BOX (vbox), hpaned, TRUE, TRUE, 0);
+ gtk_widget_show (hpaned);
+ window->details->split_view_hpane = hpaned;
+
+ gtk_box_pack_start (GTK_BOX (vbox), win->details->statusbar, FALSE, FALSE, 0);
+ gtk_widget_show (win->details->statusbar);
+
+ caja_navigation_window_pane_setup (pane);
+
+ gtk_paned_pack1 (GTK_PANED(hpaned), pane->widget, TRUE, FALSE);
+ gtk_widget_show (pane->widget);
+
+ /* this has to be done after the location bar has been set up,
+ * but before menu stuff is being called */
+ caja_window_set_active_pane (win, CAJA_WINDOW_PANE (pane));
+
+ caja_navigation_window_initialize_actions (window);
+
+ caja_navigation_window_initialize_menus (window);
+
+ ui_manager = caja_window_get_ui_manager (CAJA_WINDOW (window));
+ toolbar = gtk_ui_manager_get_widget (ui_manager, "/Toolbar");
+ window->details->toolbar = toolbar;
+ gtk_table_attach (GTK_TABLE (CAJA_WINDOW (window)->details->table),
+ toolbar,
+ /* X direction */ /* Y direction */
+ 0, 1, 1, 2,
+ GTK_EXPAND | GTK_FILL | GTK_SHRINK, 0,
+ 0, 0);
+ gtk_widget_show (toolbar);
+
+ caja_navigation_window_initialize_toolbars (window);
+
+ /* Set initial sensitivity of some buttons & menu items
+ * now that they're all created.
+ */
+ caja_navigation_window_allow_back (window, FALSE);
+ caja_navigation_window_allow_forward (window, FALSE);
+
+ eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_ALWAYS_USE_LOCATION_ENTRY,
+ always_use_location_entry_changed,
+ window, G_OBJECT (window));
+
+ eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_ALWAYS_USE_BROWSER,
+ always_use_browser_changed,
+ window, G_OBJECT (window));
+}
+
+static void
+always_use_location_entry_changed (gpointer callback_data)
+{
+ CajaNavigationWindow *window;
+ GList *walk;
+ gboolean use_entry;
+
+ window = CAJA_NAVIGATION_WINDOW (callback_data);
+
+ use_entry = eel_preferences_get_boolean (CAJA_PREFERENCES_ALWAYS_USE_LOCATION_ENTRY);
+
+ for (walk = CAJA_WINDOW(window)->details->panes; walk; walk = walk->next)
+ {
+ caja_navigation_window_pane_always_use_location_entry (walk->data, use_entry);
+ }
+}
+
+static void
+always_use_browser_changed (gpointer callback_data)
+{
+ CajaNavigationWindow *window;
+
+ window = CAJA_NAVIGATION_WINDOW (callback_data);
+
+ caja_navigation_window_update_spatial_menu_item (window);
+}
+
+/* Sanity check: highest mouse button value I could find was 14. 5 is our
+ * lower threshold (well-documented to be the one of the button events for the
+ * scrollwheel), so it's hardcoded in the functions below. However, if you have
+ * a button that registers higher and want to map it, file a bug and
+ * we'll move the bar. Makes you wonder why the X guys don't have
+ * defined values for these like the XKB stuff, huh?
+ */
+#define UPPER_MOUSE_LIMIT 14
+
+static void
+mouse_back_button_changed (gpointer callback_data)
+{
+ int new_back_button;
+
+ new_back_button = eel_preferences_get_integer (CAJA_PREFERENCES_MOUSE_BACK_BUTTON);
+
+ /* Bounds checking */
+ if (new_back_button < 6 || new_back_button > UPPER_MOUSE_LIMIT)
+ return;
+
+ mouse_back_button = new_back_button;
+}
+
+static void
+mouse_forward_button_changed (gpointer callback_data)
+{
+ int new_forward_button;
+
+ new_forward_button = eel_preferences_get_integer (CAJA_PREFERENCES_MOUSE_FORWARD_BUTTON);
+
+ /* Bounds checking */
+ if (new_forward_button < 6 || new_forward_button > UPPER_MOUSE_LIMIT)
+ return;
+
+ mouse_forward_button = new_forward_button;
+}
+
+static void
+use_extra_mouse_buttons_changed (gpointer callback_data)
+{
+ mouse_extra_buttons = eel_preferences_get_boolean (CAJA_PREFERENCES_MOUSE_USE_EXTRA_BUTTONS);
+}
+
+void
+caja_navigation_window_unset_focus_widget (CajaNavigationWindow *window)
+{
+ if (window->details->last_focus_widget != NULL)
+ {
+ g_object_remove_weak_pointer (G_OBJECT (window->details->last_focus_widget),
+ (gpointer *) &window->details->last_focus_widget);
+ window->details->last_focus_widget = NULL;
+ }
+}
+
+gboolean
+caja_navigation_window_is_in_temporary_navigation_bar (GtkWidget *widget,
+ CajaNavigationWindow *window)
+{
+ GList *walk;
+ gboolean is_in_any = FALSE;
+
+ for (walk = CAJA_WINDOW(window)->details->panes; walk; walk = walk->next)
+ {
+ CajaNavigationWindowPane *pane = walk->data;
+ if(gtk_widget_get_ancestor (widget, CAJA_TYPE_NAVIGATION_BAR) != NULL &&
+ pane->temporary_navigation_bar)
+ is_in_any = TRUE;
+ }
+ return is_in_any;
+}
+
+gboolean
+caja_navigation_window_is_in_temporary_search_bar (GtkWidget *widget,
+ CajaNavigationWindow *window)
+{
+ GList *walk;
+ gboolean is_in_any = FALSE;
+
+ for (walk = CAJA_WINDOW(window)->details->panes; walk; walk = walk->next)
+ {
+ CajaNavigationWindowPane *pane = walk->data;
+ if(gtk_widget_get_ancestor (widget, CAJA_TYPE_SEARCH_BAR) != NULL &&
+ pane->temporary_search_bar)
+ is_in_any = TRUE;
+ }
+ return is_in_any;
+}
+
+static void
+remember_focus_widget (CajaNavigationWindow *window)
+{
+ CajaNavigationWindow *navigation_window;
+ GtkWidget *focus_widget;
+
+ navigation_window = CAJA_NAVIGATION_WINDOW (window);
+
+ focus_widget = gtk_window_get_focus (GTK_WINDOW (window));
+ if (focus_widget != NULL &&
+ !caja_navigation_window_is_in_temporary_navigation_bar (focus_widget, navigation_window) &&
+ !caja_navigation_window_is_in_temporary_search_bar (focus_widget, navigation_window))
+ {
+ caja_navigation_window_unset_focus_widget (navigation_window);
+
+ navigation_window->details->last_focus_widget = focus_widget;
+ g_object_add_weak_pointer (G_OBJECT (focus_widget),
+ (gpointer *) &(CAJA_NAVIGATION_WINDOW (window)->details->last_focus_widget));
+ }
+}
+
+void
+caja_navigation_window_restore_focus_widget (CajaNavigationWindow *window)
+{
+ if (window->details->last_focus_widget != NULL)
+ {
+ if (CAJA_IS_VIEW (window->details->last_focus_widget))
+ {
+ caja_view_grab_focus (CAJA_VIEW (window->details->last_focus_widget));
+ }
+ else
+ {
+ gtk_widget_grab_focus (window->details->last_focus_widget);
+ }
+
+ caja_navigation_window_unset_focus_widget (window);
+ }
+}
+
+static void
+side_pane_close_requested_callback (GtkWidget *widget,
+ gpointer user_data)
+{
+ CajaNavigationWindow *window;
+
+ window = CAJA_NAVIGATION_WINDOW (user_data);
+
+ caja_navigation_window_hide_sidebar (window);
+}
+
+static void
+side_pane_size_allocate_callback (GtkWidget *widget,
+ GtkAllocation *allocation,
+ gpointer user_data)
+{
+ CajaNavigationWindow *window;
+
+ window = CAJA_NAVIGATION_WINDOW (user_data);
+
+ if (allocation->width != window->details->side_pane_width)
+ {
+ window->details->side_pane_width = allocation->width;
+ if (eel_preferences_key_is_writable (CAJA_PREFERENCES_SIDEBAR_WIDTH))
+ {
+ eel_preferences_set_integer
+ (CAJA_PREFERENCES_SIDEBAR_WIDTH,
+ allocation->width <= 1 ? 0 : allocation->width);
+ }
+ }
+}
+
+static void
+setup_side_pane_width (CajaNavigationWindow *window)
+{
+ static gboolean setup_auto_value= TRUE;
+
+ g_return_if_fail (window->sidebar != NULL);
+
+ if (setup_auto_value)
+ {
+ setup_auto_value = FALSE;
+ eel_preferences_add_auto_integer
+ (CAJA_PREFERENCES_SIDEBAR_WIDTH,
+ &side_pane_width_auto_value);
+ }
+
+ window->details->side_pane_width = side_pane_width_auto_value;
+
+ gtk_paned_set_position (GTK_PANED (window->details->content_paned),
+ side_pane_width_auto_value);
+}
+
+static void
+set_current_side_panel (CajaNavigationWindow *window,
+ CajaSidebar *panel)
+{
+ if (window->details->current_side_panel)
+ {
+ caja_sidebar_is_visible_changed (window->details->current_side_panel,
+ FALSE);
+ eel_remove_weak_pointer (&window->details->current_side_panel);
+ }
+
+ if (panel != NULL)
+ {
+ caja_sidebar_is_visible_changed (panel, TRUE);
+ }
+ window->details->current_side_panel = panel;
+ eel_add_weak_pointer (&window->details->current_side_panel);
+}
+
+static void
+side_pane_switch_page_callback (CajaSidePane *side_pane,
+ GtkWidget *widget,
+ CajaNavigationWindow *window)
+{
+ const char *id;
+ CajaSidebar *sidebar;
+
+ sidebar = CAJA_SIDEBAR (widget);
+
+ if (sidebar == NULL)
+ {
+ return;
+ }
+
+ set_current_side_panel (window, sidebar);
+
+ id = caja_sidebar_get_sidebar_id (sidebar);
+ if (eel_preferences_key_is_writable (CAJA_PREFERENCES_SIDE_PANE_VIEW))
+ {
+ eel_preferences_set (CAJA_PREFERENCES_SIDE_PANE_VIEW, id);
+ }
+}
+
+static void
+caja_navigation_window_set_up_sidebar (CajaNavigationWindow *window)
+{
+ GtkWidget *title;
+
+ window->sidebar = caja_side_pane_new ();
+
+ title = caja_side_pane_get_title (window->sidebar);
+ gtk_size_group_add_widget (window->details->header_size_group,
+ title);
+
+ gtk_paned_pack1 (GTK_PANED (window->details->content_paned),
+ GTK_WIDGET (window->sidebar),
+ FALSE, FALSE);
+
+ setup_side_pane_width (window);
+ g_signal_connect (window->sidebar,
+ "size_allocate",
+ G_CALLBACK (side_pane_size_allocate_callback),
+ window);
+
+ add_sidebar_panels (window);
+
+ g_signal_connect (window->sidebar,
+ "close_requested",
+ G_CALLBACK (side_pane_close_requested_callback),
+ window);
+
+ g_signal_connect (window->sidebar,
+ "switch_page",
+ G_CALLBACK (side_pane_switch_page_callback),
+ window);
+
+ gtk_widget_show (GTK_WIDGET (window->sidebar));
+}
+
+static void
+caja_navigation_window_tear_down_sidebar (CajaNavigationWindow *window)
+{
+ GList *node, *next;
+ CajaSidebar *sidebar_panel;
+
+ g_signal_handlers_disconnect_by_func (window->sidebar,
+ side_pane_switch_page_callback,
+ window);
+
+ for (node = window->sidebar_panels; node != NULL; node = next)
+ {
+ next = node->next;
+
+ sidebar_panel = CAJA_SIDEBAR (node->data);
+
+ caja_navigation_window_remove_sidebar_panel (window,
+ sidebar_panel);
+ }
+
+ gtk_widget_destroy (GTK_WIDGET (window->sidebar));
+ window->sidebar = NULL;
+}
+
+static void
+caja_navigation_window_unrealize (GtkWidget *widget)
+{
+ CajaNavigationWindow *window;
+
+ window = CAJA_NAVIGATION_WINDOW (widget);
+
+ GTK_WIDGET_CLASS (parent_class)->unrealize (widget);
+}
+
+static gboolean
+caja_navigation_window_state_event (GtkWidget *widget,
+ GdkEventWindowState *event)
+{
+ if (event->changed_mask & GDK_WINDOW_STATE_MAXIMIZED)
+ {
+ eel_preferences_set_boolean (CAJA_PREFERENCES_NAVIGATION_WINDOW_MAXIMIZED,
+ event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED);
+ }
+
+ if (GTK_WIDGET_CLASS (parent_class)->window_state_event != NULL)
+ {
+ return GTK_WIDGET_CLASS (parent_class)->window_state_event (widget, event);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+caja_navigation_window_key_press_event (GtkWidget *widget,
+ GdkEventKey *event)
+{
+ CajaNavigationWindow *window;
+ int i;
+
+ window = CAJA_NAVIGATION_WINDOW (widget);
+
+ for (i = 0; i < G_N_ELEMENTS (extra_navigation_window_keybindings); i++)
+ {
+ if (extra_navigation_window_keybindings[i].keyval == event->keyval)
+ {
+ GtkAction *action;
+
+ action = gtk_action_group_get_action (window->details->navigation_action_group,
+ extra_navigation_window_keybindings[i].action);
+
+ g_assert (action != NULL);
+ if (gtk_action_is_sensitive (action))
+ {
+ gtk_action_activate (action);
+ return TRUE;
+ }
+
+ break;
+ }
+ }
+
+ return GTK_WIDGET_CLASS (caja_navigation_window_parent_class)->key_press_event (widget, event);
+}
+
+static gboolean
+caja_navigation_window_button_press_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ CajaNavigationWindow *window;
+ gboolean handled;
+
+ handled = FALSE;
+ window = CAJA_NAVIGATION_WINDOW (widget);
+
+ if (mouse_extra_buttons && (event->button == mouse_back_button))
+ {
+ caja_navigation_window_go_back (window);
+ handled = TRUE;
+ }
+ else if (mouse_extra_buttons && (event->button == mouse_forward_button))
+ {
+ caja_navigation_window_go_forward (window);
+ handled = TRUE;
+ }
+ else if (GTK_WIDGET_CLASS (caja_navigation_window_parent_class)->button_press_event)
+ {
+ handled = GTK_WIDGET_CLASS (caja_navigation_window_parent_class)->button_press_event (widget, event);
+ }
+ else
+ {
+ handled = FALSE;
+ }
+ return handled;
+}
+
+static void
+caja_navigation_window_destroy (GtkObject *object)
+{
+ CajaNavigationWindow *window;
+
+ window = CAJA_NAVIGATION_WINDOW (object);
+
+ caja_navigation_window_unset_focus_widget (window);
+
+ window->sidebar = NULL;
+ g_list_foreach (window->sidebar_panels, (GFunc)g_object_unref, NULL);
+ g_list_free (window->sidebar_panels);
+ window->sidebar_panels = NULL;
+
+ window->details->content_paned = NULL;
+ window->details->split_view_hpane = NULL;
+
+ GTK_OBJECT_CLASS (parent_class)->destroy (object);
+}
+
+static void
+caja_navigation_window_finalize (GObject *object)
+{
+ CajaNavigationWindow *window;
+
+ window = CAJA_NAVIGATION_WINDOW (object);
+
+ caja_navigation_window_remove_go_menu_callback (window);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+/*
+ * Main API
+ */
+
+void
+caja_navigation_window_add_sidebar_panel (CajaNavigationWindow *window,
+ CajaSidebar *sidebar_panel)
+{
+ const char *sidebar_id;
+ char *label;
+ char *tooltip;
+ char *default_id;
+ GdkPixbuf *icon;
+
+ g_return_if_fail (CAJA_IS_NAVIGATION_WINDOW (window));
+ g_return_if_fail (CAJA_IS_SIDEBAR (sidebar_panel));
+ g_return_if_fail (CAJA_IS_SIDE_PANE (window->sidebar));
+ g_return_if_fail (g_list_find (window->sidebar_panels, sidebar_panel) == NULL);
+
+ label = caja_sidebar_get_tab_label (sidebar_panel);
+ tooltip = caja_sidebar_get_tab_tooltip (sidebar_panel);
+ caja_side_pane_add_panel (window->sidebar,
+ GTK_WIDGET (sidebar_panel),
+ label,
+ tooltip);
+ g_free (tooltip);
+ g_free (label);
+
+ icon = caja_sidebar_get_tab_icon (sidebar_panel);
+ caja_side_pane_set_panel_image (CAJA_NAVIGATION_WINDOW (window)->sidebar,
+ GTK_WIDGET (sidebar_panel),
+ icon);
+ if (icon)
+ {
+ g_object_unref (icon);
+ }
+
+ g_signal_connect (sidebar_panel, "tab_icon_changed",
+ (GCallback)side_panel_image_changed_callback, window);
+
+ window->sidebar_panels = g_list_prepend (window->sidebar_panels,
+ g_object_ref (sidebar_panel));
+
+ /* Show if default */
+ sidebar_id = caja_sidebar_get_sidebar_id (sidebar_panel);
+ default_id = eel_preferences_get (CAJA_PREFERENCES_SIDE_PANE_VIEW);
+ if (sidebar_id && default_id && !strcmp (sidebar_id, default_id))
+ {
+ caja_side_pane_show_panel (window->sidebar,
+ GTK_WIDGET (sidebar_panel));
+ }
+ g_free (default_id);
+}
+
+void
+caja_navigation_window_remove_sidebar_panel (CajaNavigationWindow *window,
+ CajaSidebar *sidebar_panel)
+{
+ g_return_if_fail (CAJA_IS_NAVIGATION_WINDOW (window));
+ g_return_if_fail (CAJA_IS_SIDEBAR (sidebar_panel));
+
+ if (g_list_find (window->sidebar_panels, sidebar_panel) == NULL)
+ {
+ return;
+ }
+
+ g_signal_handlers_disconnect_by_func (sidebar_panel, side_panel_image_changed_callback, window);
+
+ caja_side_pane_remove_panel (window->sidebar,
+ GTK_WIDGET (sidebar_panel));
+ window->sidebar_panels = g_list_remove (window->sidebar_panels, sidebar_panel);
+ g_object_unref (sidebar_panel);
+}
+
+void
+caja_navigation_window_go_back (CajaNavigationWindow *window)
+{
+ caja_navigation_window_back_or_forward (window, TRUE, 0, FALSE);
+}
+
+void
+caja_navigation_window_go_forward (CajaNavigationWindow *window)
+{
+ caja_navigation_window_back_or_forward (window, FALSE, 0, FALSE);
+}
+
+void
+caja_navigation_window_allow_back (CajaNavigationWindow *window, gboolean allow)
+{
+ GtkAction *action;
+
+ action = gtk_action_group_get_action (window->details->navigation_action_group,
+ CAJA_ACTION_BACK);
+
+ gtk_action_set_sensitive (action, allow);
+}
+
+void
+caja_navigation_window_allow_forward (CajaNavigationWindow *window, gboolean allow)
+{
+ GtkAction *action;
+
+ action = gtk_action_group_get_action (window->details->navigation_action_group,
+ CAJA_ACTION_FORWARD);
+
+ gtk_action_set_sensitive (action, allow);
+}
+
+static void
+real_sync_title (CajaWindow *window,
+ CajaWindowSlot *slot)
+{
+ CajaNavigationWindow *navigation_window;
+ CajaNavigationWindowPane *pane;
+ CajaNotebook *notebook;
+ char *full_title;
+ char *window_title;
+
+ navigation_window = CAJA_NAVIGATION_WINDOW (window);
+
+ EEL_CALL_PARENT (CAJA_WINDOW_CLASS,
+ sync_title, (window, slot));
+
+ if (slot == window->details->active_pane->active_slot)
+ {
+ /* if spatial mode is default, we keep "File Browser" in the window title
+ * to recognize browser windows. Otherwise, we default to the directory name.
+ */
+ if (!eel_preferences_get_boolean (CAJA_PREFERENCES_ALWAYS_USE_BROWSER))
+ {
+ full_title = g_strdup_printf (_("%s - File Browser"), slot->title);
+ window_title = eel_str_middle_truncate (full_title, MAX_TITLE_LENGTH);
+ g_free (full_title);
+ }
+ else
+ {
+ window_title = eel_str_middle_truncate (slot->title, MAX_TITLE_LENGTH);
+ }
+
+ gtk_window_set_title (GTK_WINDOW (window), window_title);
+ g_free (window_title);
+ }
+
+ pane = CAJA_NAVIGATION_WINDOW_PANE (slot->pane);
+ notebook = CAJA_NOTEBOOK (pane->notebook);
+ caja_notebook_sync_tab_label (notebook, slot);
+}
+
+static CajaIconInfo *
+real_get_icon (CajaWindow *window,
+ CajaWindowSlot *slot)
+{
+ return caja_file_get_icon (slot->viewed_file, 48,
+ CAJA_FILE_ICON_FLAGS_IGNORE_VISITING |
+ CAJA_FILE_ICON_FLAGS_USE_MOUNT_ICON);
+}
+
+static void
+real_sync_allow_stop (CajaWindow *window,
+ CajaWindowSlot *slot)
+{
+ CajaNavigationWindow *navigation_window;
+ CajaNotebook *notebook;
+
+ navigation_window = CAJA_NAVIGATION_WINDOW (window);
+ caja_navigation_window_set_spinner_active (navigation_window, slot->allow_stop);
+
+ notebook = CAJA_NOTEBOOK (CAJA_NAVIGATION_WINDOW_PANE (slot->pane)->notebook);
+ caja_notebook_sync_loading (notebook, slot);
+}
+
+static void
+real_prompt_for_location (CajaWindow *window, const char *initial)
+{
+ CajaNavigationWindowPane *pane;
+
+ remember_focus_widget (CAJA_NAVIGATION_WINDOW (window));
+
+ pane = CAJA_NAVIGATION_WINDOW_PANE (window->details->active_pane);
+
+ caja_navigation_window_pane_show_location_bar_temporarily (pane);
+ caja_navigation_window_pane_show_navigation_bar_temporarily (pane);
+
+ if (initial)
+ {
+ caja_navigation_bar_set_location (CAJA_NAVIGATION_BAR (pane->navigation_bar),
+ initial);
+ }
+}
+
+void
+caja_navigation_window_show_search (CajaNavigationWindow *window)
+{
+ CajaNavigationWindowPane *pane;
+
+ pane = CAJA_NAVIGATION_WINDOW_PANE (CAJA_WINDOW (window)->details->active_pane);
+ if (!caja_navigation_window_pane_search_bar_showing (pane))
+ {
+ remember_focus_widget (window);
+
+ caja_navigation_window_pane_show_location_bar_temporarily (pane);
+ caja_navigation_window_pane_set_bar_mode (pane, CAJA_BAR_SEARCH);
+ pane->temporary_search_bar = TRUE;
+ caja_search_bar_clear (CAJA_SEARCH_BAR (pane->search_bar));
+ }
+
+ caja_search_bar_grab_focus (CAJA_SEARCH_BAR (pane->search_bar));
+}
+
+void
+caja_navigation_window_hide_search (CajaNavigationWindow *window)
+{
+ CajaNavigationWindowPane *pane = CAJA_NAVIGATION_WINDOW_PANE (CAJA_WINDOW (window)->details->active_pane);
+ if (caja_navigation_window_pane_search_bar_showing (pane))
+ {
+ if (caja_navigation_window_pane_hide_temporary_bars (pane))
+ {
+ caja_navigation_window_restore_focus_widget (window);
+ }
+ }
+}
+
+/* This updates the UI state of the search button, but does not
+ in itself trigger a search action */
+void
+caja_navigation_window_set_search_button (CajaNavigationWindow *window,
+ gboolean state)
+{
+ GtkAction *action;
+
+ action = gtk_action_group_get_action (window->details->navigation_action_group,
+ "Search");
+
+ /* Block callback so we don't activate the action and thus focus the
+ search entry */
+ g_object_set_data (G_OBJECT (action), "blocked", GINT_TO_POINTER (1));
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), state);
+ g_object_set_data (G_OBJECT (action), "blocked", NULL);
+}
+
+static void
+side_panel_image_changed_callback (CajaSidebar *side_panel,
+ gpointer callback_data)
+{
+ CajaWindow *window;
+ GdkPixbuf *icon;
+
+ window = CAJA_WINDOW (callback_data);
+
+ icon = caja_sidebar_get_tab_icon (side_panel);
+ caja_side_pane_set_panel_image (CAJA_NAVIGATION_WINDOW (window)->sidebar,
+ GTK_WIDGET (side_panel),
+ icon);
+ if (icon != NULL)
+ {
+ g_object_unref (icon);
+ }
+}
+
+/**
+ * add_sidebar_panels:
+ * @window: A CajaNavigationWindow
+ *
+ * Adds all sidebars available
+ *
+ */
+static void
+add_sidebar_panels (CajaNavigationWindow *window)
+{
+ GtkWidget *current;
+ GList *providers;
+ GList *p;
+ CajaSidebar *sidebar_panel;
+
+ g_assert (CAJA_IS_NAVIGATION_WINDOW (window));
+
+ if (window->sidebar == NULL)
+ {
+ return;
+ }
+
+ providers = caja_module_get_extensions_for_type (CAJA_TYPE_SIDEBAR_PROVIDER);
+
+ for (p = providers; p != NULL; p = p->next)
+ {
+ CajaSidebarProvider *provider;
+
+ provider = CAJA_SIDEBAR_PROVIDER (p->data);
+
+ sidebar_panel = caja_sidebar_provider_create (provider,
+ CAJA_WINDOW_INFO (window));
+ caja_navigation_window_add_sidebar_panel (window,
+ sidebar_panel);
+
+ g_object_unref (sidebar_panel);
+ }
+
+ caja_module_extension_list_free (providers);
+
+ current = caja_side_pane_get_current_panel (window->sidebar);
+ set_current_side_panel
+ (window,
+ CAJA_SIDEBAR (current));
+}
+
+gboolean
+caja_navigation_window_toolbar_showing (CajaNavigationWindow *window)
+{
+ if (window->details->toolbar != NULL)
+ {
+ return gtk_widget_get_visible (window->details->toolbar);
+ }
+ /* If we're not visible yet we haven't changed visibility, so its TRUE */
+ return TRUE;
+}
+
+void
+caja_navigation_window_hide_status_bar (CajaNavigationWindow *window)
+{
+ gtk_widget_hide (CAJA_WINDOW (window)->details->statusbar);
+
+ caja_navigation_window_update_show_hide_menu_items (window);
+ if (eel_preferences_key_is_writable (CAJA_PREFERENCES_START_WITH_STATUS_BAR) &&
+ eel_preferences_get_boolean (CAJA_PREFERENCES_START_WITH_STATUS_BAR))
+ {
+ eel_preferences_set_boolean (CAJA_PREFERENCES_START_WITH_STATUS_BAR, FALSE);
+ }
+}
+
+void
+caja_navigation_window_show_status_bar (CajaNavigationWindow *window)
+{
+ gtk_widget_show (CAJA_WINDOW (window)->details->statusbar);
+
+ caja_navigation_window_update_show_hide_menu_items (window);
+ if (eel_preferences_key_is_writable (CAJA_PREFERENCES_START_WITH_STATUS_BAR) &&
+ !eel_preferences_get_boolean (CAJA_PREFERENCES_START_WITH_STATUS_BAR))
+ {
+ eel_preferences_set_boolean (CAJA_PREFERENCES_START_WITH_STATUS_BAR, TRUE);
+ }
+}
+
+gboolean
+caja_navigation_window_status_bar_showing (CajaNavigationWindow *window)
+{
+ if (CAJA_WINDOW (window)->details->statusbar != NULL)
+ {
+ return gtk_widget_get_visible (CAJA_WINDOW (window)->details->statusbar);
+ }
+ /* If we're not visible yet we haven't changed visibility, so its TRUE */
+ return TRUE;
+}
+
+
+void
+caja_navigation_window_hide_toolbar (CajaNavigationWindow *window)
+{
+ gtk_widget_hide (window->details->toolbar);
+ caja_navigation_window_update_show_hide_menu_items (window);
+ if (eel_preferences_key_is_writable (CAJA_PREFERENCES_START_WITH_TOOLBAR) &&
+ eel_preferences_get_boolean (CAJA_PREFERENCES_START_WITH_TOOLBAR))
+ {
+ eel_preferences_set_boolean (CAJA_PREFERENCES_START_WITH_TOOLBAR, FALSE);
+ }
+}
+
+void
+caja_navigation_window_show_toolbar (CajaNavigationWindow *window)
+{
+ gtk_widget_show (window->details->toolbar);
+ caja_navigation_window_update_show_hide_menu_items (window);
+ if (eel_preferences_key_is_writable (CAJA_PREFERENCES_START_WITH_TOOLBAR) &&
+ !eel_preferences_get_boolean (CAJA_PREFERENCES_START_WITH_TOOLBAR))
+ {
+ eel_preferences_set_boolean (CAJA_PREFERENCES_START_WITH_TOOLBAR, TRUE);
+ }
+}
+
+void
+caja_navigation_window_hide_sidebar (CajaNavigationWindow *window)
+{
+ if (window->sidebar == NULL)
+ {
+ return;
+ }
+
+ caja_navigation_window_tear_down_sidebar (window);
+ caja_navigation_window_update_show_hide_menu_items (window);
+
+ if (eel_preferences_key_is_writable (CAJA_PREFERENCES_START_WITH_SIDEBAR) &&
+ eel_preferences_get_boolean (CAJA_PREFERENCES_START_WITH_SIDEBAR))
+ {
+ eel_preferences_set_boolean (CAJA_PREFERENCES_START_WITH_SIDEBAR, FALSE);
+ }
+}
+
+void
+caja_navigation_window_show_sidebar (CajaNavigationWindow *window)
+{
+ if (window->sidebar != NULL)
+ {
+ return;
+ }
+
+ caja_navigation_window_set_up_sidebar (window);
+ caja_navigation_window_update_show_hide_menu_items (window);
+ if (eel_preferences_key_is_writable (CAJA_PREFERENCES_START_WITH_SIDEBAR) &&
+ !eel_preferences_get_boolean (CAJA_PREFERENCES_START_WITH_SIDEBAR))
+ {
+ eel_preferences_set_boolean (CAJA_PREFERENCES_START_WITH_SIDEBAR, TRUE);
+ }
+}
+
+gboolean
+caja_navigation_window_sidebar_showing (CajaNavigationWindow *window)
+{
+ g_return_val_if_fail (CAJA_IS_NAVIGATION_WINDOW (window), FALSE);
+
+ return (window->sidebar != NULL)
+ && caja_horizontal_splitter_is_hidden (CAJA_HORIZONTAL_SPLITTER (window->details->content_paned));
+}
+
+/**
+ * caja_navigation_window_get_base_page_index:
+ * @window: Window to get index from
+ *
+ * Returns the index of the base page in the history list.
+ * Base page is not the currently displayed page, but the page
+ * that acts as the base from which the back and forward commands
+ * navigate from.
+ */
+gint
+caja_navigation_window_get_base_page_index (CajaNavigationWindow *window)
+{
+ CajaNavigationWindowSlot *slot;
+ gint forward_count;
+
+ slot = CAJA_NAVIGATION_WINDOW_SLOT (CAJA_WINDOW (window)->details->active_pane->active_slot);
+
+ forward_count = g_list_length (slot->forward_list);
+
+ /* If forward is empty, the base it at the top of the list */
+ if (forward_count == 0)
+ {
+ return 0;
+ }
+
+ /* The forward count indicate the relative postion of the base page
+ * in the history list
+ */
+ return forward_count;
+}
+
+/**
+ * caja_navigation_window_show:
+ * @widget: a #GtkWidget.
+ *
+ * Call parent and then show/hide window items
+ * base on user prefs.
+ */
+static void
+caja_navigation_window_show (GtkWidget *widget)
+{
+ CajaNavigationWindow *window;
+ gboolean show_location_bar;
+ gboolean always_use_location_entry;
+ GList *walk;
+
+ window = CAJA_NAVIGATION_WINDOW (widget);
+
+ /* Initially show or hide views based on preferences; once the window is displayed
+ * these can be controlled on a per-window basis from View menu items.
+ */
+
+ if (eel_preferences_get_boolean (CAJA_PREFERENCES_START_WITH_TOOLBAR))
+ {
+ caja_navigation_window_show_toolbar (window);
+ }
+ else
+ {
+ caja_navigation_window_hide_toolbar (window);
+ }
+
+ show_location_bar = eel_preferences_get_boolean (CAJA_PREFERENCES_START_WITH_LOCATION_BAR);
+ always_use_location_entry = eel_preferences_get_boolean (CAJA_PREFERENCES_ALWAYS_USE_LOCATION_ENTRY);
+ for (walk = CAJA_WINDOW(window)->details->panes; walk; walk = walk->next)
+ {
+ CajaNavigationWindowPane *pane = walk->data;
+ if (show_location_bar)
+ {
+ caja_navigation_window_pane_show_location_bar (pane, FALSE);
+ }
+ else
+ {
+ caja_navigation_window_pane_hide_location_bar (pane, FALSE);
+ }
+
+ if (always_use_location_entry)
+ {
+ caja_navigation_window_pane_set_bar_mode (pane, CAJA_BAR_NAVIGATION);
+ }
+ else
+ {
+ caja_navigation_window_pane_set_bar_mode (pane, CAJA_BAR_PATH);
+ }
+ }
+
+ if (eel_preferences_get_boolean (CAJA_PREFERENCES_START_WITH_SIDEBAR))
+ {
+ caja_navigation_window_show_sidebar (window);
+ }
+ else
+ {
+ caja_navigation_window_hide_sidebar (window);
+ }
+
+ if (eel_preferences_get_boolean (CAJA_PREFERENCES_START_WITH_STATUS_BAR))
+ {
+ caja_navigation_window_show_status_bar (window);
+ }
+ else
+ {
+ caja_navigation_window_hide_status_bar (window);
+ }
+
+ GTK_WIDGET_CLASS (parent_class)->show (widget);
+}
+
+static void
+caja_navigation_window_save_geometry (CajaNavigationWindow *window)
+{
+ char *geometry_string;
+ gboolean is_maximized;
+
+ g_assert (CAJA_IS_WINDOW (window));
+
+ if (gtk_widget_get_window (GTK_WIDGET (window)))
+ {
+ geometry_string = eel_gtk_window_get_geometry_string (GTK_WINDOW (window));
+ is_maximized = gdk_window_get_state (gtk_widget_get_window (GTK_WIDGET (window)))
+ & GDK_WINDOW_STATE_MAXIMIZED;
+
+ if (eel_preferences_key_is_writable (CAJA_PREFERENCES_NAVIGATION_WINDOW_SAVED_GEOMETRY) &&
+ !is_maximized)
+ {
+ eel_preferences_set
+ (CAJA_PREFERENCES_NAVIGATION_WINDOW_SAVED_GEOMETRY,
+ geometry_string);
+ }
+ g_free (geometry_string);
+
+ if (eel_preferences_key_is_writable (CAJA_PREFERENCES_NAVIGATION_WINDOW_MAXIMIZED))
+ {
+ eel_preferences_set_boolean
+ (CAJA_PREFERENCES_NAVIGATION_WINDOW_MAXIMIZED,
+ is_maximized);
+ }
+ }
+}
+
+static void
+real_window_close (CajaWindow *window)
+{
+ caja_navigation_window_save_geometry (CAJA_NAVIGATION_WINDOW (window));
+}
+
+static void
+real_get_min_size (CajaWindow *window,
+ guint *min_width, guint *min_height)
+{
+ if (min_width)
+ {
+ *min_width = CAJA_NAVIGATION_WINDOW_MIN_WIDTH;
+ }
+ if (min_height)
+ {
+ *min_height = CAJA_NAVIGATION_WINDOW_MIN_HEIGHT;
+ }
+}
+
+static void
+real_get_default_size (CajaWindow *window,
+ guint *default_width, guint *default_height)
+{
+ if (default_width)
+ {
+ *default_width = CAJA_NAVIGATION_WINDOW_DEFAULT_WIDTH;
+ }
+
+ if (default_height)
+ {
+ *default_height = CAJA_NAVIGATION_WINDOW_DEFAULT_HEIGHT;
+ }
+}
+
+static CajaWindowSlot *
+real_open_slot (CajaWindowPane *pane,
+ CajaWindowOpenSlotFlags flags)
+{
+ CajaWindowSlot *slot;
+
+ slot = (CajaWindowSlot *) g_object_new (CAJA_TYPE_NAVIGATION_WINDOW_SLOT, NULL);
+ slot->pane = pane;
+
+ caja_navigation_window_pane_add_slot_in_tab (CAJA_NAVIGATION_WINDOW_PANE (pane), slot, flags);
+ gtk_widget_show (slot->content_box);
+
+ return slot;
+}
+
+static void
+real_close_slot (CajaWindowPane *pane,
+ CajaWindowSlot *slot)
+{
+ int page_num;
+ GtkNotebook *notebook;
+
+ notebook = GTK_NOTEBOOK (CAJA_NAVIGATION_WINDOW_PANE (pane)->notebook);
+
+ page_num = gtk_notebook_page_num (notebook, slot->content_box);
+ g_assert (page_num >= 0);
+
+ caja_navigation_window_pane_remove_page (CAJA_NAVIGATION_WINDOW_PANE (pane), page_num);
+
+ gtk_notebook_set_show_tabs (notebook,
+ gtk_notebook_get_n_pages (notebook) > 1);
+
+ EEL_CALL_PARENT (CAJA_WINDOW_CLASS,
+ close_slot, (pane, slot));
+}
+
+static void
+caja_navigation_window_class_init (CajaNavigationWindowClass *class)
+{
+ CAJA_WINDOW_CLASS (class)->window_type = CAJA_WINDOW_NAVIGATION;
+ CAJA_WINDOW_CLASS (class)->bookmarks_placeholder = MENU_PATH_BOOKMARKS_PLACEHOLDER;
+
+ G_OBJECT_CLASS (class)->finalize = caja_navigation_window_finalize;
+ GTK_OBJECT_CLASS (class)->destroy = caja_navigation_window_destroy;
+ GTK_WIDGET_CLASS (class)->show = caja_navigation_window_show;
+ GTK_WIDGET_CLASS (class)->unrealize = caja_navigation_window_unrealize;
+ GTK_WIDGET_CLASS (class)->window_state_event = caja_navigation_window_state_event;
+ GTK_WIDGET_CLASS (class)->key_press_event = caja_navigation_window_key_press_event;
+ GTK_WIDGET_CLASS (class)->button_press_event = caja_navigation_window_button_press_event;
+ CAJA_WINDOW_CLASS (class)->sync_allow_stop = real_sync_allow_stop;
+ CAJA_WINDOW_CLASS (class)->prompt_for_location = real_prompt_for_location;
+ CAJA_WINDOW_CLASS (class)->sync_title = real_sync_title;
+ CAJA_WINDOW_CLASS (class)->get_icon = real_get_icon;
+ CAJA_WINDOW_CLASS (class)->get_min_size = real_get_min_size;
+ CAJA_WINDOW_CLASS (class)->get_default_size = real_get_default_size;
+ CAJA_WINDOW_CLASS (class)->close = real_window_close;
+
+ CAJA_WINDOW_CLASS (class)->open_slot = real_open_slot;
+ CAJA_WINDOW_CLASS (class)->close_slot = real_close_slot;
+
+ g_type_class_add_private (G_OBJECT_CLASS (class), sizeof (CajaNavigationWindowDetails));
+
+
+ eel_preferences_add_callback (CAJA_PREFERENCES_MOUSE_BACK_BUTTON,
+ mouse_back_button_changed,
+ NULL);
+
+ eel_preferences_add_callback (CAJA_PREFERENCES_MOUSE_FORWARD_BUTTON,
+ mouse_forward_button_changed,
+ NULL);
+
+ eel_preferences_add_callback (CAJA_PREFERENCES_MOUSE_USE_EXTRA_BUTTONS,
+ use_extra_mouse_buttons_changed,
+ NULL);
+}
+
+static CajaWindowSlot *
+create_extra_pane (CajaNavigationWindow *window)
+{
+ CajaWindow *win;
+ CajaNavigationWindowPane *pane;
+ CajaWindowSlot *slot;
+ GtkPaned *paned;
+
+ win = CAJA_WINDOW (window);
+
+ /* New pane */
+ pane = caja_navigation_window_pane_new (win);
+ win->details->panes = g_list_append (win->details->panes, pane);
+
+ caja_navigation_window_pane_setup (pane);
+
+ paned = GTK_PANED (window->details->split_view_hpane);
+ if (gtk_paned_get_child1 (paned) == NULL)
+ {
+ gtk_paned_pack1 (paned, pane->widget, TRUE, FALSE);
+ }
+ else
+ {
+ gtk_paned_pack2 (paned, pane->widget, TRUE, FALSE);
+ }
+
+ /* slot */
+ slot = caja_window_open_slot (CAJA_WINDOW_PANE (pane),
+ CAJA_WINDOW_OPEN_SLOT_APPEND);
+ CAJA_WINDOW_PANE (pane)->active_slot = slot;
+
+ return slot;
+}
+
+void
+caja_navigation_window_split_view_on (CajaNavigationWindow *window)
+{
+ CajaWindow *win;
+ CajaNavigationWindowPane *pane;
+ CajaWindowSlot *slot, *old_active_slot;
+ GFile *location;
+ GtkAction *action;
+
+ win = CAJA_WINDOW (window);
+
+ old_active_slot = caja_window_get_active_slot (win);
+ slot = create_extra_pane (window);
+ pane = CAJA_NAVIGATION_WINDOW_PANE (slot->pane);
+
+ location = NULL;
+ if (old_active_slot != NULL)
+ {
+ location = caja_window_slot_get_location (old_active_slot);
+ if (location != NULL)
+ {
+ if (g_file_has_uri_scheme (location, "x-caja-search"))
+ {
+ g_object_unref (location);
+ location = NULL;
+ }
+ }
+ }
+ if (location == NULL)
+ {
+ location = g_file_new_for_path (g_get_home_dir ());
+ }
+
+ caja_window_slot_go_to (slot, location, FALSE);
+ g_object_unref (location);
+
+ action = gtk_action_group_get_action (CAJA_NAVIGATION_WINDOW (CAJA_WINDOW_PANE (pane)->window)->details->navigation_action_group,
+ CAJA_ACTION_SHOW_HIDE_LOCATION_BAR);
+ if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
+ {
+ caja_navigation_window_pane_show_location_bar (pane, TRUE);
+ }
+ else
+ {
+ caja_navigation_window_pane_hide_location_bar (pane, TRUE);
+ }
+}
+
+void
+caja_navigation_window_split_view_off (CajaNavigationWindow *window)
+{
+ CajaWindow *win;
+ CajaWindowPane *pane, *active_pane;
+ GList *l, *next;
+
+ win = CAJA_WINDOW (window);
+
+ g_return_if_fail (win);
+
+ active_pane = win->details->active_pane;
+
+ /* delete all panes except the first (main) pane */
+ for (l = win->details->panes; l != NULL; l = next)
+ {
+ next = l->next;
+ pane = l->data;
+ if (pane != active_pane)
+ {
+ caja_window_close_pane (pane);
+ }
+ }
+
+ caja_navigation_window_update_show_hide_menu_items (window);
+ caja_navigation_window_update_split_view_actions_sensitivity (window);
+}
+
+gboolean
+caja_navigation_window_split_view_showing (CajaNavigationWindow *window)
+{
+ return g_list_length (CAJA_WINDOW (window)->details->panes) > 1;
+}
diff --git a/src/caja-navigation-window.h b/src/caja-navigation-window.h
new file mode 100644
index 00000000..bff5d561
--- /dev/null
+++ b/src/caja-navigation-window.h
@@ -0,0 +1,119 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 1999, 2000 Red Hat, Inc.
+ * Copyright (C) 1999, 2000, 2001 Eazel, Inc.
+ * Copyright (C) 2003 Ximian, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Authors: Elliot Lee <[email protected]>
+ * Darin Adler <[email protected]>
+ *
+ */
+/* caja-navigation-window.h: Interface of the navigation window object */
+
+#ifndef CAJA_NAVIGATION_WINDOW_H
+#define CAJA_NAVIGATION_WINDOW_H
+
+#include <gtk/gtk.h>
+#include <eel/eel-glib-extensions.h>
+#include <libcaja-private/caja-bookmark.h>
+#include <libcaja-private/caja-sidebar.h>
+#include "caja-application.h"
+#include "caja-information-panel.h"
+#include "caja-side-pane.h"
+#include "caja-window.h"
+
+#define CAJA_TYPE_NAVIGATION_WINDOW caja_navigation_window_get_type()
+#define CAJA_NAVIGATION_WINDOW(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_NAVIGATION_WINDOW, CajaNavigationWindow))
+#define CAJA_NAVIGATION_WINDOW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_NAVIGATION_WINDOW, CajaNavigationWindowClass))
+#define CAJA_IS_NAVIGATION_WINDOW(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_NAVIGATION_WINDOW))
+#define CAJA_IS_NAVIGATION_WINDOW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_NAVIGATION_WINDOW))
+#define CAJA_NAVIGATION_WINDOW_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_NAVIGATION_WINDOW, CajaNavigationWindowClass))
+
+typedef struct _CajaNavigationWindow CajaNavigationWindow;
+typedef struct _CajaNavigationWindowClass CajaNavigationWindowClass;
+typedef struct _CajaNavigationWindowDetails CajaNavigationWindowDetails;
+
+
+struct _CajaNavigationWindow
+{
+ CajaWindow parent_object;
+
+ CajaNavigationWindowDetails *details;
+
+ /** UI stuff **/
+ CajaSidePane *sidebar;
+
+ /* Current views stuff */
+ GList *sidebar_panels;
+};
+
+
+struct _CajaNavigationWindowClass
+{
+ CajaWindowClass parent_spot;
+};
+
+GType caja_navigation_window_get_type (void);
+void caja_navigation_window_allow_back (CajaNavigationWindow *window,
+ gboolean allow);
+void caja_navigation_window_allow_forward (CajaNavigationWindow *window,
+ gboolean allow);
+void caja_navigation_window_clear_back_list (CajaNavigationWindow *window);
+void caja_navigation_window_clear_forward_list (CajaNavigationWindow *window);
+void caja_forget_history (void);
+gint caja_navigation_window_get_base_page_index (CajaNavigationWindow *window);
+void caja_navigation_window_hide_toolbar (CajaNavigationWindow *window);
+void caja_navigation_window_show_toolbar (CajaNavigationWindow *window);
+gboolean caja_navigation_window_toolbar_showing (CajaNavigationWindow *window);
+void caja_navigation_window_hide_sidebar (CajaNavigationWindow *window);
+void caja_navigation_window_show_sidebar (CajaNavigationWindow *window);
+gboolean caja_navigation_window_sidebar_showing (CajaNavigationWindow *window);
+void caja_navigation_window_add_sidebar_panel (CajaNavigationWindow *window,
+ CajaSidebar *sidebar_panel);
+void caja_navigation_window_remove_sidebar_panel (CajaNavigationWindow *window,
+ CajaSidebar *sidebar_panel);
+void caja_navigation_window_hide_status_bar (CajaNavigationWindow *window);
+void caja_navigation_window_show_status_bar (CajaNavigationWindow *window);
+gboolean caja_navigation_window_status_bar_showing (CajaNavigationWindow *window);
+void caja_navigation_window_back_or_forward (CajaNavigationWindow *window,
+ gboolean back,
+ guint distance,
+ gboolean new_tab);
+void caja_navigation_window_show_search (CajaNavigationWindow *window);
+void caja_navigation_window_unset_focus_widget (CajaNavigationWindow *window);
+void caja_navigation_window_hide_search (CajaNavigationWindow *window);
+void caja_navigation_window_set_search_button (CajaNavigationWindow *window,
+ gboolean state);
+void caja_navigation_window_restore_focus_widget (CajaNavigationWindow *window);
+void caja_navigation_window_split_view_on (CajaNavigationWindow *window);
+void caja_navigation_window_split_view_off (CajaNavigationWindow *window);
+gboolean caja_navigation_window_split_view_showing (CajaNavigationWindow *window);
+
+gboolean caja_navigation_window_is_in_temporary_navigation_bar (GtkWidget *widget,
+ CajaNavigationWindow *window);
+gboolean caja_navigation_window_is_in_temporary_search_bar (GtkWidget *widget,
+ CajaNavigationWindow *window);
+
+#endif
diff --git a/src/caja-notebook.c b/src/caja-notebook.c
new file mode 100644
index 00000000..6f65ff30
--- /dev/null
+++ b/src/caja-notebook.c
@@ -0,0 +1,570 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright © 2002 Christophe Fergeau
+ * Copyright © 2003, 2004 Marco Pesenti Gritti
+ * Copyright © 2003, 2004, 2005 Christian Persch
+ * (ephy-notebook.c)
+ *
+ * Copyright © 2008 Free Software Foundation, Inc.
+ * (caja-notebook.c)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "config.h"
+
+#include "caja-notebook.h"
+#include "caja-navigation-window.h"
+#include "caja-window-manage-views.h"
+#include "caja-window-private.h"
+#include "caja-window-slot.h"
+#include "caja-navigation-window-pane.h"
+#include <libcaja-private/caja-dnd.h>
+
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+#define TAB_WIDTH_N_CHARS 15
+
+#define AFTER_ALL_TABS -1
+#define NOT_IN_APP_WINDOWS -2
+
+#define INSANE_NUMBER_OF_URLS 20
+
+static void caja_notebook_init (CajaNotebook *notebook);
+static void caja_notebook_class_init (CajaNotebookClass *klass);
+static int caja_notebook_insert_page (GtkNotebook *notebook,
+ GtkWidget *child,
+ GtkWidget *tab_label,
+ GtkWidget *menu_label,
+ int position);
+static void caja_notebook_remove (GtkContainer *container,
+ GtkWidget *tab_widget);
+
+static const GtkTargetEntry url_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 },
+};
+
+enum
+{
+ TAB_CLOSE_REQUEST,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (CajaNotebook, caja_notebook, GTK_TYPE_NOTEBOOK);
+
+static void
+caja_notebook_class_init (CajaNotebookClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+ GtkNotebookClass *notebook_class = GTK_NOTEBOOK_CLASS (klass);
+
+ container_class->remove = caja_notebook_remove;
+
+ notebook_class->insert_page = caja_notebook_insert_page;
+
+ gtk_rc_parse_string ("style \"caja-tab-close-button-style\"\n"
+ "{\n"
+ "GtkWidget::focus-padding = 0\n"
+ "GtkWidget::focus-line-width = 0\n"
+ "xthickness = 0\n"
+ "ythickness = 0\n"
+ "}\n"
+ "widget \"*.caja-tab-close-button\" style \"caja-tab-close-button-style\"");
+
+ signals[TAB_CLOSE_REQUEST] =
+ g_signal_new ("tab-close-request",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaNotebookClass, tab_close_request),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1,
+ CAJA_TYPE_WINDOW_SLOT);
+}
+
+
+/* FIXME remove when gtknotebook's func for this becomes public, bug #.... */
+static CajaNotebook *
+find_notebook_at_pointer (gint abs_x, gint abs_y)
+{
+ GdkWindow *win_at_pointer, *toplevel_win;
+ gpointer toplevel = NULL;
+ gint x, y;
+
+ /* FIXME multi-head */
+ win_at_pointer = gdk_window_at_pointer (&x, &y);
+ if (win_at_pointer == NULL)
+ {
+ /* We are outside all windows containing a notebook */
+ return NULL;
+ }
+
+ toplevel_win = gdk_window_get_toplevel (win_at_pointer);
+
+ /* get the GtkWidget which owns the toplevel GdkWindow */
+ gdk_window_get_user_data (toplevel_win, &toplevel);
+
+ /* toplevel should be an CajaWindow */
+ if (toplevel != NULL && CAJA_IS_NAVIGATION_WINDOW (toplevel))
+ {
+ return CAJA_NOTEBOOK (CAJA_NAVIGATION_WINDOW_PANE (CAJA_WINDOW (toplevel)->details->active_pane)->notebook);
+ }
+
+ return NULL;
+}
+
+static gboolean
+is_in_notebook_window (CajaNotebook *notebook,
+ gint abs_x, gint abs_y)
+{
+ CajaNotebook *nb_at_pointer;
+
+ nb_at_pointer = find_notebook_at_pointer (abs_x, abs_y);
+
+ return nb_at_pointer == notebook;
+}
+
+static gint
+find_tab_num_at_pos (CajaNotebook *notebook, gint abs_x, gint abs_y)
+{
+ GtkPositionType tab_pos;
+ int page_num = 0;
+ GtkNotebook *nb = GTK_NOTEBOOK (notebook);
+ GtkWidget *page;
+ GtkAllocation allocation;
+
+ tab_pos = gtk_notebook_get_tab_pos (GTK_NOTEBOOK (notebook));
+
+ if (gtk_notebook_get_n_pages (nb) == 0)
+ {
+ return AFTER_ALL_TABS;
+ }
+
+ /* For some reason unfullscreen + quick click can
+ cause a wrong click event to be reported to the tab */
+ if (!is_in_notebook_window(notebook, abs_x, abs_y))
+ {
+ return NOT_IN_APP_WINDOWS;
+ }
+
+ while ((page = gtk_notebook_get_nth_page (nb, page_num)))
+ {
+ GtkWidget *tab;
+ gint max_x, max_y;
+ gint x_root, y_root;
+
+ tab = gtk_notebook_get_tab_label (nb, page);
+ g_return_val_if_fail (tab != NULL, -1);
+
+ if (!gtk_widget_get_mapped (GTK_WIDGET (tab)))
+ {
+ page_num++;
+ continue;
+ }
+
+ gdk_window_get_origin (gtk_widget_get_window (tab),
+ &x_root, &y_root);
+ gtk_widget_get_allocation (tab, &allocation);
+
+ max_x = x_root + allocation.x + allocation.width;
+ max_y = y_root + allocation.y + allocation.height;
+
+ if (((tab_pos == GTK_POS_TOP)
+ || (tab_pos == GTK_POS_BOTTOM))
+ &&(abs_x<=max_x))
+ {
+ return page_num;
+ }
+ else if (((tab_pos == GTK_POS_LEFT)
+ || (tab_pos == GTK_POS_RIGHT))
+ && (abs_y<=max_y))
+ {
+ return page_num;
+ }
+
+ page_num++;
+ }
+ return AFTER_ALL_TABS;
+}
+
+static gboolean
+button_press_cb (CajaNotebook *notebook,
+ GdkEventButton *event,
+ gpointer data)
+{
+ int tab_clicked;
+
+ tab_clicked = find_tab_num_at_pos (notebook, event->x_root, event->y_root);
+
+ if (event->type == GDK_BUTTON_PRESS &&
+ event->button == 3 &&
+ (event->state & gtk_accelerator_get_default_mod_mask ()) == 0)
+ {
+ if (tab_clicked == -1)
+ {
+ /* consume event, so that we don't pop up the context menu when
+ * the mouse if not over a tab label
+ */
+ return TRUE;
+ }
+
+ /* switch to the page the mouse is over, but don't consume the event */
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), tab_clicked);
+ }
+
+ return FALSE;
+}
+
+static void
+caja_notebook_init (CajaNotebook *notebook)
+{
+ gtk_notebook_set_scrollable (GTK_NOTEBOOK (notebook), TRUE);
+ gtk_notebook_set_show_border (GTK_NOTEBOOK (notebook), FALSE);
+ gtk_notebook_set_show_tabs (GTK_NOTEBOOK (notebook), FALSE);
+
+ g_signal_connect (notebook, "button-press-event",
+ (GCallback)button_press_cb, NULL);
+
+ /* Set up drag-and-drop target */
+ /* TODO this would be used for opening a new tab.
+ * It will only work properly as soon as GtkNotebook
+ * supports to find out whether a particular point
+ * is on a tab button or not.
+ */
+#if 0
+ gtk_drag_dest_set (GTK_WIDGET (notebook), 0,
+ url_drag_types, G_N_ELEMENTS (url_drag_types),
+ GDK_ACTION_LINK);
+ gtk_drag_dest_set_track_motion (GTK_WIDGET (notebook), TRUE);
+#endif
+}
+
+void
+caja_notebook_sync_loading (CajaNotebook *notebook,
+ CajaWindowSlot *slot)
+{
+ GtkWidget *tab_label, *spinner, *icon;
+ gboolean active;
+
+ g_return_if_fail (CAJA_IS_NOTEBOOK (notebook));
+ g_return_if_fail (CAJA_IS_WINDOW_SLOT (slot));
+
+ tab_label = gtk_notebook_get_tab_label (GTK_NOTEBOOK (notebook), slot->content_box);
+ g_return_if_fail (GTK_IS_WIDGET (tab_label));
+
+ spinner = GTK_WIDGET (g_object_get_data (G_OBJECT (tab_label), "spinner"));
+ icon = GTK_WIDGET (g_object_get_data (G_OBJECT (tab_label), "icon"));
+ g_return_if_fail (spinner != NULL && icon != NULL);
+
+ active = FALSE;
+ g_object_get (spinner, "active", &active, NULL);
+ if (active == slot->allow_stop)
+ {
+ return;
+ }
+
+ if (slot->allow_stop)
+ {
+ gtk_widget_hide (icon);
+ gtk_widget_show (spinner);
+ gtk_spinner_start (GTK_SPINNER (spinner));
+ }
+ else
+ {
+ gtk_spinner_stop (GTK_SPINNER (spinner));
+ gtk_widget_hide (spinner);
+ gtk_widget_show (icon);
+ }
+}
+
+void
+caja_notebook_sync_tab_label (CajaNotebook *notebook,
+ CajaWindowSlot *slot)
+{
+ GtkWidget *hbox, *label;
+ char *location_name;
+
+ g_return_if_fail (CAJA_IS_NOTEBOOK (notebook));
+ g_return_if_fail (CAJA_IS_WINDOW_SLOT (slot));
+ g_return_if_fail (GTK_IS_WIDGET (slot->content_box));
+
+ hbox = gtk_notebook_get_tab_label (GTK_NOTEBOOK (notebook), slot->content_box);
+ g_return_if_fail (GTK_IS_WIDGET (hbox));
+
+ label = GTK_WIDGET (g_object_get_data (G_OBJECT (hbox), "label"));
+ g_return_if_fail (GTK_IS_WIDGET (label));
+
+ gtk_label_set_text (GTK_LABEL (label), slot->title);
+
+ if (slot->location != NULL)
+ {
+ /* Set the tooltip on the label's parent (the tab label hbox),
+ * so it covers all of the tab label.
+ */
+ location_name = g_file_get_parse_name (slot->location);
+ gtk_widget_set_tooltip_text (gtk_widget_get_parent (label), location_name);
+ g_free (location_name);
+ }
+ else
+ {
+ gtk_widget_set_tooltip_text (gtk_widget_get_parent (label), NULL);
+ }
+}
+
+static void
+close_button_clicked_cb (GtkWidget *widget,
+ CajaWindowSlot *slot)
+{
+ GtkWidget *notebook;
+
+ notebook = gtk_widget_get_ancestor (slot->content_box, CAJA_TYPE_NOTEBOOK);
+ if (notebook != NULL)
+ {
+ g_signal_emit (notebook, signals[TAB_CLOSE_REQUEST], 0, slot);
+ }
+}
+
+static GtkWidget *
+build_tab_label (CajaNotebook *nb, CajaWindowSlot *slot)
+{
+ CajaDragSlotProxyInfo *drag_info;
+ GtkWidget *hbox, *label, *close_button, *image, *spinner, *icon;
+
+ /* set hbox spacing and label padding (see below) so that there's an
+ * equal amount of space around the label */
+ hbox = gtk_hbox_new (FALSE, 4);
+ gtk_widget_show (hbox);
+
+ /* setup load feedback */
+ spinner = gtk_spinner_new ();
+ gtk_box_pack_start (GTK_BOX (hbox), spinner, FALSE, FALSE, 0);
+
+ /* setup site icon, empty by default */
+ icon = gtk_image_new ();
+ gtk_box_pack_start (GTK_BOX (hbox), icon, FALSE, FALSE, 0);
+ /* don't show the icon */
+
+ /* setup label */
+ label = gtk_label_new (NULL);
+ gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
+ gtk_label_set_single_line_mode (GTK_LABEL (label), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_misc_set_padding (GTK_MISC (label), 0, 0);
+ gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0);
+ gtk_widget_show (label);
+
+ /* setup close button */
+ close_button = gtk_button_new ();
+ gtk_button_set_relief (GTK_BUTTON (close_button),
+ GTK_RELIEF_NONE);
+ /* don't allow focus on the close button */
+ gtk_button_set_focus_on_click (GTK_BUTTON (close_button), FALSE);
+
+ gtk_widget_set_name (close_button, "caja-tab-close-button");
+
+ image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU);
+ gtk_widget_set_tooltip_text (close_button, _("Close tab"));
+ g_signal_connect_object (close_button, "clicked",
+ G_CALLBACK (close_button_clicked_cb), slot, 0);
+
+ gtk_container_add (GTK_CONTAINER (close_button), image);
+ gtk_widget_show (image);
+
+ gtk_box_pack_start (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
+ gtk_widget_show (close_button);
+
+ drag_info = g_new0 (CajaDragSlotProxyInfo, 1);
+ drag_info->target_slot = slot;
+ g_object_set_data_full (G_OBJECT (hbox), "proxy-drag-info",
+ drag_info, (GDestroyNotify) g_free);
+
+ caja_drag_slot_proxy_init (hbox, drag_info);
+
+ g_object_set_data (G_OBJECT (hbox), "label", label);
+ g_object_set_data (G_OBJECT (hbox), "spinner", spinner);
+ g_object_set_data (G_OBJECT (hbox), "icon", icon);
+ g_object_set_data (G_OBJECT (hbox), "close-button", close_button);
+
+ return hbox;
+}
+
+static int
+caja_notebook_insert_page (GtkNotebook *gnotebook,
+ GtkWidget *tab_widget,
+ GtkWidget *tab_label,
+ GtkWidget *menu_label,
+ int position)
+{
+ g_assert (GTK_IS_WIDGET (tab_widget));
+
+ position = GTK_NOTEBOOK_CLASS (caja_notebook_parent_class)->insert_page (gnotebook,
+ tab_widget,
+ tab_label,
+ menu_label,
+ position);
+
+ gtk_notebook_set_show_tabs (gnotebook,
+ gtk_notebook_get_n_pages (gnotebook) > 1);
+ gtk_notebook_set_tab_reorderable (gnotebook, tab_widget, TRUE);
+
+ return position;
+}
+
+int
+caja_notebook_add_tab (CajaNotebook *notebook,
+ CajaWindowSlot *slot,
+ int position,
+ gboolean jump_to)
+{
+ GtkNotebook *gnotebook = GTK_NOTEBOOK (notebook);
+ GtkWidget *tab_label;
+
+ g_return_val_if_fail (CAJA_IS_NOTEBOOK (notebook), -1);
+ g_return_val_if_fail (CAJA_IS_WINDOW_SLOT (slot), -1);
+
+ tab_label = build_tab_label (notebook, slot);
+
+ position = gtk_notebook_insert_page (GTK_NOTEBOOK (notebook),
+ slot->content_box,
+ tab_label,
+ position);
+
+ gtk_container_child_set (GTK_CONTAINER (notebook),
+ slot->content_box,
+ "tab-expand", TRUE,
+ NULL);
+
+ caja_notebook_sync_tab_label (notebook, slot);
+ caja_notebook_sync_loading (notebook, slot);
+
+
+ /* FIXME gtk bug! */
+ /* FIXME: this should be fixed in gtk 2.12; check & remove this! */
+ /* The signal handler may have reordered the tabs */
+ position = gtk_notebook_page_num (gnotebook, slot->content_box);
+
+ if (jump_to)
+ {
+ gtk_notebook_set_current_page (gnotebook, position);
+
+ }
+
+ return position;
+}
+
+static void
+caja_notebook_remove (GtkContainer *container,
+ GtkWidget *tab_widget)
+{
+ GtkNotebook *gnotebook = GTK_NOTEBOOK (container);
+ GTK_CONTAINER_CLASS (caja_notebook_parent_class)->remove (container, tab_widget);
+
+ gtk_notebook_set_show_tabs (gnotebook,
+ gtk_notebook_get_n_pages (gnotebook) > 1);
+
+}
+
+void
+caja_notebook_reorder_current_child_relative (CajaNotebook *notebook,
+ int offset)
+{
+ GtkNotebook *gnotebook;
+ GtkWidget *child;
+ int page;
+
+ g_return_if_fail (CAJA_IS_NOTEBOOK (notebook));
+
+ if (!caja_notebook_can_reorder_current_child_relative (notebook, offset))
+ {
+ return;
+ }
+
+ gnotebook = GTK_NOTEBOOK (notebook);
+
+ page = gtk_notebook_get_current_page (gnotebook);
+ child = gtk_notebook_get_nth_page (gnotebook, page);
+ gtk_notebook_reorder_child (gnotebook, child, page + offset);
+}
+
+void
+caja_notebook_set_current_page_relative (CajaNotebook *notebook,
+ int offset)
+{
+ GtkNotebook *gnotebook;
+ int page;
+
+ g_return_if_fail (CAJA_IS_NOTEBOOK (notebook));
+
+ if (!caja_notebook_can_set_current_page_relative (notebook, offset))
+ {
+ return;
+ }
+
+ gnotebook = GTK_NOTEBOOK (notebook);
+
+ page = gtk_notebook_get_current_page (gnotebook);
+ gtk_notebook_set_current_page (gnotebook, page + offset);
+
+}
+
+static gboolean
+caja_notebook_is_valid_relative_position (CajaNotebook *notebook,
+ int offset)
+{
+ GtkNotebook *gnotebook;
+ int page;
+ int n_pages;
+
+ gnotebook = GTK_NOTEBOOK (notebook);
+
+ page = gtk_notebook_get_current_page (gnotebook);
+ n_pages = gtk_notebook_get_n_pages (gnotebook) - 1;
+ if (page < 0 ||
+ (offset < 0 && page < -offset) ||
+ (offset > 0 && page > n_pages - offset))
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+caja_notebook_can_reorder_current_child_relative (CajaNotebook *notebook,
+ int offset)
+{
+ g_return_val_if_fail (CAJA_IS_NOTEBOOK (notebook), FALSE);
+
+ return caja_notebook_is_valid_relative_position (notebook, offset);
+}
+
+gboolean
+caja_notebook_can_set_current_page_relative (CajaNotebook *notebook,
+ int offset)
+{
+ g_return_val_if_fail (CAJA_IS_NOTEBOOK (notebook), FALSE);
+
+ return caja_notebook_is_valid_relative_position (notebook, offset);
+}
+
diff --git a/src/caja-notebook.h b/src/caja-notebook.h
new file mode 100644
index 00000000..9a896be8
--- /dev/null
+++ b/src/caja-notebook.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright © 2002 Christophe Fergeau
+ * Copyright © 2003 Marco Pesenti Gritti
+ * Copyright © 2003, 2004 Christian Persch
+ * (ephy-notebook.c)
+ *
+ * Copyright © 2008 Free Software Foundation, Inc.
+ * (caja-notebook.c)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $Id: caja-notebook.h 8210 2008-04-11 20:05:25Z chpe $
+ */
+
+#ifndef CAJA_NOTEBOOK_H
+#define CAJA_NOTEBOOK_H
+
+#include <glib.h>
+
+#include <gtk/gtk.h>
+#include "caja-window-slot.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define CAJA_TYPE_NOTEBOOK (caja_notebook_get_type ())
+#define CAJA_NOTEBOOK(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), CAJA_TYPE_NOTEBOOK, CajaNotebook))
+#define CAJA_NOTEBOOK_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), CAJA_TYPE_NOTEBOOK, CajaNotebookClass))
+#define CAJA_IS_NOTEBOOK(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), CAJA_TYPE_NOTEBOOK))
+#define CAJA_IS_NOTEBOOK_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), CAJA_TYPE_NOTEBOOK))
+#define CAJA_NOTEBOOK_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), CAJA_TYPE_NOTEBOOK, CajaNotebookClass))
+
+ typedef struct _CajaNotebookClass CajaNotebookClass;
+ typedef struct _CajaNotebook CajaNotebook;
+ typedef struct _CajaNotebookPrivate CajaNotebookPrivate;
+
+ struct _CajaNotebook
+ {
+ GtkNotebook parent;
+
+ /*< private >*/
+ CajaNotebookPrivate *priv;
+ };
+
+ struct _CajaNotebookClass
+ {
+ GtkNotebookClass parent_class;
+
+ /* Signals */
+ void (* tab_close_request) (CajaNotebook *notebook,
+ CajaWindowSlot *slot);
+ };
+
+ GType caja_notebook_get_type (void);
+
+ int caja_notebook_add_tab (CajaNotebook *nb,
+ CajaWindowSlot *slot,
+ int position,
+ gboolean jump_to);
+
+ void caja_notebook_set_show_tabs (CajaNotebook *nb,
+ gboolean show_tabs);
+
+ void caja_notebook_set_dnd_enabled (CajaNotebook *nb,
+ gboolean enabled);
+ void caja_notebook_sync_tab_label (CajaNotebook *nb,
+ CajaWindowSlot *slot);
+ void caja_notebook_sync_loading (CajaNotebook *nb,
+ CajaWindowSlot *slot);
+
+ void caja_notebook_reorder_current_child_relative (CajaNotebook *notebook,
+ int offset);
+ void caja_notebook_set_current_page_relative (CajaNotebook *notebook,
+ int offset);
+
+ gboolean caja_notebook_can_reorder_current_child_relative (CajaNotebook *notebook,
+ int offset);
+ gboolean caja_notebook_can_set_current_page_relative (CajaNotebook *notebook,
+ int offset);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CAJA_NOTEBOOK_H */
+
diff --git a/src/caja-notes-viewer.c b/src/caja-notes-viewer.c
new file mode 100644
index 00000000..0ae29348
--- /dev/null
+++ b/src/caja-notes-viewer.c
@@ -0,0 +1,535 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2000, 2001 Eazel, Inc.
+ *
+ * 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
+ * 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.
+ *
+ * Author: Andy Hertzfeld <[email protected]>
+ *
+ */
+
+/* notes sidebar panel -- allows editing per-directory notes */
+
+#include <config.h>
+
+#include "caja-notes-viewer.h"
+
+#include <eel/eel-debug.h>
+#include <eel/eel-gtk-extensions.h>
+#include <eel/eel-string.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <libcaja-private/caja-file-attributes.h>
+#include <libcaja-private/caja-file.h>
+#include <libcaja-private/caja-file-utilities.h>
+#include <libcaja-private/caja-global-preferences.h>
+#include <libcaja-private/caja-metadata.h>
+#include <libcaja-private/caja-clipboard.h>
+#include <libcaja-private/caja-module.h>
+#include <libcaja-private/caja-sidebar-provider.h>
+#include <libcaja-private/caja-window-info.h>
+#include <libcaja-private/caja-window-slot-info.h>
+#include <libcaja-extension/caja-property-page-provider.h>
+
+#define SAVE_TIMEOUT 3
+
+static void load_note_text_from_metadata (CajaFile *file,
+ CajaNotesViewer *notes);
+static void notes_save_metainfo (CajaNotesViewer *notes);
+static void caja_notes_viewer_sidebar_iface_init (CajaSidebarIface *iface);
+static void on_changed (GtkEditable *editable,
+ CajaNotesViewer *notes);
+static void property_page_provider_iface_init (CajaPropertyPageProviderIface *iface);
+static void sidebar_provider_iface_init (CajaSidebarProviderIface *iface);
+
+typedef struct
+{
+ GtkScrolledWindowClass parent;
+} CajaNotesViewerClass;
+
+typedef struct
+{
+ GObject parent;
+} CajaNotesViewerProvider;
+
+typedef struct
+{
+ GObjectClass parent;
+} CajaNotesViewerProviderClass;
+
+
+G_DEFINE_TYPE_WITH_CODE (CajaNotesViewer, caja_notes_viewer, GTK_TYPE_SCROLLED_WINDOW,
+ G_IMPLEMENT_INTERFACE (CAJA_TYPE_SIDEBAR,
+ caja_notes_viewer_sidebar_iface_init));
+
+static GType caja_notes_viewer_provider_get_type (void);
+
+G_DEFINE_TYPE_WITH_CODE (CajaNotesViewerProvider, caja_notes_viewer_provider, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (CAJA_TYPE_PROPERTY_PAGE_PROVIDER,
+ property_page_provider_iface_init);
+ G_IMPLEMENT_INTERFACE (CAJA_TYPE_SIDEBAR_PROVIDER,
+ sidebar_provider_iface_init));
+
+
+struct _CajaNotesViewerDetails
+{
+ GtkWidget *note_text_field;
+ GtkTextBuffer *text_buffer;
+ char *uri;
+ CajaFile *file;
+ guint save_timeout_id;
+ char *previous_saved_text;
+ GdkPixbuf *icon;
+};
+
+static gboolean
+schedule_save_callback (gpointer data)
+{
+ CajaNotesViewer *notes;
+
+ notes = data;
+
+ /* Zero out save_timeout_id so no one will try to cancel our
+ * in-progress timeout callback.
+ */
+ notes->details->save_timeout_id = 0;
+
+ notes_save_metainfo (notes);
+
+ return FALSE;
+}
+
+static void
+cancel_pending_save (CajaNotesViewer *notes)
+{
+ if (notes->details->save_timeout_id != 0)
+ {
+ g_source_remove (notes->details->save_timeout_id);
+ notes->details->save_timeout_id = 0;
+ }
+}
+
+static void
+schedule_save (CajaNotesViewer *notes)
+{
+ cancel_pending_save (notes);
+
+ notes->details->save_timeout_id = g_timeout_add_seconds (SAVE_TIMEOUT, schedule_save_callback, notes);
+}
+
+/* notifies event listeners if the notes data actually changed */
+static void
+set_saved_text (CajaNotesViewer *notes, char *new_notes)
+{
+ char *old_text;
+
+ old_text = notes->details->previous_saved_text;
+ notes->details->previous_saved_text = new_notes;
+
+ if (eel_strcmp (old_text, new_notes) != 0)
+ {
+ g_signal_emit_by_name (CAJA_SIDEBAR (notes),
+ "tab_icon_changed");
+ }
+
+ g_free (old_text);
+}
+
+/* save the metainfo corresponding to the current uri, if any, into the text field */
+static void
+notes_save_metainfo (CajaNotesViewer *notes)
+{
+ char *notes_text;
+ GtkTextIter start_iter;
+ GtkTextIter end_iter;
+
+ if (notes->details->file == NULL)
+ {
+ return;
+ }
+
+ cancel_pending_save (notes);
+
+ /* Block the handler, so we don't respond to our own change.
+ */
+ g_signal_handlers_block_matched (notes->details->file,
+ G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL,
+ G_CALLBACK (load_note_text_from_metadata),
+ notes);
+
+ gtk_text_buffer_get_start_iter (notes->details->text_buffer, &start_iter);
+ gtk_text_buffer_get_end_iter (notes->details->text_buffer, &end_iter);
+ notes_text = gtk_text_buffer_get_text (notes->details->text_buffer,
+ &start_iter,
+ &end_iter,
+ FALSE);
+
+ caja_file_set_metadata (notes->details->file,
+ CAJA_METADATA_KEY_ANNOTATION,
+ NULL, notes_text);
+
+ g_signal_handlers_unblock_matched (notes->details->file,
+ G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL,
+ G_CALLBACK (load_note_text_from_metadata),
+ notes);
+
+ set_saved_text (notes, notes_text);
+}
+
+static void
+load_note_text_from_metadata (CajaFile *file,
+ CajaNotesViewer *notes)
+{
+ char *saved_text;
+
+ g_assert (CAJA_IS_FILE (file));
+ g_assert (notes->details->file == file);
+
+ saved_text = caja_file_get_metadata (file, CAJA_METADATA_KEY_ANNOTATION, "");
+
+ /* This fn is called for any change signal on the file, so make sure that the
+ * metadata has actually changed.
+ */
+ if (eel_strcmp (saved_text, notes->details->previous_saved_text) != 0)
+ {
+ set_saved_text (notes, saved_text);
+ cancel_pending_save (notes);
+
+ /* Block the handler, so we don't respond to our own change.
+ */
+ g_signal_handlers_block_matched (notes->details->text_buffer,
+ G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL,
+ G_CALLBACK (on_changed),
+ notes);
+ gtk_text_buffer_set_text (notes->details->text_buffer, saved_text, -1);
+ g_signal_handlers_unblock_matched (notes->details->text_buffer,
+ G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL,
+ G_CALLBACK (on_changed),
+ notes);
+ }
+ else
+ {
+ g_free (saved_text);
+ }
+}
+
+static void
+done_with_file (CajaNotesViewer *notes)
+{
+ cancel_pending_save (notes);
+
+ if (notes->details->file != NULL)
+ {
+ caja_file_monitor_remove (notes->details->file, notes);
+ g_signal_handlers_disconnect_matched (notes->details->file,
+ G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL,
+ G_CALLBACK (load_note_text_from_metadata),
+ notes);
+ caja_file_unref (notes->details->file);
+ }
+}
+
+static void
+notes_load_metainfo (CajaNotesViewer *notes)
+{
+ CajaFileAttributes attributes;
+
+ done_with_file (notes);
+ notes->details->file = caja_file_get_by_uri (notes->details->uri);
+
+ /* Block the handler, so we don't respond to our own change.
+ */
+ g_signal_handlers_block_matched (notes->details->text_buffer,
+ G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL,
+ G_CALLBACK (on_changed),
+ notes);
+ gtk_text_buffer_set_text (notes->details->text_buffer, "", -1);
+ g_signal_handlers_unblock_matched (notes->details->text_buffer,
+ G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL,
+ G_CALLBACK (on_changed),
+ notes);
+
+ if (notes->details->file == NULL)
+ {
+ return;
+ }
+
+ attributes = CAJA_FILE_ATTRIBUTE_INFO;
+ caja_file_monitor_add (notes->details->file, notes, attributes);
+
+ if (caja_file_check_if_ready (notes->details->file, attributes))
+ {
+ load_note_text_from_metadata (notes->details->file, notes);
+ }
+
+ g_signal_connect (notes->details->file, "changed",
+ G_CALLBACK (load_note_text_from_metadata), notes);
+}
+
+static void
+loading_uri_callback (CajaWindowInfo *window,
+ const char *location,
+ CajaNotesViewer *notes)
+{
+ if (strcmp (notes->details->uri, location) != 0)
+ {
+ notes_save_metainfo (notes);
+ g_free (notes->details->uri);
+ notes->details->uri = g_strdup (location);
+ notes_load_metainfo (notes);
+ }
+}
+
+static gboolean
+on_text_field_focus_out_event (GtkWidget *widget,
+ GdkEventFocus *event,
+ gpointer callback_data)
+{
+ CajaNotesViewer *notes;
+
+ notes = callback_data;
+ notes_save_metainfo (notes);
+ return FALSE;
+}
+
+static void
+on_changed (GtkEditable *editable, CajaNotesViewer *notes)
+{
+ schedule_save (notes);
+}
+
+static void
+caja_notes_viewer_init (CajaNotesViewer *sidebar)
+{
+ CajaNotesViewerDetails *details;
+ CajaIconInfo *info;
+
+ details = g_new0 (CajaNotesViewerDetails, 1);
+ sidebar->details = details;
+
+ details->uri = g_strdup ("");
+
+ info = caja_icon_info_lookup_from_name ("emblem-note", 16);
+ details->icon = caja_icon_info_get_pixbuf (info);
+
+ /* create the text container */
+ details->text_buffer = gtk_text_buffer_new (NULL);
+ details->note_text_field = gtk_text_view_new_with_buffer (details->text_buffer);
+
+ gtk_text_view_set_editable (GTK_TEXT_VIEW (details->note_text_field), TRUE);
+ gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (details->note_text_field),
+ GTK_WRAP_WORD);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sidebar),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sidebar),
+ GTK_SHADOW_IN);
+ gtk_scrolled_window_set_hadjustment (GTK_SCROLLED_WINDOW (sidebar), NULL);
+ gtk_scrolled_window_set_vadjustment (GTK_SCROLLED_WINDOW (sidebar), NULL);
+ gtk_container_add (GTK_CONTAINER (sidebar), details->note_text_field);
+
+ g_signal_connect (details->note_text_field, "focus_out_event",
+ G_CALLBACK (on_text_field_focus_out_event), sidebar);
+ g_signal_connect (details->text_buffer, "changed",
+ G_CALLBACK (on_changed), sidebar);
+
+ gtk_widget_show_all (GTK_WIDGET (sidebar));
+
+}
+
+static void
+caja_notes_viewer_finalize (GObject *object)
+{
+ CajaNotesViewer *sidebar;
+
+ sidebar = CAJA_NOTES_VIEWER (object);
+
+ done_with_file (sidebar);
+ if (sidebar->details->icon != NULL)
+ {
+ g_object_unref (sidebar->details->icon);
+ }
+ g_free (sidebar->details->uri);
+ g_free (sidebar->details->previous_saved_text);
+ g_free (sidebar->details);
+
+ G_OBJECT_CLASS (caja_notes_viewer_parent_class)->finalize (object);
+}
+
+
+static void
+caja_notes_viewer_class_init (CajaNotesViewerClass *class)
+{
+ G_OBJECT_CLASS (class)->finalize = caja_notes_viewer_finalize;
+}
+
+static const char *
+caja_notes_viewer_get_sidebar_id (CajaSidebar *sidebar)
+{
+ return CAJA_NOTES_SIDEBAR_ID;
+}
+
+static char *
+caja_notes_viewer_get_tab_label (CajaSidebar *sidebar)
+{
+ return g_strdup (_("Notes"));
+}
+
+static char *
+caja_notes_viewer_get_tab_tooltip (CajaSidebar *sidebar)
+{
+ return g_strdup (_("Show Notes"));
+}
+
+static GdkPixbuf *
+caja_notes_viewer_get_tab_icon (CajaSidebar *sidebar)
+{
+ CajaNotesViewer *notes;
+
+ notes = CAJA_NOTES_VIEWER (sidebar);
+
+ if (notes->details->previous_saved_text != NULL &&
+ notes->details->previous_saved_text[0] != '\0')
+ {
+ return g_object_ref (notes->details->icon);
+ }
+
+ return NULL;
+}
+
+static void
+caja_notes_viewer_is_visible_changed (CajaSidebar *sidebar,
+ gboolean is_visible)
+{
+ /* Do nothing */
+}
+
+static void
+caja_notes_viewer_sidebar_iface_init (CajaSidebarIface *iface)
+{
+ iface->get_sidebar_id = caja_notes_viewer_get_sidebar_id;
+ iface->get_tab_label = caja_notes_viewer_get_tab_label;
+ iface->get_tab_tooltip = caja_notes_viewer_get_tab_tooltip;
+ iface->get_tab_icon = caja_notes_viewer_get_tab_icon;
+ iface->is_visible_changed = caja_notes_viewer_is_visible_changed;
+}
+
+static void
+caja_notes_viewer_set_parent_window (CajaNotesViewer *sidebar,
+ CajaWindowInfo *window)
+{
+ CajaWindowSlotInfo *slot;
+
+ slot = caja_window_info_get_active_slot (window);
+
+ g_signal_connect_object (window, "loading_uri",
+ G_CALLBACK (loading_uri_callback), sidebar, 0);
+
+ g_free (sidebar->details->uri);
+ sidebar->details->uri = caja_window_slot_info_get_current_location (slot);
+ notes_load_metainfo (sidebar);
+
+ caja_clipboard_set_up_text_view
+ (GTK_TEXT_VIEW (sidebar->details->note_text_field),
+ caja_window_info_get_ui_manager (window));
+}
+
+static CajaSidebar *
+caja_notes_viewer_create_sidebar (CajaSidebarProvider *provider,
+ CajaWindowInfo *window)
+{
+ CajaNotesViewer *sidebar;
+
+ sidebar = g_object_new (caja_notes_viewer_get_type (), NULL);
+ caja_notes_viewer_set_parent_window (sidebar, window);
+ g_object_ref_sink (sidebar);
+
+ return CAJA_SIDEBAR (sidebar);
+}
+
+static GList *
+get_property_pages (CajaPropertyPageProvider *provider,
+ GList *files)
+{
+ GList *pages;
+ CajaPropertyPage *page;
+ CajaFileInfo *file;
+ char *uri;
+ CajaNotesViewer *viewer;
+
+
+ /* Only show the property page if 1 file is selected */
+ if (!files || files->next != NULL)
+ {
+ return NULL;
+ }
+
+ pages = NULL;
+
+ file = CAJA_FILE_INFO (files->data);
+ uri = caja_file_info_get_uri (file);
+
+ viewer = g_object_new (caja_notes_viewer_get_type (), NULL);
+ g_free (viewer->details->uri);
+ viewer->details->uri = uri;
+ notes_load_metainfo (viewer);
+
+ page = caja_property_page_new
+ ("CajaNotesViewer::property_page",
+ gtk_label_new (_("Notes")),
+ GTK_WIDGET (viewer));
+ pages = g_list_append (pages, page);
+
+ return pages;
+}
+
+static void
+property_page_provider_iface_init (CajaPropertyPageProviderIface *iface)
+{
+ iface->get_pages = get_property_pages;
+}
+
+static void
+sidebar_provider_iface_init (CajaSidebarProviderIface *iface)
+{
+ iface->create = caja_notes_viewer_create_sidebar;
+}
+
+static void
+caja_notes_viewer_provider_init (CajaNotesViewerProvider *sidebar)
+{
+}
+
+static void
+caja_notes_viewer_provider_class_init (CajaNotesViewerProviderClass *class)
+{
+}
+
+void
+caja_notes_viewer_register (void)
+{
+ caja_module_add_type (caja_notes_viewer_provider_get_type ());
+}
+
diff --git a/src/caja-notes-viewer.h b/src/caja-notes-viewer.h
new file mode 100644
index 00000000..1bcac54c
--- /dev/null
+++ b/src/caja-notes-viewer.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 1999, 2000, 2004 Red Hat, Inc.
+ * Copyright (C) 2000, 2001 Eazel, Inc.
+ *
+ * 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
+ * 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: Andy Hertzfeld <[email protected]>
+ * Alexander Larsson <[email protected]>
+ */
+#ifndef _CAJA_NOTES_VIEWER_H
+#define _CAJA_NOTES_VIEWER_H
+
+#include <gtk/gtk.h>
+#include <libcaja-private/caja-view.h>
+#include <libcaja-private/caja-window-info.h>
+
+#define CAJA_NOTES_SIDEBAR_ID "CajaNotesSidebar"
+
+#define CAJA_TYPE_NOTES_VIEWER caja_notes_viewer_get_type()
+#define CAJA_NOTES_VIEWER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_NOTES_VIEWER, CajaNotesViewer))
+
+typedef struct _CajaNotesViewerDetails CajaNotesViewerDetails;
+
+typedef struct
+{
+ GtkScrolledWindow parent;
+ CajaNotesViewerDetails *details;
+} CajaNotesViewer;
+
+GType caja_notes_viewer_get_type (void);
+void caja_notes_viewer_register (void);
+
+#endif
diff --git a/src/caja-pathbar.c b/src/caja-pathbar.c
new file mode 100644
index 00000000..78c100e2
--- /dev/null
+++ b/src/caja-pathbar.c
@@ -0,0 +1,2165 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* caja-pathbar.c
+ * Copyright (C) 2004 Red Hat, Inc., Jonathan Blandford <[email protected]>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include <eel/eel-debug.h>
+#include <eel/eel-gtk-extensions.h>
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-preferences.h>
+#include <eel/eel-string.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <libcaja-private/caja-file.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-trash-monitor.h>
+#include <libcaja-private/caja-marshal.h>
+#include <libcaja-private/caja-dnd.h>
+#include <libcaja-private/caja-icon-dnd.h>
+#include "caja-pathbar.h"
+#include "caja-window.h"
+#include "caja-window-private.h"
+#include "caja-window-slot.h"
+
+enum
+{
+ PATH_CLICKED,
+ PATH_SET,
+ LAST_SIGNAL
+};
+
+typedef enum
+{
+ NORMAL_BUTTON,
+ ROOT_BUTTON,
+ HOME_BUTTON,
+ DESKTOP_BUTTON,
+ MOUNT_BUTTON,
+ DEFAULT_LOCATION_BUTTON,
+} ButtonType;
+
+#define BUTTON_DATA(x) ((ButtonData *)(x))
+
+#define SCROLL_TIMEOUT 150
+#define INITIAL_SCROLL_TIMEOUT 300
+
+static guint path_bar_signals [LAST_SIGNAL] = { 0 };
+
+static gboolean desktop_is_home;
+
+#define CAJA_PATH_BAR_ICON_SIZE 16
+
+typedef struct _ButtonData ButtonData;
+
+struct _ButtonData
+{
+ GtkWidget *button;
+ ButtonType type;
+ char *dir_name;
+ GFile *path;
+ CajaFile *file;
+ unsigned int file_changed_signal_id;
+
+ /* custom icon */
+ GdkPixbuf *custom_icon;
+
+ /* flag to indicate its the base folder in the URI */
+ gboolean is_base_dir;
+
+ GtkWidget *image;
+ GtkWidget *label;
+ guint ignore_changes : 1;
+ guint file_is_hidden : 1;
+ guint fake_root : 1;
+
+ CajaDragSlotProxyInfo drag_info;
+};
+
+G_DEFINE_TYPE (CajaPathBar,
+ caja_path_bar,
+ GTK_TYPE_CONTAINER);
+
+static void caja_path_bar_finalize (GObject *object);
+static void caja_path_bar_dispose (GObject *object);
+static void caja_path_bar_size_request (GtkWidget *widget,
+ GtkRequisition *requisition);
+static void caja_path_bar_unmap (GtkWidget *widget);
+static void caja_path_bar_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation);
+static void caja_path_bar_add (GtkContainer *container,
+ GtkWidget *widget);
+static void caja_path_bar_remove (GtkContainer *container,
+ GtkWidget *widget);
+static void caja_path_bar_forall (GtkContainer *container,
+ gboolean include_internals,
+ GtkCallback callback,
+ gpointer callback_data);
+static void caja_path_bar_scroll_up (CajaPathBar *path_bar);
+static void caja_path_bar_scroll_down (CajaPathBar *path_bar);
+static gboolean caja_path_bar_scroll (GtkWidget *path_bar,
+ GdkEventScroll *scroll);
+static void caja_path_bar_stop_scrolling (CajaPathBar *path_bar);
+static gboolean caja_path_bar_slider_button_press (GtkWidget *widget,
+ GdkEventButton *event,
+ CajaPathBar *path_bar);
+static gboolean caja_path_bar_slider_button_release (GtkWidget *widget,
+ GdkEventButton *event,
+ CajaPathBar *path_bar);
+static void caja_path_bar_grab_notify (GtkWidget *widget,
+ gboolean was_grabbed);
+static void caja_path_bar_state_changed (GtkWidget *widget,
+ GtkStateType previous_state);
+static void caja_path_bar_style_set (GtkWidget *widget,
+ GtkStyle *previous_style);
+static void caja_path_bar_screen_changed (GtkWidget *widget,
+ GdkScreen *previous_screen);
+static void caja_path_bar_check_icon_theme (CajaPathBar *path_bar);
+static void caja_path_bar_update_button_appearance (ButtonData *button_data);
+static void caja_path_bar_update_button_state (ButtonData *button_data,
+ gboolean current_dir);
+static gboolean caja_path_bar_update_path (CajaPathBar *path_bar,
+ GFile *file_path,
+ gboolean emit_signal);
+
+static GtkWidget *
+get_slider_button (CajaPathBar *path_bar,
+ GtkArrowType arrow_type)
+{
+ GtkWidget *button;
+
+ gtk_widget_push_composite_child ();
+
+ button = gtk_button_new ();
+ gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE);
+ gtk_container_add (GTK_CONTAINER (button), gtk_arrow_new (arrow_type, GTK_SHADOW_OUT));
+ gtk_container_add (GTK_CONTAINER (path_bar), button);
+ gtk_widget_show_all (button);
+
+ gtk_widget_pop_composite_child ();
+
+ return button;
+}
+
+static void
+update_button_types (CajaPathBar *path_bar)
+{
+ GList *list;
+ GFile *path = NULL;
+
+ for (list = path_bar->button_list; list; list = list->next)
+ {
+ ButtonData *button_data;
+ button_data = BUTTON_DATA (list->data);
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button_data->button)))
+ {
+ path = button_data->path;
+ break;
+ }
+ }
+ if (path != NULL)
+ {
+ caja_path_bar_update_path (path_bar, path, TRUE);
+ }
+}
+
+
+static void
+desktop_location_changed_callback (gpointer user_data)
+{
+ CajaPathBar *path_bar;
+
+ path_bar = CAJA_PATH_BAR (user_data);
+
+ g_object_unref (path_bar->desktop_path);
+ g_object_unref (path_bar->home_path);
+ path_bar->desktop_path = caja_get_desktop_location ();
+ path_bar->home_path = g_file_new_for_path (g_get_home_dir ());
+ desktop_is_home = g_file_equal (path_bar->home_path, path_bar->desktop_path);
+
+ update_button_types (path_bar);
+}
+
+static void
+trash_state_changed_cb (CajaTrashMonitor *monitor,
+ gboolean state,
+ CajaPathBar *path_bar)
+{
+ GFile *file;
+ GList *list;
+
+ file = g_file_new_for_uri ("trash:///");
+ for (list = path_bar->button_list; list; list = list->next)
+ {
+ ButtonData *button_data;
+ button_data = BUTTON_DATA (list->data);
+ if (g_file_equal (file, button_data->path))
+ {
+ GIcon *icon;
+ CajaIconInfo *icon_info;
+ GdkPixbuf *pixbuf;
+
+ icon = caja_trash_monitor_get_icon ();
+ icon_info = caja_icon_info_lookup (icon, CAJA_PATH_BAR_ICON_SIZE);
+ pixbuf = caja_icon_info_get_pixbuf_at_size (icon_info, CAJA_PATH_BAR_ICON_SIZE);
+ gtk_image_set_from_pixbuf (GTK_IMAGE (button_data->image), pixbuf);
+ }
+ }
+ g_object_unref (file);
+}
+
+static gboolean
+slider_timeout (gpointer user_data)
+{
+ CajaPathBar *path_bar;
+
+ path_bar = CAJA_PATH_BAR (user_data);
+
+ path_bar->drag_slider_timeout = 0;
+
+ if (gtk_widget_get_visible (GTK_WIDGET (path_bar)))
+ {
+ if (path_bar->drag_slider_timeout_for_up_button)
+ {
+ caja_path_bar_scroll_up (path_bar);
+ }
+ else
+ {
+ caja_path_bar_scroll_down (path_bar);
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+caja_path_bar_slider_drag_motion (GtkWidget *widget,
+ GdkDragContext *context,
+ int x,
+ int y,
+ unsigned int time,
+ gpointer user_data)
+{
+ CajaPathBar *path_bar;
+ GtkSettings *settings;
+ unsigned int timeout;
+
+ path_bar = CAJA_PATH_BAR (user_data);
+
+ if (path_bar->drag_slider_timeout == 0)
+ {
+ settings = gtk_widget_get_settings (widget);
+
+ g_object_get (settings, "gtk-timeout-expand", &timeout, NULL);
+ path_bar->drag_slider_timeout =
+ g_timeout_add (timeout,
+ slider_timeout,
+ path_bar);
+
+ path_bar->drag_slider_timeout_for_up_button =
+ widget == path_bar->up_slider_button;
+ }
+}
+
+static void
+caja_path_bar_slider_drag_leave (GtkWidget *widget,
+ GdkDragContext *context,
+ unsigned int time,
+ gpointer user_data)
+{
+ CajaPathBar *path_bar;
+
+ path_bar = CAJA_PATH_BAR (user_data);
+
+ if (path_bar->drag_slider_timeout != 0)
+ {
+ g_source_remove (path_bar->drag_slider_timeout);
+ path_bar->drag_slider_timeout = 0;
+ }
+}
+
+static void
+caja_path_bar_init (CajaPathBar *path_bar)
+{
+ char *p;
+
+ gtk_widget_set_has_window (GTK_WIDGET (path_bar), FALSE);
+ gtk_widget_set_redraw_on_allocate (GTK_WIDGET (path_bar), FALSE);
+
+ path_bar->spacing = 3;
+ path_bar->up_slider_button = get_slider_button (path_bar, GTK_ARROW_LEFT);
+ path_bar->down_slider_button = get_slider_button (path_bar, GTK_ARROW_RIGHT);
+ path_bar->icon_size = CAJA_PATH_BAR_ICON_SIZE;
+
+ p = caja_get_desktop_directory ();
+ path_bar->desktop_path = g_file_new_for_path (p);
+ g_free (p);
+ path_bar->home_path = g_file_new_for_path (g_get_home_dir ());
+ path_bar->root_path = g_file_new_for_path ("/");
+ desktop_is_home = g_file_equal (path_bar->home_path, path_bar->desktop_path);
+
+ eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_DESKTOP_IS_HOME_DIR,
+ desktop_location_changed_callback,
+ path_bar,
+ G_OBJECT (path_bar));
+
+ g_signal_connect_swapped (path_bar->up_slider_button, "clicked", G_CALLBACK (caja_path_bar_scroll_up), path_bar);
+ g_signal_connect_swapped (path_bar->down_slider_button, "clicked", G_CALLBACK (caja_path_bar_scroll_down), path_bar);
+
+ g_signal_connect (path_bar->up_slider_button, "button_press_event", G_CALLBACK (caja_path_bar_slider_button_press), path_bar);
+ g_signal_connect (path_bar->up_slider_button, "button_release_event", G_CALLBACK (caja_path_bar_slider_button_release), path_bar);
+ g_signal_connect (path_bar->down_slider_button, "button_press_event", G_CALLBACK (caja_path_bar_slider_button_press), path_bar);
+ g_signal_connect (path_bar->down_slider_button, "button_release_event", G_CALLBACK (caja_path_bar_slider_button_release), path_bar);
+
+ gtk_drag_dest_set (GTK_WIDGET (path_bar->up_slider_button),
+ 0, NULL, 0, 0);
+ gtk_drag_dest_set_track_motion (GTK_WIDGET (path_bar->up_slider_button), TRUE);
+ g_signal_connect (path_bar->up_slider_button,
+ "drag-motion",
+ G_CALLBACK (caja_path_bar_slider_drag_motion),
+ path_bar);
+ g_signal_connect (path_bar->up_slider_button,
+ "drag-leave",
+ G_CALLBACK (caja_path_bar_slider_drag_leave),
+ path_bar);
+
+ gtk_drag_dest_set (GTK_WIDGET (path_bar->down_slider_button),
+ 0, NULL, 0, 0);
+ gtk_drag_dest_set_track_motion (GTK_WIDGET (path_bar->up_slider_button), TRUE);
+ g_signal_connect (path_bar->down_slider_button,
+ "drag-motion",
+ G_CALLBACK (caja_path_bar_slider_drag_motion),
+ path_bar);
+ g_signal_connect (path_bar->down_slider_button,
+ "drag-leave",
+ G_CALLBACK (caja_path_bar_slider_drag_leave),
+ path_bar);
+
+ g_signal_connect (caja_trash_monitor_get (),
+ "trash_state_changed",
+ G_CALLBACK (trash_state_changed_cb),
+ path_bar);
+}
+
+static void
+caja_path_bar_class_init (CajaPathBarClass *path_bar_class)
+{
+ GObjectClass *gobject_class;
+ GtkObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+ GtkContainerClass *container_class;
+
+ gobject_class = (GObjectClass *) path_bar_class;
+ object_class = (GtkObjectClass *) path_bar_class;
+ widget_class = (GtkWidgetClass *) path_bar_class;
+ container_class = (GtkContainerClass *) path_bar_class;
+
+ gobject_class->finalize = caja_path_bar_finalize;
+ gobject_class->dispose = caja_path_bar_dispose;
+
+ widget_class->size_request = caja_path_bar_size_request;
+ widget_class->unmap = caja_path_bar_unmap;
+ widget_class->size_allocate = caja_path_bar_size_allocate;
+ widget_class->style_set = caja_path_bar_style_set;
+ widget_class->screen_changed = caja_path_bar_screen_changed;
+ widget_class->grab_notify = caja_path_bar_grab_notify;
+ widget_class->state_changed = caja_path_bar_state_changed;
+ widget_class->scroll_event = caja_path_bar_scroll;
+
+ container_class->add = caja_path_bar_add;
+ container_class->forall = caja_path_bar_forall;
+ container_class->remove = caja_path_bar_remove;
+
+ path_bar_signals [PATH_CLICKED] =
+ g_signal_new ("path-clicked",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (CajaPathBarClass, path_clicked),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ G_TYPE_FILE);
+ path_bar_signals [PATH_SET] =
+ g_signal_new ("path-set",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (CajaPathBarClass, path_set),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ G_TYPE_FILE);
+}
+
+
+static void
+caja_path_bar_finalize (GObject *object)
+{
+ CajaPathBar *path_bar;
+
+ path_bar = CAJA_PATH_BAR (object);
+
+ caja_path_bar_stop_scrolling (path_bar);
+
+ if (path_bar->drag_slider_timeout != 0)
+ {
+ g_source_remove (path_bar->drag_slider_timeout);
+ path_bar->drag_slider_timeout = 0;
+ }
+
+ g_list_free (path_bar->button_list);
+ if (path_bar->root_path)
+ {
+ g_object_unref (path_bar->root_path);
+ path_bar->root_path = NULL;
+ }
+ if (path_bar->home_path)
+ {
+ g_object_unref (path_bar->home_path);
+ path_bar->home_path = NULL;
+ }
+ if (path_bar->desktop_path)
+ {
+ g_object_unref (path_bar->desktop_path);
+ path_bar->desktop_path = NULL;
+ }
+
+ g_signal_handlers_disconnect_by_func (caja_trash_monitor_get (),
+ trash_state_changed_cb, path_bar);
+
+ G_OBJECT_CLASS (caja_path_bar_parent_class)->finalize (object);
+}
+
+/* Removes the settings signal handler. It's safe to call multiple times */
+static void
+remove_settings_signal (CajaPathBar *path_bar,
+ GdkScreen *screen)
+{
+ if (path_bar->settings_signal_id)
+ {
+ GtkSettings *settings;
+
+ settings = gtk_settings_get_for_screen (screen);
+ g_signal_handler_disconnect (settings,
+ path_bar->settings_signal_id);
+ path_bar->settings_signal_id = 0;
+ }
+}
+
+static void
+caja_path_bar_dispose (GObject *object)
+{
+ remove_settings_signal (CAJA_PATH_BAR (object), gtk_widget_get_screen (GTK_WIDGET (object)));
+
+ G_OBJECT_CLASS (caja_path_bar_parent_class)->dispose (object);
+}
+
+/* Size requisition:
+ *
+ * Ideally, our size is determined by another widget, and we are just filling
+ * available space.
+ */
+static void
+caja_path_bar_size_request (GtkWidget *widget,
+ GtkRequisition *requisition)
+{
+ ButtonData *button_data;
+ CajaPathBar *path_bar;
+ GtkRequisition child_requisition;
+ GList *list;
+ guint border_width;
+
+ path_bar = CAJA_PATH_BAR (widget);
+
+ requisition->width = 0;
+ requisition->height = 0;
+
+ for (list = path_bar->button_list; list; list = list->next)
+ {
+ button_data = BUTTON_DATA (list->data);
+ gtk_widget_size_request (button_data->button, &child_requisition);
+ requisition->width = MAX (child_requisition.width, requisition->width);
+ requisition->height = MAX (child_requisition.height, requisition->height);
+ }
+
+ /* Add space for slider, if we have more than one path */
+ /* Theoretically, the slider could be bigger than the other button. But we're */
+ /* not going to worry about that now.*/
+
+ path_bar->slider_width = MIN(requisition->height * 2 / 3 + 5, requisition->height);
+ if (path_bar->button_list && path_bar->button_list->next != NULL)
+ {
+ requisition->width += (path_bar->spacing + path_bar->slider_width) * 2;
+ }
+
+ gtk_widget_size_request (path_bar->up_slider_button, &child_requisition);
+ gtk_widget_size_request (path_bar->down_slider_button, &child_requisition);
+
+ border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
+ requisition->width += border_width * 2;
+ requisition->height += border_width * 2;
+
+ gtk_widget_set_size_request (widget, requisition->width,
+ requisition->height);
+}
+
+static void
+caja_path_bar_update_slider_buttons (CajaPathBar *path_bar)
+{
+ if (path_bar->button_list)
+ {
+
+ GtkWidget *button;
+
+ button = BUTTON_DATA (path_bar->button_list->data)->button;
+ if (gtk_widget_get_child_visible (button))
+ {
+ gtk_widget_set_sensitive (path_bar->down_slider_button, FALSE);
+ }
+ else
+ {
+ gtk_widget_set_sensitive (path_bar->down_slider_button, TRUE);
+ }
+ button = BUTTON_DATA (g_list_last (path_bar->button_list)->data)->button;
+ if (gtk_widget_get_child_visible (button))
+ {
+ gtk_widget_set_sensitive (path_bar->up_slider_button, FALSE);
+ }
+ else
+ {
+ gtk_widget_set_sensitive (path_bar->up_slider_button, TRUE);
+ }
+ }
+}
+
+static void
+caja_path_bar_unmap (GtkWidget *widget)
+{
+ caja_path_bar_stop_scrolling (CAJA_PATH_BAR (widget));
+
+ GTK_WIDGET_CLASS (caja_path_bar_parent_class)->unmap (widget);
+}
+
+/* This is a tad complicated */
+static void
+caja_path_bar_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ GtkWidget *child;
+ CajaPathBar *path_bar;
+ GtkTextDirection direction;
+ GtkAllocation child_allocation;
+ GList *list, *first_button;
+ gint width;
+ gint allocation_width;
+ gint border_width;
+ gboolean need_sliders;
+ gint up_slider_offset;
+ gint down_slider_offset;
+ GtkRequisition child_requisition;
+ GtkAllocation widget_allocation;
+
+ need_sliders = FALSE;
+ up_slider_offset = 0;
+ down_slider_offset = 0;
+ path_bar = CAJA_PATH_BAR (widget);
+
+ gtk_widget_set_allocation (widget, allocation);
+
+ /* No path is set so we don't have to allocate anything. */
+ if (path_bar->button_list == NULL)
+ {
+ return;
+ }
+ direction = gtk_widget_get_direction (widget);
+ border_width = (gint) gtk_container_get_border_width (GTK_CONTAINER (path_bar));
+ allocation_width = allocation->width - 2 * border_width;
+
+ /* First, we check to see if we need the scrollbars. */
+ if (path_bar->fake_root)
+ {
+ width = path_bar->spacing + path_bar->slider_width;
+ }
+ else
+ {
+ width = 0;
+ }
+
+ gtk_widget_get_child_requisition (BUTTON_DATA (path_bar->button_list->data)->button, &child_requisition);
+ width += child_requisition.width;
+
+ for (list = path_bar->button_list->next; list; list = list->next)
+ {
+ child = BUTTON_DATA (list->data)->button;
+ gtk_widget_get_child_requisition (child, &child_requisition);
+ width += child_requisition.width + path_bar->spacing;
+
+ if (list == path_bar->fake_root)
+ {
+ break;
+ }
+ }
+
+ if (width <= allocation_width)
+ {
+ if (path_bar->fake_root)
+ {
+ first_button = path_bar->fake_root;
+ }
+ else
+ {
+ first_button = g_list_last (path_bar->button_list);
+ }
+ }
+ else
+ {
+ gboolean reached_end;
+ gint slider_space;
+ reached_end = FALSE;
+ slider_space = 2 * (path_bar->spacing + path_bar->slider_width);
+
+ if (path_bar->first_scrolled_button)
+ {
+ first_button = path_bar->first_scrolled_button;
+ }
+ else
+ {
+ first_button = path_bar->button_list;
+ }
+
+ need_sliders = TRUE;
+ /* To see how much space we have, and how many buttons we can display.
+ * We start at the first button, count forward until hit the new
+ * button, then count backwards.
+ */
+ /* Count down the path chain towards the end. */
+ gtk_widget_get_child_requisition (BUTTON_DATA (first_button->data)->button, &child_requisition);
+ width = child_requisition.width;
+ list = first_button->prev;
+ while (list && !reached_end)
+ {
+ child = BUTTON_DATA (list->data)->button;
+ gtk_widget_get_child_requisition (child, &child_requisition);
+
+ if (width + child_requisition.width + path_bar->spacing + slider_space > allocation_width)
+ {
+ reached_end = TRUE;
+ }
+ else
+ {
+ if (list == path_bar->fake_root)
+ {
+ break;
+ }
+ else
+ {
+ width += child_requisition.width + path_bar->spacing;
+ }
+ }
+
+ list = list->prev;
+ }
+
+ /* Finally, we walk up, seeing how many of the previous buttons we can add*/
+
+ while (first_button->next && ! reached_end)
+ {
+ child = BUTTON_DATA (first_button->next->data)->button;
+ gtk_widget_get_child_requisition (child, &child_requisition);
+
+ if (width + child_requisition.width + path_bar->spacing + slider_space > allocation_width)
+ {
+ reached_end = TRUE;
+ }
+ else
+ {
+ width += child_requisition.width + path_bar->spacing;
+ if (first_button == path_bar->fake_root)
+ {
+ break;
+ }
+ first_button = first_button->next;
+ }
+ }
+ }
+
+ /* Now, we allocate space to the buttons */
+ child_allocation.y = allocation->y + border_width;
+ child_allocation.height = MAX (1, (gint) allocation->height - border_width * 2);
+
+ if (direction == GTK_TEXT_DIR_RTL)
+ {
+ child_allocation.x = allocation->x + allocation->width - border_width;
+ if (need_sliders || path_bar->fake_root)
+ {
+ child_allocation.x -= (path_bar->spacing + path_bar->slider_width);
+ up_slider_offset = allocation->width - border_width - path_bar->slider_width;
+ }
+ }
+ else
+ {
+ child_allocation.x = allocation->x + border_width;
+ if (need_sliders || path_bar->fake_root)
+ {
+ up_slider_offset = border_width;
+ child_allocation.x += (path_bar->spacing + path_bar->slider_width);
+ }
+ }
+
+ for (list = first_button; list; list = list->prev)
+ {
+ child = BUTTON_DATA (list->data)->button;
+ gtk_widget_get_child_requisition (child, &child_requisition);
+
+ gtk_widget_get_allocation (widget, &widget_allocation);
+
+ child_allocation.width = child_requisition.width;
+ if (direction == GTK_TEXT_DIR_RTL)
+ {
+ child_allocation.x -= child_allocation.width;
+ }
+ /* Check to see if we've don't have any more space to allocate buttons */
+ if (need_sliders && direction == GTK_TEXT_DIR_RTL)
+ {
+ if (child_allocation.x - path_bar->spacing - path_bar->slider_width < widget_allocation.x + border_width)
+ {
+ break;
+ }
+ }
+ else
+ {
+ if (need_sliders && direction == GTK_TEXT_DIR_LTR)
+ {
+ if (child_allocation.x + child_allocation.width + path_bar->spacing + path_bar->slider_width > widget_allocation.x + border_width + allocation_width)
+ {
+ break;
+ }
+ }
+ }
+
+ gtk_widget_set_child_visible (BUTTON_DATA (list->data)->button, TRUE);
+ gtk_widget_size_allocate (child, &child_allocation);
+
+ if (direction == GTK_TEXT_DIR_RTL)
+ {
+ child_allocation.x -= path_bar->spacing;
+ down_slider_offset = child_allocation.x - widget_allocation.x - path_bar->slider_width;
+ down_slider_offset = border_width;
+ }
+ else
+ {
+ down_slider_offset = child_allocation.x - widget_allocation.x;
+ down_slider_offset = allocation->width - border_width - path_bar->slider_width;
+ child_allocation.x += child_allocation.width + path_bar->spacing;
+ }
+ }
+ /* Now we go hide all the widgets that don't fit */
+ while (list)
+ {
+ gtk_widget_set_child_visible (BUTTON_DATA (list->data)->button, FALSE);
+ list = list->prev;
+ }
+ for (list = first_button->next; list; list = list->next)
+ {
+ gtk_widget_set_child_visible (BUTTON_DATA (list->data)->button, FALSE);
+ }
+
+ if (need_sliders || path_bar->fake_root)
+ {
+ child_allocation.width = path_bar->slider_width;
+ child_allocation.x = up_slider_offset + allocation->x;
+ gtk_widget_size_allocate (path_bar->up_slider_button, &child_allocation);
+
+ gtk_widget_set_child_visible (path_bar->up_slider_button, TRUE);
+ gtk_widget_show_all (path_bar->up_slider_button);
+
+ }
+ else
+ {
+ gtk_widget_set_child_visible (path_bar->up_slider_button, FALSE);
+ }
+
+ if (need_sliders)
+ {
+ child_allocation.width = path_bar->slider_width;
+ child_allocation.x = down_slider_offset + allocation->x;
+ gtk_widget_size_allocate (path_bar->down_slider_button, &child_allocation);
+
+ gtk_widget_set_child_visible (path_bar->down_slider_button, TRUE);
+ gtk_widget_show_all (path_bar->down_slider_button);
+ caja_path_bar_update_slider_buttons (path_bar);
+ }
+ else
+ {
+ gtk_widget_set_child_visible (path_bar->down_slider_button, FALSE);
+ }
+}
+
+static void
+caja_path_bar_style_set (GtkWidget *widget, GtkStyle *previous_style)
+{
+ if (GTK_WIDGET_CLASS (caja_path_bar_parent_class)->style_set)
+ {
+ GTK_WIDGET_CLASS (caja_path_bar_parent_class)->style_set (widget, previous_style);
+ }
+
+ caja_path_bar_check_icon_theme (CAJA_PATH_BAR (widget));
+}
+
+static void
+caja_path_bar_screen_changed (GtkWidget *widget,
+ GdkScreen *previous_screen)
+{
+ if (GTK_WIDGET_CLASS (caja_path_bar_parent_class)->screen_changed)
+ {
+ GTK_WIDGET_CLASS (caja_path_bar_parent_class)->screen_changed (widget, previous_screen);
+ }
+ /* We might nave a new settings, so we remove the old one */
+ if (previous_screen)
+ {
+ remove_settings_signal (CAJA_PATH_BAR (widget), previous_screen);
+ }
+ caja_path_bar_check_icon_theme (CAJA_PATH_BAR (widget));
+}
+
+static gboolean
+caja_path_bar_scroll (GtkWidget *widget,
+ GdkEventScroll *event)
+{
+ CajaPathBar *path_bar;
+
+ path_bar = CAJA_PATH_BAR (widget);
+
+ switch (event->direction)
+ {
+ case GDK_SCROLL_RIGHT:
+ case GDK_SCROLL_DOWN:
+ caja_path_bar_scroll_down (path_bar);
+ return TRUE;
+
+ case GDK_SCROLL_LEFT:
+ case GDK_SCROLL_UP:
+ caja_path_bar_scroll_up (path_bar);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+static void
+caja_path_bar_add (GtkContainer *container,
+ GtkWidget *widget)
+{
+ gtk_widget_set_parent (widget, GTK_WIDGET (container));
+}
+
+static void
+caja_path_bar_remove_1 (GtkContainer *container,
+ GtkWidget *widget)
+{
+ gboolean was_visible = gtk_widget_get_visible (widget);
+ gtk_widget_unparent (widget);
+ if (was_visible)
+ {
+ gtk_widget_queue_resize (GTK_WIDGET (container));
+ }
+}
+
+static void
+caja_path_bar_remove (GtkContainer *container,
+ GtkWidget *widget)
+{
+ CajaPathBar *path_bar;
+ GList *children;
+
+ path_bar = CAJA_PATH_BAR (container);
+
+ if (widget == path_bar->up_slider_button)
+ {
+ caja_path_bar_remove_1 (container, widget);
+ path_bar->up_slider_button = NULL;
+ return;
+ }
+
+ if (widget == path_bar->down_slider_button)
+ {
+ caja_path_bar_remove_1 (container, widget);
+ path_bar->down_slider_button = NULL;
+ return;
+ }
+
+ children = path_bar->button_list;
+ while (children)
+ {
+ if (widget == BUTTON_DATA (children->data)->button)
+ {
+ caja_path_bar_remove_1 (container, widget);
+ path_bar->button_list = g_list_remove_link (path_bar->button_list, children);
+ g_list_free_1 (children);
+ return;
+ }
+ children = children->next;
+ }
+}
+
+static void
+caja_path_bar_forall (GtkContainer *container,
+ gboolean include_internals,
+ GtkCallback callback,
+ gpointer callback_data)
+{
+ CajaPathBar *path_bar;
+ GList *children;
+
+ g_return_if_fail (callback != NULL);
+ path_bar = CAJA_PATH_BAR (container);
+
+ children = path_bar->button_list;
+ while (children)
+ {
+ GtkWidget *child;
+ child = BUTTON_DATA (children->data)->button;
+ children = children->next;
+ (* callback) (child, callback_data);
+ }
+
+ if (path_bar->up_slider_button)
+ {
+ (* callback) (path_bar->up_slider_button, callback_data);
+ }
+
+ if (path_bar->down_slider_button)
+ {
+ (* callback) (path_bar->down_slider_button, callback_data);
+ }
+}
+
+static void
+caja_path_bar_scroll_down (CajaPathBar *path_bar)
+{
+ GList *list;
+ GList *down_button;
+ GList *up_button;
+ gint space_available;
+ gint space_needed;
+ gint border_width;
+ GtkTextDirection direction;
+ GtkAllocation allocation, button_allocation, slider_allocation;
+
+ down_button = NULL;
+ up_button = NULL;
+
+ if (path_bar->ignore_click)
+ {
+ path_bar->ignore_click = FALSE;
+ return;
+ }
+
+ gtk_widget_queue_resize (GTK_WIDGET (path_bar));
+
+ border_width = gtk_container_get_border_width (GTK_CONTAINER (path_bar));
+ direction = gtk_widget_get_direction (GTK_WIDGET (path_bar));
+
+ /* We find the button at the 'down' end that we have to make */
+ /* visible */
+ for (list = path_bar->button_list; list; list = list->next)
+ {
+ if (list->next && gtk_widget_get_child_visible (BUTTON_DATA (list->next->data)->button))
+ {
+ down_button = list;
+ break;
+ }
+ }
+
+ if (down_button == NULL)
+ {
+ return;
+ }
+
+ /* Find the last visible button on the 'up' end */
+ for (list = g_list_last (path_bar->button_list); list; list = list->prev)
+ {
+ if (gtk_widget_get_child_visible (BUTTON_DATA (list->data)->button))
+ {
+ up_button = list;
+ break;
+ }
+ }
+
+ gtk_widget_get_allocation (BUTTON_DATA (down_button->data)->button, &button_allocation);
+ gtk_widget_get_allocation (GTK_WIDGET (path_bar), &allocation);
+ gtk_widget_get_allocation (path_bar->down_slider_button, &slider_allocation);
+
+ space_needed = button_allocation.width + path_bar->spacing;
+ if (direction == GTK_TEXT_DIR_RTL)
+ {
+ space_available = slider_allocation.x - allocation.x;
+ }
+ else
+ {
+ space_available = (allocation.x + allocation.width - border_width) -
+ (slider_allocation.x + slider_allocation.width);
+ }
+
+ /* We have space_available extra space that's not being used. We
+ * need space_needed space to make the button fit. So we walk down
+ * from the end, removing buttons until we get all the space we
+ * need. */
+ gtk_widget_get_allocation (BUTTON_DATA (up_button->data)->button, &button_allocation);
+ while (space_available < space_needed)
+ {
+ space_available += button_allocation.width + path_bar->spacing;
+ up_button = up_button->prev;
+ path_bar->first_scrolled_button = up_button;
+ }
+}
+
+static void
+caja_path_bar_scroll_up (CajaPathBar *path_bar)
+{
+ GList *list;
+
+ if (path_bar->ignore_click)
+ {
+ path_bar->ignore_click = FALSE;
+ return;
+ }
+
+ gtk_widget_queue_resize (GTK_WIDGET (path_bar));
+
+ for (list = g_list_last (path_bar->button_list); list; list = list->prev)
+ {
+ if (list->prev && gtk_widget_get_child_visible (BUTTON_DATA (list->prev->data)->button))
+ {
+ if (list->prev == path_bar->fake_root)
+ {
+ path_bar->fake_root = NULL;
+ }
+ path_bar->first_scrolled_button = list;
+ return;
+ }
+ }
+}
+
+static gboolean
+caja_path_bar_scroll_timeout (CajaPathBar *path_bar)
+{
+ gboolean retval = FALSE;
+
+ GDK_THREADS_ENTER ();
+
+ if (path_bar->timer)
+ {
+ if (gtk_widget_has_focus (path_bar->up_slider_button))
+ {
+ caja_path_bar_scroll_up (path_bar);
+ }
+ else
+ {
+ if (gtk_widget_has_focus (path_bar->down_slider_button))
+ {
+ caja_path_bar_scroll_down (path_bar);
+ }
+ }
+ if (path_bar->need_timer)
+ {
+ path_bar->need_timer = FALSE;
+
+ path_bar->timer = g_timeout_add (SCROLL_TIMEOUT,
+ (GSourceFunc)caja_path_bar_scroll_timeout,
+ path_bar);
+
+ }
+ else
+ {
+ retval = TRUE;
+ }
+ }
+
+
+ GDK_THREADS_LEAVE ();
+
+ return retval;
+}
+
+static void
+caja_path_bar_stop_scrolling (CajaPathBar *path_bar)
+{
+ if (path_bar->timer)
+ {
+ g_source_remove (path_bar->timer);
+ path_bar->timer = 0;
+ path_bar->need_timer = FALSE;
+ }
+}
+
+static gboolean
+caja_path_bar_slider_button_press (GtkWidget *widget,
+ GdkEventButton *event,
+ CajaPathBar *path_bar)
+{
+ if (!gtk_widget_has_focus (widget))
+ {
+ gtk_widget_grab_focus (widget);
+ }
+
+ if (event->type != GDK_BUTTON_PRESS || event->button != 1)
+ {
+ return FALSE;
+ }
+
+ path_bar->ignore_click = FALSE;
+
+ if (widget == path_bar->up_slider_button)
+ {
+ caja_path_bar_scroll_up (path_bar);
+ }
+ else
+ {
+ if (widget == path_bar->down_slider_button)
+ {
+ caja_path_bar_scroll_down (path_bar);
+ }
+ }
+
+ if (!path_bar->timer)
+ {
+ path_bar->need_timer = TRUE;
+ path_bar->timer = g_timeout_add (INITIAL_SCROLL_TIMEOUT,
+ (GSourceFunc)caja_path_bar_scroll_timeout,
+ path_bar);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+caja_path_bar_slider_button_release (GtkWidget *widget,
+ GdkEventButton *event,
+ CajaPathBar *path_bar)
+{
+ if (event->type != GDK_BUTTON_RELEASE)
+ {
+ return FALSE;
+ }
+
+ path_bar->ignore_click = TRUE;
+ caja_path_bar_stop_scrolling (path_bar);
+
+ return FALSE;
+}
+
+static void
+caja_path_bar_grab_notify (GtkWidget *widget,
+ gboolean was_grabbed)
+{
+ if (!was_grabbed)
+ {
+ caja_path_bar_stop_scrolling (CAJA_PATH_BAR (widget));
+ }
+}
+
+static void
+caja_path_bar_state_changed (GtkWidget *widget,
+ GtkStateType previous_state)
+{
+ if (!gtk_widget_get_sensitive (widget))
+ {
+ caja_path_bar_stop_scrolling (CAJA_PATH_BAR (widget));
+ }
+}
+
+
+
+/* Changes the icons wherever it is needed */
+static void
+reload_icons (CajaPathBar *path_bar)
+{
+ GList *list;
+
+ for (list = path_bar->button_list; list; list = list->next)
+ {
+ ButtonData *button_data;
+
+ button_data = BUTTON_DATA (list->data);
+ if (button_data->type != NORMAL_BUTTON || button_data->is_base_dir)
+ {
+ caja_path_bar_update_button_appearance (button_data);
+ }
+
+ }
+}
+
+static void
+change_icon_theme (CajaPathBar *path_bar)
+{
+ path_bar->icon_size = CAJA_PATH_BAR_ICON_SIZE;
+ reload_icons (path_bar);
+}
+
+/* Callback used when a GtkSettings value changes */
+static void
+settings_notify_cb (GObject *object,
+ GParamSpec *pspec,
+ CajaPathBar *path_bar)
+{
+ const char *name;
+
+ name = g_param_spec_get_name (pspec);
+
+ if (! strcmp (name, "gtk-icon-theme-name") || ! strcmp (name, "gtk-icon-sizes"))
+ {
+ change_icon_theme (path_bar);
+ }
+}
+
+static void
+caja_path_bar_check_icon_theme (CajaPathBar *path_bar)
+{
+ GtkSettings *settings;
+
+ if (path_bar->settings_signal_id)
+ {
+ return;
+ }
+
+ settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (path_bar)));
+ path_bar->settings_signal_id = g_signal_connect (settings, "notify", G_CALLBACK (settings_notify_cb), path_bar);
+
+ change_icon_theme (path_bar);
+}
+
+/* Public functions and their helpers */
+void
+caja_path_bar_clear_buttons (CajaPathBar *path_bar)
+{
+ while (path_bar->button_list != NULL)
+ {
+ gtk_container_remove (GTK_CONTAINER (path_bar), BUTTON_DATA (path_bar->button_list->data)->button);
+ }
+ path_bar->first_scrolled_button = NULL;
+ path_bar->fake_root = NULL;
+}
+
+static void
+button_clicked_cb (GtkWidget *button,
+ gpointer data)
+{
+ ButtonData *button_data;
+ CajaPathBar *path_bar;
+ GList *button_list;
+ gboolean child_is_hidden;
+
+ button_data = BUTTON_DATA (data);
+ if (button_data->ignore_changes)
+ {
+ return;
+ }
+
+ path_bar = CAJA_PATH_BAR (gtk_widget_get_parent (button));
+
+ button_list = g_list_find (path_bar->button_list, button_data);
+ g_assert (button_list != NULL);
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
+
+ if (button_list->prev)
+ {
+ ButtonData *child_data;
+
+ child_data = BUTTON_DATA (button_list->prev->data);
+ child_is_hidden = child_data->file_is_hidden;
+ }
+ else
+ {
+ child_is_hidden = FALSE;
+ }
+ g_signal_emit (path_bar, path_bar_signals [PATH_CLICKED], 0, button_data->path);
+}
+
+static CajaIconInfo *
+get_custom_user_icon_info (ButtonData *button_data)
+{
+ /* Bug 80925: With tiny display sizes we get huge memory allocations. */
+#if 0
+ CajaIconInfo *icon_info;
+ GFile *icon_file;
+ GIcon *icon;
+ char *custom_icon_uri;
+
+ icon = NULL;
+
+ if (button_data->file != NULL)
+ {
+ custom_icon_uri = caja_file_get_custom_icon (button_data->file);
+ if (custom_icon_uri != NULL)
+ {
+ icon_file = g_file_new_for_uri (custom_icon_uri);
+
+ if (g_file_is_native (icon_file))
+ {
+ icon = g_file_icon_new (icon_file);
+ }
+
+ g_object_unref (icon_file);
+ g_free (custom_icon_uri);
+ }
+ }
+
+ if (icon != NULL)
+ {
+ icon_info = caja_icon_info_lookup (icon, CAJA_PATH_BAR_ICON_SIZE);
+ g_object_unref (icon);
+
+ return icon_info;
+ }
+#endif
+
+ return NULL;
+}
+
+static CajaIconInfo *
+get_type_icon_info (ButtonData *button_data)
+{
+ switch (button_data->type)
+ {
+ case ROOT_BUTTON:
+ return caja_icon_info_lookup_from_name (CAJA_ICON_FILESYSTEM,
+ CAJA_PATH_BAR_ICON_SIZE);
+
+ case HOME_BUTTON:
+ return caja_icon_info_lookup_from_name (CAJA_ICON_HOME,
+ CAJA_PATH_BAR_ICON_SIZE);
+
+ case DESKTOP_BUTTON:
+ return caja_icon_info_lookup_from_name (CAJA_ICON_DESKTOP,
+ CAJA_PATH_BAR_ICON_SIZE);
+
+ case NORMAL_BUTTON:
+ if (button_data->is_base_dir)
+ {
+ return caja_file_get_icon (button_data->file,
+ CAJA_PATH_BAR_ICON_SIZE,
+ CAJA_FILE_ICON_FLAGS_NONE);
+ }
+
+ default:
+ return NULL;
+ }
+
+ return NULL;
+}
+
+static void
+button_data_free (ButtonData *button_data)
+{
+ g_object_unref (button_data->path);
+ g_free (button_data->dir_name);
+ if (button_data->custom_icon)
+ {
+ g_object_unref (button_data->custom_icon);
+ }
+ if (button_data->file != NULL)
+ {
+ g_signal_handler_disconnect (button_data->file,
+ button_data->file_changed_signal_id);
+ caja_file_monitor_remove (button_data->file, button_data);
+ caja_file_unref (button_data->file);
+ }
+
+ g_object_unref (button_data->drag_info.target_location);
+ button_data->drag_info.target_location = NULL;
+
+ g_free (button_data);
+}
+
+static const char *
+get_dir_name (ButtonData *button_data)
+{
+ if (button_data->type == DESKTOP_BUTTON)
+ {
+ return _("Desktop");
+ }
+ else
+ {
+ return button_data->dir_name;
+ }
+}
+
+/* We always want to request the same size for the label, whether
+ * or not the contents are bold
+ */
+static void
+label_size_request_cb (GtkWidget *widget,
+ GtkRequisition *requisition,
+ ButtonData *button_data)
+{
+ const gchar *dir_name = get_dir_name (button_data);
+ PangoLayout *layout;
+ gint bold_width, bold_height;
+ gchar *markup;
+
+ layout = gtk_widget_create_pango_layout (button_data->label, dir_name);
+ pango_layout_get_pixel_size (layout, &requisition->width, &requisition->height);
+
+ markup = g_markup_printf_escaped ("<b>%s</b>", dir_name);
+ pango_layout_set_markup (layout, markup, -1);
+ g_free (markup);
+
+ pango_layout_get_pixel_size (layout, &bold_width, &bold_height);
+ requisition->width = MAX (requisition->width, bold_width);
+ requisition->height = MAX (requisition->height, bold_height);
+
+ g_object_unref (layout);
+}
+
+static void
+caja_path_bar_update_button_appearance (ButtonData *button_data)
+{
+ CajaIconInfo *icon_info;
+ GdkPixbuf *pixbuf;
+ const gchar *dir_name = get_dir_name (button_data);
+
+ if (button_data->label != NULL)
+ {
+ if (gtk_label_get_use_markup (GTK_LABEL (button_data->label)))
+ {
+ char *markup;
+
+ markup = g_markup_printf_escaped ("<b>%s</b>", dir_name);
+ gtk_label_set_markup (GTK_LABEL (button_data->label), markup);
+ g_free (markup);
+ }
+ else
+ {
+ gtk_label_set_text (GTK_LABEL (button_data->label), dir_name);
+ }
+ }
+
+ if (button_data->image != NULL)
+ {
+ if (button_data->custom_icon)
+ {
+ gtk_image_set_from_pixbuf (GTK_IMAGE (button_data->image), button_data->custom_icon);
+ gtk_widget_show (GTK_WIDGET (button_data->image));
+ }
+ else
+ {
+ icon_info = get_custom_user_icon_info (button_data);
+ if (icon_info == NULL)
+ {
+ icon_info = get_type_icon_info (button_data);
+ }
+
+ pixbuf = NULL;
+
+ if (icon_info != NULL)
+ {
+ pixbuf = caja_icon_info_get_pixbuf_at_size (icon_info, CAJA_PATH_BAR_ICON_SIZE);
+ g_object_unref (icon_info);
+ }
+
+ if (pixbuf != NULL)
+ {
+ gtk_image_set_from_pixbuf (GTK_IMAGE (button_data->image), pixbuf);
+ gtk_widget_show (GTK_WIDGET (button_data->image));
+ g_object_unref (pixbuf);
+ }
+ else
+ {
+ gtk_widget_hide (GTK_WIDGET (button_data->image));
+ }
+ }
+ }
+
+}
+
+static void
+caja_path_bar_update_button_state (ButtonData *button_data,
+ gboolean current_dir)
+{
+ if (button_data->label != NULL)
+ {
+ g_object_set (button_data->label, "use-markup", current_dir, NULL);
+ }
+
+ caja_path_bar_update_button_appearance (button_data);
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button_data->button)) != current_dir)
+ {
+ button_data->ignore_changes = TRUE;
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button_data->button), current_dir);
+ button_data->ignore_changes = FALSE;
+ }
+}
+
+static gboolean
+setup_file_path_mounted_mount (GFile *location, ButtonData *button_data)
+{
+ GVolumeMonitor *volume_monitor;
+ GList *mounts, *l;
+ GMount *mount;
+ gboolean result;
+ GIcon *icon;
+ CajaIconInfo *info;
+ GFile *root, *default_location;
+
+ result = FALSE;
+ volume_monitor = g_volume_monitor_get ();
+ mounts = g_volume_monitor_get_mounts (volume_monitor);
+ for (l = mounts; l != NULL; l = l->next)
+ {
+ mount = l->data;
+ if (g_mount_is_shadowed (mount))
+ {
+ continue;
+ }
+ if (result)
+ {
+ continue;
+ }
+ root = g_mount_get_root (mount);
+ if (g_file_equal (location, root))
+ {
+ result = TRUE;
+ /* set mount specific details in button_data */
+ if (button_data)
+ {
+ icon = g_mount_get_icon (mount);
+ if (icon == NULL)
+ {
+ icon = g_themed_icon_new (CAJA_ICON_FOLDER);
+ }
+ info = caja_icon_info_lookup (icon, CAJA_PATH_BAR_ICON_SIZE);
+ g_object_unref (icon);
+ button_data->custom_icon = caja_icon_info_get_pixbuf_at_size (info, CAJA_PATH_BAR_ICON_SIZE);
+ g_object_unref (info);
+ button_data->dir_name = g_mount_get_name (mount);
+ button_data->type = MOUNT_BUTTON;
+ button_data->fake_root = TRUE;
+ }
+ g_object_unref (root);
+ break;
+ }
+ default_location = g_mount_get_default_location (mount);
+ if (!g_file_equal (default_location, root) &&
+ g_file_equal (location, default_location))
+ {
+ result = TRUE;
+ /* set mount specific details in button_data */
+ if (button_data)
+ {
+ icon = g_mount_get_icon (mount);
+ if (icon == NULL)
+ {
+ icon = g_themed_icon_new (CAJA_ICON_FOLDER);
+ }
+ info = caja_icon_info_lookup (icon, CAJA_PATH_BAR_ICON_SIZE);
+ g_object_unref (icon);
+ button_data->custom_icon = caja_icon_info_get_pixbuf_at_size (info, CAJA_PATH_BAR_ICON_SIZE);
+ g_object_unref (info);
+ button_data->type = DEFAULT_LOCATION_BUTTON;
+ button_data->fake_root = TRUE;
+ }
+ g_object_unref (default_location);
+ g_object_unref (root);
+ break;
+ }
+ g_object_unref (default_location);
+ g_object_unref (root);
+ }
+ eel_g_object_list_free (mounts);
+ return result;
+}
+
+static void
+setup_button_type (ButtonData *button_data,
+ CajaPathBar *path_bar,
+ GFile *location)
+{
+ if (path_bar->root_path != NULL && g_file_equal (location, path_bar->root_path))
+ {
+ button_data->type = ROOT_BUTTON;
+ }
+ else if (path_bar->home_path != NULL && g_file_equal (location, path_bar->home_path))
+ {
+ button_data->type = HOME_BUTTON;
+ button_data->fake_root = TRUE;
+ }
+ else if (path_bar->desktop_path != NULL && g_file_equal (location, path_bar->desktop_path))
+ {
+ if (!desktop_is_home)
+ {
+ button_data->type = DESKTOP_BUTTON;
+ }
+ else
+ {
+ button_data->type = NORMAL_BUTTON;
+ }
+ }
+ else if (setup_file_path_mounted_mount (location, button_data))
+ {
+ /* already setup */
+ }
+ else
+ {
+ button_data->type = NORMAL_BUTTON;
+ }
+}
+
+static void
+button_drag_data_get_cb (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint time_,
+ gpointer user_data)
+{
+ ButtonData *button_data;
+ char *uri_list[2];
+ char *tmp;
+
+ button_data = user_data;
+
+ uri_list[0] = g_file_get_uri (button_data->path);
+ uri_list[1] = NULL;
+
+ if (info == CAJA_ICON_DND_MATE_ICON_LIST)
+ {
+ tmp = g_strdup_printf ("%s\r\n", uri_list[0]);
+ gtk_selection_data_set (selection_data, gtk_selection_data_get_target (selection_data),
+ 8, tmp, strlen (tmp));
+ g_free (tmp);
+ }
+ else if (info == CAJA_ICON_DND_URI_LIST)
+ {
+ gtk_selection_data_set_uris (selection_data, uri_list);
+ }
+
+ g_free (uri_list[0]);
+}
+
+static void
+setup_button_drag_source (ButtonData *button_data)
+{
+ GtkTargetList *target_list;
+ const GtkTargetEntry targets[] =
+ {
+ { CAJA_ICON_DND_MATE_ICON_LIST_TYPE, 0, CAJA_ICON_DND_MATE_ICON_LIST }
+ };
+
+ gtk_drag_source_set (button_data->button,
+ GDK_BUTTON1_MASK |
+ GDK_BUTTON2_MASK,
+ NULL, 0,
+ GDK_ACTION_MOVE |
+ GDK_ACTION_COPY |
+ GDK_ACTION_LINK |
+ GDK_ACTION_ASK);
+
+ target_list = gtk_target_list_new (targets, G_N_ELEMENTS (targets));
+ gtk_target_list_add_uri_targets (target_list, CAJA_ICON_DND_URI_LIST);
+ gtk_drag_source_set_target_list (button_data->button, target_list);
+ gtk_target_list_unref (target_list);
+
+ g_signal_connect (button_data->button, "drag-data-get",
+ G_CALLBACK (button_drag_data_get_cb),
+ button_data);
+}
+
+static void
+button_data_file_changed (CajaFile *file,
+ ButtonData *button_data)
+{
+ GFile *location, *current_location, *parent, *button_parent;
+ ButtonData *current_button_data;
+ char *display_name;
+ CajaPathBar *path_bar;
+ gboolean renamed, child;
+
+ path_bar = (CajaPathBar *) gtk_widget_get_ancestor (button_data->button,
+ CAJA_TYPE_PATH_BAR);
+ if (path_bar == NULL)
+ {
+ return;
+ }
+
+ g_assert (path_bar->current_path != NULL);
+ g_assert (path_bar->current_button_data != NULL);
+
+ current_button_data = path_bar->current_button_data;
+
+ location = caja_file_get_location (file);
+ if (!g_file_equal (button_data->path, location))
+ {
+ parent = g_file_get_parent (location);
+ button_parent = g_file_get_parent (button_data->path);
+
+ renamed = (parent != NULL && button_parent != NULL) &&
+ g_file_equal (parent, button_parent);
+
+ if (parent != NULL)
+ {
+ g_object_unref (parent);
+ }
+ if (button_parent != NULL)
+ {
+ g_object_unref (button_parent);
+ }
+
+ if (renamed)
+ {
+ button_data->path = g_object_ref (location);
+ }
+ else
+ {
+ /* the file has been moved.
+ * If it was below the currently displayed location, remove it.
+ * If it was not below the currently displayed location, update the path bar
+ */
+ child = g_file_has_prefix (button_data->path,
+ path_bar->current_path);
+
+ if (child)
+ {
+ /* moved file inside current path hierarchy */
+ g_object_unref (location);
+ location = g_file_get_parent (button_data->path);
+ current_location = g_object_ref (path_bar->current_path);
+ }
+ else
+ {
+ /* moved current path, or file outside current path hierarchy.
+ * Update path bar to new locations.
+ */
+ current_location = caja_file_get_location (current_button_data->file);
+ }
+
+ caja_path_bar_update_path (path_bar, location, FALSE);
+ caja_path_bar_set_path (path_bar, current_location);
+ g_object_unref (location);
+ g_object_unref (current_location);
+ return;
+ }
+ }
+ else if (caja_file_is_gone (file))
+ {
+ gint idx, position;
+
+ /* remove this and the following buttons */
+ position = g_list_position (path_bar->button_list,
+ g_list_find (path_bar->button_list, button_data));
+
+ if (position != -1)
+ {
+ for (idx = 0; idx <= position; idx++)
+ {
+ gtk_container_remove (GTK_CONTAINER (path_bar),
+ BUTTON_DATA (path_bar->button_list->data)->button);
+ }
+ }
+
+ g_object_unref (location);
+ return;
+ }
+ g_object_unref (location);
+
+ /* MOUNTs use the GMount as the name, so don't update for those */
+ if (button_data->type != MOUNT_BUTTON)
+ {
+ display_name = caja_file_get_display_name (file);
+ if (eel_strcmp (display_name, button_data->dir_name) != 0)
+ {
+ g_free (button_data->dir_name);
+ button_data->dir_name = g_strdup (display_name);
+ }
+
+ g_free (display_name);
+ }
+ caja_path_bar_update_button_appearance (button_data);
+}
+
+static ButtonData *
+make_directory_button (CajaPathBar *path_bar,
+ CajaFile *file,
+ gboolean current_dir,
+ gboolean base_dir,
+ gboolean file_is_hidden)
+{
+ GFile *path;
+ GtkWidget *child;
+ GtkWidget *label_alignment;
+ ButtonData *button_data;
+
+ path = caja_file_get_location (file);
+
+ child = NULL;
+ label_alignment = NULL;
+
+ file_is_hidden = !! file_is_hidden;
+ /* Is it a special button? */
+ button_data = g_new0 (ButtonData, 1);
+
+ setup_button_type (button_data, path_bar, path);
+ button_data->button = gtk_toggle_button_new ();
+ gtk_button_set_focus_on_click (GTK_BUTTON (button_data->button), FALSE);
+ /* TODO update button type when xdg directories change */
+
+ button_data->drag_info.target_location = g_object_ref (path);
+
+ button_data->image = gtk_image_new ();
+
+ switch (button_data->type)
+ {
+ case ROOT_BUTTON:
+ child = button_data->image;
+ button_data->label = NULL;
+ break;
+ case HOME_BUTTON:
+ case DESKTOP_BUTTON:
+ case MOUNT_BUTTON:
+ case DEFAULT_LOCATION_BUTTON:
+ button_data->label = gtk_label_new (NULL);
+ label_alignment = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
+ gtk_container_add (GTK_CONTAINER (label_alignment), button_data->label);
+ child = gtk_hbox_new (FALSE, 2);
+ gtk_box_pack_start (GTK_BOX (child), button_data->image, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (child), label_alignment, FALSE, FALSE, 0);
+ break;
+ case NORMAL_BUTTON:
+ default:
+ button_data->label = gtk_label_new (NULL);
+ label_alignment = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
+ gtk_container_add (GTK_CONTAINER (label_alignment), button_data->label);
+ child = gtk_hbox_new (FALSE, 2);
+ gtk_box_pack_start (GTK_BOX (child), button_data->image, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (child), label_alignment, FALSE, FALSE, 0);
+ button_data->is_base_dir = base_dir;
+ }
+
+ /* label_alignment is created because we can't override size-request
+ * on label itself and still have the contents of the label centered
+ * properly in the label's requisition
+ */
+
+ if (label_alignment)
+ {
+ g_signal_connect (label_alignment, "size-request",
+ G_CALLBACK (label_size_request_cb), button_data);
+ }
+
+ if (button_data->path == NULL)
+ {
+ button_data->path = g_object_ref (path);
+ }
+ if (button_data->dir_name == NULL)
+ {
+ button_data->dir_name = caja_file_get_display_name (file);
+ }
+ if (button_data->file == NULL)
+ {
+ button_data->file = caja_file_ref (file);
+ caja_file_monitor_add (button_data->file, button_data,
+ CAJA_FILE_ATTRIBUTES_FOR_ICON);
+ button_data->file_changed_signal_id =
+ g_signal_connect (button_data->file, "changed",
+ G_CALLBACK (button_data_file_changed),
+ button_data);
+ }
+
+ button_data->file_is_hidden = file_is_hidden;
+
+ gtk_container_add (GTK_CONTAINER (button_data->button), child);
+ gtk_widget_show_all (button_data->button);
+
+ caja_path_bar_update_button_state (button_data, current_dir);
+
+ g_signal_connect (button_data->button, "clicked", G_CALLBACK (button_clicked_cb), button_data);
+ g_object_weak_ref (G_OBJECT (button_data->button), (GWeakNotify) button_data_free, button_data);
+
+ setup_button_drag_source (button_data);
+
+ caja_drag_slot_proxy_init (button_data->button,
+ &(button_data->drag_info));
+
+ g_object_unref (path);
+
+ return button_data;
+}
+
+static gboolean
+caja_path_bar_check_parent_path (CajaPathBar *path_bar,
+ GFile *location,
+ ButtonData **current_button_data)
+{
+ GList *list;
+ GList *current_path;
+ gboolean need_new_fake_root;
+
+ current_path = NULL;
+ need_new_fake_root = FALSE;
+
+ if (current_button_data)
+ {
+ *current_button_data = NULL;
+ }
+
+ for (list = path_bar->button_list; list; list = list->next)
+ {
+ ButtonData *button_data;
+
+ button_data = list->data;
+ if (g_file_equal (location, button_data->path))
+ {
+ current_path = list;
+
+ if (current_button_data)
+ {
+ *current_button_data = button_data;
+ }
+ break;
+ }
+ if (list == path_bar->fake_root)
+ {
+ need_new_fake_root = TRUE;
+ }
+ }
+
+ if (current_path)
+ {
+
+ if (need_new_fake_root)
+ {
+ path_bar->fake_root = NULL;
+ for (list = current_path; list; list = list->next)
+ {
+ ButtonData *button_data;
+
+ button_data = list->data;
+ if (list->prev != NULL &&
+ button_data->fake_root)
+ {
+ path_bar->fake_root = list;
+ break;
+ }
+ }
+ }
+
+ for (list = path_bar->button_list; list; list = list->next)
+ {
+
+ caja_path_bar_update_button_state (BUTTON_DATA (list->data),
+ (list == current_path) ? TRUE : FALSE);
+ }
+
+ if (!gtk_widget_get_child_visible (BUTTON_DATA (current_path->data)->button))
+ {
+ path_bar->first_scrolled_button = current_path;
+ gtk_widget_queue_resize (GTK_WIDGET (path_bar));
+ }
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static gboolean
+caja_path_bar_update_path (CajaPathBar *path_bar,
+ GFile *file_path,
+ gboolean emit_signal)
+{
+ CajaFile *file, *parent_file;
+ gboolean first_directory, last_directory;
+ gboolean result;
+ GList *new_buttons, *l, *fake_root;
+ ButtonData *button_data, *current_button_data;
+
+ g_return_val_if_fail (CAJA_IS_PATH_BAR (path_bar), FALSE);
+ g_return_val_if_fail (file_path != NULL, FALSE);
+
+ fake_root = NULL;
+ result = TRUE;
+ first_directory = TRUE;
+ last_directory = FALSE;
+ new_buttons = NULL;
+ current_button_data = NULL;
+
+ file = caja_file_get (file_path);
+
+ gtk_widget_push_composite_child ();
+
+ while (file != NULL)
+ {
+ parent_file = caja_file_get_parent (file);
+ last_directory = !parent_file;
+ button_data = make_directory_button (path_bar, file, first_directory, last_directory, FALSE);
+ caja_file_unref (file);
+
+ if (first_directory)
+ {
+ current_button_data = button_data;
+ }
+
+ new_buttons = g_list_prepend (new_buttons, button_data);
+
+ if (parent_file != NULL &&
+ button_data->fake_root)
+ {
+ fake_root = new_buttons;
+ }
+
+ file = parent_file;
+ first_directory = FALSE;
+ }
+
+ caja_path_bar_clear_buttons (path_bar);
+ path_bar->button_list = g_list_reverse (new_buttons);
+ path_bar->fake_root = fake_root;
+
+ for (l = path_bar->button_list; l; l = l->next)
+ {
+ GtkWidget *button;
+ button = BUTTON_DATA (l->data)->button;
+ gtk_container_add (GTK_CONTAINER (path_bar), button);
+ }
+
+ gtk_widget_pop_composite_child ();
+
+ if (path_bar->current_path != NULL)
+ {
+ g_object_unref (path_bar->current_path);
+ }
+
+ path_bar->current_path = g_object_ref (file_path);
+ path_bar->current_button_data = current_button_data;
+
+ g_signal_emit (path_bar, path_bar_signals [PATH_SET], 0, file_path);
+
+ return result;
+}
+
+gboolean
+caja_path_bar_set_path (CajaPathBar *path_bar, GFile *file_path)
+{
+ ButtonData *button_data;
+
+ g_return_val_if_fail (CAJA_IS_PATH_BAR (path_bar), FALSE);
+ g_return_val_if_fail (file_path != NULL, FALSE);
+
+ /* Check whether the new path is already present in the pathbar as buttons.
+ * This could be a parent directory or a previous selected subdirectory. */
+ if (caja_path_bar_check_parent_path (path_bar, file_path, &button_data))
+ {
+ if (path_bar->current_path != NULL)
+ {
+ g_object_unref (path_bar->current_path);
+ }
+
+ path_bar->current_path = g_object_ref (file_path);
+ path_bar->current_button_data = button_data;
+
+ return TRUE;
+ }
+
+ return caja_path_bar_update_path (path_bar, file_path, TRUE);
+}
+
+GFile *
+caja_path_bar_get_path_for_button (CajaPathBar *path_bar,
+ GtkWidget *button)
+{
+ GList *list;
+
+ g_return_val_if_fail (CAJA_IS_PATH_BAR (path_bar), NULL);
+ g_return_val_if_fail (GTK_IS_BUTTON (button), NULL);
+
+ for (list = path_bar->button_list; list; list = list->next)
+ {
+ ButtonData *button_data;
+ button_data = BUTTON_DATA (list->data);
+ if (button_data->button == button)
+ {
+ return g_object_ref (button_data->path);
+ }
+ }
+
+ return NULL;
+}
+
+/**
+ * _caja_path_bar_up:
+ * @path_bar: a #CajaPathBar
+ *
+ * If the selected button in the pathbar is not the furthest button "up" (in the
+ * root direction), act as if the user clicked on the next button up.
+ **/
+void
+caja_path_bar_up (CajaPathBar *path_bar)
+{
+ GList *l;
+
+ for (l = path_bar->button_list; l; l = l->next)
+ {
+ GtkWidget *button = BUTTON_DATA (l->data)->button;
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
+ {
+ if (l->next)
+ {
+ GtkWidget *next_button = BUTTON_DATA (l->next->data)->button;
+ button_clicked_cb (next_button, l->next->data);
+ }
+ break;
+ }
+ }
+}
+
+/**
+ * _caja_path_bar_down:
+ * @path_bar: a #CajaPathBar
+ *
+ * If the selected button in the pathbar is not the furthest button "down" (in the
+ * leaf direction), act as if the user clicked on the next button down.
+ **/
+void
+caja_path_bar_down (CajaPathBar *path_bar)
+{
+ GList *l;
+
+ for (l = path_bar->button_list; l; l = l->next)
+ {
+ GtkWidget *button = BUTTON_DATA (l->data)->button;
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
+ {
+ if (l->prev)
+ {
+ GtkWidget *prev_button = BUTTON_DATA (l->prev->data)->button;
+ button_clicked_cb (prev_button, l->prev->data);
+ }
+ break;
+ }
+ }
+}
+
+GtkWidget *
+caja_path_bar_get_button_from_button_list_entry (gpointer entry)
+{
+ return BUTTON_DATA(entry)->button;
+}
diff --git a/src/caja-pathbar.h b/src/caja-pathbar.h
new file mode 100644
index 00000000..2a2ad90e
--- /dev/null
+++ b/src/caja-pathbar.h
@@ -0,0 +1,90 @@
+/* caja-pathbar.h
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ *
+ */
+
+#ifndef CAJA_PATHBAR_H
+#define CAJA_PATHBAR_H
+
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+
+typedef struct _CajaPathBar CajaPathBar;
+typedef struct _CajaPathBarClass CajaPathBarClass;
+
+
+#define CAJA_TYPE_PATH_BAR (caja_path_bar_get_type ())
+#define CAJA_PATH_BAR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_PATH_BAR, CajaPathBar))
+#define CAJA_PATH_BAR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_PATH_BAR, CajaPathBarClass))
+#define CAJA_IS_PATH_BAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_PATH_BAR))
+#define CAJA_IS_PATH_BAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_PATH_BAR))
+#define CAJA_PATH_BAR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_PATH_BAR, CajaPathBarClass))
+
+struct _CajaPathBar
+{
+ GtkContainer parent;
+
+ GFile *root_path;
+ GFile *home_path;
+ GFile *desktop_path;
+
+ GFile *current_path;
+ gpointer current_button_data;
+
+ GList *button_list;
+ GList *first_scrolled_button;
+ GList *fake_root;
+ GtkWidget *up_slider_button;
+ GtkWidget *down_slider_button;
+ guint settings_signal_id;
+ gint icon_size;
+ gint16 slider_width;
+ gint16 spacing;
+ gint16 button_offset;
+ guint timer;
+ guint slider_visible : 1;
+ guint need_timer : 1;
+ guint ignore_click : 1;
+
+ unsigned int drag_slider_timeout;
+ gboolean drag_slider_timeout_for_up_button;
+};
+
+struct _CajaPathBarClass
+{
+ GtkContainerClass parent_class;
+
+ void (* path_clicked) (CajaPathBar *path_bar,
+ GFile *location);
+ void (* path_set) (CajaPathBar *path_bar,
+ GFile *location);
+};
+
+GType caja_path_bar_get_type (void) G_GNUC_CONST;
+
+gboolean caja_path_bar_set_path (CajaPathBar *path_bar, GFile *file);
+GFile * caja_path_bar_get_path_for_button (CajaPathBar *path_bar,
+ GtkWidget *button);
+void caja_path_bar_clear_buttons (CajaPathBar *path_bar);
+
+void caja_path_bar_up (CajaPathBar *path_bar);
+void caja_path_bar_down (CajaPathBar *path_bar);
+
+GtkWidget * caja_path_bar_get_button_from_button_list_entry (gpointer entry);
+
+#endif /* CAJA_PATHBAR_H */
diff --git a/src/caja-places-sidebar.c b/src/caja-places-sidebar.c
new file mode 100644
index 00000000..52c1048e
--- /dev/null
+++ b/src/caja-places-sidebar.c
@@ -0,0 +1,3092 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * 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
+ * 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.
+ *
+ * Author : Mr Jamie McCracken (jamiemcc at blueyonder dot co dot uk)
+ *
+ */
+
+#include <config.h>
+
+#include <eel/eel-debug.h>
+#include <eel/eel-gtk-extensions.h>
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-preferences.h>
+#include <eel/eel-string.h>
+#include <eel/eel-stock-dialogs.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <libcaja-private/caja-debug-log.h>
+#include <libcaja-private/caja-dnd.h>
+#include <libcaja-private/caja-bookmark.h>
+#include <libcaja-private/caja-global-preferences.h>
+#include <libcaja-private/caja-sidebar-provider.h>
+#include <libcaja-private/caja-module.h>
+#include <libcaja-private/caja-file.h>
+#include <libcaja-private/caja-file-utilities.h>
+#include <libcaja-private/caja-file-operations.h>
+#include <libcaja-private/caja-trash-monitor.h>
+#include <libcaja-private/caja-icon-names.h>
+#include <libcaja-private/caja-autorun.h>
+#include <libcaja-private/caja-window-info.h>
+#include <libcaja-private/caja-window-slot-info.h>
+#include <gio/gio.h>
+
+#include "caja-bookmark-list.h"
+#include "caja-places-sidebar.h"
+#include "caja-window.h"
+
+#define EJECT_BUTTON_XPAD 5
+
+typedef struct
+{
+ GtkScrolledWindow parent;
+ GtkTreeView *tree_view;
+ GtkCellRenderer *icon_cell_renderer;
+ GtkCellRenderer *eject_text_cell_renderer;
+ char *uri;
+ GtkListStore *store;
+ GtkTreeModel *filter_model;
+ CajaWindowInfo *window;
+ CajaBookmarkList *bookmarks;
+ GVolumeMonitor *volume_monitor;
+
+ /* DnD */
+ GList *drag_list;
+ gboolean drag_data_received;
+ int drag_data_info;
+ gboolean drop_occured;
+
+ GtkWidget *popup_menu;
+ GtkWidget *popup_menu_open_in_new_tab_item;
+ GtkWidget *popup_menu_remove_item;
+ GtkWidget *popup_menu_rename_item;
+ GtkWidget *popup_menu_separator_item;
+ GtkWidget *popup_menu_mount_item;
+ GtkWidget *popup_menu_unmount_item;
+ GtkWidget *popup_menu_eject_item;
+ GtkWidget *popup_menu_rescan_item;
+ GtkWidget *popup_menu_format_item;
+ GtkWidget *popup_menu_empty_trash_item;
+ GtkWidget *popup_menu_start_item;
+ GtkWidget *popup_menu_stop_item;
+
+ /* volume mounting - delayed open process */
+ gboolean mounting;
+ CajaWindowSlotInfo *go_to_after_mount_slot;
+ CajaWindowOpenFlags go_to_after_mount_flags;
+} CajaPlacesSidebar;
+
+typedef struct
+{
+ GtkScrolledWindowClass parent;
+} CajaPlacesSidebarClass;
+
+typedef struct
+{
+ GObject parent;
+} CajaPlacesSidebarProvider;
+
+typedef struct
+{
+ GObjectClass parent;
+} CajaPlacesSidebarProviderClass;
+
+enum
+{
+ PLACES_SIDEBAR_COLUMN_ROW_TYPE,
+ PLACES_SIDEBAR_COLUMN_URI,
+ PLACES_SIDEBAR_COLUMN_DRIVE,
+ PLACES_SIDEBAR_COLUMN_VOLUME,
+ PLACES_SIDEBAR_COLUMN_MOUNT,
+ PLACES_SIDEBAR_COLUMN_NAME,
+ PLACES_SIDEBAR_COLUMN_ICON,
+ PLACES_SIDEBAR_COLUMN_INDEX,
+ PLACES_SIDEBAR_COLUMN_EJECT,
+ PLACES_SIDEBAR_COLUMN_NO_EJECT,
+ PLACES_SIDEBAR_COLUMN_BOOKMARK,
+ PLACES_SIDEBAR_COLUMN_NO_BOOKMARK,
+ PLACES_SIDEBAR_COLUMN_TOOLTIP,
+
+ PLACES_SIDEBAR_COLUMN_COUNT
+};
+
+typedef enum
+{
+ PLACES_BUILT_IN,
+ PLACES_MOUNTED_VOLUME,
+ PLACES_BOOKMARK,
+ PLACES_SEPARATOR
+} PlaceType;
+
+static void caja_places_sidebar_iface_init (CajaSidebarIface *iface);
+static void sidebar_provider_iface_init (CajaSidebarProviderIface *iface);
+static GType caja_places_sidebar_provider_get_type (void);
+static void open_selected_bookmark (CajaPlacesSidebar *sidebar,
+ GtkTreeModel *model,
+ GtkTreePath *path,
+ CajaWindowOpenFlags flags);
+static void caja_places_sidebar_style_set (GtkWidget *widget,
+ GtkStyle *previous_style);
+static gboolean eject_or_unmount_bookmark (CajaPlacesSidebar *sidebar,
+ GtkTreePath *path);
+static gboolean eject_or_unmount_selection (CajaPlacesSidebar *sidebar);
+static void check_unmount_and_eject (GMount *mount,
+ GVolume *volume,
+ GDrive *drive,
+ gboolean *show_unmount,
+ gboolean *show_eject);
+
+static void bookmarks_check_popup_sensitivity (CajaPlacesSidebar *sidebar);
+
+/* Identifiers for target types */
+enum
+{
+ GTK_TREE_MODEL_ROW,
+ TEXT_URI_LIST
+};
+
+/* Target types for dragging from the shortcuts list */
+static const GtkTargetEntry caja_shortcuts_source_targets[] =
+{
+ { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, GTK_TREE_MODEL_ROW }
+};
+
+/* Target types for dropping into the shortcuts list */
+static const GtkTargetEntry caja_shortcuts_drop_targets [] =
+{
+ { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, GTK_TREE_MODEL_ROW },
+ { "text/uri-list", 0, TEXT_URI_LIST }
+};
+
+/* Drag and drop interface declarations */
+typedef struct
+{
+ GtkTreeModelFilter parent;
+
+ CajaPlacesSidebar *sidebar;
+} CajaShortcutsModelFilter;
+
+typedef struct
+{
+ GtkTreeModelFilterClass parent_class;
+} CajaShortcutsModelFilterClass;
+
+#define CAJA_SHORTCUTS_MODEL_FILTER_TYPE (_caja_shortcuts_model_filter_get_type ())
+#define CAJA_SHORTCUTS_MODEL_FILTER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_SHORTCUTS_MODEL_FILTER_TYPE, CajaShortcutsModelFilter))
+
+GType _caja_shortcuts_model_filter_get_type (void);
+static void caja_shortcuts_model_filter_drag_source_iface_init (GtkTreeDragSourceIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (CajaShortcutsModelFilter,
+ _caja_shortcuts_model_filter,
+ GTK_TYPE_TREE_MODEL_FILTER,
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_DRAG_SOURCE,
+ caja_shortcuts_model_filter_drag_source_iface_init));
+
+static GtkTreeModel *caja_shortcuts_model_filter_new (CajaPlacesSidebar *sidebar,
+ GtkTreeModel *child_model,
+ GtkTreePath *root);
+
+G_DEFINE_TYPE_WITH_CODE (CajaPlacesSidebar, caja_places_sidebar, GTK_TYPE_SCROLLED_WINDOW,
+ G_IMPLEMENT_INTERFACE (CAJA_TYPE_SIDEBAR,
+ caja_places_sidebar_iface_init));
+
+G_DEFINE_TYPE_WITH_CODE (CajaPlacesSidebarProvider, caja_places_sidebar_provider, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (CAJA_TYPE_SIDEBAR_PROVIDER,
+ sidebar_provider_iface_init));
+
+static GtkTreeIter
+add_place (CajaPlacesSidebar *sidebar,
+ PlaceType place_type,
+ const char *name,
+ GIcon *icon,
+ const char *uri,
+ GDrive *drive,
+ GVolume *volume,
+ GMount *mount,
+ const int index,
+ const char *tooltip)
+{
+ GdkPixbuf *pixbuf;
+ GtkTreeIter iter, child_iter;
+ CajaIconInfo *icon_info;
+ int icon_size;
+ gboolean show_eject, show_unmount;
+ gboolean show_eject_button;
+
+ icon_size = caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU);
+ icon_info = caja_icon_info_lookup (icon, icon_size);
+
+ pixbuf = caja_icon_info_get_pixbuf_at_size (icon_info, icon_size);
+ g_object_unref (icon_info);
+ check_unmount_and_eject (mount, volume, drive,
+ &show_unmount, &show_eject);
+
+ if (show_unmount || show_eject)
+ {
+ g_assert (place_type != PLACES_BOOKMARK);
+ }
+
+ if (mount == NULL)
+ {
+ show_eject_button = FALSE;
+ }
+ else
+ {
+ show_eject_button = (show_unmount || show_eject);
+ }
+
+ gtk_list_store_append (sidebar->store, &iter);
+ gtk_list_store_set (sidebar->store, &iter,
+ PLACES_SIDEBAR_COLUMN_ICON, pixbuf,
+ PLACES_SIDEBAR_COLUMN_NAME, name,
+ PLACES_SIDEBAR_COLUMN_URI, uri,
+ PLACES_SIDEBAR_COLUMN_DRIVE, drive,
+ PLACES_SIDEBAR_COLUMN_VOLUME, volume,
+ PLACES_SIDEBAR_COLUMN_MOUNT, mount,
+ PLACES_SIDEBAR_COLUMN_ROW_TYPE, place_type,
+ PLACES_SIDEBAR_COLUMN_INDEX, index,
+ PLACES_SIDEBAR_COLUMN_EJECT, show_eject_button,
+ PLACES_SIDEBAR_COLUMN_NO_EJECT, !show_eject_button,
+ PLACES_SIDEBAR_COLUMN_BOOKMARK, place_type != PLACES_BOOKMARK,
+ PLACES_SIDEBAR_COLUMN_NO_BOOKMARK, place_type == PLACES_BOOKMARK,
+ PLACES_SIDEBAR_COLUMN_TOOLTIP, tooltip,
+ -1);
+
+ if (pixbuf != NULL)
+ {
+ g_object_unref (pixbuf);
+ }
+ gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (sidebar->filter_model));
+ gtk_tree_model_filter_convert_child_iter_to_iter (GTK_TREE_MODEL_FILTER (sidebar->filter_model),
+ &child_iter,
+ &iter);
+ return child_iter;
+}
+
+static void
+compare_for_selection (CajaPlacesSidebar *sidebar,
+ const gchar *location,
+ const gchar *added_uri,
+ const gchar *last_uri,
+ GtkTreeIter *iter,
+ GtkTreePath **path)
+{
+ int res;
+
+ res = eel_strcmp (added_uri, last_uri);
+
+ if (res == 0)
+ {
+ /* last_uri always comes first */
+ if (*path != NULL)
+ {
+ gtk_tree_path_free (*path);
+ }
+ *path = gtk_tree_model_get_path (sidebar->filter_model,
+ iter);
+ }
+ else if (eel_strcmp (location, added_uri) == 0)
+ {
+ if (*path == NULL)
+ {
+ *path = gtk_tree_model_get_path (sidebar->filter_model,
+ iter);
+ }
+ }
+}
+
+static void
+update_places (CajaPlacesSidebar *sidebar)
+{
+ CajaBookmark *bookmark;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter, last_iter;
+ GtkTreePath *select_path;
+ GtkTreeModel *model;
+ GVolumeMonitor *volume_monitor;
+ GList *mounts, *l, *ll;
+ GMount *mount;
+ GList *drives;
+ GDrive *drive;
+ GList *volumes;
+ GVolume *volume;
+ int bookmark_count, index;
+ char *location, *mount_uri, *name, *desktop_path, *last_uri;
+ GIcon *icon;
+ GFile *root;
+ CajaWindowSlotInfo *slot;
+ char *tooltip;
+
+ model = NULL;
+ last_uri = NULL;
+ select_path = NULL;
+
+ selection = gtk_tree_view_get_selection (sidebar->tree_view);
+ if (gtk_tree_selection_get_selected (selection, &model, &last_iter))
+ {
+ gtk_tree_model_get (model,
+ &last_iter,
+ PLACES_SIDEBAR_COLUMN_URI, &last_uri, -1);
+ }
+ gtk_list_store_clear (sidebar->store);
+
+ slot = caja_window_info_get_active_slot (sidebar->window);
+ location = caja_window_slot_info_get_current_location (slot);
+
+ /* add built in bookmarks */
+ desktop_path = caja_get_desktop_directory ();
+
+ if (strcmp (g_get_home_dir(), desktop_path) != 0)
+ {
+ char *display_name;
+
+ mount_uri = caja_get_home_directory_uri ();
+ display_name = g_filename_display_basename (g_get_home_dir ());
+ icon = g_themed_icon_new (CAJA_ICON_HOME);
+ last_iter = add_place (sidebar, PLACES_BUILT_IN,
+ display_name, icon,
+ mount_uri, NULL, NULL, NULL, 0,
+ _("Open your personal folder"));
+ g_object_unref (icon);
+ g_free (display_name);
+ compare_for_selection (sidebar,
+ location, mount_uri, last_uri,
+ &last_iter, &select_path);
+ g_free (mount_uri);
+ }
+
+ mount_uri = g_filename_to_uri (desktop_path, NULL, NULL);
+ icon = g_themed_icon_new (CAJA_ICON_DESKTOP);
+ last_iter = add_place (sidebar, PLACES_BUILT_IN,
+ _("Desktop"), icon,
+ mount_uri, NULL, NULL, NULL, 0,
+ _("Open the contents of your desktop in a folder"));
+ g_object_unref (icon);
+ compare_for_selection (sidebar,
+ location, mount_uri, last_uri,
+ &last_iter, &select_path);
+ g_free (mount_uri);
+ g_free (desktop_path);
+
+ mount_uri = "file:///"; /* No need to strdup */
+ icon = g_themed_icon_new (CAJA_ICON_FILESYSTEM);
+ last_iter = add_place (sidebar, PLACES_BUILT_IN,
+ _("File System"), icon,
+ mount_uri, NULL, NULL, NULL, 0,
+ _("Open the contents of the File System"));
+ g_object_unref (icon);
+ compare_for_selection (sidebar,
+ location, mount_uri, last_uri,
+ &last_iter, &select_path);
+
+ mount_uri = "network:///"; /* No need to strdup */
+ icon = g_themed_icon_new (CAJA_ICON_NETWORK);
+ last_iter = add_place (sidebar, PLACES_BUILT_IN,
+ _("Network"), icon,
+ mount_uri, NULL, NULL, NULL, 0,
+ _("Browse the contents of the network"));
+ g_object_unref (icon);
+ compare_for_selection (sidebar,
+ location, mount_uri, last_uri,
+ &last_iter, &select_path);
+
+ volume_monitor = sidebar->volume_monitor;
+
+ /* first go through all connected drives */
+ drives = g_volume_monitor_get_connected_drives (volume_monitor);
+ for (l = drives; l != NULL; l = l->next)
+ {
+ drive = l->data;
+
+ volumes = g_drive_get_volumes (drive);
+ if (volumes != NULL)
+ {
+ for (ll = volumes; ll != NULL; ll = ll->next)
+ {
+ volume = ll->data;
+ mount = g_volume_get_mount (volume);
+ if (mount != NULL)
+ {
+ /* Show mounted volume in the sidebar */
+ icon = g_mount_get_icon (mount);
+ root = g_mount_get_default_location (mount);
+ mount_uri = g_file_get_uri (root);
+ name = g_mount_get_name (mount);
+ tooltip = g_file_get_parse_name (root);
+ last_iter = add_place (sidebar, PLACES_MOUNTED_VOLUME,
+ name, icon, mount_uri,
+ drive, volume, mount, 0, tooltip);
+ compare_for_selection (sidebar,
+ location, mount_uri, last_uri,
+ &last_iter, &select_path);
+ g_object_unref (root);
+ g_object_unref (mount);
+ g_object_unref (icon);
+ g_free (tooltip);
+ g_free (name);
+ g_free (mount_uri);
+ }
+ else
+ {
+ /* Do show the unmounted volumes in the sidebar;
+ * this is so the user can mount it (in case automounting
+ * is off).
+ *
+ * Also, even if automounting is enabled, this gives a visual
+ * cue that the user should remember to yank out the media if
+ * he just unmounted it.
+ */
+ icon = g_volume_get_icon (volume);
+ name = g_volume_get_name (volume);
+ tooltip = g_strdup_printf (_("Mount and open %s"), name);
+ last_iter = add_place (sidebar, PLACES_MOUNTED_VOLUME,
+ name, icon, NULL,
+ drive, volume, NULL, 0, tooltip);
+ g_object_unref (icon);
+ g_free (name);
+ g_free (tooltip);
+ }
+ g_object_unref (volume);
+ }
+ g_list_free (volumes);
+ }
+ else
+ {
+ if (g_drive_is_media_removable (drive) && !g_drive_is_media_check_automatic (drive))
+ {
+ /* If the drive has no mountable volumes and we cannot detect media change.. we
+ * display the drive in the sidebar so the user can manually poll the drive by
+ * right clicking and selecting "Rescan..."
+ *
+ * This is mainly for drives like floppies where media detection doesn't
+ * work.. but it's also for human beings who like to turn off media detection
+ * in the OS to save battery juice.
+ */
+ icon = g_drive_get_icon (drive);
+ name = g_drive_get_name (drive);
+ tooltip = g_strdup_printf (_("Mount and open %s"), name);
+ last_iter = add_place (sidebar, PLACES_BUILT_IN,
+ name, icon, NULL,
+ drive, NULL, NULL, 0, tooltip);
+ g_object_unref (icon);
+ g_free (tooltip);
+ g_free (name);
+ }
+ }
+ g_object_unref (drive);
+ }
+ g_list_free (drives);
+
+ /* add all volumes that is not associated with a drive */
+ volumes = g_volume_monitor_get_volumes (volume_monitor);
+ for (l = volumes; l != NULL; l = l->next)
+ {
+ volume = l->data;
+ drive = g_volume_get_drive (volume);
+ if (drive != NULL)
+ {
+ g_object_unref (volume);
+ g_object_unref (drive);
+ continue;
+ }
+ mount = g_volume_get_mount (volume);
+ if (mount != NULL)
+ {
+ icon = g_mount_get_icon (mount);
+ root = g_mount_get_default_location (mount);
+ mount_uri = g_file_get_uri (root);
+ tooltip = g_file_get_parse_name (root);
+ g_object_unref (root);
+ name = g_mount_get_name (mount);
+ last_iter = add_place (sidebar, PLACES_MOUNTED_VOLUME,
+ name, icon, mount_uri,
+ NULL, volume, mount, 0, tooltip);
+ compare_for_selection (sidebar,
+ location, mount_uri, last_uri,
+ &last_iter, &select_path);
+ g_object_unref (mount);
+ g_object_unref (icon);
+ g_free (name);
+ g_free (tooltip);
+ g_free (mount_uri);
+ }
+ else
+ {
+ /* see comment above in why we add an icon for an unmounted mountable volume */
+ icon = g_volume_get_icon (volume);
+ name = g_volume_get_name (volume);
+ last_iter = add_place (sidebar, PLACES_MOUNTED_VOLUME,
+ name, icon, NULL,
+ NULL, volume, NULL, 0, name);
+ g_object_unref (icon);
+ g_free (name);
+ }
+ g_object_unref (volume);
+ }
+ g_list_free (volumes);
+
+ /* add mounts that has no volume (/etc/mtab mounts, ftp, sftp,...) */
+ mounts = g_volume_monitor_get_mounts (volume_monitor);
+ for (l = mounts; l != NULL; l = l->next)
+ {
+ mount = l->data;
+ if (g_mount_is_shadowed (mount))
+ {
+ g_object_unref (mount);
+ continue;
+ }
+ volume = g_mount_get_volume (mount);
+ if (volume != NULL)
+ {
+ g_object_unref (volume);
+ g_object_unref (mount);
+ continue;
+ }
+ icon = g_mount_get_icon (mount);
+ root = g_mount_get_default_location (mount);
+ mount_uri = g_file_get_uri (root);
+ name = g_mount_get_name (mount);
+ tooltip = g_file_get_parse_name (root);
+ last_iter = add_place (sidebar, PLACES_MOUNTED_VOLUME,
+ name, icon, mount_uri,
+ NULL, NULL, mount, 0, tooltip);
+ compare_for_selection (sidebar,
+ location, mount_uri, last_uri,
+ &last_iter, &select_path);
+ g_object_unref (root);
+ g_object_unref (mount);
+ g_object_unref (icon);
+ g_free (name);
+ g_free (mount_uri);
+ g_free (tooltip);
+ }
+ g_list_free (mounts);
+
+ mount_uri = "trash:///"; /* No need to strdup */
+ icon = caja_trash_monitor_get_icon ();
+ last_iter = add_place (sidebar, PLACES_BUILT_IN,
+ _("Trash"), icon, mount_uri,
+ NULL, NULL, NULL, 0,
+ _("Open the trash"));
+ compare_for_selection (sidebar,
+ location, mount_uri, last_uri,
+ &last_iter, &select_path);
+ g_object_unref (icon);
+
+ /* add separator */
+
+ gtk_list_store_append (sidebar->store, &iter);
+ gtk_list_store_set (sidebar->store, &iter,
+ PLACES_SIDEBAR_COLUMN_ROW_TYPE, PLACES_SEPARATOR,
+ -1);
+
+ /* add bookmarks */
+
+ bookmark_count = caja_bookmark_list_length (sidebar->bookmarks);
+ for (index = 0; index < bookmark_count; ++index)
+ {
+ bookmark = caja_bookmark_list_item_at (sidebar->bookmarks, index);
+
+ if (caja_bookmark_uri_known_not_to_exist (bookmark))
+ {
+ continue;
+ }
+
+ name = caja_bookmark_get_name (bookmark);
+ icon = caja_bookmark_get_icon (bookmark);
+ mount_uri = caja_bookmark_get_uri (bookmark);
+ root = caja_bookmark_get_location (bookmark);
+ tooltip = g_file_get_parse_name (root);
+ last_iter = add_place (sidebar, PLACES_BOOKMARK,
+ name, icon, mount_uri,
+ NULL, NULL, NULL, index,
+ tooltip);
+ compare_for_selection (sidebar,
+ location, mount_uri, last_uri,
+ &last_iter, &select_path);
+ g_free (name);
+ g_object_unref (root);
+ g_object_unref (icon);
+ g_free (mount_uri);
+ g_free (tooltip);
+ }
+ g_free (location);
+
+ if (select_path != NULL)
+ {
+ gtk_tree_selection_select_path (selection, select_path);
+ }
+
+ if (select_path != NULL)
+ {
+ gtk_tree_path_free (select_path);
+ }
+
+ g_free (last_uri);
+}
+
+static gboolean
+caja_shortcuts_row_separator_func (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ PlaceType type;
+
+ gtk_tree_model_get (model, iter, PLACES_SIDEBAR_COLUMN_ROW_TYPE, &type, -1);
+
+ if (type == PLACES_SEPARATOR)
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+mount_added_callback (GVolumeMonitor *volume_monitor,
+ GMount *mount,
+ CajaPlacesSidebar *sidebar)
+{
+ update_places (sidebar);
+}
+
+static void
+mount_removed_callback (GVolumeMonitor *volume_monitor,
+ GMount *mount,
+ CajaPlacesSidebar *sidebar)
+{
+ update_places (sidebar);
+}
+
+static void
+mount_changed_callback (GVolumeMonitor *volume_monitor,
+ GMount *mount,
+ CajaPlacesSidebar *sidebar)
+{
+ update_places (sidebar);
+}
+
+static void
+volume_added_callback (GVolumeMonitor *volume_monitor,
+ GVolume *volume,
+ CajaPlacesSidebar *sidebar)
+{
+ update_places (sidebar);
+}
+
+static void
+volume_removed_callback (GVolumeMonitor *volume_monitor,
+ GVolume *volume,
+ CajaPlacesSidebar *sidebar)
+{
+ update_places (sidebar);
+}
+
+static void
+volume_changed_callback (GVolumeMonitor *volume_monitor,
+ GVolume *volume,
+ CajaPlacesSidebar *sidebar)
+{
+ update_places (sidebar);
+}
+
+static void
+drive_disconnected_callback (GVolumeMonitor *volume_monitor,
+ GDrive *drive,
+ CajaPlacesSidebar *sidebar)
+{
+ update_places (sidebar);
+}
+
+static void
+drive_connected_callback (GVolumeMonitor *volume_monitor,
+ GDrive *drive,
+ CajaPlacesSidebar *sidebar)
+{
+ update_places (sidebar);
+}
+
+static void
+drive_changed_callback (GVolumeMonitor *volume_monitor,
+ GDrive *drive,
+ CajaPlacesSidebar *sidebar)
+{
+ update_places (sidebar);
+}
+
+static gboolean
+clicked_eject_button (CajaPlacesSidebar *sidebar,
+ GtkTreePath **path)
+{
+ GdkEvent *event = gtk_get_current_event ();
+ GdkEventButton *button_event = (GdkEventButton *) event;
+ GtkTreeViewColumn *column;
+ GtkTextDirection direction;
+ int width, total_width;
+ int eject_button_size;
+ gboolean show_eject;
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+
+ *path = NULL;
+ model = gtk_tree_view_get_model (sidebar->tree_view);
+
+ if ((event->type == GDK_BUTTON_PRESS || event->type == GDK_BUTTON_RELEASE) &&
+ gtk_tree_view_get_path_at_pos (sidebar->tree_view,
+ button_event->x, button_event->y,
+ path, &column, NULL, NULL))
+ {
+
+ gtk_tree_model_get_iter (model, &iter, *path);
+ gtk_tree_model_get (model, &iter,
+ PLACES_SIDEBAR_COLUMN_EJECT, &show_eject,
+ -1);
+
+ if (!show_eject)
+ {
+ goto out;
+ }
+
+ total_width = 0;
+
+ gtk_widget_style_get (GTK_WIDGET (sidebar->tree_view),
+ "horizontal-separator", &width,
+ NULL);
+ total_width += width / 2;
+
+ direction = gtk_widget_get_direction (GTK_WIDGET (sidebar->tree_view));
+ if (direction != GTK_TEXT_DIR_RTL)
+ {
+ gtk_tree_view_column_cell_get_position (column,
+ sidebar->icon_cell_renderer,
+ NULL, &width);
+ total_width += width;
+
+ gtk_tree_view_column_cell_get_position (column,
+ sidebar->eject_text_cell_renderer,
+ NULL, &width);
+ total_width += width;
+ }
+
+ total_width += EJECT_BUTTON_XPAD;
+
+ eject_button_size = caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU);
+
+ if (button_event->x - total_width >= 0 &&
+ button_event->x - total_width <= eject_button_size)
+ {
+ return TRUE;
+ }
+ }
+
+out:
+ if (*path != NULL)
+ {
+ gtk_tree_path_free (*path);
+ }
+
+ return FALSE;
+}
+
+static void
+desktop_location_changed_callback (gpointer user_data)
+{
+ CajaPlacesSidebar *sidebar;
+
+ sidebar = CAJA_PLACES_SIDEBAR (user_data);
+
+ update_places (sidebar);
+}
+
+static void
+loading_uri_callback (CajaWindowInfo *window,
+ char *location,
+ CajaPlacesSidebar *sidebar)
+{
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+ gboolean valid;
+ char *uri;
+
+ if (strcmp (sidebar->uri, location) != 0)
+ {
+ g_free (sidebar->uri);
+ sidebar->uri = g_strdup (location);
+
+ /* set selection if any place matches location */
+ selection = gtk_tree_view_get_selection (sidebar->tree_view);
+ gtk_tree_selection_unselect_all (selection);
+ valid = gtk_tree_model_get_iter_first (sidebar->filter_model, &iter);
+
+ while (valid)
+ {
+ gtk_tree_model_get (sidebar->filter_model, &iter,
+ PLACES_SIDEBAR_COLUMN_URI, &uri,
+ -1);
+ if (uri != NULL)
+ {
+ if (strcmp (uri, location) == 0)
+ {
+ g_free (uri);
+ gtk_tree_selection_select_iter (selection, &iter);
+ break;
+ }
+ g_free (uri);
+ }
+ valid = gtk_tree_model_iter_next (sidebar->filter_model, &iter);
+ }
+ }
+}
+
+
+static unsigned int
+get_bookmark_index (GtkTreeView *tree_view)
+{
+ GtkTreeModel *model;
+ GtkTreePath *p;
+ GtkTreeIter iter;
+ PlaceType place_type;
+ int bookmark_index;
+
+ model = gtk_tree_view_get_model (tree_view);
+
+ bookmark_index = -1;
+
+ /* find separator */
+ p = gtk_tree_path_new_first ();
+ while (p != NULL)
+ {
+ gtk_tree_model_get_iter (model, &iter, p);
+ gtk_tree_model_get (model, &iter,
+ PLACES_SIDEBAR_COLUMN_ROW_TYPE, &place_type,
+ -1);
+
+ if (place_type == PLACES_SEPARATOR)
+ {
+ bookmark_index = *gtk_tree_path_get_indices (p) + 1;
+ break;
+ }
+
+ gtk_tree_path_next (p);
+ }
+ gtk_tree_path_free (p);
+
+ g_assert (bookmark_index >= 0);
+
+ return bookmark_index;
+}
+
+/* Computes the appropriate row and position for dropping */
+static void
+compute_drop_position (GtkTreeView *tree_view,
+ int x,
+ int y,
+ GtkTreePath **path,
+ GtkTreeViewDropPosition *pos,
+ CajaPlacesSidebar *sidebar)
+{
+ int bookmarks_index;
+ int num_rows;
+ int row;
+
+ bookmarks_index = get_bookmark_index (tree_view);
+
+ num_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (sidebar->filter_model), NULL);
+
+ if (!gtk_tree_view_get_dest_row_at_pos (tree_view,
+ x,
+ y,
+ path,
+ pos))
+ {
+ row = num_rows - 1;
+ *path = gtk_tree_path_new_from_indices (row, -1);
+ *pos = GTK_TREE_VIEW_DROP_AFTER;
+ return;
+ }
+
+ row = *gtk_tree_path_get_indices (*path);
+ gtk_tree_path_free (*path);
+
+ if (row == bookmarks_index - 1)
+ {
+ /* Only allow to drop after separator */
+ *pos = GTK_TREE_VIEW_DROP_AFTER;
+ }
+ else if (row < bookmarks_index)
+ {
+ /* Hardcoded shortcuts can only be dragged into */
+ *pos = GTK_TREE_VIEW_DROP_INTO_OR_BEFORE;
+ }
+ else if (row >= num_rows)
+ {
+ row = num_rows - 1;
+ *pos = GTK_TREE_VIEW_DROP_AFTER;
+ }
+ else if (*pos != GTK_TREE_VIEW_DROP_BEFORE &&
+ sidebar->drag_data_received &&
+ sidebar->drag_data_info == GTK_TREE_MODEL_ROW)
+ {
+ /* bookmark rows are never dragged into other bookmark rows */
+ *pos = GTK_TREE_VIEW_DROP_AFTER;
+ }
+
+ *path = gtk_tree_path_new_from_indices (row, -1);
+}
+
+
+static gboolean
+get_drag_data (GtkTreeView *tree_view,
+ GdkDragContext *context,
+ unsigned int time)
+{
+ GdkAtom target;
+
+ target = gtk_drag_dest_find_target (GTK_WIDGET (tree_view),
+ context,
+ NULL);
+
+ if (target == GDK_NONE)
+ {
+ return FALSE;
+ }
+
+ gtk_drag_get_data (GTK_WIDGET (tree_view),
+ context, target, time);
+
+ return TRUE;
+}
+
+static void
+free_drag_data (CajaPlacesSidebar *sidebar)
+{
+ sidebar->drag_data_received = FALSE;
+
+ if (sidebar->drag_list)
+ {
+ caja_drag_destroy_selection_list (sidebar->drag_list);
+ sidebar->drag_list = NULL;
+ }
+}
+
+static gboolean
+can_accept_file_as_bookmark (CajaFile *file)
+{
+ return caja_file_is_directory (file);
+}
+
+static gboolean
+can_accept_items_as_bookmarks (const GList *items)
+{
+ int max;
+ char *uri;
+ CajaFile *file;
+
+ /* Iterate through selection checking if item will get accepted as a bookmark.
+ * If more than 100 items selected, return an over-optimistic result.
+ */
+ for (max = 100; items != NULL && max >= 0; items = items->next, max--)
+ {
+ uri = ((CajaDragSelectionItem *)items->data)->uri;
+ file = caja_file_get_by_uri (uri);
+ if (!can_accept_file_as_bookmark (file))
+ {
+ caja_file_unref (file);
+ return FALSE;
+ }
+ caja_file_unref (file);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+drag_motion_callback (GtkTreeView *tree_view,
+ GdkDragContext *context,
+ int x,
+ int y,
+ unsigned int time,
+ CajaPlacesSidebar *sidebar)
+{
+ GtkTreePath *path;
+ GtkTreeViewDropPosition pos;
+ int action;
+ GtkTreeIter iter, child_iter;
+ char *uri;
+
+ if (!sidebar->drag_data_received)
+ {
+ if (!get_drag_data (tree_view, context, time))
+ {
+ return FALSE;
+ }
+ }
+
+ compute_drop_position (tree_view, x, y, &path, &pos, sidebar);
+
+ if (pos == GTK_TREE_VIEW_DROP_BEFORE ||
+ pos == GTK_TREE_VIEW_DROP_AFTER )
+ {
+ if (sidebar->drag_data_received &&
+ sidebar->drag_data_info == GTK_TREE_MODEL_ROW)
+ {
+ action = GDK_ACTION_MOVE;
+ }
+ else if (can_accept_items_as_bookmarks (sidebar->drag_list))
+ {
+ action = GDK_ACTION_COPY;
+ }
+ else
+ {
+ action = 0;
+ }
+ }
+ else
+ {
+ if (sidebar->drag_list == NULL)
+ {
+ action = 0;
+ }
+ else
+ {
+ gtk_tree_model_get_iter (sidebar->filter_model,
+ &iter, path);
+ gtk_tree_model_filter_convert_iter_to_child_iter (
+ GTK_TREE_MODEL_FILTER (sidebar->filter_model),
+ &child_iter, &iter);
+ gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store),
+ &child_iter,
+ PLACES_SIDEBAR_COLUMN_URI, &uri,
+ -1);
+ caja_drag_default_drop_action_for_icons (context, uri,
+ sidebar->drag_list,
+ &action);
+ g_free (uri);
+ }
+ }
+
+ gtk_tree_view_set_drag_dest_row (tree_view, path, pos);
+ gtk_tree_path_free (path);
+ g_signal_stop_emission_by_name (tree_view, "drag-motion");
+
+ if (action != 0)
+ {
+ gdk_drag_status (context, action, time);
+ }
+ else
+ {
+ gdk_drag_status (context, 0, time);
+ }
+
+ return TRUE;
+}
+
+static void
+drag_leave_callback (GtkTreeView *tree_view,
+ GdkDragContext *context,
+ unsigned int time,
+ CajaPlacesSidebar *sidebar)
+{
+ free_drag_data (sidebar);
+ gtk_tree_view_set_drag_dest_row (tree_view, NULL, GTK_TREE_VIEW_DROP_BEFORE);
+ g_signal_stop_emission_by_name (tree_view, "drag-leave");
+}
+
+/* Parses a "text/uri-list" string and inserts its URIs as bookmarks */
+static void
+bookmarks_drop_uris (CajaPlacesSidebar *sidebar,
+ GtkSelectionData *selection_data,
+ int position)
+{
+ CajaBookmark *bookmark;
+ CajaFile *file;
+ char *uri, *name;
+ char **uris;
+ int i;
+ GFile *location;
+ GIcon *icon;
+
+ uris = gtk_selection_data_get_uris (selection_data);
+ if (!uris)
+ return;
+
+ for (i = 0; uris[i]; i++)
+ {
+ uri = uris[i];
+ file = caja_file_get_by_uri (uri);
+
+ if (!can_accept_file_as_bookmark (file))
+ {
+ caja_file_unref (file);
+ continue;
+ }
+
+ uri = caja_file_get_drop_target_uri (file);
+ location = g_file_new_for_uri (uri);
+ caja_file_unref (file);
+
+ name = caja_compute_title_for_location (location);
+ icon = g_themed_icon_new (CAJA_ICON_FOLDER);
+ bookmark = caja_bookmark_new (location, name, TRUE, icon);
+
+ if (!caja_bookmark_list_contains (sidebar->bookmarks, bookmark))
+ {
+ caja_bookmark_list_insert_item (sidebar->bookmarks, bookmark, position++);
+ }
+
+ g_object_unref (location);
+ g_object_unref (bookmark);
+ g_object_unref (icon);
+ g_free (name);
+ g_free (uri);
+ }
+
+ g_strfreev (uris);
+}
+
+static GList *
+uri_list_from_selection (GList *selection)
+{
+ CajaDragSelectionItem *item;
+ GList *ret;
+ GList *l;
+
+ ret = NULL;
+ for (l = selection; l != NULL; l = l->next)
+ {
+ item = l->data;
+ ret = g_list_prepend (ret, item->uri);
+ }
+
+ return g_list_reverse (ret);
+}
+
+static GList*
+build_selection_list (const char *data)
+{
+ CajaDragSelectionItem *item;
+ GList *result;
+ char **uris;
+ char *uri;
+ int i;
+
+ uris = g_uri_list_extract_uris (data);
+
+ result = NULL;
+ for (i = 0; uris[i]; i++)
+ {
+ uri = uris[i];
+ item = caja_drag_selection_item_new ();
+ item->uri = g_strdup (uri);
+ item->got_icon_position = FALSE;
+ result = g_list_prepend (result, item);
+ }
+
+ g_strfreev (uris);
+
+ return g_list_reverse (result);
+}
+
+static gboolean
+get_selected_iter (CajaPlacesSidebar *sidebar,
+ GtkTreeIter *iter)
+{
+ GtkTreeSelection *selection;
+ GtkTreeIter parent_iter;
+
+ selection = gtk_tree_view_get_selection (sidebar->tree_view);
+ if (!gtk_tree_selection_get_selected (selection, NULL, &parent_iter))
+ {
+ return FALSE;
+ }
+ gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (sidebar->filter_model),
+ iter,
+ &parent_iter);
+ return TRUE;
+}
+
+/* Reorders the selected bookmark to the specified position */
+static void
+reorder_bookmarks (CajaPlacesSidebar *sidebar,
+ int new_position)
+{
+ GtkTreeIter iter;
+ PlaceType type;
+ int old_position;
+
+ /* Get the selected path */
+
+ if (!get_selected_iter (sidebar, &iter))
+ g_assert_not_reached ();
+
+ gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), &iter,
+ PLACES_SIDEBAR_COLUMN_ROW_TYPE, &type,
+ PLACES_SIDEBAR_COLUMN_INDEX, &old_position,
+ -1);
+
+ if (type != PLACES_BOOKMARK ||
+ old_position < 0 ||
+ old_position >= caja_bookmark_list_length (sidebar->bookmarks))
+ {
+ return;
+ }
+
+ caja_bookmark_list_move_item (sidebar->bookmarks, old_position,
+ new_position);
+}
+
+static void
+drag_data_received_callback (GtkWidget *widget,
+ GdkDragContext *context,
+ int x,
+ int y,
+ GtkSelectionData *selection_data,
+ unsigned int info,
+ unsigned int time,
+ CajaPlacesSidebar *sidebar)
+{
+ GtkTreeView *tree_view;
+ GtkTreePath *tree_path;
+ GtkTreeViewDropPosition tree_pos;
+ GtkTreeIter iter;
+ int position;
+ GtkTreeModel *model;
+ char *drop_uri;
+ GList *selection_list, *uris;
+ PlaceType type;
+ gboolean success;
+
+ tree_view = GTK_TREE_VIEW (widget);
+
+ if (!sidebar->drag_data_received)
+ {
+ if (gtk_selection_data_get_target (selection_data) != GDK_NONE &&
+ info == TEXT_URI_LIST)
+ {
+ sidebar->drag_list = build_selection_list (gtk_selection_data_get_data (selection_data));
+ }
+ else
+ {
+ sidebar->drag_list = NULL;
+ }
+ sidebar->drag_data_received = TRUE;
+ sidebar->drag_data_info = info;
+ }
+
+ g_signal_stop_emission_by_name (widget, "drag-data-received");
+
+ if (!sidebar->drop_occured)
+ {
+ return;
+ }
+
+ /* Compute position */
+ compute_drop_position (tree_view, x, y, &tree_path, &tree_pos, sidebar);
+
+ success = FALSE;
+
+ if (tree_pos == GTK_TREE_VIEW_DROP_BEFORE ||
+ tree_pos == GTK_TREE_VIEW_DROP_AFTER)
+ {
+ model = gtk_tree_view_get_model (tree_view);
+
+ if (!gtk_tree_model_get_iter (model, &iter, tree_path))
+ {
+ goto out;
+ }
+
+ gtk_tree_model_get (model, &iter,
+ PLACES_SIDEBAR_COLUMN_ROW_TYPE, &type,
+ PLACES_SIDEBAR_COLUMN_INDEX, &position,
+ -1);
+
+ if (type != PLACES_SEPARATOR && type != PLACES_BOOKMARK)
+ {
+ goto out;
+ }
+
+ if (type == PLACES_BOOKMARK &&
+ tree_pos == GTK_TREE_VIEW_DROP_AFTER)
+ {
+ position++;
+ }
+
+ switch (info)
+ {
+ case TEXT_URI_LIST:
+ bookmarks_drop_uris (sidebar, selection_data, position);
+ success = TRUE;
+ break;
+ case GTK_TREE_MODEL_ROW:
+ reorder_bookmarks (sidebar, position);
+ success = TRUE;
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ }
+ else
+ {
+ GdkDragAction real_action;
+
+ /* file transfer requested */
+ real_action = gdk_drag_context_get_selected_action (context);
+
+ if (real_action == GDK_ACTION_ASK)
+ {
+ real_action =
+ caja_drag_drop_action_ask (GTK_WIDGET (tree_view),
+ gdk_drag_context_get_actions (context));
+ }
+
+ if (real_action > 0)
+ {
+ model = gtk_tree_view_get_model (tree_view);
+
+ gtk_tree_model_get_iter (model, &iter, tree_path);
+ gtk_tree_model_get (model, &iter,
+ PLACES_SIDEBAR_COLUMN_URI, &drop_uri,
+ -1);
+
+ switch (info)
+ {
+ case TEXT_URI_LIST:
+ selection_list = build_selection_list (gtk_selection_data_get_data (selection_data));
+ uris = uri_list_from_selection (selection_list);
+ caja_file_operations_copy_move (uris, NULL, drop_uri,
+ real_action, GTK_WIDGET (tree_view),
+ NULL, NULL);
+ caja_drag_destroy_selection_list (selection_list);
+ g_list_free (uris);
+ success = TRUE;
+ break;
+ case GTK_TREE_MODEL_ROW:
+ success = FALSE;
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ g_free (drop_uri);
+ }
+ }
+
+out:
+ sidebar->drop_occured = FALSE;
+ free_drag_data (sidebar);
+ gtk_drag_finish (context, success, FALSE, time);
+
+ gtk_tree_path_free (tree_path);
+}
+
+static gboolean
+drag_drop_callback (GtkTreeView *tree_view,
+ GdkDragContext *context,
+ int x,
+ int y,
+ unsigned int time,
+ CajaPlacesSidebar *sidebar)
+{
+ gboolean retval = FALSE;
+ sidebar->drop_occured = TRUE;
+ retval = get_drag_data (tree_view, context, time);
+ g_signal_stop_emission_by_name (tree_view, "drag-drop");
+ return retval;
+}
+
+/* Callback used when the file list's popup menu is detached */
+static void
+bookmarks_popup_menu_detach_cb (GtkWidget *attach_widget,
+ GtkMenu *menu)
+{
+ CajaPlacesSidebar *sidebar;
+
+ sidebar = CAJA_PLACES_SIDEBAR (attach_widget);
+ g_assert (CAJA_IS_PLACES_SIDEBAR (sidebar));
+
+ sidebar->popup_menu = NULL;
+ sidebar->popup_menu_remove_item = NULL;
+ sidebar->popup_menu_rename_item = NULL;
+ sidebar->popup_menu_separator_item = NULL;
+ sidebar->popup_menu_mount_item = NULL;
+ sidebar->popup_menu_unmount_item = NULL;
+ sidebar->popup_menu_eject_item = NULL;
+ sidebar->popup_menu_rescan_item = NULL;
+ sidebar->popup_menu_format_item = NULL;
+ sidebar->popup_menu_start_item = NULL;
+ sidebar->popup_menu_stop_item = NULL;
+ sidebar->popup_menu_empty_trash_item = NULL;
+}
+
+static void
+check_unmount_and_eject (GMount *mount,
+ GVolume *volume,
+ GDrive *drive,
+ gboolean *show_unmount,
+ gboolean *show_eject)
+{
+ *show_unmount = FALSE;
+ *show_eject = FALSE;
+
+ if (drive != NULL)
+ {
+ *show_eject = g_drive_can_eject (drive);
+ }
+
+ if (volume != NULL)
+ {
+ *show_eject |= g_volume_can_eject (volume);
+ }
+ if (mount != NULL)
+ {
+ *show_eject |= g_mount_can_eject (mount);
+ *show_unmount = g_mount_can_unmount (mount) && !*show_eject;
+ }
+}
+
+static void
+check_visibility (GMount *mount,
+ GVolume *volume,
+ GDrive *drive,
+ gboolean *show_mount,
+ gboolean *show_unmount,
+ gboolean *show_eject,
+ gboolean *show_rescan,
+ gboolean *show_format,
+ gboolean *show_start,
+ gboolean *show_stop)
+{
+ *show_mount = FALSE;
+ *show_format = FALSE;
+ *show_rescan = FALSE;
+ *show_start = FALSE;
+ *show_stop = FALSE;
+
+ check_unmount_and_eject (mount, volume, drive, show_unmount, show_eject);
+
+ if (drive != NULL)
+ {
+ if (g_drive_is_media_removable (drive) &&
+ !g_drive_is_media_check_automatic (drive) &&
+ g_drive_can_poll_for_media (drive))
+ *show_rescan = TRUE;
+
+ *show_start = g_drive_can_start (drive) || g_drive_can_start_degraded (drive);
+ *show_stop = g_drive_can_stop (drive);
+
+ if (*show_stop)
+ *show_unmount = FALSE;
+ }
+
+ if (volume != NULL)
+ {
+ if (mount == NULL)
+ *show_mount = g_volume_can_mount (volume);
+ }
+}
+
+static void
+bookmarks_check_popup_sensitivity (CajaPlacesSidebar *sidebar)
+{
+ GtkTreeIter iter;
+ PlaceType type;
+ GDrive *drive = NULL;
+ GVolume *volume = NULL;
+ GMount *mount = NULL;
+ gboolean show_mount;
+ gboolean show_unmount;
+ gboolean show_eject;
+ gboolean show_rescan;
+ gboolean show_format;
+ gboolean show_start;
+ gboolean show_stop;
+ gboolean show_empty_trash;
+ char *uri = NULL;
+
+ type = PLACES_BUILT_IN;
+
+ if (sidebar->popup_menu == NULL)
+ {
+ return;
+ }
+
+ if (get_selected_iter (sidebar, &iter))
+ {
+ gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), &iter,
+ PLACES_SIDEBAR_COLUMN_ROW_TYPE, &type,
+ PLACES_SIDEBAR_COLUMN_DRIVE, &drive,
+ PLACES_SIDEBAR_COLUMN_VOLUME, &volume,
+ PLACES_SIDEBAR_COLUMN_MOUNT, &mount,
+ PLACES_SIDEBAR_COLUMN_URI, &uri,
+ -1);
+ }
+
+ gtk_widget_show (sidebar->popup_menu_open_in_new_tab_item);
+
+ gtk_widget_set_sensitive (sidebar->popup_menu_remove_item, (type == PLACES_BOOKMARK));
+ gtk_widget_set_sensitive (sidebar->popup_menu_rename_item, (type == PLACES_BOOKMARK));
+ gtk_widget_set_sensitive (sidebar->popup_menu_empty_trash_item, !caja_trash_monitor_is_empty ());
+
+ check_visibility (mount, volume, drive,
+ &show_mount, &show_unmount, &show_eject, &show_rescan, &show_format, &show_start, &show_stop);
+
+ /* We actually want both eject and unmount since eject will unmount all volumes.
+ * TODO: hide unmount if the drive only has a single mountable volume
+ */
+
+ show_empty_trash = (uri != NULL) &&
+ (!strcmp (uri, "trash:///"));
+
+ eel_gtk_widget_set_shown (sidebar->popup_menu_separator_item,
+ show_mount || show_unmount || show_eject || show_format || show_empty_trash);
+ eel_gtk_widget_set_shown (sidebar->popup_menu_mount_item, show_mount);
+ eel_gtk_widget_set_shown (sidebar->popup_menu_unmount_item, show_unmount);
+ eel_gtk_widget_set_shown (sidebar->popup_menu_eject_item, show_eject);
+ eel_gtk_widget_set_shown (sidebar->popup_menu_rescan_item, show_rescan);
+ eel_gtk_widget_set_shown (sidebar->popup_menu_format_item, show_format);
+ eel_gtk_widget_set_shown (sidebar->popup_menu_start_item, show_start);
+ eel_gtk_widget_set_shown (sidebar->popup_menu_stop_item, show_stop);
+ eel_gtk_widget_set_shown (sidebar->popup_menu_empty_trash_item, show_empty_trash);
+
+ /* Adjust start/stop items to reflect the type of the drive */
+ gtk_menu_item_set_label (GTK_MENU_ITEM (sidebar->popup_menu_start_item), _("_Start"));
+ gtk_menu_item_set_label (GTK_MENU_ITEM (sidebar->popup_menu_stop_item), _("_Stop"));
+ if ((show_start || show_stop) && drive != NULL)
+ {
+ switch (g_drive_get_start_stop_type (drive))
+ {
+ case G_DRIVE_START_STOP_TYPE_SHUTDOWN:
+ /* start() for type G_DRIVE_START_STOP_TYPE_SHUTDOWN is normally not used */
+ gtk_menu_item_set_label (GTK_MENU_ITEM (sidebar->popup_menu_start_item), _("_Power On"));
+ gtk_menu_item_set_label (GTK_MENU_ITEM (sidebar->popup_menu_stop_item), _("_Safely Remove Drive"));
+ break;
+ case G_DRIVE_START_STOP_TYPE_NETWORK:
+ gtk_menu_item_set_label (GTK_MENU_ITEM (sidebar->popup_menu_start_item), _("_Connect Drive"));
+ gtk_menu_item_set_label (GTK_MENU_ITEM (sidebar->popup_menu_stop_item), _("_Disconnect Drive"));
+ break;
+ case G_DRIVE_START_STOP_TYPE_MULTIDISK:
+ gtk_menu_item_set_label (GTK_MENU_ITEM (sidebar->popup_menu_start_item), _("_Start Multi-disk Device"));
+ gtk_menu_item_set_label (GTK_MENU_ITEM (sidebar->popup_menu_stop_item), _("_Stop Multi-disk Device"));
+ break;
+ case G_DRIVE_START_STOP_TYPE_PASSWORD:
+ /* stop() for type G_DRIVE_START_STOP_TYPE_PASSWORD is normally not used */
+ gtk_menu_item_set_label (GTK_MENU_ITEM (sidebar->popup_menu_start_item), _("_Unlock Drive"));
+ gtk_menu_item_set_label (GTK_MENU_ITEM (sidebar->popup_menu_stop_item), _("_Lock Drive"));
+ break;
+
+ default:
+ case G_DRIVE_START_STOP_TYPE_UNKNOWN:
+ /* uses defaults set above */
+ break;
+ }
+ }
+
+
+ g_free (uri);
+}
+
+/* Callback used when the selection in the shortcuts tree changes */
+static void
+bookmarks_selection_changed_cb (GtkTreeSelection *selection,
+ CajaPlacesSidebar *sidebar)
+{
+ bookmarks_check_popup_sensitivity (sidebar);
+}
+
+static void
+volume_mounted_cb (GVolume *volume,
+ GObject *user_data)
+{
+ GMount *mount;
+ CajaPlacesSidebar *sidebar;
+ GFile *location;
+
+ sidebar = CAJA_PLACES_SIDEBAR (user_data);
+
+ sidebar->mounting = FALSE;
+
+ mount = g_volume_get_mount (volume);
+ if (mount != NULL)
+ {
+ location = g_mount_get_default_location (mount);
+
+ if (sidebar->go_to_after_mount_slot != NULL)
+ {
+ if ((sidebar->go_to_after_mount_flags & CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW) == 0)
+ {
+ caja_window_slot_info_open_location (sidebar->go_to_after_mount_slot, location,
+ CAJA_WINDOW_OPEN_ACCORDING_TO_MODE,
+ sidebar->go_to_after_mount_flags, NULL);
+ }
+ else
+ {
+ CajaWindow *cur, *new;
+
+ cur = CAJA_WINDOW (sidebar->window);
+ new = caja_application_create_navigation_window (cur->application,
+ NULL,
+ gtk_window_get_screen (GTK_WINDOW (cur)));
+ caja_window_go_to (new, location);
+ }
+ }
+
+ g_object_unref (G_OBJECT (location));
+ g_object_unref (G_OBJECT (mount));
+ }
+
+
+ eel_remove_weak_pointer (&(sidebar->go_to_after_mount_slot));
+}
+
+static void
+drive_start_from_bookmark_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GError *error;
+ char *primary;
+ char *name;
+
+ error = NULL;
+ if (!g_drive_poll_for_media_finish (G_DRIVE (source_object), res, &error))
+ {
+ if (error->code != G_IO_ERROR_FAILED_HANDLED)
+ {
+ name = g_drive_get_name (G_DRIVE (source_object));
+ primary = g_strdup_printf (_("Unable to start %s"), name);
+ g_free (name);
+ eel_show_error_dialog (primary,
+ error->message,
+ NULL);
+ g_free (primary);
+ }
+ g_error_free (error);
+ }
+}
+
+static void
+open_selected_bookmark (CajaPlacesSidebar *sidebar,
+ GtkTreeModel *model,
+ GtkTreePath *path,
+ CajaWindowOpenFlags flags)
+{
+ CajaWindowSlotInfo *slot;
+ GtkTreeIter iter;
+ GFile *location;
+ char *uri;
+
+ if (!path)
+ {
+ return;
+ }
+
+ if (!gtk_tree_model_get_iter (model, &iter, path))
+ {
+ return;
+ }
+
+ gtk_tree_model_get (model, &iter, PLACES_SIDEBAR_COLUMN_URI, &uri, -1);
+
+ if (uri != NULL)
+ {
+ caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
+ "activate from places sidebar window=%p: %s",
+ sidebar->window, uri);
+ location = g_file_new_for_uri (uri);
+ /* Navigate to the clicked location */
+ if ((flags & CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW) == 0)
+ {
+ slot = caja_window_info_get_active_slot (sidebar->window);
+ caja_window_slot_info_open_location (slot, location,
+ CAJA_WINDOW_OPEN_ACCORDING_TO_MODE,
+ flags, NULL);
+ }
+ else
+ {
+ CajaWindow *cur, *new;
+
+ cur = CAJA_WINDOW (sidebar->window);
+ new = caja_application_create_navigation_window (cur->application,
+ NULL,
+ gtk_window_get_screen (GTK_WINDOW (cur)));
+ caja_window_go_to (new, location);
+ }
+ g_object_unref (location);
+ g_free (uri);
+
+ }
+ else
+ {
+ GDrive *drive;
+ GVolume *volume;
+ CajaWindowSlot *slot;
+
+ gtk_tree_model_get (model, &iter,
+ PLACES_SIDEBAR_COLUMN_DRIVE, &drive,
+ PLACES_SIDEBAR_COLUMN_VOLUME, &volume,
+ -1);
+
+ if (volume != NULL && !sidebar->mounting)
+ {
+ sidebar->mounting = TRUE;
+
+ g_assert (sidebar->go_to_after_mount_slot == NULL);
+
+ slot = caja_window_info_get_active_slot (sidebar->window);
+ sidebar->go_to_after_mount_slot = slot;
+ eel_add_weak_pointer (&(sidebar->go_to_after_mount_slot));
+
+ sidebar->go_to_after_mount_flags = flags;
+
+ caja_file_operations_mount_volume_full (NULL, volume, FALSE,
+ volume_mounted_cb,
+ G_OBJECT (sidebar));
+ }
+ else if (volume == NULL && drive != NULL &&
+ (g_drive_can_start (drive) || g_drive_can_start_degraded (drive)))
+ {
+ GMountOperation *mount_op;
+
+ mount_op = gtk_mount_operation_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (sidebar))));
+ g_drive_start (drive, G_DRIVE_START_NONE, mount_op, NULL, drive_start_from_bookmark_cb, NULL);
+ g_object_unref (mount_op);
+ }
+
+ if (drive != NULL)
+ g_object_unref (drive);
+ if (volume != NULL)
+ g_object_unref (volume);
+ }
+}
+
+static void
+open_shortcut_from_menu (CajaPlacesSidebar *sidebar,
+ CajaWindowOpenFlags flags)
+{
+ GtkTreeModel *model;
+ GtkTreePath *path;
+
+ model = gtk_tree_view_get_model (sidebar->tree_view);
+ gtk_tree_view_get_cursor (sidebar->tree_view, &path, NULL);
+
+ open_selected_bookmark (sidebar, model, path, flags);
+
+ gtk_tree_path_free (path);
+}
+
+static void
+open_shortcut_cb (GtkMenuItem *item,
+ CajaPlacesSidebar *sidebar)
+{
+ open_shortcut_from_menu (sidebar, 0);
+}
+
+static void
+open_shortcut_in_new_window_cb (GtkMenuItem *item,
+ CajaPlacesSidebar *sidebar)
+{
+ open_shortcut_from_menu (sidebar, CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW);
+}
+
+static void
+open_shortcut_in_new_tab_cb (GtkMenuItem *item,
+ CajaPlacesSidebar *sidebar)
+{
+ open_shortcut_from_menu (sidebar, CAJA_WINDOW_OPEN_FLAG_NEW_TAB);
+}
+
+/* Rename the selected bookmark */
+static void
+rename_selected_bookmark (CajaPlacesSidebar *sidebar)
+{
+ GtkTreeIter iter;
+ GtkTreePath *path;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *cell;
+ GList *renderers;
+
+ if (get_selected_iter (sidebar, &iter))
+ {
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (sidebar->store), &iter);
+ column = gtk_tree_view_get_column (GTK_TREE_VIEW (sidebar->tree_view), 0);
+ renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column));
+ cell = g_list_nth_data (renderers, 3);
+ g_list_free (renderers);
+ g_object_set (cell, "editable", TRUE, NULL);
+ gtk_tree_view_set_cursor_on_cell (GTK_TREE_VIEW (sidebar->tree_view),
+ path, column, cell, TRUE);
+ gtk_tree_path_free (path);
+ }
+}
+
+static void
+rename_shortcut_cb (GtkMenuItem *item,
+ CajaPlacesSidebar *sidebar)
+{
+ rename_selected_bookmark (sidebar);
+}
+
+/* Removes the selected bookmarks */
+static void
+remove_selected_bookmarks (CajaPlacesSidebar *sidebar)
+{
+ GtkTreeIter iter;
+ PlaceType type;
+ int index;
+
+ if (!get_selected_iter (sidebar, &iter))
+ {
+ return;
+ }
+
+ gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), &iter,
+ PLACES_SIDEBAR_COLUMN_ROW_TYPE, &type,
+ -1);
+
+ if (type != PLACES_BOOKMARK)
+ {
+ return;
+ }
+
+ gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), &iter,
+ PLACES_SIDEBAR_COLUMN_INDEX, &index,
+ -1);
+
+ caja_bookmark_list_delete_item_at (sidebar->bookmarks, index);
+}
+
+static void
+remove_shortcut_cb (GtkMenuItem *item,
+ CajaPlacesSidebar *sidebar)
+{
+ remove_selected_bookmarks (sidebar);
+}
+
+static void
+mount_shortcut_cb (GtkMenuItem *item,
+ CajaPlacesSidebar *sidebar)
+{
+ GtkTreeIter iter;
+ GVolume *volume;
+
+ if (!get_selected_iter (sidebar, &iter))
+ {
+ return;
+ }
+
+ gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), &iter,
+ PLACES_SIDEBAR_COLUMN_VOLUME, &volume,
+ -1);
+
+ if (volume != NULL)
+ {
+ caja_file_operations_mount_volume (NULL, volume, FALSE);
+ g_object_unref (volume);
+ }
+}
+
+static void
+unmount_done (gpointer data)
+{
+ CajaWindow *window;
+
+ window = data;
+ caja_window_info_set_initiated_unmount (window, FALSE);
+ g_object_unref (window);
+}
+
+static void
+do_unmount (GMount *mount,
+ CajaPlacesSidebar *sidebar)
+{
+ if (mount != NULL)
+ {
+ caja_window_info_set_initiated_unmount (sidebar->window, TRUE);
+ caja_file_operations_unmount_mount_full (NULL, mount, FALSE, TRUE,
+ unmount_done,
+ g_object_ref (sidebar->window));
+ }
+}
+
+static void
+do_unmount_selection (CajaPlacesSidebar *sidebar)
+{
+ GtkTreeIter iter;
+ GMount *mount;
+
+ if (!get_selected_iter (sidebar, &iter))
+ {
+ return;
+ }
+
+ gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), &iter,
+ PLACES_SIDEBAR_COLUMN_MOUNT, &mount,
+ -1);
+
+ if (mount != NULL)
+ {
+ do_unmount (mount, sidebar);
+ g_object_unref (mount);
+ }
+}
+
+static void
+unmount_shortcut_cb (GtkMenuItem *item,
+ CajaPlacesSidebar *sidebar)
+{
+ do_unmount_selection (sidebar);
+}
+
+static void
+drive_eject_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ CajaWindow *window;
+ GError *error;
+ char *primary;
+ char *name;
+
+ window = user_data;
+ caja_window_info_set_initiated_unmount (window, FALSE);
+ g_object_unref (window);
+
+ error = NULL;
+ if (!g_drive_eject_with_operation_finish (G_DRIVE (source_object), res, &error))
+ {
+ if (error->code != G_IO_ERROR_FAILED_HANDLED)
+ {
+ name = g_drive_get_name (G_DRIVE (source_object));
+ primary = g_strdup_printf (_("Unable to eject %s"), name);
+ g_free (name);
+ eel_show_error_dialog (primary,
+ error->message,
+ NULL);
+ g_free (primary);
+ }
+ g_error_free (error);
+ }
+}
+
+static void
+volume_eject_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ CajaWindow *window;
+ GError *error;
+ char *primary;
+ char *name;
+
+ window = user_data;
+ caja_window_info_set_initiated_unmount (window, FALSE);
+ g_object_unref (window);
+
+ error = NULL;
+ if (!g_volume_eject_with_operation_finish (G_VOLUME (source_object), res, &error))
+ {
+ if (error->code != G_IO_ERROR_FAILED_HANDLED)
+ {
+ name = g_volume_get_name (G_VOLUME (source_object));
+ primary = g_strdup_printf (_("Unable to eject %s"), name);
+ g_free (name);
+ eel_show_error_dialog (primary,
+ error->message,
+ NULL);
+ g_free (primary);
+ }
+ g_error_free (error);
+ }
+}
+
+static void
+mount_eject_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ CajaWindow *window;
+ GError *error;
+ char *primary;
+ char *name;
+
+ window = user_data;
+ caja_window_info_set_initiated_unmount (window, FALSE);
+ g_object_unref (window);
+
+ error = NULL;
+ if (!g_mount_eject_with_operation_finish (G_MOUNT (source_object), res, &error))
+ {
+ if (error->code != G_IO_ERROR_FAILED_HANDLED)
+ {
+ name = g_mount_get_name (G_MOUNT (source_object));
+ primary = g_strdup_printf (_("Unable to eject %s"), name);
+ g_free (name);
+ eel_show_error_dialog (primary,
+ error->message,
+ NULL);
+ g_free (primary);
+ }
+ g_error_free (error);
+ }
+}
+
+static void
+do_eject (GMount *mount,
+ GVolume *volume,
+ GDrive *drive,
+ CajaPlacesSidebar *sidebar)
+{
+ GMountOperation *mount_op;
+
+ mount_op = gtk_mount_operation_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (sidebar))));
+ if (mount != NULL)
+ {
+ caja_window_info_set_initiated_unmount (sidebar->window, TRUE);
+ g_mount_eject_with_operation (mount, 0, mount_op, NULL, mount_eject_cb,
+ g_object_ref (sidebar->window));
+ }
+ else if (volume != NULL)
+ {
+ caja_window_info_set_initiated_unmount (sidebar->window, TRUE);
+ g_volume_eject_with_operation (volume, 0, mount_op, NULL, volume_eject_cb,
+ g_object_ref (sidebar->window));
+ }
+ else if (drive != NULL)
+ {
+ caja_window_info_set_initiated_unmount (sidebar->window, TRUE);
+ g_drive_eject_with_operation (drive, 0, mount_op, NULL, drive_eject_cb,
+ g_object_ref (sidebar->window));
+ }
+ g_object_unref (mount_op);
+}
+
+static void
+eject_shortcut_cb (GtkMenuItem *item,
+ CajaPlacesSidebar *sidebar)
+{
+ GtkTreeIter iter;
+ GMount *mount;
+ GVolume *volume;
+ GDrive *drive;
+
+ if (!get_selected_iter (sidebar, &iter))
+ {
+ return;
+ }
+
+ gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), &iter,
+ PLACES_SIDEBAR_COLUMN_MOUNT, &mount,
+ PLACES_SIDEBAR_COLUMN_VOLUME, &volume,
+ PLACES_SIDEBAR_COLUMN_DRIVE, &drive,
+ -1);
+
+ do_eject (mount, volume, drive, sidebar);
+}
+
+static gboolean
+eject_or_unmount_bookmark (CajaPlacesSidebar *sidebar,
+ GtkTreePath *path)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gboolean can_unmount, can_eject;
+ GMount *mount;
+ GVolume *volume;
+ GDrive *drive;
+ gboolean ret;
+
+ model = GTK_TREE_MODEL (sidebar->filter_model);
+
+ if (!path)
+ {
+ return FALSE;
+ }
+ if (!gtk_tree_model_get_iter (model, &iter, path))
+ {
+ return FALSE;
+ }
+
+ gtk_tree_model_get (model, &iter,
+ PLACES_SIDEBAR_COLUMN_MOUNT, &mount,
+ PLACES_SIDEBAR_COLUMN_VOLUME, &volume,
+ PLACES_SIDEBAR_COLUMN_DRIVE, &drive,
+ -1);
+
+ ret = FALSE;
+
+ check_unmount_and_eject (mount, volume, drive, &can_unmount, &can_eject);
+ /* if we can eject, it has priority over unmount */
+ if (can_eject)
+ {
+ do_eject (mount, volume, drive, sidebar);
+ ret = TRUE;
+ }
+ else if (can_unmount)
+ {
+ do_unmount (mount, sidebar);
+ ret = TRUE;
+ }
+
+ if (mount != NULL)
+ g_object_unref (mount);
+ if (volume != NULL)
+ g_object_unref (volume);
+ if (drive != NULL)
+ g_object_unref (drive);
+
+ return ret;
+}
+
+static gboolean
+eject_or_unmount_selection (CajaPlacesSidebar *sidebar)
+{
+ GtkTreeIter iter;
+ GtkTreePath *path;
+ gboolean ret;
+
+ if (!get_selected_iter (sidebar, &iter))
+ {
+ return FALSE;
+ }
+
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (sidebar->store), &iter);
+ if (path == NULL)
+ {
+ return FALSE;
+ }
+
+ ret = eject_or_unmount_bookmark (sidebar, path);
+
+ gtk_tree_path_free (path);
+
+ return ret;
+}
+
+static void
+drive_poll_for_media_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GError *error;
+ char *primary;
+ char *name;
+
+ error = NULL;
+ if (!g_drive_poll_for_media_finish (G_DRIVE (source_object), res, &error))
+ {
+ if (error->code != G_IO_ERROR_FAILED_HANDLED)
+ {
+ name = g_drive_get_name (G_DRIVE (source_object));
+ primary = g_strdup_printf (_("Unable to poll %s for media changes"), name);
+ g_free (name);
+ eel_show_error_dialog (primary,
+ error->message,
+ NULL);
+ g_free (primary);
+ }
+ g_error_free (error);
+ }
+}
+
+static void
+rescan_shortcut_cb (GtkMenuItem *item,
+ CajaPlacesSidebar *sidebar)
+{
+ GtkTreeIter iter;
+ GDrive *drive;
+
+ if (!get_selected_iter (sidebar, &iter))
+ {
+ return;
+ }
+
+ gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), &iter,
+ PLACES_SIDEBAR_COLUMN_DRIVE, &drive,
+ -1);
+
+ if (drive != NULL)
+ {
+ g_drive_poll_for_media (drive, NULL, drive_poll_for_media_cb, NULL);
+ }
+ g_object_unref (drive);
+}
+
+static void
+format_shortcut_cb (GtkMenuItem *item,
+ CajaPlacesSidebar *sidebar)
+{
+ g_spawn_command_line_async ("gfloppy", NULL);
+}
+
+static void
+drive_start_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GError *error;
+ char *primary;
+ char *name;
+
+ error = NULL;
+ if (!g_drive_poll_for_media_finish (G_DRIVE (source_object), res, &error))
+ {
+ if (error->code != G_IO_ERROR_FAILED_HANDLED)
+ {
+ name = g_drive_get_name (G_DRIVE (source_object));
+ primary = g_strdup_printf (_("Unable to start %s"), name);
+ g_free (name);
+ eel_show_error_dialog (primary,
+ error->message,
+ NULL);
+ g_free (primary);
+ }
+ g_error_free (error);
+ }
+}
+
+static void
+start_shortcut_cb (GtkMenuItem *item,
+ CajaPlacesSidebar *sidebar)
+{
+ GtkTreeIter iter;
+ GDrive *drive;
+
+ if (!get_selected_iter (sidebar, &iter))
+ {
+ return;
+ }
+
+ gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), &iter,
+ PLACES_SIDEBAR_COLUMN_DRIVE, &drive,
+ -1);
+
+ if (drive != NULL)
+ {
+ GMountOperation *mount_op;
+
+ mount_op = gtk_mount_operation_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (sidebar))));
+
+ g_drive_start (drive, G_DRIVE_START_NONE, mount_op, NULL, drive_start_cb, NULL);
+
+ g_object_unref (mount_op);
+ }
+ g_object_unref (drive);
+}
+
+static void
+drive_stop_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ CajaWindow *window;
+ GError *error;
+ char *primary;
+ char *name;
+
+ window = user_data;
+ caja_window_info_set_initiated_unmount (window, FALSE);
+ g_object_unref (window);
+
+ error = NULL;
+ if (!g_drive_poll_for_media_finish (G_DRIVE (source_object), res, &error))
+ {
+ if (error->code != G_IO_ERROR_FAILED_HANDLED)
+ {
+ name = g_drive_get_name (G_DRIVE (source_object));
+ primary = g_strdup_printf (_("Unable to stop %s"), name);
+ g_free (name);
+ eel_show_error_dialog (primary,
+ error->message,
+ NULL);
+ g_free (primary);
+ }
+ g_error_free (error);
+ }
+}
+
+static void
+stop_shortcut_cb (GtkMenuItem *item,
+ CajaPlacesSidebar *sidebar)
+{
+ GtkTreeIter iter;
+ GDrive *drive;
+
+ if (!get_selected_iter (sidebar, &iter))
+ {
+ return;
+ }
+
+ gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), &iter,
+ PLACES_SIDEBAR_COLUMN_DRIVE, &drive,
+ -1);
+
+ if (drive != NULL)
+ {
+ GMountOperation *mount_op;
+
+ mount_op = gtk_mount_operation_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (sidebar))));
+ caja_window_info_set_initiated_unmount (sidebar->window, TRUE);
+ g_drive_stop (drive, G_MOUNT_UNMOUNT_NONE, mount_op, NULL, drive_stop_cb,
+ g_object_ref (sidebar->window));
+ g_object_unref (mount_op);
+ }
+ g_object_unref (drive);
+}
+
+static void
+empty_trash_cb (GtkMenuItem *item,
+ CajaPlacesSidebar *sidebar)
+{
+ caja_file_operations_empty_trash (GTK_WIDGET (sidebar->window));
+}
+
+/* Handler for GtkWidget::key-press-event on the shortcuts list */
+static gboolean
+bookmarks_key_press_event_cb (GtkWidget *widget,
+ GdkEventKey *event,
+ CajaPlacesSidebar *sidebar)
+{
+ guint modifiers;
+
+ modifiers = gtk_accelerator_get_default_mod_mask ();
+
+ if (event->keyval == GDK_Down &&
+ (event->state & modifiers) == GDK_MOD1_MASK)
+ {
+ return eject_or_unmount_selection (sidebar);
+ }
+
+ if ((event->keyval == GDK_Delete
+ || event->keyval == GDK_KP_Delete)
+ && (event->state & modifiers) == 0)
+ {
+ remove_selected_bookmarks (sidebar);
+ return TRUE;
+ }
+
+ if ((event->keyval == GDK_F2)
+ && (event->state & modifiers) == 0)
+ {
+ rename_selected_bookmark (sidebar);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/* Constructs the popup menu for the file list if needed */
+static void
+bookmarks_build_popup_menu (CajaPlacesSidebar *sidebar)
+{
+ GtkWidget *item;
+
+ if (sidebar->popup_menu)
+ {
+ return;
+ }
+
+ sidebar->popup_menu = gtk_menu_new ();
+ gtk_menu_attach_to_widget (GTK_MENU (sidebar->popup_menu),
+ GTK_WIDGET (sidebar),
+ bookmarks_popup_menu_detach_cb);
+
+ item = gtk_image_menu_item_new_with_mnemonic (_("_Open"));
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item),
+ gtk_image_new_from_stock (GTK_STOCK_OPEN, GTK_ICON_SIZE_MENU));
+ g_signal_connect (item, "activate",
+ G_CALLBACK (open_shortcut_cb), sidebar);
+ gtk_widget_show (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item);
+
+ item = gtk_menu_item_new_with_mnemonic (_("Open in New _Tab"));
+ sidebar->popup_menu_open_in_new_tab_item = item;
+ g_signal_connect (item, "activate",
+ G_CALLBACK (open_shortcut_in_new_tab_cb), sidebar);
+ gtk_widget_show (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item);
+
+ item = gtk_menu_item_new_with_mnemonic (_("Open in New _Window"));
+ g_signal_connect (item, "activate",
+ G_CALLBACK (open_shortcut_in_new_window_cb), sidebar);
+ gtk_widget_show (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item);
+
+ eel_gtk_menu_append_separator (GTK_MENU (sidebar->popup_menu));
+
+ item = gtk_image_menu_item_new_with_label (_("Remove"));
+ sidebar->popup_menu_remove_item = item;
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item),
+ gtk_image_new_from_stock (GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU));
+ g_signal_connect (item, "activate",
+ G_CALLBACK (remove_shortcut_cb), sidebar);
+ gtk_widget_show (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item);
+
+ item = gtk_menu_item_new_with_label (_("Rename..."));
+ sidebar->popup_menu_rename_item = item;
+ g_signal_connect (item, "activate",
+ G_CALLBACK (rename_shortcut_cb), sidebar);
+ gtk_widget_show (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item);
+
+ /* Mount/Unmount/Eject menu items */
+
+ sidebar->popup_menu_separator_item =
+ GTK_WIDGET (eel_gtk_menu_append_separator (GTK_MENU (sidebar->popup_menu)));
+
+ item = gtk_menu_item_new_with_mnemonic (_("_Mount"));
+ sidebar->popup_menu_mount_item = item;
+ g_signal_connect (item, "activate",
+ G_CALLBACK (mount_shortcut_cb), sidebar);
+ gtk_widget_show (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item);
+
+ item = gtk_menu_item_new_with_mnemonic (_("_Unmount"));
+ sidebar->popup_menu_unmount_item = item;
+ g_signal_connect (item, "activate",
+ G_CALLBACK (unmount_shortcut_cb), sidebar);
+ gtk_widget_show (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item);
+
+ item = gtk_menu_item_new_with_mnemonic (_("_Eject"));
+ sidebar->popup_menu_eject_item = item;
+ g_signal_connect (item, "activate",
+ G_CALLBACK (eject_shortcut_cb), sidebar);
+ gtk_widget_show (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item);
+
+ item = gtk_menu_item_new_with_mnemonic (_("_Detect Media"));
+ sidebar->popup_menu_rescan_item = item;
+ g_signal_connect (item, "activate",
+ G_CALLBACK (rescan_shortcut_cb), sidebar);
+ gtk_widget_show (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item);
+
+ item = gtk_menu_item_new_with_mnemonic (_("_Format"));
+ sidebar->popup_menu_format_item = item;
+ g_signal_connect (item, "activate",
+ G_CALLBACK (format_shortcut_cb), sidebar);
+ gtk_widget_show (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item);
+
+ item = gtk_menu_item_new_with_mnemonic (_("_Start"));
+ sidebar->popup_menu_start_item = item;
+ g_signal_connect (item, "activate",
+ G_CALLBACK (start_shortcut_cb), sidebar);
+ gtk_widget_show (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item);
+
+ item = gtk_menu_item_new_with_mnemonic (_("_Stop"));
+ sidebar->popup_menu_stop_item = item;
+ g_signal_connect (item, "activate",
+ G_CALLBACK (stop_shortcut_cb), sidebar);
+ gtk_widget_show (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item);
+
+ /* Empty Trash menu item */
+
+ item = gtk_menu_item_new_with_mnemonic (_("Empty _Trash"));
+ sidebar->popup_menu_empty_trash_item = item;
+ g_signal_connect (item, "activate",
+ G_CALLBACK (empty_trash_cb), sidebar);
+ gtk_widget_show (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item);
+
+ bookmarks_check_popup_sensitivity (sidebar);
+}
+
+static void
+bookmarks_update_popup_menu (CajaPlacesSidebar *sidebar)
+{
+ bookmarks_build_popup_menu (sidebar);
+}
+
+static void
+bookmarks_popup_menu (CajaPlacesSidebar *sidebar,
+ GdkEventButton *event)
+{
+ bookmarks_update_popup_menu (sidebar);
+ eel_pop_up_context_menu (GTK_MENU(sidebar->popup_menu),
+ EEL_DEFAULT_POPUP_MENU_DISPLACEMENT,
+ EEL_DEFAULT_POPUP_MENU_DISPLACEMENT,
+ event);
+}
+
+/* Callback used for the GtkWidget::popup-menu signal of the shortcuts list */
+static gboolean
+bookmarks_popup_menu_cb (GtkWidget *widget,
+ CajaPlacesSidebar *sidebar)
+{
+ bookmarks_popup_menu (sidebar, NULL);
+ return TRUE;
+}
+
+static gboolean
+bookmarks_button_release_event_cb (GtkWidget *widget,
+ GdkEventButton *event,
+ CajaPlacesSidebar *sidebar)
+{
+ GtkTreePath *path;
+ GtkTreeModel *model;
+ GtkTreeView *tree_view;
+ gboolean ret;
+
+ if (event->type != GDK_BUTTON_RELEASE)
+ {
+ return TRUE;
+ }
+
+ if (clicked_eject_button (sidebar, &path))
+ {
+ ret = eject_or_unmount_bookmark (sidebar, path);
+ gtk_tree_path_free (path);
+ return ret;
+ }
+
+ tree_view = GTK_TREE_VIEW (widget);
+ model = gtk_tree_view_get_model (tree_view);
+
+ if (event->button == 1)
+ {
+
+ if (event->window != gtk_tree_view_get_bin_window (tree_view))
+ {
+ return FALSE;
+ }
+
+ gtk_tree_view_get_path_at_pos (tree_view, (int) event->x, (int) event->y,
+ &path, NULL, NULL, NULL);
+
+ open_selected_bookmark (sidebar, model, path, 0);
+
+ gtk_tree_path_free (path);
+ }
+
+ return FALSE;
+}
+
+/* Callback used when a button is pressed on the shortcuts list.
+ * We trap button 3 to bring up a popup menu, and button 2 to
+ * open in a new tab.
+ */
+static gboolean
+bookmarks_button_press_event_cb (GtkWidget *widget,
+ GdkEventButton *event,
+ CajaPlacesSidebar *sidebar)
+{
+ if (event->type != GDK_BUTTON_PRESS)
+ {
+ /* ignore multiple clicks */
+ return TRUE;
+ }
+
+ if (event->button == 3)
+ {
+ bookmarks_popup_menu (sidebar, event);
+ }
+ else if (event->button == 2)
+ {
+ GtkTreeModel *model;
+ GtkTreePath *path;
+ GtkTreeView *tree_view;
+
+ tree_view = GTK_TREE_VIEW (widget);
+ g_assert (tree_view == sidebar->tree_view);
+
+ model = gtk_tree_view_get_model (tree_view);
+
+ gtk_tree_view_get_path_at_pos (tree_view, (int) event->x, (int) event->y,
+ &path, NULL, NULL, NULL);
+
+ open_selected_bookmark (sidebar, model, path,
+ event->state & GDK_CONTROL_MASK ?
+ CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW :
+ CAJA_WINDOW_OPEN_FLAG_NEW_TAB);
+
+ if (path != NULL)
+ {
+ gtk_tree_path_free (path);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+
+static void
+bookmarks_edited (GtkCellRenderer *cell,
+ gchar *path_string,
+ gchar *new_text,
+ CajaPlacesSidebar *sidebar)
+{
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ CajaBookmark *bookmark;
+ int index;
+
+ g_object_set (cell, "editable", FALSE, NULL);
+
+ path = gtk_tree_path_new_from_string (path_string);
+ gtk_tree_model_get_iter (GTK_TREE_MODEL (sidebar->store), &iter, path);
+ gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), &iter,
+ PLACES_SIDEBAR_COLUMN_INDEX, &index,
+ -1);
+ gtk_tree_path_free (path);
+ bookmark = caja_bookmark_list_item_at (sidebar->bookmarks, index);
+
+ if (bookmark != NULL)
+ {
+ caja_bookmark_set_name (bookmark, new_text);
+ }
+}
+
+static void
+bookmarks_editing_canceled (GtkCellRenderer *cell,
+ CajaPlacesSidebar *sidebar)
+{
+ g_object_set (cell, "editable", FALSE, NULL);
+}
+
+static void
+trash_state_changed_cb (CajaTrashMonitor *trash_monitor,
+ gboolean state,
+ gpointer data)
+{
+ CajaPlacesSidebar *sidebar;
+
+ sidebar = CAJA_PLACES_SIDEBAR (data);
+
+ /* The trash icon changed, update the sidebar */
+ update_places (sidebar);
+
+ bookmarks_check_popup_sensitivity (sidebar);
+}
+
+static void
+caja_places_sidebar_init (CajaPlacesSidebar *sidebar)
+{
+ GtkTreeView *tree_view;
+ GtkTreeViewColumn *col;
+ GtkCellRenderer *cell;
+ GtkTreeSelection *selection;
+
+ sidebar->volume_monitor = g_volume_monitor_get ();
+
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sidebar),
+ GTK_POLICY_NEVER,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_hadjustment (GTK_SCROLLED_WINDOW (sidebar), NULL);
+ gtk_scrolled_window_set_vadjustment (GTK_SCROLLED_WINDOW (sidebar), NULL);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sidebar), GTK_SHADOW_IN);
+
+ /* tree view */
+ tree_view = GTK_TREE_VIEW (gtk_tree_view_new ());
+ gtk_tree_view_set_headers_visible (tree_view, FALSE);
+
+ col = GTK_TREE_VIEW_COLUMN (gtk_tree_view_column_new ());
+
+ cell = gtk_cell_renderer_pixbuf_new ();
+ sidebar->icon_cell_renderer = cell;
+ gtk_tree_view_column_pack_start (col, cell, FALSE);
+ gtk_tree_view_column_set_attributes (col, cell,
+ "pixbuf", PLACES_SIDEBAR_COLUMN_ICON,
+ NULL);
+
+
+ cell = gtk_cell_renderer_text_new ();
+ sidebar->eject_text_cell_renderer = cell;
+ gtk_tree_view_column_pack_start (col, cell, TRUE);
+ gtk_tree_view_column_set_attributes (col, cell,
+ "text", PLACES_SIDEBAR_COLUMN_NAME,
+ "visible", PLACES_SIDEBAR_COLUMN_EJECT,
+ NULL);
+ g_object_set (cell,
+ "ellipsize", PANGO_ELLIPSIZE_END,
+ "ellipsize-set", TRUE,
+ NULL);
+
+
+ cell = gtk_cell_renderer_pixbuf_new ();
+ g_object_set (cell,
+ "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE,
+ "icon-name", "media-eject",
+ "stock-size", GTK_ICON_SIZE_MENU,
+ "xpad", EJECT_BUTTON_XPAD,
+ NULL);
+ gtk_tree_view_column_pack_start (col, cell, FALSE);
+ gtk_tree_view_column_set_attributes (col, cell,
+ "visible", PLACES_SIDEBAR_COLUMN_EJECT,
+ NULL);
+
+ cell = gtk_cell_renderer_text_new ();
+ gtk_tree_view_column_pack_start (col, cell, TRUE);
+ g_object_set (G_OBJECT (cell), "editable", FALSE, NULL);
+ gtk_tree_view_column_set_attributes (col, cell,
+ "text", PLACES_SIDEBAR_COLUMN_NAME,
+ "visible", PLACES_SIDEBAR_COLUMN_NO_EJECT,
+ "editable-set", PLACES_SIDEBAR_COLUMN_BOOKMARK,
+ NULL);
+ g_object_set (cell,
+ "ellipsize", PANGO_ELLIPSIZE_END,
+ "ellipsize-set", TRUE,
+ NULL);
+
+ g_signal_connect (cell, "edited",
+ G_CALLBACK (bookmarks_edited), sidebar);
+ g_signal_connect (cell, "editing-canceled",
+ G_CALLBACK (bookmarks_editing_canceled), sidebar);
+
+ gtk_tree_view_set_row_separator_func (tree_view,
+ caja_shortcuts_row_separator_func,
+ NULL,
+ NULL);
+
+ /* this is required to align the eject buttons to the right */
+ gtk_tree_view_column_set_max_width (GTK_TREE_VIEW_COLUMN (col), CAJA_ICON_SIZE_SMALLER);
+ gtk_tree_view_append_column (tree_view, col);
+
+ sidebar->store = gtk_list_store_new (PLACES_SIDEBAR_COLUMN_COUNT,
+ G_TYPE_INT,
+ G_TYPE_STRING,
+ G_TYPE_DRIVE,
+ G_TYPE_VOLUME,
+ G_TYPE_MOUNT,
+ G_TYPE_STRING,
+ GDK_TYPE_PIXBUF,
+ G_TYPE_INT,
+ G_TYPE_BOOLEAN,
+ G_TYPE_BOOLEAN,
+ G_TYPE_BOOLEAN,
+ G_TYPE_BOOLEAN,
+ G_TYPE_STRING
+ );
+
+ gtk_tree_view_set_tooltip_column (tree_view, PLACES_SIDEBAR_COLUMN_TOOLTIP);
+
+ sidebar->filter_model = caja_shortcuts_model_filter_new (sidebar,
+ GTK_TREE_MODEL (sidebar->store),
+ NULL);
+
+ gtk_tree_view_set_model (tree_view, sidebar->filter_model);
+ gtk_container_add (GTK_CONTAINER (sidebar), GTK_WIDGET (tree_view));
+ gtk_widget_show (GTK_WIDGET (tree_view));
+
+ gtk_widget_show (GTK_WIDGET (sidebar));
+ sidebar->tree_view = tree_view;
+
+ gtk_tree_view_set_search_column (tree_view, PLACES_SIDEBAR_COLUMN_NAME);
+ selection = gtk_tree_view_get_selection (tree_view);
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
+
+ gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW (tree_view),
+ GDK_BUTTON1_MASK,
+ caja_shortcuts_source_targets,
+ G_N_ELEMENTS (caja_shortcuts_source_targets),
+ GDK_ACTION_MOVE);
+ gtk_drag_dest_set (GTK_WIDGET (tree_view),
+ 0,
+ caja_shortcuts_drop_targets, G_N_ELEMENTS (caja_shortcuts_drop_targets),
+ GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK);
+
+ g_signal_connect (tree_view, "key-press-event",
+ G_CALLBACK (bookmarks_key_press_event_cb), sidebar);
+
+ g_signal_connect (tree_view, "drag-motion",
+ G_CALLBACK (drag_motion_callback), sidebar);
+ g_signal_connect (tree_view, "drag-leave",
+ G_CALLBACK (drag_leave_callback), sidebar);
+ g_signal_connect (tree_view, "drag-data-received",
+ G_CALLBACK (drag_data_received_callback), sidebar);
+ g_signal_connect (tree_view, "drag-drop",
+ G_CALLBACK (drag_drop_callback), sidebar);
+ g_signal_connect (selection, "changed",
+ G_CALLBACK (bookmarks_selection_changed_cb), sidebar);
+ g_signal_connect (tree_view, "popup-menu",
+ G_CALLBACK (bookmarks_popup_menu_cb), sidebar);
+ g_signal_connect (tree_view, "button-press-event",
+ G_CALLBACK (bookmarks_button_press_event_cb), sidebar);
+ g_signal_connect (tree_view, "button-release-event",
+ G_CALLBACK (bookmarks_button_release_event_cb), sidebar);
+
+ eel_gtk_tree_view_set_activate_on_single_click (sidebar->tree_view,
+ TRUE);
+
+ eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_DESKTOP_IS_HOME_DIR,
+ desktop_location_changed_callback,
+ sidebar,
+ G_OBJECT (sidebar));
+
+ g_signal_connect_object (caja_trash_monitor_get (),
+ "trash_state_changed",
+ G_CALLBACK (trash_state_changed_cb),
+ sidebar, 0);
+}
+
+static void
+caja_places_sidebar_dispose (GObject *object)
+{
+ CajaPlacesSidebar *sidebar;
+
+ sidebar = CAJA_PLACES_SIDEBAR (object);
+
+ sidebar->window = NULL;
+ sidebar->tree_view = NULL;
+
+ g_free (sidebar->uri);
+ sidebar->uri = NULL;
+
+ free_drag_data (sidebar);
+
+ if (sidebar->store != NULL)
+ {
+ g_object_unref (sidebar->store);
+ sidebar->store = NULL;
+ }
+
+ if (sidebar->volume_monitor != NULL)
+ {
+ g_object_unref (sidebar->volume_monitor);
+ sidebar->volume_monitor = NULL;
+ }
+
+ if (sidebar->bookmarks != NULL)
+ {
+ g_object_unref (sidebar->bookmarks);
+ sidebar->bookmarks = NULL;
+ }
+
+ eel_remove_weak_pointer (&(sidebar->go_to_after_mount_slot));
+
+ G_OBJECT_CLASS (caja_places_sidebar_parent_class)->dispose (object);
+}
+
+static void
+caja_places_sidebar_class_init (CajaPlacesSidebarClass *class)
+{
+ G_OBJECT_CLASS (class)->dispose = caja_places_sidebar_dispose;
+
+ GTK_WIDGET_CLASS (class)->style_set = caja_places_sidebar_style_set;
+}
+
+static const char *
+caja_places_sidebar_get_sidebar_id (CajaSidebar *sidebar)
+{
+ return CAJA_PLACES_SIDEBAR_ID;
+}
+
+static char *
+caja_places_sidebar_get_tab_label (CajaSidebar *sidebar)
+{
+ return g_strdup (_("Places"));
+}
+
+static char *
+caja_places_sidebar_get_tab_tooltip (CajaSidebar *sidebar)
+{
+ return g_strdup (_("Show Places"));
+}
+
+static GdkPixbuf *
+caja_places_sidebar_get_tab_icon (CajaSidebar *sidebar)
+{
+ return NULL;
+}
+
+static void
+caja_places_sidebar_is_visible_changed (CajaSidebar *sidebar,
+ gboolean is_visible)
+{
+ /* Do nothing */
+}
+
+static void
+caja_places_sidebar_iface_init (CajaSidebarIface *iface)
+{
+ iface->get_sidebar_id = caja_places_sidebar_get_sidebar_id;
+ iface->get_tab_label = caja_places_sidebar_get_tab_label;
+ iface->get_tab_tooltip = caja_places_sidebar_get_tab_tooltip;
+ iface->get_tab_icon = caja_places_sidebar_get_tab_icon;
+ iface->is_visible_changed = caja_places_sidebar_is_visible_changed;
+}
+
+static void
+caja_places_sidebar_set_parent_window (CajaPlacesSidebar *sidebar,
+ CajaWindowInfo *window)
+{
+ CajaWindowSlotInfo *slot;
+
+ sidebar->window = window;
+
+ slot = caja_window_info_get_active_slot (window);
+
+ sidebar->bookmarks = caja_bookmark_list_new ();
+ sidebar->uri = caja_window_slot_info_get_current_location (slot);
+
+ g_signal_connect_object (sidebar->bookmarks, "contents_changed",
+ G_CALLBACK (update_places),
+ sidebar, G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (window, "loading_uri",
+ G_CALLBACK (loading_uri_callback),
+ sidebar, 0);
+
+ g_signal_connect_object (sidebar->volume_monitor, "volume_added",
+ G_CALLBACK (volume_added_callback), sidebar, 0);
+ g_signal_connect_object (sidebar->volume_monitor, "volume_removed",
+ G_CALLBACK (volume_removed_callback), sidebar, 0);
+ g_signal_connect_object (sidebar->volume_monitor, "volume_changed",
+ G_CALLBACK (volume_changed_callback), sidebar, 0);
+ g_signal_connect_object (sidebar->volume_monitor, "mount_added",
+ G_CALLBACK (mount_added_callback), sidebar, 0);
+ g_signal_connect_object (sidebar->volume_monitor, "mount_removed",
+ G_CALLBACK (mount_removed_callback), sidebar, 0);
+ g_signal_connect_object (sidebar->volume_monitor, "mount_changed",
+ G_CALLBACK (mount_changed_callback), sidebar, 0);
+ g_signal_connect_object (sidebar->volume_monitor, "drive_disconnected",
+ G_CALLBACK (drive_disconnected_callback), sidebar, 0);
+ g_signal_connect_object (sidebar->volume_monitor, "drive_connected",
+ G_CALLBACK (drive_connected_callback), sidebar, 0);
+ g_signal_connect_object (sidebar->volume_monitor, "drive_changed",
+ G_CALLBACK (drive_changed_callback), sidebar, 0);
+
+ update_places (sidebar);
+}
+
+static void
+caja_places_sidebar_style_set (GtkWidget *widget,
+ GtkStyle *previous_style)
+{
+ CajaPlacesSidebar *sidebar;
+
+ sidebar = CAJA_PLACES_SIDEBAR (widget);
+
+ update_places (sidebar);
+}
+
+static CajaSidebar *
+caja_places_sidebar_create (CajaSidebarProvider *provider,
+ CajaWindowInfo *window)
+{
+ CajaPlacesSidebar *sidebar;
+
+ sidebar = g_object_new (caja_places_sidebar_get_type (), NULL);
+ caja_places_sidebar_set_parent_window (sidebar, window);
+ g_object_ref_sink (sidebar);
+
+ return CAJA_SIDEBAR (sidebar);
+}
+
+static void
+sidebar_provider_iface_init (CajaSidebarProviderIface *iface)
+{
+ iface->create = caja_places_sidebar_create;
+}
+
+static void
+caja_places_sidebar_provider_init (CajaPlacesSidebarProvider *sidebar)
+{
+}
+
+static void
+caja_places_sidebar_provider_class_init (CajaPlacesSidebarProviderClass *class)
+{
+}
+
+void
+caja_places_sidebar_register (void)
+{
+ caja_module_add_type (caja_places_sidebar_provider_get_type ());
+}
+
+/* Drag and drop interfaces */
+
+static void
+_caja_shortcuts_model_filter_class_init (CajaShortcutsModelFilterClass *class)
+{
+}
+
+static void
+_caja_shortcuts_model_filter_init (CajaShortcutsModelFilter *model)
+{
+ model->sidebar = NULL;
+}
+
+/* GtkTreeDragSource::row_draggable implementation for the shortcuts filter model */
+static gboolean
+caja_shortcuts_model_filter_row_draggable (GtkTreeDragSource *drag_source,
+ GtkTreePath *path)
+{
+ CajaShortcutsModelFilter *model;
+ int pos;
+ int bookmarks_pos;
+ int num_bookmarks;
+
+ model = CAJA_SHORTCUTS_MODEL_FILTER (drag_source);
+
+ pos = *gtk_tree_path_get_indices (path);
+ bookmarks_pos = get_bookmark_index (model->sidebar->tree_view);
+ num_bookmarks = caja_bookmark_list_length (model->sidebar->bookmarks);
+
+ return (pos >= bookmarks_pos && pos < bookmarks_pos + num_bookmarks);
+}
+
+/* GtkTreeDragSource::drag_data_get implementation for the shortcuts filter model */
+static gboolean
+caja_shortcuts_model_filter_drag_data_get (GtkTreeDragSource *drag_source,
+ GtkTreePath *path,
+ GtkSelectionData *selection_data)
+{
+ CajaShortcutsModelFilter *model;
+
+ model = CAJA_SHORTCUTS_MODEL_FILTER (drag_source);
+
+ /* FIXME */
+
+ return FALSE;
+}
+
+/* Fill the GtkTreeDragSourceIface vtable */
+static void
+caja_shortcuts_model_filter_drag_source_iface_init (GtkTreeDragSourceIface *iface)
+{
+ iface->row_draggable = caja_shortcuts_model_filter_row_draggable;
+ iface->drag_data_get = caja_shortcuts_model_filter_drag_data_get;
+}
+
+static GtkTreeModel *
+caja_shortcuts_model_filter_new (CajaPlacesSidebar *sidebar,
+ GtkTreeModel *child_model,
+ GtkTreePath *root)
+{
+ CajaShortcutsModelFilter *model;
+
+ model = g_object_new (CAJA_SHORTCUTS_MODEL_FILTER_TYPE,
+ "child-model", child_model,
+ "virtual-root", root,
+ NULL);
+
+ model->sidebar = sidebar;
+
+ return GTK_TREE_MODEL (model);
+}
diff --git a/src/caja-places-sidebar.h b/src/caja-places-sidebar.h
new file mode 100644
index 00000000..6faef394
--- /dev/null
+++ b/src/caja-places-sidebar.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ *
+ * 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
+ * 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.
+ *
+ * Author : Mr Jamie McCracken (jamiemcc at blueyonder dot co dot uk)
+ *
+ */
+#ifndef _CAJA_PLACES_SIDEBAR_H
+#define _CAJA_PLACES_SIDEBAR_H
+
+#include <libcaja-private/caja-view.h>
+#include <libcaja-private/caja-window-info.h>
+#include <gtk/gtk.h>
+
+#define CAJA_PLACES_SIDEBAR_ID "CajaPlacesSidebar"
+
+#define CAJA_TYPE_PLACES_SIDEBAR caja_places_sidebar_get_type()
+#define CAJA_PLACES_SIDEBAR(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_PLACES_SIDEBAR, CajaPlacesSidebar))
+#define CAJA_PLACES_SIDEBAR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_PLACES_SIDEBAR, CajaPlacesSidebarClass))
+#define CAJA_IS_PLACES_SIDEBAR(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_PLACES_SIDEBAR))
+#define CAJA_IS_PLACES_SIDEBAR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_PLACES_SIDEBAR))
+#define CAJA_PLACES_SIDEBAR_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_PLACES_SIDEBAR, CajaPlacesSidebarClass))
+
+
+GType caja_places_sidebar_get_type (void);
+void caja_places_sidebar_register (void);
+
+#endif
diff --git a/src/caja-property-browser.c b/src/caja-property-browser.c
new file mode 100644
index 00000000..aa7b29f2
--- /dev/null
+++ b/src/caja-property-browser.c
@@ -0,0 +1,2492 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Author: Andy Hertzfeld <[email protected]>
+ */
+
+/* This is the implementation of the property browser window, which
+ * gives the user access to an extensible palette of properties which
+ * can be dropped on various elements of the user interface to
+ * customize them
+ */
+
+#include <config.h>
+#include <math.h>
+#include "caja-property-browser.h"
+
+#include <eel/eel-gdk-extensions.h>
+#include <eel/eel-gdk-pixbuf-extensions.h>
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-mate-extensions.h>
+#include <eel/eel-gtk-extensions.h>
+#include <eel/eel-gtk-macros.h>
+#include <eel/eel-image-table.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-xml-extensions.h>
+#include <libxml/parser.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gio/gio.h>
+#include <libcaja-private/caja-customization-data.h>
+#include <libcaja-private/caja-directory.h>
+#include <libcaja-private/caja-emblem-utils.h>
+#include <libcaja-private/caja-file-utilities.h>
+#include <libcaja-private/caja-file.h>
+#include <libcaja-private/caja-global-preferences.h>
+#include <libcaja-private/caja-metadata.h>
+#include <libcaja-private/caja-signaller.h>
+#include <atk/atkrelationset.h>
+
+/* property types */
+
+typedef enum
+{
+ CAJA_PROPERTY_NONE,
+ CAJA_PROPERTY_PATTERN,
+ CAJA_PROPERTY_COLOR,
+ CAJA_PROPERTY_EMBLEM
+} CajaPropertyType;
+
+struct CajaPropertyBrowserDetails
+{
+ GtkHBox *container;
+
+ GtkWidget *content_container;
+ GtkWidget *content_frame;
+ GtkWidget *content_table;
+
+ GtkWidget *category_container;
+ GtkWidget *category_box;
+
+ GtkWidget *title_box;
+ GtkWidget *title_label;
+ GtkWidget *help_label;
+
+ GtkWidget *bottom_box;
+
+ GtkWidget *add_button;
+ GtkWidget *add_button_image;
+ GtkWidget *remove_button;
+ GtkWidget *remove_button_image;
+
+ GtkWidget *patterns_dialog;
+ GtkWidget *colors_dialog;
+ GtkWidget *emblems_dialog;
+
+ GtkWidget *keyword;
+ GtkWidget *emblem_image;
+ GtkWidget *image_button;
+
+ GtkWidget *color_picker;
+ GtkWidget *color_name;
+
+ GList *keywords;
+
+ char *path;
+ char *category;
+ char *dragged_file;
+ char *drag_type;
+ char *image_path;
+ char *filename;
+
+ CajaPropertyType category_type;
+
+ int category_position;
+
+ GdkPixbuf *property_chit;
+
+ gboolean remove_mode;
+ gboolean keep_around;
+ gboolean has_local;
+};
+
+static void caja_property_browser_class_init (GtkObjectClass *object_klass);
+static void caja_property_browser_init (GtkObject *object);
+static void caja_property_browser_destroy (GtkObject *object);
+static void caja_property_browser_update_contents (CajaPropertyBrowser *property_browser);
+static void caja_property_browser_set_category (CajaPropertyBrowser *property_browser,
+ const char *new_category);
+static void caja_property_browser_set_dragged_file (CajaPropertyBrowser *property_browser,
+ const char *dragged_file_name);
+static void caja_property_browser_set_drag_type (CajaPropertyBrowser *property_browser,
+ const char *new_drag_type);
+static void add_new_button_callback (GtkWidget *widget,
+ CajaPropertyBrowser *property_browser);
+static void cancel_remove_mode (CajaPropertyBrowser *property_browser);
+static void done_button_callback (GtkWidget *widget,
+ GtkWidget *property_browser);
+static void help_button_callback (GtkWidget *widget,
+ GtkWidget *property_browser);
+static void remove_button_callback (GtkWidget *widget,
+ CajaPropertyBrowser *property_browser);
+static gboolean caja_property_browser_delete_event_callback (GtkWidget *widget,
+ GdkEvent *event,
+ gpointer user_data);
+static void caja_property_browser_hide_callback (GtkWidget *widget,
+ gpointer user_data);
+static void caja_property_browser_drag_end (GtkWidget *widget,
+ GdkDragContext *context);
+static void caja_property_browser_drag_begin (GtkWidget *widget,
+ GdkDragContext *context);
+static void caja_property_browser_drag_data_get (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint32 time);
+static void caja_property_browser_theme_changed (gpointer user_data);
+static void emit_emblems_changed_signal (void);
+static void emblems_changed_callback (GObject *signaller,
+ CajaPropertyBrowser *property_browser);
+
+/* misc utilities */
+static void element_clicked_callback (GtkWidget *image_table,
+ GtkWidget *child,
+ const EelImageTableEvent *event,
+ gpointer callback_data);
+
+static GdkPixbuf * make_drag_image (CajaPropertyBrowser *property_browser,
+ const char *file_name);
+static GdkPixbuf * make_color_drag_image (CajaPropertyBrowser *property_browser,
+ const char *color_spec,
+ gboolean trim_edges);
+
+
+#define BROWSER_CATEGORIES_FILE_NAME "browser.xml"
+
+#define PROPERTY_BROWSER_WIDTH 540
+#define PROPERTY_BROWSER_HEIGHT 340
+#define MAX_EMBLEM_HEIGHT 52
+#define STANDARD_BUTTON_IMAGE_HEIGHT 42
+
+#define MAX_ICON_WIDTH 63
+#define MAX_ICON_HEIGHT 63
+#define COLOR_SQUARE_SIZE 48
+
+#define LABELED_IMAGE_SPACING 2
+#define IMAGE_TABLE_X_SPACING 6
+#define IMAGE_TABLE_Y_SPACING 4
+
+#define ERASE_OBJECT_NAME "erase.png"
+
+enum
+{
+ PROPERTY_TYPE
+};
+
+static GtkTargetEntry drag_types[] =
+{
+ { "text/uri-list", 0, PROPERTY_TYPE }
+};
+
+
+EEL_CLASS_BOILERPLATE (CajaPropertyBrowser,
+ caja_property_browser,
+ GTK_TYPE_WINDOW)
+
+/* initializing the class object by installing the operations we override */
+static void
+caja_property_browser_class_init (GtkObjectClass *object_klass)
+{
+ CajaPropertyBrowserClass *klass;
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (object_klass);
+
+ klass = CAJA_PROPERTY_BROWSER_CLASS (object_klass);
+
+ object_klass->destroy = caja_property_browser_destroy;
+ widget_class->drag_begin = caja_property_browser_drag_begin;
+ widget_class->drag_data_get = caja_property_browser_drag_data_get;
+ widget_class->drag_end = caja_property_browser_drag_end;
+}
+
+/* initialize the instance's fields, create the necessary subviews, etc. */
+
+static void
+caja_property_browser_init (GtkObject *object)
+{
+ CajaPropertyBrowser *property_browser;
+ GtkWidget *widget, *temp_box, *temp_hbox, *temp_frame, *vbox;
+ GtkWidget *temp_button;
+ GtkWidget *viewport;
+ char *temp_str;
+
+ property_browser = CAJA_PROPERTY_BROWSER (object);
+ widget = GTK_WIDGET (object);
+
+ property_browser->details = g_new0 (CajaPropertyBrowserDetails, 1);
+
+ property_browser->details->category = g_strdup ("patterns");
+ property_browser->details->category_type = CAJA_PROPERTY_PATTERN;
+
+ /* load the chit frame */
+ temp_str = caja_pixmap_file ("chit_frame.png");
+ if (temp_str != NULL)
+ {
+ property_browser->details->property_chit = gdk_pixbuf_new_from_file (temp_str, NULL);
+ }
+ g_free (temp_str);
+
+ /* set the initial size of the property browser */
+ gtk_window_set_default_size (GTK_WINDOW (property_browser),
+ PROPERTY_BROWSER_WIDTH,
+ PROPERTY_BROWSER_HEIGHT);
+
+ /* set the title and standard close accelerator */
+ gtk_window_set_title (GTK_WINDOW (widget), _("Backgrounds and Emblems"));
+ gtk_window_set_wmclass (GTK_WINDOW (widget), "property_browser", "Caja");
+ gtk_window_set_type_hint (GTK_WINDOW (widget), GDK_WINDOW_TYPE_HINT_DIALOG);
+ eel_gtk_window_set_up_close_accelerator (GTK_WINDOW (widget));
+
+ /* create the main vbox. */
+ vbox = gtk_vbox_new (FALSE, 12);
+ gtk_container_set_border_width (GTK_CONTAINER (vbox), 12);
+ gtk_widget_show (vbox);
+ gtk_container_add (GTK_CONTAINER (property_browser), vbox);
+
+ /* create the container box */
+ property_browser->details->container = GTK_HBOX (gtk_hbox_new (FALSE, 6));
+ gtk_widget_show (GTK_WIDGET (property_browser->details->container));
+ gtk_box_pack_start (GTK_BOX (vbox),
+ GTK_WIDGET (property_browser->details->container),
+ TRUE, TRUE, 0);
+
+ /* make the category container */
+ property_browser->details->category_container = gtk_scrolled_window_new (NULL, NULL);
+ property_browser->details->category_position = -1;
+
+ viewport = gtk_viewport_new (NULL, NULL);
+ gtk_widget_show (viewport);
+ gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport), GTK_SHADOW_NONE);
+
+ gtk_box_pack_start (GTK_BOX (property_browser->details->container),
+ property_browser->details->category_container, FALSE, FALSE, 0);
+ gtk_widget_show (property_browser->details->category_container);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (property_browser->details->category_container),
+ GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+
+ /* allocate a table to hold the category selector */
+ property_browser->details->category_box = gtk_vbox_new (FALSE, 6);
+ gtk_container_add(GTK_CONTAINER(viewport), property_browser->details->category_box);
+ gtk_container_add (GTK_CONTAINER (property_browser->details->category_container), viewport);
+ gtk_widget_show (GTK_WIDGET (property_browser->details->category_box));
+
+ /* make the content container vbox */
+ property_browser->details->content_container = gtk_vbox_new (FALSE, 6);
+ gtk_widget_show (property_browser->details->content_container);
+ gtk_box_pack_start (GTK_BOX (property_browser->details->container),
+ property_browser->details->content_container,
+ TRUE, TRUE, 0);
+
+ /* create the title box */
+ property_browser->details->title_box = gtk_event_box_new();
+
+ gtk_widget_show(property_browser->details->title_box);
+ gtk_box_pack_start (GTK_BOX(property_browser->details->content_container),
+ property_browser->details->title_box,
+ FALSE, FALSE, 0);
+
+ temp_frame = gtk_frame_new(NULL);
+ gtk_frame_set_shadow_type(GTK_FRAME(temp_frame), GTK_SHADOW_NONE);
+ gtk_widget_show(temp_frame);
+ gtk_container_add(GTK_CONTAINER(property_browser->details->title_box), temp_frame);
+
+ temp_hbox = gtk_hbox_new(FALSE, 0);
+ gtk_widget_show(temp_hbox);
+
+ gtk_container_add(GTK_CONTAINER(temp_frame), temp_hbox);
+
+ /* add the title label */
+ property_browser->details->title_label = gtk_label_new ("");
+ eel_gtk_label_set_scale (GTK_LABEL (property_browser->details->title_label), PANGO_SCALE_X_LARGE);
+ eel_gtk_label_make_bold (GTK_LABEL (property_browser->details->title_label));
+
+ gtk_widget_show(property_browser->details->title_label);
+ gtk_box_pack_start (GTK_BOX(temp_hbox), property_browser->details->title_label, FALSE, FALSE, 0);
+
+ /* add the help label */
+ property_browser->details->help_label = gtk_label_new ("");
+ gtk_widget_show(property_browser->details->help_label);
+ gtk_box_pack_end (GTK_BOX (temp_hbox), property_browser->details->help_label, FALSE, FALSE, 0);
+
+ /* add the bottom box to hold the command buttons */
+ temp_box = gtk_event_box_new();
+ gtk_widget_show(temp_box);
+
+ property_browser->details->bottom_box = gtk_hbox_new (FALSE, 6);
+ gtk_widget_show (property_browser->details->bottom_box);
+
+ gtk_box_pack_end (GTK_BOX (vbox), temp_box, FALSE, FALSE, 0);
+ gtk_container_add (GTK_CONTAINER (temp_box), property_browser->details->bottom_box);
+
+ /* create the "help" button */
+ temp_button = gtk_button_new_from_stock (GTK_STOCK_HELP);
+
+ gtk_widget_show (temp_button);
+ gtk_box_pack_start (GTK_BOX (property_browser->details->bottom_box), temp_button, FALSE, FALSE, 0);
+ g_signal_connect_object (temp_button, "clicked", G_CALLBACK (help_button_callback), property_browser, 0);
+
+ /* create the "done" button */
+ temp_button = gtk_button_new_from_stock (GTK_STOCK_CLOSE);
+ gtk_widget_set_can_default (temp_button, TRUE);
+
+ gtk_widget_show (temp_button);
+ gtk_box_pack_end (GTK_BOX (property_browser->details->bottom_box), temp_button, FALSE, FALSE, 0);
+ gtk_widget_grab_default (temp_button);
+ gtk_widget_grab_focus (temp_button);
+ g_signal_connect_object (temp_button, "clicked", G_CALLBACK (done_button_callback), property_browser, 0);
+
+ /* create the "remove" button */
+ property_browser->details->remove_button = gtk_button_new_with_mnemonic (_("_Remove..."));
+
+ property_browser->details->remove_button_image = gtk_image_new_from_stock (GTK_STOCK_REMOVE, GTK_ICON_SIZE_BUTTON);
+ gtk_button_set_image (GTK_BUTTON (property_browser->details->remove_button),
+ property_browser->details->remove_button_image);
+ gtk_widget_show_all (property_browser->details->remove_button);
+
+ gtk_box_pack_end (GTK_BOX (property_browser->details->bottom_box),
+ property_browser->details->remove_button, FALSE, FALSE, 0);
+
+ g_signal_connect_object (property_browser->details->remove_button, "clicked",
+ G_CALLBACK (remove_button_callback), property_browser, 0);
+
+ /* now create the "add new" button */
+ property_browser->details->add_button = gtk_button_new_with_mnemonic (_("Add new..."));
+
+ property_browser->details->add_button_image = gtk_image_new_from_stock (GTK_STOCK_ADD, GTK_ICON_SIZE_BUTTON);
+ gtk_button_set_image (GTK_BUTTON (property_browser->details->add_button),
+ property_browser->details->add_button_image);
+ gtk_widget_show_all (property_browser->details->add_button);
+
+ gtk_box_pack_end (GTK_BOX(property_browser->details->bottom_box),
+ property_browser->details->add_button, FALSE, FALSE, 0);
+
+ g_signal_connect_object (property_browser->details->add_button, "clicked",
+ G_CALLBACK (add_new_button_callback), property_browser, 0);
+
+
+ /* now create the actual content, with the category pane and the content frame */
+
+ /* the actual contents are created when necessary */
+ property_browser->details->content_frame = NULL;
+
+ /* add a callback for when the theme changes */
+ eel_preferences_add_callback (CAJA_PREFERENCES_THEME,
+ caja_property_browser_theme_changed,
+ property_browser);
+
+ g_signal_connect (property_browser, "delete_event",
+ G_CALLBACK (caja_property_browser_delete_event_callback), NULL);
+ g_signal_connect (property_browser, "hide",
+ G_CALLBACK (caja_property_browser_hide_callback), NULL);
+
+ g_signal_connect_object (caja_signaller_get_current (),
+ "emblems_changed",
+ G_CALLBACK (emblems_changed_callback), property_browser, 0);
+
+ /* initially, display the top level */
+ caja_property_browser_set_path(property_browser, BROWSER_CATEGORIES_FILE_NAME);
+}
+
+/* Destroy the three dialogs for adding patterns/colors/emblems if any of them
+ exist. */
+static void
+caja_property_browser_destroy_dialogs (CajaPropertyBrowser *property_browser)
+{
+ if (property_browser->details->patterns_dialog)
+ {
+ gtk_widget_destroy (property_browser->details->patterns_dialog);
+ property_browser->details->patterns_dialog = NULL;
+ }
+ if (property_browser->details->colors_dialog)
+ {
+ gtk_widget_destroy (property_browser->details->colors_dialog);
+ property_browser->details->colors_dialog = NULL;
+ }
+ if (property_browser->details->emblems_dialog)
+ {
+ gtk_widget_destroy (property_browser->details->emblems_dialog);
+ property_browser->details->emblems_dialog = NULL;
+ }
+}
+
+static void
+caja_property_browser_destroy (GtkObject *object)
+{
+ CajaPropertyBrowser *property_browser;
+
+
+ property_browser = CAJA_PROPERTY_BROWSER (object);
+
+ caja_property_browser_destroy_dialogs (property_browser);
+
+ g_free (property_browser->details->path);
+ g_free (property_browser->details->category);
+ g_free (property_browser->details->dragged_file);
+ g_free (property_browser->details->drag_type);
+
+ eel_g_list_free_deep (property_browser->details->keywords);
+
+ if (property_browser->details->property_chit)
+ {
+ g_object_unref (property_browser->details->property_chit);
+ }
+
+ g_free (property_browser->details);
+
+ eel_preferences_remove_callback (CAJA_PREFERENCES_THEME,
+ caja_property_browser_theme_changed,
+ property_browser);
+
+ EEL_CALL_PARENT (GTK_OBJECT_CLASS, destroy, (object));
+}
+
+/* create a new instance */
+CajaPropertyBrowser *
+caja_property_browser_new (GdkScreen *screen)
+{
+ CajaPropertyBrowser *browser;
+
+ browser = CAJA_PROPERTY_BROWSER
+ (gtk_widget_new (caja_property_browser_get_type (), NULL));
+
+ gtk_window_set_screen (GTK_WINDOW (browser), screen);
+ gtk_widget_show (GTK_WIDGET(browser));
+
+ return browser;
+}
+
+/* show the main property browser */
+
+void
+caja_property_browser_show (GdkScreen *screen)
+{
+ static GtkWindow *browser = NULL;
+
+ if (browser == NULL)
+ {
+ browser = GTK_WINDOW (caja_property_browser_new (screen));
+ g_object_add_weak_pointer (G_OBJECT (browser),
+ (gpointer *) &browser);
+ }
+ else
+ {
+ gtk_window_set_screen (browser, screen);
+ gtk_window_present (browser);
+ }
+}
+
+static gboolean
+caja_property_browser_delete_event_callback (GtkWidget *widget,
+ GdkEvent *event,
+ gpointer user_data)
+{
+ /* Hide but don't destroy */
+ gtk_widget_hide(widget);
+ return TRUE;
+}
+
+static void
+caja_property_browser_hide_callback (GtkWidget *widget,
+ gpointer user_data)
+{
+ CajaPropertyBrowser *property_browser;
+
+ property_browser = CAJA_PROPERTY_BROWSER (widget);
+
+ cancel_remove_mode (property_browser);
+
+ /* Destroy the 3 dialogs to add new patterns/colors/emblems. */
+ caja_property_browser_destroy_dialogs (property_browser);
+}
+
+/* remember the name of the dragged file */
+static void
+caja_property_browser_set_dragged_file (CajaPropertyBrowser *property_browser,
+ const char *dragged_file_name)
+{
+ g_free (property_browser->details->dragged_file);
+ property_browser->details->dragged_file = g_strdup (dragged_file_name);
+}
+
+/* remember the drag type */
+static void
+caja_property_browser_set_drag_type (CajaPropertyBrowser *property_browser,
+ const char *new_drag_type)
+{
+ g_free (property_browser->details->drag_type);
+ property_browser->details->drag_type = g_strdup (new_drag_type);
+}
+
+static void
+caja_property_browser_drag_begin (GtkWidget *widget,
+ GdkDragContext *context)
+{
+ CajaPropertyBrowser *property_browser;
+ GtkWidget *child;
+ GdkPixbuf *pixbuf;
+ int x_delta, y_delta;
+ char *element_name;
+
+ property_browser = CAJA_PROPERTY_BROWSER (widget);
+
+ child = g_object_steal_data (G_OBJECT (property_browser), "dragged-image");
+ g_return_if_fail (child != NULL);
+
+ element_name = g_object_get_data (G_OBJECT (child), "property-name");
+ g_return_if_fail (child != NULL);
+
+ /* compute the offsets for dragging */
+ if (strcmp (drag_types[0].target, "application/x-color") != 0)
+ {
+ /* it's not a color, so, for now, it must be an image */
+ /* fiddle with the category to handle the "reset" case properly */
+ char * save_category = property_browser->details->category;
+ if (eel_strcmp (property_browser->details->category, "colors") == 0)
+ {
+ property_browser->details->category = "patterns";
+ }
+ pixbuf = make_drag_image (property_browser, element_name);
+ property_browser->details->category = save_category;
+ }
+ else
+ {
+ pixbuf = make_color_drag_image (property_browser, element_name, TRUE);
+ }
+
+ /* set the pixmap and mask for dragging */
+ if (pixbuf != NULL)
+ {
+ x_delta = gdk_pixbuf_get_width (pixbuf) / 2;
+ y_delta = gdk_pixbuf_get_height (pixbuf) / 2;
+
+ gtk_drag_set_icon_pixbuf
+ (context,
+ pixbuf,
+ x_delta, y_delta);
+ g_object_unref (pixbuf);
+ }
+
+}
+
+
+/* drag and drop data get handler */
+
+static void
+caja_property_browser_drag_data_get (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint32 time)
+{
+ char *image_file_name, *image_file_uri;
+ gboolean is_reset;
+ CajaPropertyBrowser *property_browser = CAJA_PROPERTY_BROWSER(widget);
+ GdkAtom target;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (context != NULL);
+
+ target = gtk_selection_data_get_target (selection_data);
+
+ switch (info)
+ {
+ case PROPERTY_TYPE:
+ /* formulate the drag data based on the drag type. Eventually, we will
+ probably select the behavior from properties in the category xml definition,
+ but for now we hardwire it to the drag_type */
+
+ is_reset = FALSE;
+ if (strcmp (property_browser->details->drag_type,
+ "property/keyword") == 0)
+ {
+ char *keyword_str = eel_filename_strip_extension (property_browser->details->dragged_file);
+ gtk_selection_data_set (selection_data, target, 8, keyword_str, strlen (keyword_str));
+ g_free (keyword_str);
+ return;
+ }
+ else if (strcmp (property_browser->details->drag_type,
+ "application/x-color") == 0)
+ {
+ GdkColor color;
+ guint16 colorArray[4];
+
+ /* handle the "reset" case as an image */
+ if (eel_strcmp (property_browser->details->dragged_file, RESET_IMAGE_NAME) != 0)
+ {
+ gdk_color_parse (property_browser->details->dragged_file, &color);
+
+ colorArray[0] = color.red;
+ colorArray[1] = color.green;
+ colorArray[2] = color.blue;
+ colorArray[3] = 0xffff;
+
+ gtk_selection_data_set(selection_data,
+ target, 16, (const char *) &colorArray[0], 8);
+ return;
+ }
+ else
+ {
+ is_reset = TRUE;
+ }
+
+ }
+
+ image_file_name = g_strdup_printf ("%s/%s/%s",
+ CAJA_DATADIR,
+ is_reset ? "patterns" : property_browser->details->category,
+ property_browser->details->dragged_file);
+
+ if (!g_file_test (image_file_name, G_FILE_TEST_EXISTS))
+ {
+ char *user_directory;
+ g_free (image_file_name);
+
+ user_directory = caja_get_user_directory ();
+ image_file_name = g_strdup_printf ("%s/%s/%s",
+ user_directory,
+ property_browser->details->category,
+ property_browser->details->dragged_file);
+
+ g_free (user_directory);
+ }
+
+ image_file_uri = g_filename_to_uri (image_file_name, NULL, NULL);
+ gtk_selection_data_set (selection_data, target, 8, image_file_uri, strlen (image_file_uri));
+ g_free (image_file_name);
+ g_free (image_file_uri);
+
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+/* drag and drop end handler, where we destroy ourselves, since the transaction is complete */
+
+static void
+caja_property_browser_drag_end (GtkWidget *widget, GdkDragContext *context)
+{
+ CajaPropertyBrowser *property_browser = CAJA_PROPERTY_BROWSER(widget);
+ if (!property_browser->details->keep_around)
+ {
+ gtk_widget_hide (GTK_WIDGET (widget));
+ }
+}
+
+/* utility routine to check if the passed-in uri is an image file */
+static gboolean
+ensure_file_is_image (GFile *file)
+{
+ GFileInfo *info;
+ const char *mime_type;
+ gboolean ret;
+
+ info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0, NULL, NULL);
+ if (info == NULL)
+ {
+ return FALSE;
+ }
+
+ mime_type = g_file_info_get_content_type (info);
+ if (mime_type == NULL)
+ {
+ return FALSE;
+ }
+
+ ret = (g_content_type_is_a (mime_type, "image/*") &&
+ !g_content_type_equals (mime_type, "image/svg") &&
+ !g_content_type_equals (mime_type, "image/svg+xml"));
+
+ g_object_unref (info);
+
+ return ret;
+}
+
+/* create the appropriate pixbuf for the passed in file */
+
+static GdkPixbuf *
+make_drag_image (CajaPropertyBrowser *property_browser, const char* file_name)
+{
+ GdkPixbuf *pixbuf, *orig_pixbuf;
+ char *image_file_name;
+ char *icon_name;
+ gboolean is_reset;
+ CajaIconInfo *info;
+
+ if (property_browser->details->category_type == CAJA_PROPERTY_EMBLEM)
+ {
+ if (strcmp (file_name, "erase") == 0)
+ {
+ pixbuf = NULL;
+
+ image_file_name = caja_pixmap_file (ERASE_OBJECT_NAME);
+ if (image_file_name != NULL)
+ {
+ pixbuf = gdk_pixbuf_new_from_file (image_file_name, NULL);
+ }
+ g_free (image_file_name);
+ }
+ else
+ {
+ icon_name = caja_emblem_get_icon_name_from_keyword (file_name);
+ info = caja_icon_info_lookup_from_name (icon_name, CAJA_ICON_SIZE_STANDARD);
+ pixbuf = caja_icon_info_get_pixbuf_at_size (info, CAJA_ICON_SIZE_STANDARD);
+ g_object_unref (info);
+ g_free (icon_name);
+ }
+ return pixbuf;
+ }
+
+ image_file_name = g_strdup_printf ("%s/%s/%s",
+ CAJA_DATADIR,
+ property_browser->details->category,
+ file_name);
+
+ if (!g_file_test (image_file_name, G_FILE_TEST_EXISTS))
+ {
+ char *user_directory;
+ g_free (image_file_name);
+
+ user_directory = caja_get_user_directory ();
+
+ image_file_name = g_strdup_printf ("%s/%s/%s",
+ user_directory,
+ property_browser->details->category,
+ file_name);
+
+ g_free (user_directory);
+ }
+
+ orig_pixbuf = gdk_pixbuf_new_from_file_at_scale (image_file_name,
+ MAX_ICON_WIDTH, MAX_ICON_HEIGHT,
+ TRUE,
+ NULL);
+
+ g_free (image_file_name);
+
+ if (orig_pixbuf == NULL)
+ {
+ return NULL;
+ }
+
+ is_reset = eel_strcmp (file_name, RESET_IMAGE_NAME) == 0;
+
+ if (strcmp (property_browser->details->category, "patterns") == 0 &&
+ property_browser->details->property_chit != NULL)
+ {
+ pixbuf = caja_customization_make_pattern_chit (orig_pixbuf, property_browser->details->property_chit, TRUE, is_reset);
+ }
+ else
+ {
+ pixbuf = eel_gdk_pixbuf_scale_down_to_fit (orig_pixbuf, MAX_ICON_WIDTH, MAX_ICON_HEIGHT);
+ }
+
+ g_object_unref (orig_pixbuf);
+
+ return pixbuf;
+}
+
+
+/* create a pixbuf and fill it with a color */
+
+static GdkPixbuf*
+make_color_drag_image (CajaPropertyBrowser *property_browser, const char *color_spec, gboolean trim_edges)
+{
+ GdkPixbuf *color_square;
+ GdkPixbuf *ret;
+ int row, col, stride;
+ char *pixels, *row_pixels;
+ GdkColor color;
+
+ color_square = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, COLOR_SQUARE_SIZE, COLOR_SQUARE_SIZE);
+
+ gdk_color_parse (color_spec, &color);
+ color.red >>= 8;
+ color.green >>= 8;
+ color.blue >>= 8;
+
+ pixels = gdk_pixbuf_get_pixels (color_square);
+ stride = gdk_pixbuf_get_rowstride (color_square);
+
+ /* loop through and set each pixel */
+ for (row = 0; row < COLOR_SQUARE_SIZE; row++)
+ {
+ row_pixels = (pixels + (row * stride));
+ for (col = 0; col < COLOR_SQUARE_SIZE; col++)
+ {
+ *row_pixels++ = color.red;
+ *row_pixels++ = color.green;
+ *row_pixels++ = color.blue;
+ *row_pixels++ = 255;
+ }
+ }
+
+ g_assert (color_square != NULL);
+
+ if (property_browser->details->property_chit != NULL)
+ {
+ ret = caja_customization_make_pattern_chit (color_square,
+ property_browser->details->property_chit,
+ trim_edges, FALSE);
+ g_object_unref (color_square);
+ }
+ else
+ {
+ ret = color_square;
+ }
+
+ return ret;
+}
+
+/* this callback handles button presses on the category widget. It maintains the active state */
+
+static void
+category_toggled_callback (GtkWidget *widget, char *category_name)
+{
+ CajaPropertyBrowser *property_browser;
+
+ property_browser = CAJA_PROPERTY_BROWSER (g_object_get_data (G_OBJECT (widget), "user_data"));
+
+ /* exit remove mode when the user switches categories, since there might be nothing to remove
+ in the new category */
+ property_browser->details->remove_mode = FALSE;
+
+ caja_property_browser_set_category (property_browser, category_name);
+}
+
+static xmlDocPtr
+read_browser_xml (CajaPropertyBrowser *property_browser)
+{
+ char *path;
+ xmlDocPtr document;
+
+ path = caja_get_data_file_path (property_browser->details->path);
+ if (path == NULL)
+ {
+ return NULL;
+ }
+ document = xmlParseFile (path);
+ g_free (path);
+ return document;
+}
+
+static void
+write_browser_xml (CajaPropertyBrowser *property_browser,
+ xmlDocPtr document)
+{
+ char *user_directory, *path;
+
+ user_directory = caja_get_user_directory ();
+ path = g_build_filename (user_directory, property_browser->details->path, NULL);
+ g_free (user_directory);
+ xmlSaveFile (path, document);
+ g_free (path);
+}
+
+static xmlNodePtr
+get_color_category (xmlDocPtr document)
+{
+ return eel_xml_get_root_child_by_name_and_property (document, "category", "name", "colors");
+}
+
+/* routines to remove specific category types. First, handle colors */
+
+static void
+remove_color (CajaPropertyBrowser *property_browser, const char* color_name)
+{
+ /* load the local xml file to remove the color */
+ xmlDocPtr document;
+ xmlNodePtr cur_node, color_node;
+ gboolean match;
+ char *name;
+
+ document = read_browser_xml (property_browser);
+ if (document == NULL)
+ {
+ return;
+ }
+
+ /* find the colors category */
+ cur_node = get_color_category (document);
+ if (cur_node != NULL)
+ {
+ /* loop through the colors to find one that matches */
+ for (color_node = eel_xml_get_children (cur_node);
+ color_node != NULL;
+ color_node = color_node->next)
+ {
+
+ if (color_node->type != XML_ELEMENT_NODE)
+ {
+ continue;
+ }
+
+ name = xmlGetProp (color_node, "name");
+ match = name != NULL
+ && strcmp (name, color_name) == 0;
+ xmlFree (name);
+
+ if (match)
+ {
+ xmlUnlinkNode (color_node);
+ xmlFreeNode (color_node);
+ write_browser_xml (property_browser, document);
+ break;
+ }
+ }
+ }
+
+ xmlFreeDoc (document);
+}
+
+/* remove the pattern matching the passed in name */
+
+static void
+remove_pattern(CajaPropertyBrowser *property_browser, const char* pattern_name)
+{
+ char *pattern_path;
+ char *user_directory;
+
+ user_directory = caja_get_user_directory ();
+
+ /* build the pathname of the pattern */
+ pattern_path = g_build_filename (user_directory,
+ "patterns",
+ pattern_name,
+ NULL);
+ g_free (user_directory);
+
+ /* delete the pattern from the pattern directory */
+ if (g_unlink (pattern_path) != 0)
+ {
+ char *message = g_strdup_printf (_("Sorry, but pattern %s could not be deleted."), pattern_name);
+ char *detail = _("Check that you have permission to delete the pattern.");
+ eel_show_error_dialog (message, detail, GTK_WINDOW (property_browser));
+ g_free (message);
+ }
+
+ g_free (pattern_path);
+}
+
+/* remove the emblem matching the passed in name */
+
+static void
+remove_emblem (CajaPropertyBrowser *property_browser, const char* emblem_name)
+{
+ /* delete the emblem from the emblem directory */
+ if (caja_emblem_remove_emblem (emblem_name) == FALSE)
+ {
+ char *message = g_strdup_printf (_("Sorry, but emblem %s could not be deleted."), emblem_name);
+ char *detail = _("Check that you have permission to delete the emblem.");
+ eel_show_error_dialog (message, detail, GTK_WINDOW (property_browser));
+ g_free (message);
+ }
+ else
+ {
+ emit_emblems_changed_signal ();
+ }
+}
+
+/* handle removing the passed in element */
+
+static void
+caja_property_browser_remove_element (CajaPropertyBrowser *property_browser, EelLabeledImage *child)
+{
+ const char *element_name;
+ char *color_name;
+
+ element_name = g_object_get_data (G_OBJECT (child), "property-name");
+
+ /* lookup category and get mode, then case out and handle the modes */
+ switch (property_browser->details->category_type)
+ {
+ case CAJA_PROPERTY_PATTERN:
+ remove_pattern (property_browser, element_name);
+ break;
+ case CAJA_PROPERTY_COLOR:
+ color_name = eel_labeled_image_get_text (child);
+ remove_color (property_browser, color_name);
+ g_free (color_name);
+ break;
+ case CAJA_PROPERTY_EMBLEM:
+ remove_emblem (property_browser, element_name);
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+update_preview_cb (GtkFileChooser *fc,
+ GtkImage *preview)
+{
+ char *filename;
+ GdkPixbuf *pixbuf;
+
+ filename = gtk_file_chooser_get_preview_filename (fc);
+ if (filename)
+ {
+ pixbuf = gdk_pixbuf_new_from_file (filename, NULL);
+
+ gtk_file_chooser_set_preview_widget_active (fc, pixbuf != NULL);
+
+ if (pixbuf)
+ {
+ gtk_image_set_from_pixbuf (preview, pixbuf);
+ g_object_unref (pixbuf);
+ }
+
+ g_free (filename);
+ }
+}
+
+static void
+icon_button_clicked_cb (GtkButton *b,
+ CajaPropertyBrowser *browser)
+{
+ GtkWidget *dialog;
+ GtkFileFilter *filter;
+ GtkWidget *preview;
+ int res;
+
+ dialog = gtk_file_chooser_dialog_new (_("Select an Image File for the New Emblem"),
+ GTK_WINDOW (browser),
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
+ NULL);
+ gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog),
+ DATADIR "/pixmaps");
+ 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_file_chooser_set_preview_widget (GTK_FILE_CHOOSER (dialog),
+ preview);
+ g_signal_connect (dialog, "update-preview",
+ G_CALLBACK (update_preview_cb), preview);
+
+ res = gtk_dialog_run (GTK_DIALOG (dialog));
+
+ if (res == GTK_RESPONSE_ACCEPT)
+ {
+ /* update the image */
+ g_free (browser->details->filename);
+ browser->details->filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
+ gtk_image_set_from_file (GTK_IMAGE (browser->details->image_button), browser->details->filename);
+ }
+
+ gtk_widget_destroy (dialog);
+}
+
+/* here's where we create the emblem dialog */
+static GtkWidget*
+caja_emblem_dialog_new (CajaPropertyBrowser *property_browser)
+{
+ GtkWidget *widget;
+ GtkWidget *button;
+ GtkWidget *dialog;
+ GtkWidget *label;
+ GtkWidget *table = gtk_table_new(2, 2, FALSE);
+
+ dialog = gtk_dialog_new_with_buttons (_("Create a New Emblem"),
+ GTK_WINDOW (property_browser), 0,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OK, GTK_RESPONSE_OK,
+ NULL);
+
+ /* install the table in the dialog */
+ 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_widget_show (table);
+
+ gtk_window_set_resizable (GTK_WINDOW (dialog), TRUE);
+ 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_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE);
+ gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), table, TRUE, TRUE, 0);
+ gtk_dialog_set_default_response (GTK_DIALOG(dialog), GTK_RESPONSE_OK);
+
+ /* make the keyword label and field */
+
+ widget = gtk_label_new_with_mnemonic(_("_Keyword:"));
+ gtk_misc_set_alignment (GTK_MISC (widget), 0, 0.5);
+ gtk_widget_show(widget);
+ gtk_table_attach(GTK_TABLE(table), widget, 0, 1, 0, 1, GTK_FILL, GTK_FILL, 0, 0);
+
+ property_browser->details->keyword = gtk_entry_new ();
+ gtk_entry_set_activates_default (GTK_ENTRY (property_browser->details->keyword), TRUE);
+ gtk_entry_set_max_length (GTK_ENTRY (property_browser->details->keyword), 24);
+ gtk_widget_show(property_browser->details->keyword);
+ gtk_table_attach(GTK_TABLE(table), property_browser->details->keyword, 1, 2, 0, 1, GTK_FILL, GTK_FILL, 0, 0);
+ gtk_widget_grab_focus(property_browser->details->keyword);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (widget),
+ GTK_WIDGET (property_browser->details->keyword));
+
+ /* default image is the generic emblem */
+ g_free (property_browser->details->image_path);
+ property_browser->details->image_path = g_build_filename (CAJA_PIXMAPDIR, "emblems.png", NULL);
+
+ /* set up a file chooser to pick the image file */
+ label = gtk_label_new_with_mnemonic (_("_Image:"));
+ gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
+ gtk_widget_show (label);
+ gtk_table_attach (GTK_TABLE(table), label, 0, 1, 1, 2, GTK_FILL, GTK_FILL, 0, 0);
+
+ widget = gtk_hbox_new (FALSE, 0);
+ gtk_widget_show (widget);
+
+ button = gtk_button_new ();
+ property_browser->details->image_button = gtk_image_new_from_file (property_browser->details->image_path);
+ gtk_button_set_image (GTK_BUTTON (button), property_browser->details->image_button);
+ g_signal_connect (button, "clicked", G_CALLBACK (icon_button_clicked_cb),
+ property_browser);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), button);
+
+ gtk_widget_show (button);
+ gtk_table_attach (GTK_TABLE (table), widget, 1, 2, 1, 2, GTK_FILL, GTK_FILL, 0, 0);
+ gtk_box_pack_start (GTK_BOX (widget), button, FALSE, FALSE, 0);
+
+ return dialog;
+}
+
+/* create the color selection dialog */
+
+static GtkWidget*
+caja_color_selection_dialog_new (CajaPropertyBrowser *property_browser)
+{
+ GtkWidget *widget;
+ GtkWidget *dialog;
+ GtkWidget *table = gtk_table_new(2, 2, FALSE);
+
+ dialog = gtk_dialog_new_with_buttons (_("Create a New Color:"),
+ GTK_WINDOW (property_browser), 0,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OK, GTK_RESPONSE_OK,
+ NULL);
+
+ /* install the table in the dialog */
+
+ gtk_widget_show (table);
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), table, TRUE, TRUE, 0);
+ gtk_dialog_set_default_response (GTK_DIALOG(dialog), GTK_RESPONSE_OK);
+
+ /* make the name label and field */
+
+ widget = gtk_label_new_with_mnemonic(_("Color _name:"));
+ gtk_widget_show(widget);
+ gtk_table_attach(GTK_TABLE(table), widget, 0, 1, 0, 1, GTK_FILL, GTK_FILL, 0, 0);
+
+ property_browser->details->color_name = gtk_entry_new ();
+ gtk_entry_set_activates_default (GTK_ENTRY (property_browser->details->color_name), TRUE);
+ gtk_entry_set_max_length (GTK_ENTRY (property_browser->details->color_name), 24);
+ gtk_widget_grab_focus (property_browser->details->color_name);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (widget), property_browser->details->color_name);
+ gtk_widget_show(property_browser->details->color_name);
+ gtk_table_attach(GTK_TABLE(table), property_browser->details->color_name, 1, 2, 0, 1, GTK_FILL, GTK_FILL, 0, 0);
+ gtk_widget_grab_focus(property_browser->details->color_name);
+
+ /* default image is the generic emblem */
+ g_free(property_browser->details->image_path);
+
+ widget = gtk_label_new_with_mnemonic(_("Color _value:"));
+ gtk_widget_show(widget);
+ gtk_table_attach(GTK_TABLE(table), widget, 0, 1, 1, 2, GTK_FILL, GTK_FILL, 0, 0);
+
+ property_browser->details->color_picker = gtk_color_button_new ();
+ gtk_widget_show (property_browser->details->color_picker);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (widget), property_browser->details->color_picker);
+
+ gtk_table_attach(GTK_TABLE(table), property_browser->details->color_picker, 1, 2, 1, 2, GTK_FILL, GTK_FILL, 0, 0);
+
+ return dialog;
+}
+
+/* add the newly selected file to the browser images */
+static void
+add_pattern_to_browser (GtkDialog *dialog, gint response_id, gpointer *data)
+{
+ char *directory_path, *destination_name;
+ char *basename;
+ char *user_directory;
+ GFile *dest, *selected;
+
+ CajaPropertyBrowser *property_browser = CAJA_PROPERTY_BROWSER (data);
+
+ if (response_id != GTK_RESPONSE_ACCEPT)
+ {
+ gtk_widget_hide (GTK_WIDGET (dialog));
+ return;
+ }
+
+ selected = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
+
+ /* don't allow the user to change the reset image */
+ basename = g_file_get_basename (selected);
+ if (basename && eel_strcmp (basename, RESET_IMAGE_NAME) == 0)
+ {
+ eel_show_error_dialog (_("Sorry, but you cannot replace the reset image."),
+ _("Reset is a special image that cannot be deleted."),
+ NULL);
+ g_object_unref (selected);
+ g_free (basename);
+ return;
+ }
+
+
+ user_directory = caja_get_user_directory ();
+
+ /* copy the image file to the patterns directory */
+ directory_path = g_build_filename (user_directory, "patterns", NULL);
+ g_free (user_directory);
+ destination_name = g_build_filename (directory_path, basename, NULL);
+
+ /* make the directory if it doesn't exist */
+ if (!g_file_test (directory_path, G_FILE_TEST_EXISTS))
+ {
+ g_mkdir_with_parents (directory_path, 0775);
+ }
+
+ dest = g_file_new_for_path (destination_name);
+
+ g_free (destination_name);
+ g_free (directory_path);
+
+ if (!g_file_copy (selected, dest,
+ 0,
+ NULL, NULL, NULL, NULL))
+ {
+ char *message = g_strdup_printf (_("Sorry, but the pattern %s could not be installed."), basename);
+ eel_show_error_dialog (message, NULL, GTK_WINDOW (property_browser));
+ g_free (message);
+ }
+ g_object_unref (selected);
+ g_object_unref (dest);
+ g_free (basename);
+
+
+ /* update the property browser's contents to show the new one */
+ caja_property_browser_update_contents (property_browser);
+
+ gtk_widget_hide (GTK_WIDGET (dialog));
+}
+
+/* here's where we initiate adding a new pattern by putting up a file selector */
+
+static void
+add_new_pattern (CajaPropertyBrowser *property_browser)
+{
+ GtkWidget *dialog;
+ GtkFileFilter *filter;
+ GtkWidget *preview;
+
+ if (property_browser->details->patterns_dialog)
+ {
+ gtk_window_present (GTK_WINDOW (property_browser->details->patterns_dialog));
+ }
+ else
+ {
+ property_browser->details->patterns_dialog = dialog =
+ gtk_file_chooser_dialog_new (_("Select an Image File to Add as a Pattern"),
+ GTK_WINDOW (property_browser),
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
+ NULL);
+ gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog),
+ DATADIR "/caja/patterns/");
+ filter = gtk_file_filter_new ();
+ gtk_file_filter_add_pixbuf_formats (filter);
+ gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), filter);
+
+ gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (dialog), FALSE);
+
+ preview = gtk_image_new ();
+ gtk_file_chooser_set_preview_widget (GTK_FILE_CHOOSER (dialog),
+ preview);
+ g_signal_connect (dialog, "update-preview",
+ G_CALLBACK (update_preview_cb), preview);
+
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (add_pattern_to_browser),
+ property_browser);
+
+ gtk_widget_show (GTK_WIDGET (dialog));
+
+ if (property_browser->details->patterns_dialog)
+ eel_add_weak_pointer (&property_browser->details->patterns_dialog);
+ }
+}
+
+/* here's where we add the passed in color to the file that defines the colors */
+
+static void
+add_color_to_file (CajaPropertyBrowser *property_browser, const char *color_spec, const char *color_name)
+{
+ xmlNodePtr cur_node, new_color_node, children_node;
+ xmlDocPtr document;
+ xmlChar *child_color_name;
+ gboolean color_name_exists = FALSE;
+
+ document = read_browser_xml (property_browser);
+ if (document == NULL)
+ {
+ return;
+ }
+
+ /* find the colors category */
+ cur_node = get_color_category (document);
+ if (cur_node != NULL)
+ {
+ /* check if theres already a color whith that name */
+ children_node = cur_node->xmlChildrenNode;
+ while (children_node != NULL)
+ {
+ child_color_name = xmlGetProp (children_node, "name");
+ if (xmlStrcmp (color_name, child_color_name) == 0)
+ {
+ color_name_exists = TRUE;
+ xmlFree (child_color_name);
+ break;
+ }
+ xmlFree (child_color_name);
+
+ children_node = children_node->next;
+ }
+
+ /* add a new color node */
+ if (!color_name_exists)
+ {
+ new_color_node = xmlNewChild (cur_node, NULL, "color", NULL);
+ xmlNodeSetContent (new_color_node, color_spec);
+ xmlSetProp (new_color_node, "local", "1");
+ xmlSetProp (new_color_node, "name", color_name);
+
+ write_browser_xml (property_browser, document);
+ }
+ else
+ {
+ eel_show_error_dialog (_("The color cannot be installed."),
+ _("Sorry, but you must specify an unused color name for the new color."),
+ GTK_WINDOW (property_browser));
+ }
+ }
+
+ xmlFreeDoc (document);
+}
+
+/* handle the OK button being pushed on the color selection dialog */
+static void
+add_color_to_browser (GtkWidget *widget, gint which_button, gpointer *data)
+{
+ char * color_spec;
+ const char *color_name;
+ char *stripped_color_name;
+
+ CajaPropertyBrowser *property_browser = CAJA_PROPERTY_BROWSER (data);
+
+ if (which_button == GTK_RESPONSE_OK)
+ {
+ GdkColor color;
+
+ gtk_color_button_get_color (GTK_COLOR_BUTTON (property_browser->details->color_picker), &color);
+ color_spec = gdk_color_to_string (&color);
+
+ color_name = gtk_entry_get_text (GTK_ENTRY (property_browser->details->color_name));
+ stripped_color_name = g_strstrip (g_strdup (color_name));
+ if (strlen (stripped_color_name) == 0)
+ {
+ eel_show_error_dialog (_("The color cannot be installed."),
+ _("Sorry, but you must specify a non-blank name for the new color."),
+ GTK_WINDOW (property_browser));
+
+ }
+ else
+ {
+ add_color_to_file (property_browser, color_spec, stripped_color_name);
+ caja_property_browser_update_contents(property_browser);
+ }
+ g_free (stripped_color_name);
+ g_free (color_spec);
+ }
+
+ gtk_widget_destroy(property_browser->details->colors_dialog);
+ property_browser->details->colors_dialog = NULL;
+}
+
+/* create the color selection dialog, pre-set with the color that was just selected */
+static void
+show_color_selection_window (GtkWidget *widget, gpointer *data)
+{
+ GdkColor color;
+ CajaPropertyBrowser *property_browser = CAJA_PROPERTY_BROWSER(data);
+
+ gtk_color_selection_get_current_color (GTK_COLOR_SELECTION
+ (gtk_color_selection_dialog_get_color_selection (GTK_COLOR_SELECTION_DIALOG (property_browser->details->colors_dialog))),
+ &color);
+ gtk_widget_destroy (property_browser->details->colors_dialog);
+
+ /* allocate a new color selection dialog */
+ property_browser->details->colors_dialog = caja_color_selection_dialog_new (property_browser);
+
+ /* set the color to the one picked by the selector */
+ gtk_color_button_set_color (GTK_COLOR_BUTTON (property_browser->details->color_picker), &color);
+
+ /* connect the signals to the new dialog */
+
+ eel_add_weak_pointer (&property_browser->details->colors_dialog);
+
+ g_signal_connect_object (property_browser->details->colors_dialog, "response",
+ G_CALLBACK (add_color_to_browser), property_browser, 0);
+ gtk_window_set_position (GTK_WINDOW (property_browser->details->colors_dialog), GTK_WIN_POS_MOUSE);
+ gtk_widget_show (GTK_WIDGET(property_browser->details->colors_dialog));
+}
+
+
+/* here's the routine to add a new color, by putting up a color selector */
+
+static void
+add_new_color (CajaPropertyBrowser *property_browser)
+{
+ if (property_browser->details->colors_dialog)
+ {
+ gtk_window_present (GTK_WINDOW (property_browser->details->colors_dialog));
+ }
+ else
+ {
+ GtkColorSelectionDialog *color_dialog;
+ GtkWidget *ok_button, *cancel_button, *help_button;
+
+ property_browser->details->colors_dialog = gtk_color_selection_dialog_new (_("Select a Color to Add"));
+ color_dialog = GTK_COLOR_SELECTION_DIALOG (property_browser->details->colors_dialog);
+
+ eel_add_weak_pointer (&property_browser->details->colors_dialog);
+
+ g_object_get (color_dialog, "ok-button", &ok_button,
+ "cancel-button", &cancel_button,
+ "help-button", &help_button, NULL);
+
+ g_signal_connect_object (ok_button, "clicked",
+ G_CALLBACK (show_color_selection_window), property_browser, 0);
+ g_signal_connect_object (cancel_button, "clicked",
+ G_CALLBACK (gtk_widget_destroy), color_dialog, G_CONNECT_SWAPPED);
+ gtk_widget_hide (help_button);
+
+ gtk_window_set_position (GTK_WINDOW (color_dialog), GTK_WIN_POS_MOUSE);
+ gtk_widget_show (GTK_WIDGET(color_dialog));
+ }
+}
+
+/* here's where we handle clicks in the emblem dialog buttons */
+static void
+emblem_dialog_clicked (GtkWidget *dialog, int which_button, CajaPropertyBrowser *property_browser)
+{
+ const char *new_keyword;
+ char *stripped_keyword;
+ char *emblem_path;
+ GFile *emblem_file;
+ GdkPixbuf *pixbuf;
+
+ if (which_button == GTK_RESPONSE_OK)
+ {
+
+ /* update the image path from the file entry */
+ if (property_browser->details->filename)
+ {
+ emblem_path = property_browser->details->filename;
+ emblem_file = g_file_new_for_path (emblem_path);
+ if (ensure_file_is_image (emblem_file))
+ {
+ g_free (property_browser->details->image_path);
+ property_browser->details->image_path = emblem_path;
+ }
+ else
+ {
+ char *message = g_strdup_printf
+ (_("Sorry, but \"%s\" is not a usable image file."), emblem_path);
+ eel_show_error_dialog (_("The file is not an image."), message, GTK_WINDOW (property_browser));
+ g_free (message);
+ g_free (emblem_path);
+ emblem_path = NULL;
+ g_object_unref (emblem_file);
+ return;
+ }
+ g_object_unref (emblem_file);
+ }
+
+ emblem_file = g_file_new_for_path (property_browser->details->image_path);
+ pixbuf = caja_emblem_load_pixbuf_for_emblem (emblem_file);
+ g_object_unref (emblem_file);
+
+ if (pixbuf == NULL)
+ {
+ char *message = g_strdup_printf
+ (_("Sorry, but \"%s\" is not a usable image file."), property_browser->details->image_path);
+ eel_show_error_dialog (_("The file is not an image."), message, GTK_WINDOW (property_browser));
+ g_free (message);
+ }
+
+ new_keyword = gtk_entry_get_text(GTK_ENTRY(property_browser->details->keyword));
+ if (new_keyword == NULL)
+ {
+ stripped_keyword = NULL;
+ }
+ else
+ {
+ stripped_keyword = g_strstrip (g_strdup (new_keyword));
+ }
+
+
+ caja_emblem_install_custom_emblem (pixbuf,
+ stripped_keyword,
+ stripped_keyword,
+ GTK_WINDOW (property_browser));
+ if (pixbuf != NULL)
+ g_object_unref (pixbuf);
+
+ caja_emblem_refresh_list ();
+
+ emit_emblems_changed_signal ();
+
+ g_free (stripped_keyword);
+ }
+
+ gtk_widget_destroy (dialog);
+
+ property_browser->details->keyword = NULL;
+ property_browser->details->emblem_image = NULL;
+ property_browser->details->filename = NULL;
+}
+
+/* here's the routine to add a new emblem, by putting up an emblem dialog */
+
+static void
+add_new_emblem (CajaPropertyBrowser *property_browser)
+{
+ if (property_browser->details->emblems_dialog)
+ {
+ gtk_window_present (GTK_WINDOW (property_browser->details->emblems_dialog));
+ }
+ else
+ {
+ property_browser->details->emblems_dialog = caja_emblem_dialog_new (property_browser);
+
+ eel_add_weak_pointer (&property_browser->details->emblems_dialog);
+
+ g_signal_connect_object (property_browser->details->emblems_dialog, "response",
+ G_CALLBACK (emblem_dialog_clicked), property_browser, 0);
+ gtk_window_set_position (GTK_WINDOW (property_browser->details->emblems_dialog), GTK_WIN_POS_MOUSE);
+ gtk_widget_show (GTK_WIDGET(property_browser->details->emblems_dialog));
+ }
+}
+
+/* cancelremove mode */
+static void
+cancel_remove_mode (CajaPropertyBrowser *property_browser)
+{
+ if (property_browser->details->remove_mode)
+ {
+ property_browser->details->remove_mode = FALSE;
+ caja_property_browser_update_contents(property_browser);
+ gtk_widget_show (property_browser->details->help_label);
+ }
+}
+
+/* handle the add_new button */
+
+static void
+add_new_button_callback(GtkWidget *widget, CajaPropertyBrowser *property_browser)
+{
+ /* handle remove mode, where we act as a cancel button */
+ if (property_browser->details->remove_mode)
+ {
+ cancel_remove_mode (property_browser);
+ return;
+ }
+
+ switch (property_browser->details->category_type)
+ {
+ case CAJA_PROPERTY_PATTERN:
+ add_new_pattern (property_browser);
+ break;
+ case CAJA_PROPERTY_COLOR:
+ add_new_color (property_browser);
+ break;
+ case CAJA_PROPERTY_EMBLEM:
+ add_new_emblem (property_browser);
+ break;
+ default:
+ break;
+ }
+}
+
+/* handle the "done" button */
+static void
+done_button_callback (GtkWidget *widget, GtkWidget *property_browser)
+{
+ cancel_remove_mode (CAJA_PROPERTY_BROWSER (property_browser));
+ gtk_widget_hide (property_browser);
+}
+
+/* handle the "help" button */
+static void
+help_button_callback (GtkWidget *widget, GtkWidget *property_browser)
+{
+ GError *error = NULL;
+ GtkWidget *dialog;
+
+ gtk_show_uri (gtk_widget_get_screen (property_browser),
+ "ghelp:user-guide#goscaja-50",
+ gtk_get_current_event_time (), &error);
+
+ if (error)
+ {
+ dialog = gtk_message_dialog_new (GTK_WINDOW (property_browser),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_OK,
+ _("There was an error displaying help: \n%s"),
+ error->message);
+
+ g_signal_connect (G_OBJECT (dialog),
+ "response", G_CALLBACK (gtk_widget_destroy),
+ NULL);
+ gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+ gtk_widget_show (dialog);
+ g_error_free (error);
+ }
+}
+
+/* handle the "remove" button */
+static void
+remove_button_callback(GtkWidget *widget, CajaPropertyBrowser *property_browser)
+{
+ if (property_browser->details->remove_mode)
+ {
+ return;
+ }
+
+ property_browser->details->remove_mode = TRUE;
+ gtk_widget_hide (property_browser->details->help_label);
+ caja_property_browser_update_contents(property_browser);
+}
+
+/* this callback handles clicks on the image or color based content content elements */
+
+static void
+element_clicked_callback (GtkWidget *image_table,
+ GtkWidget *child,
+ const EelImageTableEvent *event,
+ gpointer callback_data)
+{
+ CajaPropertyBrowser *property_browser;
+ GtkTargetList *target_list;
+ GdkDragContext *context;
+ const char *element_name;
+ GdkDragAction action;
+
+ g_return_if_fail (EEL_IS_IMAGE_TABLE (image_table));
+ g_return_if_fail (EEL_IS_LABELED_IMAGE (child));
+ g_return_if_fail (event != NULL);
+ g_return_if_fail (CAJA_IS_PROPERTY_BROWSER (callback_data));
+ g_return_if_fail (g_object_get_data (G_OBJECT (child), "property-name") != NULL);
+
+ element_name = g_object_get_data (G_OBJECT (child), "property-name");
+ property_browser = CAJA_PROPERTY_BROWSER (callback_data);
+
+ /* handle remove mode by removing the element */
+ if (property_browser->details->remove_mode)
+ {
+ caja_property_browser_remove_element (property_browser, EEL_LABELED_IMAGE (child));
+ property_browser->details->remove_mode = FALSE;
+ caja_property_browser_update_contents (property_browser);
+ gtk_widget_show (property_browser->details->help_label);
+ return;
+ }
+
+ /* set up the drag and drop type corresponding to the category */
+ drag_types[0].target = property_browser->details->drag_type;
+
+ /* treat the reset property in the colors section specially */
+ if (eel_strcmp (element_name, RESET_IMAGE_NAME) == 0)
+ {
+ drag_types[0].target = "x-special/mate-reset-background";
+ }
+
+ target_list = gtk_target_list_new (drag_types, G_N_ELEMENTS (drag_types));
+ caja_property_browser_set_dragged_file(property_browser, element_name);
+ action = event->button == 3 ? GDK_ACTION_ASK : GDK_ACTION_MOVE | GDK_ACTION_COPY;
+
+ g_object_set_data (G_OBJECT (property_browser), "dragged-image", child);
+
+ context = gtk_drag_begin (GTK_WIDGET (property_browser),
+ target_list,
+ GDK_ACTION_ASK | GDK_ACTION_MOVE | GDK_ACTION_COPY,
+ event->button,
+ event->event);
+ gtk_target_list_unref (target_list);
+
+ /* optionally (if the shift key is down) hide the property browser - it will later be destroyed when the drag ends */
+ property_browser->details->keep_around = (event->state & GDK_SHIFT_MASK) == 0;
+ if (! property_browser->details->keep_around)
+ {
+ gtk_widget_hide (GTK_WIDGET (property_browser));
+ }
+}
+
+static void
+labeled_image_configure (EelLabeledImage *labeled_image)
+{
+ g_return_if_fail (EEL_IS_LABELED_IMAGE (labeled_image));
+
+ eel_labeled_image_set_spacing (labeled_image, LABELED_IMAGE_SPACING);
+}
+
+/* Make a color tile for a property */
+static GtkWidget *
+labeled_image_new (const char *text,
+ GdkPixbuf *pixbuf,
+ const char *property_name,
+ double scale_factor)
+{
+ GtkWidget *labeled_image;
+
+ labeled_image = eel_labeled_image_new (text, pixbuf);
+ labeled_image_configure (EEL_LABELED_IMAGE (labeled_image));
+
+ if (property_name != NULL)
+ {
+ g_object_set_data_full (G_OBJECT (labeled_image),
+ "property-name",
+ g_strdup (property_name),
+ g_free);
+ }
+
+ return labeled_image;
+}
+
+static void
+make_properties_from_directories (CajaPropertyBrowser *property_browser)
+{
+ CajaCustomizationData *customization_data;
+ char *object_name;
+ char *object_label;
+ GdkPixbuf *object_pixbuf;
+ EelImageTable *image_table;
+ GtkWidget *reset_object = NULL;
+ GList *icons, *l;
+ char *icon_name;
+ char *keyword;
+ GtkWidget *property_image;
+ GtkWidget *blank;
+ guint num_images;
+ char *path;
+ CajaIconInfo *info;
+
+ g_return_if_fail (CAJA_IS_PROPERTY_BROWSER (property_browser));
+ g_return_if_fail (EEL_IS_IMAGE_TABLE (property_browser->details->content_table));
+
+ image_table = EEL_IMAGE_TABLE (property_browser->details->content_table);
+
+ if (property_browser->details->category_type == CAJA_PROPERTY_EMBLEM)
+ {
+ eel_g_list_free_deep (property_browser->details->keywords);
+ property_browser->details->keywords = NULL;
+
+ icons = caja_emblem_list_available ();
+
+ property_browser->details->has_local = FALSE;
+ l = icons;
+ while (l != NULL)
+ {
+ icon_name = (char *)l->data;
+ l = l->next;
+
+ if (!caja_emblem_should_show_in_list (icon_name))
+ {
+ continue;
+ }
+
+ object_name = caja_emblem_get_keyword_from_icon_name (icon_name);
+ if (caja_emblem_can_remove_emblem (object_name))
+ {
+ property_browser->details->has_local = TRUE;
+ }
+ else if (property_browser->details->remove_mode)
+ {
+ g_free (object_name);
+ continue;
+ }
+ info = caja_icon_info_lookup_from_name (icon_name, CAJA_ICON_SIZE_STANDARD);
+ object_pixbuf = caja_icon_info_get_pixbuf_at_size (info, CAJA_ICON_SIZE_STANDARD);
+ object_label = g_strdup (caja_icon_info_get_display_name (info));
+ g_object_unref (info);
+
+ if (object_label == NULL)
+ {
+ object_label = g_strdup (object_name);
+ }
+
+ property_image = labeled_image_new (object_label, object_pixbuf, object_name, PANGO_SCALE_LARGE);
+ eel_labeled_image_set_fixed_image_height (EEL_LABELED_IMAGE (property_image), MAX_EMBLEM_HEIGHT);
+
+ keyword = eel_filename_strip_extension (object_name);
+ property_browser->details->keywords = g_list_prepend (property_browser->details->keywords,
+ keyword);
+
+ gtk_container_add (GTK_CONTAINER (image_table), property_image);
+ gtk_widget_show (property_image);
+
+ g_free (object_name);
+ g_free (object_label);
+ if (object_pixbuf != NULL)
+ {
+ g_object_unref (object_pixbuf);
+ }
+ }
+ eel_g_list_free_deep (icons);
+ }
+ else
+ {
+ customization_data = caja_customization_data_new (property_browser->details->category,
+ !property_browser->details->remove_mode,
+ MAX_ICON_WIDTH,
+ MAX_ICON_HEIGHT);
+ if (customization_data == NULL)
+ {
+ return;
+ }
+
+ /* interate through the set of objects and display each */
+ while (caja_customization_data_get_next_element_for_display (customization_data,
+ &object_name,
+ &object_pixbuf,
+ &object_label))
+ {
+
+ property_image = labeled_image_new (object_label, object_pixbuf, object_name, PANGO_SCALE_LARGE);
+
+ gtk_container_add (GTK_CONTAINER (image_table), property_image);
+ gtk_widget_show (property_image);
+
+ /* Keep track of ERASE objects to place them prominently later */
+ if (property_browser->details->category_type == CAJA_PROPERTY_PATTERN
+ && !eel_strcmp (object_name, RESET_IMAGE_NAME))
+ {
+ g_assert (reset_object == NULL);
+ reset_object = property_image;
+ }
+
+ gtk_widget_show (property_image);
+
+ g_free (object_name);
+ g_free (object_label);
+ if (object_pixbuf != NULL)
+ {
+ g_object_unref (object_pixbuf);
+ }
+ }
+
+ property_browser->details->has_local = caja_customization_data_private_data_was_displayed (customization_data);
+ caja_customization_data_destroy (customization_data);
+ }
+
+ /*
+ * We place ERASE objects (for emblems) at the end with a blank in between.
+ */
+ if (property_browser->details->category_type == CAJA_PROPERTY_EMBLEM)
+ {
+ blank = eel_image_table_add_empty_image (image_table);
+ labeled_image_configure (EEL_LABELED_IMAGE (blank));
+
+
+ num_images = eel_wrap_table_get_num_children (EEL_WRAP_TABLE (image_table));
+ g_assert (num_images > 0);
+ eel_wrap_table_reorder_child (EEL_WRAP_TABLE (image_table),
+ blank,
+ num_images - 1);
+
+ gtk_widget_show (blank);
+
+ object_pixbuf = NULL;
+
+ path = caja_pixmap_file (ERASE_OBJECT_NAME);
+ if (path != NULL)
+ {
+ object_pixbuf = gdk_pixbuf_new_from_file (path, NULL);
+ }
+ g_free (path);
+ property_image = labeled_image_new (_("Erase"), object_pixbuf, "erase", PANGO_SCALE_LARGE);
+ eel_labeled_image_set_fixed_image_height (EEL_LABELED_IMAGE (property_image), MAX_EMBLEM_HEIGHT);
+
+ gtk_container_add (GTK_CONTAINER (image_table), property_image);
+ gtk_widget_show (property_image);
+
+ eel_wrap_table_reorder_child (EEL_WRAP_TABLE (image_table),
+ property_image, -1);
+
+ if (object_pixbuf != NULL)
+ {
+ g_object_unref (object_pixbuf);
+ }
+ }
+
+ /*
+ * We place RESET objects (for colors and patterns) at the beginning.
+ */
+ if (reset_object != NULL)
+ {
+ g_assert (EEL_IS_LABELED_IMAGE (reset_object));
+ eel_wrap_table_reorder_child (EEL_WRAP_TABLE (image_table),
+ reset_object,
+ 0);
+ }
+
+}
+
+/* utility routine to add a reset property in the first position */
+static void
+add_reset_property (CajaPropertyBrowser *property_browser)
+{
+ char *reset_image_file_name;
+ GtkWidget *reset_image;
+ GdkPixbuf *reset_pixbuf, *reset_chit;
+
+ reset_chit = NULL;
+
+ reset_image_file_name = g_strdup_printf ("%s/%s/%s", CAJA_DATADIR, "patterns", RESET_IMAGE_NAME);
+ reset_pixbuf = gdk_pixbuf_new_from_file (reset_image_file_name, NULL);
+ if (reset_pixbuf != NULL && property_browser->details->property_chit != NULL)
+ {
+ reset_chit = caja_customization_make_pattern_chit (reset_pixbuf, property_browser->details->property_chit, FALSE, TRUE);
+ }
+
+ g_free (reset_image_file_name);
+
+ reset_image = labeled_image_new (_("Reset"), reset_chit != NULL ? reset_chit : reset_pixbuf, RESET_IMAGE_NAME, PANGO_SCALE_MEDIUM);
+ gtk_container_add (GTK_CONTAINER (property_browser->details->content_table), reset_image);
+ eel_wrap_table_reorder_child (EEL_WRAP_TABLE (property_browser->details->content_table),
+ reset_image,
+ 0);
+ gtk_widget_show (reset_image);
+
+ if (reset_pixbuf != NULL)
+ {
+ g_object_unref (reset_pixbuf);
+ }
+
+ if (reset_chit != NULL)
+ {
+ g_object_unref (reset_chit);
+ }
+}
+
+/* generate properties from the children of the passed in node */
+/* for now, we just handle color nodes */
+
+static void
+make_properties_from_xml_node (CajaPropertyBrowser *property_browser,
+ xmlNodePtr node)
+{
+ xmlNodePtr child_node;
+ GdkPixbuf *pixbuf;
+ GtkWidget *new_property;
+ char *deleted, *local, *color, *name;
+
+ gboolean local_only = property_browser->details->remove_mode;
+
+ /* add a reset property in the first slot */
+ if (!property_browser->details->remove_mode)
+ {
+ add_reset_property (property_browser);
+ }
+
+ property_browser->details->has_local = FALSE;
+
+ for (child_node = eel_xml_get_children (node);
+ child_node != NULL;
+ child_node = child_node->next)
+ {
+
+ if (child_node->type != XML_ELEMENT_NODE)
+ {
+ continue;
+ }
+
+ /* We used to mark colors that were removed with the "deleted" attribute.
+ * To prevent these colors from suddenly showing up now, this legacy remains.
+ */
+ deleted = xmlGetProp (child_node, "deleted");
+ local = xmlGetProp (child_node, "local");
+
+ if (deleted == NULL && (!local_only || local != NULL))
+ {
+ if (local != NULL)
+ {
+ property_browser->details->has_local = TRUE;
+ }
+
+ color = xmlNodeGetContent (child_node);
+ name = eel_xml_get_property_translated (child_node, "name");
+
+ /* make the image from the color spec */
+ pixbuf = make_color_drag_image (property_browser, color, FALSE);
+
+ /* make the tile from the pixmap and name */
+ new_property = labeled_image_new (name, pixbuf, color, PANGO_SCALE_LARGE);
+
+ gtk_container_add (GTK_CONTAINER (property_browser->details->content_table), new_property);
+ gtk_widget_show (new_property);
+
+ g_object_unref (pixbuf);
+ xmlFree (color);
+ xmlFree (name);
+ }
+
+ xmlFree (local);
+ xmlFree (deleted);
+ }
+}
+
+/* handle theme changes by updating the browser contents */
+
+static void
+caja_property_browser_theme_changed (gpointer user_data)
+{
+ CajaPropertyBrowser *property_browser;
+
+ property_browser = CAJA_PROPERTY_BROWSER(user_data);
+ caja_property_browser_update_contents (property_browser);
+}
+
+/* make_category generates widgets corresponding all of the objects in the passed in directory */
+static void
+make_category(CajaPropertyBrowser *property_browser, const char* path, const char* mode, xmlNodePtr node, const char *description)
+{
+
+ /* set up the description in the help label */
+ gtk_label_set_text (GTK_LABEL (property_browser->details->help_label), description);
+
+ /* case out on the mode */
+ if (strcmp (mode, "directory") == 0)
+ make_properties_from_directories (property_browser);
+ else if (strcmp (mode, "inline") == 0)
+ make_properties_from_xml_node (property_browser, node);
+
+}
+
+/* Create a category button */
+static GtkWidget *
+property_browser_category_button_new (const char *display_name,
+ const char *image)
+{
+ GtkWidget *button;
+ char *file_name;
+
+ g_return_val_if_fail (display_name != NULL, NULL);
+ g_return_val_if_fail (image != NULL, NULL);
+
+ file_name = caja_pixmap_file (image);
+ if (file_name != NULL)
+ {
+ button = eel_labeled_image_radio_button_new_from_file_name (display_name, file_name);
+ }
+ else
+ {
+ button = eel_labeled_image_radio_button_new (display_name, NULL);
+ }
+
+ gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (button), FALSE);
+
+ /* We also want all of the buttons to be the same height */
+ eel_labeled_image_set_fixed_image_height (EEL_LABELED_IMAGE (gtk_bin_get_child (GTK_BIN (button))), STANDARD_BUTTON_IMAGE_HEIGHT);
+
+ g_free (file_name);
+
+ return button;
+}
+
+/* this is a utility routine to generate a category link widget and install it in the browser */
+static void
+make_category_link (CajaPropertyBrowser *property_browser,
+ const char *name,
+ const char *display_name,
+ const char *image,
+ GtkRadioButton **group)
+{
+ GtkWidget *button;
+
+ g_return_if_fail (name != NULL);
+ g_return_if_fail (image != NULL);
+ g_return_if_fail (display_name != NULL);
+ g_return_if_fail (CAJA_IS_PROPERTY_BROWSER (property_browser));
+
+ button = property_browser_category_button_new (display_name, image);
+ gtk_widget_show (button);
+
+ if (*group)
+ {
+ gtk_radio_button_set_group (GTK_RADIO_BUTTON (button),
+ gtk_radio_button_get_group (*group));
+ }
+ else
+ {
+ *group = GTK_RADIO_BUTTON (button);
+ }
+
+ /* if the button represents the current category, highlight it */
+ if (property_browser->details->category &&
+ strcmp (property_browser->details->category, name) == 0)
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
+
+ /* Place it in the category box */
+ gtk_box_pack_start (GTK_BOX (property_browser->details->category_box),
+ button, FALSE, FALSE, 0);
+
+ property_browser->details->category_position += 1;
+
+ /* add a signal to handle clicks */
+ g_object_set_data (G_OBJECT(button), "user_data", property_browser);
+ g_signal_connect_data
+ (button, "toggled",
+ G_CALLBACK (category_toggled_callback),
+ g_strdup (name), (GClosureNotify) g_free, 0);
+}
+
+/* update_contents populates the property browser with information specified by the path and other state variables */
+void
+caja_property_browser_update_contents (CajaPropertyBrowser *property_browser)
+{
+ xmlNodePtr cur_node;
+ xmlDocPtr document;
+ GtkWidget *viewport;
+ GtkRadioButton *group;
+ gboolean got_categories;
+ char *name, *image, *type, *description, *display_name, *path, *mode;
+ const char *text;
+
+ /* load the xml document corresponding to the path and selection */
+ document = read_browser_xml (property_browser);
+ if (document == NULL)
+ {
+ return;
+ }
+
+ /* remove the existing content box, if any, and allocate a new one */
+ if (property_browser->details->content_frame)
+ {
+ gtk_widget_destroy(property_browser->details->content_frame);
+ }
+
+ /* allocate a new container, with a scrollwindow and viewport */
+ property_browser->details->content_frame = gtk_scrolled_window_new (NULL, NULL);
+ viewport = gtk_viewport_new (NULL, NULL);
+ gtk_widget_show(viewport);
+ gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport), GTK_SHADOW_IN);
+ gtk_container_add (GTK_CONTAINER (property_browser->details->content_container), property_browser->details->content_frame);
+ gtk_widget_show (property_browser->details->content_frame);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (property_browser->details->content_frame),
+ GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+
+ /* allocate a table to hold the content widgets */
+ property_browser->details->content_table = eel_image_table_new (TRUE);
+ eel_wrap_table_set_x_spacing (EEL_WRAP_TABLE (property_browser->details->content_table),
+ IMAGE_TABLE_X_SPACING);
+ eel_wrap_table_set_y_spacing (EEL_WRAP_TABLE (property_browser->details->content_table),
+ IMAGE_TABLE_Y_SPACING);
+
+ g_signal_connect_object (property_browser->details->content_table, "child_pressed",
+ G_CALLBACK (element_clicked_callback), property_browser, 0);
+
+ gtk_container_add(GTK_CONTAINER(viewport), property_browser->details->content_table);
+ gtk_container_add (GTK_CONTAINER (property_browser->details->content_frame), viewport);
+ gtk_widget_show (GTK_WIDGET (property_browser->details->content_table));
+
+ /* iterate through the xml file to generate the widgets */
+ got_categories = property_browser->details->category_position >= 0;
+ if (!got_categories)
+ {
+ property_browser->details->category_position = 0;
+ }
+
+ group = NULL;
+ for (cur_node = eel_xml_get_children (xmlDocGetRootElement (document));
+ cur_node != NULL;
+ cur_node = cur_node->next)
+ {
+
+ if (cur_node->type != XML_ELEMENT_NODE)
+ {
+ continue;
+ }
+
+ if (strcmp (cur_node->name, "category") == 0)
+ {
+ name = xmlGetProp (cur_node, "name");
+
+ if (property_browser->details->category != NULL
+ && strcmp (property_browser->details->category, name) == 0)
+ {
+ path = xmlGetProp (cur_node, "path");
+ mode = xmlGetProp (cur_node, "mode");
+ description = eel_xml_get_property_translated (cur_node, "description");
+ type = xmlGetProp (cur_node, "type");
+
+ make_category (property_browser,
+ path,
+ mode,
+ cur_node,
+ description);
+ caja_property_browser_set_drag_type (property_browser, type);
+
+ xmlFree (path);
+ xmlFree (mode);
+ xmlFree (description);
+ xmlFree (type);
+ }
+
+ if (!got_categories)
+ {
+ display_name = eel_xml_get_property_translated (cur_node, "display_name");
+ image = xmlGetProp (cur_node, "image");
+
+ make_category_link (property_browser,
+ name,
+ display_name,
+ image,
+ &group);
+
+ xmlFree (display_name);
+ xmlFree (image);
+ }
+
+ xmlFree (name);
+ }
+ }
+
+ /* release the xml document and we're done */
+ xmlFreeDoc (document);
+
+ /* update the title and button */
+
+ if (property_browser->details->category == NULL)
+ {
+ gtk_label_set_text (GTK_LABEL (property_browser->details->title_label), _("Select a Category:"));
+ gtk_widget_hide(property_browser->details->add_button);
+ gtk_widget_hide(property_browser->details->remove_button);
+
+ }
+ else
+ {
+ char *label_text;
+ char *stock_id;
+ if (property_browser->details->remove_mode)
+ {
+ stock_id = GTK_STOCK_CANCEL;
+ text = _("C_ancel Remove");
+ }
+ else
+ {
+ stock_id = GTK_STOCK_ADD;
+ /* FIXME: Using spaces to add padding is not good design. */
+ switch (property_browser->details->category_type)
+ {
+ case CAJA_PROPERTY_PATTERN:
+ text = _("_Add a New Pattern...");
+ break;
+ case CAJA_PROPERTY_COLOR:
+ text = _("_Add a New Color...");
+ break;
+ case CAJA_PROPERTY_EMBLEM:
+ text = _("_Add a New Emblem...");
+ break;
+ default:
+ text = NULL;
+ break;
+ }
+ }
+
+ /* enable the "add new" button and update it's name and icon */
+ gtk_image_set_from_stock (GTK_IMAGE(property_browser->details->add_button_image), stock_id,
+ GTK_ICON_SIZE_BUTTON);
+
+ if (text != NULL)
+ {
+ gtk_button_set_label (GTK_BUTTON (property_browser->details->add_button), text);
+
+ }
+ gtk_widget_show (property_browser->details->add_button);
+
+
+ if (property_browser->details->remove_mode)
+ {
+
+ switch (property_browser->details->category_type)
+ {
+ case CAJA_PROPERTY_PATTERN:
+ label_text = g_strdup (_("Click on a pattern to remove it"));
+ break;
+ case CAJA_PROPERTY_COLOR:
+ label_text = g_strdup (_("Click on a color to remove it"));
+ break;
+ case CAJA_PROPERTY_EMBLEM:
+ label_text = g_strdup (_("Click on an emblem to remove it"));
+ break;
+ default:
+ label_text = NULL;
+ break;
+ }
+ }
+ else
+ {
+ switch (property_browser->details->category_type)
+ {
+ case CAJA_PROPERTY_PATTERN:
+ label_text = g_strdup (_("Patterns:"));
+ break;
+ case CAJA_PROPERTY_COLOR:
+ label_text = g_strdup (_("Colors:"));
+ break;
+ case CAJA_PROPERTY_EMBLEM:
+ label_text = g_strdup (_("Emblems:"));
+ break;
+ default:
+ label_text = NULL;
+ break;
+ }
+ }
+
+ if (label_text)
+ {
+ gtk_label_set_text_with_mnemonic
+ (GTK_LABEL (property_browser->details->title_label), label_text);
+ }
+ g_free(label_text);
+
+ /* enable the remove button (if necessary) and update its name */
+
+ /* case out instead of substituting to provide flexibilty for other languages */
+ /* FIXME: Using spaces to add padding is not good design. */
+ switch (property_browser->details->category_type)
+ {
+ case CAJA_PROPERTY_PATTERN:
+ text = _("_Remove a Pattern...");
+ break;
+ case CAJA_PROPERTY_COLOR:
+ text = _("_Remove a Color...");
+ break;
+ case CAJA_PROPERTY_EMBLEM:
+ text = _("_Remove an Emblem...");
+ break;
+ default:
+ text = NULL;
+ break;
+ }
+
+ if (property_browser->details->remove_mode
+ || !property_browser->details->has_local)
+ gtk_widget_hide(property_browser->details->remove_button);
+ else
+ gtk_widget_show(property_browser->details->remove_button);
+ if (text != NULL)
+ {
+ gtk_button_set_label (GTK_BUTTON (property_browser->details->remove_button), text);
+ }
+ }
+}
+
+/* set the category and regenerate contents as necessary */
+
+static void
+caja_property_browser_set_category (CajaPropertyBrowser *property_browser,
+ const char *new_category)
+{
+ /* there's nothing to do if the category is the same as the current one */
+ if (eel_strcmp (property_browser->details->category, new_category) == 0)
+ {
+ return;
+ }
+
+ g_free (property_browser->details->category);
+ property_browser->details->category = g_strdup (new_category);
+
+ /* set up the property type enum */
+ if (eel_strcmp (new_category, "patterns") == 0)
+ {
+ property_browser->details->category_type = CAJA_PROPERTY_PATTERN;
+ }
+ else if (eel_strcmp (new_category, "colors") == 0)
+ {
+ property_browser->details->category_type = CAJA_PROPERTY_COLOR;
+ }
+ else if (eel_strcmp (new_category, "emblems") == 0)
+ {
+ property_browser->details->category_type = CAJA_PROPERTY_EMBLEM;
+ }
+ else
+ {
+ property_browser->details->category_type = CAJA_PROPERTY_NONE;
+ }
+
+ /* populate the per-uri box with the info */
+ caja_property_browser_update_contents (property_browser);
+}
+
+
+/* here is the routine that populates the property browser with the appropriate information
+ when the path changes */
+
+void
+caja_property_browser_set_path (CajaPropertyBrowser *property_browser,
+ const char *new_path)
+{
+ /* there's nothing to do if the uri is the same as the current one */
+ if (eel_strcmp (property_browser->details->path, new_path) == 0)
+ {
+ return;
+ }
+
+ g_free (property_browser->details->path);
+ property_browser->details->path = g_strdup (new_path);
+
+ /* populate the per-uri box with the info */
+ caja_property_browser_update_contents (property_browser);
+}
+
+static void
+emblems_changed_callback (GObject *signaller,
+ CajaPropertyBrowser *property_browser)
+{
+ caja_property_browser_update_contents (property_browser);
+}
+
+
+static void
+emit_emblems_changed_signal (void)
+{
+ g_signal_emit_by_name (caja_signaller_get_current (), "emblems_changed");
+}
diff --git a/src/caja-property-browser.h b/src/caja-property-browser.h
new file mode 100644
index 00000000..e5becc39
--- /dev/null
+++ b/src/caja-property-browser.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Author: Andy Hertzfeld <[email protected]>
+ */
+
+/* This is the header file for the property browser window, which
+ * gives the user access to an extensible palette of properties which
+ * can be dropped on various elements of the user interface to
+ * customize them
+ */
+
+#ifndef CAJA_PROPERTY_BROWSER_H
+#define CAJA_PROPERTY_BROWSER_H
+
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+
+typedef struct CajaPropertyBrowser CajaPropertyBrowser;
+typedef struct CajaPropertyBrowserClass CajaPropertyBrowserClass;
+
+#define CAJA_TYPE_PROPERTY_BROWSER caja_property_browser_get_type()
+#define CAJA_PROPERTY_BROWSER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_PROPERTY_BROWSER, CajaPropertyBrowser))
+#define CAJA_PROPERTY_BROWSER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_PROPERTY_BROWSER, CajaPropertyBrowserClass))
+#define CAJA_IS_PROPERTY_BROWSER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_PROPERTY_BROWSER))
+#define CAJA_IS_PROPERTY_BROWSER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_PROPERTY_BROWSER))
+#define CAJA_PROPERTY_BROWSER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_PROPERTY_BROWSER, CajaPropertyBrowserClass))
+
+typedef struct CajaPropertyBrowserDetails CajaPropertyBrowserDetails;
+
+struct CajaPropertyBrowser
+{
+ GtkWindow window;
+ CajaPropertyBrowserDetails *details;
+};
+
+struct CajaPropertyBrowserClass
+{
+ GtkWindowClass parent_class;
+};
+
+GType caja_property_browser_get_type (void);
+CajaPropertyBrowser *caja_property_browser_new (GdkScreen *screen);
+void caja_property_browser_show (GdkScreen *screen);
+void caja_property_browser_set_path (CajaPropertyBrowser *panel,
+ const char *new_path);
+
+#endif /* CAJA_PROPERTY_BROWSER_H */
diff --git a/src/caja-query-editor.c b/src/caja-query-editor.c
new file mode 100644
index 00000000..dc2bb0ae
--- /dev/null
+++ b/src/caja-query-editor.c
@@ -0,0 +1,1397 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * Copyright (C) 2005 Red Hat, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Alexander Larsson <[email protected]>
+ *
+ */
+
+#include <config.h>
+#include "caja-query-editor.h"
+#include "caja-window-slot.h"
+
+#include <string.h>
+#include <libcaja-private/caja-marshal.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <eel/eel-gtk-macros.h>
+#include <eel/eel-glib-extensions.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+
+typedef enum
+{
+ CAJA_QUERY_EDITOR_ROW_LOCATION,
+ CAJA_QUERY_EDITOR_ROW_TYPE,
+
+ CAJA_QUERY_EDITOR_ROW_LAST
+} CajaQueryEditorRowType;
+
+typedef struct
+{
+ CajaQueryEditorRowType type;
+ CajaQueryEditor *editor;
+ GtkWidget *hbox;
+ GtkWidget *combo;
+
+ GtkWidget *type_widget;
+
+ void *data;
+} CajaQueryEditorRow;
+
+
+typedef struct
+{
+ const char *name;
+ GtkWidget * (*create_widgets) (CajaQueryEditorRow *row);
+ void (*add_to_query) (CajaQueryEditorRow *row,
+ CajaQuery *query);
+ void (*free_data) (CajaQueryEditorRow *row);
+ void (*add_rows_from_query) (CajaQueryEditor *editor,
+ CajaQuery *query);
+} CajaQueryEditorRowOps;
+
+struct CajaQueryEditorDetails
+{
+ gboolean is_indexed;
+ GtkWidget *entry;
+ gboolean change_frozen;
+ guint typing_timeout_id;
+ gboolean is_visible;
+ GtkWidget *invisible_vbox;
+ GtkWidget *visible_vbox;
+
+ GList *rows;
+ char *last_set_query_text;
+
+ CajaSearchBar *bar;
+ CajaWindowSlot *slot;
+};
+
+enum
+{
+ CHANGED,
+ CANCEL,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+static void caja_query_editor_class_init (CajaQueryEditorClass *class);
+static void caja_query_editor_init (CajaQueryEditor *editor);
+
+static void entry_activate_cb (GtkWidget *entry, CajaQueryEditor *editor);
+static void entry_changed_cb (GtkWidget *entry, CajaQueryEditor *editor);
+static void caja_query_editor_changed_force (CajaQueryEditor *editor,
+ gboolean force);
+static void caja_query_editor_changed (CajaQueryEditor *editor);
+static CajaQueryEditorRow * caja_query_editor_add_row (CajaQueryEditor *editor,
+ CajaQueryEditorRowType type);
+
+static GtkWidget *location_row_create_widgets (CajaQueryEditorRow *row);
+static void location_row_add_to_query (CajaQueryEditorRow *row,
+ CajaQuery *query);
+static void location_row_free_data (CajaQueryEditorRow *row);
+static void location_add_rows_from_query (CajaQueryEditor *editor,
+ CajaQuery *query);
+static GtkWidget *type_row_create_widgets (CajaQueryEditorRow *row);
+static void type_row_add_to_query (CajaQueryEditorRow *row,
+ CajaQuery *query);
+static void type_row_free_data (CajaQueryEditorRow *row);
+static void type_add_rows_from_query (CajaQueryEditor *editor,
+ CajaQuery *query);
+
+
+
+static CajaQueryEditorRowOps row_type[] =
+{
+ {
+ N_("Location"),
+ location_row_create_widgets,
+ location_row_add_to_query,
+ location_row_free_data,
+ location_add_rows_from_query
+ },
+ {
+ N_("File Type"),
+ type_row_create_widgets,
+ type_row_add_to_query,
+ type_row_free_data,
+ type_add_rows_from_query
+ },
+};
+
+EEL_CLASS_BOILERPLATE (CajaQueryEditor,
+ caja_query_editor,
+ GTK_TYPE_VBOX)
+
+static void
+caja_query_editor_finalize (GObject *object)
+{
+ CajaQueryEditor *editor;
+
+ editor = CAJA_QUERY_EDITOR (object);
+
+ g_free (editor->details);
+
+ EEL_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
+}
+
+static void
+caja_query_editor_dispose (GObject *object)
+{
+ CajaQueryEditor *editor;
+
+ editor = CAJA_QUERY_EDITOR (object);
+
+ if (editor->details->typing_timeout_id)
+ {
+ g_source_remove (editor->details->typing_timeout_id);
+ editor->details->typing_timeout_id = 0;
+ }
+
+ if (editor->details->bar != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (editor->details->entry,
+ entry_activate_cb,
+ editor);
+ g_signal_handlers_disconnect_by_func (editor->details->entry,
+ entry_changed_cb,
+ editor);
+
+ caja_search_bar_return_entry (editor->details->bar);
+ eel_remove_weak_pointer (&editor->details->bar);
+ }
+
+ EEL_CALL_PARENT (G_OBJECT_CLASS, dispose, (object));
+}
+
+static void
+caja_query_editor_class_init (CajaQueryEditorClass *class)
+{
+ GObjectClass *gobject_class;
+ GtkBindingSet *binding_set;
+
+ gobject_class = G_OBJECT_CLASS (class);
+ gobject_class->finalize = caja_query_editor_finalize;
+ gobject_class->dispose = caja_query_editor_dispose;
+
+ signals[CHANGED] =
+ g_signal_new ("changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaQueryEditorClass, changed),
+ NULL, NULL,
+ caja_marshal_VOID__OBJECT_BOOLEAN,
+ G_TYPE_NONE, 2, CAJA_TYPE_QUERY, G_TYPE_BOOLEAN);
+
+ signals[CANCEL] =
+ g_signal_new ("cancel",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (CajaQueryEditorClass, cancel),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ binding_set = gtk_binding_set_by_class (class);
+ gtk_binding_entry_add_signal (binding_set, GDK_Escape, 0, "cancel", 0);
+}
+
+static void
+entry_activate_cb (GtkWidget *entry, CajaQueryEditor *editor)
+{
+ if (editor->details->typing_timeout_id)
+ {
+ g_source_remove (editor->details->typing_timeout_id);
+ editor->details->typing_timeout_id = 0;
+ }
+
+ caja_query_editor_changed_force (editor, TRUE);
+}
+
+static gboolean
+typing_timeout_cb (gpointer user_data)
+{
+ CajaQueryEditor *editor;
+
+ editor = CAJA_QUERY_EDITOR (user_data);
+
+ caja_query_editor_changed (editor);
+
+ editor->details->typing_timeout_id = 0;
+
+ return FALSE;
+}
+
+#define TYPING_TIMEOUT 750
+
+static void
+entry_changed_cb (GtkWidget *entry, CajaQueryEditor *editor)
+{
+ if (editor->details->change_frozen)
+ {
+ return;
+ }
+
+ if (editor->details->typing_timeout_id)
+ {
+ g_source_remove (editor->details->typing_timeout_id);
+ }
+
+ editor->details->typing_timeout_id =
+ g_timeout_add (TYPING_TIMEOUT,
+ typing_timeout_cb,
+ editor);
+}
+
+static void
+edit_clicked (GtkButton *button, CajaQueryEditor *editor)
+{
+ caja_query_editor_set_visible (editor, TRUE);
+ caja_query_editor_grab_focus (editor);
+}
+
+/* Location */
+
+static GtkWidget *
+location_row_create_widgets (CajaQueryEditorRow *row)
+{
+ GtkWidget *chooser;
+
+ chooser = gtk_file_chooser_button_new (_("Select folder to search in"),
+ GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER);
+ 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 ());
+ gtk_widget_show (chooser);
+
+ g_signal_connect_swapped (chooser, "current-folder-changed",
+ G_CALLBACK (caja_query_editor_changed),
+ row->editor);
+
+ gtk_box_pack_start (GTK_BOX (row->hbox), chooser, FALSE, FALSE, 0);
+
+ return chooser;
+}
+
+static void
+location_row_add_to_query (CajaQueryEditorRow *row,
+ CajaQuery *query)
+{
+ char *folder, *uri;
+
+ folder = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (row->type_widget));
+ if (folder == NULL)
+ {
+ /* I don't know why, but i got NULL here on initial search in browser mode
+ even with the location set to the homedir in create_widgets... */
+ folder = g_strdup (g_get_home_dir ());
+ }
+
+ uri = g_filename_to_uri (folder, NULL, NULL);
+ g_free (folder);
+
+ caja_query_set_location (query, uri);
+ g_free (uri);
+}
+
+static void
+location_row_free_data (CajaQueryEditorRow *row)
+{
+}
+
+static void
+location_add_rows_from_query (CajaQueryEditor *editor,
+ CajaQuery *query)
+{
+ CajaQueryEditorRow *row;
+ char *uri, *folder;
+
+ uri = caja_query_get_location (query);
+
+ if (uri == NULL)
+ {
+ return;
+ }
+ folder = g_filename_from_uri (uri, NULL, NULL);
+ g_free (uri);
+ if (folder == NULL)
+ {
+ return;
+ }
+
+ row = caja_query_editor_add_row (editor,
+ CAJA_QUERY_EDITOR_ROW_LOCATION);
+ gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (row->type_widget),
+ folder);
+
+ g_free (folder);
+}
+
+
+/* Type */
+
+static gboolean
+type_separator_func (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ char *text;
+ gboolean res;
+
+ gtk_tree_model_get (model, iter, 0, &text, -1);
+
+ res = text != NULL && strcmp (text, "---") == 0;
+
+ g_free (text);
+ return res;
+}
+
+struct
+{
+ char *name;
+ char *mimetypes[20];
+} mime_type_groups[] =
+{
+ {
+ N_("Documents"),
+ {
+ "application/rtf",
+ "application/msword",
+ "application/vnd.sun.xml.writer",
+ "application/vnd.sun.xml.writer.global",
+ "application/vnd.sun.xml.writer.template",
+ "application/vnd.oasis.opendocument.text",
+ "application/vnd.oasis.opendocument.text-template",
+ "application/x-abiword",
+ "application/x-applix-word",
+ "application/x-mswrite",
+ "application/docbook+xml",
+ "application/x-kword",
+ "application/x-kword-crypt",
+ "application/x-lyx",
+ NULL
+ }
+ },
+ {
+ N_("Music"),
+ {
+ "application/ogg",
+ "audio/ac3",
+ "audio/basic",
+ "audio/midi",
+ "audio/x-flac",
+ "audio/mp4",
+ "audio/mpeg",
+ "audio/x-mpeg",
+ "audio/x-ms-asx",
+ "audio/x-pn-realaudio",
+ NULL
+ }
+ },
+ {
+ N_("Video"),
+ {
+ "video/mp4",
+ "video/3gpp",
+ "video/mpeg",
+ "video/quicktime",
+ "video/vivo",
+ "video/x-avi",
+ "video/x-mng",
+ "video/x-ms-asf",
+ "video/x-ms-wmv",
+ "video/x-msvideo",
+ "video/x-nsv",
+ "video/x-real-video",
+ NULL
+ }
+ },
+ {
+ N_("Picture"),
+ {
+ "application/vnd.oasis.opendocument.image",
+ "application/x-krita",
+ "image/bmp",
+ "image/cgm",
+ "image/gif",
+ "image/jpeg",
+ "image/jpeg2000",
+ "image/png",
+ "image/svg+xml",
+ "image/tiff",
+ "image/x-compressed-xcf",
+ "image/x-pcx",
+ "image/x-photo-cd",
+ "image/x-psd",
+ "image/x-tga",
+ "image/x-xcf",
+ NULL
+ }
+ },
+ {
+ N_("Illustration"),
+ {
+ "application/illustrator",
+ "application/vnd.corel-draw",
+ "application/vnd.stardivision.draw",
+ "application/vnd.oasis.opendocument.graphics",
+ "application/x-dia-diagram",
+ "application/x-karbon",
+ "application/x-killustrator",
+ "application/x-kivio",
+ "application/x-kontour",
+ "application/x-wpg",
+ NULL
+ }
+ },
+ {
+ N_("Spreadsheet"),
+ {
+ "application/vnd.lotus-1-2-3",
+ "application/vnd.ms-excel",
+ "application/vnd.stardivision.calc",
+ "application/vnd.sun.xml.calc",
+ "application/vnd.oasis.opendocument.spreadsheet",
+ "application/x-applix-spreadsheet",
+ "application/x-gnumeric",
+ "application/x-kspread",
+ "application/x-kspread-crypt",
+ "application/x-quattropro",
+ "application/x-sc",
+ "application/x-siag",
+ NULL
+ }
+ },
+ {
+ N_("Presentation"),
+ {
+ "application/vnd.ms-powerpoint",
+ "application/vnd.sun.xml.impress",
+ "application/vnd.oasis.opendocument.presentation",
+ "application/x-magicpoint",
+ "application/x-kpresenter",
+ NULL
+ }
+ },
+ {
+ N_("Pdf / Postscript"),
+ {
+ "application/pdf",
+ "application/postscript",
+ "application/x-dvi",
+ "image/x-eps",
+ NULL
+ }
+ },
+ {
+ N_("Text File"),
+ {
+ "text/plain",
+ NULL
+ }
+ }
+};
+
+static void
+type_add_custom_type (CajaQueryEditorRow *row,
+ const char *mime_type,
+ const char *description,
+ GtkTreeIter *iter)
+{
+ GtkTreeModel *model;
+ GtkListStore *store;
+
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (row->type_widget));
+ store = GTK_LIST_STORE (model);
+
+ gtk_list_store_append (store, iter);
+ gtk_list_store_set (store, iter,
+ 0, description,
+ 2, mime_type,
+ -1);
+}
+
+
+static void
+type_combo_changed (GtkComboBox *combo_box, CajaQueryEditorRow *row)
+{
+ GtkTreeIter iter;
+ gboolean other;
+ GtkTreeModel *model;
+
+ if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (row->type_widget),
+ &iter))
+ {
+ return;
+ }
+
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (row->type_widget));
+ gtk_tree_model_get (model, &iter, 3, &other, -1);
+
+ if (other)
+ {
+ GList *mime_infos, *l;
+ GtkWidget *dialog;
+ GtkWidget *scrolled, *treeview;
+ GtkListStore *store;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *renderer;
+ GtkWidget *toplevel;
+ GtkTreeSelection *selection;
+
+ mime_infos = g_content_types_get_registered ();
+
+ store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
+ for (l = mime_infos; l != NULL; l = l->next)
+ {
+ GtkTreeIter iter;
+ char *mime_type = l->data;
+ char *description;
+
+ description = g_content_type_get_description (mime_type);
+ if (description == NULL)
+ {
+ description = g_strdup (mime_type);
+ }
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ 0, description,
+ 1, mime_type,
+ -1);
+
+ g_free (mime_type);
+ g_free (description);
+ }
+ g_list_free (mime_infos);
+
+
+
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (combo_box));
+ dialog = gtk_dialog_new_with_buttons (_("Select type"),
+ GTK_WINDOW (toplevel),
+ GTK_DIALOG_NO_SEPARATOR,
+ GTK_STOCK_OK, GTK_RESPONSE_OK,
+ NULL);
+ gtk_window_set_default_size (GTK_WINDOW (dialog), 400, 600);
+
+ scrolled = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled),
+ GTK_SHADOW_IN);
+
+ gtk_widget_show (scrolled);
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), scrolled, TRUE, TRUE, 6);
+
+ treeview = gtk_tree_view_new ();
+ gtk_tree_view_set_model (GTK_TREE_VIEW (treeview),
+ GTK_TREE_MODEL (store));
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store), 0,
+ GTK_SORT_ASCENDING);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
+
+
+ renderer = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes ("Name",
+ renderer,
+ "text",
+ 0,
+ NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
+ gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE);
+
+ gtk_widget_show (treeview);
+ gtk_container_add (GTK_CONTAINER (scrolled), treeview);
+
+ if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK)
+ {
+ char *mimetype, *description;
+
+ gtk_tree_selection_get_selected (selection, NULL, &iter);
+ gtk_tree_model_get (GTK_TREE_MODEL (store), &iter,
+ 0, &description,
+ 1, &mimetype,
+ -1);
+
+ type_add_custom_type (row, mimetype, description, &iter);
+ gtk_combo_box_set_active_iter (GTK_COMBO_BOX (row->type_widget),
+ &iter);
+ }
+ else
+ {
+ gtk_combo_box_set_active (GTK_COMBO_BOX (row->type_widget), 0);
+ }
+
+ gtk_widget_destroy (dialog);
+ }
+
+ caja_query_editor_changed (row->editor);
+}
+
+static GtkWidget *
+type_row_create_widgets (CajaQueryEditorRow *row)
+{
+ GtkWidget *combo;
+ GtkCellRenderer *cell;
+ GtkListStore *store;
+ GtkTreeIter iter;
+ int i;
+
+ store = gtk_list_store_new (4, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_STRING, G_TYPE_BOOLEAN);
+ combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL (store));
+ g_object_unref (store);
+
+ 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_combo_box_set_row_separator_func (GTK_COMBO_BOX (combo),
+ type_separator_func,
+ NULL, NULL);
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter, 0, _("Any"), -1);
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter, 0, "---", -1);
+
+ for (i = 0; i < G_N_ELEMENTS (mime_type_groups); i++)
+ {
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ 0, gettext (mime_type_groups[i].name),
+ 1, mime_type_groups[i].mimetypes,
+ -1);
+ }
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter, 0, "---", -1);
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter, 0, _("Other Type..."), 3, TRUE, -1);
+
+ gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 0);
+
+ g_signal_connect (combo, "changed",
+ G_CALLBACK (type_combo_changed),
+ row);
+
+ gtk_widget_show (combo);
+
+ gtk_box_pack_start (GTK_BOX (row->hbox), combo, FALSE, FALSE, 0);
+
+ return combo;
+}
+
+static void
+type_row_add_to_query (CajaQueryEditorRow *row,
+ CajaQuery *query)
+{
+ GtkTreeIter iter;
+ char **mimetypes;
+ char *mimetype;
+ GtkTreeModel *model;
+
+ if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (row->type_widget),
+ &iter))
+ {
+ return;
+ }
+
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (row->type_widget));
+ gtk_tree_model_get (model, &iter, 1, &mimetypes, 2, &mimetype, -1);
+
+ if (mimetypes != NULL)
+ {
+ while (*mimetypes != NULL)
+ {
+ caja_query_add_mime_type (query, *mimetypes);
+ mimetypes++;
+ }
+ }
+ if (mimetype)
+ {
+ caja_query_add_mime_type (query, mimetype);
+ g_free (mimetype);
+ }
+}
+
+static void
+type_row_free_data (CajaQueryEditorRow *row)
+{
+}
+
+static gboolean
+all_group_types_in_list (char **group_types, GList *mime_types)
+{
+ GList *l;
+ char **group_type;
+ char *mime_type;
+ gboolean found;
+
+ group_type = group_types;
+ while (*group_type != NULL)
+ {
+ found = FALSE;
+
+ for (l = mime_types; l != NULL; l = l->next)
+ {
+ mime_type = l->data;
+
+ if (strcmp (mime_type, *group_type) == 0)
+ {
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (!found)
+ {
+ return FALSE;
+ }
+ group_type++;
+ }
+ return TRUE;
+}
+
+static GList *
+remove_group_types_from_list (char **group_types, GList *mime_types)
+{
+ GList *l, *next;
+ char **group_type;
+ char *mime_type;
+ gboolean found;
+
+ group_type = group_types;
+ while (*group_type != NULL)
+ {
+ found = FALSE;
+
+ for (l = mime_types; l != NULL; l = next)
+ {
+ mime_type = l->data;
+ next = l->next;
+
+ if (strcmp (mime_type, *group_type) == 0)
+ {
+ mime_types = g_list_remove_link (mime_types, l);
+ g_free (mime_type);
+ break;
+ }
+ }
+
+ group_type++;
+ }
+ return mime_types;
+}
+
+
+static void
+type_add_rows_from_query (CajaQueryEditor *editor,
+ CajaQuery *query)
+{
+ GList *mime_types;
+ char *mime_type;
+ const char *desc;
+ CajaQueryEditorRow *row;
+ GtkTreeIter iter;
+ int i;
+ GtkTreeModel *model;
+ GList *l;
+
+ mime_types = caja_query_get_mime_types (query);
+
+ if (mime_types == NULL)
+ {
+ return;
+ }
+
+ for (i = 0; i < G_N_ELEMENTS (mime_type_groups); i++)
+ {
+ if (all_group_types_in_list (mime_type_groups[i].mimetypes,
+ mime_types))
+ {
+ mime_types = remove_group_types_from_list (mime_type_groups[i].mimetypes,
+ mime_types);
+
+ row = caja_query_editor_add_row (editor,
+ CAJA_QUERY_EDITOR_ROW_TYPE);
+
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (row->type_widget));
+
+ gtk_tree_model_iter_nth_child (model, &iter, NULL, i + 2);
+ gtk_combo_box_set_active_iter (GTK_COMBO_BOX (row->type_widget),
+ &iter);
+ }
+ }
+
+ for (l = mime_types; l != NULL; l = l->next)
+ {
+ mime_type = l->data;
+
+ desc = g_content_type_get_description (mime_type);
+ if (desc == NULL)
+ {
+ desc = mime_type;
+ }
+
+ row = caja_query_editor_add_row (editor,
+ CAJA_QUERY_EDITOR_ROW_TYPE);
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (row->type_widget));
+
+ type_add_custom_type (row, mime_type, desc, &iter);
+ gtk_combo_box_set_active_iter (GTK_COMBO_BOX (row->type_widget),
+ &iter);
+ }
+
+ eel_g_list_free_deep (mime_types);
+
+}
+
+/* End of row types */
+
+static CajaQueryEditorRowType
+get_next_free_type (CajaQueryEditor *editor)
+{
+ CajaQueryEditorRow *row;
+ CajaQueryEditorRowType type;
+ gboolean found;
+ GList *l;
+
+
+ for (type = 0; type < CAJA_QUERY_EDITOR_ROW_LAST; type++)
+ {
+ found = FALSE;
+ for (l = editor->details->rows; l != NULL; l = l->next)
+ {
+ row = l->data;
+ if (row->type == type)
+ {
+ found = TRUE;
+ break;
+ }
+ }
+ if (!found)
+ {
+ return type;
+ }
+ }
+ return CAJA_QUERY_EDITOR_ROW_TYPE;
+}
+
+static void
+remove_row_cb (GtkButton *clicked_button, CajaQueryEditorRow *row)
+{
+ CajaQueryEditor *editor;
+
+ editor = row->editor;
+ gtk_container_remove (GTK_CONTAINER (editor->details->visible_vbox),
+ row->hbox);
+
+ editor->details->rows = g_list_remove (editor->details->rows, row);
+
+ row_type[row->type].free_data (row);
+ g_free (row);
+
+ caja_query_editor_changed (editor);
+}
+
+static void
+create_type_widgets (CajaQueryEditorRow *row)
+{
+ row->type_widget = row_type[row->type].create_widgets (row);
+}
+
+static void
+row_type_combo_changed_cb (GtkComboBox *combo_box, CajaQueryEditorRow *row)
+{
+ CajaQueryEditorRowType type;
+
+ type = gtk_combo_box_get_active (combo_box);
+
+ if (type == row->type)
+ {
+ return;
+ }
+
+ if (row->type_widget != NULL)
+ {
+ gtk_widget_destroy (row->type_widget);
+ row->type_widget = NULL;
+ }
+
+ row_type[row->type].free_data (row);
+ row->data = NULL;
+
+ row->type = type;
+
+ create_type_widgets (row);
+
+ caja_query_editor_changed (row->editor);
+}
+
+static CajaQueryEditorRow *
+caja_query_editor_add_row (CajaQueryEditor *editor,
+ CajaQueryEditorRowType type)
+{
+ GtkWidget *hbox, *button, *image, *combo;
+ CajaQueryEditorRow *row;
+ int i;
+
+ row = g_new0 (CajaQueryEditorRow, 1);
+ row->editor = editor;
+ row->type = type;
+
+ hbox = gtk_hbox_new (FALSE, 6);
+ row->hbox = hbox;
+ gtk_widget_show (hbox);
+ gtk_box_pack_start (GTK_BOX (editor->details->visible_vbox), hbox, FALSE, FALSE, 0);
+
+ combo = gtk_combo_box_new_text ();
+ row->combo = combo;
+ for (i = 0; i < CAJA_QUERY_EDITOR_ROW_LAST; i++)
+ {
+ gtk_combo_box_append_text (GTK_COMBO_BOX (combo), gettext (row_type[i].name));
+ }
+ gtk_widget_show (combo);
+ gtk_box_pack_start (GTK_BOX (hbox), combo, FALSE, FALSE, 0);
+
+ gtk_combo_box_set_active (GTK_COMBO_BOX (combo), row->type);
+
+ editor->details->rows = g_list_append (editor->details->rows, row);
+
+ g_signal_connect (combo, "changed",
+ G_CALLBACK (row_type_combo_changed_cb), row);
+
+ create_type_widgets (row);
+
+ button = gtk_button_new ();
+ image = gtk_image_new_from_stock (GTK_STOCK_REMOVE,
+ GTK_ICON_SIZE_SMALL_TOOLBAR);
+ gtk_container_add (GTK_CONTAINER (button), image);
+ gtk_widget_show (image);
+ gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
+ gtk_widget_show (button);
+
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (remove_row_cb), row);
+ gtk_widget_set_tooltip_text (button,
+ _("Remove this criterion from the search"));
+
+ gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, FALSE, 0);
+
+ return row;
+}
+
+static void
+go_search_cb (GtkButton *clicked_button, CajaQueryEditor *editor)
+{
+ caja_query_editor_changed_force (editor, TRUE);
+}
+
+static void
+add_new_row_cb (GtkButton *clicked_button, CajaQueryEditor *editor)
+{
+ caja_query_editor_add_row (editor, get_next_free_type (editor));
+ caja_query_editor_changed (editor);
+}
+
+static void
+caja_query_editor_init (CajaQueryEditor *editor)
+{
+ GtkWidget *hbox, *label, *button;
+ char *label_markup;
+
+ editor->details = g_new0 (CajaQueryEditorDetails, 1);
+ editor->details->is_visible = TRUE;
+
+ editor->details->invisible_vbox = gtk_vbox_new (FALSE, 6);
+ gtk_box_pack_start (GTK_BOX (editor), editor->details->invisible_vbox,
+ FALSE, FALSE, 0);
+ editor->details->visible_vbox = gtk_vbox_new (FALSE, 6);
+ gtk_box_pack_start (GTK_BOX (editor), editor->details->visible_vbox,
+ FALSE, FALSE, 0);
+ /* Only show visible vbox */
+ gtk_widget_show (editor->details->visible_vbox);
+
+ /* Create invisible part: */
+ hbox = gtk_hbox_new (FALSE, 6);
+ gtk_box_pack_start (GTK_BOX (editor->details->invisible_vbox),
+ hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ label = gtk_label_new ("");
+ label_markup = g_strconcat ("<b>", _("Search Folder"), "</b>", NULL);
+ gtk_label_set_markup (GTK_LABEL (label), label_markup);
+ g_free (label_markup);
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+
+ button = gtk_button_new_with_label (_("Edit"));
+ gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (edit_clicked), editor);
+
+ gtk_widget_set_tooltip_text (button,
+ _("Edit the saved search"));
+}
+
+void
+caja_query_editor_set_default_query (CajaQueryEditor *editor)
+{
+ if (!editor->details->is_indexed)
+ {
+ caja_query_editor_add_row (editor, CAJA_QUERY_EDITOR_ROW_LOCATION);
+ caja_query_editor_changed (editor);
+ }
+}
+
+static void
+finish_first_line (CajaQueryEditor *editor, GtkWidget *hbox, gboolean use_go)
+{
+ GtkWidget *button, *image;
+
+ button = gtk_button_new ();
+ image = gtk_image_new_from_stock (GTK_STOCK_ADD,
+ GTK_ICON_SIZE_SMALL_TOOLBAR);
+ gtk_container_add (GTK_CONTAINER (button), image);
+ gtk_widget_show (image);
+ gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
+ gtk_widget_show (button);
+
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (add_new_row_cb), editor);
+
+ gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, FALSE, 0);
+
+ gtk_widget_set_tooltip_text (button,
+ _("Add a new criterion to this search"));
+
+ if (!editor->details->is_indexed)
+ {
+ if (use_go)
+ {
+ button = gtk_button_new_with_label (_("Go"));
+ }
+ else
+ {
+ button = gtk_button_new_with_label (_("Reload"));
+ }
+ gtk_widget_show (button);
+
+ gtk_widget_set_tooltip_text (button,
+ _("Perform or update the search"));
+
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (go_search_cb), editor);
+
+ gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, FALSE, 0);
+ }
+}
+
+static void
+setup_internal_entry (CajaQueryEditor *editor)
+{
+ GtkWidget *hbox, *label;
+ char *label_markup;
+
+ /* Create visible part: */
+ hbox = gtk_hbox_new (FALSE, 6);
+ gtk_widget_show (hbox);
+ gtk_box_pack_start (GTK_BOX (editor->details->visible_vbox), hbox, FALSE, FALSE, 0);
+
+ label = gtk_label_new ("");
+ label_markup = g_strconcat ("<b>", _("_Search for:"), "</b>", NULL);
+ gtk_label_set_markup_with_mnemonic (GTK_LABEL (label), label_markup);
+ g_free (label_markup);
+ gtk_widget_show (label);
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+
+ editor->details->entry = gtk_entry_new ();
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), editor->details->entry);
+ gtk_box_pack_start (GTK_BOX (hbox), editor->details->entry, TRUE, TRUE, 0);
+
+ g_signal_connect (editor->details->entry, "activate",
+ G_CALLBACK (entry_activate_cb), editor);
+ g_signal_connect (editor->details->entry, "changed",
+ G_CALLBACK (entry_changed_cb), editor);
+ gtk_widget_show (editor->details->entry);
+
+ finish_first_line (editor, hbox, TRUE);
+}
+
+static void
+setup_external_entry (CajaQueryEditor *editor, GtkWidget *entry)
+{
+ GtkWidget *hbox, *label;
+
+ /* Create visible part: */
+ hbox = gtk_hbox_new (FALSE, 6);
+ gtk_widget_show (hbox);
+ gtk_box_pack_start (GTK_BOX (editor->details->visible_vbox), hbox, FALSE, FALSE, 0);
+
+ label = gtk_label_new (_("Search results"));
+ gtk_widget_show (label);
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+
+ editor->details->entry = entry;
+ g_signal_connect (editor->details->entry, "activate",
+ G_CALLBACK (entry_activate_cb), editor);
+ g_signal_connect (editor->details->entry, "changed",
+ G_CALLBACK (entry_changed_cb), editor);
+
+ finish_first_line (editor, hbox, FALSE);
+
+}
+
+void
+caja_query_editor_set_visible (CajaQueryEditor *editor,
+ gboolean visible)
+{
+ editor->details->is_visible = visible;
+ if (visible)
+ {
+ gtk_widget_show (editor->details->visible_vbox);
+ gtk_widget_hide (editor->details->invisible_vbox);
+ }
+ else
+ {
+ gtk_widget_hide (editor->details->visible_vbox);
+ gtk_widget_show (editor->details->invisible_vbox);
+ }
+}
+
+static gboolean
+query_is_valid (CajaQueryEditor *editor)
+{
+ const char *text;
+
+ text = gtk_entry_get_text (GTK_ENTRY (editor->details->entry));
+
+ return text != NULL && text[0] != '\0';
+}
+
+static void
+caja_query_editor_changed_force (CajaQueryEditor *editor, gboolean force_reload)
+{
+ CajaQuery *query;
+
+ if (editor->details->change_frozen)
+ {
+ return;
+ }
+
+ if (query_is_valid (editor))
+ {
+ query = caja_query_editor_get_query (editor);
+ g_signal_emit (editor, signals[CHANGED], 0,
+ query, editor->details->is_indexed || force_reload);
+ g_object_unref (query);
+ }
+}
+
+static void
+caja_query_editor_changed (CajaQueryEditor *editor)
+{
+ caja_query_editor_changed_force (editor, FALSE);
+}
+
+void
+caja_query_editor_grab_focus (CajaQueryEditor *editor)
+{
+ if (editor->details->is_visible)
+ {
+ gtk_widget_grab_focus (editor->details->entry);
+ }
+}
+
+CajaQuery *
+caja_query_editor_get_query (CajaQueryEditor *editor)
+{
+ const char *query_text;
+ CajaQuery *query;
+ GList *l;
+ CajaQueryEditorRow *row;
+
+ if (editor == NULL || editor->details == NULL || editor->details->entry == NULL)
+ {
+ return NULL;
+ }
+
+ query_text = gtk_entry_get_text (GTK_ENTRY (editor->details->entry));
+
+ /* Empty string is a NULL query */
+ if (query_text && query_text[0] == '\0')
+ {
+ return NULL;
+ }
+
+ query = caja_query_new ();
+ caja_query_set_text (query, query_text);
+
+ for (l = editor->details->rows; l != NULL; l = l->next)
+ {
+ row = l->data;
+
+ row_type[row->type].add_to_query (row, query);
+ }
+
+ return query;
+}
+
+void
+caja_query_editor_clear_query (CajaQueryEditor *editor)
+{
+ editor->details->change_frozen = TRUE;
+ gtk_entry_set_text (GTK_ENTRY (editor->details->entry), "");
+
+ g_free (editor->details->last_set_query_text);
+ editor->details->last_set_query_text = g_strdup ("");
+
+ editor->details->change_frozen = FALSE;
+}
+
+GtkWidget *
+caja_query_editor_new (gboolean start_hidden,
+ gboolean is_indexed)
+{
+ GtkWidget *editor;
+
+ editor = g_object_new (CAJA_TYPE_QUERY_EDITOR, NULL);
+
+ CAJA_QUERY_EDITOR (editor)->details->is_indexed = is_indexed;
+
+ caja_query_editor_set_visible (CAJA_QUERY_EDITOR (editor),
+ !start_hidden);
+
+ setup_internal_entry (CAJA_QUERY_EDITOR (editor));
+
+ return editor;
+}
+
+static void
+detach_from_external_entry (CajaQueryEditor *editor)
+{
+ if (editor->details->bar != NULL)
+ {
+ caja_search_bar_return_entry (editor->details->bar);
+ g_signal_handlers_block_by_func (editor->details->entry,
+ entry_activate_cb,
+ editor);
+ g_signal_handlers_block_by_func (editor->details->entry,
+ entry_changed_cb,
+ editor);
+ }
+}
+
+static void
+attach_to_external_entry (CajaQueryEditor *editor)
+{
+ if (editor->details->bar != NULL)
+ {
+ caja_search_bar_borrow_entry (editor->details->bar);
+ g_signal_handlers_unblock_by_func (editor->details->entry,
+ entry_activate_cb,
+ editor);
+ g_signal_handlers_unblock_by_func (editor->details->entry,
+ entry_changed_cb,
+ editor);
+
+ editor->details->change_frozen = TRUE;
+ gtk_entry_set_text (GTK_ENTRY (editor->details->entry),
+ editor->details->last_set_query_text);
+ editor->details->change_frozen = FALSE;
+ }
+}
+
+GtkWidget*
+caja_query_editor_new_with_bar (gboolean start_hidden,
+ gboolean is_indexed,
+ gboolean start_attached,
+ CajaSearchBar *bar,
+ CajaWindowSlot *slot)
+{
+ GtkWidget *entry;
+ CajaQueryEditor *editor;
+
+ editor = CAJA_QUERY_EDITOR (g_object_new (CAJA_TYPE_QUERY_EDITOR, NULL));
+ editor->details->is_indexed = is_indexed;
+
+ caja_query_editor_set_visible (editor, !start_hidden);
+
+ editor->details->bar = bar;
+ eel_add_weak_pointer (&editor->details->bar);
+
+ editor->details->slot = slot;
+
+ entry = caja_search_bar_borrow_entry (bar);
+ setup_external_entry (editor, entry);
+ if (!start_attached)
+ {
+ detach_from_external_entry (editor);
+ }
+
+ g_signal_connect_object (slot, "active",
+ G_CALLBACK (attach_to_external_entry),
+ editor, G_CONNECT_SWAPPED);
+ g_signal_connect_object (slot, "inactive",
+ G_CALLBACK (detach_from_external_entry),
+ editor, G_CONNECT_SWAPPED);
+
+ return GTK_WIDGET (editor);
+}
+
+void
+caja_query_editor_set_query (CajaQueryEditor *editor, CajaQuery *query)
+{
+ CajaQueryEditorRowType type;
+ char *text;
+
+ if (!query)
+ {
+ caja_query_editor_clear_query (editor);
+ return;
+ }
+
+ text = caja_query_get_text (query);
+
+ if (!text)
+ {
+ text = g_strdup ("");
+ }
+
+ editor->details->change_frozen = TRUE;
+ gtk_entry_set_text (GTK_ENTRY (editor->details->entry), text);
+
+ for (type = 0; type < CAJA_QUERY_EDITOR_ROW_LAST; type++)
+ {
+ row_type[type].add_rows_from_query (editor, query);
+ }
+
+ editor->details->change_frozen = FALSE;
+
+ g_free (editor->details->last_set_query_text);
+ editor->details->last_set_query_text = text;
+}
diff --git a/src/caja-query-editor.h b/src/caja-query-editor.h
new file mode 100644
index 00000000..bd1982b7
--- /dev/null
+++ b/src/caja-query-editor.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * Copyright (C) 2005 Red Hat, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Alexander Larsson <[email protected]>
+ *
+ */
+
+#ifndef CAJA_QUERY_EDITOR_H
+#define CAJA_QUERY_EDITOR_H
+
+#include <gtk/gtk.h>
+#include <libcaja-private/caja-query.h>
+#include <libcaja-private/caja-window-info.h>
+#include <caja-search-bar.h>
+
+#define CAJA_TYPE_QUERY_EDITOR caja_query_editor_get_type()
+#define CAJA_QUERY_EDITOR(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_QUERY_EDITOR, CajaQueryEditor))
+#define CAJA_QUERY_EDITOR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_QUERY_EDITOR, CajaQueryEditorClass))
+#define CAJA_IS_QUERY_EDITOR(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_QUERY_EDITOR))
+#define CAJA_IS_QUERY_EDITOR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_QUERY_EDITOR))
+#define CAJA_QUERY_EDITOR_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_QUERY_EDITOR, CajaQueryEditorClass))
+
+typedef struct CajaQueryEditorDetails CajaQueryEditorDetails;
+
+typedef struct CajaQueryEditor
+{
+ GtkVBox parent;
+ CajaQueryEditorDetails *details;
+} CajaQueryEditor;
+
+typedef struct
+{
+ GtkVBoxClass parent_class;
+
+ void (* changed) (CajaQueryEditor *editor,
+ CajaQuery *query,
+ gboolean reload);
+ void (* cancel) (CajaQueryEditor *editor);
+} CajaQueryEditorClass;
+
+GType caja_query_editor_get_type (void);
+GtkWidget* caja_query_editor_new (gboolean start_hidden,
+ gboolean is_indexed);
+GtkWidget* caja_query_editor_new_with_bar (gboolean start_hidden,
+ gboolean is_indexed,
+ gboolean start_attached,
+ CajaSearchBar *bar,
+ CajaWindowSlot *slot);
+void caja_query_editor_set_default_query (CajaQueryEditor *editor);
+
+void caja_query_editor_grab_focus (CajaQueryEditor *editor);
+void caja_query_editor_clear_query (CajaQueryEditor *editor);
+
+CajaQuery *caja_query_editor_get_query (CajaQueryEditor *editor);
+void caja_query_editor_set_query (CajaQueryEditor *editor,
+ CajaQuery *query);
+void caja_query_editor_set_visible (CajaQueryEditor *editor,
+ gboolean visible);
+
+#endif /* CAJA_QUERY_EDITOR_H */
diff --git a/src/caja-search-bar.c b/src/caja-search-bar.c
new file mode 100644
index 00000000..6d40239f
--- /dev/null
+++ b/src/caja-search-bar.c
@@ -0,0 +1,256 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * Copyright (C) 2005 Novell, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Anders Carlsson <[email protected]>
+ *
+ */
+
+#include <config.h>
+#include "caja-search-bar.h"
+
+#include <glib/gi18n.h>
+#include <eel/eel-gtk-macros.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+
+struct CajaSearchBarDetails
+{
+ GtkWidget *entry;
+ gboolean entry_borrowed;
+};
+
+enum
+{
+ ACTIVATE,
+ CANCEL,
+ FOCUS_IN,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+static void caja_search_bar_class_init (CajaSearchBarClass *class);
+static void caja_search_bar_init (CajaSearchBar *bar);
+
+EEL_CLASS_BOILERPLATE (CajaSearchBar,
+ caja_search_bar,
+ GTK_TYPE_EVENT_BOX)
+
+
+static void
+finalize (GObject *object)
+{
+ CajaSearchBar *bar;
+
+ bar = CAJA_SEARCH_BAR (object);
+
+ g_free (bar->details);
+
+ EEL_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
+}
+
+static void
+caja_search_bar_class_init (CajaSearchBarClass *class)
+{
+ GObjectClass *gobject_class;
+ GtkBindingSet *binding_set;
+
+ gobject_class = G_OBJECT_CLASS (class);
+ gobject_class->finalize = finalize;
+
+ signals[ACTIVATE] =
+ g_signal_new ("activate",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaSearchBarClass, activate),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[FOCUS_IN] =
+ g_signal_new ("focus-in",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaSearchBarClass, focus_in),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[CANCEL] =
+ g_signal_new ("cancel",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (CajaSearchBarClass, cancel),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ binding_set = gtk_binding_set_by_class (class);
+ gtk_binding_entry_add_signal (binding_set, GDK_Escape, 0, "cancel", 0);
+}
+
+static gboolean
+entry_has_text (CajaSearchBar *bar)
+{
+ const char *text;
+
+ text = gtk_entry_get_text (GTK_ENTRY (bar->details->entry));
+
+ return text != NULL && text[0] != '\0';
+}
+
+static void
+entry_icon_release_cb (GtkEntry *entry,
+ GtkEntryIconPosition position,
+ GdkEvent *event,
+ CajaSearchBar *bar)
+{
+ g_signal_emit_by_name (entry, "activate", 0);
+}
+
+static void
+entry_activate_cb (GtkWidget *entry, CajaSearchBar *bar)
+{
+ if (entry_has_text (bar) && !bar->details->entry_borrowed)
+ {
+ g_signal_emit (bar, signals[ACTIVATE], 0);
+ }
+}
+
+static gboolean
+focus_in_event_callback (GtkWidget *widget,
+ GdkEventFocus *event,
+ gpointer user_data)
+{
+ CajaSearchBar *bar;
+
+ bar = CAJA_SEARCH_BAR (user_data);
+
+ g_signal_emit (bar, signals[FOCUS_IN], 0);
+
+ return FALSE;
+}
+
+static void
+caja_search_bar_init (CajaSearchBar *bar)
+{
+ GtkWidget *alignment;
+ GtkWidget *hbox;
+ GtkWidget *label;
+
+ bar->details = g_new0 (CajaSearchBarDetails, 1);
+
+ gtk_event_box_set_visible_window (GTK_EVENT_BOX (bar), FALSE);
+
+ alignment = gtk_alignment_new (0.5, 0.5,
+ 1.0, 1.0);
+ gtk_alignment_set_padding (GTK_ALIGNMENT (alignment),
+ 0, 0, 6, 6);
+ gtk_widget_show (alignment);
+ gtk_container_add (GTK_CONTAINER (bar), alignment);
+
+ hbox = gtk_hbox_new (FALSE, 6);
+ gtk_widget_show (hbox);
+ gtk_container_add (GTK_CONTAINER (alignment), hbox);
+
+ label = gtk_label_new (_("Search:"));
+ gtk_widget_show (label);
+
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+
+ bar->details->entry = gtk_entry_new ();
+ gtk_entry_set_icon_from_stock (GTK_ENTRY (bar->details->entry),
+ GTK_ENTRY_ICON_SECONDARY,
+ GTK_STOCK_FIND);
+ gtk_box_pack_start (GTK_BOX (hbox), bar->details->entry, TRUE, TRUE, 0);
+
+ g_signal_connect (bar->details->entry, "activate",
+ G_CALLBACK (entry_activate_cb), bar);
+ g_signal_connect (bar->details->entry, "icon-release",
+ G_CALLBACK (entry_icon_release_cb), bar);
+ g_signal_connect (bar->details->entry, "focus-in-event",
+ G_CALLBACK (focus_in_event_callback), bar);
+
+ gtk_widget_show (bar->details->entry);
+}
+
+GtkWidget *
+caja_search_bar_borrow_entry (CajaSearchBar *bar)
+{
+ GtkBindingSet *binding_set;
+
+ bar->details->entry_borrowed = TRUE;
+
+ binding_set = gtk_binding_set_by_class (G_OBJECT_GET_CLASS (bar));
+ gtk_binding_entry_remove (binding_set, GDK_Escape, 0);
+ return bar->details->entry;
+}
+
+void
+caja_search_bar_return_entry (CajaSearchBar *bar)
+{
+ GtkBindingSet *binding_set;
+
+ bar->details->entry_borrowed = FALSE;
+
+ binding_set = gtk_binding_set_by_class (G_OBJECT_GET_CLASS (bar));
+ gtk_binding_entry_add_signal (binding_set, GDK_Escape, 0, "cancel", 0);
+}
+
+GtkWidget *
+caja_search_bar_new (void)
+{
+ GtkWidget *bar;
+
+ bar = g_object_new (CAJA_TYPE_SEARCH_BAR, NULL);
+
+ return bar;
+}
+
+CajaQuery *
+caja_search_bar_get_query (CajaSearchBar *bar)
+{
+ const char *query_text;
+ CajaQuery *query;
+
+ query_text = gtk_entry_get_text (GTK_ENTRY (bar->details->entry));
+
+ /* Empty string is a NULL query */
+ if (query_text && query_text[0] == '\0')
+ {
+ return NULL;
+ }
+
+ query = caja_query_new ();
+ caja_query_set_text (query, query_text);
+
+ return query;
+}
+
+void
+caja_search_bar_grab_focus (CajaSearchBar *bar)
+{
+ gtk_widget_grab_focus (bar->details->entry);
+}
+
+void
+caja_search_bar_clear (CajaSearchBar *bar)
+{
+ gtk_entry_set_text (GTK_ENTRY (bar->details->entry), "");
+}
diff --git a/src/caja-search-bar.h b/src/caja-search-bar.h
new file mode 100644
index 00000000..a3946c9f
--- /dev/null
+++ b/src/caja-search-bar.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * Copyright (C) 2005 Novell, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Anders Carlsson <[email protected]>
+ *
+ */
+
+#ifndef CAJA_SEARCH_BAR_H
+#define CAJA_SEARCH_BAR_H
+
+#include <gtk/gtk.h>
+#include <libcaja-private/caja-query.h>
+
+#define CAJA_TYPE_SEARCH_BAR caja_search_bar_get_type()
+#define CAJA_SEARCH_BAR(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_SEARCH_BAR, CajaSearchBar))
+#define CAJA_SEARCH_BAR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_SEARCH_BAR, CajaSearchBarClass))
+#define CAJA_IS_SEARCH_BAR(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_SEARCH_BAR))
+#define CAJA_IS_SEARCH_BAR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_SEARCH_BAR))
+#define CAJA_SEARCH_BAR_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_SEARCH_BAR, CajaSearchBarClass))
+
+typedef struct CajaSearchBarDetails CajaSearchBarDetails;
+
+typedef struct CajaSearchBar
+{
+ GtkEventBox parent;
+ CajaSearchBarDetails *details;
+} CajaSearchBar;
+
+typedef struct
+{
+ GtkEventBoxClass parent_class;
+
+ void (* activate) (CajaSearchBar *bar);
+ void (* cancel) (CajaSearchBar *bar);
+ void (* focus_in) (CajaSearchBar *bar);
+} CajaSearchBarClass;
+
+GType caja_search_bar_get_type (void);
+GtkWidget* caja_search_bar_new (void);
+
+GtkWidget * caja_search_bar_borrow_entry (CajaSearchBar *bar);
+void caja_search_bar_return_entry (CajaSearchBar *bar);
+void caja_search_bar_grab_focus (CajaSearchBar *bar);
+CajaQuery *caja_search_bar_get_query (CajaSearchBar *bar);
+void caja_search_bar_clear (CajaSearchBar *bar);
+
+#endif /* CAJA_SEARCH_BAR_H */
diff --git a/src/caja-self-check-functions.c b/src/caja-self-check-functions.c
new file mode 100644
index 00000000..92bb7a3e
--- /dev/null
+++ b/src/caja-self-check-functions.c
@@ -0,0 +1,40 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 1999, 2000 Eazel, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Author: Darin Adler <[email protected]>
+ */
+
+/* caja-self-check-functions.c: Wrapper for all self check functions
+ * in Caja proper.
+ */
+
+#include <config.h>
+
+#if ! defined (CAJA_OMIT_SELF_CHECK)
+
+#include "caja-self-check-functions.h"
+
+void caja_run_self_checks(void)
+{
+ CAJA_FOR_EACH_SELF_CHECK_FUNCTION (CAJA_CALL_SELF_CHECK_FUNCTION)
+}
+
+#endif /* ! CAJA_OMIT_SELF_CHECK */
diff --git a/src/caja-self-check-functions.h b/src/caja-self-check-functions.h
new file mode 100644
index 00000000..cc6e364e
--- /dev/null
+++ b/src/caja-self-check-functions.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 1999, 2000 Eazel, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Author: Darin Adler <[email protected]>
+ */
+
+/* caja-self-check-functions.h: Wrapper and prototypes for all self
+ * check functions in Caja proper.
+ */
+
+
+void caja_run_self_checks (void);
+
+/* Putting the prototypes for these self-check functions in each
+ header file for the files they are defined in would make compiling
+ the self-check framework take way too long (since one file would
+ have to include everything).
+
+ So we put the list of functions here instead.
+
+ Instead of just putting prototypes here, we put this macro that
+ can be used to do operations on the whole list of functions.
+*/
+
+#define CAJA_FOR_EACH_SELF_CHECK_FUNCTION(macro) \
+/* Add new self-check functions to the list above this line. */
+
+/* Generate prototypes for all the functions. */
+CAJA_FOR_EACH_SELF_CHECK_FUNCTION (CAJA_SELF_CHECK_FUNCTION_PROTOTYPE)
diff --git a/src/caja-shell-ui.xml b/src/caja-shell-ui.xml
new file mode 100644
index 00000000..0c27d173
--- /dev/null
+++ b/src/caja-shell-ui.xml
@@ -0,0 +1,87 @@
+<ui>
+<accelerator action="ZoomInAccel"/>
+<accelerator action="ZoomInAccel2"/>
+<accelerator action="ZoomOutAccel"/>
+<menubar name="MenuBar">
+ <menu action="File">
+ <placeholder name="New Items Placeholder"/>
+ <separator/>
+ <placeholder name="Open Placeholder"/>
+ <separator/>
+ <placeholder name="Location Placeholder"/>
+ <menuitem name="Connect to Server" action="Connect to Server"/>
+ <separator/>
+ <placeholder name="File Items Placeholder"/>
+ <separator/>
+ <placeholder name="Global File Items Placeholder"/>
+ <separator/>
+ <placeholder name="Extension Actions"/>
+ <separator/>
+ <placeholder name="Close Items Placeholder"/>
+ <menuitem name="Close" action="Close"/>
+ </menu>
+ <menu action="Edit">
+ <placeholder name="Clipboard Actions">
+ </placeholder>
+ <separator/>
+ <placeholder name="Copy Move to Placeholder"/>
+ <separator/>
+ <placeholder name="Select Items"/>
+ <separator/>
+ <placeholder name="File Items Placeholder"/>
+ <separator/>
+ <placeholder name="Dangerous File Items Placeholder"/>
+ <separator/>
+ <placeholder name="Edit Items Placeholder"/>
+ <placeholder name="Global Edit Items Placeholder"/>
+ <separator/>
+ <placeholder name="Extension Actions"/>
+ <separator/>
+ <menuitem name="Backgrounds and Emblems" action="Backgrounds and Emblems"/>
+ <menuitem name="Preferences" action="Preferences"/>
+ </menu>
+ <menu action="View">
+ <menuitem name="Stop" action="Stop"/>
+ <menuitem name="Reload" action="Reload"/>
+ <separator/>
+ <placeholder name="Show Hide Placeholder"/>
+ <separator/>
+ <placeholder name="View Preferences Placeholder"/>
+ <separator/>
+ <placeholder name="View Items Placeholder"/>
+ <separator/>
+ <placeholder name="Zoom Items Placeholder">
+ <menuitem name="Zoom In" action="Zoom In"/>
+ <menuitem name="Zoom Out" action="Zoom Out"/>
+ <menuitem name="Zoom Normal" action="Zoom Normal"/>
+ </placeholder>
+ <placeholder name="View Choices">
+ <separator/>
+ <placeholder name="Extra Viewer"/>
+ <separator name="Before Short List"/>
+ <placeholder name="Short List"/>
+ </placeholder>
+ </menu>
+ <placeholder name="Other Menus"/>
+ <menu action="Help">
+ <menuitem name="Caja Manual" action="Caja Manual"/>
+ <menuitem name="About Caja" action="About Caja"/>
+ </menu>
+</menubar>
+<popup name="background">
+ <placeholder name="Before Zoom Items">
+ <placeholder name="New Window Items"/>
+ <placeholder name="New Object Items"/>
+ <separator/>
+ <placeholder name="Extension Actions"/>
+ </placeholder>
+ <separator/>
+ <placeholder name="Zoom Items">
+ <menuitem name="Zoom In" action="Zoom In"/>
+ <menuitem name="Zoom Out" action="Zoom Out"/>
+ <menuitem name="Zoom Normal" action="Zoom Normal"/>
+ </placeholder>
+ <separator/>
+ <placeholder name="After Zoom Items"/>
+</popup>
+</ui>
diff --git a/src/caja-side-pane.c b/src/caja-side-pane.c
new file mode 100644
index 00000000..0037a0fb
--- /dev/null
+++ b/src/caja-side-pane.c
@@ -0,0 +1,675 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-side-pane.c
+ *
+ * Copyright (C) 2002 Ximian Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Author: Dave Camp <[email protected]>
+ */
+
+#include <config.h>
+#include "caja-side-pane.h"
+
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-gtk-macros.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+
+typedef struct
+{
+ char *title;
+ char *tooltip;
+ GtkWidget *widget;
+ GtkWidget *menu_item;
+ GtkWidget *shortcut;
+} SidePanel;
+
+struct _CajaSidePaneDetails
+{
+ GtkWidget *notebook;
+ GtkWidget *menu;
+
+ GtkWidget *title_frame;
+ GtkWidget *title_hbox;
+ GtkWidget *title_label;
+ GtkWidget *shortcut_box;
+ GList *panels;
+};
+
+static void caja_side_pane_class_init (CajaSidePaneClass *klass);
+static void caja_side_pane_init (GObject *object);
+static void caja_side_pane_dispose (GObject *object);
+static void caja_side_pane_finalize (GObject *object);
+
+enum
+{
+ CLOSE_REQUESTED,
+ SWITCH_PAGE,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+EEL_CLASS_BOILERPLATE (CajaSidePane, caja_side_pane, GTK_TYPE_VBOX)
+
+static SidePanel *
+panel_for_widget (CajaSidePane *side_pane, GtkWidget *widget)
+{
+ GList *l;
+ SidePanel *panel;
+
+ for (l = side_pane->details->panels; l != NULL; l = l->next)
+ {
+ panel = l->data;
+ if (panel->widget == widget)
+ {
+ return panel;
+ }
+ }
+
+ return NULL;
+}
+
+static void
+side_panel_free (SidePanel *panel)
+{
+ g_free (panel->title);
+ g_free (panel->tooltip);
+ g_slice_free (SidePanel, panel);
+}
+
+static void
+switch_page_callback (GtkWidget *notebook,
+ GtkWidget *page,
+ guint page_num,
+ gpointer user_data)
+{
+ CajaSidePane *side_pane;
+ SidePanel *panel;
+
+ side_pane = CAJA_SIDE_PANE (user_data);
+
+ panel = panel_for_widget (side_pane,
+ gtk_notebook_get_nth_page (GTK_NOTEBOOK (side_pane->details->notebook),
+ page_num));
+
+ if (panel && side_pane->details->title_label)
+ {
+ gtk_label_set_text (GTK_LABEL (side_pane->details->title_label),
+ panel->title);
+ }
+
+ g_signal_emit (side_pane, signals[SWITCH_PAGE], 0,
+ panel ? panel->widget : NULL);
+}
+
+static void
+select_panel (CajaSidePane *side_pane, SidePanel *panel)
+{
+ int page_num;
+
+ page_num = gtk_notebook_page_num
+ (GTK_NOTEBOOK (side_pane->details->notebook), panel->widget);
+ gtk_notebook_set_current_page
+ (GTK_NOTEBOOK (side_pane->details->notebook), page_num);
+}
+
+static void
+caja_side_pane_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ int width;
+ GtkAllocation child_allocation, frame_allocation;
+ CajaSidePane *pane;
+ GtkWidget *frame;
+ GtkWidget *hbox;
+ GtkRequisition child_requisition;
+
+ GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation);
+
+ pane = CAJA_SIDE_PANE(widget);
+ frame = pane->details->title_frame;
+ hbox = pane->details->title_hbox;
+
+ gtk_widget_get_child_requisition (hbox, &child_requisition);
+ width = child_requisition.width;
+
+ gtk_widget_get_allocation (frame, &frame_allocation);
+ child_allocation = frame_allocation;
+ child_allocation.width = MAX (width, frame_allocation.width);
+
+ gtk_widget_size_allocate (frame, &child_allocation);
+}
+
+/* initializing the class object by installing the operations we override */
+static void
+caja_side_pane_class_init (CajaSidePaneClass *klass)
+{
+ GObjectClass *gobject_class;
+ GtkWidgetClass *widget_class;
+
+ gobject_class = G_OBJECT_CLASS (klass);
+ widget_class = GTK_WIDGET_CLASS (klass);
+
+ gobject_class->finalize = caja_side_pane_finalize;
+ gobject_class->dispose = caja_side_pane_dispose;
+ widget_class->size_allocate = caja_side_pane_size_allocate;
+
+ signals[CLOSE_REQUESTED] = g_signal_new
+ ("close_requested",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaSidePaneClass,
+ close_requested),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ signals[SWITCH_PAGE] = g_signal_new
+ ("switch_page",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaSidePaneClass,
+ switch_page),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, GTK_TYPE_WIDGET);
+
+ g_type_class_add_private (gobject_class, sizeof (CajaSidePaneDetails));
+}
+
+static void
+panel_item_activate_callback (GtkMenuItem *item,
+ gpointer user_data)
+{
+ CajaSidePane *side_pane;
+ SidePanel *panel;
+
+ side_pane = CAJA_SIDE_PANE (user_data);
+
+ panel = g_object_get_data (G_OBJECT (item), "panel-item");
+
+ select_panel (side_pane, panel);
+}
+
+
+static void
+menu_position_under (GtkMenu *menu,
+ int *x,
+ int *y,
+ gboolean *push_in,
+ gpointer user_data)
+{
+ GtkWidget *widget;
+ GtkAllocation allocation;
+
+ g_return_if_fail (GTK_IS_BUTTON (user_data));
+ g_return_if_fail (!gtk_widget_get_has_window (GTK_WIDGET (user_data)));
+
+ widget = GTK_WIDGET (user_data);
+
+ gdk_window_get_origin (gtk_widget_get_window (widget), x, y);
+ gtk_widget_get_allocation (widget, &allocation);
+
+ *x += allocation.x;
+ *y += allocation.y + allocation.height;
+
+ *push_in = FALSE;
+}
+
+static gboolean
+select_button_press_callback (GtkWidget *widget,
+ GdkEventButton *event,
+ gpointer user_data)
+{
+ CajaSidePane *side_pane;
+
+ side_pane = CAJA_SIDE_PANE (user_data);
+
+ if ((event->type == GDK_BUTTON_PRESS) && event->button == 1)
+ {
+ GtkRequisition requisition;
+ GtkAllocation allocation;
+ gint width;
+
+ gtk_widget_get_allocation (widget, &allocation);
+ width = allocation.width;
+ gtk_widget_set_size_request (side_pane->details->menu, -1, -1);
+ gtk_widget_size_request (side_pane->details->menu, &requisition);
+ gtk_widget_set_size_request (side_pane->details->menu,
+ MAX (width, requisition.width), -1);
+
+ gtk_widget_grab_focus (widget);
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), TRUE);
+ gtk_menu_popup (GTK_MENU (side_pane->details->menu),
+ NULL, NULL, menu_position_under, widget,
+ event->button, event->time);
+
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static gboolean
+select_button_key_press_callback (GtkWidget *widget,
+ GdkEventKey *event,
+ gpointer user_data)
+{
+ CajaSidePane *side_pane;
+
+ side_pane = CAJA_SIDE_PANE (user_data);
+
+ if (event->keyval == GDK_space ||
+ event->keyval == GDK_KP_Space ||
+ event->keyval == GDK_Return ||
+ event->keyval == GDK_KP_Enter)
+ {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), TRUE);
+ gtk_menu_popup (GTK_MENU (side_pane->details->menu),
+ NULL, NULL, menu_position_under, widget,
+ 1, event->time);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+close_clicked_callback (GtkWidget *widget,
+ gpointer user_data)
+{
+ CajaSidePane *side_pane;
+
+ side_pane = CAJA_SIDE_PANE (user_data);
+
+ g_signal_emit (side_pane, signals[CLOSE_REQUESTED], 0);
+}
+
+static void
+menu_deactivate_callback (GtkWidget *widget,
+ gpointer user_data)
+{
+ GtkWidget *menu_button;
+
+ menu_button = GTK_WIDGET (user_data);
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (menu_button), FALSE);
+}
+
+static void
+menu_detach_callback (GtkWidget *widget,
+ GtkMenu *menu)
+{
+ CajaSidePane *side_pane;
+
+ side_pane = CAJA_SIDE_PANE (widget);
+
+ side_pane->details->menu = NULL;
+}
+
+static void
+caja_side_pane_init (GObject *object)
+{
+ CajaSidePane *side_pane;
+ GtkWidget *frame;
+ GtkWidget *hbox;
+ GtkWidget *close_button;
+ GtkWidget *select_button;
+ GtkWidget *select_hbox;
+ GtkWidget *arrow;
+ GtkWidget *image;
+
+ side_pane = CAJA_SIDE_PANE (object);
+
+ side_pane->details = G_TYPE_INSTANCE_GET_PRIVATE (object, CAJA_TYPE_SIDE_PANE, CajaSidePaneDetails);
+
+ /* The frame (really a vbox) has the border */
+ frame = gtk_vbox_new (FALSE, 0);
+ gtk_container_set_border_width (GTK_CONTAINER (frame), 4);
+ side_pane->details->title_frame = frame;
+ gtk_widget_show (frame);
+ gtk_box_pack_start (GTK_BOX (side_pane), frame, FALSE, FALSE, 0);
+
+ /* And the title_hbox is what gets the same size as the other
+ headers */
+ hbox = gtk_hbox_new (FALSE, 0);
+ side_pane->details->title_hbox = hbox;
+ gtk_widget_show (hbox);
+ gtk_container_add (GTK_CONTAINER (frame), hbox);
+
+ select_button = gtk_toggle_button_new ();
+ gtk_button_set_relief (GTK_BUTTON (select_button), GTK_RELIEF_NONE);
+ gtk_widget_show (select_button);
+
+ g_signal_connect (select_button,
+ "button_press_event",
+ G_CALLBACK (select_button_press_callback),
+ side_pane);
+ g_signal_connect (select_button,
+ "key_press_event",
+ G_CALLBACK (select_button_key_press_callback),
+ side_pane);
+
+ select_hbox = gtk_hbox_new (FALSE, 0);
+ gtk_widget_show (select_hbox);
+
+ side_pane->details->title_label = gtk_label_new ("");
+ eel_add_weak_pointer (&side_pane->details->title_label);
+
+ gtk_widget_show (side_pane->details->title_label);
+ gtk_box_pack_start (GTK_BOX (select_hbox),
+ side_pane->details->title_label,
+ FALSE, FALSE, 0);
+
+ arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE);
+ gtk_widget_show (arrow);
+ gtk_box_pack_end (GTK_BOX (select_hbox), arrow, FALSE, FALSE, 0);
+
+ gtk_container_add (GTK_CONTAINER (select_button), select_hbox);
+ gtk_box_pack_start (GTK_BOX (hbox), select_button, TRUE, TRUE, 0);
+
+ close_button = gtk_button_new ();
+ gtk_button_set_relief (GTK_BUTTON (close_button), GTK_RELIEF_NONE);
+ g_signal_connect (close_button,
+ "clicked",
+ G_CALLBACK (close_clicked_callback),
+ side_pane);
+
+ gtk_widget_show (close_button);
+
+ image = gtk_image_new_from_stock (GTK_STOCK_CLOSE,
+ GTK_ICON_SIZE_MENU);
+ gtk_widget_show (image);
+
+ gtk_container_add (GTK_CONTAINER (close_button), image);
+
+ gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
+
+ side_pane->details->shortcut_box = gtk_hbox_new (TRUE, 0);
+ gtk_widget_show (side_pane->details->shortcut_box);
+ gtk_box_pack_end (GTK_BOX (hbox),
+ side_pane->details->shortcut_box,
+ FALSE, FALSE, 0);
+
+ side_pane->details->notebook = gtk_notebook_new ();
+ gtk_notebook_set_show_tabs (GTK_NOTEBOOK (side_pane->details->notebook),
+ FALSE);
+ gtk_notebook_set_show_border (GTK_NOTEBOOK (side_pane->details->notebook),
+ FALSE);
+ g_signal_connect_object (side_pane->details->notebook,
+ "switch_page",
+ G_CALLBACK (switch_page_callback),
+ side_pane,
+ 0);
+
+ gtk_widget_show (side_pane->details->notebook);
+
+ gtk_box_pack_start (GTK_BOX (side_pane), side_pane->details->notebook,
+ TRUE, TRUE, 0);
+
+ side_pane->details->menu = gtk_menu_new ();
+ g_signal_connect (side_pane->details->menu,
+ "deactivate",
+ G_CALLBACK (menu_deactivate_callback),
+ select_button);
+ gtk_menu_attach_to_widget (GTK_MENU (side_pane->details->menu),
+ GTK_WIDGET (side_pane),
+ menu_detach_callback);
+
+ gtk_widget_show (side_pane->details->menu);
+
+ gtk_widget_set_tooltip_text (close_button,
+ _("Close the side pane"));
+}
+
+static void
+caja_side_pane_dispose (GObject *object)
+{
+ CajaSidePane *side_pane;
+
+ side_pane = CAJA_SIDE_PANE (object);
+
+ if (side_pane->details->menu)
+ {
+ gtk_menu_detach (GTK_MENU (side_pane->details->menu));
+ side_pane->details->menu = NULL;
+ }
+
+ EEL_CALL_PARENT (G_OBJECT_CLASS, dispose, (object));
+}
+
+static void
+caja_side_pane_finalize (GObject *object)
+{
+ CajaSidePane *side_pane;
+ GList *l;
+
+ side_pane = CAJA_SIDE_PANE (object);
+
+ for (l = side_pane->details->panels; l != NULL; l = l->next)
+ {
+ side_panel_free (l->data);
+ }
+
+ g_list_free (side_pane->details->panels);
+
+ EEL_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
+}
+
+CajaSidePane *
+caja_side_pane_new (void)
+{
+ return CAJA_SIDE_PANE (gtk_widget_new (caja_side_pane_get_type (), NULL));
+}
+
+void
+caja_side_pane_add_panel (CajaSidePane *side_pane,
+ GtkWidget *widget,
+ const char *title,
+ const char *tooltip)
+{
+ SidePanel *panel;
+
+ g_return_if_fail (side_pane != NULL);
+ g_return_if_fail (CAJA_IS_SIDE_PANE (side_pane));
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+ g_return_if_fail (title != NULL);
+ g_return_if_fail (tooltip != NULL);
+
+ panel = g_slice_new0 (SidePanel);
+ panel->title = g_strdup (title);
+ panel->tooltip = g_strdup (tooltip);
+ panel->widget = widget;
+
+ gtk_widget_show (widget);
+
+ panel->menu_item = gtk_image_menu_item_new_with_label (title);
+ gtk_widget_show (panel->menu_item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (side_pane->details->menu),
+ panel->menu_item);
+ g_object_set_data (G_OBJECT (panel->menu_item), "panel-item", panel);
+
+ g_signal_connect (panel->menu_item,
+ "activate",
+ G_CALLBACK (panel_item_activate_callback),
+ side_pane);
+
+ side_pane->details->panels = g_list_append (side_pane->details->panels,
+ panel);
+
+ gtk_notebook_append_page (GTK_NOTEBOOK (side_pane->details->notebook),
+ widget,
+ NULL);
+}
+
+void
+caja_side_pane_remove_panel (CajaSidePane *side_pane,
+ GtkWidget *widget)
+{
+ SidePanel *panel;
+ int page_num;
+
+ g_return_if_fail (side_pane != NULL);
+ g_return_if_fail (CAJA_IS_SIDE_PANE (side_pane));
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+
+ panel = panel_for_widget (side_pane, widget);
+
+ g_return_if_fail (panel != NULL);
+
+ if (panel)
+ {
+ page_num = gtk_notebook_page_num (GTK_NOTEBOOK (side_pane->details->notebook),
+ widget);
+ gtk_notebook_remove_page (GTK_NOTEBOOK (side_pane->details->notebook),
+ page_num);
+ gtk_container_remove (GTK_CONTAINER (side_pane->details->menu),
+ panel->menu_item);
+
+ side_pane->details->panels =
+ g_list_remove (side_pane->details->panels,
+ panel);
+
+ side_panel_free (panel);
+ }
+}
+
+void
+caja_side_pane_show_panel (CajaSidePane *side_pane,
+ GtkWidget *widget)
+{
+ SidePanel *panel;
+ int page_num;
+
+ g_return_if_fail (side_pane != NULL);
+ g_return_if_fail (CAJA_IS_SIDE_PANE (side_pane));
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+
+ panel = panel_for_widget (side_pane, widget);
+
+ g_return_if_fail (panel != NULL);
+
+ page_num = gtk_notebook_page_num (GTK_NOTEBOOK (side_pane->details->notebook),
+ widget);
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (side_pane->details->notebook),
+ page_num);
+}
+
+
+static void
+shortcut_clicked_callback (GtkWidget *button,
+ gpointer user_data)
+{
+ CajaSidePane *side_pane;
+ GtkWidget *page;
+
+ side_pane = CAJA_SIDE_PANE (user_data);
+
+ page = GTK_WIDGET (g_object_get_data (G_OBJECT (button), "side-page"));
+
+ caja_side_pane_show_panel (side_pane, page);
+}
+
+static GtkWidget *
+create_shortcut (CajaSidePane *side_pane,
+ SidePanel *panel,
+ GdkPixbuf *pixbuf)
+{
+ GtkWidget *button;
+ GtkWidget *image;
+
+ button = gtk_button_new ();
+ gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
+
+ g_object_set_data (G_OBJECT (button), "side-page", panel->widget);
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (shortcut_clicked_callback), side_pane);
+
+ gtk_widget_set_tooltip_text (button, panel->tooltip);
+
+ image = gtk_image_new_from_pixbuf (pixbuf);
+ gtk_widget_show (image);
+ gtk_container_add (GTK_CONTAINER (button), image);
+
+ return button;
+}
+
+void
+caja_side_pane_set_panel_image (CajaSidePane *side_pane,
+ GtkWidget *widget,
+ GdkPixbuf *pixbuf)
+{
+ SidePanel *panel;
+ GtkWidget *image;
+
+ g_return_if_fail (side_pane != NULL);
+ g_return_if_fail (CAJA_IS_SIDE_PANE (side_pane));
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+ g_return_if_fail (pixbuf == NULL || GDK_IS_PIXBUF (pixbuf));
+
+ panel = panel_for_widget (side_pane, widget);
+
+ g_return_if_fail (panel != NULL);
+
+ if (pixbuf)
+ {
+ image = gtk_image_new_from_pixbuf (pixbuf);
+ gtk_widget_show (image);
+ }
+ else
+ {
+ image = NULL;
+ }
+
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (panel->menu_item),
+ image);
+
+ if (panel->shortcut)
+ {
+ gtk_widget_destroy (panel->shortcut);
+ panel->shortcut = NULL;
+ }
+
+ if (pixbuf)
+ {
+ panel->shortcut = create_shortcut (side_pane, panel, pixbuf);
+ gtk_widget_show (panel->shortcut);
+ gtk_box_pack_start (GTK_BOX (side_pane->details->shortcut_box),
+ panel->shortcut,
+ FALSE, FALSE, 0);
+ }
+}
+
+GtkWidget *
+caja_side_pane_get_current_panel (CajaSidePane *side_pane)
+{
+ int index;
+
+ index = gtk_notebook_get_current_page (GTK_NOTEBOOK (side_pane->details->notebook));
+ return gtk_notebook_get_nth_page (GTK_NOTEBOOK (side_pane->details->notebook), index);
+}
+
+GtkWidget *
+caja_side_pane_get_title (CajaSidePane *side_pane)
+{
+ return side_pane->details->title_hbox;
+}
diff --git a/src/caja-side-pane.h b/src/caja-side-pane.h
new file mode 100644
index 00000000..ebb6d8cf
--- /dev/null
+++ b/src/caja-side-pane.h
@@ -0,0 +1,82 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-side-pane.c
+ *
+ * Copyright (C) 2002 Ximian, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Author: Dave Camp <[email protected]>
+ */
+
+#ifndef CAJA_SIDE_PANE_H
+#define CAJA_SIDE_PANE_H
+
+#include <gtk/gtk.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define CAJA_TYPE_SIDE_PANE caja_side_pane_get_type()
+#define CAJA_SIDE_PANE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_SIDE_PANE, CajaSidePane))
+#define CAJA_SIDE_PANE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_SIDE_PANE, CajaSidePaneClass))
+#define CAJA_IS_SIDE_PANE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_SIDE_PANE))
+#define CAJA_IS_SIDE_PANE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_SIDE_PANE))
+#define CAJA_SIDE_PANE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_SIDE_PANE, CajaSidePaneClass))
+
+ typedef struct _CajaSidePaneDetails CajaSidePaneDetails;
+
+ typedef struct
+ {
+ GtkVBox parent;
+ CajaSidePaneDetails *details;
+ } CajaSidePane;
+
+ typedef struct
+ {
+ GtkVBoxClass parent_slot;
+
+ void (*close_requested) (CajaSidePane *side_pane);
+ void (*switch_page) (CajaSidePane *side_pane,
+ GtkWidget *child);
+ } CajaSidePaneClass;
+
+ GType caja_side_pane_get_type (void);
+ CajaSidePane *caja_side_pane_new (void);
+ void caja_side_pane_add_panel (CajaSidePane *side_pane,
+ GtkWidget *widget,
+ const char *title,
+ const char *tooltip);
+ void caja_side_pane_remove_panel (CajaSidePane *side_pane,
+ GtkWidget *widget);
+ void caja_side_pane_show_panel (CajaSidePane *side_pane,
+ GtkWidget *widget);
+ void caja_side_pane_set_panel_image (CajaSidePane *side_pane,
+ GtkWidget *widget,
+ GdkPixbuf *pixbuf);
+ GtkWidget *caja_side_pane_get_current_panel (CajaSidePane *side_pane);
+ GtkWidget *caja_side_pane_get_title (CajaSidePane *side_pane);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CAJA_SIDE_PANE_H */
diff --git a/src/caja-sidebar-title.c b/src/caja-sidebar-title.c
new file mode 100644
index 00000000..0ad228b5
--- /dev/null
+++ b/src/caja-sidebar-title.c
@@ -0,0 +1,732 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2000, 2001 Eazel, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Author: Andy Hertzfeld <[email protected]>
+ */
+
+/* This is the sidebar title widget, which is the title part of the sidebar. */
+
+#include <config.h>
+#include <math.h>
+#include "caja-sidebar-title.h"
+
+#include "caja-window.h"
+
+#include <eel/eel-background.h>
+#include <eel/eel-gdk-extensions.h>
+#include <eel/eel-gdk-pixbuf-extensions.h>
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-gtk-extensions.h>
+#include <eel/eel-gtk-macros.h>
+#include <eel/eel-pango-extensions.h>
+#include <eel/eel-string.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <libcaja-private/caja-file-attributes.h>
+#include <libcaja-private/caja-global-preferences.h>
+#include <libcaja-private/caja-metadata.h>
+#include <libcaja-private/caja-sidebar.h>
+#include <string.h>
+#include <stdlib.h>
+
+/* maximum allowable size to be displayed as the title */
+#define MAX_TITLE_SIZE 256
+#define MINIMUM_INFO_WIDTH 32
+#define SIDEBAR_INFO_MARGIN 4
+#define SHADOW_OFFSET 1
+
+#define MORE_INFO_FONT_SIZE 12
+#define MIN_TITLE_FONT_SIZE 12
+#define TITLE_PADDING 4
+
+static void caja_sidebar_title_class_init (CajaSidebarTitleClass *klass);
+static void caja_sidebar_title_destroy (GtkObject *object);
+static void caja_sidebar_title_init (CajaSidebarTitle *pixmap);
+static void caja_sidebar_title_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation);
+static void update_icon (CajaSidebarTitle *sidebar_title);
+static GtkWidget * sidebar_title_create_title_label (void);
+static GtkWidget * sidebar_title_create_more_info_label (void);
+static void update_all (CajaSidebarTitle *sidebar_title);
+static void update_more_info (CajaSidebarTitle *sidebar_title);
+static void update_title_font (CajaSidebarTitle *sidebar_title);
+static void style_set (GtkWidget *widget,
+ GtkStyle *previous_style);
+static guint get_best_icon_size (CajaSidebarTitle *sidebar_title);
+
+struct CajaSidebarTitleDetails
+{
+ CajaFile *file;
+ guint file_changed_connection;
+ gboolean monitoring_count;
+
+ char *title_text;
+ GtkWidget *icon;
+ GtkWidget *title_label;
+ GtkWidget *more_info_label;
+ GtkWidget *emblem_box;
+
+ guint best_icon_size;
+
+ gboolean determined_icon;
+};
+
+EEL_CLASS_BOILERPLATE (CajaSidebarTitle, caja_sidebar_title, gtk_vbox_get_type ())
+
+static void
+caja_sidebar_title_class_init (CajaSidebarTitleClass *class)
+{
+ GtkObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+
+ object_class = (GtkObjectClass*) class;
+ widget_class = (GtkWidgetClass*) class;
+
+ object_class->destroy = caja_sidebar_title_destroy;
+ widget_class->size_allocate = caja_sidebar_title_size_allocate;
+ widget_class->style_set = style_set;
+
+}
+
+static void
+style_set (GtkWidget *widget,
+ GtkStyle *previous_style)
+{
+ CajaSidebarTitle *sidebar_title;
+ PangoFontDescription *font_desc;
+ GtkStyle *style;
+
+ g_return_if_fail (CAJA_IS_SIDEBAR_TITLE (widget));
+
+ sidebar_title = CAJA_SIDEBAR_TITLE (widget);
+
+ /* Update the dynamically-sized title font */
+ update_title_font (sidebar_title);
+
+ /* Update the fixed-size "more info" font */
+ style = gtk_widget_get_style (widget);
+ font_desc = pango_font_description_copy (style->font_desc);
+ if (pango_font_description_get_size (font_desc) < MORE_INFO_FONT_SIZE * PANGO_SCALE)
+ {
+ pango_font_description_set_size (font_desc, MORE_INFO_FONT_SIZE * PANGO_SCALE);
+ }
+
+ gtk_widget_modify_font (sidebar_title->details->more_info_label,
+ font_desc);
+ pango_font_description_free (font_desc);
+}
+
+static void
+caja_sidebar_title_init (CajaSidebarTitle *sidebar_title)
+{
+ sidebar_title->details = g_new0 (CajaSidebarTitleDetails, 1);
+
+ /* Create the icon */
+ sidebar_title->details->icon = gtk_image_new ();
+ gtk_box_pack_start (GTK_BOX (sidebar_title), sidebar_title->details->icon, 0, 0, 0);
+ gtk_widget_show (sidebar_title->details->icon);
+
+ /* Create the title label */
+ sidebar_title->details->title_label = sidebar_title_create_title_label ();
+ gtk_box_pack_start (GTK_BOX (sidebar_title), sidebar_title->details->title_label, 0, 0, 0);
+ gtk_widget_show (sidebar_title->details->title_label);
+
+ /* Create the more info label */
+ sidebar_title->details->more_info_label = sidebar_title_create_more_info_label ();
+ gtk_box_pack_start (GTK_BOX (sidebar_title), sidebar_title->details->more_info_label, 0, 0, 0);
+ gtk_widget_show (sidebar_title->details->more_info_label);
+
+ sidebar_title->details->emblem_box = gtk_hbox_new (FALSE, 0);
+ gtk_widget_show (sidebar_title->details->emblem_box);
+ gtk_box_pack_start (GTK_BOX (sidebar_title), sidebar_title->details->emblem_box, 0, 0, 0);
+
+ sidebar_title->details->best_icon_size = get_best_icon_size (sidebar_title);
+ /* Keep track of changes in graphics trade offs */
+ update_all (sidebar_title);
+
+ /* initialize the label colors & fonts */
+ style_set (GTK_WIDGET (sidebar_title), NULL);
+
+ eel_preferences_add_callback_while_alive (
+ CAJA_PREFERENCES_SHOW_DIRECTORY_ITEM_COUNTS,
+ (EelPreferencesCallback) update_more_info,
+ sidebar_title, G_OBJECT (sidebar_title));
+}
+
+/* destroy by throwing away private storage */
+static void
+release_file (CajaSidebarTitle *sidebar_title)
+{
+ if (sidebar_title->details->file_changed_connection != 0)
+ {
+ g_signal_handler_disconnect (sidebar_title->details->file,
+ sidebar_title->details->file_changed_connection);
+ sidebar_title->details->file_changed_connection = 0;
+ }
+
+ if (sidebar_title->details->file != NULL)
+ {
+ caja_file_monitor_remove (sidebar_title->details->file, sidebar_title);
+ caja_file_unref (sidebar_title->details->file);
+ sidebar_title->details->file = NULL;
+ }
+}
+
+static void
+caja_sidebar_title_destroy (GtkObject *object)
+{
+ CajaSidebarTitle *sidebar_title;
+
+ sidebar_title = CAJA_SIDEBAR_TITLE (object);
+
+ if (sidebar_title->details)
+ {
+ release_file (sidebar_title);
+
+ g_free (sidebar_title->details->title_text);
+ g_free (sidebar_title->details);
+ sidebar_title->details = NULL;
+ }
+
+ EEL_CALL_PARENT (GTK_OBJECT_CLASS, destroy, (object));
+}
+
+/* return a new index title object */
+GtkWidget *
+caja_sidebar_title_new (void)
+{
+ return gtk_widget_new (caja_sidebar_title_get_type (), NULL);
+}
+
+void
+caja_sidebar_title_select_text_color (CajaSidebarTitle *sidebar_title,
+ EelBackground *background,
+ gboolean is_default)
+{
+ char *sidebar_title_color;
+ char *sidebar_info_title_color;
+ char *sidebar_title_shadow_color;
+
+ g_return_if_fail (background != NULL);
+
+ /* if the background is set to the default, the theme can explicitly
+ * define the title colors. Check if the background has been customized
+ * and if the theme specified any colors
+ */
+ sidebar_title_color = NULL;
+ sidebar_info_title_color = NULL;
+ sidebar_title_shadow_color = NULL;
+
+ /* FIXME bugzilla.gnome.org 42496: for now, both the title and info
+ * colors are the same - and hard coded */
+ if (eel_background_is_dark (background))
+ {
+ sidebar_title_color = g_strdup ("#FFFFFF");
+ sidebar_info_title_color = g_strdup ("#FFFFFF");
+ sidebar_title_shadow_color = g_strdup ("#000000");
+ }
+ else
+ {
+ sidebar_title_color = g_strdup ("#000000");
+ sidebar_info_title_color = g_strdup ("#000000");
+ sidebar_title_shadow_color = g_strdup ("#FFFFFF");
+ }
+
+ eel_gtk_widget_set_foreground_color (sidebar_title->details->title_label,
+ sidebar_title_color);
+ eel_gtk_widget_set_foreground_color (sidebar_title->details->more_info_label,
+ sidebar_info_title_color);
+
+ eel_gtk_label_set_drop_shadow_color (GTK_LABEL (sidebar_title->details->title_label),
+ eel_parse_rgb_with_white_default (sidebar_title_shadow_color));
+ eel_gtk_label_set_drop_shadow_color (GTK_LABEL (sidebar_title->details->more_info_label),
+ eel_parse_rgb_with_white_default (sidebar_title_shadow_color));
+
+ eel_gtk_label_set_drop_shadow_offset (GTK_LABEL (sidebar_title->details->title_label),
+ SHADOW_OFFSET);
+ eel_gtk_label_set_drop_shadow_offset (GTK_LABEL (sidebar_title->details->more_info_label),
+ SHADOW_OFFSET);
+
+ g_free (sidebar_title_color);
+ g_free (sidebar_info_title_color);
+ g_free (sidebar_title_shadow_color);
+}
+
+static char*
+get_property_from_component (CajaSidebarTitle *sidebar_title, const char *property)
+{
+ /* There used to be a way to get icon and summary_text from main view,
+ * but its not used right now, so this sas stubbed out for now
+ */
+ return NULL;
+}
+
+static guint
+get_best_icon_size (CajaSidebarTitle *sidebar_title)
+{
+ gint width;
+ GtkAllocation allocation;
+
+ gtk_widget_get_allocation (GTK_WIDGET (sidebar_title), &allocation);
+ width = allocation.width - TITLE_PADDING;
+
+ if (width < 0)
+ {
+ /* use smallest available icon size */
+ return caja_icon_get_smaller_icon_size (0);
+ }
+ else
+ {
+ return caja_icon_get_smaller_icon_size ((guint) width);
+ }
+}
+
+/* set up the icon image */
+static void
+update_icon (CajaSidebarTitle *sidebar_title)
+{
+ GdkPixbuf *pixbuf;
+ CajaIconInfo *info;
+ char *icon_name;
+ gboolean leave_pixbuf_unchanged;
+
+ leave_pixbuf_unchanged = FALSE;
+
+ /* see if the current content view is specifying an icon */
+ icon_name = get_property_from_component (sidebar_title, "icon_name");
+
+ pixbuf = NULL;
+ if (icon_name != NULL && icon_name[0] != '\0')
+ {
+ info = caja_icon_info_lookup_from_name (icon_name, CAJA_ICON_SIZE_LARGE);
+ pixbuf = caja_icon_info_get_pixbuf_at_size (info, CAJA_ICON_SIZE_LARGE);
+ g_object_unref (info);
+ }
+ else if (sidebar_title->details->file != NULL &&
+ caja_file_check_if_ready (sidebar_title->details->file,
+ CAJA_FILE_ATTRIBUTES_FOR_ICON))
+ {
+ pixbuf = caja_file_get_icon_pixbuf (sidebar_title->details->file,
+ sidebar_title->details->best_icon_size,
+ TRUE,
+ CAJA_FILE_ICON_FLAGS_USE_THUMBNAILS |
+ CAJA_FILE_ICON_FLAGS_USE_MOUNT_ICON_AS_EMBLEM);
+ }
+ else if (sidebar_title->details->determined_icon)
+ {
+ /* We used to know the icon for this file, but now the file says it isn't
+ * ready. This means that some file info has been invalidated, which
+ * doesn't necessarily mean that the previously-determined icon is
+ * wrong (in fact, in practice it usually doesn't mean that). Keep showing
+ * the one we last determined for now.
+ */
+ leave_pixbuf_unchanged = TRUE;
+ }
+
+ g_free (icon_name);
+
+ if (!leave_pixbuf_unchanged)
+ {
+ gtk_image_set_from_pixbuf (GTK_IMAGE (sidebar_title->details->icon), pixbuf);
+ }
+
+ if (pixbuf != NULL)
+ {
+ sidebar_title->details->determined_icon = TRUE;
+ g_object_unref (pixbuf);
+ }
+}
+
+static void
+update_title_font (CajaSidebarTitle *sidebar_title)
+{
+ int available_width;
+ PangoFontDescription *title_font;
+ int largest_fitting_font_size;
+ int max_style_font_size;
+ GtkStyle *style;
+ GtkAllocation allocation;
+
+ /* Make sure theres work to do */
+ if (eel_strlen (sidebar_title->details->title_text) < 1)
+ {
+ return;
+ }
+
+ gtk_widget_get_allocation (GTK_WIDGET (sidebar_title), &allocation);
+ available_width = allocation.width - TITLE_PADDING;
+
+ /* No work to do */
+ if (available_width <= 0)
+ {
+ return;
+ }
+
+ style = gtk_widget_get_style (GTK_WIDGET (sidebar_title));
+ title_font = pango_font_description_copy (style->font_desc);
+
+ max_style_font_size = pango_font_description_get_size (title_font) * 1.8 / PANGO_SCALE;
+ if (max_style_font_size < MIN_TITLE_FONT_SIZE + 1)
+ {
+ max_style_font_size = MIN_TITLE_FONT_SIZE + 1;
+ }
+
+ largest_fitting_font_size = eel_pango_font_description_get_largest_fitting_font_size (
+ title_font,
+ gtk_widget_get_pango_context (sidebar_title->details->title_label),
+ sidebar_title->details->title_text,
+ available_width,
+ MIN_TITLE_FONT_SIZE,
+ max_style_font_size);
+ pango_font_description_set_size (title_font, largest_fitting_font_size * PANGO_SCALE);
+
+ pango_font_description_set_weight (title_font, PANGO_WEIGHT_BOLD);
+
+ gtk_widget_modify_font (sidebar_title->details->title_label,
+ title_font);
+ pango_font_description_free (title_font);
+}
+
+static void
+update_title (CajaSidebarTitle *sidebar_title)
+{
+ GtkLabel *label;
+ const char *text;
+
+ label = GTK_LABEL (sidebar_title->details->title_label);
+ text = sidebar_title->details->title_text;
+
+ if (eel_strcmp (text, gtk_label_get_text (label)) == 0)
+ {
+ return;
+ }
+ gtk_label_set_text (label, text);
+ update_title_font (sidebar_title);
+}
+
+static void
+append_and_eat (GString *string, const char *separator, char *new_string)
+{
+ if (new_string == NULL)
+ {
+ return;
+ }
+ if (separator != NULL)
+ {
+ g_string_append (string, separator);
+ }
+ g_string_append (string, new_string);
+ g_free (new_string);
+}
+
+static int
+measure_width_callback (const char *string, gpointer callback_data)
+{
+ PangoLayout *layout;
+ int width;
+
+ layout = PANGO_LAYOUT (callback_data);
+ pango_layout_set_text (layout, string, -1);
+ pango_layout_get_pixel_size (layout, &width, NULL);
+ return width;
+}
+
+static void
+update_more_info (CajaSidebarTitle *sidebar_title)
+{
+ CajaFile *file;
+ GString *info_string;
+ char *type_string, *component_info;
+ char *date_modified_str;
+ int sidebar_width;
+ PangoLayout *layout;
+ GtkAllocation allocation;
+
+ file = sidebar_title->details->file;
+
+ /* allow components to specify the info if they wish to */
+ component_info = get_property_from_component (sidebar_title, "summary_info");
+ if (component_info != NULL && strlen (component_info) > 0)
+ {
+ info_string = g_string_new (component_info);
+ g_free (component_info);
+ }
+ else
+ {
+ info_string = g_string_new (NULL);
+
+ type_string = NULL;
+ if (file != NULL && caja_file_should_show_type (file))
+ {
+ type_string = caja_file_get_string_attribute (file, "type");
+ }
+
+ if (type_string != NULL)
+ {
+ append_and_eat (info_string, NULL, type_string);
+ append_and_eat (info_string, ", ",
+ caja_file_get_string_attribute (file, "size"));
+ }
+ else
+ {
+ append_and_eat (info_string, NULL,
+ caja_file_get_string_attribute (file, "size"));
+ }
+
+ gtk_widget_get_allocation (GTK_WIDGET (sidebar_title), &allocation);
+ sidebar_width = allocation.width - 2 * SIDEBAR_INFO_MARGIN;
+ if (sidebar_width > MINIMUM_INFO_WIDTH)
+ {
+ layout = pango_layout_copy (gtk_label_get_layout (GTK_LABEL (sidebar_title->details->more_info_label)));
+ pango_layout_set_width (layout, -1);
+ date_modified_str = caja_file_fit_modified_date_as_string
+ (file, sidebar_width, measure_width_callback, NULL, layout);
+ g_object_unref (layout);
+ append_and_eat (info_string, "\n", date_modified_str);
+ }
+ }
+ gtk_label_set_text (GTK_LABEL (sidebar_title->details->more_info_label),
+ info_string->str);
+
+ g_string_free (info_string, TRUE);
+}
+
+/* add a pixbuf to the emblem box */
+static void
+add_emblem (CajaSidebarTitle *sidebar_title, GdkPixbuf *pixbuf)
+{
+ GtkWidget *image_widget;
+
+ image_widget = gtk_image_new_from_pixbuf (pixbuf);
+ gtk_widget_show (image_widget);
+ gtk_container_add (GTK_CONTAINER (sidebar_title->details->emblem_box), image_widget);
+}
+
+static void
+update_emblems (CajaSidebarTitle *sidebar_title)
+{
+ GList *pixbufs, *p;
+ GdkPixbuf *pixbuf;
+
+ /* exit if we don't have the file yet */
+ if (sidebar_title->details->file == NULL)
+ {
+ return;
+ }
+
+ /* First, deallocate any existing ones */
+ gtk_container_foreach (GTK_CONTAINER (sidebar_title->details->emblem_box),
+ (GtkCallback) gtk_widget_destroy,
+ NULL);
+
+ /* fetch the emblem icons from metadata */
+ pixbufs = caja_file_get_emblem_pixbufs (sidebar_title->details->file,
+ caja_icon_get_emblem_size_for_icon_size (CAJA_ICON_SIZE_STANDARD),
+ FALSE,
+ NULL);
+
+ /* loop through the list of emblems, installing them in the box */
+ for (p = pixbufs; p != NULL; p = p->next)
+ {
+ pixbuf = p->data;
+ add_emblem (sidebar_title, pixbuf);
+ g_object_unref (pixbuf);
+ }
+ g_list_free (pixbufs);
+}
+
+/* return the filename text */
+char *
+caja_sidebar_title_get_text (CajaSidebarTitle *sidebar_title)
+{
+ return g_strdup (sidebar_title->details->title_text);
+}
+
+/* set up the filename text */
+void
+caja_sidebar_title_set_text (CajaSidebarTitle *sidebar_title,
+ const char* new_text)
+{
+ g_free (sidebar_title->details->title_text);
+
+ /* truncate the title to a reasonable size */
+ if (new_text && strlen (new_text) > MAX_TITLE_SIZE)
+ {
+ sidebar_title->details->title_text = g_strndup (new_text, MAX_TITLE_SIZE);
+ }
+ else
+ {
+ sidebar_title->details->title_text = g_strdup (new_text);
+ }
+ /* Recompute the displayed text. */
+ update_title (sidebar_title);
+}
+
+static gboolean
+item_count_ready (CajaSidebarTitle *sidebar_title)
+{
+ return sidebar_title->details->file != NULL
+ && caja_file_get_directory_item_count
+ (sidebar_title->details->file, NULL, NULL) != 0;
+}
+
+static void
+monitor_add (CajaSidebarTitle *sidebar_title)
+{
+ CajaFileAttributes attributes;
+
+ /* Monitor the things needed to get the right icon. Don't
+ * monitor a directory's item count at first even though the
+ * "size" attribute is based on that, because the main view
+ * will get it for us in most cases, and in other cases it's
+ * OK to not show the size -- if we did monitor it, we'd be in
+ * a race with the main view and could cause it to have to
+ * load twice. Once we have a size, though, we want to monitor
+ * the size to guarantee it stays up to date.
+ */
+
+ sidebar_title->details->monitoring_count = item_count_ready (sidebar_title);
+
+ attributes = CAJA_FILE_ATTRIBUTES_FOR_ICON | CAJA_FILE_ATTRIBUTE_INFO;
+ if (sidebar_title->details->monitoring_count)
+ {
+ attributes |= CAJA_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT;
+ }
+
+ caja_file_monitor_add (sidebar_title->details->file, sidebar_title, attributes);
+}
+
+static void
+update_all (CajaSidebarTitle *sidebar_title)
+{
+ update_icon (sidebar_title);
+
+ update_title (sidebar_title);
+ update_more_info (sidebar_title);
+
+ update_emblems (sidebar_title);
+
+ /* Redo monitor once the count is ready. */
+ if (!sidebar_title->details->monitoring_count && item_count_ready (sidebar_title))
+ {
+ caja_file_monitor_remove (sidebar_title->details->file, sidebar_title);
+ monitor_add (sidebar_title);
+ }
+}
+
+void
+caja_sidebar_title_set_file (CajaSidebarTitle *sidebar_title,
+ CajaFile *file,
+ const char *initial_text)
+{
+ if (file != sidebar_title->details->file)
+ {
+ release_file (sidebar_title);
+ sidebar_title->details->file = file;
+ sidebar_title->details->determined_icon = FALSE;
+ caja_file_ref (sidebar_title->details->file);
+
+ /* attach file */
+ if (file != NULL)
+ {
+ sidebar_title->details->file_changed_connection =
+ g_signal_connect_object
+ (sidebar_title->details->file, "changed",
+ G_CALLBACK (update_all), sidebar_title, G_CONNECT_SWAPPED);
+ monitor_add (sidebar_title);
+ }
+ }
+
+ g_free (sidebar_title->details->title_text);
+ sidebar_title->details->title_text = g_strdup (initial_text);
+
+ update_all (sidebar_title);
+}
+
+static void
+caja_sidebar_title_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ CajaSidebarTitle *sidebar_title;
+ guint16 old_width;
+ guint best_icon_size;
+ GtkAllocation old_allocation, new_allocation;
+
+ sidebar_title = CAJA_SIDEBAR_TITLE (widget);
+
+ gtk_widget_get_allocation (widget, &old_allocation);
+ old_width = old_allocation.width;
+
+ EEL_CALL_PARENT (GTK_WIDGET_CLASS, size_allocate, (widget, allocation));
+
+ gtk_widget_get_allocation (widget, &new_allocation);
+
+ if (old_width != new_allocation.width)
+ {
+ best_icon_size = get_best_icon_size (sidebar_title);
+ if (best_icon_size != sidebar_title->details->best_icon_size)
+ {
+ sidebar_title->details->best_icon_size = best_icon_size;
+ update_icon (sidebar_title);
+ }
+
+ /* update the title font and info format as the size changes. */
+ update_title_font (sidebar_title);
+ update_more_info (sidebar_title);
+ }
+}
+
+gboolean
+caja_sidebar_title_hit_test_icon (CajaSidebarTitle *sidebar_title, int x, int y)
+{
+ g_return_val_if_fail (CAJA_IS_SIDEBAR_TITLE (sidebar_title), FALSE);
+
+ return eel_point_in_widget (sidebar_title->details->icon, x, y);
+}
+
+static GtkWidget *
+sidebar_title_create_title_label (void)
+{
+ GtkWidget *title_label;
+
+ title_label = gtk_label_new ("");
+ eel_gtk_label_make_bold (GTK_LABEL (title_label));
+ gtk_label_set_line_wrap (GTK_LABEL (title_label), TRUE);
+ gtk_label_set_justify (GTK_LABEL (title_label), GTK_JUSTIFY_CENTER);
+ gtk_label_set_selectable (GTK_LABEL (title_label), TRUE);
+ gtk_label_set_ellipsize (GTK_LABEL (title_label), PANGO_ELLIPSIZE_END);
+
+ return title_label;
+}
+
+static GtkWidget *
+sidebar_title_create_more_info_label (void)
+{
+ GtkWidget *more_info_label;
+
+ more_info_label = gtk_label_new ("");
+ eel_gtk_label_set_scale (GTK_LABEL (more_info_label), PANGO_SCALE_SMALL);
+ gtk_label_set_justify (GTK_LABEL (more_info_label), GTK_JUSTIFY_CENTER);
+ gtk_label_set_selectable (GTK_LABEL (more_info_label), TRUE);
+ gtk_label_set_ellipsize (GTK_LABEL (more_info_label), PANGO_ELLIPSIZE_END);
+
+ return more_info_label;
+}
diff --git a/src/caja-sidebar-title.h b/src/caja-sidebar-title.h
new file mode 100644
index 00000000..14d56a3a
--- /dev/null
+++ b/src/caja-sidebar-title.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Author: Andy Hertzfeld <[email protected]>
+ */
+
+/*
+ * This is the header file for the sidebar title, which is part of the sidebar.
+ */
+
+#ifndef CAJA_SIDEBAR_TITLE_H
+#define CAJA_SIDEBAR_TITLE_H
+
+#include <gtk/gtk.h>
+#include <eel/eel-background.h>
+#include <libcaja-private/caja-file.h>
+
+#define CAJA_TYPE_SIDEBAR_TITLE caja_sidebar_title_get_type()
+#define CAJA_SIDEBAR_TITLE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_SIDEBAR_TITLE, CajaSidebarTitle))
+#define CAJA_SIDEBAR_TITLE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_SIDEBAR_TITLE, CajaSidebarTitleClass))
+#define CAJA_IS_SIDEBAR_TITLE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_SIDEBAR_TITLE))
+#define CAJA_IS_SIDEBAR_TITLE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_SIDEBAR_TITLE))
+#define CAJA_SIDEBAR_TITLE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_SIDEBAR_TITLE, CajaSidebarTitleClass))
+
+typedef struct CajaSidebarTitleDetails CajaSidebarTitleDetails;
+
+typedef struct
+{
+ GtkVBox box;
+ CajaSidebarTitleDetails *details;
+} CajaSidebarTitle;
+
+typedef struct
+{
+ GtkVBoxClass parent_class;
+} CajaSidebarTitleClass;
+
+GType caja_sidebar_title_get_type (void);
+GtkWidget *caja_sidebar_title_new (void);
+void caja_sidebar_title_set_file (CajaSidebarTitle *sidebar_title,
+ CajaFile *file,
+ const char *initial_text);
+void caja_sidebar_title_set_text (CajaSidebarTitle *sidebar_title,
+ const char *new_title);
+char * caja_sidebar_title_get_text (CajaSidebarTitle *sidebar_title);
+gboolean caja_sidebar_title_hit_test_icon (CajaSidebarTitle *sidebar_title,
+ int x,
+ int y);
+void caja_sidebar_title_select_text_color (CajaSidebarTitle *sidebar_title,
+ EelBackground *background,
+ gboolean is_default);
+
+#endif /* CAJA_SIDEBAR_TITLE_H */
diff --git a/src/caja-spatial-window-ui.xml b/src/caja-spatial-window-ui.xml
new file mode 100644
index 00000000..101888f4
--- /dev/null
+++ b/src/caja-spatial-window-ui.xml
@@ -0,0 +1,29 @@
+<ui>
+<menubar name="MenuBar">
+ <menu action="File">
+ <placeholder name="Location Placeholder">
+ <menuitem name="Up" action="Up"/>
+ <menuitem name="Go to Location" action="Go to Location"/>
+ </placeholder>
+ <placeholder name="Close Items Placeholder">
+ <menuitem name="Close Parent Folders" action="Close Parent Folders"/>
+ <menuitem name="Close All Folders" action="Close All Folders"/>
+ </placeholder>
+ </menu>
+ <placeholder name="Other Menus">
+ <menu action="Places">
+ <menuitem name="Home" action="Home"/>
+ <menuitem name="Go to Computer" action="Go to Computer"/>
+ <menuitem name="Go to Templates" action="Go to Templates"/>
+ <menuitem name="Go to Trash" action="Go to Trash"/>
+ <menuitem name="Go to Network" action="Go to Network"/>
+ <menuitem name="Search" action="Search"/>
+ <separator/>
+ <placeholder name="Bookmarks Placeholder"/>
+ <separator/>
+ <menuitem name="Add Bookmark" action="Add Bookmark"/>
+ <menuitem name="Edit Bookmark" action="Edit Bookmarks"/>
+ </menu>
+ </placeholder>
+</menubar>
+</ui>
diff --git a/src/caja-spatial-window.c b/src/caja-spatial-window.c
new file mode 100644
index 00000000..9f148f53
--- /dev/null
+++ b/src/caja-spatial-window.c
@@ -0,0 +1,1209 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 1999, 2000 Red Hat, Inc.
+ * Copyright (C) 1999, 2000, 2001 Eazel, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Authors: Elliot Lee <[email protected]>
+ * John Sullivan <[email protected]>
+ *
+ */
+
+/* caja-window.c: Implementation of the main window object */
+
+#include <config.h>
+#include "caja-spatial-window.h"
+#include "caja-window-private.h"
+#include "caja-window-bookmarks.h"
+
+#include "caja-actions.h"
+#include "caja-application.h"
+#include "caja-desktop-window.h"
+#include "caja-bookmarks-window.h"
+#include "caja-location-dialog.h"
+#include "caja-main.h"
+#include "caja-query-editor.h"
+#include "caja-search-bar.h"
+#include "caja-window-manage-views.h"
+#include "caja-zoom-control.h"
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-gtk-extensions.h>
+#include <eel/eel-gtk-macros.h>
+#include <eel/eel-string.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gdk/gdkkeysyms.h>
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.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-file-attributes.h>
+#include <libcaja-private/caja-global-preferences.h>
+#include <libcaja-private/caja-horizontal-splitter.h>
+#include <libcaja-private/caja-metadata.h>
+#include <libcaja-private/caja-mime-actions.h>
+#include <libcaja-private/caja-program-choosing.h>
+#include <libcaja-private/caja-undo.h>
+#include <libcaja-private/caja-search-directory.h>
+#include <libcaja-private/caja-search-engine.h>
+#include <libcaja-private/caja-signaller.h>
+
+#define MAX_TITLE_LENGTH 180
+#define MAX_SHORTNAME_PATH 16
+
+#define SPATIAL_ACTION_PLACES "Places"
+#define SPATIAL_ACTION_GO_TO_LOCATION "Go to Location"
+#define SPATIAL_ACTION_CLOSE_PARENT_FOLDERS "Close Parent Folders"
+#define SPATIAL_ACTION_CLOSE_ALL_FOLDERS "Close All Folders"
+#define MENU_PATH_SPATIAL_BOOKMARKS_PLACEHOLDER "/MenuBar/Other Menus/Places/Bookmarks Placeholder"
+
+struct _CajaSpatialWindowDetails
+{
+ GtkActionGroup *spatial_action_group; /* owned by ui_manager */
+ char *last_geometry;
+ guint save_geometry_timeout_id;
+
+ gboolean saved_data_on_close;
+ GtkWidget *content_box;
+ GtkWidget *location_button;
+ GtkWidget *location_label;
+ GtkWidget *location_icon;
+};
+
+static const GtkTargetEntry location_button_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 },
+};
+
+G_DEFINE_TYPE(CajaSpatialWindow, caja_spatial_window, CAJA_TYPE_WINDOW)
+#define parent_class caja_spatial_window_parent_class
+
+static void caja_spatial_window_save_geometry (CajaWindowSlot *slot);
+
+static gboolean
+save_window_geometry_timeout (gpointer callback_data)
+{
+ CajaSpatialWindow *window;
+ CajaWindowSlot *slot;
+
+ window = CAJA_SPATIAL_WINDOW (callback_data);
+ slot = caja_window_get_active_slot (CAJA_WINDOW (window));
+
+ if (slot != NULL)
+ {
+ caja_spatial_window_save_geometry (slot);
+ }
+
+ window->details->save_geometry_timeout_id = 0;
+
+ return FALSE;
+}
+
+static gboolean
+caja_spatial_window_configure_event (GtkWidget *widget,
+ GdkEventConfigure *event)
+{
+ CajaSpatialWindow *window;
+ char *geometry_string;
+
+ window = CAJA_SPATIAL_WINDOW (widget);
+
+ GTK_WIDGET_CLASS (caja_spatial_window_parent_class)->configure_event (widget, event);
+
+ /* Only save the geometry if the user hasn't resized the window
+ * for a second. Otherwise delay the callback another second.
+ */
+ if (window->details->save_geometry_timeout_id != 0)
+ {
+ g_source_remove (window->details->save_geometry_timeout_id);
+ }
+ if (gtk_widget_get_visible (GTK_WIDGET (window)) && !CAJA_IS_DESKTOP_WINDOW (window))
+ {
+ geometry_string = eel_gtk_window_get_geometry_string (GTK_WINDOW (window));
+
+ /* If the last geometry is NULL the window must have just
+ * been shown. No need to save geometry to disk since it
+ * must be the same.
+ */
+ if (window->details->last_geometry == NULL)
+ {
+ window->details->last_geometry = geometry_string;
+ return FALSE;
+ }
+
+ /* Don't save geometry if it's the same as before. */
+ if (!strcmp (window->details->last_geometry,
+ geometry_string))
+ {
+ g_free (geometry_string);
+ return FALSE;
+ }
+
+ g_free (window->details->last_geometry);
+ window->details->last_geometry = geometry_string;
+
+ window->details->save_geometry_timeout_id =
+ g_timeout_add_seconds (1, save_window_geometry_timeout, window);
+ }
+
+ return FALSE;
+}
+
+static void
+caja_spatial_window_unrealize (GtkWidget *widget)
+{
+ CajaSpatialWindow *window;
+ CajaWindowSlot *slot;
+
+ window = CAJA_SPATIAL_WINDOW (widget);
+ slot = caja_window_get_active_slot (CAJA_WINDOW (window));
+
+ GTK_WIDGET_CLASS (caja_spatial_window_parent_class)->unrealize (widget);
+
+ if (window->details->save_geometry_timeout_id != 0)
+ {
+ g_source_remove (window->details->save_geometry_timeout_id);
+ window->details->save_geometry_timeout_id = 0;
+
+ if (slot != NULL)
+ {
+ caja_spatial_window_save_geometry (slot);
+ }
+ }
+}
+
+static gboolean
+caja_spatial_window_state_event (GtkWidget *widget,
+ GdkEventWindowState *event)
+{
+ CajaWindow *window;
+ CajaWindowSlot *slot;
+ CajaFile *viewed_file;
+
+ window = CAJA_WINDOW (widget);
+ slot = window->details->active_pane->active_slot;
+ viewed_file = slot->viewed_file;
+
+ if (!CAJA_IS_DESKTOP_WINDOW (widget))
+ {
+
+ if (event->changed_mask & GDK_WINDOW_STATE_MAXIMIZED &&
+ viewed_file != NULL)
+ {
+ caja_file_set_boolean_metadata (viewed_file,
+ CAJA_METADATA_KEY_WINDOW_MAXIMIZED,
+ FALSE,
+ event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED);
+ }
+
+ if (event->changed_mask & GDK_WINDOW_STATE_STICKY &&
+ viewed_file != NULL)
+ {
+ caja_file_set_boolean_metadata (viewed_file,
+ CAJA_METADATA_KEY_WINDOW_STICKY,
+ FALSE,
+ event->new_window_state & GDK_WINDOW_STATE_STICKY);
+ }
+
+ if (event->changed_mask & GDK_WINDOW_STATE_ABOVE &&
+ viewed_file != NULL)
+ {
+ caja_file_set_boolean_metadata (viewed_file,
+ CAJA_METADATA_KEY_WINDOW_KEEP_ABOVE,
+ FALSE,
+ event->new_window_state & GDK_WINDOW_STATE_ABOVE);
+ }
+
+ }
+
+ if (GTK_WIDGET_CLASS (caja_spatial_window_parent_class)->window_state_event != NULL)
+ {
+ return GTK_WIDGET_CLASS (caja_spatial_window_parent_class)->window_state_event (widget, event);
+ }
+
+ return FALSE;
+}
+
+static void
+caja_spatial_window_destroy (GtkObject *object)
+{
+ CajaSpatialWindow *window;
+
+ window = CAJA_SPATIAL_WINDOW (object);
+
+ window->details->content_box = NULL;
+
+ GTK_OBJECT_CLASS (caja_spatial_window_parent_class)->destroy (object);
+}
+
+static void
+caja_spatial_window_finalize (GObject *object)
+{
+ CajaSpatialWindow *window;
+
+ window = CAJA_SPATIAL_WINDOW (object);
+
+ if (window->details->last_geometry != NULL)
+ {
+ g_free (window->details->last_geometry);
+ }
+
+ G_OBJECT_CLASS (caja_spatial_window_parent_class)->finalize (object);
+}
+
+static void
+caja_spatial_window_save_geometry (CajaWindowSlot *slot)
+{
+ CajaWindow *window;
+ CajaFile *viewed_file;
+ char *geometry_string;
+
+ window = CAJA_WINDOW (slot->pane->window);
+
+ viewed_file = slot->viewed_file;
+
+ if (viewed_file == NULL)
+ {
+ /* We never showed a file */
+ return;
+ }
+
+ if (gtk_widget_get_window (GTK_WIDGET (window)) &&
+ !(gdk_window_get_state (gtk_widget_get_window (GTK_WIDGET(window))) & GDK_WINDOW_STATE_MAXIMIZED))
+ {
+ geometry_string = eel_gtk_window_get_geometry_string (GTK_WINDOW (window));
+
+ caja_file_set_metadata (viewed_file,
+ CAJA_METADATA_KEY_WINDOW_GEOMETRY,
+ NULL,
+ geometry_string);
+
+ g_free (geometry_string);
+ }
+}
+
+static void
+caja_spatial_window_save_scroll_position (CajaWindowSlot *slot)
+{
+ CajaWindow *window;
+ char *scroll_string;
+
+ window = CAJA_WINDOW (slot->pane->window);
+
+ if (slot->content_view == NULL ||
+ slot->viewed_file == NULL)
+ {
+ return;
+ }
+
+ scroll_string = caja_view_get_first_visible_file (slot->content_view);
+ caja_file_set_metadata (slot->viewed_file,
+ CAJA_METADATA_KEY_WINDOW_SCROLL_POSITION,
+ NULL,
+ scroll_string);
+ g_free (scroll_string);
+}
+
+static void
+caja_spatial_window_save_show_hidden_files_mode (CajaWindowSlot *slot)
+{
+ CajaWindow *window;
+ char *show_hidden_file_setting;
+ CajaWindowShowHiddenFilesMode mode;
+
+ if (slot->viewed_file == NULL)
+ {
+ return;
+ }
+
+ window = CAJA_WINDOW (slot->pane->window);
+
+ mode = CAJA_WINDOW (window)->details->show_hidden_files_mode;
+ if (mode != CAJA_WINDOW_SHOW_HIDDEN_FILES_DEFAULT)
+ {
+ if (mode == CAJA_WINDOW_SHOW_HIDDEN_FILES_ENABLE)
+ {
+ show_hidden_file_setting = "1";
+ }
+ else
+ {
+ show_hidden_file_setting = "0";
+ }
+ caja_file_set_metadata (slot->viewed_file,
+ CAJA_METADATA_KEY_WINDOW_SHOW_HIDDEN_FILES,
+ NULL,
+ show_hidden_file_setting);
+ }
+}
+
+static void
+caja_spatial_window_show (GtkWidget *widget)
+{
+ CajaWindow *window;
+ CajaWindowSlot *slot;
+
+ window = CAJA_WINDOW (widget);
+ slot = caja_window_get_active_slot (window);
+
+ GTK_WIDGET_CLASS (caja_spatial_window_parent_class)->show (widget);
+
+ if (slot != NULL && slot->query_editor != NULL)
+ {
+ caja_query_editor_grab_focus (CAJA_QUERY_EDITOR (slot->query_editor));
+ }
+}
+
+static void
+action_close_parent_folders_callback (GtkAction *action,
+ gpointer user_data)
+{
+ caja_application_close_parent_windows (CAJA_SPATIAL_WINDOW (user_data));
+}
+
+static void
+action_close_all_folders_callback (GtkAction *action,
+ gpointer user_data)
+{
+ caja_application_close_all_spatial_windows ();
+}
+
+static void
+real_prompt_for_location (CajaWindow *window,
+ const char *initial)
+{
+ GtkWidget *dialog;
+
+ dialog = caja_location_dialog_new (window);
+ if (initial != NULL)
+ {
+ caja_location_dialog_set_location (CAJA_LOCATION_DIALOG (dialog),
+ initial);
+ }
+
+ gtk_widget_show (dialog);
+}
+
+static CajaIconInfo *
+real_get_icon (CajaWindow *window,
+ CajaWindowSlot *slot)
+{
+ return caja_file_get_icon (slot->viewed_file, 48,
+ CAJA_FILE_ICON_FLAGS_IGNORE_VISITING |
+ CAJA_FILE_ICON_FLAGS_USE_MOUNT_ICON);
+}
+
+static void
+sync_window_title (CajaWindow *window)
+{
+ CajaWindowSlot *slot;
+
+ slot = caja_window_get_active_slot (window);
+
+ if (slot->title == NULL || slot->title[0] == '\0')
+ {
+ gtk_window_set_title (GTK_WINDOW (window), _("Caja"));
+ }
+ else
+ {
+ char *window_title;
+
+ window_title = eel_str_middle_truncate (slot->title, MAX_TITLE_LENGTH);
+ gtk_window_set_title (GTK_WINDOW (window), window_title);
+ g_free (window_title);
+ }
+}
+
+static void
+real_sync_title (CajaWindow *window,
+ CajaWindowSlot *slot)
+{
+ g_assert (slot == caja_window_get_active_slot (window));
+
+ sync_window_title (window);
+}
+
+static void
+real_get_min_size (CajaWindow *window,
+ guint *min_width, guint *min_height)
+{
+ if (min_width)
+ {
+ *min_width = CAJA_SPATIAL_WINDOW_MIN_WIDTH;
+ }
+ if (min_height)
+ {
+ *min_height = CAJA_SPATIAL_WINDOW_MIN_HEIGHT;
+ }
+}
+
+static void
+real_get_default_size (CajaWindow *window,
+ guint *default_width, guint *default_height)
+{
+ if (default_width)
+ {
+ *default_width = CAJA_SPATIAL_WINDOW_DEFAULT_WIDTH;
+ }
+ if (default_height)
+ {
+ *default_height = CAJA_SPATIAL_WINDOW_DEFAULT_HEIGHT;
+ }
+}
+
+static void
+real_sync_allow_stop (CajaWindow *window,
+ CajaWindowSlot *slot)
+{
+}
+
+static void
+real_set_allow_up (CajaWindow *window, gboolean allow)
+{
+ CajaSpatialWindow *spatial;
+ GtkAction *action;
+
+ spatial = CAJA_SPATIAL_WINDOW (window);
+
+ action = gtk_action_group_get_action (spatial->details->spatial_action_group,
+ SPATIAL_ACTION_CLOSE_PARENT_FOLDERS);
+ gtk_action_set_sensitive (action, allow);
+
+ CAJA_WINDOW_CLASS (caja_spatial_window_parent_class)->set_allow_up (window, allow);
+}
+
+static CajaWindowSlot *
+real_open_slot (CajaWindowPane *pane,
+ CajaWindowOpenSlotFlags flags)
+{
+ CajaWindowSlot *slot;
+ GList *slots;
+
+ g_assert (caja_window_get_active_slot (pane->window) == NULL);
+
+ slots = caja_window_get_slots (pane->window);
+ g_assert (slots == NULL);
+ g_list_free (slots);
+
+ slot = g_object_new (CAJA_TYPE_WINDOW_SLOT, NULL);
+ slot->pane = pane;
+ gtk_container_add (GTK_CONTAINER (CAJA_SPATIAL_WINDOW (pane->window)->details->content_box),
+ slot->content_box);
+ gtk_widget_show (slot->content_box);
+ return slot;
+}
+
+static void
+save_spatial_data (CajaWindowSlot *slot)
+{
+ caja_spatial_window_save_geometry (slot);
+ caja_spatial_window_save_scroll_position (slot);
+ caja_spatial_window_save_show_hidden_files_mode (slot);
+}
+
+static void
+real_close_slot (CajaWindowPane *pane,
+ CajaWindowSlot *slot)
+{
+ /* Save spatial data for close if we didn't already */
+ if (!CAJA_SPATIAL_WINDOW (pane->window)->details->saved_data_on_close)
+ {
+ save_spatial_data (slot);
+ }
+
+ EEL_CALL_PARENT (CAJA_WINDOW_CLASS,
+ close_slot, (pane, slot));
+}
+
+static void
+real_window_close (CajaWindow *window)
+{
+ CajaWindowSlot *slot;
+
+ /* We're closing the window, save the geometry. */
+ /* Note that we do this in window close, not slot close, because slot
+ * close is too late, by then the widgets have been unrealized.
+ * This is for the close by WM case, if you're closing via Ctrl-W that
+ * means we close the slots first and this is not an issue */
+ if (window->details->active_pane != NULL &&
+ window->details->active_pane->active_slot != NULL)
+ {
+ slot = window->details->active_pane->active_slot;
+
+ save_spatial_data (slot);
+ CAJA_SPATIAL_WINDOW (window)->details->saved_data_on_close = TRUE;
+ }
+
+ EEL_CALL_PARENT (CAJA_WINDOW_CLASS,
+ close, (window));
+}
+
+static void
+location_menu_item_activated_callback (GtkWidget *menu_item,
+ CajaWindow *window)
+{
+ CajaWindowSlot *slot;
+ char *location;
+ GFile *current;
+ GFile *dest;
+ GdkEvent *event;
+
+ slot = window->details->active_pane->active_slot;
+
+ location = caja_window_slot_get_location_uri (slot);
+ current = g_file_new_for_uri (location);
+ g_free (location);
+
+ dest = g_object_get_data (G_OBJECT (menu_item), "uri");
+
+ event = gtk_get_current_event();
+
+ if (!g_file_equal (current, dest))
+ {
+ GFile *child;
+ gboolean close_behind;
+ GList *selection;
+
+ close_behind = FALSE;
+ selection = NULL;
+
+ child = g_object_get_data (G_OBJECT(menu_item), "child_uri");
+ if (child != NULL)
+ {
+ selection = g_list_prepend (NULL, g_object_ref (child));
+ }
+
+ if (event != NULL && ((GdkEventAny *) event)->type == GDK_BUTTON_RELEASE &&
+ (((GdkEventButton *) event)->button == 2 ||
+ (((GdkEventButton *) event)->state & GDK_SHIFT_MASK) != 0))
+ {
+ close_behind = TRUE;
+ }
+
+ caja_window_slot_open_location_with_selection
+ (slot, dest, selection, close_behind);
+
+ eel_g_object_list_free (selection);
+ }
+
+ if (event != NULL)
+ {
+ gdk_event_free (event);
+ }
+
+ g_object_unref (current);
+}
+
+static void
+got_file_info_for_location_menu_callback (CajaFile *file,
+ gpointer callback_data)
+{
+ GtkWidget *menu_item = callback_data;
+ GtkWidget *label;
+ GtkWidget *icon;
+ GdkPixbuf *pixbuf;
+ char *name;
+
+ g_return_if_fail (CAJA_IS_FILE (file));
+
+ pixbuf = NULL;
+
+ name = caja_file_get_display_name (file);
+ label = gtk_bin_get_child (GTK_BIN (menu_item));
+ gtk_label_set_label (GTK_LABEL (label), name);
+ g_free (name);
+
+ pixbuf = caja_file_get_icon_pixbuf (file,
+ caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU),
+ TRUE,
+ CAJA_FILE_ICON_FLAGS_IGNORE_VISITING);
+
+ if (pixbuf != NULL)
+ {
+ icon = gtk_image_new_from_pixbuf (pixbuf);
+ g_object_unref (pixbuf);
+ }
+ else
+ {
+ icon = gtk_image_new_from_stock (GTK_STOCK_OPEN, GTK_ICON_SIZE_MENU);
+ }
+
+ if (icon)
+ {
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item), icon);
+ }
+ g_object_unref (file);
+ g_object_unref (menu_item);
+}
+
+static void
+menu_deactivate_callback (GtkWidget *menu,
+ gpointer data)
+{
+ GMainLoop *loop;
+
+ loop = data;
+
+ if (g_main_loop_is_running (loop))
+ {
+ g_main_loop_quit (loop);
+ }
+}
+
+static void
+menu_popup_pos (GtkMenu *menu,
+ gint *x,
+ gint *y,
+ gboolean *push_in,
+ gpointer user_data)
+{
+ GtkWidget *widget;
+ GtkRequisition menu_requisition, button_requisition;
+ GtkAllocation allocation;
+
+ widget = user_data;
+
+ gtk_widget_size_request (GTK_WIDGET (menu), &menu_requisition);
+ gtk_widget_size_request (widget, &button_requisition);
+ gtk_widget_get_allocation (widget, &allocation);
+
+ gdk_window_get_origin (gtk_widget_get_window (widget), x, y);
+ *x += allocation.x;
+ *y += allocation.y;
+
+ *y -= menu_requisition.height - button_requisition.height;
+
+ *push_in = TRUE;
+}
+
+static gboolean
+location_button_pressed_callback (GtkWidget *widget,
+ GdkEventButton *event,
+ CajaWindow *window)
+{
+ CajaView *view;
+
+ view = window->details->active_pane->active_slot->content_view;
+
+ if (event->button == 3 && view != NULL)
+ {
+ caja_view_pop_up_location_context_menu (view, event, NULL);
+ }
+
+ return FALSE;
+}
+
+static void
+location_button_clicked_callback (GtkWidget *widget,
+ CajaSpatialWindow *window)
+{
+ CajaWindowSlot *slot;
+ GtkWidget *popup, *menu_item, *first_item = NULL;
+ char *location;
+ GFile *uri;
+ GFile *child_uri;
+ GMainLoop *loop;
+
+ slot = CAJA_WINDOW (window)->details->active_pane->active_slot;
+
+ popup = gtk_menu_new ();
+ first_item = NULL;
+
+ location = caja_window_slot_get_location_uri (slot);
+ g_return_if_fail (location != NULL);
+
+ uri = g_file_new_for_uri (location);
+ g_free (location);
+
+ child_uri = NULL;
+ while (uri != NULL)
+ {
+ CajaFile *file;
+ char *name;
+
+ file = caja_file_get (uri);
+
+ name = caja_file_get_display_name (file);
+ menu_item = gtk_image_menu_item_new_with_label (name);
+ gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (menu_item), TRUE);
+ g_free (name);
+
+ if (first_item == NULL)
+ {
+ first_item = menu_item;
+ }
+
+ g_object_ref (menu_item);
+ caja_file_call_when_ready (file,
+ CAJA_FILE_ATTRIBUTE_INFO,
+ got_file_info_for_location_menu_callback,
+ menu_item);
+
+ gtk_widget_show (menu_item);
+ g_signal_connect (menu_item, "activate",
+ G_CALLBACK (location_menu_item_activated_callback),
+ window);
+
+ g_object_set_data_full (G_OBJECT (menu_item),
+ "uri",
+ g_object_ref (uri),
+ (GDestroyNotify)g_object_unref);
+
+ if (child_uri)
+ {
+ g_object_set_data_full (G_OBJECT (menu_item),
+ "child_uri",
+ g_object_ref (child_uri),
+ (GDestroyNotify)g_object_unref);
+ }
+
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (popup), menu_item);
+
+ if (child_uri)
+ {
+ g_object_unref (child_uri);
+ }
+ child_uri = uri;
+ uri = g_file_get_parent (uri);
+ }
+
+ if (child_uri)
+ {
+ g_object_unref (child_uri);
+ }
+ if (uri)
+ {
+ g_object_unref (uri);
+ }
+
+ gtk_menu_set_screen (GTK_MENU (popup), gtk_widget_get_screen (widget));
+
+ loop = g_main_loop_new (NULL, FALSE);
+
+ g_signal_connect (popup, "deactivate",
+ G_CALLBACK (menu_deactivate_callback),
+ loop);
+
+ gtk_grab_add (popup);
+ gtk_menu_popup (GTK_MENU (popup), NULL, NULL, menu_popup_pos, widget, 1, GDK_CURRENT_TIME);
+ gtk_menu_shell_select_item (GTK_MENU_SHELL (popup), first_item);
+ g_main_loop_run (loop);
+ gtk_grab_remove (popup);
+ g_main_loop_unref (loop);
+ g_object_ref_sink (popup);
+ g_object_unref (popup);
+}
+
+static int
+get_dnd_icon_size (CajaSpatialWindow *window)
+{
+ CajaWindow *parent;
+ CajaView *view;
+ CajaZoomLevel zoom_level;
+
+ parent = CAJA_WINDOW(window);
+ view = parent->details->active_pane->active_slot->content_view;
+
+ if (view == NULL)
+ {
+ return CAJA_ICON_SIZE_STANDARD;
+ }
+ else
+ {
+ zoom_level = caja_view_get_zoom_level (view);
+ return caja_get_icon_size_for_zoom_level (zoom_level);
+ }
+
+}
+
+static void
+location_button_drag_begin_callback (GtkWidget *widget,
+ GdkDragContext *context,
+ CajaSpatialWindow *window)
+{
+ CajaWindowSlot *slot;
+ GdkPixbuf *pixbuf;
+
+ slot = CAJA_WINDOW (window)->details->active_pane->active_slot;
+
+ pixbuf = caja_file_get_icon_pixbuf (slot->viewed_file,
+ get_dnd_icon_size (window),
+ FALSE,
+ CAJA_FILE_ICON_FLAGS_IGNORE_VISITING | CAJA_FILE_ICON_FLAGS_FOR_DRAG_ACCEPT);
+
+ gtk_drag_set_icon_pixbuf (context, pixbuf, 0, 0);
+
+ g_object_unref (pixbuf);
+}
+
+/* build MATE icon list, which only contains the window's URI.
+ * If we just used URIs, moving the folder to trash
+ * wouldn't work */
+static void
+get_data_binder (CajaDragEachSelectedItemDataGet iteratee,
+ gpointer iterator_context,
+ gpointer data)
+{
+ CajaSpatialWindow *window;
+ CajaWindowSlot *slot;
+ char *location;
+ int icon_size;
+
+ g_assert (CAJA_IS_SPATIAL_WINDOW (iterator_context));
+ window = CAJA_SPATIAL_WINDOW (iterator_context);
+
+ slot = CAJA_WINDOW (window)->details->active_pane->active_slot;
+
+ location = caja_window_slot_get_location_uri (slot);
+ icon_size = get_dnd_icon_size (window);
+
+ iteratee (location,
+ 0,
+ 0,
+ icon_size,
+ icon_size,
+ data);
+
+ g_free (location);
+}
+
+static void
+location_button_drag_data_get_callback (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint time,
+ CajaSpatialWindow *window)
+{
+ caja_drag_drag_data_get (widget, context, selection_data,
+ info, time, window, get_data_binder);
+}
+
+void
+caja_spatial_window_set_location_button (CajaSpatialWindow *window,
+ GFile *location)
+{
+ if (location != NULL)
+ {
+ CajaFile *file;
+ char *name;
+ GError *error;
+
+ file = caja_file_get (location);
+
+ /* FIXME: monitor for name change... */
+ name = caja_file_get_display_name (file);
+ gtk_label_set_label (GTK_LABEL (window->details->location_label),
+ name);
+ g_free (name);
+ gtk_widget_set_sensitive (window->details->location_button, TRUE);
+
+ error = caja_file_get_file_info_error (file);
+ if (error == NULL)
+ {
+ GdkPixbuf *pixbuf;
+
+ pixbuf = caja_file_get_icon_pixbuf (file,
+ caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU),
+ TRUE,
+ CAJA_FILE_ICON_FLAGS_IGNORE_VISITING);
+
+ if (pixbuf != NULL)
+ {
+ gtk_image_set_from_pixbuf (GTK_IMAGE (window->details->location_icon), pixbuf);
+ g_object_unref (pixbuf);
+ }
+ else
+ {
+ gtk_image_set_from_stock (GTK_IMAGE (window->details->location_icon),
+ GTK_STOCK_OPEN, GTK_ICON_SIZE_MENU);
+ }
+ }
+ g_object_unref (file);
+
+ }
+ else
+ {
+ gtk_label_set_label (GTK_LABEL (window->details->location_label),
+ "");
+ gtk_widget_set_sensitive (window->details->location_button, FALSE);
+ }
+}
+
+static void
+action_go_to_location_callback (GtkAction *action,
+ gpointer user_data)
+{
+ CajaWindow *window;
+
+ window = CAJA_WINDOW (user_data);
+
+ caja_window_prompt_for_location (window, NULL);
+}
+
+static void
+action_add_bookmark_callback (GtkAction *action,
+ gpointer user_data)
+{
+ CajaWindow *window;
+
+ window = CAJA_WINDOW (user_data);
+
+ if (!CAJA_IS_DESKTOP_WINDOW (window)) /* don't bookmark x-caja-desktop:/// */
+ {
+ caja_window_add_bookmark_for_current_location (window);
+ }
+}
+
+static void
+action_edit_bookmarks_callback (GtkAction *action,
+ gpointer user_data)
+{
+ caja_window_edit_bookmarks (CAJA_WINDOW (user_data));
+}
+
+static void
+action_search_callback (GtkAction *action,
+ gpointer user_data)
+{
+ CajaWindow *window;
+ char *uri;
+ GFile *f;
+
+ window = CAJA_WINDOW (user_data);
+
+ uri = caja_search_directory_generate_new_uri ();
+ f = g_file_new_for_uri (uri);
+ caja_window_go_to (window, f);
+ g_object_unref (f);
+ g_free (uri);
+}
+
+static const GtkActionEntry spatial_entries[] =
+{
+ /* name, stock id, label */ { SPATIAL_ACTION_PLACES, NULL, N_("_Places") },
+ /* name, stock id, label */ {
+ SPATIAL_ACTION_GO_TO_LOCATION, NULL, N_("Open _Location..."),
+ "<control>L", N_("Specify a location to open"),
+ G_CALLBACK (action_go_to_location_callback)
+ },
+ /* name, stock id, label */ {
+ SPATIAL_ACTION_CLOSE_PARENT_FOLDERS, NULL, N_("Close P_arent Folders"),
+ "<control><shift>W", N_("Close this folder's parents"),
+ G_CALLBACK (action_close_parent_folders_callback)
+ },
+ /* name, stock id, label */ {
+ SPATIAL_ACTION_CLOSE_ALL_FOLDERS, NULL, N_("Clos_e All Folders"),
+ "<control>Q", N_("Close all folder windows"),
+ G_CALLBACK (action_close_all_folders_callback)
+ },
+ /* name, stock id, label */ { "Add Bookmark", GTK_STOCK_ADD, N_("_Add Bookmark"),
+ "<control>d", N_("Add a bookmark for the current location to this menu"),
+ G_CALLBACK (action_add_bookmark_callback)
+ },
+ /* name, stock id, label */ { "Edit Bookmarks", NULL, N_("_Edit Bookmarks..."),
+ "<control>b", N_("Display a window that allows editing the bookmarks in this menu"),
+ G_CALLBACK (action_edit_bookmarks_callback)
+ },
+ /* name, stock id, label */ { "Search", "gtk-find", N_("_Search for Files..."),
+ "<control>F", N_("Locate documents and folders on this computer by name or content"),
+ G_CALLBACK (action_search_callback)
+ },
+};
+
+static const char* icon_entries[] =
+{
+ "/MenuBar/Other Menus/Places/Home",
+ "/MenuBar/Other Menus/Places/Go to Computer",
+ "/MenuBar/Other Menus/Places/Go to Templates",
+ "/MenuBar/Other Menus/Places/Go to Trash",
+ "/MenuBar/Other Menus/Places/Go to Network"
+};
+
+static void
+caja_spatial_window_init (CajaSpatialWindow *window)
+{
+ GtkRcStyle *rc_style;
+ GtkWidget *arrow;
+ GtkWidget *hbox, *vbox;
+ GtkActionGroup *action_group;
+ GtkUIManager *ui_manager;
+ GtkTargetList *targets;
+ const char *ui;
+ int i;
+ GtkWidget *menuitem;
+ CajaWindow *win;
+ CajaWindowPane *pane;
+
+ window->details = G_TYPE_INSTANCE_GET_PRIVATE (window,
+ CAJA_TYPE_SPATIAL_WINDOW,
+ CajaSpatialWindowDetails);
+
+ win = CAJA_WINDOW (window);
+
+ gtk_table_attach (GTK_TABLE (win->details->table),
+ win->details->statusbar,
+ /* X direction */ /* Y direction */
+ 0, 1, 5, 6,
+ GTK_EXPAND | GTK_FILL | GTK_SHRINK, 0,
+ 0, 0);
+ gtk_widget_show (win->details->statusbar);
+
+ pane = caja_window_pane_new (win);
+ win->details->panes = g_list_prepend (win->details->panes, pane);
+
+ window->affect_spatial_window_on_next_location_change = TRUE;
+
+ vbox = gtk_vbox_new (FALSE, 0);
+ gtk_table_attach (GTK_TABLE (CAJA_WINDOW (window)->details->table),
+ vbox,
+ /* X direction */ /* Y direction */
+ 0, 1, 1, 4,
+ GTK_EXPAND | GTK_FILL | GTK_SHRINK, GTK_EXPAND | GTK_FILL | GTK_SHRINK,
+ 0, 0);
+ gtk_widget_show (vbox);
+ window->details->content_box = vbox;
+
+ window->details->location_button = gtk_button_new ();
+ g_signal_connect (window->details->location_button,
+ "button-press-event",
+ G_CALLBACK (location_button_pressed_callback),
+ window);
+ gtk_button_set_relief (GTK_BUTTON (window->details->location_button),
+ GTK_RELIEF_NORMAL);
+ rc_style = gtk_widget_get_modifier_style (window->details->location_button);
+ rc_style->xthickness = 0;
+ rc_style->ythickness = 0;
+ gtk_widget_modify_style (window->details->location_button,
+ rc_style);
+
+ gtk_widget_show (window->details->location_button);
+ hbox = gtk_hbox_new (FALSE, 3);
+ gtk_container_add (GTK_CONTAINER (window->details->location_button),
+ hbox);
+ gtk_widget_show (hbox);
+
+ window->details->location_icon = gtk_image_new_from_stock (GTK_STOCK_OPEN, GTK_ICON_SIZE_MENU);
+ gtk_box_pack_start (GTK_BOX (hbox), window->details->location_icon, FALSE, FALSE, 0);
+ gtk_widget_show (window->details->location_icon);
+
+ window->details->location_label = gtk_label_new ("");
+ gtk_label_set_ellipsize (GTK_LABEL (window->details->location_label), PANGO_ELLIPSIZE_END);
+ gtk_label_set_max_width_chars (GTK_LABEL (window->details->location_label), MAX_SHORTNAME_PATH);
+ gtk_box_pack_start (GTK_BOX (hbox), window->details->location_label,
+ FALSE, FALSE, 0);
+ gtk_widget_show (window->details->location_label);
+
+ arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE);
+ gtk_box_pack_start (GTK_BOX (hbox), arrow, FALSE, FALSE, 0);
+ gtk_widget_show (arrow);
+
+ gtk_drag_source_set (window->details->location_button,
+ GDK_BUTTON1_MASK | GDK_BUTTON2_MASK, location_button_drag_types,
+ G_N_ELEMENTS (location_button_drag_types),
+ GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK | GDK_ACTION_ASK);
+ g_signal_connect (window->details->location_button,
+ "drag_begin",
+ G_CALLBACK (location_button_drag_begin_callback),
+ window);
+ g_signal_connect (window->details->location_button,
+ "drag_data_get",
+ G_CALLBACK (location_button_drag_data_get_callback),
+ window);
+
+ targets = gtk_drag_source_get_target_list (window->details->location_button);
+ gtk_target_list_add_text_targets (targets, CAJA_ICON_DND_TEXT);
+
+ gtk_widget_set_sensitive (window->details->location_button, FALSE);
+ g_signal_connect (window->details->location_button,
+ "clicked",
+ G_CALLBACK (location_button_clicked_callback), window);
+ gtk_box_pack_start (GTK_BOX (CAJA_WINDOW (window)->details->statusbar),
+ window->details->location_button,
+ FALSE, TRUE, 0);
+
+ gtk_box_reorder_child (GTK_BOX (CAJA_WINDOW (window)->details->statusbar),
+ window->details->location_button, 0);
+
+ action_group = gtk_action_group_new ("SpatialActions");
+ gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
+ window->details->spatial_action_group = action_group;
+ gtk_action_group_add_actions (action_group,
+ spatial_entries, G_N_ELEMENTS (spatial_entries),
+ window);
+
+ ui_manager = caja_window_get_ui_manager (CAJA_WINDOW (window));
+ 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-spatial-window-ui.xml");
+ gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, NULL);
+
+ for (i = 0; i < G_N_ELEMENTS (icon_entries); i++)
+ {
+ menuitem = gtk_ui_manager_get_widget (ui_manager, icon_entries[i]);
+ gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (menuitem), TRUE);
+ }
+ caja_window_set_active_pane (win, pane);
+}
+
+static void
+caja_spatial_window_class_init (CajaSpatialWindowClass *class)
+{
+ GtkBindingSet *binding_set;
+
+ CAJA_WINDOW_CLASS (class)->window_type = CAJA_WINDOW_SPATIAL;
+ CAJA_WINDOW_CLASS (class)->bookmarks_placeholder = MENU_PATH_SPATIAL_BOOKMARKS_PLACEHOLDER;
+
+ G_OBJECT_CLASS (class)->finalize = caja_spatial_window_finalize;
+ GTK_OBJECT_CLASS (class)->destroy = caja_spatial_window_destroy;
+ GTK_WIDGET_CLASS (class)->show = caja_spatial_window_show;
+ GTK_WIDGET_CLASS (class)->configure_event = caja_spatial_window_configure_event;
+ GTK_WIDGET_CLASS (class)->unrealize = caja_spatial_window_unrealize;
+ GTK_WIDGET_CLASS (class)->window_state_event = caja_spatial_window_state_event;
+
+ CAJA_WINDOW_CLASS (class)->prompt_for_location =
+ real_prompt_for_location;
+ CAJA_WINDOW_CLASS (class)->get_icon =
+ real_get_icon;
+ CAJA_WINDOW_CLASS (class)->sync_title =
+ real_sync_title;
+ CAJA_WINDOW_CLASS(class)->get_min_size = real_get_min_size;
+ CAJA_WINDOW_CLASS(class)->get_default_size = real_get_default_size;
+
+ CAJA_WINDOW_CLASS(class)->sync_allow_stop =
+ real_sync_allow_stop;
+ CAJA_WINDOW_CLASS(class)->set_allow_up =
+ real_set_allow_up;
+
+ CAJA_WINDOW_CLASS (class)->open_slot = real_open_slot;
+ CAJA_WINDOW_CLASS (class)->close = real_window_close;
+ CAJA_WINDOW_CLASS (class)->close_slot = real_close_slot;
+
+ binding_set = gtk_binding_set_by_class (class);
+ gtk_binding_entry_add_signal (binding_set, GDK_BackSpace, GDK_SHIFT_MASK,
+ "go_up", 1,
+ G_TYPE_BOOLEAN, TRUE);
+ gtk_binding_entry_add_signal (binding_set, GDK_Up, GDK_SHIFT_MASK | GDK_MOD1_MASK,
+ "go_up", 1,
+ G_TYPE_BOOLEAN, TRUE);
+
+ g_type_class_add_private (G_OBJECT_CLASS (class), sizeof(CajaSpatialWindowDetails));
+}
diff --git a/src/caja-spatial-window.h b/src/caja-spatial-window.h
new file mode 100644
index 00000000..0e3ba745
--- /dev/null
+++ b/src/caja-spatial-window.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 1999, 2000 Red Hat, Inc.
+ * Copyright (C) 1999, 2000, 2001 Eazel, Inc.
+ * Copyright (C) 2003 Ximian, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+/* caja-window.h: Interface of the main window object */
+
+#ifndef CAJA_SPATIAL_WINDOW_H
+#define CAJA_SPATIAL_WINDOW_H
+
+#include "caja-window.h"
+#include "caja-window-private.h"
+
+#define CAJA_TYPE_SPATIAL_WINDOW caja_spatial_window_get_type()
+#define CAJA_SPATIAL_WINDOW(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_SPATIAL_WINDOW, CajaSpatialWindow))
+#define CAJA_SPATIAL_WINDOW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_SPATIAL_WINDOW, CajaSpatialWindowClass))
+#define CAJA_IS_SPATIAL_WINDOW(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_SPATIAL_WINDOW))
+#define CAJA_IS_SPATIAL_WINDOW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_SPATIAL_WINDOW))
+#define CAJA_SPATIAL_WINDOW_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_SPATIAL_WINDOW, CajaSpatialWindowClass))
+
+#ifndef CAJA_SPATIAL_WINDOW_DEFINED
+#define CAJA_SPATIAL_WINDOW_DEFINED
+typedef struct _CajaSpatialWindow CajaSpatialWindow;
+#endif
+typedef struct _CajaSpatialWindowClass CajaSpatialWindowClass;
+typedef struct _CajaSpatialWindowDetails CajaSpatialWindowDetails;
+
+struct _CajaSpatialWindow
+{
+ CajaWindow parent_object;
+
+ gboolean affect_spatial_window_on_next_location_change;
+
+ CajaSpatialWindowDetails *details;
+};
+
+struct _CajaSpatialWindowClass
+{
+ CajaWindowClass parent_spot;
+};
+
+
+GType caja_spatial_window_get_type (void);
+void caja_spatial_window_set_location_button (CajaSpatialWindow *window,
+ GFile *location);
+
+#endif
diff --git a/src/caja-trash-bar.c b/src/caja-trash-bar.c
new file mode 100644
index 00000000..48ac3dfb
--- /dev/null
+++ b/src/caja-trash-bar.c
@@ -0,0 +1,236 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006 Paolo Borelli <[email protected]>
+ *
+ * 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: Paolo Borelli <[email protected]>
+ *
+ */
+
+#include "config.h"
+
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+
+#include "caja-trash-bar.h"
+
+#include "caja-window.h"
+#include <libcaja-private/caja-file-operations.h>
+#include <libcaja-private/caja-file-utilities.h>
+#include <libcaja-private/caja-file.h>
+#include <libcaja-private/caja-trash-monitor.h>
+
+#define CAJA_TRASH_BAR_GET_PRIVATE(o)\
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), CAJA_TYPE_TRASH_BAR, CajaTrashBarPrivate))
+
+enum
+{
+ PROP_WINDOW = 1,
+ NUM_PROPERTIES
+};
+
+struct CajaTrashBarPrivate
+{
+ GtkWidget *empty_button;
+ GtkWidget *restore_button;
+
+ CajaWindow *window;
+ gulong selection_handler_id;
+};
+
+G_DEFINE_TYPE (CajaTrashBar, caja_trash_bar, GTK_TYPE_HBOX);
+
+static void
+restore_button_clicked_cb (GtkWidget *button,
+ CajaTrashBar *bar)
+{
+ GList *locations, *files, *l;
+
+ locations = caja_window_info_get_selection (CAJA_WINDOW_INFO (bar->priv->window));
+ files = NULL;
+
+ for (l = locations; l != NULL; l = l->next)
+ {
+ files = g_list_prepend (files, caja_file_get (l->data));
+ }
+
+ caja_restore_files_from_trash (files, GTK_WINDOW (gtk_widget_get_toplevel (button)));
+
+ caja_file_list_free (files);
+ eel_g_object_list_free (locations);
+}
+
+static void
+selection_changed_cb (CajaWindow *window,
+ CajaTrashBar *bar)
+{
+ int count;
+
+ count = caja_window_info_get_selection_count (CAJA_WINDOW_INFO (window));
+
+ gtk_widget_set_sensitive (bar->priv->restore_button, (count > 0));
+}
+
+static void
+connect_window_and_update_button (CajaTrashBar *bar)
+{
+ bar->priv->selection_handler_id =
+ g_signal_connect (bar->priv->window, "selection_changed",
+ G_CALLBACK (selection_changed_cb), bar);
+
+ selection_changed_cb (bar->priv->window, bar);
+}
+
+static void
+caja_trash_bar_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CajaTrashBar *bar;
+
+ bar = CAJA_TRASH_BAR (object);
+
+ switch (prop_id)
+ {
+ case PROP_WINDOW:
+ bar->priv->window = g_value_get_object (value);
+ connect_window_and_update_button (bar);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+caja_trash_bar_finalize (GObject *obj)
+{
+ CajaTrashBar *bar;
+
+ bar = CAJA_TRASH_BAR (obj);
+
+ if (bar->priv->selection_handler_id)
+ {
+ g_signal_handler_disconnect (bar->priv->window, bar->priv->selection_handler_id);
+ }
+
+ G_OBJECT_CLASS (caja_trash_bar_parent_class)->finalize (obj);
+}
+
+static void
+caja_trash_bar_trash_state_changed (CajaTrashMonitor *trash_monitor,
+ gboolean state,
+ gpointer data)
+{
+ CajaTrashBar *bar;
+
+ bar = CAJA_TRASH_BAR (data);
+
+ gtk_widget_set_sensitive (bar->priv->empty_button,
+ !caja_trash_monitor_is_empty ());
+}
+
+static void
+caja_trash_bar_class_init (CajaTrashBarClass *klass)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = caja_trash_bar_set_property;
+ object_class->finalize = caja_trash_bar_finalize;
+
+ g_object_class_install_property (object_class,
+ PROP_WINDOW,
+ g_param_spec_object ("window",
+ "window",
+ "the CajaWindow",
+ CAJA_TYPE_WINDOW,
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ g_type_class_add_private (klass, sizeof (CajaTrashBarPrivate));
+}
+
+static void
+empty_trash_callback (GtkWidget *button, gpointer data)
+{
+ GtkWidget *window;
+
+ window = gtk_widget_get_toplevel (button);
+
+ caja_file_operations_empty_trash (window);
+}
+
+static void
+caja_trash_bar_init (CajaTrashBar *bar)
+{
+ GtkWidget *label;
+ GtkWidget *hbox;
+
+ bar->priv = CAJA_TRASH_BAR_GET_PRIVATE (bar);
+
+ hbox = GTK_WIDGET (bar);
+
+ label = gtk_label_new (_("Trash"));
+ gtk_widget_show (label);
+ gtk_box_pack_start (GTK_BOX (bar), label, FALSE, FALSE, 0);
+
+ bar->priv->empty_button = gtk_button_new_with_mnemonic (_("Empty _Trash"));
+ gtk_widget_show (bar->priv->empty_button);
+ gtk_box_pack_end (GTK_BOX (hbox), bar->priv->empty_button, FALSE, FALSE, 0);
+
+ gtk_widget_set_sensitive (bar->priv->empty_button,
+ !caja_trash_monitor_is_empty ());
+ gtk_widget_set_tooltip_text (bar->priv->empty_button,
+ _("Delete all items in the Trash"));
+
+ g_signal_connect (bar->priv->empty_button,
+ "clicked",
+ G_CALLBACK (empty_trash_callback),
+ bar);
+
+ bar->priv->restore_button = gtk_button_new_with_mnemonic (_("Restore Selected Items"));
+ gtk_widget_show (bar->priv->restore_button);
+ gtk_box_pack_end (GTK_BOX (hbox), bar->priv->restore_button, FALSE, FALSE, 6);
+
+ gtk_widget_set_sensitive (bar->priv->restore_button, FALSE);
+ gtk_widget_set_tooltip_text (bar->priv->restore_button,
+ _("Restore selected items to their original position"));
+
+ g_signal_connect (bar->priv->restore_button,
+ "clicked",
+ G_CALLBACK (restore_button_clicked_cb),
+ bar);
+
+ g_signal_connect_object (caja_trash_monitor_get (),
+ "trash_state_changed",
+ G_CALLBACK (caja_trash_bar_trash_state_changed),
+ bar,
+ 0);
+}
+
+GtkWidget *
+caja_trash_bar_new (CajaWindow *window)
+{
+ GObject *bar;
+
+ bar = g_object_new (CAJA_TYPE_TRASH_BAR, "window", window, NULL);
+
+ return GTK_WIDGET (bar);
+}
diff --git a/src/caja-trash-bar.h b/src/caja-trash-bar.h
new file mode 100644
index 00000000..93b86986
--- /dev/null
+++ b/src/caja-trash-bar.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006 Paolo Borelli <[email protected]>
+ *
+ * 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: Paolo Borelli <[email protected]>
+ *
+ */
+
+#ifndef __CAJA_TRASH_BAR_H
+#define __CAJA_TRASH_BAR_H
+
+#include "caja-window.h"
+
+#include <gtk/gtk.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define CAJA_TYPE_TRASH_BAR (caja_trash_bar_get_type ())
+#define CAJA_TRASH_BAR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), CAJA_TYPE_TRASH_BAR, CajaTrashBar))
+#define CAJA_TRASH_BAR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), CAJA_TYPE_TRASH_BAR, CajaTrashBarClass))
+#define CAJA_IS_TRASH_BAR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), CAJA_TYPE_TRASH_BAR))
+#define CAJA_IS_TRASH_BAR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), CAJA_TYPE_TRASH_BAR))
+#define CAJA_TRASH_BAR_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), CAJA_TYPE_TRASH_BAR, CajaTrashBarClass))
+
+ typedef struct CajaTrashBarPrivate CajaTrashBarPrivate;
+
+ typedef struct
+ {
+ GtkHBox box;
+
+ CajaTrashBarPrivate *priv;
+ } CajaTrashBar;
+
+ typedef struct
+ {
+ GtkHBoxClass parent_class;
+ } CajaTrashBarClass;
+
+ GType caja_trash_bar_get_type (void) G_GNUC_CONST;
+
+ GtkWidget *caja_trash_bar_new (CajaWindow *window);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __GS_TRASH_BAR_H */
diff --git a/src/caja-view-as-action.c b/src/caja-view-as-action.c
new file mode 100644
index 00000000..c7077e8a
--- /dev/null
+++ b/src/caja-view-as-action.c
@@ -0,0 +1,288 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2009 Red Hat, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Authors: Alexander Larsson <[email protected]>
+ *
+ */
+
+#include <config.h>
+
+#include "caja-view-as-action.h"
+#include "caja-navigation-window.h"
+#include "caja-window-private.h"
+#include "caja-navigation-window-slot.h"
+#include <gtk/gtk.h>
+#include <eel/eel-gtk-extensions.h>
+#include <libcaja-private/caja-view-factory.h>
+
+G_DEFINE_TYPE (CajaViewAsAction, caja_view_as_action, GTK_TYPE_ACTION)
+
+static void caja_view_as_action_init (CajaViewAsAction *action);
+static void caja_view_as_action_class_init (CajaViewAsActionClass *class);
+
+static GObjectClass *parent_class = NULL;
+
+#define CAJA_VIEW_AS_ACTION_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), CAJA_TYPE_VIEW_AS_ACTION, CajaViewAsActionPrivate))
+
+struct CajaViewAsActionPrivate
+{
+ CajaNavigationWindow *window;
+};
+
+enum
+{
+ PROP_0,
+ PROP_WINDOW
+};
+
+
+static void
+activate_nth_short_list_item (CajaWindow *window, guint index)
+{
+ CajaWindowSlot *slot;
+
+ g_assert (CAJA_IS_WINDOW (window));
+
+ slot = caja_window_get_active_slot (window);
+ g_assert (index < g_list_length (window->details->short_list_viewers));
+
+ caja_window_slot_set_content_view (slot,
+ g_list_nth_data (window->details->short_list_viewers, index));
+}
+
+static void
+activate_extra_viewer (CajaWindow *window)
+{
+ CajaWindowSlot *slot;
+
+ g_assert (CAJA_IS_WINDOW (window));
+
+ slot = caja_window_get_active_slot (window);
+ g_assert (window->details->extra_viewer != NULL);
+
+ caja_window_slot_set_content_view (slot, window->details->extra_viewer);
+}
+
+static void
+view_as_menu_switch_views_callback (GtkComboBox *combo_box, CajaNavigationWindow *window)
+{
+ int active;
+
+ g_assert (GTK_IS_COMBO_BOX (combo_box));
+ g_assert (CAJA_IS_NAVIGATION_WINDOW (window));
+
+ active = gtk_combo_box_get_active (combo_box);
+
+ if (active < 0)
+ {
+ return;
+ }
+ else if (active < GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo_box), "num viewers")))
+ {
+ activate_nth_short_list_item (CAJA_WINDOW (window), active);
+ }
+ else
+ {
+ activate_extra_viewer (CAJA_WINDOW (window));
+ }
+}
+
+static void
+view_as_changed_callback (CajaWindow *window,
+ GtkComboBox *combo_box)
+{
+ CajaWindowSlot *slot;
+ GList *node;
+ int index;
+ int selected_index = -1;
+ GtkTreeModel *model;
+ GtkListStore *store;
+ const CajaViewInfo *info;
+
+ /* 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.
+ */
+ model = gtk_combo_box_get_model (combo_box);
+ g_return_if_fail (GTK_IS_LIST_STORE (model));
+ store = GTK_LIST_STORE (model);
+ gtk_list_store_clear (store);
+
+ slot = caja_window_get_active_slot (window);
+
+ /* Add a menu item for each view in the preferred list for this location. */
+ for (node = window->details->short_list_viewers, index = 0;
+ node != NULL;
+ node = node->next, ++index)
+ {
+ info = caja_view_factory_lookup (node->data);
+ gtk_combo_box_append_text (combo_box, _(info->view_combo_label));
+
+ if (caja_window_slot_content_view_matches_iid (slot, (char *)node->data))
+ {
+ selected_index = index;
+ }
+ }
+ g_object_set_data (G_OBJECT (combo_box), "num viewers", GINT_TO_POINTER (index));
+ if (selected_index == -1)
+ {
+ const char *id;
+ /* We're using an extra viewer, add a menu item for it */
+
+ id = caja_window_slot_get_content_view_id (slot);
+ info = caja_view_factory_lookup (id);
+ gtk_combo_box_append_text (combo_box,
+ _(info->view_combo_label));
+ selected_index = index;
+ }
+
+ gtk_combo_box_set_active (combo_box, selected_index);
+}
+
+
+static void
+connect_proxy (GtkAction *action,
+ GtkWidget *proxy)
+{
+ if (GTK_IS_TOOL_ITEM (proxy))
+ {
+ GtkToolItem *item = GTK_TOOL_ITEM (proxy);
+ CajaViewAsAction *vaction = CAJA_VIEW_AS_ACTION (action);
+ CajaNavigationWindow *window = vaction->priv->window;
+ GtkWidget *view_as_menu_vbox;
+ GtkWidget *view_as_combo_box;
+
+ /* Option menu for content view types; it's empty here, filled in when a uri is set.
+ * Pack it into vbox so it doesn't grow vertically when location bar does.
+ */
+ view_as_menu_vbox = gtk_vbox_new (FALSE, 4);
+ gtk_widget_show (view_as_menu_vbox);
+
+ gtk_container_set_border_width (GTK_CONTAINER (item), 4);
+ gtk_container_add (GTK_CONTAINER (item), view_as_menu_vbox);
+
+ view_as_combo_box = gtk_combo_box_new_text ();
+
+ gtk_combo_box_set_focus_on_click (GTK_COMBO_BOX (view_as_combo_box), FALSE);
+ gtk_box_pack_end (GTK_BOX (view_as_menu_vbox), view_as_combo_box, TRUE, FALSE, 0);
+ gtk_widget_show (view_as_combo_box);
+ g_signal_connect_object (view_as_combo_box, "changed",
+ G_CALLBACK (view_as_menu_switch_views_callback), window, 0);
+
+ g_signal_connect (window, "view-as-changed",
+ G_CALLBACK (view_as_changed_callback),
+ view_as_combo_box);
+ }
+
+ (* GTK_ACTION_CLASS (parent_class)->connect_proxy) (action, proxy);
+}
+
+static void
+disconnect_proxy (GtkAction *action,
+ GtkWidget *proxy)
+{
+ if (GTK_IS_TOOL_ITEM (proxy))
+ {
+ CajaViewAsAction *vaction = CAJA_VIEW_AS_ACTION (action);
+ CajaNavigationWindow *window = vaction->priv->window;
+
+ g_signal_handlers_disconnect_matched (window,
+ G_SIGNAL_MATCH_FUNC,
+ 0, 0, NULL, G_CALLBACK (view_as_changed_callback), NULL);
+ }
+
+ (* GTK_ACTION_CLASS (parent_class)->disconnect_proxy) (action, proxy);
+}
+
+static void
+caja_view_as_action_finalize (GObject *object)
+{
+ (* G_OBJECT_CLASS (parent_class)->finalize) (object);
+}
+
+static void
+caja_view_as_action_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CajaViewAsAction *zoom;
+
+ zoom = CAJA_VIEW_AS_ACTION (object);
+
+ switch (prop_id)
+ {
+ case PROP_WINDOW:
+ zoom->priv->window = CAJA_NAVIGATION_WINDOW (g_value_get_object (value));
+ break;
+ }
+}
+
+static void
+caja_view_as_action_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ CajaViewAsAction *zoom;
+
+ zoom = CAJA_VIEW_AS_ACTION (object);
+
+ switch (prop_id)
+ {
+ case PROP_WINDOW:
+ g_value_set_object (value, zoom->priv->window);
+ break;
+ }
+}
+
+static void
+caja_view_as_action_class_init (CajaViewAsActionClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+ GtkActionClass *action_class = GTK_ACTION_CLASS (class);
+
+ object_class->finalize = caja_view_as_action_finalize;
+ object_class->set_property = caja_view_as_action_set_property;
+ object_class->get_property = caja_view_as_action_get_property;
+
+ parent_class = g_type_class_peek_parent (class);
+
+ action_class->toolbar_item_type = GTK_TYPE_TOOL_ITEM;
+ action_class->connect_proxy = connect_proxy;
+ action_class->disconnect_proxy = disconnect_proxy;
+
+ g_object_class_install_property (object_class,
+ PROP_WINDOW,
+ g_param_spec_object ("window",
+ "Window",
+ "The navigation window",
+ G_TYPE_OBJECT,
+ G_PARAM_READWRITE));
+
+ g_type_class_add_private (object_class, sizeof(CajaViewAsActionPrivate));
+}
+
+static void
+caja_view_as_action_init (CajaViewAsAction *action)
+{
+ action->priv = CAJA_VIEW_AS_ACTION_GET_PRIVATE (action);
+}
diff --git a/src/caja-view-as-action.h b/src/caja-view-as-action.h
new file mode 100644
index 00000000..815f7abd
--- /dev/null
+++ b/src/caja-view-as-action.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2009 Red Hat, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Authors: Alexander Larsson <[email protected]>
+ *
+ */
+
+#ifndef CAJA_VIEW_AS_ACTION_H
+#define CAJA_VIEW_AS_ACTION_H
+
+#include <gtk/gtk.h>
+
+#define CAJA_TYPE_VIEW_AS_ACTION (caja_view_as_action_get_type ())
+#define CAJA_VIEW_AS_ACTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_VIEW_AS_ACTION, CajaViewAsAction))
+#define CAJA_VIEW_AS_ACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_VIEW_AS_ACTION, CajaViewAsActionClass))
+#define CAJA_IS_VIEW_AS_ACTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_VIEW_AS_ACTION))
+#define CAJA_IS_VIEW_AS_ACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), CAJA_TYPE_VIEW_AS_ACTION))
+#define CAJA_VIEW_AS_ACTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), CAJA_TYPE_VIEW_AS_ACTION, CajaViewAsActionClass))
+
+typedef struct _CajaViewAsAction CajaViewAsAction;
+typedef struct _CajaViewAsActionClass CajaViewAsActionClass;
+typedef struct CajaViewAsActionPrivate CajaViewAsActionPrivate;
+
+struct _CajaViewAsAction
+{
+ GtkAction parent;
+
+ /*< private >*/
+ CajaViewAsActionPrivate *priv;
+};
+
+struct _CajaViewAsActionClass
+{
+ GtkActionClass parent_class;
+};
+
+GType caja_view_as_action_get_type (void);
+
+#endif
diff --git a/src/caja-window-bookmarks.c b/src/caja-window-bookmarks.c
new file mode 100644
index 00000000..d16f04e9
--- /dev/null
+++ b/src/caja-window-bookmarks.c
@@ -0,0 +1,295 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2000, 2001 Eazel, Inc.
+ * Copyright (C) 2005 Red Hat, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Author: John Sullivan <[email protected]>
+ * Alexander Larsson <[email protected]>
+ */
+
+#include <config.h>
+
+#include <locale.h>
+
+#include "caja-actions.h"
+#include "caja-bookmark-list.h"
+#include "caja-bookmarks-window.h"
+#include "caja-window-bookmarks.h"
+#include "caja-window-private.h"
+#include <libcaja-private/caja-undo-manager.h>
+#include <eel/eel-debug.h>
+#include <eel/eel-stock-dialogs.h>
+#include <eel/eel-vfs-extensions.h>
+#include <eel/eel-gtk-extensions.h>
+#include <glib/gi18n.h>
+
+#define MENU_ITEM_MAX_WIDTH_CHARS 32
+
+static GtkWindow *bookmarks_window = NULL;
+
+static void refresh_bookmarks_menu (CajaWindow *window);
+
+static void
+remove_bookmarks_for_uri_if_yes (GtkDialog *dialog, int response, gpointer callback_data)
+{
+ const char *uri;
+ CajaWindow *window;
+
+ g_assert (GTK_IS_DIALOG (dialog));
+ g_assert (callback_data != NULL);
+
+ window = callback_data;
+
+ if (response == GTK_RESPONSE_YES)
+ {
+ uri = g_object_get_data (G_OBJECT (dialog), "uri");
+ caja_bookmark_list_delete_items_with_uri (window->details->bookmark_list, uri);
+ }
+
+ gtk_object_destroy (GTK_OBJECT (dialog));
+}
+
+static void
+show_bogus_bookmark_window (CajaWindow *window,
+ CajaBookmark *bookmark)
+{
+ GtkDialog *dialog;
+ GFile *location;
+ char *uri_for_display;
+ char *prompt;
+ char *detail;
+
+ location = caja_bookmark_get_location (bookmark);
+ uri_for_display = g_file_get_parse_name (location);
+
+ prompt = _("Do you want to remove any bookmarks with the "
+ "non-existing location from your list?");
+ detail = g_strdup_printf (_("The location \"%s\" does not exist."), uri_for_display);
+
+ dialog = eel_show_yes_no_dialog (prompt, detail,
+ _("Bookmark for Nonexistent Location"),
+ GTK_STOCK_CANCEL,
+ GTK_WINDOW (window));
+
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (remove_bookmarks_for_uri_if_yes), window);
+ g_object_set_data_full (G_OBJECT (dialog), "uri", g_file_get_uri (location), g_free);
+
+ gtk_dialog_set_default_response (dialog, GTK_RESPONSE_NO);
+
+ g_object_unref (location);
+ g_free (uri_for_display);
+ g_free (detail);
+}
+
+static GtkWindow *
+get_or_create_bookmarks_window (CajaWindow *window)
+{
+ GObject *undo_manager_source;
+
+ undo_manager_source = G_OBJECT (window);
+
+ if (bookmarks_window == NULL)
+ {
+ bookmarks_window = create_bookmarks_window (window->details->bookmark_list,
+ undo_manager_source);
+ }
+ else
+ {
+ edit_bookmarks_dialog_set_signals (undo_manager_source);
+ }
+
+ return bookmarks_window;
+}
+
+/**
+ * caja_bookmarks_exiting:
+ *
+ * Last chance to save state before app exits.
+ * Called when application exits; don't call from anywhere else.
+ **/
+void
+caja_bookmarks_exiting (void)
+{
+ if (bookmarks_window != NULL)
+ {
+ caja_bookmarks_window_save_geometry (bookmarks_window);
+ gtk_widget_destroy (GTK_WIDGET (bookmarks_window));
+ }
+}
+
+/**
+ * add_bookmark_for_current_location
+ *
+ * Add a bookmark for the displayed location to the bookmarks menu.
+ * Does nothing if there's already a bookmark for the displayed location.
+ */
+void
+caja_window_add_bookmark_for_current_location (CajaWindow *window)
+{
+ CajaBookmark *bookmark;
+ CajaWindowSlot *slot;
+ CajaBookmarkList *list;
+
+ g_assert (CAJA_IS_WINDOW (window));
+
+ slot = window->details->active_pane->active_slot;
+ bookmark = slot->current_location_bookmark;
+ list = window->details->bookmark_list;
+
+ if (!caja_bookmark_list_contains (list, bookmark))
+ {
+ caja_bookmark_list_append (list, bookmark);
+ }
+}
+
+void
+caja_window_edit_bookmarks (CajaWindow *window)
+{
+ GtkWindow *dialog;
+
+ dialog = get_or_create_bookmarks_window (window);
+
+ gtk_window_set_screen (
+ dialog, gtk_window_get_screen (GTK_WINDOW (window)));
+ gtk_window_present (dialog);
+}
+
+static void
+remove_bookmarks_menu_items (CajaWindow *window)
+{
+ GtkUIManager *ui_manager;
+
+ ui_manager = caja_window_get_ui_manager (window);
+ if (window->details->bookmarks_merge_id != 0)
+ {
+ gtk_ui_manager_remove_ui (ui_manager,
+ window->details->bookmarks_merge_id);
+ window->details->bookmarks_merge_id = 0;
+ }
+ if (window->details->bookmarks_action_group != NULL)
+ {
+ gtk_ui_manager_remove_action_group (ui_manager,
+ window->details->bookmarks_action_group);
+ window->details->bookmarks_action_group = NULL;
+ }
+}
+
+static void
+connect_proxy_cb (GtkActionGroup *action_group,
+ GtkAction *action,
+ GtkWidget *proxy,
+ gpointer dummy)
+{
+ GtkLabel *label;
+
+ if (!GTK_IS_MENU_ITEM (proxy))
+ return;
+
+ label = GTK_LABEL (gtk_bin_get_child (GTK_BIN (proxy)));
+
+ gtk_label_set_use_underline (label, FALSE);
+ gtk_label_set_ellipsize (label, PANGO_ELLIPSIZE_END);
+ gtk_label_set_max_width_chars (label, MENU_ITEM_MAX_WIDTH_CHARS);
+}
+
+static void
+update_bookmarks (CajaWindow *window)
+{
+ CajaBookmarkList *bookmarks;
+ CajaBookmark *bookmark;
+ guint bookmark_count;
+ guint index;
+ GtkUIManager *ui_manager;
+
+ g_assert (CAJA_IS_WINDOW (window));
+ g_assert (window->details->bookmarks_merge_id == 0);
+ g_assert (window->details->bookmarks_action_group == NULL);
+
+ if (window->details->bookmark_list == NULL)
+ {
+ window->details->bookmark_list = caja_bookmark_list_new ();
+ }
+
+ bookmarks = window->details->bookmark_list;
+
+ ui_manager = caja_window_get_ui_manager (CAJA_WINDOW (window));
+
+ window->details->bookmarks_merge_id = gtk_ui_manager_new_merge_id (ui_manager);
+ window->details->bookmarks_action_group = gtk_action_group_new ("BookmarksGroup");
+ g_signal_connect (window->details->bookmarks_action_group, "connect-proxy",
+ G_CALLBACK (connect_proxy_cb), NULL);
+
+ gtk_ui_manager_insert_action_group (ui_manager,
+ window->details->bookmarks_action_group,
+ -1);
+ g_object_unref (window->details->bookmarks_action_group);
+
+ /* append new set of bookmarks */
+ bookmark_count = caja_bookmark_list_length (bookmarks);
+ for (index = 0; index < bookmark_count; ++index)
+ {
+ bookmark = caja_bookmark_list_item_at (bookmarks, index);
+
+ if (caja_bookmark_uri_known_not_to_exist (bookmark))
+ {
+ continue;
+ }
+
+ caja_menus_append_bookmark_to_menu
+ (CAJA_WINDOW (window),
+ bookmark,
+ CAJA_WINDOW_GET_CLASS (window)->bookmarks_placeholder,
+ "dynamic",
+ index,
+ window->details->bookmarks_action_group,
+ window->details->bookmarks_merge_id,
+ G_CALLBACK (refresh_bookmarks_menu),
+ show_bogus_bookmark_window);
+ }
+}
+
+static void
+refresh_bookmarks_menu (CajaWindow *window)
+{
+ g_assert (CAJA_IS_WINDOW (window));
+
+ remove_bookmarks_menu_items (window);
+ update_bookmarks (window);
+}
+
+/**
+ * caja_window_initialize_bookmarks_menu
+ *
+ * Fill in bookmarks menu with stored bookmarks, and wire up signals
+ * so we'll be notified when bookmark list changes.
+ */
+void
+caja_window_initialize_bookmarks_menu (CajaWindow *window)
+{
+ g_assert (CAJA_IS_WINDOW (window));
+
+ refresh_bookmarks_menu (window);
+
+ /* Recreate dynamic part of menu if bookmark list changes */
+ g_signal_connect_object (window->details->bookmark_list, "contents_changed",
+ G_CALLBACK (refresh_bookmarks_menu),
+ window, G_CONNECT_SWAPPED);
+}
diff --git a/src/caja-window-bookmarks.h b/src/caja-window-bookmarks.h
new file mode 100644
index 00000000..48969a33
--- /dev/null
+++ b/src/caja-window-bookmarks.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2005 Red Hat, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Author: Alexander Larsson <[email protected]>
+ */
+
+#ifndef CAJA_WINDOW_BOOKMARKS_H
+#define CAJA_WINDOW_BOOKMARKS_H
+
+#include <libcaja-private/caja-bookmark.h>
+#include <caja-window.h>
+#include "caja-bookmark-list.h"
+
+void caja_bookmarks_exiting (void);
+void caja_window_add_bookmark_for_current_location (CajaWindow *window);
+void caja_window_edit_bookmarks (CajaWindow *window);
+void caja_window_initialize_bookmarks_menu (CajaWindow *window);
+
+#endif
diff --git a/src/caja-window-manage-views.c b/src/caja-window-manage-views.c
new file mode 100644
index 00000000..df36f821
--- /dev/null
+++ b/src/caja-window-manage-views.c
@@ -0,0 +1,2337 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 1999, 2000 Red Hat, Inc.
+ * Copyright (C) 1999, 2000, 2001 Eazel, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Authors: Elliot Lee <[email protected]>
+ * John Sullivan <[email protected]>
+ * Darin Adler <[email protected]>
+ */
+
+#include <config.h>
+#include "caja-window-manage-views.h"
+
+#include "caja-actions.h"
+#include "caja-application.h"
+#include "caja-location-bar.h"
+#include "caja-search-bar.h"
+#include "caja-pathbar.h"
+#include "caja-main.h"
+#include "caja-window-private.h"
+#include "caja-window-slot.h"
+#include "caja-navigation-window-slot.h"
+#include "caja-trash-bar.h"
+#include "caja-x-content-bar.h"
+#include "caja-zoom-control.h"
+#include "caja-navigation-window-pane.h"
+#include <eel/eel-accessibility.h>
+#include <eel/eel-debug.h>
+#include <eel/eel-gdk-extensions.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 <gtk/gtk.h>
+#include <gdk/gdkx.h>
+#include <glib/gi18n.h>
+#include <libcaja-extension/caja-location-widget-provider.h>
+#include <libcaja-private/caja-debug-log.h>
+#include <libcaja-private/caja-file-attributes.h>
+#include <libcaja-private/caja-file-utilities.h>
+#include <libcaja-private/caja-file.h>
+#include <libcaja-private/caja-global-preferences.h>
+#include <libcaja-private/caja-metadata.h>
+#include <libcaja-private/caja-mime-actions.h>
+#include <libcaja-private/caja-module.h>
+#include <libcaja-private/caja-monitor.h>
+#include <libcaja-private/caja-search-directory.h>
+#include <libcaja-private/caja-view-factory.h>
+#include <libcaja-private/caja-window-info.h>
+#include <libcaja-private/caja-window-slot-info.h>
+#include <libcaja-private/caja-autorun.h>
+
+/* FIXME bugzilla.gnome.org 41243:
+ * We should use inheritance instead of these special cases
+ * for the desktop window.
+ */
+#include "caja-desktop-window.h"
+
+/* This number controls a maximum character count for a URL that is
+ * displayed as part of a dialog. It's fairly arbitrary -- big enough
+ * to allow most "normal" URIs to display in full, but small enough to
+ * prevent the dialog from getting insanely wide.
+ */
+#define MAX_URI_IN_DIALOG_LENGTH 60
+
+static void begin_location_change (CajaWindowSlot *slot,
+ GFile *location,
+ GList *new_selection,
+ CajaLocationChangeType type,
+ guint distance,
+ const char *scroll_pos);
+static void free_location_change (CajaWindowSlot *slot);
+static void end_location_change (CajaWindowSlot *slot);
+static void cancel_location_change (CajaWindowSlot *slot);
+static void got_file_info_for_view_selection_callback (CajaFile *file,
+ gpointer callback_data);
+static void create_content_view (CajaWindowSlot *slot,
+ const char *view_id);
+static void display_view_selection_failure (CajaWindow *window,
+ CajaFile *file,
+ GFile *location,
+ GError *error);
+static void load_new_location (CajaWindowSlot *slot,
+ GFile *location,
+ GList *selection,
+ gboolean tell_current_content_view,
+ gboolean tell_new_content_view);
+static void location_has_really_changed (CajaWindowSlot *slot);
+static void update_for_new_location (CajaWindowSlot *slot);
+
+void
+caja_window_report_selection_changed (CajaWindowInfo *window)
+{
+ if (window->details->temporarily_ignore_view_signals)
+ {
+ return;
+ }
+
+ g_signal_emit_by_name (window, "selection_changed");
+}
+
+/* set_displayed_location:
+ */
+static void
+set_displayed_location (CajaWindowSlot *slot, GFile *location)
+{
+ CajaWindow *window;
+ GFile *bookmark_location;
+ gboolean recreate;
+ char *name;
+
+ window = slot->pane->window;
+
+ if (slot->current_location_bookmark == NULL || location == NULL)
+ {
+ recreate = TRUE;
+ }
+ else
+ {
+ bookmark_location = caja_bookmark_get_location (slot->current_location_bookmark);
+ recreate = !g_file_equal (bookmark_location, location);
+ g_object_unref (bookmark_location);
+ }
+
+ if (recreate)
+ {
+ /* We've changed locations, must recreate bookmark for current location. */
+ if (slot->last_location_bookmark != NULL)
+ {
+ g_object_unref (slot->last_location_bookmark);
+ }
+ slot->last_location_bookmark = slot->current_location_bookmark;
+ name = g_file_get_basename (location);
+ slot->current_location_bookmark = (location == NULL) ? NULL
+ : caja_bookmark_new (location, name, FALSE, NULL);
+ g_free (name);
+ }
+}
+
+static void
+check_bookmark_location_matches (CajaBookmark *bookmark, GFile *location)
+{
+ GFile *bookmark_location;
+ char *bookmark_uri, *uri;
+
+ bookmark_location = caja_bookmark_get_location (bookmark);
+ if (!g_file_equal (location, bookmark_location))
+ {
+ bookmark_uri = g_file_get_uri (bookmark_location);
+ uri = g_file_get_uri (location);
+ g_warning ("bookmark uri is %s, but expected %s", bookmark_uri, uri);
+ g_free (uri);
+ g_free (bookmark_uri);
+ }
+ g_object_unref (bookmark_location);
+}
+
+/* Debugging function used to verify that the last_location_bookmark
+ * is in the state we expect when we're about to use it to update the
+ * Back or Forward list.
+ */
+static void
+check_last_bookmark_location_matches_slot (CajaWindowSlot *slot)
+{
+ check_bookmark_location_matches (slot->last_location_bookmark,
+ slot->location);
+}
+
+static void
+handle_go_back (CajaNavigationWindowSlot *navigation_slot,
+ GFile *location)
+{
+ CajaWindowSlot *slot;
+ guint i;
+ GList *link;
+ CajaBookmark *bookmark;
+
+ slot = CAJA_WINDOW_SLOT (navigation_slot);
+
+ /* Going back. Move items from the back list to the forward list. */
+ g_assert (g_list_length (navigation_slot->back_list) > slot->location_change_distance);
+ check_bookmark_location_matches (CAJA_BOOKMARK (g_list_nth_data (navigation_slot->back_list,
+ slot->location_change_distance)),
+ location);
+ g_assert (slot->location != NULL);
+
+ /* Move current location to Forward list */
+
+ check_last_bookmark_location_matches_slot (slot);
+
+ /* Use the first bookmark in the history list rather than creating a new one. */
+ navigation_slot->forward_list = g_list_prepend (navigation_slot->forward_list,
+ slot->last_location_bookmark);
+ g_object_ref (navigation_slot->forward_list->data);
+
+ /* Move extra links from Back to Forward list */
+ for (i = 0; i < slot->location_change_distance; ++i)
+ {
+ bookmark = CAJA_BOOKMARK (navigation_slot->back_list->data);
+ navigation_slot->back_list =
+ g_list_remove (navigation_slot->back_list, bookmark);
+ navigation_slot->forward_list =
+ g_list_prepend (navigation_slot->forward_list, bookmark);
+ }
+
+ /* One bookmark falls out of back/forward lists and becomes viewed location */
+ link = navigation_slot->back_list;
+ navigation_slot->back_list = g_list_remove_link (navigation_slot->back_list, link);
+ g_object_unref (link->data);
+ g_list_free_1 (link);
+}
+
+static void
+handle_go_forward (CajaNavigationWindowSlot *navigation_slot,
+ GFile *location)
+{
+ CajaWindowSlot *slot;
+ guint i;
+ GList *link;
+ CajaBookmark *bookmark;
+
+ slot = CAJA_WINDOW_SLOT (navigation_slot);
+
+ /* Going forward. Move items from the forward list to the back list. */
+ g_assert (g_list_length (navigation_slot->forward_list) > slot->location_change_distance);
+ check_bookmark_location_matches (CAJA_BOOKMARK (g_list_nth_data (navigation_slot->forward_list,
+ slot->location_change_distance)),
+ location);
+ g_assert (slot->location != NULL);
+
+ /* Move current location to Back list */
+ check_last_bookmark_location_matches_slot (slot);
+
+ /* Use the first bookmark in the history list rather than creating a new one. */
+ navigation_slot->back_list = g_list_prepend (navigation_slot->back_list,
+ slot->last_location_bookmark);
+ g_object_ref (navigation_slot->back_list->data);
+
+ /* Move extra links from Forward to Back list */
+ for (i = 0; i < slot->location_change_distance; ++i)
+ {
+ bookmark = CAJA_BOOKMARK (navigation_slot->forward_list->data);
+ navigation_slot->forward_list =
+ g_list_remove (navigation_slot->back_list, bookmark);
+ navigation_slot->back_list =
+ g_list_prepend (navigation_slot->forward_list, bookmark);
+ }
+
+ /* One bookmark falls out of back/forward lists and becomes viewed location */
+ link = navigation_slot->forward_list;
+ navigation_slot->forward_list = g_list_remove_link (navigation_slot->forward_list, link);
+ g_object_unref (link->data);
+ g_list_free_1 (link);
+}
+
+static void
+handle_go_elsewhere (CajaWindowSlot *slot, GFile *location)
+{
+#if !NEW_UI_COMPLETE
+ CajaNavigationWindowSlot *navigation_slot;
+
+ if (CAJA_IS_NAVIGATION_WINDOW_SLOT (slot))
+ {
+ navigation_slot = CAJA_NAVIGATION_WINDOW_SLOT (slot);
+
+ /* Clobber the entire forward list, and move displayed location to back list */
+ caja_navigation_window_slot_clear_forward_list (navigation_slot);
+
+ if (slot->location != NULL)
+ {
+ /* If we're returning to the same uri somehow, don't put this uri on back list.
+ * This also avoids a problem where set_displayed_location
+ * didn't update last_location_bookmark since the uri didn't change.
+ */
+ if (!g_file_equal (slot->location, location))
+ {
+ /* Store bookmark for current location in back list, unless there is no current location */
+ check_last_bookmark_location_matches_slot (slot);
+ /* Use the first bookmark in the history list rather than creating a new one. */
+ navigation_slot->back_list = g_list_prepend (navigation_slot->back_list,
+ slot->last_location_bookmark);
+ g_object_ref (navigation_slot->back_list->data);
+ }
+ }
+ }
+#endif
+}
+
+void
+caja_window_update_up_button (CajaWindow *window)
+{
+ CajaWindowSlot *slot;
+ gboolean allowed;
+ GFile *parent;
+
+ slot = window->details->active_pane->active_slot;
+
+ allowed = FALSE;
+ if (slot->location != NULL)
+ {
+ parent = g_file_get_parent (slot->location);
+ allowed = parent != NULL;
+ if (parent != NULL)
+ {
+ g_object_unref (parent);
+ }
+ }
+
+ caja_window_allow_up (window, allowed);
+}
+
+static void
+viewed_file_changed_callback (CajaFile *file,
+ CajaWindowSlot *slot)
+{
+ CajaWindow *window;
+ GFile *new_location;
+ gboolean is_in_trash, was_in_trash;
+
+ window = slot->pane->window;
+
+ g_assert (CAJA_IS_FILE (file));
+ g_assert (CAJA_IS_WINDOW_PANE (slot->pane));
+ g_assert (CAJA_IS_WINDOW (window));
+
+ g_assert (file == slot->viewed_file);
+
+ if (!caja_file_is_not_yet_confirmed (file))
+ {
+ slot->viewed_file_seen = TRUE;
+ }
+
+ was_in_trash = slot->viewed_file_in_trash;
+
+ slot->viewed_file_in_trash = is_in_trash = caja_file_is_in_trash (file);
+
+ /* Close window if the file it's viewing has been deleted or moved to trash. */
+ if (caja_file_is_gone (file) || (is_in_trash && !was_in_trash))
+ {
+ /* Don't close the window in the case where the
+ * file was never seen in the first place.
+ */
+ if (slot->viewed_file_seen)
+ {
+ /* Detecting a file is gone may happen in the
+ * middle of a pending location change, we
+ * need to cancel it before closing the window
+ * or things break.
+ */
+ /* FIXME: It makes no sense that this call is
+ * needed. When the window is destroyed, it
+ * calls caja_window_manage_views_destroy,
+ * which calls free_location_change, which
+ * should be sufficient. Also, if this was
+ * really needed, wouldn't it be needed for
+ * all other caja_window_close callers?
+ */
+ end_location_change (slot);
+
+ if (CAJA_IS_NAVIGATION_WINDOW (window))
+ {
+ /* auto-show existing parent. */
+ GFile *go_to_file, *parent, *location;
+
+ go_to_file = NULL;
+ location = caja_file_get_location (file);
+ parent = g_file_get_parent (location);
+ g_object_unref (location);
+ if (parent)
+ {
+ go_to_file = caja_find_existing_uri_in_hierarchy (parent);
+ g_object_unref (parent);
+ }
+
+ if (go_to_file != NULL)
+ {
+ /* the path bar URI will be set to go_to_uri immediately
+ * in begin_location_change, but we don't want the
+ * inexistant children to show up anymore */
+ if (slot == slot->pane->active_slot)
+ {
+ /* multiview-TODO also update CajaWindowSlot
+ * [which as of writing doesn't save/store any path bar state]
+ */
+ caja_path_bar_clear_buttons (CAJA_PATH_BAR (CAJA_NAVIGATION_WINDOW_PANE (slot->pane)->path_bar));
+ }
+
+ caja_window_slot_go_to (slot, go_to_file, FALSE);
+ g_object_unref (go_to_file);
+ }
+ else
+ {
+ caja_window_slot_go_home (slot, FALSE);
+ }
+ }
+ else
+ {
+ caja_window_close (window);
+ }
+ }
+ }
+ else
+ {
+ new_location = caja_file_get_location (file);
+
+ /* If the file was renamed, update location and/or
+ * title. */
+ if (!g_file_equal (new_location,
+ slot->location))
+ {
+ g_object_unref (slot->location);
+ slot->location = new_location;
+ if (slot == slot->pane->active_slot)
+ {
+ caja_window_pane_sync_location_widgets (slot->pane);
+ }
+ }
+ else
+ {
+ /* TODO?
+ * why do we update title & icon at all in this case? */
+ g_object_unref (new_location);
+ }
+
+ caja_window_slot_update_title (slot);
+ caja_window_slot_update_icon (slot);
+ }
+}
+
+static void
+update_history (CajaWindowSlot *slot,
+ CajaLocationChangeType type,
+ GFile *new_location)
+{
+ switch (type)
+ {
+ case CAJA_LOCATION_CHANGE_STANDARD:
+ case CAJA_LOCATION_CHANGE_FALLBACK:
+ caja_window_slot_add_current_location_to_history_list (slot);
+ handle_go_elsewhere (slot, new_location);
+ return;
+ case CAJA_LOCATION_CHANGE_RELOAD:
+ /* for reload there is no work to do */
+ return;
+ case CAJA_LOCATION_CHANGE_BACK:
+ caja_window_slot_add_current_location_to_history_list (slot);
+ handle_go_back (CAJA_NAVIGATION_WINDOW_SLOT (slot), new_location);
+ return;
+ case CAJA_LOCATION_CHANGE_FORWARD:
+ caja_window_slot_add_current_location_to_history_list (slot);
+ handle_go_forward (CAJA_NAVIGATION_WINDOW_SLOT (slot), new_location);
+ return;
+ case CAJA_LOCATION_CHANGE_REDIRECT:
+ /* for the redirect case, the caller can do the updating */
+ return;
+ }
+ g_return_if_fail (FALSE);
+}
+
+static void
+cancel_viewed_file_changed_callback (CajaWindowSlot *slot)
+{
+ CajaFile *file;
+
+ file = slot->viewed_file;
+ if (file != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (G_OBJECT (file),
+ G_CALLBACK (viewed_file_changed_callback),
+ slot);
+ caja_file_monitor_remove (file, &slot->viewed_file);
+ }
+}
+
+static void
+new_window_show_callback (GtkWidget *widget,
+ gpointer user_data)
+{
+ CajaWindow *window;
+
+ window = CAJA_WINDOW (user_data);
+
+ caja_window_close (window);
+
+ g_signal_handlers_disconnect_by_func (widget,
+ G_CALLBACK (new_window_show_callback),
+ user_data);
+}
+
+
+void
+caja_window_slot_open_location_full (CajaWindowSlot *slot,
+ GFile *location,
+ CajaWindowOpenMode mode,
+ CajaWindowOpenFlags flags,
+ GList *new_selection)
+{
+ CajaWindow *window;
+ CajaWindow *target_window;
+ CajaWindowPane *pane;
+ CajaWindowSlot *target_slot;
+ CajaWindowOpenFlags slot_flags;
+ gboolean do_load_location = TRUE;
+ GFile *old_location;
+ char *old_uri, *new_uri;
+ int new_slot_position;
+ GList *l;
+
+ window = slot->pane->window;
+
+ target_window = NULL;
+ target_slot = NULL;
+
+ old_uri = caja_window_slot_get_location_uri (slot);
+ if (old_uri == NULL)
+ {
+ old_uri = g_strdup ("(none)");
+ }
+ new_uri = g_file_get_uri (location);
+ caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
+ "window %p open location: old=\"%s\", new=\"%s\"",
+ window,
+ old_uri,
+ new_uri);
+ g_free (old_uri);
+ g_free (new_uri);
+
+ g_assert (!((flags & CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW) != 0 &&
+ (flags & CAJA_WINDOW_OPEN_FLAG_NEW_TAB) != 0));
+
+
+ old_location = caja_window_slot_get_location (slot);
+ switch (mode)
+ {
+ case CAJA_WINDOW_OPEN_ACCORDING_TO_MODE :
+ if (eel_preferences_get_boolean (CAJA_PREFERENCES_ALWAYS_USE_BROWSER))
+ {
+ target_window = window;
+ if (CAJA_IS_SPATIAL_WINDOW (window))
+ {
+ if (!CAJA_SPATIAL_WINDOW (window)->affect_spatial_window_on_next_location_change)
+ {
+ target_window = caja_application_create_navigation_window
+ (window->application,
+ NULL,
+ gtk_window_get_screen (GTK_WINDOW (window)));
+ }
+ else
+ {
+ CAJA_SPATIAL_WINDOW (window)->affect_spatial_window_on_next_location_change = FALSE;
+ }
+ }
+ else if ((flags & CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW) != 0)
+ {
+ target_window = caja_application_create_navigation_window
+ (window->application,
+ NULL,
+ gtk_window_get_screen (GTK_WINDOW (window)));
+ }
+ }
+ else if (CAJA_IS_SPATIAL_WINDOW (window))
+ {
+ if (!CAJA_SPATIAL_WINDOW (window)->affect_spatial_window_on_next_location_change)
+ {
+ target_window = caja_application_present_spatial_window_with_selection (
+ window->application,
+ window,
+ NULL,
+ location,
+ new_selection,
+ gtk_window_get_screen (GTK_WINDOW (window)));
+ do_load_location = FALSE;
+ }
+ else
+ {
+ CAJA_SPATIAL_WINDOW (window)->affect_spatial_window_on_next_location_change = FALSE;
+ target_window = window;
+ }
+ }
+ else if (flags & CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW)
+ {
+ target_window = caja_application_create_navigation_window
+ (window->application,
+ NULL,
+ gtk_window_get_screen (GTK_WINDOW (window)));
+ }
+ else
+ {
+ target_window = window;
+ }
+ break;
+ case CAJA_WINDOW_OPEN_IN_SPATIAL :
+ target_window = caja_application_present_spatial_window (
+ window->application,
+ window,
+ NULL,
+ location,
+ gtk_window_get_screen (GTK_WINDOW (window)));
+ break;
+ case CAJA_WINDOW_OPEN_IN_NAVIGATION :
+ target_window = caja_application_create_navigation_window
+ (window->application,
+ NULL,
+ gtk_window_get_screen (GTK_WINDOW (window)));
+ break;
+ default :
+ g_warning ("Unknown open location mode");
+ g_object_unref (old_location);
+ return;
+ }
+
+ g_assert (target_window != NULL);
+
+ if ((flags & CAJA_WINDOW_OPEN_FLAG_NEW_TAB) != 0 &&
+ CAJA_IS_NAVIGATION_WINDOW (window))
+ {
+ g_assert (target_window == window);
+
+ slot_flags = 0;
+
+ new_slot_position = eel_preferences_get_enum (CAJA_PREFERENCES_NEW_TAB_POSITION);
+ if (new_slot_position == CAJA_NEW_TAB_POSITION_END)
+ {
+ slot_flags = CAJA_WINDOW_OPEN_SLOT_APPEND;
+ }
+
+ target_slot = caja_window_open_slot (window->details->active_pane, slot_flags);
+ }
+
+ if ((flags & CAJA_WINDOW_OPEN_FLAG_CLOSE_BEHIND) != 0)
+ {
+ if (CAJA_IS_SPATIAL_WINDOW (window) && !CAJA_IS_DESKTOP_WINDOW (window))
+ {
+ if (gtk_widget_get_visible (GTK_WIDGET (target_window)))
+ {
+ caja_window_close (window);
+ }
+ else
+ {
+ g_signal_connect_object (target_window,
+ "show",
+ G_CALLBACK (new_window_show_callback),
+ window,
+ G_CONNECT_AFTER);
+ }
+ }
+ }
+
+ if (target_slot == NULL)
+ {
+ if (target_window == window)
+ {
+ target_slot = slot;
+ }
+ else
+ {
+ target_slot = target_window->details->active_pane->active_slot;
+ }
+ }
+
+ if ((!do_load_location) ||
+ (target_window == window && target_slot == slot &&
+ old_location && g_file_equal (old_location, location)))
+ {
+ if (old_location)
+ {
+ g_object_unref (old_location);
+ }
+ return;
+ }
+
+ if (old_location)
+ {
+ g_object_unref (old_location);
+ }
+
+ begin_location_change (target_slot, location, new_selection,
+ CAJA_LOCATION_CHANGE_STANDARD, 0, NULL);
+
+ /* Additionally, load this in all slots that have no location, this means
+ we load both panes in e.g. a newly opened dual pane window. */
+ for (l = target_window->details->panes; l != NULL; l = l->next)
+ {
+ pane = l->data;
+ slot = pane->active_slot;
+ if (slot->location == NULL && slot->pending_location == NULL)
+ {
+ begin_location_change (slot, location, new_selection,
+ CAJA_LOCATION_CHANGE_STANDARD, 0, NULL);
+ }
+ }
+}
+
+void
+caja_window_slot_open_location (CajaWindowSlot *slot,
+ GFile *location,
+ gboolean close_behind)
+{
+ CajaWindowOpenFlags flags;
+
+ flags = 0;
+ if (close_behind)
+ {
+ flags = CAJA_WINDOW_OPEN_FLAG_CLOSE_BEHIND;
+ }
+
+ caja_window_slot_open_location_full (slot, location,
+ CAJA_WINDOW_OPEN_ACCORDING_TO_MODE,
+ flags, NULL);
+}
+
+void
+caja_window_slot_open_location_with_selection (CajaWindowSlot *slot,
+ GFile *location,
+ GList *selection,
+ gboolean close_behind)
+{
+ CajaWindowOpenFlags flags;
+
+ flags = 0;
+ if (close_behind)
+ {
+ flags = CAJA_WINDOW_OPEN_FLAG_CLOSE_BEHIND;
+ }
+ caja_window_slot_open_location_full (slot, location,
+ CAJA_WINDOW_OPEN_ACCORDING_TO_MODE,
+ flags, selection);
+}
+
+
+void
+caja_window_slot_go_home (CajaWindowSlot *slot, gboolean new_tab)
+{
+ GFile *home;
+ CajaWindowOpenFlags flags;
+
+ g_return_if_fail (CAJA_IS_WINDOW_SLOT (slot));
+
+ if (new_tab)
+ {
+ flags = CAJA_WINDOW_OPEN_FLAG_NEW_TAB;
+ }
+ else
+ {
+ flags = 0;
+ }
+
+ home = g_file_new_for_path (g_get_home_dir ());
+ caja_window_slot_open_location_full (slot, home,
+ CAJA_WINDOW_OPEN_ACCORDING_TO_MODE,
+ flags, NULL);
+ g_object_unref (home);
+}
+
+#if 0
+static char *
+caja_window_slot_get_view_label (CajaWindowSlot *slot)
+{
+ const CajaViewInfo *info;
+
+ info = caja_view_factory_lookup (caja_window_slot_get_content_view_id (slot));
+
+ return g_strdup (info->label);
+}
+#endif
+
+static char *
+caja_window_slot_get_view_error_label (CajaWindowSlot *slot)
+{
+ const CajaViewInfo *info;
+
+ info = caja_view_factory_lookup (caja_window_slot_get_content_view_id (slot));
+
+ return g_strdup (info->error_label);
+}
+
+static char *
+caja_window_slot_get_view_startup_error_label (CajaWindowSlot *slot)
+{
+ const CajaViewInfo *info;
+
+ info = caja_view_factory_lookup (caja_window_slot_get_content_view_id (slot));
+
+ return g_strdup (info->startup_error_label);
+}
+
+static void
+report_current_content_view_failure_to_user (CajaWindowSlot *slot)
+{
+ CajaWindow *window;
+ char *message;
+
+ window = slot->pane->window;
+
+ message = caja_window_slot_get_view_startup_error_label (slot);
+ eel_show_error_dialog (message,
+ _("You can choose another view or go to a different location."),
+ GTK_WINDOW (window));
+ g_free (message);
+}
+
+static void
+report_nascent_content_view_failure_to_user (CajaWindowSlot *slot,
+ CajaView *view)
+{
+ CajaWindow *window;
+ char *message;
+
+ window = slot->pane->window;
+
+ /* TODO? why are we using the current view's error label here, instead of the next view's?
+ * This behavior has already been present in pre-slot days.
+ */
+ message = caja_window_slot_get_view_error_label (slot);
+ eel_show_error_dialog (message,
+ _("The location cannot be displayed with this viewer."),
+ GTK_WINDOW (window));
+ g_free (message);
+}
+
+
+const char *
+caja_window_slot_get_content_view_id (CajaWindowSlot *slot)
+{
+ if (slot->content_view == NULL)
+ {
+ return NULL;
+ }
+ return caja_view_get_view_id (slot->content_view);
+}
+
+gboolean
+caja_window_slot_content_view_matches_iid (CajaWindowSlot *slot,
+ const char *iid)
+{
+ if (slot->content_view == NULL)
+ {
+ return FALSE;
+ }
+ return eel_strcmp (caja_view_get_view_id (slot->content_view), iid) == 0;
+}
+
+
+/*
+ * begin_location_change
+ *
+ * Change a window's location.
+ * @window: The CajaWindow whose location should be changed.
+ * @location: A url specifying the location to load
+ * @new_selection: The initial selection to present after loading the location
+ * @type: Which type of location change is this? Standard, back, forward, or reload?
+ * @distance: If type is back or forward, the index into the back or forward chain. If
+ * type is standard or reload, this is ignored, and must be 0.
+ * @scroll_pos: The file to scroll to when the location is loaded.
+ *
+ * This is the core function for changing the location of a window. Every change to the
+ * location begins here.
+ */
+static void
+begin_location_change (CajaWindowSlot *slot,
+ GFile *location,
+ GList *new_selection,
+ CajaLocationChangeType type,
+ guint distance,
+ const char *scroll_pos)
+{
+ CajaWindow *window;
+ CajaDirectory *directory;
+ CajaFile *file;
+ gboolean force_reload;
+ char *current_pos;
+
+ g_assert (slot != NULL);
+ g_assert (location != NULL);
+ g_assert (type == CAJA_LOCATION_CHANGE_BACK
+ || type == CAJA_LOCATION_CHANGE_FORWARD
+ || distance == 0);
+
+ window = slot->pane->window;
+ g_assert (CAJA_IS_WINDOW (window));
+ g_object_ref (window);
+
+ end_location_change (slot);
+
+ caja_window_slot_set_allow_stop (slot, TRUE);
+ caja_window_slot_set_status (slot, " ");
+
+ g_assert (slot->pending_location == NULL);
+ g_assert (slot->pending_selection == NULL);
+
+ slot->pending_location = g_object_ref (location);
+ slot->location_change_type = type;
+ slot->location_change_distance = distance;
+ slot->tried_mount = FALSE;
+ slot->pending_selection = eel_g_object_list_copy (new_selection);
+
+ slot->pending_scroll_to = g_strdup (scroll_pos);
+
+ directory = caja_directory_get (location);
+
+ /* The code to force a reload is here because if we do it
+ * after determining an initial view (in the components), then
+ * we end up fetching things twice.
+ */
+ if (type == CAJA_LOCATION_CHANGE_RELOAD)
+ {
+ force_reload = TRUE;
+ }
+ else if (!caja_monitor_active ())
+ {
+ force_reload = TRUE;
+ }
+ else
+ {
+ force_reload = !caja_directory_is_local (directory);
+ }
+
+ if (force_reload)
+ {
+ caja_directory_force_reload (directory);
+ file = caja_directory_get_corresponding_file (directory);
+ caja_file_invalidate_all_attributes (file);
+ caja_file_unref (file);
+ }
+
+ caja_directory_unref (directory);
+
+ /* Set current_bookmark scroll pos */
+ if (slot->current_location_bookmark != NULL &&
+ slot->content_view != NULL)
+ {
+ current_pos = caja_view_get_first_visible_file (slot->content_view);
+ caja_bookmark_set_scroll_pos (slot->current_location_bookmark, current_pos);
+ g_free (current_pos);
+ }
+
+ /* Get the info needed for view selection */
+
+ slot->determine_view_file = caja_file_get (location);
+ g_assert (slot->determine_view_file != NULL);
+
+ /* if the currently viewed file is marked gone while loading the new location,
+ * this ensures that the window isn't destroyed */
+ cancel_viewed_file_changed_callback (slot);
+
+ caja_file_call_when_ready (slot->determine_view_file,
+ CAJA_FILE_ATTRIBUTE_INFO |
+ CAJA_FILE_ATTRIBUTE_MOUNT,
+ got_file_info_for_view_selection_callback,
+ slot);
+
+ g_object_unref (window);
+}
+
+static void
+setup_new_spatial_window (CajaWindowSlot *slot, CajaFile *file)
+{
+ CajaWindow *window;
+ char *show_hidden_file_setting;
+ char *geometry_string;
+ char *scroll_string;
+ gboolean maximized, sticky, above;
+ GtkAction *action;
+
+ window = slot->pane->window;
+
+ if (CAJA_IS_SPATIAL_WINDOW (window) && !CAJA_IS_DESKTOP_WINDOW (window))
+ {
+ /* load show hidden state */
+ show_hidden_file_setting = caja_file_get_metadata
+ (file, CAJA_METADATA_KEY_WINDOW_SHOW_HIDDEN_FILES,
+ NULL);
+ if (show_hidden_file_setting != NULL)
+ {
+ if (strcmp (show_hidden_file_setting, "1") == 0)
+ {
+ window->details->show_hidden_files_mode = CAJA_WINDOW_SHOW_HIDDEN_FILES_ENABLE;
+ }
+ else
+ {
+ window->details->show_hidden_files_mode = CAJA_WINDOW_SHOW_HIDDEN_FILES_DISABLE;
+ }
+
+ /* Update the UI, since we initialize it to the default */
+ action = gtk_action_group_get_action (window->details->main_action_group, CAJA_ACTION_SHOW_HIDDEN_FILES);
+ gtk_action_block_activate (action);
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
+ window->details->show_hidden_files_mode == CAJA_WINDOW_SHOW_HIDDEN_FILES_ENABLE);
+ gtk_action_unblock_activate (action);
+ }
+ else
+ {
+ CAJA_WINDOW (window)->details->show_hidden_files_mode = CAJA_WINDOW_SHOW_HIDDEN_FILES_DEFAULT;
+ }
+ g_free (show_hidden_file_setting);
+
+ /* load the saved window geometry */
+ maximized = caja_file_get_boolean_metadata
+ (file, CAJA_METADATA_KEY_WINDOW_MAXIMIZED, FALSE);
+ if (maximized)
+ {
+ gtk_window_maximize (GTK_WINDOW (window));
+ }
+ else
+ {
+ gtk_window_unmaximize (GTK_WINDOW (window));
+ }
+
+ sticky = caja_file_get_boolean_metadata
+ (file, CAJA_METADATA_KEY_WINDOW_STICKY, FALSE);
+ if (sticky)
+ {
+ gtk_window_stick (GTK_WINDOW (window));
+ }
+ else
+ {
+ gtk_window_unstick (GTK_WINDOW (window));
+ }
+
+ above = caja_file_get_boolean_metadata
+ (file, CAJA_METADATA_KEY_WINDOW_KEEP_ABOVE, FALSE);
+ if (above)
+ {
+ gtk_window_set_keep_above (GTK_WINDOW (window), TRUE);
+ }
+ else
+ {
+ gtk_window_set_keep_above (GTK_WINDOW (window), FALSE);
+ }
+
+ geometry_string = caja_file_get_metadata
+ (file, CAJA_METADATA_KEY_WINDOW_GEOMETRY, NULL);
+ if (geometry_string != NULL)
+ {
+ eel_gtk_window_set_initial_geometry_from_string
+ (GTK_WINDOW (window),
+ geometry_string,
+ CAJA_SPATIAL_WINDOW_MIN_WIDTH,
+ CAJA_SPATIAL_WINDOW_MIN_HEIGHT,
+ FALSE);
+ }
+ g_free (geometry_string);
+
+ if (slot->pending_selection == NULL)
+ {
+ /* If there is no pending selection, then load the saved scroll position. */
+ scroll_string = caja_file_get_metadata
+ (file, CAJA_METADATA_KEY_WINDOW_SCROLL_POSITION,
+ NULL);
+ }
+ else
+ {
+ /* If there is a pending selection, we want to scroll to an item in
+ * the pending selection list. */
+ scroll_string = g_file_get_uri (slot->pending_selection->data);
+ }
+
+ /* scroll_string might be NULL if there was no saved scroll position. */
+ if (scroll_string != NULL)
+ {
+ slot->pending_scroll_to = scroll_string;
+ }
+ }
+}
+
+typedef struct
+{
+ GCancellable *cancellable;
+ CajaWindowSlot *slot;
+} MountNotMountedData;
+
+static void
+mount_not_mounted_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ MountNotMountedData *data;
+ CajaWindow *window;
+ CajaWindowSlot *slot;
+ GError *error;
+ GCancellable *cancellable;
+
+ data = user_data;
+ slot = data->slot;
+ window = slot->pane->window;
+ cancellable = data->cancellable;
+ g_free (data);
+
+ if (g_cancellable_is_cancelled (cancellable))
+ {
+ /* Cancelled, don't call back */
+ g_object_unref (cancellable);
+ return;
+ }
+
+ slot->mount_cancellable = NULL;
+
+ slot->determine_view_file = caja_file_get (slot->pending_location);
+
+ error = NULL;
+ if (!g_file_mount_enclosing_volume_finish (G_FILE (source_object), res, &error))
+ {
+ slot->mount_error = error;
+ got_file_info_for_view_selection_callback (slot->determine_view_file, slot);
+ slot->mount_error = NULL;
+ g_error_free (error);
+ }
+ else
+ {
+ caja_file_invalidate_all_attributes (slot->determine_view_file);
+ caja_file_call_when_ready (slot->determine_view_file,
+ CAJA_FILE_ATTRIBUTE_INFO,
+ got_file_info_for_view_selection_callback,
+ slot);
+ }
+
+ g_object_unref (cancellable);
+}
+
+static void
+got_file_info_for_view_selection_callback (CajaFile *file,
+ gpointer callback_data)
+{
+ GError *error;
+ char *view_id;
+ char *mimetype;
+ CajaWindow *window;
+ CajaWindowSlot *slot;
+ CajaFile *viewed_file;
+ GFile *location;
+ GMountOperation *mount_op;
+ MountNotMountedData *data;
+
+ slot = callback_data;
+ g_assert (CAJA_IS_WINDOW_SLOT (slot));
+ g_assert (slot->determine_view_file == file);
+
+ window = slot->pane->window;
+ g_assert (CAJA_IS_WINDOW (window));
+
+ slot->determine_view_file = NULL;
+
+ if (slot->mount_error)
+ {
+ error = slot->mount_error;
+ }
+ else
+ {
+ error = caja_file_get_file_info_error (file);
+ }
+
+ if (error && error->domain == G_IO_ERROR && error->code == G_IO_ERROR_NOT_MOUNTED &&
+ !slot->tried_mount)
+ {
+ slot->tried_mount = TRUE;
+
+ mount_op = gtk_mount_operation_new (GTK_WINDOW (window));
+ g_mount_operation_set_password_save (mount_op, G_PASSWORD_SAVE_FOR_SESSION);
+ location = caja_file_get_location (file);
+ data = g_new0 (MountNotMountedData, 1);
+ data->cancellable = g_cancellable_new ();
+ data->slot = slot;
+ slot->mount_cancellable = data->cancellable;
+ g_file_mount_enclosing_volume (location, 0, mount_op, slot->mount_cancellable,
+ mount_not_mounted_callback, data);
+ g_object_unref (location);
+ g_object_unref (mount_op);
+
+ caja_file_unref (file);
+
+ return;
+ }
+
+ location = slot->pending_location;
+
+ view_id = NULL;
+
+ if (error == NULL ||
+ (error->domain == G_IO_ERROR && error->code == G_IO_ERROR_NOT_SUPPORTED))
+ {
+ /* We got the information we need, now pick what view to use: */
+
+ mimetype = caja_file_get_mime_type (file);
+
+ /* If fallback, don't use view from metadata */
+ if (slot->location_change_type != CAJA_LOCATION_CHANGE_FALLBACK)
+ {
+ /* Look in metadata for view */
+ view_id = caja_file_get_metadata
+ (file, CAJA_METADATA_KEY_DEFAULT_VIEW, NULL);
+ if (view_id != NULL &&
+ !caja_view_factory_view_supports_uri (view_id,
+ location,
+ caja_file_get_file_type (file),
+ mimetype))
+ {
+ g_free (view_id);
+ view_id = NULL;
+ }
+ }
+
+ /* Otherwise, use default */
+ if (view_id == NULL)
+ {
+ view_id = caja_global_preferences_get_default_folder_viewer_preference_as_iid ();
+
+ if (view_id != NULL &&
+ !caja_view_factory_view_supports_uri (view_id,
+ location,
+ caja_file_get_file_type (file),
+ mimetype))
+ {
+ g_free (view_id);
+ view_id = NULL;
+ }
+ }
+
+ g_free (mimetype);
+ }
+
+ if (view_id != NULL)
+ {
+ if (!gtk_widget_get_visible (GTK_WIDGET (window)) && CAJA_IS_SPATIAL_WINDOW (window))
+ {
+ /* We now have the metadata to set up the window position, etc */
+ setup_new_spatial_window (slot, file);
+ }
+ create_content_view (slot, view_id);
+ g_free (view_id);
+ }
+ else
+ {
+ display_view_selection_failure (window, file,
+ location, error);
+
+ if (!gtk_widget_get_visible (GTK_WIDGET (window)))
+ {
+ /* Destroy never-had-a-chance-to-be-seen window. This case
+ * happens when a new window cannot display its initial URI.
+ */
+ /* if this is the only window, we don't want to quit, so we redirect it to home */
+ if (caja_application_get_n_windows () <= 1)
+ {
+ g_assert (caja_application_get_n_windows () == 1);
+
+ /* Make sure we re-use this window */
+ if (CAJA_IS_SPATIAL_WINDOW (window))
+ {
+ CAJA_SPATIAL_WINDOW (window)->affect_spatial_window_on_next_location_change = TRUE;
+ }
+ /* the user could have typed in a home directory that doesn't exist,
+ in which case going home would cause an infinite loop, so we
+ better test for that */
+
+ if (!caja_is_root_directory (location))
+ {
+ if (!caja_is_home_directory (location))
+ {
+ caja_window_slot_go_home (slot, FALSE);
+ }
+ else
+ {
+ GFile *root;
+
+ root = g_file_new_for_path ("/");
+ /* the last fallback is to go to a known place that can't be deleted! */
+ caja_window_slot_go_to (slot, location, FALSE);
+ g_object_unref (root);
+ }
+ }
+ else
+ {
+ gtk_object_destroy (GTK_OBJECT (window));
+ }
+ }
+ else
+ {
+ /* Since this is a window, destroying it will also unref it. */
+ gtk_object_destroy (GTK_OBJECT (window));
+ }
+ }
+ else
+ {
+ /* Clean up state of already-showing window */
+ end_location_change (slot);
+
+ /* TODO? shouldn't we call
+ * cancel_viewed_file_changed_callback (slot);
+ * at this point, or in end_location_change()
+ */
+ /* We're missing a previous location (if opened location
+ * in a new tab) so close it and return */
+ if (slot->location == NULL)
+ {
+ caja_window_slot_close (slot);
+ }
+ else
+ {
+ /* We disconnected this, so we need to re-connect it */
+ viewed_file = caja_file_get (slot->location);
+ caja_window_slot_set_viewed_file (slot, viewed_file);
+ caja_file_monitor_add (viewed_file, &slot->viewed_file, 0);
+ g_signal_connect_object (viewed_file, "changed",
+ G_CALLBACK (viewed_file_changed_callback), slot, 0);
+ caja_file_unref (viewed_file);
+
+ /* Leave the location bar showing the bad location that the user
+ * typed (or maybe achieved by dragging or something). Many times
+ * the mistake will just be an easily-correctable typo. The user
+ * can choose "Refresh" to get the original URI back in the location bar.
+ */
+ }
+ }
+ }
+
+ caja_file_unref (file);
+}
+
+/* Load a view into the window, either reusing the old one or creating
+ * a new one. This happens when you want to load a new location, or just
+ * switch to a different view.
+ * If pending_location is set we're loading a new location and
+ * pending_location/selection will be used. If not, we're just switching
+ * view, and the current location will be used.
+ */
+static void
+create_content_view (CajaWindowSlot *slot,
+ const char *view_id)
+{
+ CajaWindow *window;
+ CajaView *view;
+ GList *selection;
+
+ window = slot->pane->window;
+
+ /* FIXME bugzilla.gnome.org 41243:
+ * We should use inheritance instead of these special cases
+ * for the desktop window.
+ */
+ if (CAJA_IS_DESKTOP_WINDOW (window))
+ {
+ /* We force the desktop to use a desktop_icon_view. It's simpler
+ * to fix it here than trying to make it pick the right view in
+ * the first place.
+ */
+ view_id = CAJA_DESKTOP_ICON_VIEW_IID;
+ }
+
+ if (slot->content_view != NULL &&
+ eel_strcmp (caja_view_get_view_id (slot->content_view),
+ view_id) == 0)
+ {
+ /* reuse existing content view */
+ view = slot->content_view;
+ slot->new_content_view = view;
+ g_object_ref (view);
+ }
+ else
+ {
+ /* create a new content view */
+ view = caja_view_factory_create (view_id,
+ CAJA_WINDOW_SLOT_INFO (slot));
+
+ eel_accessibility_set_name (view, _("Content View"));
+ eel_accessibility_set_description (view, _("View of the current folder"));
+
+ slot->new_content_view = view;
+ caja_window_slot_connect_content_view (slot, slot->new_content_view);
+ }
+
+ /* Actually load the pending location and selection: */
+
+ if (slot->pending_location != NULL)
+ {
+ load_new_location (slot,
+ slot->pending_location,
+ slot->pending_selection,
+ FALSE,
+ TRUE);
+
+ eel_g_object_list_free (slot->pending_selection);
+ slot->pending_selection = NULL;
+ }
+ else if (slot->location != NULL)
+ {
+ selection = caja_view_get_selection (slot->content_view);
+ load_new_location (slot,
+ slot->location,
+ selection,
+ FALSE,
+ TRUE);
+ eel_g_object_list_free (selection);
+ }
+ else
+ {
+ /* Something is busted, there was no location to load.
+ Just load the homedir. */
+ caja_window_slot_go_home (slot, FALSE);
+
+ }
+}
+
+static void
+load_new_location (CajaWindowSlot *slot,
+ GFile *location,
+ GList *selection,
+ gboolean tell_current_content_view,
+ gboolean tell_new_content_view)
+{
+ CajaWindow *window;
+ GList *selection_copy;
+ CajaView *view;
+ char *uri;
+
+ g_assert (slot != NULL);
+ g_assert (location != NULL);
+
+ window = slot->pane->window;
+ g_assert (CAJA_IS_WINDOW (window));
+
+ selection_copy = eel_g_object_list_copy (selection);
+
+ view = NULL;
+
+ /* Note, these may recurse into report_load_underway */
+ if (slot->content_view != NULL && tell_current_content_view)
+ {
+ view = slot->content_view;
+ uri = g_file_get_uri (location);
+ caja_view_load_location (slot->content_view, uri);
+ g_free (uri);
+ }
+
+ if (slot->new_content_view != NULL && tell_new_content_view &&
+ (!tell_current_content_view ||
+ slot->new_content_view != slot->content_view) )
+ {
+ view = slot->new_content_view;
+ uri = g_file_get_uri (location);
+ caja_view_load_location (slot->new_content_view, uri);
+ g_free (uri);
+ }
+ if (view != NULL)
+ {
+ /* slot->new_content_view might have changed here if
+ report_load_underway was called from load_location */
+ caja_view_set_selection (view, selection_copy);
+ }
+
+ eel_g_object_list_free (selection_copy);
+}
+
+/* A view started to load the location its viewing, either due to
+ * a load_location request, or some internal reason. Expect
+ * a matching load_compete later
+ */
+void
+caja_window_report_load_underway (CajaWindow *window,
+ CajaView *view)
+{
+ CajaWindowSlot *slot;
+
+ g_assert (CAJA_IS_WINDOW (window));
+
+ if (window->details->temporarily_ignore_view_signals)
+ {
+ return;
+ }
+
+ slot = caja_window_get_slot_for_view (window, view);
+ g_assert (slot != NULL);
+
+ if (view == slot->new_content_view)
+ {
+ location_has_really_changed (slot);
+ }
+ else
+ {
+ caja_window_slot_set_allow_stop (slot, TRUE);
+ }
+}
+
+static void
+caja_window_emit_location_change (CajaWindow *window,
+ GFile *location)
+{
+ char *uri;
+
+ uri = g_file_get_uri (location);
+ g_signal_emit_by_name (window, "loading_uri", uri);
+ g_free (uri);
+}
+
+/* reports location change to window's "loading-uri" clients, i.e.
+ * sidebar panels [used when switching tabs]. It will emit the pending
+ * location, or the existing location if none is pending.
+ */
+void
+caja_window_report_location_change (CajaWindow *window)
+{
+ CajaWindowSlot *slot;
+ GFile *location;
+
+ g_assert (CAJA_IS_WINDOW (window));
+
+ slot = window->details->active_pane->active_slot;
+ g_assert (CAJA_IS_WINDOW_SLOT (slot));
+
+ location = NULL;
+
+ if (slot->pending_location != NULL)
+ {
+ location = slot->pending_location;
+ }
+
+ if (location == NULL && slot->location != NULL)
+ {
+ location = slot->location;
+ }
+
+ if (location != NULL)
+ {
+ caja_window_emit_location_change (window, location);
+ }
+}
+
+/* This is called when we have decided we can actually change to the new view/location situation. */
+static void
+location_has_really_changed (CajaWindowSlot *slot)
+{
+ CajaWindow *window;
+ GtkWidget *widget;
+ GFile *location_copy;
+
+ window = slot->pane->window;
+
+ if (slot->new_content_view != NULL)
+ {
+ widget = caja_view_get_widget (slot->new_content_view);
+ /* Switch to the new content view. */
+ if (gtk_widget_get_parent (widget) == NULL)
+ {
+ if (slot->content_view != NULL)
+ {
+ caja_window_slot_disconnect_content_view (slot, slot->content_view);
+ }
+ caja_window_slot_set_content_view_widget (slot, slot->new_content_view);
+ }
+ g_object_unref (slot->new_content_view);
+ slot->new_content_view = NULL;
+ }
+
+ if (slot->pending_location != NULL)
+ {
+ /* Tell the window we are finished. */
+ update_for_new_location (slot);
+ }
+
+ location_copy = NULL;
+ if (slot->location != NULL)
+ {
+ location_copy = g_object_ref (slot->location);
+ }
+
+ free_location_change (slot);
+
+ if (location_copy != NULL)
+ {
+ if (slot == caja_window_get_active_slot (window))
+ {
+ caja_window_emit_location_change (window, location_copy);
+ }
+
+ g_object_unref (location_copy);
+ }
+}
+
+static void
+slot_add_extension_extra_widgets (CajaWindowSlot *slot)
+{
+ GList *providers, *l;
+ GtkWidget *widget;
+ char *uri;
+
+ providers = caja_module_get_extensions_for_type (CAJA_TYPE_LOCATION_WIDGET_PROVIDER);
+
+ uri = g_file_get_uri (slot->location);
+ for (l = providers; l != NULL; l = l->next)
+ {
+ CajaLocationWidgetProvider *provider;
+
+ provider = CAJA_LOCATION_WIDGET_PROVIDER (l->data);
+ widget = caja_location_widget_provider_get_widget (provider, uri, GTK_WIDGET (slot->pane->window));
+ if (widget != NULL)
+ {
+ caja_window_slot_add_extra_location_widget (slot, widget);
+ }
+ }
+ g_free (uri);
+
+ caja_module_extension_list_free (providers);
+}
+
+static void
+caja_window_slot_show_x_content_bar (CajaWindowSlot *slot, GMount *mount, const char **x_content_types)
+{
+ unsigned int n;
+
+ g_assert (CAJA_IS_WINDOW_SLOT (slot));
+
+ for (n = 0; x_content_types[n] != NULL; n++)
+ {
+ GAppInfo *default_app;
+
+ /* skip blank media; the burn:/// location will provide it's own cluebar */
+ if (g_str_has_prefix (x_content_types[n], "x-content/blank-"))
+ {
+ continue;
+ }
+
+ /* don't show the cluebar for windows software */
+ if (g_content_type_is_a (x_content_types[n], "x-content/win32-software"))
+ {
+ continue;
+ }
+
+ /* only show the cluebar if a default app is available */
+ default_app = g_app_info_get_default_for_type (x_content_types[n], FALSE);
+ if (default_app != NULL)
+ {
+ GtkWidget *bar;
+ bar = caja_x_content_bar_new (mount, x_content_types[n]);
+ gtk_widget_show (bar);
+ caja_window_slot_add_extra_location_widget (slot, bar);
+ g_object_unref (default_app);
+ }
+ }
+}
+
+static void
+caja_window_slot_show_trash_bar (CajaWindowSlot *slot,
+ CajaWindow *window)
+{
+ GtkWidget *bar;
+
+ bar = caja_trash_bar_new (window);
+ gtk_widget_show (bar);
+
+ caja_window_slot_add_extra_location_widget (slot, bar);
+}
+
+typedef struct
+{
+ CajaWindowSlot *slot;
+ GCancellable *cancellable;
+ GMount *mount;
+} FindMountData;
+
+static void
+found_content_type_cb (const char **x_content_types, FindMountData *data)
+{
+ CajaWindowSlot *slot;
+
+ if (g_cancellable_is_cancelled (data->cancellable))
+ {
+ goto out;
+ }
+
+ slot = data->slot;
+
+ if (x_content_types != NULL && x_content_types[0] != NULL)
+ {
+ caja_window_slot_show_x_content_bar (slot, data->mount, x_content_types);
+ }
+
+ slot->find_mount_cancellable = NULL;
+
+out:
+ g_object_unref (data->mount);
+ g_object_unref (data->cancellable);
+ g_free (data);
+}
+
+static void
+found_mount_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ FindMountData *data = user_data;
+ GMount *mount;
+ CajaWindowSlot *slot;
+
+ if (g_cancellable_is_cancelled (data->cancellable))
+ {
+ goto out;
+ }
+
+ slot = data->slot;
+
+ mount = g_file_find_enclosing_mount_finish (G_FILE (source_object),
+ res,
+ NULL);
+ if (mount != NULL)
+ {
+ data->mount = mount;
+ caja_autorun_get_x_content_types_for_mount_async (mount,
+ (CajaAutorunGetContent)found_content_type_cb,
+ data->cancellable,
+ data);
+ return;
+ }
+
+ data->slot->find_mount_cancellable = NULL;
+
+out:
+ g_object_unref (data->cancellable);
+ g_free (data);
+}
+
+/* Handle the changes for the CajaWindow itself. */
+static void
+update_for_new_location (CajaWindowSlot *slot)
+{
+ CajaWindow *window;
+ GFile *new_location;
+ CajaFile *file;
+ CajaDirectory *directory;
+ gboolean location_really_changed;
+ FindMountData *data;
+
+ window = slot->pane->window;
+
+ new_location = slot->pending_location;
+ slot->pending_location = NULL;
+
+ set_displayed_location (slot, new_location);
+
+ update_history (slot, slot->location_change_type, new_location);
+
+ location_really_changed =
+ slot->location == NULL ||
+ !g_file_equal (slot->location, new_location);
+
+ /* Set the new location. */
+ if (slot->location)
+ {
+ g_object_unref (slot->location);
+ }
+ slot->location = new_location;
+
+ /* Create a CajaFile for this location, so we can catch it
+ * if it goes away.
+ */
+ cancel_viewed_file_changed_callback (slot);
+ file = caja_file_get (slot->location);
+ caja_window_slot_set_viewed_file (slot, file);
+ slot->viewed_file_seen = !caja_file_is_not_yet_confirmed (file);
+ slot->viewed_file_in_trash = caja_file_is_in_trash (file);
+ caja_file_monitor_add (file, &slot->viewed_file, 0);
+ g_signal_connect_object (file, "changed",
+ G_CALLBACK (viewed_file_changed_callback), slot, 0);
+ caja_file_unref (file);
+
+ if (slot == window->details->active_pane->active_slot)
+ {
+ /* Check if we can go up. */
+ caja_window_update_up_button (window);
+
+ caja_window_sync_zoom_widgets (window);
+
+ /* Set up the content view menu for this new location. */
+ caja_window_load_view_as_menus (window);
+
+ /* Load menus from caja extensions for this location */
+ caja_window_load_extension_menus (window);
+ }
+
+ if (location_really_changed)
+ {
+ caja_window_slot_remove_extra_location_widgets (slot);
+
+ directory = caja_directory_get (slot->location);
+
+ caja_window_slot_update_query_editor (slot);
+
+ if (caja_directory_is_in_trash (directory))
+ {
+ caja_window_slot_show_trash_bar (slot, window);
+ }
+
+ /* need the mount to determine if we should put up the x-content cluebar */
+ if (slot->find_mount_cancellable != NULL)
+ {
+ g_cancellable_cancel (slot->find_mount_cancellable);
+ slot->find_mount_cancellable = NULL;
+ }
+
+ data = g_new (FindMountData, 1);
+ data->slot = slot;
+ data->cancellable = g_cancellable_new ();
+ data->mount = NULL;
+
+ slot->find_mount_cancellable = data->cancellable;
+ g_file_find_enclosing_mount_async (slot->location,
+ G_PRIORITY_DEFAULT,
+ data->cancellable,
+ found_mount_cb,
+ data);
+
+ caja_directory_unref (directory);
+
+ slot_add_extension_extra_widgets (slot);
+ }
+
+ caja_window_slot_update_title (slot);
+ caja_window_slot_update_icon (slot);
+
+ if (slot == slot->pane->active_slot)
+ {
+ caja_window_pane_sync_location_widgets (slot->pane);
+
+ if (location_really_changed)
+ {
+ caja_window_pane_sync_search_widgets (slot->pane);
+ }
+
+ if (CAJA_IS_NAVIGATION_WINDOW (window) &&
+ slot->pane == window->details->active_pane)
+ {
+ caja_navigation_window_load_extension_toolbar_items (CAJA_NAVIGATION_WINDOW (window));
+ }
+ }
+}
+
+/* A location load previously announced by load_underway
+ * has been finished */
+void
+caja_window_report_load_complete (CajaWindow *window,
+ CajaView *view)
+{
+ CajaWindowSlot *slot;
+
+ g_assert (CAJA_IS_WINDOW (window));
+
+ if (window->details->temporarily_ignore_view_signals)
+ {
+ return;
+ }
+
+ slot = caja_window_get_slot_for_view (window, view);
+ g_assert (slot != NULL);
+
+ /* Only handle this if we're expecting it.
+ * Don't handle it if its from an old view we've switched from */
+ if (view == slot->content_view)
+ {
+ if (slot->pending_scroll_to != NULL)
+ {
+ caja_view_scroll_to_file (slot->content_view,
+ slot->pending_scroll_to);
+ }
+ end_location_change (slot);
+ }
+}
+
+static void
+end_location_change (CajaWindowSlot *slot)
+{
+ CajaWindow *window;
+ char *uri;
+
+ window = slot->pane->window;
+
+ uri = caja_window_slot_get_location_uri (slot);
+ if (uri)
+ {
+ caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
+ "finished loading window %p: %s", window, uri);
+ g_free (uri);
+ }
+
+ caja_window_slot_set_allow_stop (slot, FALSE);
+
+ /* Now we can free pending_scroll_to, since the load_complete
+ * callback already has been emitted.
+ */
+ g_free (slot->pending_scroll_to);
+ slot->pending_scroll_to = NULL;
+
+ free_location_change (slot);
+}
+
+static void
+free_location_change (CajaWindowSlot *slot)
+{
+ CajaWindow *window;
+
+ window = slot->pane->window;
+ g_assert (CAJA_IS_WINDOW (window));
+
+ if (slot->pending_location)
+ {
+ g_object_unref (slot->pending_location);
+ }
+ slot->pending_location = NULL;
+
+ eel_g_object_list_free (slot->pending_selection);
+ slot->pending_selection = NULL;
+
+ /* Don't free pending_scroll_to, since thats needed until
+ * the load_complete callback.
+ */
+
+ if (slot->mount_cancellable != NULL)
+ {
+ g_cancellable_cancel (slot->mount_cancellable);
+ slot->mount_cancellable = NULL;
+ }
+
+ if (slot->determine_view_file != NULL)
+ {
+ caja_file_cancel_call_when_ready
+ (slot->determine_view_file,
+ got_file_info_for_view_selection_callback, slot);
+ slot->determine_view_file = NULL;
+ }
+
+ if (slot->new_content_view != NULL)
+ {
+ window->details->temporarily_ignore_view_signals = TRUE;
+ caja_view_stop_loading (slot->new_content_view);
+ window->details->temporarily_ignore_view_signals = FALSE;
+
+ caja_window_slot_disconnect_content_view (slot, slot->new_content_view);
+ g_object_unref (slot->new_content_view);
+ slot->new_content_view = NULL;
+ }
+}
+
+static void
+cancel_location_change (CajaWindowSlot *slot)
+{
+ GList *selection;
+
+ if (slot->pending_location != NULL
+ && slot->location != NULL
+ && slot->content_view != NULL)
+ {
+
+ /* No need to tell the new view - either it is the
+ * same as the old view, in which case it will already
+ * be told, or it is the very pending change we wish
+ * to cancel.
+ */
+ selection = caja_view_get_selection (slot->content_view);
+ load_new_location (slot,
+ slot->location,
+ selection,
+ TRUE,
+ FALSE);
+ eel_g_object_list_free (selection);
+ }
+
+ end_location_change (slot);
+}
+
+void
+caja_window_report_view_failed (CajaWindow *window,
+ CajaView *view)
+{
+ CajaWindowSlot *slot;
+ gboolean do_close_window;
+ GFile *fallback_load_location;
+
+ if (window->details->temporarily_ignore_view_signals)
+ {
+ return;
+ }
+
+ slot = caja_window_get_slot_for_view (window, view);
+ g_assert (slot != NULL);
+
+ g_warning ("A view failed. The UI will handle this with a dialog but this should be debugged.");
+
+ do_close_window = FALSE;
+ fallback_load_location = NULL;
+
+ if (view == slot->content_view)
+ {
+ caja_window_slot_disconnect_content_view (slot, view);
+ caja_window_slot_set_content_view_widget (slot, NULL);
+
+ report_current_content_view_failure_to_user (slot);
+ }
+ else
+ {
+ /* Only report error on first try */
+ if (slot->location_change_type != CAJA_LOCATION_CHANGE_FALLBACK)
+ {
+ report_nascent_content_view_failure_to_user (slot, view);
+
+ fallback_load_location = g_object_ref (slot->pending_location);
+ }
+ else
+ {
+ if (!gtk_widget_get_visible (GTK_WIDGET (window)))
+ {
+ do_close_window = TRUE;
+ }
+ }
+ }
+
+ cancel_location_change (slot);
+
+ if (fallback_load_location != NULL)
+ {
+ /* We loose the pending selection change here, but who cares... */
+ begin_location_change (slot, fallback_load_location, NULL,
+ CAJA_LOCATION_CHANGE_FALLBACK, 0, NULL);
+ g_object_unref (fallback_load_location);
+ }
+
+ if (do_close_window)
+ {
+ gtk_widget_destroy (GTK_WIDGET (window));
+ }
+}
+
+static void
+display_view_selection_failure (CajaWindow *window, CajaFile *file,
+ GFile *location, GError *error)
+{
+ char *full_uri_for_display;
+ char *uri_for_display;
+ char *error_message;
+ char *detail_message;
+ char *scheme_string;
+ GtkDialog *dialog;
+
+ /* Some sort of failure occurred. How 'bout we tell the user? */
+ full_uri_for_display = g_file_get_parse_name (location);
+ /* Truncate the URI so it doesn't get insanely wide. Note that even
+ * though the dialog uses wrapped text, if the URI doesn't contain
+ * white space then the text-wrapping code is too stupid to wrap it.
+ */
+ uri_for_display = eel_str_middle_truncate
+ (full_uri_for_display, MAX_URI_IN_DIALOG_LENGTH);
+ g_free (full_uri_for_display);
+
+ error_message = NULL;
+ detail_message = NULL;
+ if (error == NULL)
+ {
+ if (caja_file_is_directory (file))
+ {
+ error_message = g_strdup_printf
+ (_("Could not display \"%s\"."),
+ uri_for_display);
+ detail_message = g_strdup
+ (_("Caja has no installed viewer capable of displaying the folder."));
+ }
+ else
+ {
+ error_message = g_strdup_printf
+ (_("Could not display \"%s\"."),
+ uri_for_display);
+ detail_message = g_strdup
+ (_("The location is not a folder."));
+ }
+ }
+ else if (error->domain == G_IO_ERROR)
+ {
+ switch (error->code)
+ {
+ case G_IO_ERROR_NOT_FOUND:
+ error_message = g_strdup_printf
+ (_("Could not find \"%s\"."),
+ uri_for_display);
+ detail_message = g_strdup
+ (_("Please check the spelling and try again."));
+ break;
+ case G_IO_ERROR_NOT_SUPPORTED:
+ scheme_string = g_file_get_uri_scheme (location);
+
+ error_message = g_strdup_printf (_("Could not display \"%s\"."),
+ uri_for_display);
+ if (scheme_string != NULL)
+ {
+ detail_message = g_strdup_printf (_("Caja cannot handle \"%s\" locations."),
+ scheme_string);
+ }
+ else
+ {
+ detail_message = g_strdup (_("Caja cannot handle this kind of location."));
+ }
+ g_free (scheme_string);
+ break;
+ case G_IO_ERROR_NOT_MOUNTED:
+ error_message = g_strdup_printf (_("Could not display \"%s\"."),
+ uri_for_display);
+ detail_message = g_strdup (_("Unable to mount the location."));
+ break;
+
+ case G_IO_ERROR_PERMISSION_DENIED:
+ error_message = g_strdup_printf (_("Could not display \"%s\"."),
+ uri_for_display);
+ detail_message = g_strdup (_("Access was denied."));
+ break;
+
+ case G_IO_ERROR_HOST_NOT_FOUND:
+ /* This case can be hit for user-typed strings like "foo" due to
+ * the code that guesses web addresses when there's no initial "/".
+ * But this case is also hit for legitimate web addresses when
+ * the proxy is set up wrong.
+ */
+ error_message = g_strdup_printf (_("Could not display \"%s\", because the host could not be found."),
+ uri_for_display);
+ detail_message = g_strdup (_("Check that the spelling is correct and that your proxy settings are correct."));
+ break;
+ case G_IO_ERROR_CANCELLED:
+ case G_IO_ERROR_FAILED_HANDLED:
+ g_free (uri_for_display);
+ return;
+
+ default:
+ break;
+ }
+ }
+
+ if (error_message == NULL)
+ {
+ error_message = g_strdup_printf (_("Could not display \"%s\"."),
+ uri_for_display);
+ detail_message = g_strdup_printf (_("Error: %s\nPlease select another viewer and try again."), error->message);
+ }
+
+ dialog = eel_show_error_dialog (error_message, detail_message, NULL);
+
+ g_free (uri_for_display);
+ g_free (error_message);
+ g_free (detail_message);
+}
+
+
+void
+caja_window_slot_stop_loading (CajaWindowSlot *slot)
+{
+ CajaWindow *window;
+
+ window = CAJA_WINDOW (slot->pane->window);
+ g_assert (CAJA_IS_WINDOW (window));
+
+ caja_view_stop_loading (slot->content_view);
+
+ if (slot->new_content_view != NULL)
+ {
+ window->details->temporarily_ignore_view_signals = TRUE;
+ caja_view_stop_loading (slot->new_content_view);
+ window->details->temporarily_ignore_view_signals = FALSE;
+ }
+
+ cancel_location_change (slot);
+}
+
+void
+caja_window_slot_set_content_view (CajaWindowSlot *slot,
+ const char *id)
+{
+ CajaWindow *window;
+ CajaFile *file;
+ char *uri;
+
+ g_assert (slot != NULL);
+ g_assert (slot->location != NULL);
+ g_assert (id != NULL);
+
+ window = slot->pane->window;
+ g_assert (CAJA_IS_WINDOW (window));
+
+ uri = caja_window_slot_get_location_uri (slot);
+ caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
+ "change view of window %p: \"%s\" to \"%s\"",
+ window, uri, id);
+ g_free (uri);
+
+ if (caja_window_slot_content_view_matches_iid (slot, id))
+ {
+ return;
+ }
+
+ end_location_change (slot);
+
+ file = caja_file_get (slot->location);
+ caja_file_set_metadata
+ (file, CAJA_METADATA_KEY_DEFAULT_VIEW, NULL, id);
+ caja_file_unref (file);
+
+ caja_window_slot_set_allow_stop (slot, TRUE);
+
+ if (caja_view_get_selection_count (slot->content_view) == 0)
+ {
+ /* If there is no selection, queue a scroll to the same icon that
+ * is currently visible */
+ slot->pending_scroll_to = caja_view_get_first_visible_file (slot->content_view);
+ }
+ slot->location_change_type = CAJA_LOCATION_CHANGE_RELOAD;
+
+ create_content_view (slot, id);
+}
+
+void
+caja_window_manage_views_close_slot (CajaWindowPane *pane,
+ CajaWindowSlot *slot)
+{
+ if (slot->content_view != NULL)
+ {
+ caja_window_slot_disconnect_content_view (slot, slot->content_view);
+ }
+
+ free_location_change (slot);
+ cancel_viewed_file_changed_callback (slot);
+}
+
+void
+caja_navigation_window_back_or_forward (CajaNavigationWindow *window,
+ gboolean back, guint distance, gboolean new_tab)
+{
+ CajaWindowSlot *slot;
+ CajaNavigationWindowSlot *navigation_slot;
+ GList *list;
+ GFile *location;
+ guint len;
+ CajaBookmark *bookmark;
+
+ slot = CAJA_WINDOW (window)->details->active_pane->active_slot;
+ navigation_slot = (CajaNavigationWindowSlot *) slot;
+ list = back ? navigation_slot->back_list : navigation_slot->forward_list;
+
+ len = (guint) g_list_length (list);
+
+ /* If we can't move in the direction at all, just return. */
+ if (len == 0)
+ return;
+
+ /* If the distance to move is off the end of the list, go to the end
+ of the list. */
+ if (distance >= len)
+ distance = len - 1;
+
+ bookmark = g_list_nth_data (list, distance);
+ location = caja_bookmark_get_location (bookmark);
+
+ if (new_tab)
+ {
+ caja_window_slot_open_location_full (slot, location,
+ CAJA_WINDOW_OPEN_ACCORDING_TO_MODE,
+ CAJA_WINDOW_OPEN_FLAG_NEW_TAB,
+ NULL);
+ }
+ else
+ {
+ char *scroll_pos;
+
+ scroll_pos = caja_bookmark_get_scroll_pos (bookmark);
+ begin_location_change
+ (slot,
+ location, NULL,
+ back ? CAJA_LOCATION_CHANGE_BACK : CAJA_LOCATION_CHANGE_FORWARD,
+ distance,
+ scroll_pos);
+
+ g_free (scroll_pos);
+ }
+
+ g_object_unref (location);
+}
+
+/* reload the contents of the window */
+void
+caja_window_slot_reload (CajaWindowSlot *slot)
+{
+ GFile *location;
+ char *current_pos;
+ GList *selection;
+
+ g_assert (CAJA_IS_WINDOW_SLOT (slot));
+
+ if (slot->location == NULL)
+ {
+ return;
+ }
+
+ /* peek_slot_field (window, location) can be free'd during the processing
+ * of begin_location_change, so make a copy
+ */
+ location = g_object_ref (slot->location);
+ current_pos = NULL;
+ selection = NULL;
+ if (slot->content_view != NULL)
+ {
+ current_pos = caja_view_get_first_visible_file (slot->content_view);
+ selection = caja_view_get_selection (slot->content_view);
+ }
+ begin_location_change
+ (slot, location, selection,
+ CAJA_LOCATION_CHANGE_RELOAD, 0, current_pos);
+ g_free (current_pos);
+ g_object_unref (location);
+ eel_g_object_list_free (selection);
+}
+
+void
+caja_window_reload (CajaWindow *window)
+{
+ g_assert (CAJA_IS_WINDOW (window));
+
+ caja_window_slot_reload (window->details->active_pane->active_slot);
+}
+
diff --git a/src/caja-window-manage-views.h b/src/caja-window-manage-views.h
new file mode 100644
index 00000000..76c4eeee
--- /dev/null
+++ b/src/caja-window-manage-views.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 1999, 2000 Red Hat, Inc.
+ * Copyright (C) 1999, 2000, 2001 Eazel, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Author: Darin Adler <[email protected]>
+ *
+ */
+
+#ifndef CAJA_WINDOW_MANAGE_VIEWS_H
+#define CAJA_WINDOW_MANAGE_VIEWS_H
+
+#include "caja-window.h"
+#include "caja-window-pane.h"
+#include "caja-navigation-window.h"
+
+void caja_window_manage_views_close_slot (CajaWindowPane *pane,
+ CajaWindowSlot *slot);
+
+
+/* CajaWindowInfo implementation: */
+void caja_window_report_load_underway (CajaWindow *window,
+ CajaView *view);
+void caja_window_report_selection_changed (CajaWindowInfo *window);
+void caja_window_report_view_failed (CajaWindow *window,
+ CajaView *view);
+void caja_window_report_load_complete (CajaWindow *window,
+ CajaView *view);
+void caja_window_report_location_change (CajaWindow *window);
+void caja_window_update_up_button (CajaWindow *window);
+
+#endif /* CAJA_WINDOW_MANAGE_VIEWS_H */
diff --git a/src/caja-window-menus.c b/src/caja-window-menus.c
new file mode 100644
index 00000000..c11b2869
--- /dev/null
+++ b/src/caja-window-menus.c
@@ -0,0 +1,1163 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2000, 2001 Eazel, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Author: John Sullivan <[email protected]>
+ */
+
+/* caja-window-menus.h - implementation of caja window menu operations,
+ * split into separate file just for convenience.
+ */
+#include <config.h>
+
+#include <locale.h>
+
+#include "caja-actions.h"
+#include "caja-application.h"
+#include "caja-connect-server-dialog.h"
+#include "caja-file-management-properties.h"
+#include "caja-property-browser.h"
+#include "caja-window-manage-views.h"
+#include "caja-window-bookmarks.h"
+#include "caja-window-private.h"
+#include "caja-desktop-window.h"
+#include "caja-search-bar.h"
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+#include <glib/gi18n.h>
+#include <eel/eel-gtk-extensions.h>
+#include <eel/eel-preferences.h>
+#include <libcaja-extension/caja-menu-provider.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-ui-utilities.h>
+#include <libcaja-private/caja-module.h>
+#include <libcaja-private/caja-undo-manager.h>
+#include <libcaja-private/caja-search-directory.h>
+#include <libcaja-private/caja-search-engine.h>
+#include <libcaja-private/caja-signaller.h>
+#include <libcaja-private/caja-trash-monitor.h>
+#include <string.h>
+
+#define MENU_PATH_EXTENSION_ACTIONS "/MenuBar/File/Extension Actions"
+#define POPUP_PATH_EXTENSION_ACTIONS "/background/Before Zoom Items/Extension Actions"
+
+#define NETWORK_URI "network:"
+#define COMPUTER_URI "computer:"
+#define BURN_CD_URI "burn:"
+
+/* Struct that stores all the info necessary to activate a bookmark. */
+typedef struct
+{
+ CajaBookmark *bookmark;
+ CajaWindow *window;
+ guint changed_handler_id;
+ CajaBookmarkFailedCallback failed_callback;
+} BookmarkHolder;
+
+static BookmarkHolder *
+bookmark_holder_new (CajaBookmark *bookmark,
+ CajaWindow *window,
+ GCallback refresh_callback,
+ CajaBookmarkFailedCallback failed_callback)
+{
+ BookmarkHolder *new_bookmark_holder;
+
+ new_bookmark_holder = g_new (BookmarkHolder, 1);
+ new_bookmark_holder->window = window;
+ new_bookmark_holder->bookmark = bookmark;
+ new_bookmark_holder->failed_callback = failed_callback;
+ /* Ref the bookmark because it might be unreffed away while
+ * we're holding onto it (not an issue for window).
+ */
+ g_object_ref (bookmark);
+ new_bookmark_holder->changed_handler_id =
+ g_signal_connect_object (bookmark, "appearance_changed",
+ refresh_callback,
+ window, G_CONNECT_SWAPPED);
+
+ return new_bookmark_holder;
+}
+
+static void
+bookmark_holder_free (BookmarkHolder *bookmark_holder)
+{
+ g_signal_handler_disconnect (bookmark_holder->bookmark,
+ bookmark_holder->changed_handler_id);
+ g_object_unref (bookmark_holder->bookmark);
+ g_free (bookmark_holder);
+}
+
+static void
+bookmark_holder_free_cover (gpointer callback_data, GClosure *closure)
+{
+ bookmark_holder_free (callback_data);
+}
+
+static gboolean
+should_open_in_new_tab (void)
+{
+ /* FIXME this is duplicated */
+ GdkEvent *event;
+
+ event = gtk_get_current_event ();
+
+ if (event == NULL)
+ {
+ return FALSE;
+ }
+
+ if (event->type == GDK_BUTTON_PRESS || event->type == GDK_BUTTON_RELEASE)
+ {
+ return event->button.button == 2;
+ }
+
+ gdk_event_free (event);
+
+ return FALSE;
+}
+
+static void
+activate_bookmark_in_menu_item (GtkAction *action, gpointer user_data)
+{
+ CajaWindowSlot *slot;
+ BookmarkHolder *holder;
+ GFile *location;
+
+ holder = (BookmarkHolder *)user_data;
+
+ if (caja_bookmark_uri_known_not_to_exist (holder->bookmark))
+ {
+ holder->failed_callback (holder->window, holder->bookmark);
+ }
+ else
+ {
+ location = caja_bookmark_get_location (holder->bookmark);
+ slot = caja_window_get_active_slot (holder->window);
+ caja_window_slot_go_to (slot,
+ location,
+ should_open_in_new_tab ());
+ g_object_unref (location);
+ }
+}
+
+void
+caja_menus_append_bookmark_to_menu (CajaWindow *window,
+ CajaBookmark *bookmark,
+ const char *parent_path,
+ const char *parent_id,
+ guint index_in_parent,
+ GtkActionGroup *action_group,
+ guint merge_id,
+ GCallback refresh_callback,
+ CajaBookmarkFailedCallback failed_callback)
+{
+ BookmarkHolder *bookmark_holder;
+ char action_name[128];
+ char *name;
+ char *path;
+ GdkPixbuf *pixbuf;
+ GtkAction *action;
+ GtkWidget *menuitem;
+
+ g_assert (CAJA_IS_WINDOW (window));
+ g_assert (CAJA_IS_BOOKMARK (bookmark));
+
+ bookmark_holder = bookmark_holder_new (bookmark, window, refresh_callback, failed_callback);
+ name = caja_bookmark_get_name (bookmark);
+
+ /* Create menu item with pixbuf */
+ pixbuf = caja_bookmark_get_pixbuf (bookmark, GTK_ICON_SIZE_MENU);
+
+ g_snprintf (action_name, sizeof (action_name), "%s%d", parent_id, index_in_parent);
+
+ action = gtk_action_new (action_name,
+ name,
+ _("Go to the location specified by this bookmark"),
+ NULL);
+
+ g_object_set_data_full (G_OBJECT (action), "menu-icon",
+ g_object_ref (pixbuf),
+ g_object_unref);
+
+ g_signal_connect_data (action, "activate",
+ G_CALLBACK (activate_bookmark_in_menu_item),
+ bookmark_holder,
+ bookmark_holder_free_cover, 0);
+
+ gtk_action_group_add_action (action_group,
+ GTK_ACTION (action));
+
+ g_object_unref (action);
+
+ gtk_ui_manager_add_ui (window->details->ui_manager,
+ merge_id,
+ parent_path,
+ action_name,
+ action_name,
+ GTK_UI_MANAGER_MENUITEM,
+ FALSE);
+
+ path = g_strdup_printf ("%s/%s", parent_path, action_name);
+ menuitem = gtk_ui_manager_get_widget (window->details->ui_manager,
+ path);
+ gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (menuitem),
+ TRUE);
+
+ g_object_unref (pixbuf);
+ g_free (path);
+ g_free (name);
+}
+
+static void
+action_close_window_slot_callback (GtkAction *action,
+ gpointer user_data)
+{
+ CajaWindow *window;
+ CajaWindowSlot *slot;
+
+ window = CAJA_WINDOW (user_data);
+ slot = caja_window_get_active_slot (window);
+
+ caja_window_slot_close (slot);
+}
+
+static void
+action_connect_to_server_callback (GtkAction *action,
+ gpointer user_data)
+{
+ CajaWindow *window = CAJA_WINDOW (user_data);
+ CajaWindowSlot *slot;
+ GtkWidget *dialog;
+ GFile *location;
+
+ slot = caja_window_get_active_slot (window);
+ location = caja_window_slot_get_location (slot);
+ dialog = caja_connect_server_dialog_new (window, location);
+ if (location)
+ {
+ g_object_unref (location);
+ }
+
+ gtk_widget_show (dialog);
+}
+
+static void
+action_stop_callback (GtkAction *action,
+ gpointer user_data)
+{
+ CajaWindow *window;
+ CajaWindowSlot *slot;
+
+ window = CAJA_WINDOW (user_data);
+ slot = caja_window_get_active_slot (window);
+
+ caja_window_slot_stop_loading (slot);
+}
+
+static void
+action_undo_callback (GtkAction *action,
+ gpointer user_data)
+{
+ caja_undo_manager_undo
+ (CAJA_WINDOW (user_data)->application->undo_manager);
+}
+
+static void
+action_home_callback (GtkAction *action,
+ gpointer user_data)
+{
+ CajaWindow *window;
+ CajaWindowSlot *slot;
+
+ window = CAJA_WINDOW (user_data);
+ slot = caja_window_get_active_slot (window);
+
+ caja_window_slot_go_home (slot,
+ should_open_in_new_tab ());
+}
+
+static void
+action_go_to_computer_callback (GtkAction *action,
+ gpointer user_data)
+{
+ CajaWindow *window;
+ CajaWindowSlot *slot;
+ GFile *computer;
+
+ window = CAJA_WINDOW (user_data);
+ slot = caja_window_get_active_slot (window);
+
+ computer = g_file_new_for_uri (COMPUTER_URI);
+ caja_window_slot_go_to (slot,
+ computer,
+ should_open_in_new_tab ());
+ g_object_unref (computer);
+}
+
+static void
+action_go_to_network_callback (GtkAction *action,
+ gpointer user_data)
+{
+ CajaWindow *window;
+ CajaWindowSlot *slot;
+ GFile *network;
+
+ window = CAJA_WINDOW (user_data);
+ slot = caja_window_get_active_slot (window);
+
+ network = g_file_new_for_uri (NETWORK_URI);
+ caja_window_slot_go_to (slot,
+ network,
+ should_open_in_new_tab ());
+ g_object_unref (network);
+}
+
+static void
+action_go_to_templates_callback (GtkAction *action,
+ gpointer user_data)
+{
+ CajaWindow *window;
+ CajaWindowSlot *slot;
+ char *path;
+ GFile *location;
+
+ window = CAJA_WINDOW (user_data);
+ slot = caja_window_get_active_slot (window);
+
+ path = caja_get_templates_directory ();
+ location = g_file_new_for_path (path);
+ g_free (path);
+ caja_window_slot_go_to (slot,
+ location,
+ should_open_in_new_tab ());
+ g_object_unref (location);
+}
+
+static void
+action_go_to_trash_callback (GtkAction *action,
+ gpointer user_data)
+{
+ CajaWindow *window;
+ CajaWindowSlot *slot;
+ GFile *trash;
+
+ window = CAJA_WINDOW (user_data);
+ slot = caja_window_get_active_slot (window);
+
+ trash = g_file_new_for_uri ("trash:///");
+ caja_window_slot_go_to (slot,
+ trash,
+ should_open_in_new_tab ());
+ g_object_unref (trash);
+}
+
+static void
+action_reload_callback (GtkAction *action,
+ gpointer user_data)
+{
+ caja_window_reload (CAJA_WINDOW (user_data));
+}
+
+static void
+action_zoom_in_callback (GtkAction *action,
+ gpointer user_data)
+{
+ caja_window_zoom_in (CAJA_WINDOW (user_data));
+}
+
+static void
+action_zoom_out_callback (GtkAction *action,
+ gpointer user_data)
+{
+ caja_window_zoom_out (CAJA_WINDOW (user_data));
+}
+
+static void
+action_zoom_normal_callback (GtkAction *action,
+ gpointer user_data)
+{
+ caja_window_zoom_to_default (CAJA_WINDOW (user_data));
+}
+
+static void
+action_show_hidden_files_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ CajaWindow *window;
+ CajaWindowShowHiddenFilesMode mode;
+
+ window = CAJA_WINDOW (callback_data);
+
+ if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
+ {
+ mode = CAJA_WINDOW_SHOW_HIDDEN_FILES_ENABLE;
+ }
+ else
+ {
+ mode = CAJA_WINDOW_SHOW_HIDDEN_FILES_DISABLE;
+ }
+
+ caja_window_info_set_hidden_files_mode (window, mode);
+}
+
+static void
+show_hidden_files_preference_callback (gpointer callback_data)
+{
+ CajaWindow *window;
+ GtkAction *action;
+
+ window = CAJA_WINDOW (callback_data);
+
+ if (window->details->show_hidden_files_mode == CAJA_WINDOW_SHOW_HIDDEN_FILES_DEFAULT)
+ {
+ action = gtk_action_group_get_action (window->details->main_action_group, CAJA_ACTION_SHOW_HIDDEN_FILES);
+ g_assert (GTK_IS_ACTION (action));
+
+ /* update button */
+ g_signal_handlers_block_by_func (action, action_show_hidden_files_callback, window);
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
+ eel_preferences_get_boolean (CAJA_PREFERENCES_SHOW_HIDDEN_FILES));
+ g_signal_handlers_unblock_by_func (action, action_show_hidden_files_callback, window);
+
+ /* inform views */
+ caja_window_info_set_hidden_files_mode (window, CAJA_WINDOW_SHOW_HIDDEN_FILES_DEFAULT);
+
+ }
+}
+
+static void
+preferences_respond_callback (GtkDialog *dialog,
+ gint response_id)
+{
+ if (response_id == GTK_RESPONSE_CLOSE)
+ {
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+ }
+}
+
+static void
+action_preferences_callback (GtkAction *action,
+ gpointer user_data)
+{
+ GtkWindow *window;
+
+ window = GTK_WINDOW (user_data);
+
+ caja_file_management_properties_dialog_show (G_CALLBACK (preferences_respond_callback), window);
+}
+
+static void
+action_backgrounds_and_emblems_callback (GtkAction *action,
+ gpointer user_data)
+{
+ GtkWindow *window;
+
+ window = GTK_WINDOW (user_data);
+
+ caja_property_browser_show (gtk_window_get_screen (window));
+}
+
+static void
+action_about_caja_callback (GtkAction *action,
+ gpointer user_data)
+{
+ const gchar *authors[] =
+ {
+ "Alexander Larsson",
+ "Ali Abdin",
+ "Anders Carlsson",
+ "Andy Hertzfeld",
+ "Arlo Rose",
+ "Darin Adler",
+ "David Camp",
+ "Eli Goldberg",
+ "Elliot Lee",
+ "Eskil Heyn Olsen",
+ "Ettore Perazzoli",
+ "Gene Z. Ragan",
+ "George Lebl",
+ "Ian McKellar",
+ "J Shane Culpepper",
+ "James Willcox",
+ "Jan Arne Petersen",
+ "John Harper",
+ "John Sullivan",
+ "Josh Barrow",
+ "Maciej Stachowiak",
+ "Mark McLoughlin",
+ "Mathieu Lacage",
+ "Mike Engber",
+ "Mike Fleming",
+ "Pavel Cisler",
+ "Ramiro Estrugo",
+ "Raph Levien",
+ "Rebecca Schulman",
+ "Robey Pointer",
+ "Robin * Slomkowski",
+ "Seth Nickell",
+ "Susan Kare",
+ NULL
+ };
+ const gchar *documenters[] =
+ {
+ "MATE Documentation Team",
+ "Sun Microsystem",
+ NULL
+ };
+ const gchar *license[] =
+ {
+ N_("Caja is free software; you can redistribute it and/or modify "
+ "it under the terms of the GNU General Public License as published by "
+ "the Free Software Foundation; either version 2 of the License, or "
+ "(at your option) any later version."),
+ N_("Caja is distributed in the hope that it will be useful, "
+ "but WITHOUT ANY WARRANTY; without even the implied warranty of "
+ "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the "
+ "GNU General Public License for more details."),
+ N_("You should have received a copy of the GNU General Public License "
+ "along with Caja; if not, write to the Free Software Foundation, Inc., "
+ "51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA")
+ };
+ gchar *license_trans;
+
+ license_trans = g_strjoin ("\n\n", _(license[0]), _(license[1]),
+ _(license[2]), NULL);
+
+ gtk_show_about_dialog (GTK_WINDOW (user_data),
+ "program-name", _("Caja"),
+ "version", VERSION,
+ "comments", _("Caja lets you organize "
+ "files and folders, both on "
+ "your computer and online."),
+ "copyright", _("Copyright \xC2\xA9 1999-2009 "
+ "The Caja authors"),
+ "license", license_trans,
+ "wrap-license", TRUE,
+ "authors", authors,
+ "documenters", documenters,
+ /* Translators should localize the following string
+ * which will be displayed at the bottom of the about
+ * box to give credit to the translator(s).
+ */
+ "translator-credits", _("translator-credits"),
+ "logo-icon-name", "caja",
+ "website", "https://github.com/Perberos/Mate-Desktop-Environment"
+ "/wiki/Mate-file-manager",
+ "website-label", _("Caja Web Site"),
+ NULL);
+
+ g_free (license_trans);
+
+}
+
+static void
+action_up_callback (GtkAction *action,
+ gpointer user_data)
+{
+ caja_window_go_up (CAJA_WINDOW (user_data), FALSE, should_open_in_new_tab ());
+}
+
+static void
+action_caja_manual_callback (GtkAction *action,
+ gpointer user_data)
+{
+ CajaWindow *window;
+ GError *error;
+ GtkWidget *dialog;
+
+ error = NULL;
+ window = CAJA_WINDOW (user_data);
+
+ if (CAJA_IS_DESKTOP_WINDOW (window))
+ {
+#if GTK_CHECK_VERSION(2, 24, 0)
+ gdk_spawn_command_line_on_screen(gtk_window_get_screen(GTK_WINDOW(window)), "mate-help", &error);
+#else
+
+
+
+ g_spawn_command_line_async("mate-help", &error);
+#endif
+
+ }
+ else
+ {
+ gtk_show_uri (gtk_window_get_screen (GTK_WINDOW (window)),
+ "ghelp:user-guide#goscaja-1",
+ gtk_get_current_event_time (), &error);
+ }
+
+ if (error)
+ {
+ dialog = gtk_message_dialog_new (GTK_WINDOW (window),
+ GTK_DIALOG_MODAL,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_OK,
+ _("There was an error displaying help: \n%s"),
+ error->message);
+ g_signal_connect (G_OBJECT (dialog), "response",
+ G_CALLBACK (gtk_widget_destroy),
+ NULL);
+
+ gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+ gtk_widget_show (dialog);
+ g_error_free (error);
+ }
+}
+
+static void
+menu_item_select_cb (GtkMenuItem *proxy,
+ CajaWindow *window)
+{
+ GtkAction *action;
+ char *message;
+
+ action = gtk_activatable_get_related_action (GTK_ACTIVATABLE (proxy));
+ g_return_if_fail (action != NULL);
+
+ g_object_get (G_OBJECT (action), "tooltip", &message, NULL);
+ if (message)
+ {
+ gtk_statusbar_push (GTK_STATUSBAR (window->details->statusbar),
+ window->details->help_message_cid, message);
+ g_free (message);
+ }
+}
+
+static void
+menu_item_deselect_cb (GtkMenuItem *proxy,
+ CajaWindow *window)
+{
+ gtk_statusbar_pop (GTK_STATUSBAR (window->details->statusbar),
+ window->details->help_message_cid);
+}
+
+static GtkWidget *
+get_event_widget (GtkWidget *proxy)
+{
+ GtkWidget *widget;
+
+ /**
+ * Finding the interesting widget requires internal knowledge of
+ * the widgets in question. This can't be helped, but by keeping
+ * the sneaky code in one place, it can easily be updated.
+ */
+ if (GTK_IS_MENU_ITEM (proxy))
+ {
+ /* Menu items already forward middle clicks */
+ widget = NULL;
+ }
+ else if (GTK_IS_MENU_TOOL_BUTTON (proxy))
+ {
+ widget = eel_gtk_menu_tool_button_get_button (GTK_MENU_TOOL_BUTTON (proxy));
+ }
+ else if (GTK_IS_TOOL_BUTTON (proxy))
+ {
+ /* The tool button's button is the direct child */
+ widget = gtk_bin_get_child (GTK_BIN (proxy));
+ }
+ else if (GTK_IS_BUTTON (proxy))
+ {
+ widget = proxy;
+ }
+ else
+ {
+ /* Don't touch anything we don't know about */
+ widget = NULL;
+ }
+
+ return widget;
+}
+
+static gboolean
+proxy_button_press_event_cb (GtkButton *button,
+ GdkEventButton *event,
+ gpointer user_data)
+{
+ if (event->button == 2)
+ {
+ g_signal_emit_by_name (button, "pressed", 0);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+proxy_button_release_event_cb (GtkButton *button,
+ GdkEventButton *event,
+ gpointer user_data)
+{
+ if (event->button == 2)
+ {
+ g_signal_emit_by_name (button, "released", 0);
+ }
+
+ return FALSE;
+}
+
+static void
+disconnect_proxy_cb (GtkUIManager *manager,
+ GtkAction *action,
+ GtkWidget *proxy,
+ CajaWindow *window)
+{
+ GtkWidget *widget;
+
+ if (GTK_IS_MENU_ITEM (proxy))
+ {
+ g_signal_handlers_disconnect_by_func
+ (proxy, G_CALLBACK (menu_item_select_cb), window);
+ g_signal_handlers_disconnect_by_func
+ (proxy, G_CALLBACK (menu_item_deselect_cb), window);
+ }
+
+ widget = get_event_widget (proxy);
+ if (widget)
+ {
+ g_signal_handlers_disconnect_by_func (widget,
+ G_CALLBACK (proxy_button_press_event_cb),
+ action);
+ g_signal_handlers_disconnect_by_func (widget,
+ G_CALLBACK (proxy_button_release_event_cb),
+ action);
+ }
+
+}
+
+static void
+connect_proxy_cb (GtkUIManager *manager,
+ GtkAction *action,
+ GtkWidget *proxy,
+ CajaWindow *window)
+{
+ GdkPixbuf *icon;
+ GtkWidget *widget;
+
+ if (GTK_IS_MENU_ITEM (proxy))
+ {
+ g_signal_connect (proxy, "select",
+ G_CALLBACK (menu_item_select_cb), window);
+ g_signal_connect (proxy, "deselect",
+ G_CALLBACK (menu_item_deselect_cb), window);
+
+
+ /* This is a way to easily get pixbufs into the menu items */
+ icon = g_object_get_data (G_OBJECT (action), "menu-icon");
+ if (icon != NULL)
+ {
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (proxy),
+ gtk_image_new_from_pixbuf (icon));
+ }
+ }
+ if (GTK_IS_TOOL_BUTTON (proxy))
+ {
+ icon = g_object_get_data (G_OBJECT (action), "toolbar-icon");
+ if (icon != NULL)
+ {
+ widget = gtk_image_new_from_pixbuf (icon);
+ gtk_widget_show (widget);
+ gtk_tool_button_set_icon_widget (GTK_TOOL_BUTTON (proxy),
+ widget);
+ }
+ }
+
+ widget = get_event_widget (proxy);
+ if (widget)
+ {
+ g_signal_connect (widget, "button-press-event",
+ G_CALLBACK (proxy_button_press_event_cb),
+ action);
+ g_signal_connect (widget, "button-release-event",
+ G_CALLBACK (proxy_button_release_event_cb),
+ action);
+ }
+}
+
+static void
+trash_state_changed_cb (CajaTrashMonitor *monitor,
+ gboolean state,
+ CajaWindow *window)
+{
+ GtkActionGroup *action_group;
+ GtkAction *action;
+ GIcon *gicon;
+
+ action_group = window->details->main_action_group;
+ action = gtk_action_group_get_action (action_group, "Go to Trash");
+
+ gicon = caja_trash_monitor_get_icon ();
+
+ if (gicon)
+ {
+ g_object_set (action, "gicon", gicon, NULL);
+ g_object_unref (gicon);
+ }
+}
+
+static void
+caja_window_initialize_trash_icon_monitor (CajaWindow *window)
+{
+ CajaTrashMonitor *monitor;
+
+ monitor = caja_trash_monitor_get ();
+
+ trash_state_changed_cb (monitor, TRUE, window);
+
+ g_signal_connect (monitor, "trash_state_changed",
+ G_CALLBACK (trash_state_changed_cb), window);
+}
+
+static const GtkActionEntry main_entries[] =
+{
+ /* name, stock id, label */ { "File", NULL, N_("_File") },
+ /* name, stock id, label */ { "Edit", NULL, N_("_Edit") },
+ /* name, stock id, label */ { "View", NULL, N_("_View") },
+ /* name, stock id, label */ { "Help", NULL, N_("_Help") },
+ /* name, stock id */ { "Close", GTK_STOCK_CLOSE,
+ /* label, accelerator */ N_("_Close"), "<control>W",
+ /* tooltip */ N_("Close this folder"),
+ G_CALLBACK (action_close_window_slot_callback)
+ },
+ {
+ "Backgrounds and Emblems", NULL,
+ N_("_Backgrounds and Emblems..."),
+ NULL, N_("Display patterns, colors, and emblems that can be used to customize appearance"),
+ G_CALLBACK (action_backgrounds_and_emblems_callback)
+ },
+ {
+ "Preferences", GTK_STOCK_PREFERENCES,
+ N_("Prefere_nces"),
+ NULL, N_("Edit Caja preferences"),
+ G_CALLBACK (action_preferences_callback)
+ },
+ /* name, stock id, label */ { "Undo", NULL, N_("_Undo"),
+ "<control>Z", N_("Undo the last text change"),
+ G_CALLBACK (action_undo_callback)
+ },
+ /* name, stock id, label */ { "Up", GTK_STOCK_GO_UP, N_("Open _Parent"),
+ "<alt>Up", N_("Open the parent folder"),
+ G_CALLBACK (action_up_callback)
+ },
+ /* name, stock id, label */ { "UpAccel", NULL, "UpAccel",
+ "", NULL,
+ G_CALLBACK (action_up_callback)
+ },
+ /* name, stock id */ { "Stop", GTK_STOCK_STOP,
+ /* label, accelerator */ N_("_Stop"), NULL,
+ /* tooltip */ N_("Stop loading the current location"),
+ G_CALLBACK (action_stop_callback)
+ },
+ /* name, stock id */ { "Reload", GTK_STOCK_REFRESH,
+ /* label, accelerator */ N_("_Reload"), "<control>R",
+ /* tooltip */ N_("Reload the current location"),
+ G_CALLBACK (action_reload_callback)
+ },
+ /* name, stock id */ { "Caja Manual", GTK_STOCK_HELP,
+ /* label, accelerator */ N_("_Contents"), "F1",
+ /* tooltip */ N_("Display Caja help"),
+ G_CALLBACK (action_caja_manual_callback)
+ },
+ /* name, stock id */ { "About Caja", GTK_STOCK_ABOUT,
+ /* label, accelerator */ N_("_About"), NULL,
+ /* tooltip */ N_("Display credits for the creators of Caja"),
+ G_CALLBACK (action_about_caja_callback)
+ },
+ /* name, stock id */ { "Zoom In", GTK_STOCK_ZOOM_IN,
+ /* label, accelerator */ N_("Zoom _In"), "<control>plus",
+ /* tooltip */ N_("Increase the view size"),
+ G_CALLBACK (action_zoom_in_callback)
+ },
+ /* name, stock id */ { "ZoomInAccel", NULL,
+ /* label, accelerator */ "ZoomInAccel", "<control>equal",
+ /* tooltip */ NULL,
+ G_CALLBACK (action_zoom_in_callback)
+ },
+ /* name, stock id */ { "ZoomInAccel2", NULL,
+ /* label, accelerator */ "ZoomInAccel2", "<control>KP_Add",
+ /* tooltip */ NULL,
+ G_CALLBACK (action_zoom_in_callback)
+ },
+ /* name, stock id */ { "Zoom Out", GTK_STOCK_ZOOM_OUT,
+ /* label, accelerator */ N_("Zoom _Out"), "<control>minus",
+ /* tooltip */ N_("Decrease the view size"),
+ G_CALLBACK (action_zoom_out_callback)
+ },
+ /* name, stock id */ { "ZoomOutAccel", NULL,
+ /* label, accelerator */ "ZoomOutAccel", "<control>KP_Subtract",
+ /* tooltip */ NULL,
+ G_CALLBACK (action_zoom_out_callback)
+ },
+ /* name, stock id */ { "Zoom Normal", GTK_STOCK_ZOOM_100,
+ /* label, accelerator */ N_("Normal Si_ze"), "<control>0",
+ /* tooltip */ N_("Use the normal view size"),
+ G_CALLBACK (action_zoom_normal_callback)
+ },
+ /* name, stock id */ { "Connect to Server", NULL,
+ /* label, accelerator */ N_("Connect to _Server..."), NULL,
+ /* tooltip */ N_("Connect to a remote computer or shared disk"),
+ G_CALLBACK (action_connect_to_server_callback)
+ },
+ /* name, stock id */ { "Home", CAJA_ICON_HOME,
+ /* label, accelerator */ N_("_Home Folder"), "<alt>Home",
+ /* tooltip */ N_("Open your personal folder"),
+ G_CALLBACK (action_home_callback)
+ },
+ /* name, stock id */ { "Go to Computer", CAJA_ICON_COMPUTER,
+ /* label, accelerator */ N_("_Computer"), NULL,
+ /* tooltip */ N_("Browse all local and remote disks and folders accessible from this computer"),
+ G_CALLBACK (action_go_to_computer_callback)
+ },
+ /* name, stock id */ { "Go to Network", CAJA_ICON_NETWORK,
+ /* label, accelerator */ N_("_Network"), NULL,
+ /* tooltip */ N_("Browse bookmarked and local network locations"),
+ G_CALLBACK (action_go_to_network_callback)
+ },
+ /* name, stock id */ { "Go to Templates", CAJA_ICON_TEMPLATE,
+ /* label, accelerator */ N_("T_emplates"), NULL,
+ /* tooltip */ N_("Open your personal templates folder"),
+ G_CALLBACK (action_go_to_templates_callback)
+ },
+ /* name, stock id */ { "Go to Trash", CAJA_ICON_TRASH,
+ /* label, accelerator */ N_("_Trash"), NULL,
+ /* tooltip */ N_("Open your personal trash folder"),
+ G_CALLBACK (action_go_to_trash_callback)
+ },
+};
+
+static const GtkToggleActionEntry main_toggle_entries[] =
+{
+ /* name, stock id */ { "Show Hidden Files", NULL,
+ /* label, accelerator */ N_("Show _Hidden Files"), "<control>H",
+ /* tooltip */ N_("Toggle the display of hidden files in the current window"),
+ G_CALLBACK (action_show_hidden_files_callback),
+ TRUE
+ },
+};
+
+/**
+ * caja_window_initialize_menus
+ *
+ * Create and install the set of menus for this window.
+ * @window: A recently-created CajaWindow.
+ */
+void
+caja_window_initialize_menus (CajaWindow *window)
+{
+ GtkActionGroup *action_group;
+ GtkUIManager *ui_manager;
+ GtkAction *action;
+ const char *ui;
+
+ action_group = gtk_action_group_new ("ShellActions");
+ gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
+ window->details->main_action_group = action_group;
+ gtk_action_group_add_actions (action_group,
+ main_entries, G_N_ELEMENTS (main_entries),
+ window);
+ gtk_action_group_add_toggle_actions (action_group,
+ main_toggle_entries, G_N_ELEMENTS (main_toggle_entries),
+ window);
+
+ action = gtk_action_group_get_action (action_group, CAJA_ACTION_UP);
+ g_object_set (action, "short_label", _("_Up"), NULL);
+
+ action = gtk_action_group_get_action (action_group, CAJA_ACTION_HOME);
+ g_object_set (action, "short_label", _("_Home"), NULL);
+
+ action = gtk_action_group_get_action (action_group, CAJA_ACTION_SHOW_HIDDEN_FILES);
+ g_signal_handlers_block_by_func (action, action_show_hidden_files_callback, window);
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
+ eel_preferences_get_boolean (CAJA_PREFERENCES_SHOW_HIDDEN_FILES));
+ g_signal_handlers_unblock_by_func (action, action_show_hidden_files_callback, window);
+
+
+ eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_SHOW_HIDDEN_FILES,
+ show_hidden_files_preference_callback,
+ window, G_OBJECT (window));
+
+ window->details->ui_manager = gtk_ui_manager_new ();
+ ui_manager = window->details->ui_manager;
+ gtk_window_add_accel_group (GTK_WINDOW (window),
+ gtk_ui_manager_get_accel_group (ui_manager));
+
+ g_signal_connect (ui_manager, "connect_proxy",
+ G_CALLBACK (connect_proxy_cb), window);
+ g_signal_connect (ui_manager, "disconnect_proxy",
+ G_CALLBACK (disconnect_proxy_cb), window);
+
+ 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-shell-ui.xml");
+ gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, NULL);
+
+ caja_window_initialize_trash_icon_monitor (window);
+}
+
+static GList *
+get_extension_menus (CajaWindow *window)
+{
+ CajaWindowSlot *slot;
+ GList *providers;
+ GList *items;
+ GList *l;
+
+ providers = caja_module_get_extensions_for_type (CAJA_TYPE_MENU_PROVIDER);
+ items = NULL;
+
+ slot = caja_window_get_active_slot (window);
+
+ 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_background_items (provider,
+ GTK_WIDGET (window),
+ slot->viewed_file);
+ items = g_list_concat (items, file_items);
+ }
+
+ caja_module_extension_list_free (providers);
+
+ return items;
+}
+
+static void
+add_extension_menu_items (CajaWindow *window,
+ guint merge_id,
+ GtkActionGroup *action_group,
+ GList *menu_items,
+ const char *subdirectory)
+{
+ GtkUIManager *ui_manager;
+ GList *l;
+
+ ui_manager = window->details->ui_manager;
+
+ 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 = caja_action_from_menu_item (item);
+ gtk_action_group_add_action_with_accel (action_group, action, NULL);
+
+ path = g_build_path ("/", POPUP_PATH_EXTENSION_ACTIONS, subdirectory, NULL);
+ gtk_ui_manager_add_ui (ui_manager,
+ 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 ("/", MENU_PATH_EXTENSION_ACTIONS, subdirectory, NULL);
+ gtk_ui_manager_add_ui (ui_manager,
+ 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 (window,
+ merge_id,
+ action_group,
+ children,
+ subdir);
+
+ caja_menu_item_list_free (children);
+ g_free (subdir);
+ }
+ }
+}
+
+void
+caja_window_load_extension_menus (CajaWindow *window)
+{
+ GtkActionGroup *action_group;
+ GList *items;
+ guint merge_id;
+
+ if (window->details->extensions_menu_merge_id != 0)
+ {
+ gtk_ui_manager_remove_ui (window->details->ui_manager,
+ window->details->extensions_menu_merge_id);
+ window->details->extensions_menu_merge_id = 0;
+ }
+
+ if (window->details->extensions_menu_action_group != NULL)
+ {
+ gtk_ui_manager_remove_action_group (window->details->ui_manager,
+ window->details->extensions_menu_action_group);
+ window->details->extensions_menu_action_group = NULL;
+ }
+
+ merge_id = gtk_ui_manager_new_merge_id (window->details->ui_manager);
+ window->details->extensions_menu_merge_id = merge_id;
+ action_group = gtk_action_group_new ("ExtensionsMenuGroup");
+ window->details->extensions_menu_action_group = action_group;
+ gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
+ gtk_ui_manager_insert_action_group (window->details->ui_manager, action_group, 0);
+ g_object_unref (action_group); /* owned by ui manager */
+
+ items = get_extension_menus (window);
+
+ if (items != NULL)
+ {
+ add_extension_menu_items (window, merge_id, action_group, items, "");
+
+ g_list_foreach (items, (GFunc) g_object_unref, NULL);
+ g_list_free (items);
+ }
+}
+
+void
+caja_window_remove_trash_monitor_callback (CajaWindow *window)
+{
+ CajaTrashMonitor *monitor;
+
+ monitor = caja_trash_monitor_get ();
+
+ g_signal_handlers_disconnect_by_func (monitor,
+ trash_state_changed_cb, window);
+}
+
diff --git a/src/caja-window-pane.c b/src/caja-window-pane.c
new file mode 100644
index 00000000..fde98201
--- /dev/null
+++ b/src/caja-window-pane.c
@@ -0,0 +1,305 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-window-pane.c: Caja window pane
+
+ Copyright (C) 2008 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with this program; if not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Holger Berndt <[email protected]>
+*/
+
+#include "caja-window-pane.h"
+#include "caja-window-private.h"
+#include "caja-navigation-window-pane.h"
+#include "caja-window-manage-views.h"
+#include <eel/eel-gtk-macros.h>
+
+static void caja_window_pane_init (CajaWindowPane *pane);
+static void caja_window_pane_class_init (CajaWindowPaneClass *class);
+static void caja_window_pane_dispose (GObject *object);
+
+G_DEFINE_TYPE (CajaWindowPane,
+ caja_window_pane,
+ G_TYPE_OBJECT)
+#define parent_class caja_window_pane_parent_class
+
+
+static inline CajaWindowSlot *
+get_first_inactive_slot (CajaWindowPane *pane)
+{
+ GList *l;
+ CajaWindowSlot *slot;
+
+ for (l = pane->slots; l != NULL; l = l->next)
+ {
+ slot = CAJA_WINDOW_SLOT (l->data);
+ if (slot != pane->active_slot)
+ {
+ return slot;
+ }
+ }
+
+ return NULL;
+}
+
+void
+caja_window_pane_show (CajaWindowPane *pane)
+{
+ pane->visible = TRUE;
+ EEL_CALL_METHOD (CAJA_WINDOW_PANE_CLASS, pane,
+ show, (pane));
+}
+
+void
+caja_window_pane_zoom_in (CajaWindowPane *pane)
+{
+ CajaWindowSlot *slot;
+
+ g_assert (pane != NULL);
+
+ caja_window_set_active_pane (pane->window, pane);
+
+ slot = pane->active_slot;
+ if (slot->content_view != NULL)
+ {
+ caja_view_bump_zoom_level (slot->content_view, 1);
+ }
+}
+
+void
+caja_window_pane_zoom_to_level (CajaWindowPane *pane,
+ CajaZoomLevel level)
+{
+ CajaWindowSlot *slot;
+
+ g_assert (pane != NULL);
+
+ caja_window_set_active_pane (pane->window, pane);
+
+ slot = pane->active_slot;
+ if (slot->content_view != NULL)
+ {
+ caja_view_zoom_to_level (slot->content_view, level);
+ }
+}
+
+void
+caja_window_pane_zoom_out (CajaWindowPane *pane)
+{
+ CajaWindowSlot *slot;
+
+ g_assert (pane != NULL);
+
+ caja_window_set_active_pane (pane->window, pane);
+
+ slot = pane->active_slot;
+ if (slot->content_view != NULL)
+ {
+ caja_view_bump_zoom_level (slot->content_view, -1);
+ }
+}
+
+void
+caja_window_pane_zoom_to_default (CajaWindowPane *pane)
+{
+ CajaWindowSlot *slot;
+
+ g_assert (pane != NULL);
+
+ caja_window_set_active_pane (pane->window, pane);
+
+ slot = pane->active_slot;
+ if (slot->content_view != NULL)
+ {
+ caja_view_restore_default_zoom_level (slot->content_view);
+ }
+}
+
+void
+caja_window_pane_slot_close (CajaWindowPane *pane, CajaWindowSlot *slot)
+{
+ CajaWindowSlot *next_slot;
+
+ if (pane->window)
+ {
+ CajaWindow *window;
+ window = pane->window;
+ if (pane->active_slot == slot)
+ {
+ g_assert (pane->active_slots != NULL);
+ g_assert (pane->active_slots->data == slot);
+
+ next_slot = NULL;
+ if (pane->active_slots->next != NULL)
+ {
+ next_slot = CAJA_WINDOW_SLOT (pane->active_slots->next->data);
+ }
+
+ if (next_slot == NULL)
+ {
+ next_slot = get_first_inactive_slot (CAJA_WINDOW_PANE (pane));
+ }
+
+ caja_window_set_active_slot (window, next_slot);
+ }
+ caja_window_close_slot (slot);
+
+ /* If that was the last slot in the active pane, close the pane or even the whole window. */
+ if (window->details->active_pane->slots == NULL)
+ {
+ CajaWindowPane *next_pane;
+ next_pane = caja_window_get_next_pane (window);
+
+ /* If next_pane is non-NULL, we have more than one pane available. In this
+ * case, close the current pane and switch to the next one. If there is
+ * no next pane, close the window. */
+ if(next_pane)
+ {
+ caja_window_close_pane (pane);
+ caja_window_pane_switch_to (next_pane);
+ if (CAJA_IS_NAVIGATION_WINDOW (window))
+ {
+ caja_navigation_window_update_show_hide_menu_items (CAJA_NAVIGATION_WINDOW (window));
+ }
+ }
+ else
+ {
+ caja_window_close (window);
+ }
+ }
+ }
+}
+
+static void
+real_sync_location_widgets (CajaWindowPane *pane)
+{
+ CajaWindowSlot *slot;
+
+ /* TODO: Would be nice with a real subclass for spatial panes */
+ g_assert (CAJA_IS_SPATIAL_WINDOW (pane->window));
+
+ slot = pane->active_slot;
+
+ /* Change the location button to match the current location. */
+ caja_spatial_window_set_location_button (CAJA_SPATIAL_WINDOW (pane->window),
+ slot->location);
+}
+
+
+void
+caja_window_pane_sync_location_widgets (CajaWindowPane *pane)
+{
+ EEL_CALL_METHOD (CAJA_WINDOW_PANE_CLASS, pane,
+ sync_location_widgets, (pane));
+}
+
+void
+caja_window_pane_sync_search_widgets (CajaWindowPane *pane)
+{
+ g_assert (CAJA_IS_WINDOW_PANE (pane));
+
+ EEL_CALL_METHOD (CAJA_WINDOW_PANE_CLASS, pane,
+ sync_search_widgets, (pane));
+}
+
+void
+caja_window_pane_grab_focus (CajaWindowPane *pane)
+{
+ if (CAJA_IS_WINDOW_PANE (pane) && pane->active_slot)
+ {
+ caja_view_grab_focus (pane->active_slot->content_view);
+ }
+}
+
+void
+caja_window_pane_switch_to (CajaWindowPane *pane)
+{
+ caja_window_pane_grab_focus (pane);
+}
+
+static void
+caja_window_pane_init (CajaWindowPane *pane)
+{
+ pane->slots = NULL;
+ pane->active_slots = NULL;
+ pane->active_slot = NULL;
+ pane->is_active = FALSE;
+}
+
+void
+caja_window_pane_set_active (CajaWindowPane *pane, gboolean is_active)
+{
+ if (is_active == pane->is_active)
+ {
+ return;
+ }
+
+ pane->is_active = is_active;
+
+ /* notify the current slot about its activity state (so that it can e.g. modify the bg color) */
+ caja_window_slot_is_in_active_pane (pane->active_slot, is_active);
+
+ EEL_CALL_METHOD (CAJA_WINDOW_PANE_CLASS, pane,
+ set_active, (pane, is_active));
+}
+
+static void
+caja_window_pane_class_init (CajaWindowPaneClass *class)
+{
+ G_OBJECT_CLASS (class)->dispose = caja_window_pane_dispose;
+ CAJA_WINDOW_PANE_CLASS (class)->sync_location_widgets = real_sync_location_widgets;
+}
+
+static void
+caja_window_pane_dispose (GObject *object)
+{
+ CajaWindowPane *pane = CAJA_WINDOW_PANE (object);
+
+ g_assert (pane->slots == NULL);
+
+ pane->window = NULL;
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+CajaWindowPane *
+caja_window_pane_new (CajaWindow *window)
+{
+ CajaWindowPane *pane;
+
+ pane = g_object_new (CAJA_TYPE_WINDOW_PANE, NULL);
+ pane->window = window;
+ return pane;
+}
+
+CajaWindowSlot *
+caja_window_pane_get_slot_for_content_box (CajaWindowPane *pane,
+ GtkWidget *content_box)
+{
+ CajaWindowSlot *slot;
+ GList *l;
+
+ for (l = pane->slots; l != NULL; l = l->next)
+ {
+ slot = CAJA_WINDOW_SLOT (l->data);
+
+ if (slot->content_box == content_box)
+ {
+ return slot;
+ }
+ }
+ return NULL;
+}
diff --git a/src/caja-window-pane.h b/src/caja-window-pane.h
new file mode 100644
index 00000000..2aecb478
--- /dev/null
+++ b/src/caja-window-pane.h
@@ -0,0 +1,97 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-window-pane.h: Caja window pane
+
+ Copyright (C) 2008 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with this program; if not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Holger Berndt <[email protected]>
+*/
+
+#ifndef CAJA_WINDOW_PANE_H
+#define CAJA_WINDOW_PANE_H
+
+#include "caja-window.h"
+
+#define CAJA_TYPE_WINDOW_PANE (caja_window_pane_get_type())
+#define CAJA_WINDOW_PANE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), CAJA_TYPE_WINDOW_PANE, CajaWindowPaneClass))
+#define CAJA_WINDOW_PANE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_WINDOW_PANE, CajaWindowPane))
+#define CAJA_IS_WINDOW_PANE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_WINDOW_PANE))
+#define CAJA_IS_WINDOW_PANE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), CAJA_TYPE_WINDOW_PANE))
+#define CAJA_WINDOW_PANE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), CAJA_TYPE_WINDOW_PANE, CajaWindowPaneClass))
+
+typedef struct _CajaWindowPaneClass CajaWindowPaneClass;
+
+struct _CajaWindowPaneClass
+{
+ GObjectClass parent_class;
+
+ void (*show) (CajaWindowPane *pane);
+ void (*set_active) (CajaWindowPane *pane,
+ gboolean is_active);
+ void (*sync_search_widgets) (CajaWindowPane *pane);
+ void (*sync_location_widgets) (CajaWindowPane *pane);
+};
+
+/* A CajaWindowPane is a layer between a slot and a window.
+ * Each slot is contained in one pane, and each pane can contain
+ * one or more slots. It also supports the notion of an "active slot".
+ * On the other hand, each pane is contained in a window, while each
+ * window can contain one or multiple panes. Likewise, the window has
+ * the notion of an "active pane".
+ *
+ * A spatial window has only one pane, which contains a single slot.
+ * A navigation window may have one or more panes.
+ */
+struct _CajaWindowPane
+{
+ GObject parent;
+
+ /* hosting window */
+ CajaWindow *window;
+ gboolean visible;
+
+ /* available slots, and active slot.
+ * Both of them may never be NULL. */
+ GList *slots;
+ GList *active_slots;
+ CajaWindowSlot *active_slot;
+
+ /* whether or not this pane is active */
+ gboolean is_active;
+};
+
+GType caja_window_pane_get_type (void);
+CajaWindowPane *caja_window_pane_new (CajaWindow *window);
+
+
+void caja_window_pane_show (CajaWindowPane *pane);
+void caja_window_pane_zoom_in (CajaWindowPane *pane);
+void caja_window_pane_zoom_to_level (CajaWindowPane *pane, CajaZoomLevel level);
+void caja_window_pane_zoom_out (CajaWindowPane *pane);
+void caja_window_pane_zoom_to_default (CajaWindowPane *pane);
+void caja_window_pane_sync_location_widgets (CajaWindowPane *pane);
+void caja_window_pane_sync_search_widgets (CajaWindowPane *pane);
+void caja_window_pane_set_active (CajaWindowPane *pane, gboolean is_active);
+void caja_window_pane_slot_close (CajaWindowPane *pane, CajaWindowSlot *slot);
+
+CajaWindowSlot* caja_window_pane_get_slot_for_content_box (CajaWindowPane *pane, GtkWidget *content_box);
+void caja_window_pane_switch_to (CajaWindowPane *pane);
+void caja_window_pane_grab_focus (CajaWindowPane *pane);
+
+
+#endif /* CAJA_WINDOW_PANE_H */
diff --git a/src/caja-window-private.h b/src/caja-window-private.h
new file mode 100644
index 00000000..62c2b3ba
--- /dev/null
+++ b/src/caja-window-private.h
@@ -0,0 +1,250 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 1999, 2000 Red Hat, Inc.
+ * Copyright (C) 1999, 2000, 2001 Eazel, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Authors: Elliot Lee <[email protected]>
+ * Darin Adler <[email protected]>
+ *
+ */
+
+#ifndef CAJA_WINDOW_PRIVATE_H
+#define CAJA_WINDOW_PRIVATE_H
+
+#include "caja-window.h"
+#include "caja-window-slot.h"
+#include "caja-window-pane.h"
+#include "caja-spatial-window.h"
+#include "caja-navigation-window.h"
+
+#include <libcaja-private/caja-directory.h>
+
+struct _CajaNavigationWindowPane;
+
+/* FIXME bugzilla.gnome.org 42575: Migrate more fields into here. */
+struct CajaWindowDetails
+{
+ GtkWidget *table;
+ GtkWidget *statusbar;
+ GtkWidget *menubar;
+
+ GtkUIManager *ui_manager;
+ GtkActionGroup *main_action_group; /* owned by ui_manager */
+ guint help_message_cid;
+
+ /* Menus. */
+ guint extensions_menu_merge_id;
+ GtkActionGroup *extensions_menu_action_group;
+
+ GtkActionGroup *bookmarks_action_group;
+ guint bookmarks_merge_id;
+ CajaBookmarkList *bookmark_list;
+
+ CajaWindowShowHiddenFilesMode show_hidden_files_mode;
+
+ /* View As menu */
+ GList *short_list_viewers;
+ char *extra_viewer;
+
+ /* View As choices */
+ GtkActionGroup *view_as_action_group; /* owned by ui_manager */
+ GtkRadioAction *view_as_radio_action;
+ GtkRadioAction *extra_viewer_radio_action;
+ guint short_list_merge_id;
+ guint extra_viewer_merge_id;
+
+ /* Ensures that we do not react on signals of a
+ * view that is re-used as new view when its loading
+ * is cancelled
+ */
+ gboolean temporarily_ignore_view_signals;
+
+ /* available panes, and active pane.
+ * Both of them may never be NULL.
+ */
+ GList *panes;
+ CajaWindowPane *active_pane;
+
+ /* So we can tell which window initiated
+ * an unmount operation.
+ */
+ gboolean initiated_unmount;
+};
+
+struct _CajaNavigationWindowDetails
+{
+ GtkWidget *content_paned;
+ GtkWidget *content_box;
+ GtkActionGroup *navigation_action_group; /* owned by ui_manager */
+
+ GtkSizeGroup *header_size_group;
+
+ /* Side Pane */
+ int side_pane_width;
+ CajaSidebar *current_side_panel;
+
+ /* Menus */
+ GtkActionGroup *go_menu_action_group;
+ guint refresh_go_menu_idle_id;
+ guint go_menu_merge_id;
+
+ /* Toolbar */
+ GtkWidget *toolbar;
+
+ guint extensions_toolbar_merge_id;
+ GtkActionGroup *extensions_toolbar_action_group;
+
+ /* spinner */
+ gboolean spinner_active;
+ GtkWidget *spinner;
+
+ /* focus widget before the location bar has been shown temporarily */
+ GtkWidget *last_focus_widget;
+
+ /* split view */
+ GtkWidget *split_view_hpane;
+};
+
+#define CAJA_MENU_PATH_BACK_ITEM "/menu/Go/Back"
+#define CAJA_MENU_PATH_FORWARD_ITEM "/menu/Go/Forward"
+#define CAJA_MENU_PATH_UP_ITEM "/menu/Go/Up"
+
+#define CAJA_MENU_PATH_RELOAD_ITEM "/menu/View/Reload"
+#define CAJA_MENU_PATH_ZOOM_IN_ITEM "/menu/View/Zoom Items Placeholder/Zoom In"
+#define CAJA_MENU_PATH_ZOOM_OUT_ITEM "/menu/View/Zoom Items Placeholder/Zoom Out"
+#define CAJA_MENU_PATH_ZOOM_NORMAL_ITEM "/menu/View/Zoom Items Placeholder/Zoom Normal"
+
+#define CAJA_COMMAND_BACK "/commands/Back"
+#define CAJA_COMMAND_FORWARD "/commands/Forward"
+#define CAJA_COMMAND_UP "/commands/Up"
+
+#define CAJA_COMMAND_RELOAD "/commands/Reload"
+#define CAJA_COMMAND_BURN_CD "/commands/Burn CD"
+#define CAJA_COMMAND_STOP "/commands/Stop"
+#define CAJA_COMMAND_ZOOM_IN "/commands/Zoom In"
+#define CAJA_COMMAND_ZOOM_OUT "/commands/Zoom Out"
+#define CAJA_COMMAND_ZOOM_NORMAL "/commands/Zoom Normal"
+
+/* window geometry */
+/* Min values are very small, and a Caja window at this tiny size is *almost*
+ * completely unusable. However, if all the extra bits (sidebar, location bar, etc)
+ * are turned off, you can see an icon or two at this size. See bug 5946.
+ */
+
+#define CAJA_SPATIAL_WINDOW_MIN_WIDTH 100
+#define CAJA_SPATIAL_WINDOW_MIN_HEIGHT 100
+#define CAJA_SPATIAL_WINDOW_DEFAULT_WIDTH 500
+#define CAJA_SPATIAL_WINDOW_DEFAULT_HEIGHT 300
+
+#define CAJA_NAVIGATION_WINDOW_MIN_WIDTH 200
+#define CAJA_NAVIGATION_WINDOW_MIN_HEIGHT 200
+#define CAJA_NAVIGATION_WINDOW_DEFAULT_WIDTH 800
+#define CAJA_NAVIGATION_WINDOW_DEFAULT_HEIGHT 550
+
+typedef void (*CajaBookmarkFailedCallback) (CajaWindow *window,
+ CajaBookmark *bookmark);
+
+void caja_window_set_status (CajaWindow *window,
+ CajaWindowSlot *slot,
+ const char *status);
+void caja_window_load_view_as_menus (CajaWindow *window);
+void caja_window_load_extension_menus (CajaWindow *window);
+void caja_window_initialize_menus (CajaWindow *window);
+void caja_window_remove_trash_monitor_callback (CajaWindow *window);
+CajaWindowPane *caja_window_get_next_pane (CajaWindow *window);
+void caja_menus_append_bookmark_to_menu (CajaWindow *window,
+ CajaBookmark *bookmark,
+ const char *parent_path,
+ const char *parent_id,
+ guint index_in_parent,
+ GtkActionGroup *action_group,
+ guint merge_id,
+ GCallback refresh_callback,
+ CajaBookmarkFailedCallback failed_callback);
+#ifdef NEW_UI_COMPLETE
+void caja_window_go_up (CajaWindow *window);
+#endif
+void caja_window_update_find_menu_item (CajaWindow *window);
+void caja_window_zoom_in (CajaWindow *window);
+void caja_window_zoom_out (CajaWindow *window);
+void caja_window_zoom_to_level (CajaWindow *window,
+ CajaZoomLevel level);
+void caja_window_zoom_to_default (CajaWindow *window);
+
+CajaWindowSlot *caja_window_open_slot (CajaWindowPane *pane,
+ CajaWindowOpenSlotFlags flags);
+void caja_window_close_slot (CajaWindowSlot *slot);
+
+CajaWindowSlot *caja_window_get_slot_for_view (CajaWindow *window,
+ CajaView *view);
+
+GList * caja_window_get_slots (CajaWindow *window);
+CajaWindowSlot * caja_window_get_active_slot (CajaWindow *window);
+CajaWindowSlot * caja_window_get_extra_slot (CajaWindow *window);
+void caja_window_set_active_slot (CajaWindow *window,
+ CajaWindowSlot *slot);
+void caja_window_set_active_pane (CajaWindow *window,
+ CajaWindowPane *new_pane);
+CajaWindowPane * caja_window_get_active_pane (CajaWindow *window);
+
+void caja_send_history_list_changed (void);
+void caja_remove_from_history_list_no_notify (GFile *location);
+gboolean caja_add_bookmark_to_history_list (CajaBookmark *bookmark);
+gboolean caja_add_to_history_list_no_notify (GFile *location,
+ const char *name,
+ gboolean has_custom_name,
+ GIcon *icon);
+GList * caja_get_history_list (void);
+void caja_window_bookmarks_preference_changed_callback (gpointer user_data);
+
+
+/* sync window GUI with current slot. Used when changing slots,
+ * and when updating the slot state.
+ */
+void caja_window_sync_status (CajaWindow *window);
+void caja_window_sync_allow_stop (CajaWindow *window,
+ CajaWindowSlot *slot);
+void caja_window_sync_title (CajaWindow *window,
+ CajaWindowSlot *slot);
+void caja_window_sync_zoom_widgets (CajaWindow *window);
+
+/* Navigation window menus */
+void caja_navigation_window_initialize_actions (CajaNavigationWindow *window);
+void caja_navigation_window_initialize_menus (CajaNavigationWindow *window);
+void caja_navigation_window_remove_bookmarks_menu_callback (CajaNavigationWindow *window);
+
+void caja_navigation_window_remove_bookmarks_menu_items (CajaNavigationWindow *window);
+void caja_navigation_window_update_show_hide_menu_items (CajaNavigationWindow *window);
+void caja_navigation_window_update_spatial_menu_item (CajaNavigationWindow *window);
+void caja_navigation_window_remove_go_menu_callback (CajaNavigationWindow *window);
+void caja_navigation_window_remove_go_menu_items (CajaNavigationWindow *window);
+
+/* Navigation window toolbar */
+void caja_navigation_window_activate_spinner (CajaNavigationWindow *window);
+void caja_navigation_window_initialize_toolbars (CajaNavigationWindow *window);
+void caja_navigation_window_load_extension_toolbar_items (CajaNavigationWindow *window);
+void caja_navigation_window_set_spinner_active (CajaNavigationWindow *window,
+ gboolean active);
+void caja_navigation_window_go_back (CajaNavigationWindow *window);
+void caja_navigation_window_go_forward (CajaNavigationWindow *window);
+void caja_window_close_pane (CajaWindowPane *pane);
+void caja_navigation_window_update_split_view_actions_sensitivity (CajaNavigationWindow *window);
+
+#endif /* CAJA_WINDOW_PRIVATE_H */
diff --git a/src/caja-window-slot.c b/src/caja-window-slot.c
new file mode 100644
index 00000000..61355af2
--- /dev/null
+++ b/src/caja-window-slot.c
@@ -0,0 +1,710 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-window-slot.c: Caja window slot
+
+ Copyright (C) 2008 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with this program; if not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Christian Neumair <[email protected]>
+*/
+#include "caja-window-slot.h"
+#include "caja-navigation-window-slot.h"
+
+#include "caja-desktop-window.h"
+#include "caja-window-private.h"
+#include "caja-window-manage-views.h"
+#include <libcaja-private/caja-file.h>
+#include <libcaja-private/caja-file-utilities.h>
+#include <libcaja-private/caja-window-slot-info.h>
+#include <eel/eel-gtk-macros.h>
+#include <eel/eel-string.h>
+
+static void caja_window_slot_init (CajaWindowSlot *slot);
+static void caja_window_slot_class_init (CajaWindowSlotClass *class);
+static void caja_window_slot_dispose (GObject *object);
+
+static void caja_window_slot_info_iface_init (CajaWindowSlotInfoIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (CajaWindowSlot,
+ caja_window_slot,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (CAJA_TYPE_WINDOW_SLOT_INFO,
+ caja_window_slot_info_iface_init))
+#define parent_class caja_window_slot_parent_class
+
+static void
+query_editor_changed_callback (CajaSearchBar *bar,
+ CajaQuery *query,
+ gboolean reload,
+ CajaWindowSlot *slot)
+{
+ CajaDirectory *directory;
+
+ directory = caja_directory_get_for_file (slot->viewed_file);
+ g_assert (CAJA_IS_SEARCH_DIRECTORY (directory));
+
+ caja_search_directory_set_query (CAJA_SEARCH_DIRECTORY (directory),
+ query);
+ if (reload)
+ {
+ caja_window_slot_reload (slot);
+ }
+
+ caja_directory_unref (directory);
+}
+
+static void
+real_update_query_editor (CajaWindowSlot *slot)
+{
+ GtkWidget *query_editor;
+ CajaQuery *query;
+ CajaDirectory *directory;
+ CajaSearchDirectory *search_directory;
+
+ directory = caja_directory_get (slot->location);
+
+ if (CAJA_IS_SEARCH_DIRECTORY (directory))
+ {
+ search_directory = CAJA_SEARCH_DIRECTORY (directory);
+
+ query_editor = caja_query_editor_new (caja_search_directory_is_saved_search (search_directory),
+ caja_search_directory_is_indexed (search_directory));
+
+ slot->query_editor = CAJA_QUERY_EDITOR (query_editor);
+
+ caja_window_slot_add_extra_location_widget (slot, query_editor);
+ gtk_widget_show (query_editor);
+ g_signal_connect_object (query_editor, "changed",
+ G_CALLBACK (query_editor_changed_callback), slot, 0);
+
+ query = caja_search_directory_get_query (search_directory);
+ if (query != NULL)
+ {
+ caja_query_editor_set_query (CAJA_QUERY_EDITOR (query_editor),
+ query);
+ g_object_unref (query);
+ }
+ else
+ {
+ caja_query_editor_set_default_query (CAJA_QUERY_EDITOR (query_editor));
+ }
+ }
+
+ caja_directory_unref (directory);
+}
+
+
+static void
+real_active (CajaWindowSlot *slot)
+{
+ CajaWindow *window;
+
+ window = slot->pane->window;
+
+ /* sync window to new slot */
+ caja_window_sync_status (window);
+ caja_window_sync_allow_stop (window, slot);
+ caja_window_sync_title (window, slot);
+ caja_window_sync_zoom_widgets (window);
+ caja_window_pane_sync_location_widgets (slot->pane);
+ caja_window_pane_sync_search_widgets (slot->pane);
+
+ if (slot->viewed_file != NULL)
+ {
+ caja_window_load_view_as_menus (window);
+ caja_window_load_extension_menus (window);
+ }
+}
+
+static void
+caja_window_slot_active (CajaWindowSlot *slot)
+{
+ CajaWindow *window;
+ CajaWindowPane *pane;
+
+ g_assert (CAJA_IS_WINDOW_SLOT (slot));
+
+ pane = CAJA_WINDOW_PANE (slot->pane);
+ window = CAJA_WINDOW (slot->pane->window);
+ g_assert (g_list_find (pane->slots, slot) != NULL);
+ g_assert (slot == window->details->active_pane->active_slot);
+
+ EEL_CALL_METHOD (CAJA_WINDOW_SLOT_CLASS, slot,
+ active, (slot));
+}
+
+static void
+real_inactive (CajaWindowSlot *slot)
+{
+ CajaWindow *window;
+
+ window = CAJA_WINDOW (slot->pane->window);
+ g_assert (slot == window->details->active_pane->active_slot);
+}
+
+static void
+caja_window_slot_inactive (CajaWindowSlot *slot)
+{
+ CajaWindow *window;
+ CajaWindowPane *pane;
+
+ g_assert (CAJA_IS_WINDOW_SLOT (slot));
+
+ pane = CAJA_WINDOW_PANE (slot->pane);
+ window = CAJA_WINDOW (pane->window);
+
+ g_assert (g_list_find (pane->slots, slot) != NULL);
+ g_assert (slot == window->details->active_pane->active_slot);
+
+ EEL_CALL_METHOD (CAJA_WINDOW_SLOT_CLASS, slot,
+ inactive, (slot));
+}
+
+
+static void
+caja_window_slot_init (CajaWindowSlot *slot)
+{
+ GtkWidget *content_box, *eventbox, *extras_vbox, *frame;
+
+ content_box = gtk_vbox_new (FALSE, 0);
+ slot->content_box = content_box;
+ gtk_widget_show (content_box);
+
+ frame = gtk_frame_new (NULL);
+ gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
+ gtk_box_pack_start (GTK_BOX (content_box), frame, FALSE, FALSE, 0);
+ slot->extra_location_frame = frame;
+
+ eventbox = gtk_event_box_new ();
+ gtk_widget_set_name (eventbox, "caja-extra-view-widget");
+ gtk_container_add (GTK_CONTAINER (frame), eventbox);
+ gtk_widget_show (eventbox);
+
+ extras_vbox = gtk_vbox_new (FALSE, 6);
+ gtk_container_set_border_width (GTK_CONTAINER (extras_vbox), 6);
+ slot->extra_location_widgets = extras_vbox;
+ gtk_container_add (GTK_CONTAINER (eventbox), extras_vbox);
+ gtk_widget_show (extras_vbox);
+
+ slot->view_box = gtk_vbox_new (FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (content_box), slot->view_box, TRUE, TRUE, 0);
+ gtk_widget_show (slot->view_box);
+
+ slot->title = g_strdup (_("Loading..."));
+}
+
+static void
+caja_window_slot_class_init (CajaWindowSlotClass *class)
+{
+ class->active = real_active;
+ class->inactive = real_inactive;
+ class->update_query_editor = real_update_query_editor;
+
+ G_OBJECT_CLASS (class)->dispose = caja_window_slot_dispose;
+}
+
+static int
+caja_window_slot_get_selection_count (CajaWindowSlot *slot)
+{
+ g_assert (CAJA_IS_WINDOW_SLOT (slot));
+
+ if (slot->content_view != NULL)
+ {
+ return caja_view_get_selection_count (slot->content_view);
+ }
+ return 0;
+}
+
+GFile *
+caja_window_slot_get_location (CajaWindowSlot *slot)
+{
+ g_assert (slot != NULL);
+ g_assert (CAJA_IS_WINDOW (slot->pane->window));
+
+ if (slot->location != NULL)
+ {
+ return g_object_ref (slot->location);
+ }
+ return NULL;
+}
+
+char *
+caja_window_slot_get_location_uri (CajaWindowSlotInfo *slot)
+{
+ g_assert (CAJA_IS_WINDOW_SLOT (slot));
+
+ if (slot->location)
+ {
+ return g_file_get_uri (slot->location);
+ }
+ return NULL;
+}
+
+static void
+caja_window_slot_make_hosting_pane_active (CajaWindowSlot *slot)
+{
+ g_assert (CAJA_IS_WINDOW_SLOT (slot));
+ g_assert (CAJA_IS_WINDOW_PANE (slot->pane));
+
+ caja_window_set_active_slot (slot->pane->window, slot);
+}
+
+char *
+caja_window_slot_get_title (CajaWindowSlot *slot)
+{
+ char *title;
+
+ g_assert (CAJA_IS_WINDOW_SLOT (slot));
+
+ title = NULL;
+ if (slot->new_content_view != NULL)
+ {
+ title = caja_view_get_title (slot->new_content_view);
+ }
+ else if (slot->content_view != NULL)
+ {
+ title = caja_view_get_title (slot->content_view);
+ }
+
+ if (title == NULL)
+ {
+ title = caja_compute_title_for_location (slot->location);
+ }
+
+ return title;
+}
+
+static CajaWindow *
+caja_window_slot_get_window (CajaWindowSlot *slot)
+{
+ g_assert (CAJA_IS_WINDOW_SLOT (slot));
+ return slot->pane->window;
+}
+
+/* caja_window_slot_set_title:
+ *
+ * Sets slot->title, and if it changed
+ * synchronizes the actual GtkWindow title which
+ * might look a bit different (e.g. with "file browser:" added)
+ */
+static void
+caja_window_slot_set_title (CajaWindowSlot *slot,
+ const char *title)
+{
+ CajaWindow *window;
+ gboolean changed;
+
+ g_assert (CAJA_IS_WINDOW_SLOT (slot));
+
+ window = CAJA_WINDOW (slot->pane->window);
+
+ changed = FALSE;
+
+ if (eel_strcmp (title, slot->title) != 0)
+ {
+ changed = TRUE;
+
+ g_free (slot->title);
+ slot->title = g_strdup (title);
+ }
+
+ if (eel_strlen (slot->title) > 0 && slot->current_location_bookmark &&
+ caja_bookmark_set_name (slot->current_location_bookmark,
+ slot->title))
+ {
+ changed = TRUE;
+
+ /* Name of item in history list changed, tell listeners. */
+ caja_send_history_list_changed ();
+ }
+
+ if (changed)
+ {
+ caja_window_sync_title (window, slot);
+ }
+}
+
+
+/* caja_window_slot_update_title:
+ *
+ * Re-calculate the slot title.
+ * Called when the location or view has changed.
+ * @slot: The CajaWindowSlot in question.
+ *
+ */
+void
+caja_window_slot_update_title (CajaWindowSlot *slot)
+{
+ char *title;
+
+ title = caja_window_slot_get_title (slot);
+ caja_window_slot_set_title (slot, title);
+ g_free (title);
+}
+
+/* caja_window_slot_update_icon:
+ *
+ * Re-calculate the slot icon
+ * Called when the location or view or icon set has changed.
+ * @slot: The CajaWindowSlot in question.
+ */
+void
+caja_window_slot_update_icon (CajaWindowSlot *slot)
+{
+ CajaWindow *window;
+ CajaIconInfo *info;
+ const char *icon_name;
+ GdkPixbuf *pixbuf;
+
+ window = slot->pane->window;
+
+ g_return_if_fail (CAJA_IS_WINDOW (window));
+
+ info = EEL_CALL_METHOD_WITH_RETURN_VALUE (CAJA_WINDOW_CLASS, window,
+ get_icon, (window, slot));
+
+ icon_name = NULL;
+ if (info)
+ {
+ icon_name = caja_icon_info_get_used_name (info);
+ if (icon_name != NULL)
+ {
+ /* Gtk+ doesn't short circuit this (yet), so avoid lots of work
+ * if we're setting to the same icon. This happens a lot e.g. when
+ * the trash directory changes due to the file count changing.
+ */
+ if (g_strcmp0 (icon_name, gtk_window_get_icon_name (GTK_WINDOW (window))) != 0)
+ {
+ gtk_window_set_icon_name (GTK_WINDOW (window), icon_name);
+ }
+ }
+ else
+ {
+ pixbuf = caja_icon_info_get_pixbuf_nodefault (info);
+
+ if (pixbuf)
+ {
+ gtk_window_set_icon (GTK_WINDOW (window), pixbuf);
+ g_object_unref (pixbuf);
+ }
+ }
+
+ g_object_unref (info);
+ }
+}
+
+void
+caja_window_slot_is_in_active_pane (CajaWindowSlot *slot,
+ gboolean is_active)
+{
+ /* NULL is valid, and happens during init */
+ if (!slot)
+ {
+ return;
+ }
+
+ /* it may also be that the content is not a valid directory view during init */
+ if (slot->content_view != NULL)
+ {
+ caja_view_set_is_active (slot->content_view, is_active);
+ }
+
+ if (slot->new_content_view != NULL)
+ {
+ caja_view_set_is_active (slot->new_content_view, is_active);
+ }
+}
+
+void
+caja_window_slot_connect_content_view (CajaWindowSlot *slot,
+ CajaView *view)
+{
+ CajaWindow *window;
+
+ window = slot->pane->window;
+ if (window != NULL && slot == caja_window_get_active_slot (window))
+ {
+ caja_window_connect_content_view (window, view);
+ }
+}
+
+void
+caja_window_slot_disconnect_content_view (CajaWindowSlot *slot,
+ CajaView *view)
+{
+ CajaWindow *window;
+
+ window = slot->pane->window;
+ if (window != NULL && window->details->active_pane && window->details->active_pane->active_slot == slot)
+ {
+ caja_window_disconnect_content_view (window, view);
+ }
+}
+
+void
+caja_window_slot_set_content_view_widget (CajaWindowSlot *slot,
+ CajaView *new_view)
+{
+ CajaWindow *window;
+ GtkWidget *widget;
+
+ window = slot->pane->window;
+ g_assert (CAJA_IS_WINDOW (window));
+
+ if (slot->content_view != NULL)
+ {
+ /* disconnect old view */
+ caja_window_slot_disconnect_content_view (slot, slot->content_view);
+
+ widget = caja_view_get_widget (slot->content_view);
+ gtk_widget_destroy (widget);
+ g_object_unref (slot->content_view);
+ slot->content_view = NULL;
+ }
+
+ if (new_view != NULL)
+ {
+ widget = caja_view_get_widget (new_view);
+ gtk_container_add (GTK_CONTAINER (slot->view_box),
+ GTK_WIDGET (new_view));
+
+ gtk_widget_show (widget);
+
+ slot->content_view = new_view;
+ g_object_ref (slot->content_view);
+
+ /* connect new view */
+ caja_window_slot_connect_content_view (slot, new_view);
+ }
+}
+
+void
+caja_window_slot_set_allow_stop (CajaWindowSlot *slot,
+ gboolean allow)
+{
+ CajaWindow *window;
+
+ g_assert (CAJA_IS_WINDOW_SLOT (slot));
+
+ slot->allow_stop = allow;
+
+ window = CAJA_WINDOW (slot->pane->window);
+ caja_window_sync_allow_stop (window, slot);
+}
+
+void
+caja_window_slot_set_status (CajaWindowSlot *slot,
+ const char *status)
+{
+ CajaWindow *window;
+
+ g_assert (CAJA_IS_WINDOW_SLOT (slot));
+
+ g_free (slot->status_text);
+ slot->status_text = g_strdup (status);
+
+ window = CAJA_WINDOW (slot->pane->window);
+ if (slot == window->details->active_pane->active_slot)
+ {
+ caja_window_sync_status (window);
+ }
+}
+
+/* caja_window_slot_update_query_editor:
+ *
+ * Update the query editor.
+ * Called when the location has changed.
+ *
+ * @slot: The CajaWindowSlot in question.
+ */
+void
+caja_window_slot_update_query_editor (CajaWindowSlot *slot)
+{
+ if (slot->query_editor != NULL)
+ {
+ gtk_widget_destroy (GTK_WIDGET (slot->query_editor));
+ g_assert (slot->query_editor == NULL);
+ }
+
+ EEL_CALL_METHOD (CAJA_WINDOW_SLOT_CLASS, slot,
+ update_query_editor, (slot));
+
+ eel_add_weak_pointer (&slot->query_editor);
+}
+
+static void
+remove_all (GtkWidget *widget,
+ gpointer data)
+{
+ GtkContainer *container;
+ container = GTK_CONTAINER (data);
+
+ gtk_container_remove (container, widget);
+}
+
+void
+caja_window_slot_remove_extra_location_widgets (CajaWindowSlot *slot)
+{
+ gtk_container_foreach (GTK_CONTAINER (slot->extra_location_widgets),
+ remove_all,
+ slot->extra_location_widgets);
+ gtk_widget_hide (slot->extra_location_frame);
+}
+
+void
+caja_window_slot_add_extra_location_widget (CajaWindowSlot *slot,
+ GtkWidget *widget)
+{
+ gtk_box_pack_start (GTK_BOX (slot->extra_location_widgets),
+ widget, TRUE, TRUE, 0);
+ gtk_widget_show (slot->extra_location_frame);
+}
+
+void
+caja_window_slot_add_current_location_to_history_list (CajaWindowSlot *slot)
+{
+
+ if ((slot->pane->window == NULL || !CAJA_IS_DESKTOP_WINDOW (slot->pane->window)) &&
+ caja_add_bookmark_to_history_list (slot->current_location_bookmark))
+ {
+ caja_send_history_list_changed ();
+ }
+}
+
+/* returns either the pending or the actual current location - used by side panes. */
+static char *
+real_slot_info_get_current_location (CajaWindowSlotInfo *info)
+{
+ CajaWindowSlot *slot;
+
+ slot = CAJA_WINDOW_SLOT (info);
+
+ if (slot->pending_location != NULL)
+ {
+ return g_file_get_uri (slot->pending_location);
+ }
+
+ if (slot->location != NULL)
+ {
+ return g_file_get_uri (slot->location);
+ }
+
+ g_assert_not_reached ();
+ return NULL;
+}
+
+static CajaView *
+real_slot_info_get_current_view (CajaWindowSlotInfo *info)
+{
+ CajaWindowSlot *slot;
+
+ slot = CAJA_WINDOW_SLOT (info);
+
+ if (slot->content_view != NULL)
+ {
+ return g_object_ref (slot->content_view);
+ }
+ else if (slot->new_content_view)
+ {
+ return g_object_ref (slot->new_content_view);
+ }
+
+ return NULL;
+}
+
+static void
+caja_window_slot_dispose (GObject *object)
+{
+ CajaWindowSlot *slot;
+ GtkWidget *widget;
+
+ slot = CAJA_WINDOW_SLOT (object);
+
+ if (slot->content_view)
+ {
+ widget = caja_view_get_widget (slot->content_view);
+ gtk_widget_destroy (widget);
+ g_object_unref (slot->content_view);
+ slot->content_view = NULL;
+ }
+
+ if (slot->new_content_view)
+ {
+ widget = caja_view_get_widget (slot->new_content_view);
+ gtk_widget_destroy (widget);
+ g_object_unref (slot->new_content_view);
+ slot->new_content_view = NULL;
+ }
+
+ caja_window_slot_set_viewed_file (slot, NULL);
+ /* TODO? why do we unref here? the file is NULL.
+ * It was already here before the slot move, though */
+ caja_file_unref (slot->viewed_file);
+
+ if (slot->location)
+ {
+ /* TODO? why do we ref here, instead of unreffing?
+ * It was already here before the slot migration, though */
+ g_object_ref (slot->location);
+ }
+
+ eel_g_list_free_deep (slot->pending_selection);
+ slot->pending_selection = NULL;
+
+ if (slot->current_location_bookmark != NULL)
+ {
+ g_object_unref (slot->current_location_bookmark);
+ slot->current_location_bookmark = NULL;
+ }
+ if (slot->last_location_bookmark != NULL)
+ {
+ g_object_unref (slot->last_location_bookmark);
+ slot->last_location_bookmark = NULL;
+ }
+
+ if (slot->find_mount_cancellable != NULL)
+ {
+ g_cancellable_cancel (slot->find_mount_cancellable);
+ slot->find_mount_cancellable = NULL;
+ }
+
+ slot->pane = NULL;
+
+ g_free (slot->title);
+ slot->title = NULL;
+
+ g_free (slot->status_text);
+ slot->status_text = NULL;
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+caja_window_slot_info_iface_init (CajaWindowSlotInfoIface *iface)
+{
+ iface->active = caja_window_slot_active;
+ iface->inactive = caja_window_slot_inactive;
+ iface->get_window = caja_window_slot_get_window;
+ iface->get_selection_count = caja_window_slot_get_selection_count;
+ iface->get_current_location = real_slot_info_get_current_location;
+ iface->get_current_view = real_slot_info_get_current_view;
+ iface->set_status = caja_window_slot_set_status;
+ iface->get_title = caja_window_slot_get_title;
+ iface->open_location = caja_window_slot_open_location_full;
+ iface->make_hosting_pane_active = caja_window_slot_make_hosting_pane_active;
+}
+
diff --git a/src/caja-window-slot.h b/src/caja-window-slot.h
new file mode 100644
index 00000000..64f6349f
--- /dev/null
+++ b/src/caja-window-slot.h
@@ -0,0 +1,186 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-window-slot.h: Caja window slot
+
+ Copyright (C) 2008 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with this program; if not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Christian Neumair <[email protected]>
+*/
+
+#ifndef CAJA_WINDOW_SLOT_H
+#define CAJA_WINDOW_SLOT_H
+
+#include "caja-window-pane.h"
+#include "caja-query-editor.h"
+#include <glib/gi18n.h>
+
+#define CAJA_TYPE_WINDOW_SLOT (caja_window_slot_get_type())
+#define CAJA_WINDOW_SLOT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), CAJA_TYPE_WINDOW_SLOT, CajaWindowSlotClass))
+#define CAJA_WINDOW_SLOT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_WINDOW_SLOT, CajaWindowSlot))
+#define CAJA_IS_WINDOW_SLOT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_WINDOW_SLOT))
+#define CAJA_IS_WINDOW_SLOT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), CAJA_TYPE_WINDOW_SLOT))
+#define CAJA_WINDOW_SLOT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), CAJA_TYPE_WINDOW_SLOT, CajaWindowSlotClass))
+
+typedef enum
+{
+ CAJA_LOCATION_CHANGE_STANDARD,
+ CAJA_LOCATION_CHANGE_BACK,
+ CAJA_LOCATION_CHANGE_FORWARD,
+ CAJA_LOCATION_CHANGE_RELOAD,
+ CAJA_LOCATION_CHANGE_REDIRECT,
+ CAJA_LOCATION_CHANGE_FALLBACK
+} CajaLocationChangeType;
+
+struct CajaWindowSlotClass
+{
+ GObjectClass parent_class;
+
+ /* wrapped CajaWindowInfo signals, for overloading */
+ void (* active) (CajaWindowSlot *slot);
+ void (* inactive) (CajaWindowSlot *slot);
+
+ void (* update_query_editor) (CajaWindowSlot *slot);
+};
+
+/* Each CajaWindowSlot corresponds to
+ * a location in the window for displaying
+ * a CajaView.
+ *
+ * For navigation windows, this would be a
+ * tab, while spatial windows only have one slot.
+ */
+struct CajaWindowSlot
+{
+ GObject parent;
+
+ CajaWindowPane *pane;
+
+ /* content_box contains
+ * 1) an event box containing extra_location_widgets
+ * 2) the view box for the content view
+ */
+ GtkWidget *content_box;
+ GtkWidget *extra_location_frame;
+ GtkWidget *extra_location_widgets;
+ GtkWidget *view_box;
+
+ CajaView *content_view;
+ CajaView *new_content_view;
+
+ /* Information about bookmarks */
+ CajaBookmark *current_location_bookmark;
+ CajaBookmark *last_location_bookmark;
+
+ /* Current location. */
+ GFile *location;
+ char *title;
+ char *status_text;
+
+ CajaFile *viewed_file;
+ gboolean viewed_file_seen;
+ gboolean viewed_file_in_trash;
+
+ gboolean allow_stop;
+
+ CajaQueryEditor *query_editor;
+
+ /* New location. */
+ CajaLocationChangeType location_change_type;
+ guint location_change_distance;
+ GFile *pending_location;
+ char *pending_scroll_to;
+ GList *pending_selection;
+ CajaFile *determine_view_file;
+ GCancellable *mount_cancellable;
+ GError *mount_error;
+ gboolean tried_mount;
+
+ GCancellable *find_mount_cancellable;
+
+ gboolean visible;
+};
+
+GType caja_window_slot_get_type (void);
+
+char * caja_window_slot_get_title (CajaWindowSlot *slot);
+void caja_window_slot_update_title (CajaWindowSlot *slot);
+void caja_window_slot_update_icon (CajaWindowSlot *slot);
+void caja_window_slot_update_query_editor (CajaWindowSlot *slot);
+
+GFile * caja_window_slot_get_location (CajaWindowSlot *slot);
+char * caja_window_slot_get_location_uri (CajaWindowSlot *slot);
+
+void caja_window_slot_close (CajaWindowSlot *slot);
+void caja_window_slot_reload (CajaWindowSlot *slot);
+
+void caja_window_slot_open_location (CajaWindowSlot *slot,
+ GFile *location,
+ gboolean close_behind);
+void caja_window_slot_open_location_with_selection (CajaWindowSlot *slot,
+ GFile *location,
+ GList *selection,
+ gboolean close_behind);
+void caja_window_slot_open_location_full (CajaWindowSlot *slot,
+ GFile *location,
+ CajaWindowOpenMode mode,
+ CajaWindowOpenFlags flags,
+ GList *new_selection);
+void caja_window_slot_stop_loading (CajaWindowSlot *slot);
+
+void caja_window_slot_set_content_view (CajaWindowSlot *slot,
+ const char *id);
+const char *caja_window_slot_get_content_view_id (CajaWindowSlot *slot);
+gboolean caja_window_slot_content_view_matches_iid (CajaWindowSlot *slot,
+ const char *iid);
+
+void caja_window_slot_connect_content_view (CajaWindowSlot *slot,
+ CajaView *view);
+void caja_window_slot_disconnect_content_view (CajaWindowSlot *slot,
+ CajaView *view);
+
+#define caja_window_slot_go_to(slot,location, new_tab) \
+ caja_window_slot_open_location_full(slot, location, CAJA_WINDOW_OPEN_ACCORDING_TO_MODE, \
+ (new_tab ? CAJA_WINDOW_OPEN_FLAG_NEW_TAB : 0), \
+ NULL)
+
+#define caja_window_slot_go_to_with_selection(slot,location,new_selection) \
+ caja_window_slot_open_location_with_selection(slot, location, new_selection, FALSE)
+
+void caja_window_slot_go_home (CajaWindowSlot *slot,
+ gboolean new_tab);
+void caja_window_slot_go_up (CajaWindowSlot *slot,
+ gboolean close_behind);
+
+void caja_window_slot_set_content_view_widget (CajaWindowSlot *slot,
+ CajaView *content_view);
+void caja_window_slot_set_viewed_file (CajaWindowSlot *slot,
+ CajaFile *file);
+void caja_window_slot_set_allow_stop (CajaWindowSlot *slot,
+ gboolean allow_stop);
+void caja_window_slot_set_status (CajaWindowSlot *slot,
+ const char *status);
+
+void caja_window_slot_add_extra_location_widget (CajaWindowSlot *slot,
+ GtkWidget *widget);
+void caja_window_slot_remove_extra_location_widgets (CajaWindowSlot *slot);
+
+void caja_window_slot_add_current_location_to_history_list (CajaWindowSlot *slot);
+
+void caja_window_slot_is_in_active_pane (CajaWindowSlot *slot, gboolean is_active);
+
+#endif /* CAJA_WINDOW_SLOT_H */
diff --git a/src/caja-window-toolbars.c b/src/caja-window-toolbars.c
new file mode 100644
index 00000000..90c40269
--- /dev/null
+++ b/src/caja-window-toolbars.c
@@ -0,0 +1,204 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2000, 2001 Eazel, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Author: John Sullivan <[email protected]>
+ */
+
+/* caja-window-toolbars.c - implementation of caja window toolbar operations,
+ * split into separate file just for convenience.
+ */
+
+#include <config.h>
+
+#include <unistd.h>
+#include "caja-application.h"
+#include "caja-window-manage-views.h"
+#include "caja-window-private.h"
+#include "caja-window.h"
+#include <eel/eel-mate-extensions.h>
+#include <eel/eel-gtk-extensions.h>
+#include <eel/eel-string.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <glib/gi18n.h>
+#include <libcaja-extension/caja-menu-provider.h>
+#include <libcaja-private/caja-bookmark.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-module.h>
+
+/* FIXME bugzilla.gnome.org 41243:
+ * We should use inheritance instead of these special cases
+ * for the desktop window.
+ */
+#include "caja-desktop-window.h"
+
+#define TOOLBAR_PATH_EXTENSION_ACTIONS "/Toolbar/Extra Buttons Placeholder/Extension Actions"
+
+void
+caja_navigation_window_set_spinner_active (CajaNavigationWindow *window,
+ gboolean allow)
+{
+ if (( window->details->spinner_active && allow) ||
+ (!window->details->spinner_active && !allow))
+ {
+ return;
+ }
+
+ window->details->spinner_active = allow;
+ if (allow)
+ {
+ gtk_spinner_start (GTK_SPINNER (window->details->spinner));
+ }
+ else
+ {
+ gtk_spinner_stop (GTK_SPINNER (window->details->spinner));
+ }
+}
+
+void
+caja_navigation_window_activate_spinner (CajaNavigationWindow *window)
+{
+ GtkToolItem *item;
+ GtkWidget *spinner;
+
+ if (window->details->spinner != NULL)
+ {
+ return;
+ }
+
+ item = gtk_tool_item_new ();
+ gtk_widget_show (GTK_WIDGET (item));
+ gtk_tool_item_set_expand (item, TRUE);
+ gtk_toolbar_insert (GTK_TOOLBAR (window->details->toolbar),
+ item, -1);
+
+ spinner = gtk_spinner_new ();
+ gtk_widget_show (GTK_WIDGET (spinner));
+
+ item = gtk_tool_item_new ();
+ gtk_container_add (GTK_CONTAINER (item), spinner);
+ gtk_widget_show (GTK_WIDGET (item));
+
+ gtk_toolbar_insert (GTK_TOOLBAR (window->details->toolbar),
+ item, -1);
+
+ window->details->spinner = spinner;
+}
+
+void
+caja_navigation_window_initialize_toolbars (CajaNavigationWindow *window)
+{
+ caja_navigation_window_activate_spinner (window);
+}
+
+
+static GList *
+get_extension_toolbar_items (CajaNavigationWindow *window)
+{
+ CajaWindowSlot *slot;
+ GList *items;
+ GList *providers;
+ GList *l;
+
+ providers = caja_module_get_extensions_for_type (CAJA_TYPE_MENU_PROVIDER);
+ items = NULL;
+
+ slot = CAJA_WINDOW (window)->details->active_pane->active_slot;
+
+ 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_toolbar_items
+ (provider,
+ GTK_WIDGET (window),
+ slot->viewed_file);
+ items = g_list_concat (items, file_items);
+ }
+
+ caja_module_extension_list_free (providers);
+
+ return items;
+}
+
+void
+caja_navigation_window_load_extension_toolbar_items (CajaNavigationWindow *window)
+{
+ GtkActionGroup *action_group;
+ GtkAction *action;
+ GtkUIManager *ui_manager;
+ GList *items;
+ GList *l;
+ CajaMenuItem *item;
+ guint merge_id;
+
+ ui_manager = caja_window_get_ui_manager (CAJA_WINDOW (window));
+ if (window->details->extensions_toolbar_merge_id != 0)
+ {
+ gtk_ui_manager_remove_ui (ui_manager,
+ window->details->extensions_toolbar_merge_id);
+ window->details->extensions_toolbar_merge_id = 0;
+ }
+
+ if (window->details->extensions_toolbar_action_group != NULL)
+ {
+ gtk_ui_manager_remove_action_group (ui_manager,
+ window->details->extensions_toolbar_action_group);
+ window->details->extensions_toolbar_action_group = NULL;
+ }
+
+ merge_id = gtk_ui_manager_new_merge_id (ui_manager);
+ window->details->extensions_toolbar_merge_id = merge_id;
+ action_group = gtk_action_group_new ("ExtensionsToolbarGroup");
+ window->details->extensions_toolbar_action_group = action_group;
+ gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
+ gtk_ui_manager_insert_action_group (ui_manager, action_group, -1);
+ g_object_unref (action_group); /* owned by ui manager */
+
+ items = get_extension_toolbar_items (window);
+
+ for (l = items; l != NULL; l = l->next)
+ {
+ item = CAJA_MENU_ITEM (l->data);
+
+ action = caja_toolbar_action_from_menu_item (item);
+
+ gtk_action_group_add_action (action_group,
+ GTK_ACTION (action));
+ g_object_unref (action);
+
+ gtk_ui_manager_add_ui (ui_manager,
+ merge_id,
+ TOOLBAR_PATH_EXTENSION_ACTIONS,
+ gtk_action_get_name (action),
+ gtk_action_get_name (action),
+ GTK_UI_MANAGER_TOOLITEM,
+ FALSE);
+
+ g_object_unref (item);
+ }
+
+ g_list_free (items);
+}
diff --git a/src/caja-window.c b/src/caja-window.c
new file mode 100644
index 00000000..72575f63
--- /dev/null
+++ b/src/caja-window.c
@@ -0,0 +1,2164 @@
+/*
+ * Caja
+ *
+ * Copyright (C) 1999, 2000, 2004 Red Hat, Inc.
+ * Copyright (C) 1999, 2000, 2001 Eazel, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Authors: Elliot Lee <[email protected]>
+ * John Sullivan <[email protected]>
+ * Alexander Larsson <[email protected]>
+ */
+
+/* caja-window.c: Implementation of the main window object */
+
+#include <config.h>
+#include "caja-window-private.h"
+
+#include "caja-actions.h"
+#include "caja-application.h"
+#include "caja-bookmarks-window.h"
+#include "caja-information-panel.h"
+#include "caja-main.h"
+#include "caja-window-manage-views.h"
+#include "caja-window-bookmarks.h"
+#include "caja-window-slot.h"
+#include "caja-navigation-window-slot.h"
+#include "caja-zoom-control.h"
+#include "caja-search-bar.h"
+#include "caja-navigation-window-pane.h"
+#include <eel/eel-debug.h>
+#include <eel/eel-marshal.h>
+#include <eel/eel-gtk-macros.h>
+#include <eel/eel-string.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gdk/gdkx.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#ifdef HAVE_X11_XF86KEYSYM_H
+#include <X11/XF86keysym.h>
+#endif
+#include <libcaja-private/caja-file-utilities.h>
+#include <libcaja-private/caja-file-attributes.h>
+#include <libcaja-private/caja-global-preferences.h>
+#include <libcaja-private/caja-horizontal-splitter.h>
+#include <libcaja-private/caja-metadata.h>
+#include <libcaja-private/caja-marshal.h>
+#include <libcaja-private/caja-mime-actions.h>
+#include <libcaja-private/caja-program-choosing.h>
+#include <libcaja-private/caja-view-factory.h>
+#include <libcaja-private/caja-clipboard.h>
+#include <libcaja-private/caja-undo.h>
+#include <libcaja-private/caja-search-directory.h>
+#include <libcaja-private/caja-signaller.h>
+#include <math.h>
+#include <sys/time.h>
+
+#define MAX_HISTORY_ITEMS 50
+
+#define EXTRA_VIEW_WIDGETS_BACKGROUND "#a7c6e1"
+
+#define SIDE_PANE_MINIMUM_WIDTH 1
+#define SIDE_PANE_MINIMUM_HEIGHT 400
+
+/* dock items */
+
+#define CAJA_MENU_PATH_EXTRA_VIEWER_PLACEHOLDER "/MenuBar/View/View Choices/Extra Viewer"
+#define CAJA_MENU_PATH_SHORT_LIST_PLACEHOLDER "/MenuBar/View/View Choices/Short List"
+#define CAJA_MENU_PATH_AFTER_SHORT_LIST_SEPARATOR "/MenuBar/View/View Choices/After Short List"
+
+enum {
+ ARG_0,
+ ARG_APP
+};
+
+enum {
+ GO_UP,
+ RELOAD,
+ PROMPT_FOR_LOCATION,
+ ZOOM_CHANGED,
+ VIEW_AS_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+typedef struct
+{
+ CajaWindow *window;
+ char *id;
+} ActivateViewData;
+
+static void cancel_view_as_callback (CajaWindowSlot *slot);
+static void caja_window_info_iface_init (CajaWindowInfoIface *iface);
+static void action_view_as_callback (GtkAction *action,
+ ActivateViewData *data);
+
+static GList *history_list;
+
+G_DEFINE_TYPE_WITH_CODE (CajaWindow, caja_window, GTK_TYPE_WINDOW,
+ G_IMPLEMENT_INTERFACE (CAJA_TYPE_WINDOW_INFO,
+ caja_window_info_iface_init));
+
+static const struct
+{
+ unsigned int keyval;
+ const char *action;
+} extra_window_keybindings [] =
+{
+#ifdef HAVE_X11_XF86KEYSYM_H
+ { XF86XK_AddFavorite, CAJA_ACTION_ADD_BOOKMARK },
+ { XF86XK_Favorites, CAJA_ACTION_EDIT_BOOKMARKS },
+ { XF86XK_Go, CAJA_ACTION_GO_TO_LOCATION },
+ /* TODO?{ XF86XK_History, CAJA_ACTION_HISTORY }, */
+ { XF86XK_HomePage, CAJA_ACTION_GO_HOME },
+ { XF86XK_OpenURL, CAJA_ACTION_GO_TO_LOCATION },
+ { XF86XK_Refresh, CAJA_ACTION_RELOAD },
+ { XF86XK_Reload, CAJA_ACTION_RELOAD },
+ { XF86XK_Search, CAJA_ACTION_SEARCH },
+ { XF86XK_Start, CAJA_ACTION_GO_HOME },
+ { XF86XK_Stop, CAJA_ACTION_STOP },
+ { XF86XK_ZoomIn, CAJA_ACTION_ZOOM_IN },
+ { XF86XK_ZoomOut, CAJA_ACTION_ZOOM_OUT }
+#endif
+};
+
+static void
+caja_window_init (CajaWindow *window)
+{
+ GtkWidget *table;
+ GtkWidget *menu;
+ GtkWidget *statusbar;
+
+ window->details = G_TYPE_INSTANCE_GET_PRIVATE (window, CAJA_TYPE_WINDOW, CajaWindowDetails);
+
+ window->details->panes = NULL;
+ window->details->active_pane = NULL;
+
+ window->details->show_hidden_files_mode = CAJA_WINDOW_SHOW_HIDDEN_FILES_DEFAULT;
+
+ /* Remove Top border on GtkStatusBar */
+ gtk_rc_parse_string (
+ "style \"statusbar-no-border\"\n"
+ "{\n"
+ " GtkStatusbar::shadow_type = GTK_SHADOW_NONE\n"
+ "}\n"
+ "widget \"*.statusbar-noborder\" style \"statusbar-no-border\"");
+
+ /* Set initial window title */
+ gtk_window_set_title (GTK_WINDOW (window), _("Caja"));
+
+ table = gtk_table_new (1, 6, FALSE);
+ window->details->table = table;
+ gtk_widget_show (table);
+ gtk_container_add (GTK_CONTAINER (window), table);
+
+ statusbar = gtk_statusbar_new ();
+ gtk_widget_set_name (statusbar, "statusbar-noborder");
+ window->details->statusbar = statusbar;
+ window->details->help_message_cid = gtk_statusbar_get_context_id
+ (GTK_STATUSBAR (statusbar), "help_message");
+ /* Statusbar is packed in the subclasses */
+
+ caja_window_initialize_menus (window);
+
+ menu = gtk_ui_manager_get_widget (window->details->ui_manager, "/MenuBar");
+ window->details->menubar = menu;
+ gtk_widget_show (menu);
+ gtk_table_attach (GTK_TABLE (table),
+ menu,
+ /* X direction */ /* Y direction */
+ 0, 1, 0, 1,
+ GTK_EXPAND | GTK_FILL | GTK_SHRINK, 0,
+ 0, 0);
+
+ /* Register to menu provider extension signal managing menu updates */
+ g_signal_connect_object (caja_signaller_get_current (), "popup_menu_changed",
+ G_CALLBACK (caja_window_load_extension_menus), window, G_CONNECT_SWAPPED);
+
+ gtk_quit_add_destroy (1, GTK_OBJECT (window));
+
+ /* Keep the main event loop alive as long as the window exists */
+ caja_main_event_loop_register (GTK_OBJECT (window));
+}
+
+/* Unconditionally synchronize the GtkUIManager of WINDOW. */
+static void
+caja_window_ui_update (CajaWindow *window)
+{
+ g_assert (CAJA_IS_WINDOW (window));
+
+ gtk_ui_manager_ensure_update (window->details->ui_manager);
+}
+
+static void
+caja_window_push_status (CajaWindow *window,
+ const char *text)
+{
+ g_return_if_fail (CAJA_IS_WINDOW (window));
+
+ /* clear any previous message, underflow is allowed */
+ gtk_statusbar_pop (GTK_STATUSBAR (window->details->statusbar), 0);
+
+ if (text != NULL && text[0] != '\0')
+ {
+ gtk_statusbar_push (GTK_STATUSBAR (window->details->statusbar), 0, text);
+ }
+}
+
+void
+caja_window_sync_status (CajaWindow *window)
+{
+ CajaWindowSlot *slot;
+
+ slot = window->details->active_pane->active_slot;
+ caja_window_push_status (window, slot->status_text);
+}
+
+void
+caja_window_go_to (CajaWindow *window, GFile *location)
+{
+ g_return_if_fail (CAJA_IS_WINDOW (window));
+
+ caja_window_slot_go_to (window->details->active_pane->active_slot, location, FALSE);
+}
+
+void
+caja_window_go_to_with_selection (CajaWindow *window, GFile *location, GList *new_selection)
+{
+ g_return_if_fail (CAJA_IS_WINDOW (window));
+
+ caja_window_slot_go_to_with_selection (window->details->active_pane->active_slot, location, new_selection);
+}
+
+static gboolean
+caja_window_go_up_signal (CajaWindow *window, gboolean close_behind)
+{
+ caja_window_go_up (window, close_behind, FALSE);
+ return TRUE;
+}
+
+void
+caja_window_go_up (CajaWindow *window, gboolean close_behind, gboolean new_tab)
+{
+ CajaWindowSlot *slot;
+ GFile *parent;
+ GList *selection;
+ CajaWindowOpenFlags flags;
+
+ g_assert (CAJA_IS_WINDOW (window));
+
+ slot = window->details->active_pane->active_slot;
+
+ if (slot->location == NULL)
+ {
+ return;
+ }
+
+ parent = g_file_get_parent (slot->location);
+
+ if (parent == NULL)
+ {
+ return;
+ }
+
+ selection = g_list_prepend (NULL, g_object_ref (slot->location));
+
+ flags = 0;
+ if (close_behind)
+ {
+ flags |= CAJA_WINDOW_OPEN_FLAG_CLOSE_BEHIND;
+ }
+ if (new_tab)
+ {
+ flags |= CAJA_WINDOW_OPEN_FLAG_NEW_TAB;
+ }
+
+ caja_window_slot_open_location_full (slot, parent,
+ CAJA_WINDOW_OPEN_ACCORDING_TO_MODE,
+ flags,
+ selection);
+
+ g_object_unref (parent);
+
+ eel_g_object_list_free (selection);
+}
+
+static void
+real_set_allow_up (CajaWindow *window,
+ gboolean allow)
+{
+ GtkAction *action;
+
+ g_assert (CAJA_IS_WINDOW (window));
+
+ action = gtk_action_group_get_action (window->details->main_action_group,
+ CAJA_ACTION_UP);
+ gtk_action_set_sensitive (action, allow);
+ action = gtk_action_group_get_action (window->details->main_action_group,
+ CAJA_ACTION_UP_ACCEL);
+ gtk_action_set_sensitive (action, allow);
+}
+
+void
+caja_window_allow_up (CajaWindow *window, gboolean allow)
+{
+ g_return_if_fail (CAJA_IS_WINDOW (window));
+
+ EEL_CALL_METHOD (CAJA_WINDOW_CLASS, window,
+ set_allow_up, (window, allow));
+}
+
+static void
+update_cursor (CajaWindow *window)
+{
+ CajaWindowSlot *slot;
+ GdkCursor *cursor;
+
+ slot = window->details->active_pane->active_slot;
+
+ if (slot->allow_stop)
+ {
+ cursor = gdk_cursor_new (GDK_WATCH);
+ gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (window)), cursor);
+ gdk_cursor_unref (cursor);
+ }
+ else
+ {
+ gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (window)), NULL);
+ }
+}
+
+void
+caja_window_sync_allow_stop (CajaWindow *window,
+ CajaWindowSlot *slot)
+{
+ GtkAction *action;
+ gboolean allow_stop;
+
+ g_assert (CAJA_IS_WINDOW (window));
+
+ action = gtk_action_group_get_action (window->details->main_action_group,
+ CAJA_ACTION_STOP);
+ allow_stop = gtk_action_get_sensitive (action);
+
+ if (slot != window->details->active_pane->active_slot ||
+ allow_stop != slot->allow_stop)
+ {
+ if (slot == window->details->active_pane->active_slot)
+ {
+ gtk_action_set_sensitive (action, slot->allow_stop);
+ }
+
+ if (gtk_widget_get_realized (GTK_WIDGET (window)))
+ {
+ update_cursor (window);
+ }
+
+ EEL_CALL_METHOD (CAJA_WINDOW_CLASS, window,
+ sync_allow_stop, (window, slot));
+ }
+}
+
+void
+caja_window_allow_reload (CajaWindow *window, gboolean allow)
+{
+ GtkAction *action;
+
+ g_return_if_fail (CAJA_IS_WINDOW (window));
+
+ action = gtk_action_group_get_action (window->details->main_action_group,
+ CAJA_ACTION_RELOAD);
+ gtk_action_set_sensitive (action, allow);
+}
+
+void
+caja_window_go_home (CajaWindow *window)
+{
+ g_return_if_fail (CAJA_IS_WINDOW (window));
+
+ caja_window_slot_go_home (window->details->active_pane->active_slot, FALSE);
+}
+
+void
+caja_window_prompt_for_location (CajaWindow *window,
+ const char *initial)
+{
+ g_return_if_fail (CAJA_IS_WINDOW (window));
+
+ EEL_CALL_METHOD (CAJA_WINDOW_CLASS, window,
+ prompt_for_location, (window, initial));
+}
+
+static char *
+caja_window_get_location_uri (CajaWindow *window)
+{
+ CajaWindowSlot *slot;
+
+ g_assert (CAJA_IS_WINDOW (window));
+
+ slot = window->details->active_pane->active_slot;
+
+ if (slot->location)
+ {
+ return g_file_get_uri (slot->location);
+ }
+ return NULL;
+}
+
+void
+caja_window_zoom_in (CajaWindow *window)
+{
+ g_assert (window != NULL);
+
+ caja_window_pane_zoom_in (window->details->active_pane);
+}
+
+void
+caja_window_zoom_to_level (CajaWindow *window,
+ CajaZoomLevel level)
+{
+ g_assert (window != NULL);
+
+ caja_window_pane_zoom_to_level (window->details->active_pane, level);
+}
+
+void
+caja_window_zoom_out (CajaWindow *window)
+{
+ g_assert (window != NULL);
+
+ caja_window_pane_zoom_out (window->details->active_pane);
+}
+
+void
+caja_window_zoom_to_default (CajaWindow *window)
+{
+ g_assert (window != NULL);
+
+ caja_window_pane_zoom_to_default (window->details->active_pane);
+}
+
+/* Code should never force the window taller than this size.
+ * (The user can still stretch the window taller if desired).
+ */
+static guint
+get_max_forced_height (GdkScreen *screen)
+{
+ return (gdk_screen_get_height (screen) * 90) / 100;
+}
+
+/* Code should never force the window wider than this size.
+ * (The user can still stretch the window wider if desired).
+ */
+static guint
+get_max_forced_width (GdkScreen *screen)
+{
+ return (gdk_screen_get_width (screen) * 90) / 100;
+}
+
+/* This must be called when construction of CajaWindow is finished,
+ * since it depends on the type of the argument, which isn't decided at
+ * construction time.
+ */
+static void
+caja_window_set_initial_window_geometry (CajaWindow *window)
+{
+ GdkScreen *screen;
+ guint max_width_for_screen, max_height_for_screen, min_width, min_height;
+ guint default_width, default_height;
+
+ screen = gtk_window_get_screen (GTK_WINDOW (window));
+
+ /* Don't let GTK determine the minimum size
+ * automatically. It will insist that the window be
+ * really wide based on some misguided notion about
+ * the content view area. Also, it might start the
+ * window wider (or taller) than the screen, which
+ * is evil. So we choose semi-arbitrary initial and
+ * minimum widths instead of letting GTK decide.
+ */
+ /* FIXME - the above comment suggests that the size request
+ * of the content view area is wrong, probably because of
+ * another stupid set_usize someplace. If someone gets the
+ * content view area's size request right then we can
+ * probably remove this broken set_size_request() here.
+ */
+
+ max_width_for_screen = get_max_forced_width (screen);
+ max_height_for_screen = get_max_forced_height (screen);
+
+ EEL_CALL_METHOD (CAJA_WINDOW_CLASS, window,
+ get_min_size, (window, &min_width, &min_height));
+
+ gtk_widget_set_size_request (GTK_WIDGET (window),
+ MIN (min_width,
+ max_width_for_screen),
+ MIN (min_height,
+ max_height_for_screen));
+
+ EEL_CALL_METHOD (CAJA_WINDOW_CLASS, window,
+ get_default_size, (window, &default_width, &default_height));
+
+ gtk_window_set_default_size (GTK_WINDOW (window),
+ MIN (default_width,
+ max_width_for_screen),
+ MIN (default_height,
+ max_height_for_screen));
+}
+
+static void
+caja_window_constructed (GObject *self)
+{
+ CajaWindow *window;
+
+ window = CAJA_WINDOW (self);
+
+ caja_window_initialize_bookmarks_menu (window);
+ caja_window_set_initial_window_geometry (window);
+ caja_undo_manager_attach (window->application->undo_manager, G_OBJECT (window));
+}
+
+static void
+caja_window_set_property (GObject *object,
+ guint arg_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CajaWindow *window;
+
+ window = CAJA_WINDOW (object);
+
+ switch (arg_id)
+ {
+ case ARG_APP:
+ window->application = CAJA_APPLICATION (g_value_get_object (value));
+ break;
+ }
+}
+
+static void
+caja_window_get_property (GObject *object,
+ guint arg_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (arg_id)
+ {
+ case ARG_APP:
+ g_value_set_object (value, CAJA_WINDOW (object)->application);
+ break;
+ }
+}
+
+static void
+free_stored_viewers (CajaWindow *window)
+{
+ eel_g_list_free_deep_custom (window->details->short_list_viewers,
+ (GFunc) g_free,
+ NULL);
+ window->details->short_list_viewers = NULL;
+ g_free (window->details->extra_viewer);
+ window->details->extra_viewer = NULL;
+}
+
+static void
+caja_window_destroy (GtkObject *object)
+{
+ CajaWindow *window;
+ GList *panes_copy;
+
+ window = CAJA_WINDOW (object);
+
+ /* close all panes safely */
+ panes_copy = g_list_copy (window->details->panes);
+ g_list_foreach (panes_copy, (GFunc) caja_window_close_pane, NULL);
+ g_list_free (panes_copy);
+
+ /* the panes list should now be empty */
+ g_assert (window->details->panes == NULL);
+ g_assert (window->details->active_pane == NULL);
+
+ GTK_OBJECT_CLASS (caja_window_parent_class)->destroy (object);
+}
+
+static void
+caja_window_finalize (GObject *object)
+{
+ CajaWindow *window;
+
+ window = CAJA_WINDOW (object);
+
+ caja_window_remove_trash_monitor_callback (window);
+ free_stored_viewers (window);
+
+ if (window->details->bookmark_list != NULL)
+ {
+ g_object_unref (window->details->bookmark_list);
+ }
+
+ /* caja_window_close() should have run */
+ g_assert (window->details->panes == NULL);
+
+ g_object_unref (window->details->ui_manager);
+
+ G_OBJECT_CLASS (caja_window_parent_class)->finalize (object);
+}
+
+static GObject *
+caja_window_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_params)
+{
+ GObject *object;
+ CajaWindow *window;
+ CajaWindowSlot *slot;
+
+ object = (* G_OBJECT_CLASS (caja_window_parent_class)->constructor) (type,
+ n_construct_properties,
+ construct_params);
+
+ window = CAJA_WINDOW (object);
+
+ slot = caja_window_open_slot (window->details->active_pane, 0);
+ caja_window_set_active_slot (window, slot);
+
+ return object;
+}
+
+void
+caja_window_show_window (CajaWindow *window)
+{
+ CajaWindowSlot *slot;
+ CajaWindowPane *pane;
+ GList *l, *walk;
+
+ for (walk = window->details->panes; walk; walk = walk->next)
+ {
+ pane = walk->data;
+ for (l = pane->slots; l != NULL; l = l->next)
+ {
+ slot = l->data;
+
+ caja_window_slot_update_title (slot);
+ caja_window_slot_update_icon (slot);
+ }
+ }
+
+ gtk_widget_show (GTK_WIDGET (window));
+
+ slot = window->details->active_pane->active_slot;
+
+ if (slot->viewed_file)
+ {
+ if (CAJA_IS_SPATIAL_WINDOW (window))
+ {
+ caja_file_set_has_open_window (slot->viewed_file, TRUE);
+ }
+ }
+}
+
+static void
+caja_window_view_visible (CajaWindow *window,
+ CajaView *view)
+{
+ CajaWindowSlot *slot;
+ CajaWindowPane *pane;
+ GList *l, *walk;
+
+ g_return_if_fail (CAJA_IS_WINDOW (window));
+
+ slot = caja_window_get_slot_for_view (window, view);
+
+ /* Ensure we got the right active state for newly added panes */
+ caja_window_slot_is_in_active_pane (slot, slot->pane->is_active);
+
+ if (slot->visible)
+ {
+ return;
+ }
+
+ slot->visible = TRUE;
+
+ pane = slot->pane;
+
+ if (pane->visible)
+ {
+ return;
+ }
+
+ /* Look for other non-visible slots */
+ for (l = pane->slots; l != NULL; l = l->next)
+ {
+ slot = l->data;
+
+ if (!slot->visible)
+ {
+ return;
+ }
+ }
+
+ /* None, this pane is visible */
+ caja_window_pane_show (pane);
+
+ /* Look for other non-visible panes */
+ for (walk = window->details->panes; walk; walk = walk->next)
+ {
+ pane = walk->data;
+
+ if (!pane->visible)
+ {
+ return;
+ }
+ }
+
+ caja_window_pane_grab_focus (window->details->active_pane);
+
+ /* All slots and panes visible, show window */
+ caja_window_show_window (window);
+}
+
+void
+caja_window_close (CajaWindow *window)
+{
+ g_return_if_fail (CAJA_IS_WINDOW (window));
+
+ EEL_CALL_METHOD (CAJA_WINDOW_CLASS, window,
+ close, (window));
+
+ gtk_widget_destroy (GTK_WIDGET (window));
+}
+
+CajaWindowSlot *
+caja_window_open_slot (CajaWindowPane *pane,
+ CajaWindowOpenSlotFlags flags)
+{
+ CajaWindowSlot *slot;
+
+ g_assert (CAJA_IS_WINDOW_PANE (pane));
+ g_assert (CAJA_IS_WINDOW (pane->window));
+
+ slot = EEL_CALL_METHOD_WITH_RETURN_VALUE (CAJA_WINDOW_CLASS, pane->window,
+ open_slot, (pane, flags));
+
+ g_assert (CAJA_IS_WINDOW_SLOT (slot));
+ g_assert (pane->window == slot->pane->window);
+
+ pane->slots = g_list_append (pane->slots, slot);
+
+ return slot;
+}
+
+void
+caja_window_close_pane (CajaWindowPane *pane)
+{
+ CajaWindow *window;
+
+ g_assert (CAJA_IS_WINDOW_PANE (pane));
+ g_assert (CAJA_IS_WINDOW (pane->window));
+ g_assert (g_list_find (pane->window->details->panes, pane) != NULL);
+
+ while (pane->slots != NULL)
+ {
+ CajaWindowSlot *slot = pane->slots->data;
+
+ caja_window_close_slot (slot);
+ }
+
+ window = pane->window;
+
+ /* If the pane was active, set it to NULL. The caller is responsible
+ * for setting a new active pane with caja_window_pane_switch_to()
+ * if it wants to continue using the window. */
+ if (window->details->active_pane == pane)
+ {
+ window->details->active_pane = NULL;
+ }
+
+ window->details->panes = g_list_remove (window->details->panes, pane);
+
+ g_object_unref (pane);
+}
+
+static void
+real_close_slot (CajaWindowPane *pane,
+ CajaWindowSlot *slot)
+{
+ caja_window_manage_views_close_slot (pane, slot);
+ cancel_view_as_callback (slot);
+}
+
+void
+caja_window_close_slot (CajaWindowSlot *slot)
+{
+ CajaWindowPane *pane;
+
+ g_assert (CAJA_IS_WINDOW_SLOT (slot));
+ g_assert (CAJA_IS_WINDOW_PANE(slot->pane));
+ g_assert (g_list_find (slot->pane->slots, slot) != NULL);
+
+ /* save pane because slot is not valid anymore after this call */
+ pane = slot->pane;
+
+ EEL_CALL_METHOD (CAJA_WINDOW_CLASS, slot->pane->window,
+ close_slot, (slot->pane, slot));
+
+ g_object_run_dispose (G_OBJECT (slot));
+ slot->pane = NULL;
+ g_object_unref (slot);
+ pane->slots = g_list_remove (pane->slots, slot);
+ pane->active_slots = g_list_remove (pane->active_slots, slot);
+
+}
+
+CajaWindowPane*
+caja_window_get_active_pane (CajaWindow *window)
+{
+ g_assert (CAJA_IS_WINDOW (window));
+ return window->details->active_pane;
+}
+
+static void
+real_set_active_pane (CajaWindow *window, CajaWindowPane *new_pane)
+{
+ /* make old pane inactive, and new one active.
+ * Currently active pane may be NULL (after init). */
+ if (window->details->active_pane &&
+ window->details->active_pane != new_pane)
+ {
+ caja_window_pane_set_active (new_pane->window->details->active_pane, FALSE);
+ }
+ caja_window_pane_set_active (new_pane, TRUE);
+
+ window->details->active_pane = new_pane;
+}
+
+/* Make the given pane the active pane of its associated window. This
+ * always implies making the containing active slot the active slot of
+ * the window. */
+void
+caja_window_set_active_pane (CajaWindow *window,
+ CajaWindowPane *new_pane)
+{
+ g_assert (CAJA_IS_WINDOW_PANE (new_pane));
+ if (new_pane->active_slot)
+ {
+ caja_window_set_active_slot (window, new_pane->active_slot);
+ }
+ else if (new_pane != window->details->active_pane)
+ {
+ real_set_active_pane (window, new_pane);
+ }
+}
+
+/* Make both, the given slot the active slot and its corresponding
+ * pane the active pane of the associated window.
+ * new_slot may be NULL. */
+void
+caja_window_set_active_slot (CajaWindow *window, CajaWindowSlot *new_slot)
+{
+ CajaWindowSlot *old_slot;
+
+ g_assert (CAJA_IS_WINDOW (window));
+
+ if (new_slot)
+ {
+ g_assert (CAJA_IS_WINDOW_SLOT (new_slot));
+ g_assert (CAJA_IS_WINDOW_PANE (new_slot->pane));
+ g_assert (window == new_slot->pane->window);
+ g_assert (g_list_find (new_slot->pane->slots, new_slot) != NULL);
+ }
+
+ if (window->details->active_pane != NULL)
+ {
+ old_slot = window->details->active_pane->active_slot;
+ }
+ else
+ {
+ old_slot = NULL;
+ }
+
+ if (old_slot == new_slot)
+ {
+ return;
+ }
+
+ /* make old slot inactive if it exists (may be NULL after init, for example) */
+ if (old_slot != NULL)
+ {
+ /* inform window */
+ if (old_slot->content_view != NULL)
+ {
+ caja_window_slot_disconnect_content_view (old_slot, old_slot->content_view);
+ }
+
+ /* inform slot & view */
+ g_signal_emit_by_name (old_slot, "inactive");
+ }
+
+ /* deal with panes */
+ if (new_slot &&
+ new_slot->pane != window->details->active_pane)
+ {
+ real_set_active_pane (window, new_slot->pane);
+ }
+
+ window->details->active_pane->active_slot = new_slot;
+
+ /* make new slot active, if it exists */
+ if (new_slot)
+ {
+ window->details->active_pane->active_slots =
+ g_list_remove (window->details->active_pane->active_slots, new_slot);
+ window->details->active_pane->active_slots =
+ g_list_prepend (window->details->active_pane->active_slots, new_slot);
+
+ /* inform sidebar panels */
+ caja_window_report_location_change (window);
+ /* TODO decide whether "selection-changed" should be emitted */
+
+ if (new_slot->content_view != NULL)
+ {
+ /* inform window */
+ caja_window_slot_connect_content_view (new_slot, new_slot->content_view);
+ }
+
+ /* inform slot & view */
+ g_signal_emit_by_name (new_slot, "active");
+ }
+}
+
+void
+caja_window_slot_close (CajaWindowSlot *slot)
+{
+ caja_window_pane_slot_close (slot->pane, slot);
+}
+
+static void
+caja_window_size_request (GtkWidget *widget,
+ GtkRequisition *requisition)
+{
+ GdkScreen *screen;
+ guint max_width;
+ guint max_height;
+
+ g_assert (CAJA_IS_WINDOW (widget));
+ g_assert (requisition != NULL);
+
+ GTK_WIDGET_CLASS (caja_window_parent_class)->size_request (widget, requisition);
+
+ screen = gtk_window_get_screen (GTK_WINDOW (widget));
+
+ /* Limit the requisition to be within 90% of the available screen
+ * real state.
+ *
+ * This way the user will have a fighting chance of getting
+ * control of their window back if for whatever reason one of the
+ * window's descendants decide they want to be 4000 pixels wide.
+ *
+ * Note that the user can still make the window really huge by hand.
+ *
+ * Bugs in components or other widgets that cause such huge geometries
+ * to be requested, should still be fixed. This code is here only to
+ * prevent the extremely frustrating consequence of such bugs.
+ */
+ max_width = get_max_forced_width (screen);
+ max_height = get_max_forced_height (screen);
+
+ if (requisition->width > (int) max_width)
+ {
+ requisition->width = max_width;
+ }
+
+ if (requisition->height > (int) max_height)
+ {
+ requisition->height = max_height;
+ }
+}
+
+static void
+caja_window_realize (GtkWidget *widget)
+{
+ GTK_WIDGET_CLASS (caja_window_parent_class)->realize (widget);
+ update_cursor (CAJA_WINDOW (widget));
+}
+
+static gboolean
+caja_window_key_press_event (GtkWidget *widget,
+ GdkEventKey *event)
+{
+ CajaWindow *window;
+ int i;
+
+ window = CAJA_WINDOW (widget);
+
+ for (i = 0; i < G_N_ELEMENTS (extra_window_keybindings); i++)
+ {
+ if (extra_window_keybindings[i].keyval == event->keyval)
+ {
+ const GList *action_groups;
+ GtkAction *action;
+
+ action = NULL;
+
+ action_groups = gtk_ui_manager_get_action_groups (window->details->ui_manager);
+ while (action_groups != NULL && action == NULL)
+ {
+ action = gtk_action_group_get_action (action_groups->data, extra_window_keybindings[i].action);
+ action_groups = action_groups->next;
+ }
+
+ g_assert (action != NULL);
+ if (gtk_action_is_sensitive (action))
+ {
+ gtk_action_activate (action);
+ return TRUE;
+ }
+
+ break;
+ }
+ }
+
+ return GTK_WIDGET_CLASS (caja_window_parent_class)->key_press_event (widget, event);
+}
+
+/*
+ * Main API
+ */
+
+static void
+free_activate_view_data (gpointer data)
+{
+ ActivateViewData *activate_data;
+
+ activate_data = data;
+
+ g_free (activate_data->id);
+
+ g_slice_free (ActivateViewData, activate_data);
+}
+
+static void
+action_view_as_callback (GtkAction *action,
+ ActivateViewData *data)
+{
+ CajaWindow *window;
+ CajaWindowSlot *slot;
+
+ window = data->window;
+
+ if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
+ {
+ slot = window->details->active_pane->active_slot;
+ caja_window_slot_set_content_view (slot,
+ data->id);
+ }
+}
+
+static GtkRadioAction *
+add_view_as_menu_item (CajaWindow *window,
+ const char *placeholder_path,
+ const char *identifier,
+ int index, /* extra_viewer is always index 0 */
+ guint merge_id)
+{
+ const CajaViewInfo *info;
+ GtkRadioAction *action;
+ char action_name[32];
+ ActivateViewData *data;
+
+ char accel[32];
+ char accel_path[48];
+ unsigned int accel_keyval;
+
+ info = caja_view_factory_lookup (identifier);
+
+ g_snprintf (action_name, sizeof (action_name), "view_as_%d", index);
+ action = gtk_radio_action_new (action_name,
+ _(info->view_menu_label_with_mnemonic),
+ _(info->display_location_label),
+ NULL,
+ 0);
+
+ if (index >= 1 && index <= 9)
+ {
+ g_snprintf (accel, sizeof (accel), "%d", index);
+ g_snprintf (accel_path, sizeof (accel_path), "<Caja-Window>/%s", action_name);
+
+ accel_keyval = gdk_keyval_from_name (accel);
+ g_assert (accel_keyval != GDK_VoidSymbol);
+
+ gtk_accel_map_add_entry (accel_path, accel_keyval, GDK_CONTROL_MASK);
+ gtk_action_set_accel_path (GTK_ACTION (action), accel_path);
+ }
+
+ if (window->details->view_as_radio_action != NULL)
+ {
+ gtk_radio_action_set_group (action,
+ gtk_radio_action_get_group (window->details->view_as_radio_action));
+ }
+ else if (index != 0)
+ {
+ /* Index 0 is the extra view, and we don't want to use that here,
+ as it can get deleted/changed later */
+ window->details->view_as_radio_action = action;
+ }
+
+ data = g_slice_new (ActivateViewData);
+ data->window = window;
+ data->id = g_strdup (identifier);
+ g_signal_connect_data (action, "activate",
+ G_CALLBACK (action_view_as_callback),
+ data, (GClosureNotify) free_activate_view_data, 0);
+
+ gtk_action_group_add_action (window->details->view_as_action_group,
+ GTK_ACTION (action));
+ g_object_unref (action);
+
+ gtk_ui_manager_add_ui (window->details->ui_manager,
+ merge_id,
+ placeholder_path,
+ action_name,
+ action_name,
+ GTK_UI_MANAGER_MENUITEM,
+ FALSE);
+
+ return action; /* return value owned by group */
+}
+
+/* Make a special first item in the "View as" option menu that represents
+ * the current content view. This should only be called if the current
+ * content view isn't already in the "View as" option menu.
+ */
+static void
+update_extra_viewer_in_view_as_menus (CajaWindow *window,
+ const char *id)
+{
+ gboolean had_extra_viewer;
+
+ had_extra_viewer = window->details->extra_viewer != NULL;
+
+ if (id == NULL)
+ {
+ if (!had_extra_viewer)
+ {
+ return;
+ }
+ }
+ else
+ {
+ if (had_extra_viewer
+ && strcmp (window->details->extra_viewer, id) == 0)
+ {
+ return;
+ }
+ }
+ g_free (window->details->extra_viewer);
+ window->details->extra_viewer = g_strdup (id);
+
+ if (window->details->extra_viewer_merge_id != 0)
+ {
+ gtk_ui_manager_remove_ui (window->details->ui_manager,
+ window->details->extra_viewer_merge_id);
+ window->details->extra_viewer_merge_id = 0;
+ }
+
+ if (window->details->extra_viewer_radio_action != NULL)
+ {
+ gtk_action_group_remove_action (window->details->view_as_action_group,
+ GTK_ACTION (window->details->extra_viewer_radio_action));
+ window->details->extra_viewer_radio_action = NULL;
+ }
+
+ if (id != NULL)
+ {
+ window->details->extra_viewer_merge_id = gtk_ui_manager_new_merge_id (window->details->ui_manager);
+ window->details->extra_viewer_radio_action =
+ add_view_as_menu_item (window,
+ CAJA_MENU_PATH_EXTRA_VIEWER_PLACEHOLDER,
+ window->details->extra_viewer,
+ 0,
+ window->details->extra_viewer_merge_id);
+ }
+}
+
+static void
+remove_extra_viewer_in_view_as_menus (CajaWindow *window)
+{
+ update_extra_viewer_in_view_as_menus (window, NULL);
+}
+
+static void
+replace_extra_viewer_in_view_as_menus (CajaWindow *window)
+{
+ CajaWindowSlot *slot;
+ const char *id;
+
+ slot = window->details->active_pane->active_slot;
+
+ id = caja_window_slot_get_content_view_id (slot);
+ update_extra_viewer_in_view_as_menus (window, id);
+}
+
+/**
+ * caja_window_synch_view_as_menus:
+ *
+ * Set the visible item of the "View as" option menu and
+ * the marked "View as" item in the View menu to
+ * match the current content view.
+ *
+ * @window: The CajaWindow whose "View as" option menu should be synched.
+ */
+static void
+caja_window_synch_view_as_menus (CajaWindow *window)
+{
+ CajaWindowSlot *slot;
+ int index;
+ char action_name[32];
+ GList *node;
+ GtkAction *action;
+
+ g_assert (CAJA_IS_WINDOW (window));
+
+ slot = window->details->active_pane->active_slot;
+
+ if (slot->content_view == NULL)
+ {
+ return;
+ }
+ for (node = window->details->short_list_viewers, index = 1;
+ node != NULL;
+ node = node->next, ++index)
+ {
+ if (caja_window_slot_content_view_matches_iid (slot, (char *)node->data))
+ {
+ break;
+ }
+ }
+ if (node == NULL)
+ {
+ replace_extra_viewer_in_view_as_menus (window);
+ index = 0;
+ }
+ else
+ {
+ remove_extra_viewer_in_view_as_menus (window);
+ }
+
+ g_snprintf (action_name, sizeof (action_name), "view_as_%d", index);
+ action = gtk_action_group_get_action (window->details->view_as_action_group,
+ action_name);
+
+ /* Don't trigger the action callback when we're synchronizing */
+ g_signal_handlers_block_matched (action,
+ G_SIGNAL_MATCH_FUNC,
+ 0, 0,
+ NULL,
+ action_view_as_callback,
+ NULL);
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE);
+ g_signal_handlers_unblock_matched (action,
+ G_SIGNAL_MATCH_FUNC,
+ 0, 0,
+ NULL,
+ action_view_as_callback,
+ NULL);
+}
+
+static void
+refresh_stored_viewers (CajaWindow *window)
+{
+ CajaWindowSlot *slot;
+ GList *viewers;
+ char *uri, *mimetype;
+
+ slot = window->details->active_pane->active_slot;
+
+ uri = caja_file_get_uri (slot->viewed_file);
+ mimetype = caja_file_get_mime_type (slot->viewed_file);
+ viewers = caja_view_factory_get_views_for_uri (uri,
+ caja_file_get_file_type (slot->viewed_file),
+ mimetype);
+ g_free (uri);
+ g_free (mimetype);
+
+ free_stored_viewers (window);
+ window->details->short_list_viewers = viewers;
+}
+
+static void
+load_view_as_menu (CajaWindow *window)
+{
+ CajaWindowSlot *slot;
+ GList *node;
+ int index;
+ guint merge_id;
+
+ slot = window->details->active_pane->active_slot;
+
+ if (window->details->short_list_merge_id != 0)
+ {
+ gtk_ui_manager_remove_ui (window->details->ui_manager,
+ window->details->short_list_merge_id);
+ window->details->short_list_merge_id = 0;
+ }
+ if (window->details->extra_viewer_merge_id != 0)
+ {
+ gtk_ui_manager_remove_ui (window->details->ui_manager,
+ window->details->extra_viewer_merge_id);
+ window->details->extra_viewer_merge_id = 0;
+ window->details->extra_viewer_radio_action = NULL;
+ }
+ if (window->details->view_as_action_group != NULL)
+ {
+ gtk_ui_manager_remove_action_group (window->details->ui_manager,
+ window->details->view_as_action_group);
+ window->details->view_as_action_group = NULL;
+ }
+
+
+ refresh_stored_viewers (window);
+
+ merge_id = gtk_ui_manager_new_merge_id (window->details->ui_manager);
+ window->details->short_list_merge_id = merge_id;
+ window->details->view_as_action_group = gtk_action_group_new ("ViewAsGroup");
+ gtk_action_group_set_translation_domain (window->details->view_as_action_group, GETTEXT_PACKAGE);
+ window->details->view_as_radio_action = NULL;
+
+ /* Add a menu item for each view in the preferred list for this location. */
+ /* Start on 1, because extra_viewer gets index 0 */
+ for (node = window->details->short_list_viewers, index = 1;
+ node != NULL;
+ node = node->next, ++index)
+ {
+ /* Menu item in View menu. */
+ add_view_as_menu_item (window,
+ CAJA_MENU_PATH_SHORT_LIST_PLACEHOLDER,
+ node->data,
+ index,
+ merge_id);
+ }
+ gtk_ui_manager_insert_action_group (window->details->ui_manager,
+ window->details->view_as_action_group,
+ -1);
+ g_object_unref (window->details->view_as_action_group); /* owned by ui_manager */
+
+ caja_window_synch_view_as_menus (window);
+
+ g_signal_emit (window, signals[VIEW_AS_CHANGED], 0);
+
+}
+
+static void
+load_view_as_menus_callback (CajaFile *file,
+ gpointer callback_data)
+{
+ CajaWindow *window;
+ CajaWindowSlot *slot;
+
+ slot = callback_data;
+ window = CAJA_WINDOW (slot->pane->window);
+
+ if (slot == window->details->active_pane->active_slot)
+ {
+ load_view_as_menu (window);
+ }
+}
+
+static void
+cancel_view_as_callback (CajaWindowSlot *slot)
+{
+ caja_file_cancel_call_when_ready (slot->viewed_file,
+ load_view_as_menus_callback,
+ slot);
+}
+
+void
+caja_window_load_view_as_menus (CajaWindow *window)
+{
+ CajaWindowSlot *slot;
+ CajaFileAttributes attributes;
+
+ g_return_if_fail (CAJA_IS_WINDOW (window));
+
+ attributes = caja_mime_actions_get_required_file_attributes ();
+
+ slot = window->details->active_pane->active_slot;
+
+ cancel_view_as_callback (slot);
+ caja_file_call_when_ready (slot->viewed_file,
+ attributes,
+ load_view_as_menus_callback,
+ slot);
+}
+
+void
+caja_window_display_error (CajaWindow *window, const char *error_msg)
+{
+ GtkWidget *dialog;
+
+ g_return_if_fail (CAJA_IS_WINDOW (window));
+
+ dialog = gtk_message_dialog_new (GTK_WINDOW (window), 0, GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_OK, error_msg, NULL);
+ gtk_widget_show (dialog);
+}
+
+static char *
+real_get_title (CajaWindow *window)
+{
+ g_assert (CAJA_IS_WINDOW (window));
+
+ return caja_window_slot_get_title (window->details->active_pane->active_slot);
+}
+
+static void
+real_sync_title (CajaWindow *window,
+ CajaWindowSlot *slot)
+{
+ char *copy;
+
+ if (slot == window->details->active_pane->active_slot)
+ {
+ copy = g_strdup (slot->title);
+ g_signal_emit_by_name (window, "title_changed",
+ slot->title);
+ g_free (copy);
+ }
+}
+
+void
+caja_window_sync_title (CajaWindow *window,
+ CajaWindowSlot *slot)
+{
+ EEL_CALL_METHOD (CAJA_WINDOW_CLASS, window,
+ sync_title, (window, slot));
+}
+
+void
+caja_window_sync_zoom_widgets (CajaWindow *window)
+{
+ CajaWindowSlot *slot;
+ CajaView *view;
+ GtkAction *action;
+ gboolean supports_zooming;
+ gboolean can_zoom, can_zoom_in, can_zoom_out;
+ CajaZoomLevel zoom_level;
+
+ slot = window->details->active_pane->active_slot;
+ view = slot->content_view;
+
+ if (view != NULL)
+ {
+ supports_zooming = caja_view_supports_zooming (view);
+ zoom_level = caja_view_get_zoom_level (view);
+ can_zoom = supports_zooming &&
+ zoom_level >= CAJA_ZOOM_LEVEL_SMALLEST &&
+ zoom_level <= CAJA_ZOOM_LEVEL_LARGEST;
+ can_zoom_in = can_zoom && caja_view_can_zoom_in (view);
+ can_zoom_out = can_zoom && caja_view_can_zoom_out (view);
+ }
+ else
+ {
+ zoom_level = CAJA_ZOOM_LEVEL_STANDARD;
+ supports_zooming = FALSE;
+ can_zoom = FALSE;
+ can_zoom_in = FALSE;
+ can_zoom_out = FALSE;
+ }
+
+ action = gtk_action_group_get_action (window->details->main_action_group,
+ CAJA_ACTION_ZOOM_IN);
+ gtk_action_set_visible (action, supports_zooming);
+ gtk_action_set_sensitive (action, can_zoom_in);
+
+ action = gtk_action_group_get_action (window->details->main_action_group,
+ CAJA_ACTION_ZOOM_OUT);
+ gtk_action_set_visible (action, supports_zooming);
+ gtk_action_set_sensitive (action, can_zoom_out);
+
+ action = gtk_action_group_get_action (window->details->main_action_group,
+ CAJA_ACTION_ZOOM_NORMAL);
+ gtk_action_set_visible (action, supports_zooming);
+ gtk_action_set_sensitive (action, can_zoom);
+
+ g_signal_emit (window, signals[ZOOM_CHANGED], 0,
+ zoom_level, supports_zooming, can_zoom,
+ can_zoom_in, can_zoom_out);
+}
+
+static void
+zoom_level_changed_callback (CajaView *view,
+ CajaWindow *window)
+{
+ g_assert (CAJA_IS_WINDOW (window));
+
+ /* This is called each time the component in
+ * the active slot successfully completed
+ * a zooming operation.
+ */
+ caja_window_sync_zoom_widgets (window);
+}
+
+
+/* These are called
+ * A) when switching the view within the active slot
+ * B) when switching the active slot
+ * C) when closing the active slot (disconnect)
+*/
+void
+caja_window_connect_content_view (CajaWindow *window,
+ CajaView *view)
+{
+ CajaWindowSlot *slot;
+
+ g_assert (CAJA_IS_WINDOW (window));
+ g_assert (CAJA_IS_VIEW (view));
+
+ slot = caja_window_get_slot_for_view (window, view);
+ g_assert (slot == caja_window_get_active_slot (window));
+
+ g_signal_connect (view, "zoom-level-changed",
+ G_CALLBACK (zoom_level_changed_callback),
+ window);
+
+ /* Update displayed view in menu. Only do this if we're not switching
+ * locations though, because if we are switching locations we'll
+ * install a whole new set of views in the menu later (the current
+ * views in the menu are for the old location).
+ */
+ if (slot->pending_location == NULL)
+ {
+ caja_window_load_view_as_menus (window);
+ }
+
+ caja_view_grab_focus (view);
+}
+
+void
+caja_window_disconnect_content_view (CajaWindow *window,
+ CajaView *view)
+{
+ CajaWindowSlot *slot;
+
+ g_assert (CAJA_IS_WINDOW (window));
+ g_assert (CAJA_IS_VIEW (view));
+
+ slot = caja_window_get_slot_for_view (window, view);
+ g_assert (slot == caja_window_get_active_slot (window));
+
+ g_signal_handlers_disconnect_by_func (view, G_CALLBACK (zoom_level_changed_callback), window);
+}
+
+/**
+ * caja_window_show:
+ * @widget: GtkWidget
+ *
+ * Call parent and then show/hide window items
+ * base on user prefs.
+ */
+static void
+caja_window_show (GtkWidget *widget)
+{
+ CajaWindow *window;
+
+ window = CAJA_WINDOW (widget);
+
+ GTK_WIDGET_CLASS (caja_window_parent_class)->show (widget);
+
+ caja_window_ui_update (window);
+}
+
+GtkUIManager *
+caja_window_get_ui_manager (CajaWindow *window)
+{
+ g_return_val_if_fail (CAJA_IS_WINDOW (window), NULL);
+
+ return window->details->ui_manager;
+}
+
+CajaWindowPane *
+caja_window_get_next_pane (CajaWindow *window)
+{
+ CajaWindowPane *next_pane;
+ GList *node;
+
+ /* return NULL if there is only one pane */
+ if (!window->details->panes || !window->details->panes->next)
+ {
+ return NULL;
+ }
+
+ /* get next pane in the (wrapped around) list */
+ node = g_list_find (window->details->panes, window->details->active_pane);
+ g_return_val_if_fail (node, NULL);
+ if (node->next)
+ {
+ next_pane = node->next->data;
+ }
+ else
+ {
+ next_pane = window->details->panes->data;
+ }
+
+ return next_pane;
+}
+
+
+void
+caja_window_slot_set_viewed_file (CajaWindowSlot *slot,
+ CajaFile *file)
+{
+ CajaWindow *window;
+ CajaFileAttributes attributes;
+
+ if (slot->viewed_file == file)
+ {
+ return;
+ }
+
+ caja_file_ref (file);
+
+ cancel_view_as_callback (slot);
+
+ if (slot->viewed_file != NULL)
+ {
+ window = slot->pane->window;
+
+ if (CAJA_IS_SPATIAL_WINDOW (window))
+ {
+ caja_file_set_has_open_window (slot->viewed_file,
+ FALSE);
+ }
+ caja_file_monitor_remove (slot->viewed_file,
+ slot);
+ }
+
+ if (file != NULL)
+ {
+ attributes =
+ CAJA_FILE_ATTRIBUTE_INFO |
+ CAJA_FILE_ATTRIBUTE_LINK_INFO;
+ caja_file_monitor_add (file, slot, attributes);
+ }
+
+ caja_file_unref (slot->viewed_file);
+ slot->viewed_file = file;
+}
+
+void
+caja_send_history_list_changed (void)
+{
+ g_signal_emit_by_name (caja_signaller_get_current (),
+ "history_list_changed");
+}
+
+static void
+free_history_list (void)
+{
+ eel_g_object_list_free (history_list);
+ history_list = NULL;
+}
+
+/* Remove the this URI from the history list.
+ * Do not sent out a change notice.
+ * We pass in a bookmark for convenience.
+ */
+static void
+remove_from_history_list (CajaBookmark *bookmark)
+{
+ GList *node;
+
+ /* Compare only the uris here. Comparing the names also is not
+ * necessary and can cause problems due to the asynchronous
+ * nature of when the title of the window is set.
+ */
+ node = g_list_find_custom (history_list,
+ bookmark,
+ caja_bookmark_compare_uris);
+
+ /* Remove any older entry for this same item. There can be at most 1. */
+ if (node != NULL)
+ {
+ history_list = g_list_remove_link (history_list, node);
+ g_object_unref (node->data);
+ g_list_free_1 (node);
+ }
+}
+
+gboolean
+caja_add_bookmark_to_history_list (CajaBookmark *bookmark)
+{
+ /* Note that the history is shared amongst all windows so
+ * this is not a CajaNavigationWindow function. Perhaps it belongs
+ * in its own file.
+ */
+ int i;
+ GList *l, *next;
+ static gboolean free_history_list_is_set_up;
+
+ g_assert (CAJA_IS_BOOKMARK (bookmark));
+
+ if (!free_history_list_is_set_up)
+ {
+ eel_debug_call_at_shutdown (free_history_list);
+ free_history_list_is_set_up = TRUE;
+ }
+
+ /* g_warning ("Add to history list '%s' '%s'",
+ caja_bookmark_get_name (bookmark),
+ caja_bookmark_get_uri (bookmark)); */
+
+ if (!history_list ||
+ caja_bookmark_compare_uris (history_list->data, bookmark))
+ {
+ g_object_ref (bookmark);
+ remove_from_history_list (bookmark);
+ history_list = g_list_prepend (history_list, bookmark);
+
+ for (i = 0, l = history_list; l; l = next)
+ {
+ next = l->next;
+
+ if (i++ >= MAX_HISTORY_ITEMS)
+ {
+ g_object_unref (l->data);
+ history_list = g_list_delete_link (history_list, l);
+ }
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+void
+caja_remove_from_history_list_no_notify (GFile *location)
+{
+ CajaBookmark *bookmark;
+
+ bookmark = caja_bookmark_new (location, "", FALSE, NULL);
+ remove_from_history_list (bookmark);
+ g_object_unref (bookmark);
+}
+
+gboolean
+caja_add_to_history_list_no_notify (GFile *location,
+ const char *name,
+ gboolean has_custom_name,
+ GIcon *icon)
+{
+ CajaBookmark *bookmark;
+ gboolean ret;
+
+ bookmark = caja_bookmark_new (location, name, has_custom_name, icon);
+ ret = caja_add_bookmark_to_history_list (bookmark);
+ g_object_unref (bookmark);
+
+ return ret;
+}
+
+CajaWindowSlot *
+caja_window_get_slot_for_view (CajaWindow *window,
+ CajaView *view)
+{
+ CajaWindowSlot *slot;
+ GList *l, *walk;
+
+ for (walk = window->details->panes; walk; walk = walk->next)
+ {
+ CajaWindowPane *pane = walk->data;
+
+ for (l = pane->slots; l != NULL; l = l->next)
+ {
+ slot = l->data;
+ if (slot->content_view == view ||
+ slot->new_content_view == view)
+ {
+ return slot;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+void
+caja_forget_history (void)
+{
+ CajaWindowSlot *slot;
+ CajaNavigationWindowSlot *navigation_slot;
+ GList *window_node, *l, *walk;
+
+ /* Clear out each window's back & forward lists. Also, remove
+ * each window's current location bookmark from history list
+ * so it doesn't get clobbered.
+ */
+ for (window_node = caja_application_get_window_list ();
+ window_node != NULL;
+ window_node = window_node->next)
+ {
+
+ if (CAJA_IS_NAVIGATION_WINDOW (window_node->data))
+ {
+ CajaNavigationWindow *window;
+
+ window = CAJA_NAVIGATION_WINDOW (window_node->data);
+
+ for (walk = CAJA_WINDOW (window_node->data)->details->panes; walk; walk = walk->next)
+ {
+ CajaWindowPane *pane = walk->data;
+ for (l = pane->slots; l != NULL; l = l->next)
+ {
+ navigation_slot = l->data;
+
+ caja_navigation_window_slot_clear_back_list (navigation_slot);
+ caja_navigation_window_slot_clear_forward_list (navigation_slot);
+ }
+ }
+
+ caja_navigation_window_allow_back (window, FALSE);
+ caja_navigation_window_allow_forward (window, FALSE);
+ }
+
+ for (walk = CAJA_WINDOW (window_node->data)->details->panes; walk; walk = walk->next)
+ {
+ CajaWindowPane *pane = walk->data;
+ for (l = pane->slots; l != NULL; l = l->next)
+ {
+ slot = l->data;
+ history_list = g_list_remove (history_list,
+ slot->current_location_bookmark);
+ }
+ }
+ }
+
+ /* Clobber history list. */
+ free_history_list ();
+
+ /* Re-add each window's current location to history list. */
+ for (window_node = caja_application_get_window_list ();
+ window_node != NULL;
+ window_node = window_node->next)
+ {
+ CajaWindow *window;
+ CajaWindowSlot *slot;
+ GList *l;
+
+ window = CAJA_WINDOW (window_node->data);
+ for (walk = window->details->panes; walk; walk = walk->next)
+ {
+ CajaWindowPane *pane = walk->data;
+ for (l = pane->slots; l != NULL; l = l->next)
+ {
+ slot = CAJA_WINDOW_SLOT (l->data);
+ caja_window_slot_add_current_location_to_history_list (slot);
+ }
+ }
+ }
+}
+
+GList *
+caja_get_history_list (void)
+{
+ return history_list;
+}
+
+static GList *
+caja_window_get_history (CajaWindow *window)
+{
+ return eel_g_object_list_copy (history_list);
+}
+
+
+static CajaWindowType
+caja_window_get_window_type (CajaWindow *window)
+{
+ g_assert (CAJA_IS_WINDOW (window));
+
+ return CAJA_WINDOW_GET_CLASS (window)->window_type;
+}
+
+static int
+caja_window_get_selection_count (CajaWindow *window)
+{
+ CajaWindowSlot *slot;
+
+ g_assert (CAJA_IS_WINDOW (window));
+
+ slot = window->details->active_pane->active_slot;
+
+ if (slot->content_view != NULL)
+ {
+ return caja_view_get_selection_count (slot->content_view);
+ }
+
+ return 0;
+}
+
+static GList *
+caja_window_get_selection (CajaWindow *window)
+{
+ CajaWindowSlot *slot;
+
+ g_assert (CAJA_IS_WINDOW (window));
+
+ slot = window->details->active_pane->active_slot;
+
+ if (slot->content_view != NULL)
+ {
+ return caja_view_get_selection (slot->content_view);
+ }
+ return NULL;
+}
+
+static CajaWindowShowHiddenFilesMode
+caja_window_get_hidden_files_mode (CajaWindowInfo *window)
+{
+ return window->details->show_hidden_files_mode;
+}
+
+static void
+caja_window_set_hidden_files_mode (CajaWindowInfo *window,
+ CajaWindowShowHiddenFilesMode mode)
+{
+ window->details->show_hidden_files_mode = mode;
+
+ g_signal_emit_by_name (window, "hidden_files_mode_changed");
+}
+
+static gboolean
+caja_window_get_initiated_unmount (CajaWindowInfo *window)
+{
+ return window->details->initiated_unmount;
+}
+
+static void
+caja_window_set_initiated_unmount (CajaWindowInfo *window,
+ gboolean initiated_unmount)
+{
+ window->details->initiated_unmount = initiated_unmount;
+}
+
+static char *
+caja_window_get_cached_title (CajaWindow *window)
+{
+ CajaWindowSlot *slot;
+
+ g_assert (CAJA_IS_WINDOW (window));
+
+ slot = window->details->active_pane->active_slot;
+
+ return g_strdup (slot->title);
+}
+
+CajaWindowSlot *
+caja_window_get_active_slot (CajaWindow *window)
+{
+ g_assert (CAJA_IS_WINDOW (window));
+
+ return window->details->active_pane->active_slot;
+}
+
+CajaWindowSlot *
+caja_window_get_extra_slot (CajaWindow *window)
+{
+ CajaWindowPane *extra_pane;
+ GList *node;
+
+ g_assert (CAJA_IS_WINDOW (window));
+
+
+ /* return NULL if there is only one pane */
+ if (window->details->panes == NULL ||
+ window->details->panes->next == NULL)
+ {
+ return NULL;
+ }
+
+ /* get next pane in the (wrapped around) list */
+ node = g_list_find (window->details->panes,
+ window->details->active_pane);
+ g_return_val_if_fail (node, FALSE);
+
+ if (node->next)
+ {
+ extra_pane = node->next->data;
+ }
+ else
+ {
+ extra_pane = window->details->panes->data;
+ }
+
+ return extra_pane->active_slot;
+}
+
+GList *
+caja_window_get_slots (CajaWindow *window)
+{
+ GList *walk,*list;
+
+ g_assert (CAJA_IS_WINDOW (window));
+
+ list = NULL;
+ for (walk = window->details->panes; walk; walk = walk->next)
+ {
+ CajaWindowPane *pane = walk->data;
+ list = g_list_concat (list, g_list_copy(pane->slots));
+ }
+ return list;
+}
+
+static void
+caja_window_info_iface_init (CajaWindowInfoIface *iface)
+{
+ iface->report_load_underway = caja_window_report_load_underway;
+ iface->report_load_complete = caja_window_report_load_complete;
+ iface->report_selection_changed = caja_window_report_selection_changed;
+ iface->report_view_failed = caja_window_report_view_failed;
+ iface->view_visible = caja_window_view_visible;
+ iface->close_window = caja_window_close;
+ iface->push_status = caja_window_push_status;
+ iface->get_window_type = caja_window_get_window_type;
+ iface->get_title = caja_window_get_cached_title;
+ iface->get_history = caja_window_get_history;
+ iface->get_current_location = caja_window_get_location_uri;
+ iface->get_ui_manager = caja_window_get_ui_manager;
+ iface->get_selection_count = caja_window_get_selection_count;
+ iface->get_selection = caja_window_get_selection;
+ iface->get_hidden_files_mode = caja_window_get_hidden_files_mode;
+ iface->set_hidden_files_mode = caja_window_set_hidden_files_mode;
+ iface->get_active_slot = caja_window_get_active_slot;
+ iface->get_extra_slot = caja_window_get_extra_slot;
+ iface->get_initiated_unmount = caja_window_get_initiated_unmount;
+ iface->set_initiated_unmount = caja_window_set_initiated_unmount;
+}
+
+static void
+caja_window_class_init (CajaWindowClass *class)
+{
+ GtkBindingSet *binding_set;
+
+ G_OBJECT_CLASS (class)->finalize = caja_window_finalize;
+ G_OBJECT_CLASS (class)->constructor = caja_window_constructor;
+ G_OBJECT_CLASS (class)->constructed = caja_window_constructed;
+ G_OBJECT_CLASS (class)->get_property = caja_window_get_property;
+ G_OBJECT_CLASS (class)->set_property = caja_window_set_property;
+ GTK_OBJECT_CLASS (class)->destroy = caja_window_destroy;
+ GTK_WIDGET_CLASS (class)->show = caja_window_show;
+ GTK_WIDGET_CLASS (class)->size_request = caja_window_size_request;
+ GTK_WIDGET_CLASS (class)->realize = caja_window_realize;
+ GTK_WIDGET_CLASS (class)->key_press_event = caja_window_key_press_event;
+ class->get_title = real_get_title;
+ class->sync_title = real_sync_title;
+ class->set_allow_up = real_set_allow_up;
+ class->close_slot = real_close_slot;
+
+ g_object_class_install_property (G_OBJECT_CLASS (class),
+ ARG_APP,
+ g_param_spec_object ("app",
+ "Application",
+ "The CajaApplication associated with this window.",
+ CAJA_TYPE_APPLICATION,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+ signals[GO_UP] =
+ g_signal_new ("go_up",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (CajaWindowClass, go_up),
+ g_signal_accumulator_true_handled, NULL,
+ eel_marshal_BOOLEAN__BOOLEAN,
+ G_TYPE_BOOLEAN, 1, G_TYPE_BOOLEAN);
+ signals[RELOAD] =
+ g_signal_new ("reload",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (CajaWindowClass, reload),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ signals[PROMPT_FOR_LOCATION] =
+ g_signal_new ("prompt-for-location",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (CajaWindowClass, prompt_for_location),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE, 1, G_TYPE_STRING);
+ signals[ZOOM_CHANGED] =
+ g_signal_new ("zoom-changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ caja_marshal_VOID__INT_BOOLEAN_BOOLEAN_BOOLEAN_BOOLEAN,
+ G_TYPE_NONE, 5,
+ G_TYPE_INT, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN,
+ G_TYPE_BOOLEAN, G_TYPE_BOOLEAN);
+ signals[VIEW_AS_CHANGED] =
+ g_signal_new ("view-as-changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ binding_set = gtk_binding_set_by_class (class);
+ gtk_binding_entry_add_signal (binding_set, GDK_BackSpace, 0,
+ "go_up", 1,
+ G_TYPE_BOOLEAN, FALSE);
+ gtk_binding_entry_add_signal (binding_set, GDK_F5, 0,
+ "reload", 0);
+ gtk_binding_entry_add_signal (binding_set, GDK_slash, 0,
+ "prompt-for-location", 1,
+ G_TYPE_STRING, "/");
+
+ class->reload = caja_window_reload;
+ class->go_up = caja_window_go_up_signal;
+
+ /* Allow to set the colors of the extra view widgets */
+ gtk_rc_parse_string ("\n"
+ " style \"caja-extra-view-widgets-style-internal\"\n"
+ " {\n"
+ " bg[NORMAL] = \"" EXTRA_VIEW_WIDGETS_BACKGROUND "\"\n"
+ " }\n"
+ "\n"
+ " widget \"*.caja-extra-view-widget\" style:rc \"caja-extra-view-widgets-style-internal\" \n"
+ "\n");
+
+ g_type_class_add_private (G_OBJECT_CLASS (class), sizeof (CajaWindowDetails));
+}
+
+/**
+ * caja_window_has_menubar_and_statusbar:
+ * @window: A #CajaWindow
+ *
+ * Queries whether the window should have a menubar and statusbar, based on the
+ * window_type from its class structure.
+ *
+ * Return value: TRUE if the window should have a menubar and statusbar; FALSE
+ * otherwise.
+ **/
+gboolean
+caja_window_has_menubar_and_statusbar (CajaWindow *window)
+{
+ return (caja_window_get_window_type (window) != CAJA_WINDOW_DESKTOP);
+}
diff --git a/src/caja-window.h b/src/caja-window.h
new file mode 100644
index 00000000..55083446
--- /dev/null
+++ b/src/caja-window.h
@@ -0,0 +1,164 @@
+/*
+ * Caja
+ *
+ * Copyright (C) 1999, 2000 Red Hat, Inc.
+ * Copyright (C) 1999, 2000, 2001 Eazel, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Authors: Elliot Lee <[email protected]>
+ * Darin Adler <[email protected]>
+ *
+ */
+/* caja-window.h: Interface of the main window object */
+
+#ifndef CAJA_WINDOW_H
+#define CAJA_WINDOW_H
+
+#include <gtk/gtk.h>
+#include <eel/eel-glib-extensions.h>
+#include <libcaja-private/caja-bookmark.h>
+#include <libcaja-private/caja-window-info.h>
+#include <libcaja-private/caja-search-directory.h>
+#include "caja-application.h"
+#include "caja-information-panel.h"
+#include "caja-side-pane.h"
+
+#define CAJA_TYPE_WINDOW caja_window_get_type()
+#define CAJA_WINDOW(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_WINDOW, CajaWindow))
+#define CAJA_WINDOW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_WINDOW, CajaWindowClass))
+#define CAJA_IS_WINDOW(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_WINDOW))
+#define CAJA_IS_WINDOW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_WINDOW))
+#define CAJA_WINDOW_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_WINDOW, CajaWindowClass))
+
+#ifndef CAJA_WINDOW_DEFINED
+#define CAJA_WINDOW_DEFINED
+typedef struct CajaWindow CajaWindow;
+#endif
+
+#ifndef CAJA_WINDOW_SLOT_DEFINED
+#define CAJA_WINDOW_SLOT_DEFINED
+typedef struct CajaWindowSlot CajaWindowSlot;
+#endif
+
+typedef struct _CajaWindowPane CajaWindowPane;
+
+typedef struct CajaWindowSlotClass CajaWindowSlotClass;
+typedef enum CajaWindowOpenSlotFlags CajaWindowOpenSlotFlags;
+
+GType caja_window_slot_get_type (void);
+
+typedef enum
+{
+ CAJA_WINDOW_NOT_SHOWN,
+ CAJA_WINDOW_POSITION_SET,
+ CAJA_WINDOW_SHOULD_SHOW
+} CajaWindowShowState;
+
+enum CajaWindowOpenSlotFlags
+{
+ CAJA_WINDOW_OPEN_SLOT_NONE = 0,
+ CAJA_WINDOW_OPEN_SLOT_APPEND = 1
+};
+
+typedef struct CajaWindowDetails CajaWindowDetails;
+
+typedef struct
+{
+ GtkWindowClass parent_spot;
+
+ CajaWindowType window_type;
+ const char *bookmarks_placeholder;
+
+ /* Function pointers for overriding, without corresponding signals */
+
+ char * (* get_title) (CajaWindow *window);
+ void (* sync_title) (CajaWindow *window,
+ CajaWindowSlot *slot);
+ CajaIconInfo * (* get_icon) (CajaWindow *window,
+ CajaWindowSlot *slot);
+
+ void (* sync_allow_stop) (CajaWindow *window,
+ CajaWindowSlot *slot);
+ void (* set_allow_up) (CajaWindow *window, gboolean allow);
+ void (* reload) (CajaWindow *window);
+ void (* prompt_for_location) (CajaWindow *window, const char *initial);
+ void (* get_min_size) (CajaWindow *window, guint *default_width, guint *default_height);
+ void (* get_default_size) (CajaWindow *window, guint *default_width, guint *default_height);
+ void (* close) (CajaWindow *window);
+
+ CajaWindowSlot * (* open_slot) (CajaWindowPane *pane,
+ CajaWindowOpenSlotFlags flags);
+ void (* close_slot) (CajaWindowPane *pane,
+ CajaWindowSlot *slot);
+ void (* set_active_slot) (CajaWindowPane *pane,
+ CajaWindowSlot *slot);
+
+ /* Signals used only for keybindings */
+ gboolean (* go_up) (CajaWindow *window, gboolean close);
+} CajaWindowClass;
+
+struct CajaWindow
+{
+ GtkWindow parent_object;
+
+ CajaWindowDetails *details;
+
+ /** CORBA-related elements **/
+ CajaApplication *application;
+};
+
+GType caja_window_get_type (void);
+void caja_window_show_window (CajaWindow *window);
+void caja_window_close (CajaWindow *window);
+
+void caja_window_connect_content_view (CajaWindow *window,
+ CajaView *view);
+void caja_window_disconnect_content_view (CajaWindow *window,
+ CajaView *view);
+
+void caja_window_go_to (CajaWindow *window,
+ GFile *location);
+void caja_window_go_to_with_selection (CajaWindow *window,
+ GFile *location,
+ GList *new_selection);
+void caja_window_go_home (CajaWindow *window);
+void caja_window_go_up (CajaWindow *window,
+ gboolean close_behind,
+ gboolean new_tab);
+void caja_window_prompt_for_location (CajaWindow *window,
+ const char *initial);
+void caja_window_launch_cd_burner (CajaWindow *window);
+void caja_window_display_error (CajaWindow *window,
+ const char *error_msg);
+void caja_window_reload (CajaWindow *window);
+
+void caja_window_allow_reload (CajaWindow *window,
+ gboolean allow);
+void caja_window_allow_up (CajaWindow *window,
+ gboolean allow);
+void caja_window_allow_stop (CajaWindow *window,
+ gboolean allow);
+void caja_window_allow_burn_cd (CajaWindow *window,
+ gboolean allow);
+GtkUIManager * caja_window_get_ui_manager (CajaWindow *window);
+gboolean caja_window_has_menubar_and_statusbar (CajaWindow *window);
+
+#endif
diff --git a/src/caja-x-content-bar.c b/src/caja-x-content-bar.c
new file mode 100644
index 00000000..a936bb3e
--- /dev/null
+++ b/src/caja-x-content-bar.c
@@ -0,0 +1,331 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2008 Red Hat, Inc.
+ * Copyright (C) 2006 Paolo Borelli <[email protected]>
+ *
+ * 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: David Zeuthen <[email protected]>
+ * Paolo Borelli <[email protected]>
+ *
+ */
+
+#include "config.h"
+
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+#include <string.h>
+
+#include "caja-x-content-bar.h"
+#include <libcaja-private/caja-autorun.h>
+#include <libcaja-private/caja-icon-info.h>
+
+#define CAJA_X_CONTENT_BAR_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CAJA_TYPE_X_CONTENT_BAR, CajaXContentBarPrivate))
+
+struct CajaXContentBarPrivate
+{
+ GtkWidget *label;
+ GtkWidget *button;
+
+ char *x_content_type;
+ GMount *mount;
+};
+
+enum
+{
+ PROP_0,
+ PROP_MOUNT,
+ PROP_X_CONTENT_TYPE,
+};
+
+G_DEFINE_TYPE (CajaXContentBar, caja_x_content_bar, GTK_TYPE_HBOX)
+
+void
+caja_x_content_bar_set_x_content_type (CajaXContentBar *bar, const char *x_content_type)
+{
+ char *message;
+ char *description;
+ GAppInfo *default_app;
+
+ g_free (bar->priv->x_content_type);
+ bar->priv->x_content_type = g_strdup (x_content_type);
+
+ description = g_content_type_get_description (x_content_type);
+
+ /* Customize greeting for well-known x-content types */
+ if (strcmp (x_content_type, "x-content/audio-cdda") == 0)
+ {
+ message = g_strdup (_("These files are on an Audio CD."));
+ }
+ else if (strcmp (x_content_type, "x-content/audio-dvd") == 0)
+ {
+ message = g_strdup (_("These files are on an Audio DVD."));
+ }
+ else if (strcmp (x_content_type, "x-content/video-dvd") == 0)
+ {
+ message = g_strdup (_("These files are on a Video DVD."));
+ }
+ else if (strcmp (x_content_type, "x-content/video-vcd") == 0)
+ {
+ message = g_strdup (_("These files are on a Video CD."));
+ }
+ else if (strcmp (x_content_type, "x-content/video-svcd") == 0)
+ {
+ message = g_strdup (_("These files are on a Super Video CD."));
+ }
+ else if (strcmp (x_content_type, "x-content/image-photocd") == 0)
+ {
+ message = g_strdup (_("These files are on a Photo CD."));
+ }
+ else if (strcmp (x_content_type, "x-content/image-picturecd") == 0)
+ {
+ message = g_strdup (_("These files are on a Picture CD."));
+ }
+ else if (strcmp (x_content_type, "x-content/image-dcf") == 0)
+ {
+ message = g_strdup (_("The media contains digital photos."));
+ }
+ else if (strcmp (x_content_type, "x-content/audio-player") == 0)
+ {
+ message = g_strdup (_("These files are on a digital audio player."));
+ }
+ else if (strcmp (x_content_type, "x-content/software") == 0)
+ {
+ message = g_strdup (_("The media contains software."));
+ }
+ else
+ {
+ /* fallback to generic greeting */
+ message = g_strdup_printf (_("The media has been detected as \"%s\"."), description);
+ }
+
+
+ gtk_label_set_text (GTK_LABEL (bar->priv->label), message);
+ gtk_widget_show (bar->priv->label);
+
+ /* TODO: We really need a GtkBrowserBackButton-ish widget here.. until then, we only
+ * show the default application. */
+
+ default_app = g_app_info_get_default_for_type (x_content_type, FALSE);
+ if (default_app != NULL)
+ {
+ char *button_text;
+ const char *name;
+ GIcon *icon;
+ GtkWidget *image;
+
+ icon = g_app_info_get_icon (default_app);
+ if (icon != NULL)
+ {
+ GdkPixbuf *pixbuf;
+ int icon_size;
+ CajaIconInfo *icon_info;
+ icon_size = caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_BUTTON);
+ icon_info = caja_icon_info_lookup (icon, icon_size);
+ pixbuf = caja_icon_info_get_pixbuf_at_size (icon_info, icon_size);
+ image = gtk_image_new_from_pixbuf (pixbuf);
+ g_object_unref (pixbuf);
+ g_object_unref (icon_info);
+ }
+ else
+ {
+ image = NULL;
+ }
+
+ name = g_app_info_get_display_name (default_app);
+ button_text = g_strdup_printf (_("Open %s"), name);
+
+ gtk_button_set_image (GTK_BUTTON (bar->priv->button), image);
+ gtk_button_set_label (GTK_BUTTON (bar->priv->button), button_text);
+ gtk_widget_show (bar->priv->button);
+ g_free (button_text);
+ g_object_unref (default_app);
+ }
+ else
+ {
+ gtk_widget_hide (bar->priv->button);
+ }
+
+ g_free (message);
+ g_free (description);
+}
+
+const char *
+caja_x_content_bar_get_x_content_type (CajaXContentBar *bar)
+{
+ return bar->priv->x_content_type;
+}
+
+GMount *
+caja_x_content_bar_get_mount (CajaXContentBar *bar)
+{
+ return bar->priv->mount != NULL ? g_object_ref (bar->priv->mount) : NULL;
+}
+
+void
+caja_x_content_bar_set_mount (CajaXContentBar *bar, GMount *mount)
+{
+ if (bar->priv->mount != NULL)
+ {
+ g_object_unref (bar->priv->mount);
+ }
+ bar->priv->mount = mount != NULL ? g_object_ref (mount) : NULL;
+}
+
+
+static void
+caja_x_content_bar_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CajaXContentBar *bar;
+
+ bar = CAJA_X_CONTENT_BAR (object);
+
+ switch (prop_id)
+ {
+ case PROP_MOUNT:
+ caja_x_content_bar_set_mount (bar, G_MOUNT (g_value_get_object (value)));
+ break;
+ case PROP_X_CONTENT_TYPE:
+ caja_x_content_bar_set_x_content_type (bar, g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+caja_x_content_bar_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ CajaXContentBar *bar;
+
+ bar = CAJA_X_CONTENT_BAR (object);
+
+ switch (prop_id)
+ {
+ case PROP_MOUNT:
+ g_value_set_object (value, bar->priv->mount);
+ break;
+ case PROP_X_CONTENT_TYPE:
+ g_value_set_string (value, bar->priv->x_content_type);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+caja_x_content_bar_finalize (GObject *object)
+{
+ CajaXContentBar *bar = CAJA_X_CONTENT_BAR (object);
+
+ g_free (bar->priv->x_content_type);
+ if (bar->priv->mount != NULL)
+ g_object_unref (bar->priv->mount);
+
+ G_OBJECT_CLASS (caja_x_content_bar_parent_class)->finalize (object);
+}
+
+static void
+caja_x_content_bar_class_init (CajaXContentBarClass *klass)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->get_property = caja_x_content_bar_get_property;
+ object_class->set_property = caja_x_content_bar_set_property;
+ object_class->finalize = caja_x_content_bar_finalize;
+
+ g_type_class_add_private (klass, sizeof (CajaXContentBarPrivate));
+
+ g_object_class_install_property (object_class,
+ PROP_MOUNT,
+ g_param_spec_object (
+ "mount",
+ "The GMount to run programs for",
+ "The GMount to run programs for",
+ G_TYPE_MOUNT,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class,
+ PROP_X_CONTENT_TYPE,
+ g_param_spec_string (
+ "x-content-type",
+ "The x-content type for the cluebar",
+ "The x-content type for the cluebar",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+}
+
+static void
+button_clicked_callback (GtkWidget *button, CajaXContentBar *bar)
+{
+ GAppInfo *default_app;
+
+ if (bar->priv->x_content_type == NULL ||
+ bar->priv->mount == NULL)
+ return;
+
+ default_app = g_app_info_get_default_for_type (bar->priv->x_content_type, FALSE);
+ if (default_app != NULL)
+ {
+ caja_autorun_launch_for_mount (bar->priv->mount, default_app);
+ g_object_unref (default_app);
+ }
+}
+
+static void
+caja_x_content_bar_init (CajaXContentBar *bar)
+{
+ GtkWidget *hbox;
+
+ bar->priv = CAJA_X_CONTENT_BAR_GET_PRIVATE (bar);
+
+ hbox = GTK_WIDGET (bar);
+
+ bar->priv->label = gtk_label_new (NULL);
+ gtk_label_set_ellipsize (GTK_LABEL (bar->priv->label), PANGO_ELLIPSIZE_END);
+ gtk_misc_set_alignment (GTK_MISC (bar->priv->label), 0.0, 0.5);
+ gtk_box_pack_start (GTK_BOX (bar), bar->priv->label, TRUE, TRUE, 0);
+
+ bar->priv->button = gtk_button_new ();
+ gtk_box_pack_end (GTK_BOX (hbox), bar->priv->button, FALSE, FALSE, 0);
+
+ g_signal_connect (bar->priv->button,
+ "clicked",
+ G_CALLBACK (button_clicked_callback),
+ bar);
+}
+
+GtkWidget *
+caja_x_content_bar_new (GMount *mount,
+ const char *x_content_type)
+{
+ GObject *bar;
+
+ bar = g_object_new (CAJA_TYPE_X_CONTENT_BAR,
+ "mount", mount,
+ "x-content-type", x_content_type,
+ NULL);
+
+ return GTK_WIDGET (bar);
+}
diff --git a/src/caja-x-content-bar.h b/src/caja-x-content-bar.h
new file mode 100644
index 00000000..fb844a82
--- /dev/null
+++ b/src/caja-x-content-bar.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2008 Red Hat, Inc.
+ * Copyright (C) 2006 Paolo Borelli <[email protected]>
+ *
+ * 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: David Zeuthen <[email protected]>
+ * Paolo Borelli <[email protected]>
+ *
+ */
+
+#ifndef __CAJA_X_CONTENT_BAR_H
+#define __CAJA_X_CONTENT_BAR_H
+
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define CAJA_TYPE_X_CONTENT_BAR (caja_x_content_bar_get_type ())
+#define CAJA_X_CONTENT_BAR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), CAJA_TYPE_X_CONTENT_BAR, CajaXContentBar))
+#define CAJA_X_CONTENT_BAR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), CAJA_TYPE_X_CONTENT_BAR, CajaXContentBarClass))
+#define CAJA_IS_X_CONTENT_BAR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), CAJA_TYPE_X_CONTENT_BAR))
+#define CAJA_IS_X_CONTENT_BAR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), CAJA_TYPE_X_CONTENT_BAR))
+#define CAJA_X_CONTENT_BAR_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), CAJA_TYPE_X_CONTENT_BAR, CajaXContentBarClass))
+
+ typedef struct CajaXContentBarPrivate CajaXContentBarPrivate;
+
+ typedef struct
+ {
+ GtkHBox box;
+
+ CajaXContentBarPrivate *priv;
+ } CajaXContentBar;
+
+ typedef struct
+ {
+ GtkHBoxClass parent_class;
+ } CajaXContentBarClass;
+
+ GType caja_x_content_bar_get_type (void) G_GNUC_CONST;
+
+ GtkWidget *caja_x_content_bar_new (GMount *mount,
+ const char *x_content_type);
+ const char *caja_x_content_bar_get_x_content_type (CajaXContentBar *bar);
+ void caja_x_content_bar_set_x_content_type (CajaXContentBar *bar,
+ const char *x_content_type);
+ void caja_x_content_bar_set_mount (CajaXContentBar *bar,
+ GMount *mount);
+ GMount *caja_x_content_bar_get_mount (CajaXContentBar *bar);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __CAJA_X_CONTENT_BAR_H */
diff --git a/src/caja-zoom-action.c b/src/caja-zoom-action.c
new file mode 100644
index 00000000..690042e0
--- /dev/null
+++ b/src/caja-zoom-action.c
@@ -0,0 +1,211 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2009 Red Hat, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Authors: Alexander Larsson <[email protected]>
+ *
+ */
+
+#include <config.h>
+
+#include "caja-zoom-action.h"
+#include "caja-zoom-control.h"
+#include "caja-navigation-window.h"
+#include "caja-window-private.h"
+#include "caja-navigation-window-slot.h"
+#include <gtk/gtk.h>
+#include <eel/eel-gtk-extensions.h>
+
+G_DEFINE_TYPE (CajaZoomAction, caja_zoom_action, GTK_TYPE_ACTION)
+
+static void caja_zoom_action_init (CajaZoomAction *action);
+static void caja_zoom_action_class_init (CajaZoomActionClass *class);
+
+static GObjectClass *parent_class = NULL;
+
+#define CAJA_ZOOM_ACTION_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), CAJA_TYPE_ZOOM_ACTION, CajaZoomActionPrivate))
+
+struct CajaZoomActionPrivate
+{
+ CajaNavigationWindow *window;
+};
+
+enum
+{
+ PROP_0,
+ PROP_WINDOW
+};
+
+static void
+zoom_changed_callback (CajaWindow *window,
+ CajaZoomLevel zoom_level,
+ gboolean supports_zooming,
+ gboolean can_zoom,
+ gboolean can_zoom_in,
+ gboolean can_zoom_out,
+ GtkWidget *zoom_control)
+{
+ if (supports_zooming)
+ {
+ gtk_widget_set_sensitive (zoom_control, can_zoom);
+ gtk_widget_show (zoom_control);
+ if (can_zoom)
+ {
+ caja_zoom_control_set_zoom_level (CAJA_ZOOM_CONTROL (zoom_control),
+ zoom_level);
+ }
+ }
+ else
+ {
+ gtk_widget_hide (zoom_control);
+ }
+}
+
+static void
+connect_proxy (GtkAction *action,
+ GtkWidget *proxy)
+{
+ if (GTK_IS_TOOL_ITEM (proxy))
+ {
+ GtkToolItem *item = GTK_TOOL_ITEM (proxy);
+ CajaZoomAction *zaction = CAJA_ZOOM_ACTION (action);
+ CajaNavigationWindow *window = zaction->priv->window;
+ GtkWidget *zoom_control;
+
+ zoom_control = caja_zoom_control_new ();
+ gtk_container_set_border_width (GTK_CONTAINER (item), 4);
+ gtk_container_add (GTK_CONTAINER (item), zoom_control);
+ gtk_widget_show (zoom_control);
+
+ g_signal_connect_object (zoom_control, "zoom_in",
+ G_CALLBACK (caja_window_zoom_in),
+ window, G_CONNECT_SWAPPED);
+ g_signal_connect_object (zoom_control, "zoom_out",
+ G_CALLBACK (caja_window_zoom_out),
+ window, G_CONNECT_SWAPPED);
+ g_signal_connect_object (zoom_control, "zoom_to_level",
+ G_CALLBACK (caja_window_zoom_to_level),
+ window, G_CONNECT_SWAPPED);
+ g_signal_connect_object (zoom_control, "zoom_to_default",
+ G_CALLBACK (caja_window_zoom_to_default),
+ window, G_CONNECT_SWAPPED);
+
+ g_signal_connect (window, "zoom-changed",
+ G_CALLBACK (zoom_changed_callback),
+ zoom_control);
+ }
+
+ (* GTK_ACTION_CLASS (parent_class)->connect_proxy) (action, proxy);
+}
+
+static void
+disconnect_proxy (GtkAction *action,
+ GtkWidget *proxy)
+{
+ if (GTK_IS_TOOL_ITEM (proxy))
+ {
+ GtkToolItem *item = GTK_TOOL_ITEM (proxy);
+ CajaZoomAction *zaction = CAJA_ZOOM_ACTION (action);
+ CajaNavigationWindow *window = zaction->priv->window;
+ GtkWidget *child;
+
+ child = gtk_bin_get_child (GTK_BIN (item));
+
+ g_signal_handlers_disconnect_by_func (window, G_CALLBACK (zoom_changed_callback), child);
+
+ }
+
+ (* GTK_ACTION_CLASS (parent_class)->disconnect_proxy) (action, proxy);
+}
+
+static void
+caja_zoom_action_finalize (GObject *object)
+{
+ (* G_OBJECT_CLASS (parent_class)->finalize) (object);
+}
+
+static void
+caja_zoom_action_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CajaZoomAction *zoom;
+
+ zoom = CAJA_ZOOM_ACTION (object);
+
+ switch (prop_id)
+ {
+ case PROP_WINDOW:
+ zoom->priv->window = CAJA_NAVIGATION_WINDOW (g_value_get_object (value));
+ break;
+ }
+}
+
+static void
+caja_zoom_action_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ CajaZoomAction *zoom;
+
+ zoom = CAJA_ZOOM_ACTION (object);
+
+ switch (prop_id)
+ {
+ case PROP_WINDOW:
+ g_value_set_object (value, zoom->priv->window);
+ break;
+ }
+}
+
+static void
+caja_zoom_action_class_init (CajaZoomActionClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+ GtkActionClass *action_class = GTK_ACTION_CLASS (class);
+
+ object_class->finalize = caja_zoom_action_finalize;
+ object_class->set_property = caja_zoom_action_set_property;
+ object_class->get_property = caja_zoom_action_get_property;
+
+ parent_class = g_type_class_peek_parent (class);
+
+ action_class->toolbar_item_type = GTK_TYPE_TOOL_ITEM;
+ action_class->connect_proxy = connect_proxy;
+ action_class->disconnect_proxy = disconnect_proxy;
+
+ g_object_class_install_property (object_class,
+ PROP_WINDOW,
+ g_param_spec_object ("window",
+ "Window",
+ "The navigation window",
+ G_TYPE_OBJECT,
+ G_PARAM_READWRITE));
+
+ g_type_class_add_private (object_class, sizeof(CajaZoomActionPrivate));
+}
+
+static void
+caja_zoom_action_init (CajaZoomAction *action)
+{
+ action->priv = CAJA_ZOOM_ACTION_GET_PRIVATE (action);
+}
diff --git a/src/caja-zoom-action.h b/src/caja-zoom-action.h
new file mode 100644
index 00000000..263b64a2
--- /dev/null
+++ b/src/caja-zoom-action.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2009 Red Hat, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Authors: Alexander Larsson <[email protected]>
+ *
+ */
+
+#ifndef CAJA_ZOOM_ACTION_H
+#define CAJA_ZOOM_ACTION_H
+
+#include <gtk/gtk.h>
+
+#define CAJA_TYPE_ZOOM_ACTION (caja_zoom_action_get_type ())
+#define CAJA_ZOOM_ACTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_ZOOM_ACTION, CajaZoomAction))
+#define CAJA_ZOOM_ACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_ZOOM_ACTION, CajaZoomActionClass))
+#define CAJA_IS_ZOOM_ACTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_ZOOM_ACTION))
+#define CAJA_IS_ZOOM_ACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), CAJA_TYPE_ZOOM_ACTION))
+#define CAJA_ZOOM_ACTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), CAJA_TYPE_ZOOM_ACTION, CajaZoomActionClass))
+
+typedef struct _CajaZoomAction CajaZoomAction;
+typedef struct _CajaZoomActionClass CajaZoomActionClass;
+typedef struct CajaZoomActionPrivate CajaZoomActionPrivate;
+
+struct _CajaZoomAction
+{
+ GtkAction parent;
+
+ /*< private >*/
+ CajaZoomActionPrivate *priv;
+};
+
+struct _CajaZoomActionClass
+{
+ GtkActionClass parent_class;
+};
+
+GType caja_zoom_action_get_type (void);
+
+#endif
diff --git a/src/caja-zoom-control.c b/src/caja-zoom-control.c
new file mode 100644
index 00000000..a11ca9e9
--- /dev/null
+++ b/src/caja-zoom-control.c
@@ -0,0 +1,996 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ * Copyright (C) 2004 Red Hat, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Author: Andy Hertzfeld <[email protected]>
+ * Alexander Larsson <[email protected]>
+ *
+ * This is the zoom control for the location bar
+ *
+ */
+
+#include <config.h>
+#include "caja-zoom-control.h"
+
+#include <atk/atkaction.h>
+#include <glib/gi18n.h>
+#include <eel/eel-accessibility.h>
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-graphic-effects.h>
+#include <eel/eel-gtk-extensions.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <libcaja-private/caja-file-utilities.h>
+#include <libcaja-private/caja-global-preferences.h>
+#include <libcaja-private/caja-marshal.h>
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+
+enum
+{
+ ZOOM_IN,
+ ZOOM_OUT,
+ ZOOM_TO_LEVEL,
+ ZOOM_TO_DEFAULT,
+ CHANGE_VALUE,
+ LAST_SIGNAL
+};
+
+struct CajaZoomControlDetails
+{
+ GtkWidget *zoom_in;
+ GtkWidget *zoom_out;
+ GtkWidget *zoom_label;
+ GtkWidget *zoom_button;
+
+ CajaZoomLevel zoom_level;
+ CajaZoomLevel min_zoom_level;
+ CajaZoomLevel max_zoom_level;
+ gboolean has_min_zoom_level;
+ gboolean has_max_zoom_level;
+ GList *preferred_zoom_levels;
+
+ gboolean marking_menu_items;
+};
+
+
+static guint signals[LAST_SIGNAL];
+
+static gpointer accessible_parent_class;
+
+static const char * const caja_zoom_control_accessible_action_names[] =
+{
+ N_("Zoom In"),
+ N_("Zoom Out"),
+ N_("Zoom to Default"),
+};
+
+static const int caja_zoom_control_accessible_action_signals[] =
+{
+ ZOOM_IN,
+ ZOOM_OUT,
+ ZOOM_TO_DEFAULT,
+};
+
+static const char * const caja_zoom_control_accessible_action_descriptions[] =
+{
+ N_("Increase the view size"),
+ N_("Decrease the view size"),
+ N_("Use the normal view size")
+};
+
+static GtkMenu *create_zoom_menu (CajaZoomControl *zoom_control);
+
+static GType caja_zoom_control_accessible_get_type (void);
+
+/* button assignments */
+#define CONTEXTUAL_MENU_BUTTON 3
+
+#define NUM_ACTIONS ((int)G_N_ELEMENTS (caja_zoom_control_accessible_action_names))
+
+G_DEFINE_TYPE (CajaZoomControl, caja_zoom_control, GTK_TYPE_HBOX);
+
+static void
+caja_zoom_control_finalize (GObject *object)
+{
+ g_list_free (CAJA_ZOOM_CONTROL (object)->details->preferred_zoom_levels);
+
+ G_OBJECT_CLASS (caja_zoom_control_parent_class)->finalize (object);
+}
+
+static void
+zoom_button_clicked (GtkButton *button, CajaZoomControl *zoom_control)
+{
+ g_signal_emit (zoom_control, signals[ZOOM_TO_DEFAULT], 0);
+}
+
+static void
+zoom_popup_menu_show (GdkEventButton *event, CajaZoomControl *zoom_control)
+{
+ eel_pop_up_context_menu (create_zoom_menu (zoom_control),
+ EEL_DEFAULT_POPUP_MENU_DISPLACEMENT,
+ EEL_DEFAULT_POPUP_MENU_DISPLACEMENT,
+ event);
+}
+
+static void
+menu_position_under_widget (GtkMenu *menu,
+ gint *x,
+ gint *y,
+ gboolean *push_in,
+ gpointer user_data)
+{
+ GtkWidget *widget;
+ GtkWidget *container;
+ GtkRequisition req;
+ GtkRequisition menu_req;
+ GdkRectangle monitor;
+ int monitor_num;
+ GdkScreen *screen;
+ GtkAllocation allocation;
+
+ widget = GTK_WIDGET (user_data);
+ g_assert (GTK_IS_WIDGET (widget));
+
+ container = gtk_widget_get_ancestor (widget, GTK_TYPE_CONTAINER);
+ g_assert (container != NULL);
+
+ gtk_widget_size_request (widget, &req);
+ gtk_widget_size_request (GTK_WIDGET (menu), &menu_req);
+ gtk_widget_get_allocation (widget, &allocation);
+
+ screen = gtk_widget_get_screen (GTK_WIDGET (menu));
+ monitor_num = gdk_screen_get_monitor_at_window (screen, gtk_widget_get_window (widget));
+ if (monitor_num < 0)
+ {
+ monitor_num = 0;
+ }
+ gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
+
+ gdk_window_get_origin (gtk_widget_get_window (widget), x, y);
+ if (!gtk_widget_get_has_window (widget))
+ {
+ *x += allocation.x;
+ *y += allocation.y;
+ }
+
+ if (gtk_widget_get_direction (container) == GTK_TEXT_DIR_LTR)
+ {
+ *x += allocation.width - req.width;
+ }
+ else
+ {
+ *x += req.width - menu_req.width;
+ }
+
+ if ((*y + allocation.height + menu_req.height) <= monitor.y + monitor.height)
+ {
+ *y += allocation.height;
+ }
+ else if ((*y - menu_req.height) >= monitor.y)
+ {
+ *y -= menu_req.height;
+ }
+ else if (monitor.y + monitor.height - (*y + allocation.height) > *y)
+ {
+ *y += allocation.height;
+ }
+ else
+ {
+ *y -= menu_req.height;
+ }
+
+ *push_in = FALSE;
+}
+
+
+static void
+zoom_popup_menu (GtkWidget *widget, CajaZoomControl *zoom_control)
+{
+ GtkMenu *menu;
+
+ menu = create_zoom_menu (zoom_control);
+ gtk_menu_popup (menu, NULL, NULL,
+ menu_position_under_widget, widget,
+ 0, gtk_get_current_event_time ());
+ gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
+}
+
+/* handle button presses */
+static gboolean
+caja_zoom_control_button_press_event (GtkWidget *widget,
+ GdkEventButton *event,
+ CajaZoomControl *zoom_control)
+{
+ if (event->type != GDK_BUTTON_PRESS)
+ {
+ return FALSE;
+ }
+
+ /* check for the context menu button and show the menu */
+ if (event->button == CONTEXTUAL_MENU_BUTTON)
+ {
+ zoom_popup_menu_show (event, zoom_control);
+ return TRUE;
+ }
+ /* We don't change our state (to reflect the new zoom) here.
+ The zoomable will call back with the new level.
+ Actually, the callback goes to the viewframe containing the
+ zoomable which, in turn, emits zoom_level_changed,
+ which someone (e.g. caja_window) picks up and handles by
+ calling into is - caja_zoom_control_set_zoom_level.
+ */
+
+ return FALSE;
+}
+
+static void
+zoom_out_clicked (GtkButton *button,
+ CajaZoomControl *zoom_control)
+{
+ if (caja_zoom_control_can_zoom_out (zoom_control))
+ {
+ g_signal_emit (G_OBJECT (zoom_control), signals[ZOOM_OUT], 0);
+ }
+}
+
+static void
+zoom_in_clicked (GtkButton *button,
+ CajaZoomControl *zoom_control)
+{
+ if (caja_zoom_control_can_zoom_in (zoom_control))
+ {
+ g_signal_emit (G_OBJECT (zoom_control), signals[ZOOM_IN], 0);
+ }
+}
+
+static void
+set_label_size (CajaZoomControl *zoom_control)
+{
+ const char *text;
+ PangoLayout *layout;
+ int width;
+ int height;
+
+ text = gtk_label_get_text (GTK_LABEL (zoom_control->details->zoom_label));
+ layout = gtk_label_get_layout (GTK_LABEL (zoom_control->details->zoom_label));
+ pango_layout_set_text (layout, "100%", -1);
+ pango_layout_get_pixel_size (layout, &width, &height);
+ gtk_widget_set_size_request (zoom_control->details->zoom_label, width, height);
+ gtk_label_set_text (GTK_LABEL (zoom_control->details->zoom_label),
+ text);
+}
+
+static void
+label_style_set_callback (GtkWidget *label,
+ GtkStyle *style,
+ gpointer user_data)
+{
+ set_label_size (CAJA_ZOOM_CONTROL (user_data));
+}
+
+static void
+caja_zoom_control_init (CajaZoomControl *zoom_control)
+{
+ GtkWidget *image;
+ int i;
+
+ zoom_control->details = G_TYPE_INSTANCE_GET_PRIVATE (zoom_control, CAJA_TYPE_ZOOM_CONTROL, CajaZoomControlDetails);
+
+ zoom_control->details->zoom_level = CAJA_ZOOM_LEVEL_STANDARD;
+ zoom_control->details->min_zoom_level = CAJA_ZOOM_LEVEL_SMALLEST;
+ zoom_control->details->max_zoom_level = CAJA_ZOOM_LEVEL_LARGEST;
+ zoom_control->details->has_min_zoom_level = TRUE;
+ zoom_control->details->has_max_zoom_level = TRUE;
+
+ for (i = CAJA_ZOOM_LEVEL_LARGEST; i >= CAJA_ZOOM_LEVEL_SMALLEST; i--)
+ {
+ zoom_control->details->preferred_zoom_levels = g_list_prepend (
+ zoom_control->details->preferred_zoom_levels,
+ GINT_TO_POINTER (i));
+ }
+
+ image = gtk_image_new_from_stock (GTK_STOCK_ZOOM_OUT, GTK_ICON_SIZE_MENU);
+ zoom_control->details->zoom_out = gtk_button_new ();
+ gtk_button_set_focus_on_click (GTK_BUTTON (zoom_control->details->zoom_out), FALSE);
+ gtk_button_set_relief (GTK_BUTTON (zoom_control->details->zoom_out),
+ GTK_RELIEF_NONE);
+ gtk_widget_set_tooltip_text (zoom_control->details->zoom_out,
+ _("Decrease the view size"));
+ g_signal_connect (G_OBJECT (zoom_control->details->zoom_out),
+ "clicked", G_CALLBACK (zoom_out_clicked),
+ zoom_control);
+ gtk_container_add (GTK_CONTAINER (zoom_control->details->zoom_out), image);
+ gtk_box_pack_start (GTK_BOX (zoom_control),
+ zoom_control->details->zoom_out, FALSE, FALSE, 0);
+
+ zoom_control->details->zoom_button = gtk_button_new ();
+ gtk_button_set_focus_on_click (GTK_BUTTON (zoom_control->details->zoom_button), FALSE);
+ gtk_button_set_relief (GTK_BUTTON (zoom_control->details->zoom_button),
+ GTK_RELIEF_NONE);
+ gtk_widget_set_tooltip_text (zoom_control->details->zoom_button,
+ _("Use the normal view size"));
+
+ gtk_widget_add_events (GTK_WIDGET (zoom_control->details->zoom_button),
+ GDK_BUTTON_PRESS_MASK
+ | GDK_BUTTON_RELEASE_MASK
+ | GDK_POINTER_MOTION_MASK);
+
+ g_signal_connect (G_OBJECT (zoom_control->details->zoom_button),
+ "button-press-event",
+ G_CALLBACK (caja_zoom_control_button_press_event),
+ zoom_control);
+
+ g_signal_connect (G_OBJECT (zoom_control->details->zoom_button),
+ "clicked", G_CALLBACK (zoom_button_clicked),
+ zoom_control);
+
+ g_signal_connect (G_OBJECT (zoom_control->details->zoom_button),
+ "popup-menu", G_CALLBACK (zoom_popup_menu),
+ zoom_control);
+
+ zoom_control->details->zoom_label = gtk_label_new ("100%");
+ g_signal_connect (zoom_control->details->zoom_label,
+ "style_set",
+ G_CALLBACK (label_style_set_callback),
+ zoom_control);
+ set_label_size (zoom_control);
+
+ gtk_container_add (GTK_CONTAINER (zoom_control->details->zoom_button), zoom_control->details->zoom_label);
+
+ gtk_box_pack_start (GTK_BOX (zoom_control),
+ zoom_control->details->zoom_button, TRUE, TRUE, 0);
+
+ image = gtk_image_new_from_stock (GTK_STOCK_ZOOM_IN, GTK_ICON_SIZE_MENU);
+ zoom_control->details->zoom_in = gtk_button_new ();
+ gtk_button_set_focus_on_click (GTK_BUTTON (zoom_control->details->zoom_in), FALSE);
+ gtk_button_set_relief (GTK_BUTTON (zoom_control->details->zoom_in),
+ GTK_RELIEF_NONE);
+ gtk_widget_set_tooltip_text (zoom_control->details->zoom_in,
+ _("Increase the view size"));
+ g_signal_connect (G_OBJECT (zoom_control->details->zoom_in),
+ "clicked", G_CALLBACK (zoom_in_clicked),
+ zoom_control);
+
+ gtk_container_add (GTK_CONTAINER (zoom_control->details->zoom_in), image);
+ gtk_box_pack_start (GTK_BOX (zoom_control),
+ zoom_control->details->zoom_in, FALSE, FALSE, 0);
+
+ gtk_widget_show_all (zoom_control->details->zoom_out);
+ gtk_widget_show_all (zoom_control->details->zoom_button);
+ gtk_widget_show_all (zoom_control->details->zoom_in);
+}
+
+/* Allocate a new zoom control */
+GtkWidget *
+caja_zoom_control_new (void)
+{
+ return gtk_widget_new (caja_zoom_control_get_type (), NULL);
+}
+
+static void
+caja_zoom_control_redraw (CajaZoomControl *zoom_control)
+{
+ int percent;
+ char *num_str;
+
+ gtk_widget_set_sensitive (zoom_control->details->zoom_in,
+ caja_zoom_control_can_zoom_in (zoom_control));
+ gtk_widget_set_sensitive (zoom_control->details->zoom_out,
+ caja_zoom_control_can_zoom_out (zoom_control));
+
+ percent = floor ((100.0 * caja_get_relative_icon_size_for_zoom_level (zoom_control->details->zoom_level)) + .2);
+ num_str = g_strdup_printf ("%d%%", percent);
+ gtk_label_set_text (GTK_LABEL (zoom_control->details->zoom_label), num_str);
+ g_free (num_str);
+}
+
+/* routines to create and handle the zoom menu */
+
+static void
+zoom_menu_callback (GtkMenuItem *item, gpointer callback_data)
+{
+ CajaZoomLevel zoom_level;
+ CajaZoomControl *zoom_control;
+ gboolean can_zoom;
+
+ zoom_control = CAJA_ZOOM_CONTROL (callback_data);
+
+ /* Don't do anything if we're just setting the toggle state of menu items. */
+ if (zoom_control->details->marking_menu_items)
+ {
+ return;
+ }
+
+ /* Don't send the signal if the menuitem was toggled off */
+ if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item)))
+ {
+ return;
+ }
+
+ zoom_level = (CajaZoomLevel) GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "zoom_level"));
+
+ /* Assume we can zoom and then check whether we're right. */
+ can_zoom = TRUE;
+ if (zoom_control->details->has_min_zoom_level &&
+ zoom_level < zoom_control->details->min_zoom_level)
+ can_zoom = FALSE; /* no, we're below the minimum zoom level. */
+ if (zoom_control->details->has_max_zoom_level &&
+ zoom_level > zoom_control->details->max_zoom_level)
+ can_zoom = FALSE; /* no, we're beyond the upper zoom level. */
+
+ /* if we can zoom */
+ if (can_zoom)
+ {
+ g_signal_emit (zoom_control, signals[ZOOM_TO_LEVEL], 0, zoom_level);
+ }
+}
+
+static GtkRadioMenuItem *
+create_zoom_menu_item (CajaZoomControl *zoom_control, GtkMenu *menu,
+ CajaZoomLevel zoom_level,
+ GtkRadioMenuItem *previous_radio_item)
+{
+ GtkWidget *menu_item;
+ char *item_text;
+ GSList *radio_item_group;
+ int percent;
+
+ /* Set flag so that callback isn't activated when set_active called
+ * to set toggle state of other radio items.
+ */
+ zoom_control->details->marking_menu_items = TRUE;
+
+ percent = floor ((100.0 * caja_get_relative_icon_size_for_zoom_level (zoom_level)) + .5);
+ item_text = g_strdup_printf ("%d%%", percent);
+
+ radio_item_group = previous_radio_item == NULL
+ ? NULL
+ : gtk_radio_menu_item_get_group (previous_radio_item);
+ menu_item = gtk_radio_menu_item_new_with_label (radio_item_group, item_text);
+ g_free (item_text);
+
+ gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menu_item),
+ zoom_level == zoom_control->details->zoom_level);
+
+ g_object_set_data (G_OBJECT (menu_item), "zoom_level", GINT_TO_POINTER (zoom_level));
+ g_signal_connect_object (menu_item, "activate",
+ G_CALLBACK (zoom_menu_callback), zoom_control, 0);
+
+ gtk_widget_show (menu_item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
+
+ zoom_control->details->marking_menu_items = FALSE;
+
+ return GTK_RADIO_MENU_ITEM (menu_item);
+}
+
+static GtkMenu *
+create_zoom_menu (CajaZoomControl *zoom_control)
+{
+ GtkMenu *menu;
+ GtkRadioMenuItem *previous_item;
+ GList *node;
+
+ menu = GTK_MENU (gtk_menu_new ());
+
+ previous_item = NULL;
+ for (node = zoom_control->details->preferred_zoom_levels; node != NULL; node = node->next)
+ {
+ previous_item = create_zoom_menu_item
+ (zoom_control, menu, GPOINTER_TO_INT (node->data), previous_item);
+ }
+
+ return menu;
+}
+
+static AtkObject *
+caja_zoom_control_get_accessible (GtkWidget *widget)
+{
+ AtkObject *accessible;
+
+ accessible = eel_accessibility_get_atk_object (widget);
+
+ if (accessible)
+ {
+ return accessible;
+ }
+
+ accessible = g_object_new
+ (caja_zoom_control_accessible_get_type (), NULL);
+
+ return eel_accessibility_set_atk_object_return (widget, accessible);
+}
+
+static void
+caja_zoom_control_change_value (CajaZoomControl *zoom_control,
+ GtkScrollType scroll)
+{
+ switch (scroll)
+ {
+ case GTK_SCROLL_STEP_DOWN :
+ if (caja_zoom_control_can_zoom_out (zoom_control))
+ {
+ g_signal_emit (zoom_control, signals[ZOOM_OUT], 0);
+ }
+ break;
+ case GTK_SCROLL_STEP_UP :
+ if (caja_zoom_control_can_zoom_in (zoom_control))
+ {
+ g_signal_emit (zoom_control, signals[ZOOM_IN], 0);
+ }
+ break;
+ default :
+ g_warning ("Invalid scroll type %d for CajaZoomControl:change_value", scroll);
+ }
+}
+
+void
+caja_zoom_control_set_zoom_level (CajaZoomControl *zoom_control,
+ CajaZoomLevel zoom_level)
+{
+ zoom_control->details->zoom_level = zoom_level;
+ caja_zoom_control_redraw (zoom_control);
+}
+
+void
+caja_zoom_control_set_parameters (CajaZoomControl *zoom_control,
+ CajaZoomLevel min_zoom_level,
+ CajaZoomLevel max_zoom_level,
+ gboolean has_min_zoom_level,
+ gboolean has_max_zoom_level,
+ GList *zoom_levels)
+{
+ g_return_if_fail (CAJA_IS_ZOOM_CONTROL (zoom_control));
+
+ zoom_control->details->min_zoom_level = min_zoom_level;
+ zoom_control->details->max_zoom_level = max_zoom_level;
+ zoom_control->details->has_min_zoom_level = has_min_zoom_level;
+ zoom_control->details->has_max_zoom_level = has_max_zoom_level;
+
+ g_list_free (zoom_control->details->preferred_zoom_levels);
+ zoom_control->details->preferred_zoom_levels = zoom_levels;
+
+ caja_zoom_control_redraw (zoom_control);
+}
+
+CajaZoomLevel
+caja_zoom_control_get_zoom_level (CajaZoomControl *zoom_control)
+{
+ return zoom_control->details->zoom_level;
+}
+
+CajaZoomLevel
+caja_zoom_control_get_min_zoom_level (CajaZoomControl *zoom_control)
+{
+ return zoom_control->details->min_zoom_level;
+}
+
+CajaZoomLevel
+caja_zoom_control_get_max_zoom_level (CajaZoomControl *zoom_control)
+{
+ return zoom_control->details->max_zoom_level;
+}
+
+gboolean
+caja_zoom_control_has_min_zoom_level (CajaZoomControl *zoom_control)
+{
+ return zoom_control->details->has_min_zoom_level;
+}
+
+gboolean
+caja_zoom_control_has_max_zoom_level (CajaZoomControl *zoom_control)
+{
+ return zoom_control->details->has_max_zoom_level;
+}
+
+gboolean
+caja_zoom_control_can_zoom_in (CajaZoomControl *zoom_control)
+{
+ return !zoom_control->details->has_max_zoom_level ||
+ (zoom_control->details->zoom_level
+ < zoom_control->details->max_zoom_level);
+}
+
+gboolean
+caja_zoom_control_can_zoom_out (CajaZoomControl *zoom_control)
+{
+ return !zoom_control->details->has_min_zoom_level ||
+ (zoom_control->details->zoom_level
+ > zoom_control->details->min_zoom_level);
+}
+
+static gboolean
+caja_zoom_control_scroll_event (GtkWidget *widget, GdkEventScroll *event)
+{
+ CajaZoomControl *zoom_control;
+
+ zoom_control = CAJA_ZOOM_CONTROL (widget);
+
+ if (event->type != GDK_SCROLL)
+ {
+ return FALSE;
+ }
+
+ if (event->direction == GDK_SCROLL_DOWN &&
+ caja_zoom_control_can_zoom_out (zoom_control))
+ {
+ g_signal_emit (widget, signals[ZOOM_OUT], 0);
+ }
+ else if (event->direction == GDK_SCROLL_UP &&
+ caja_zoom_control_can_zoom_in (zoom_control))
+ {
+ g_signal_emit (widget, signals[ZOOM_IN], 0);
+ }
+
+ /* We don't change our state (to reflect the new zoom) here. The zoomable will
+ * call back with the new level. Actually, the callback goes to the view-frame
+ * containing the zoomable which, in turn, emits zoom_level_changed, which
+ * someone (e.g. caja_window) picks up and handles by calling into us -
+ * caja_zoom_control_set_zoom_level.
+ */
+ return TRUE;
+}
+
+
+
+static void
+caja_zoom_control_class_init (CajaZoomControlClass *class)
+{
+ GtkWidgetClass *widget_class;
+ GtkBindingSet *binding_set;
+
+ G_OBJECT_CLASS (class)->finalize = caja_zoom_control_finalize;
+
+ widget_class = GTK_WIDGET_CLASS (class);
+
+ widget_class->get_accessible = caja_zoom_control_get_accessible;
+ widget_class->scroll_event = caja_zoom_control_scroll_event;
+
+ class->change_value = caja_zoom_control_change_value;
+
+ signals[ZOOM_IN] =
+ g_signal_new ("zoom_in",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaZoomControlClass,
+ zoom_in),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[ZOOM_OUT] =
+ g_signal_new ("zoom_out",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaZoomControlClass,
+ zoom_out),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[ZOOM_TO_LEVEL] =
+ g_signal_new ("zoom_to_level",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaZoomControlClass,
+ zoom_to_level),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__INT,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_INT);
+
+ signals[ZOOM_TO_DEFAULT] =
+ g_signal_new ("zoom_to_default",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (CajaZoomControlClass,
+ zoom_to_default),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[CHANGE_VALUE] =
+ g_signal_new ("change_value",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (CajaZoomControlClass,
+ change_value),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__ENUM,
+ G_TYPE_NONE, 1, GTK_TYPE_SCROLL_TYPE);
+
+ binding_set = gtk_binding_set_by_class (class);
+
+ gtk_binding_entry_add_signal (binding_set,
+ GDK_KP_Subtract, 0,
+ "change_value",
+ 1, GTK_TYPE_SCROLL_TYPE,
+ GTK_SCROLL_STEP_DOWN);
+ gtk_binding_entry_add_signal (binding_set,
+ GDK_minus, 0,
+ "change_value",
+ 1, GTK_TYPE_SCROLL_TYPE,
+ GTK_SCROLL_STEP_DOWN);
+
+ gtk_binding_entry_add_signal (binding_set,
+ GDK_KP_Equal, 0,
+ "zoom_to_default",
+ 0);
+ gtk_binding_entry_add_signal (binding_set,
+ GDK_KP_Equal, 0,
+ "zoom_to_default",
+ 0);
+
+ gtk_binding_entry_add_signal (binding_set,
+ GDK_KP_Add, 0,
+ "change_value",
+ 1, GTK_TYPE_SCROLL_TYPE,
+ GTK_SCROLL_STEP_UP);
+ gtk_binding_entry_add_signal (binding_set,
+ GDK_plus, 0,
+ "change_value",
+ 1, GTK_TYPE_SCROLL_TYPE,
+ GTK_SCROLL_STEP_UP);
+
+ g_type_class_add_private (G_OBJECT_CLASS (class), sizeof (CajaZoomControlDetails));
+}
+
+static gboolean
+caja_zoom_control_accessible_do_action (AtkAction *accessible, int i)
+{
+ GtkWidget *widget;
+
+ g_assert (i >= 0 && i < NUM_ACTIONS);
+
+ widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
+ if (!widget)
+ {
+ return FALSE;
+ }
+
+ g_signal_emit (widget,
+ signals[caja_zoom_control_accessible_action_signals [i]],
+ 0);
+
+ return TRUE;
+}
+
+static int
+caja_zoom_control_accessible_get_n_actions (AtkAction *accessible)
+{
+
+ return NUM_ACTIONS;
+}
+
+static const char* caja_zoom_control_accessible_action_get_description(AtkAction* accessible, int i)
+{
+ g_assert(i >= 0 && i < NUM_ACTIONS);
+
+ return _(caja_zoom_control_accessible_action_descriptions[i]);
+}
+
+static const char* caja_zoom_control_accessible_action_get_name(AtkAction* accessible, int i)
+{
+ g_assert (i >= 0 && i < NUM_ACTIONS);
+
+ return _(caja_zoom_control_accessible_action_names[i]);
+}
+
+static void caja_zoom_control_accessible_action_interface_init(AtkActionIface* iface)
+{
+ iface->do_action = caja_zoom_control_accessible_do_action;
+ iface->get_n_actions = caja_zoom_control_accessible_get_n_actions;
+ iface->get_description = caja_zoom_control_accessible_action_get_description;
+ iface->get_name = caja_zoom_control_accessible_action_get_name;
+}
+
+static void
+caja_zoom_control_accessible_get_current_value (AtkValue *accessible,
+ GValue *value)
+{
+ CajaZoomControl *control;
+
+ g_value_init (value, G_TYPE_INT);
+
+ control = CAJA_ZOOM_CONTROL (gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible)));
+ if (!control)
+ {
+ g_value_set_int (value, CAJA_ZOOM_LEVEL_STANDARD);
+ return;
+ }
+
+ g_value_set_int (value, control->details->zoom_level);
+}
+
+static void
+caja_zoom_control_accessible_get_maximum_value (AtkValue *accessible,
+ GValue *value)
+{
+ CajaZoomControl *control;
+
+ g_value_init (value, G_TYPE_INT);
+
+ control = CAJA_ZOOM_CONTROL (gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible)));
+ if (!control)
+ {
+ g_value_set_int (value, CAJA_ZOOM_LEVEL_STANDARD);
+ return;
+ }
+
+ g_value_set_int (value, control->details->max_zoom_level);
+}
+
+static void
+caja_zoom_control_accessible_get_minimum_value (AtkValue *accessible,
+ GValue *value)
+{
+ CajaZoomControl *control;
+
+ g_value_init (value, G_TYPE_INT);
+
+ control = CAJA_ZOOM_CONTROL (gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible)));
+ if (!control)
+ {
+ g_value_set_int (value, CAJA_ZOOM_LEVEL_STANDARD);
+ return;
+ }
+
+ g_value_set_int (value, control->details->min_zoom_level);
+}
+
+static CajaZoomLevel
+nearest_preferred (CajaZoomControl *zoom_control, CajaZoomLevel value)
+{
+ CajaZoomLevel last_value;
+ CajaZoomLevel current_value;
+ GList *l;
+
+ if (!zoom_control->details->preferred_zoom_levels)
+ {
+ return value;
+ }
+
+ last_value = GPOINTER_TO_INT (zoom_control->details->preferred_zoom_levels->data);
+ current_value = last_value;
+
+ for (l = zoom_control->details->preferred_zoom_levels; l != NULL; l = l->next)
+ {
+ current_value = GPOINTER_TO_INT (l->data);
+
+ if (current_value > value)
+ {
+ float center = (last_value + current_value) / 2;
+
+ return (value < center) ? last_value : current_value;
+
+ }
+
+ last_value = current_value;
+ }
+
+ return current_value;
+}
+
+static gboolean
+caja_zoom_control_accessible_set_current_value (AtkValue *accessible,
+ const GValue *value)
+{
+ CajaZoomControl *control;
+ CajaZoomLevel zoom;
+
+ control = CAJA_ZOOM_CONTROL (gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible)));
+ if (!control)
+ {
+ return FALSE;
+ }
+
+ zoom = nearest_preferred (control, g_value_get_int (value));
+
+ g_signal_emit (control, signals[ZOOM_TO_LEVEL], 0, zoom);
+
+ return TRUE;
+}
+
+static void
+caja_zoom_control_accessible_value_interface_init (AtkValueIface *iface)
+{
+ iface->get_current_value = caja_zoom_control_accessible_get_current_value;
+ iface->get_maximum_value = caja_zoom_control_accessible_get_maximum_value;
+ iface->get_minimum_value = caja_zoom_control_accessible_get_minimum_value;
+ iface->set_current_value = caja_zoom_control_accessible_set_current_value;
+}
+
+static const char* caja_zoom_control_accessible_get_name(AtkObject* accessible)
+{
+ return _("Zoom");
+}
+
+static const char* caja_zoom_control_accessible_get_description(AtkObject* accessible)
+{
+ return _("Set the zoom level of the current view");
+}
+
+static void
+caja_zoom_control_accessible_initialize (AtkObject *accessible,
+ gpointer data)
+{
+ if (ATK_OBJECT_CLASS (accessible_parent_class)->initialize != NULL)
+ {
+ ATK_OBJECT_CLASS (accessible_parent_class)->initialize (accessible, data);
+ }
+ atk_object_set_role (accessible, ATK_ROLE_DIAL);
+}
+
+static void
+caja_zoom_control_accessible_class_init (AtkObjectClass *klass)
+{
+ accessible_parent_class = g_type_class_peek_parent (klass);
+
+ klass->get_name = caja_zoom_control_accessible_get_name;
+ klass->get_description = caja_zoom_control_accessible_get_description;
+ klass->initialize = caja_zoom_control_accessible_initialize;
+}
+
+static GType
+caja_zoom_control_accessible_get_type (void)
+{
+ static GType type = 0;
+
+ if (!type)
+ {
+ static GInterfaceInfo atk_action_info =
+ {
+ (GInterfaceInitFunc)caja_zoom_control_accessible_action_interface_init,
+ (GInterfaceFinalizeFunc)NULL,
+ NULL
+ };
+
+ static GInterfaceInfo atk_value_info =
+ {
+ (GInterfaceInitFunc)caja_zoom_control_accessible_value_interface_init,
+ (GInterfaceFinalizeFunc)NULL,
+ NULL
+ };
+
+ type = eel_accessibility_create_derived_type
+ ("CajaZoomControlAccessible",
+ GTK_TYPE_HBOX,
+ caja_zoom_control_accessible_class_init);
+
+ g_type_add_interface_static (type, ATK_TYPE_ACTION,
+ &atk_action_info);
+ g_type_add_interface_static (type, ATK_TYPE_VALUE,
+ &atk_value_info);
+ }
+
+ return type;
+}
+
+void
+caja_zoom_control_set_active_appearance (CajaZoomControl *zoom_control, gboolean is_active)
+{
+ gtk_widget_set_sensitive (gtk_bin_get_child (GTK_BIN (zoom_control->details->zoom_in)), is_active);
+ gtk_widget_set_sensitive (gtk_bin_get_child (GTK_BIN (zoom_control->details->zoom_out)), is_active);
+ gtk_widget_set_sensitive (zoom_control->details->zoom_label, is_active);
+}
diff --git a/src/caja-zoom-control.h b/src/caja-zoom-control.h
new file mode 100644
index 00000000..d24d062c
--- /dev/null
+++ b/src/caja-zoom-control.h
@@ -0,0 +1,91 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Caja
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Caja is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Author: Andy Hertzfeld <[email protected]>
+ *
+ * This is the header file for the zoom control on the location bar
+ *
+ */
+
+#ifndef CAJA_ZOOM_CONTROL_H
+#define CAJA_ZOOM_CONTROL_H
+
+#include <gtk/gtk.h>
+#include <libcaja-private/caja-icon-info.h> /* For CajaZoomLevel */
+
+#define CAJA_TYPE_ZOOM_CONTROL caja_zoom_control_get_type()
+#define CAJA_ZOOM_CONTROL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAJA_TYPE_ZOOM_CONTROL, CajaZoomControl))
+#define CAJA_ZOOM_CONTROL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), CAJA_TYPE_ZOOM_CONTROL, CajaZoomControlClass))
+#define CAJA_IS_ZOOM_CONTROL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CAJA_TYPE_ZOOM_CONTROL))
+#define CAJA_IS_ZOOM_CONTROL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), CAJA_TYPE_ZOOM_CONTROL))
+#define CAJA_ZOOM_CONTROL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), CAJA_TYPE_ZOOM_CONTROL, CajaZoomControlClass))
+
+typedef struct CajaZoomControl CajaZoomControl;
+typedef struct CajaZoomControlClass CajaZoomControlClass;
+typedef struct CajaZoomControlDetails CajaZoomControlDetails;
+
+struct CajaZoomControl
+{
+ GtkHBox parent;
+ CajaZoomControlDetails *details;
+};
+
+struct CajaZoomControlClass
+{
+ GtkHBoxClass parent_class;
+
+ void (*zoom_in) (CajaZoomControl *control);
+ void (*zoom_out) (CajaZoomControl *control);
+ void (*zoom_to_level) (CajaZoomControl *control,
+ CajaZoomLevel zoom_level);
+ void (*zoom_to_default) (CajaZoomControl *control);
+
+ /* Action signal for keybindings, do not connect to this */
+ void (*change_value) (CajaZoomControl *control,
+ GtkScrollType scroll);
+};
+
+GType caja_zoom_control_get_type (void);
+GtkWidget * caja_zoom_control_new (void);
+void caja_zoom_control_set_zoom_level (CajaZoomControl *zoom_control,
+ CajaZoomLevel zoom_level);
+void caja_zoom_control_set_parameters (CajaZoomControl *zoom_control,
+ CajaZoomLevel min_zoom_level,
+ CajaZoomLevel max_zoom_level,
+ gboolean has_min_zoom_level,
+ gboolean has_max_zoom_level,
+ GList *zoom_levels);
+CajaZoomLevel caja_zoom_control_get_zoom_level (CajaZoomControl *zoom_control);
+CajaZoomLevel caja_zoom_control_get_min_zoom_level (CajaZoomControl *zoom_control);
+CajaZoomLevel caja_zoom_control_get_max_zoom_level (CajaZoomControl *zoom_control);
+gboolean caja_zoom_control_has_min_zoom_level (CajaZoomControl *zoom_control);
+gboolean caja_zoom_control_has_max_zoom_level (CajaZoomControl *zoom_control);
+gboolean caja_zoom_control_can_zoom_in (CajaZoomControl *zoom_control);
+gboolean caja_zoom_control_can_zoom_out (CajaZoomControl *zoom_control);
+
+void caja_zoom_control_set_active_appearance (CajaZoomControl *zoom_control, gboolean is_active);
+
+#endif /* CAJA_ZOOM_CONTROL_H */
diff --git a/src/check-caja b/src/check-caja
new file mode 100755
index 00000000..68678bb8
--- /dev/null
+++ b/src/check-caja
@@ -0,0 +1,2 @@
+#!/bin/sh
+./caja --check --g-fatal-warnings
diff --git a/src/file-manager/Makefile.am b/src/file-manager/Makefile.am
new file mode 100644
index 00000000..4090fc91
--- /dev/null
+++ b/src/file-manager/Makefile.am
@@ -0,0 +1,61 @@
+include $(top_srcdir)/Makefile.shared
+
+noinst_LTLIBRARIES=libcaja-file-manager.la
+
+INCLUDES = \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/cut-n-paste-code \
+ $(CORE_CFLAGS) \
+ $(WARNING_CFLAGS) \
+ -DCAJA_DATADIR=\""$(datadir)/caja"\" \
+ -DDATADIR=\""$(datadir)"\" \
+ $(DISABLE_DEPRECATED_CFLAGS) \
+ $(NULL)
+
+
+
+libcaja_file_manager_la_SOURCES = \
+ fm-actions.h \
+ fm-desktop-icon-view.c \
+ fm-desktop-icon-view.h \
+ fm-directory-view.c \
+ fm-directory-view.h \
+ fm-ditem-page.c \
+ fm-ditem-page.h \
+ fm-error-reporting.c \
+ fm-error-reporting.h \
+ fm-icon-container.c \
+ fm-icon-container.h \
+ fm-icon-view.c \
+ fm-icon-view.h \
+ fm-list-model.c \
+ fm-list-model.h \
+ fm-list-view-private.h \
+ fm-list-view.c \
+ fm-list-view.h \
+ fm-properties-window.c \
+ fm-properties-window.h \
+ fm-tree-model.c \
+ fm-tree-model.h \
+ fm-tree-view.c \
+ fm-tree-view.h \
+ caja-audio-mime-types.h \
+ $(NULL)
+
+EMPTY_VIEW_SOURCES = \
+ fm-empty-view.c \
+ fm-empty-view.h
+
+if ENABLE_EMPTY_VIEW
+libcaja_file_manager_la_SOURCES += $(EMPTY_VIEW_SOURCES)
+endif
+
+uidir = $(datadir)/caja/ui
+ui_DATA = \
+ caja-desktop-icon-view-ui.xml \
+ caja-directory-view-ui.xml \
+ caja-icon-view-ui.xml \
+ caja-list-view-ui.xml \
+ $(NULL)
+
+EXTRA_DIST = $(ui_DATA)
diff --git a/src/file-manager/caja-audio-mime-types.h b/src/file-manager/caja-audio-mime-types.h
new file mode 100644
index 00000000..74194cc8
--- /dev/null
+++ b/src/file-manager/caja-audio-mime-types.h
@@ -0,0 +1,46 @@
+/* generated with mime-types-include.sh in the totem module, don't edit or
+ commit in the caja module without filing a bug against totem */
+static const char* audio_mime_types[] =
+{
+ "audio/3gpp",
+ "audio/ac3",
+ "audio/AMR",
+ "audio/AMR-WB",
+ "audio/basic",
+ "audio/midi",
+ "audio/mp4",
+ "audio/mpeg",
+ "audio/ogg",
+ "audio/prs.sid",
+ "audio/vnd.rn-realaudio",
+ "audio/x-aiff",
+ "audio/x-ape",
+ "audio/x-flac",
+ "audio/x-gsm",
+ "audio/x-it",
+ "audio/x-m4a",
+ "audio/x-matroska",
+ "audio/x-mod",
+ "audio/x-mp3",
+ "audio/x-mpeg",
+ "audio/x-ms-asf",
+ "audio/x-ms-asx",
+ "audio/x-ms-wax",
+ "audio/x-ms-wma",
+ "audio/x-musepack",
+ "audio/x-pn-aiff",
+ "audio/x-pn-au",
+ "audio/x-pn-wav",
+ "audio/x-pn-windows-acm",
+ "audio/x-realaudio",
+ "audio/x-real-audio",
+ "audio/x-sbc",
+ "audio/x-speex",
+ "audio/x-tta",
+ "audio/x-wav",
+ "audio/x-wavpack",
+ "audio/x-vorbis",
+ "audio/x-vorbis+ogg",
+ "audio/x-xm",
+ "application/x-flac",
+};
diff --git a/src/file-manager/caja-desktop-icon-view-ui.xml b/src/file-manager/caja-desktop-icon-view-ui.xml
new file mode 100644
index 00000000..d437801d
--- /dev/null
+++ b/src/file-manager/caja-desktop-icon-view-ui.xml
@@ -0,0 +1,21 @@
+<ui>
+<popup name="background">
+ <placeholder name="Before Zoom Items">
+ <placeholder name="New Window Items">
+ </placeholder>
+ <placeholder name="New Object Items">
+ <menuitem name="New Launcher" action="New Launcher Desktop"/>
+ </placeholder>
+ </placeholder>
+ <placeholder name="After Zoom Items">
+ <placeholder name="Background Items">
+ <menuitem name="Change Background" action="Change Background"/>
+ </placeholder>
+ </placeholder>
+</popup>
+<popup name="selection">
+ <placeholder name="Empty Trash Holder">
+ <menuitem name="Empty Trash" action="Empty Trash Conditional"/>
+ </placeholder>
+</popup>
+</ui>
diff --git a/src/file-manager/caja-directory-view-ui.xml b/src/file-manager/caja-directory-view-ui.xml
new file mode 100644
index 00000000..80b9e881
--- /dev/null
+++ b/src/file-manager/caja-directory-view-ui.xml
@@ -0,0 +1,231 @@
+<ui>
+<accelerator action="OpenAccel"/>
+<accelerator action="OpenCloseParent"/>
+<accelerator action="PropertiesAccel"/>
+<accelerator action="RenameSelectAll"/>
+<menubar name="MenuBar">
+ <menu action="File">
+ <placeholder name="New Items Placeholder">
+ <menuitem name="New Folder" action="New Folder"/>
+ <menu action="New Documents">
+ <menuitem name="No Templates" action="No Templates"/>
+ <placeholder name="New Documents Placeholder"/>
+ <separator name="After New Documents"/>
+ <menuitem name="New Empty File" action="New Empty File"/>
+ </menu>
+ <menuitem name="New Launcher" action="New Launcher"/>
+ </placeholder>
+ <placeholder name="Open Placeholder">
+ <menuitem name="Open" action="Open"/>
+ <menuitem name="OpenInNewTab" action="OpenInNewTab"/>
+ <menuitem name="OpenAlternate" action="OpenAlternate"/>
+ <placeholder name="Applications Placeholder">
+ </placeholder>
+ <menu action="Open With">
+ <placeholder name="Applications Placeholder"/>
+ <separator name="Open With Separator"/>
+ <menuitem name="OtherApplication" action="OtherApplication1"/>
+ </menu>
+ <placeholder name="OtherApplicationPlaceholder">
+ <menuitem name="OtherApplication" action="OtherApplication2"/>
+ </placeholder>
+ <menu action="Scripts">
+ <placeholder name="Scripts Placeholder"/>
+ <separator name="After Scripts"/>
+ <menuitem name="Open Scripts Folder" action="Open Scripts Folder"/>
+ </menu>
+ </placeholder>
+ <placeholder name="File Items Placeholder">
+ <menuitem name="Self Mount Volume" action="Self Mount Volume"/>
+ <menuitem name="Self Unmount Volume" action="Self Unmount Volume"/>
+ <menuitem name="Self Eject Volume" action="Self Eject Volume"/>
+ <menuitem name="Self Format Volume" action="Self Format Volume"/>
+ <menuitem name="Self Start Volume" action="Self Start Volume"/>
+ <menuitem name="Self Stop Volume" action="Self Stop Volume"/>
+ <menuitem name="Self Poll" action="Self Poll"/>
+ <separator name="Properties Separator"/>
+ <menuitem name="Properties" action="Properties"/>
+ </placeholder>
+ <placeholder name="Global File Items Placeholder">
+ <menuitem name="Empty Trash" action="Empty Trash"/>
+ <menuitem name="Save Search" action="Save Search"/>
+ <menuitem name="Save Search As" action="Save Search As"/>
+ </placeholder>
+ </menu>
+ <menu action="Edit">
+ <placeholder name="Clipboard Actions">
+ <menuitem name="Cut" action="Cut"/>
+ <menuitem name="Copy" action="Copy"/>
+ <menuitem name="Paste" action="Paste"/>
+ </placeholder>
+ <placeholder name="Select Items">
+ <menuitem name="Select All" action="Select All"/>
+ <menuitem name="Select Pattern" action="Select Pattern"/>
+ <menuitem name="Invert Selection" action="Invert Selection"/>
+ </placeholder>
+ <placeholder name="File Items Placeholder">
+ <menuitem name="Duplicate" action="Duplicate"/>
+ <menuitem name="Create Link" action="Create Link"/>
+ <menuitem name="Rename" action="Rename"/>
+ <menu action="CopyToMenu">
+ <menuitem name="Copy to next pane" action="Copy to next pane"/>
+ <menuitem name="Copy to Home" action="Copy to Home"/>
+ <menuitem name="Copy to Desktop" action="Copy to Desktop"/>
+ </menu>
+ <menu action="MoveToMenu">
+ <menuitem name="Move to next pane" action="Move to next pane"/>
+ <menuitem name="Copy to Home" action="Move to Home"/>
+ <menuitem name="Copy to Desktop" action="Move to Desktop"/>
+ </menu>
+ </placeholder>
+ <placeholder name="Dangerous File Items Placeholder">
+ <menuitem name="Trash" action="Trash"/>
+ <menuitem name="Delete" action="Delete"/>
+ <menuitem name="Restore From Trash" action="Restore From Trash"/>
+ </placeholder>
+ <placeholder name="Extension Actions"/>
+ </menu>
+ <menu action="View">
+ <placeholder name="View Preferences Placeholder">
+ <menuitem name="Reset to Defaults" action="Reset to Defaults"/>
+ <menuitem name="Show Hidden Files" action="Show Hidden Files"/>
+ </placeholder>
+ </menu>
+</menubar>
+<popup name="background">
+ <placeholder name="Before Zoom Items">
+ <placeholder name="New Object Items">
+ <menuitem name="New Folder" action="New Folder"/>
+ <menuitem name="New Launcher" action="New Launcher"/>
+ <menu action="New Documents">
+ <menuitem name="No Templates" action="No Templates"/>
+ <placeholder name="New Documents Placeholder"/>
+ <separator name="After New Documents"/>
+ <menuitem name="New Empty File" action="New Empty File"/>
+ </menu>
+ <menu action="Scripts">
+ <placeholder name="Scripts Placeholder"/>
+ <separator name="After Scripts"/>
+ <menuitem name="Open Scripts Folder" action="Open Scripts Folder"/>
+ </menu>
+ </placeholder>
+ <separator name="View items separator"/>
+ <placeholder name="View Items"/>
+ <separator name="Clipboard separator"/>
+ <placeholder name="File Clipboard Actions">
+ <menuitem name="Paste" action="Paste"/>
+ </placeholder>
+ </placeholder>
+
+ <separator name="Folder Items separator"/>
+ <placeholder name="Folder Items Placeholder">
+ <menuitem name="Self Mount Volume" action="Self Mount Volume"/>
+ <menuitem name="Self Unmount Volume" action="Self Unmount Volume"/>
+ <menuitem name="Self Eject Volume" action="Self Eject Volume"/>
+ <menuitem name="Self Format Volume" action="Self Format Volume"/>
+ <menuitem name="Self Start Volume" action="Self Start Volume"/>
+ <menuitem name="Self Stop Volume" action="Self Stop Volume"/>
+ <menuitem name="Self Poll" action="Self Poll"/>
+ <separator name="Properties separator"/>
+ <menuitem name="Properties" action="Properties"/>
+ </placeholder>
+
+</popup>
+<popup name="selection">
+ <placeholder name="Open Placeholder">
+ <menuitem name="Open" action="Open"/>
+ <menuitem name="OpenInNewTab" action="OpenInNewTab"/>
+ <menuitem name="OpenAlternate" action="OpenAlternate"/>
+ <menuitem name="OpenFolderWindow" action="OpenFolderWindow"/>
+ <separator name="applications separator"/>
+ <placeholder name="Applications Placeholder"/>
+ <menu action="Open With">
+ <placeholder name="Applications Placeholder"/>
+ <separator name="open with separator"/>
+ <menuitem name="OtherApplication" action="OtherApplication1"/>
+ </menu>
+ <placeholder name="OtherApplicationPlaceholder">
+ <menuitem name="OtherApplication2" action="OtherApplication2"/>
+ </placeholder>
+ <menu action="Scripts">
+ <placeholder name="Scripts Placeholder"/>
+ <separator name="After Scripts"/>
+ <menuitem name="Open Scripts Folder" action="Open Scripts Folder"/>
+ </menu>
+ </placeholder>
+ <separator name="Clipboard separator"/>
+ <placeholder name="File Clipboard Actions">
+ <menuitem name="Cut" action="Cut"/>
+ <menuitem name="Copy" action="Copy"/>
+ <menuitem name="Paste Files Into" action="Paste Files Into"/>
+ </placeholder>
+ <separator name="File actions separator"/>
+ <placeholder name="File Actions">
+ <menuitem name="Create Link" action="Create Link"/>
+ <menuitem name="Rename" action="Rename"/>
+ <menu action="CopyToMenu">
+ <menuitem name="Copy to next pane" action="Copy to next pane"/>
+ <menuitem name="Copy to Home" action="Copy to Home"/>
+ <menuitem name="Copy to Desktop" action="Copy to Desktop"/>
+ </menu>
+ <menu action="MoveToMenu">
+ <menuitem name="Move to next pane" action="Move to next pane"/>
+ <menuitem name="Copy to Home" action="Move to Home"/>
+ <menuitem name="Copy to Desktop" action="Move to Desktop"/>
+ </menu>
+ </placeholder>
+ <separator name="Dangerous separator"/>
+ <placeholder name="Dangerous File Actions">
+ <menuitem name="Trash" action="Trash"/>
+ <menuitem name="Delete" action="Delete"/>
+ <menuitem name="Restore From Trash" action="Restore From Trash"/>
+ </placeholder>
+ <separator name="Appearance separator"/>
+ <placeholder name="Icon Appearance Items">
+ </placeholder>
+ <separator name="Extension actions separator"/>
+ <placeholder name="Extension Actions"/>
+ <separator name="Removable separator"/>
+ <placeholder name="Removable Media Placeholder">
+ <menuitem name="Mount Volume" action="Mount Volume"/>
+ <menuitem name="Unmount Volume" action="Unmount Volume"/>
+ <menuitem name="Eject Volume" action="Eject Volume"/>
+ <menuitem name="Format Volume" action="Format Volume"/>
+ <menuitem name="Start Volume" action="Start Volume"/>
+ <menuitem name="Stop Volume" action="Stop Volume"/>
+ <menuitem name="Poll" action="Poll"/>
+ </placeholder>
+ <menuitem name="Connect To Server Link" action="Connect To Server Link"/>
+ <separator name="Properties Separator"/>
+ <menuitem name="Properties" action="Properties"/>
+</popup>
+<popup name="location">
+ <placeholder name="Open Placeholder">
+ <menuitem name="LocationOpenInNewTab" action="LocationOpenInNewTab"/>
+ <menuitem name="LocationOpenAlternate" action="LocationOpenAlternate"/>
+ <menuitem name="LocationOpenFolderWindow" action="LocationOpenFolderWindow"/>
+ </placeholder>
+ <separator name="Location After Open Separator"/>
+ <placeholder name="Clipboard Actions">
+ <menuitem name="Cut" action="LocationCut"/>
+ <menuitem name="Copy" action="LocationCopy"/>
+ <menuitem name="LocationPasteFilesInto" action="LocationPasteFilesInto"/>
+ </placeholder>
+ <separator name="Location After Clipboard Separator"/>
+ <placeholder name="Dangerous File Actions">
+ <menuitem name="Trash" action="LocationTrash"/>
+ <menuitem name="Delete" action="LocationDelete"/>
+ <menuitem name="Restore From Trash" action="LocationRestoreFromTrash"/>
+ </placeholder>
+ <separator name="Location After Dangerous Separator"/>
+ <menuitem name="Location Mount Volume" action="Location Mount Volume"/>
+ <menuitem name="Location Unmount Volume" action="Location Unmount Volume"/>
+ <menuitem name="Location Eject Volume" action="Location Eject Volume"/>
+ <menuitem name="Location Format Volume" action="Location Format Volume"/>
+ <menuitem name="Location Start Volume" action="Location Start Volume"/>
+ <menuitem name="Location Stop Volume" action="Location Stop Volume"/>
+ <menuitem name="Location Poll" action="Location Poll"/>
+ <separator name="Properties Separator"/>
+ <menuitem name="LocationProperties" action="LocationProperties"/>
+</popup>
+</ui>
diff --git a/src/file-manager/caja-icon-view-ui.xml b/src/file-manager/caja-icon-view-ui.xml
new file mode 100644
index 00000000..89c4cb6e
--- /dev/null
+++ b/src/file-manager/caja-icon-view-ui.xml
@@ -0,0 +1,56 @@
+<ui>
+<menubar name="MenuBar">
+ <menu action="Edit">
+ <placeholder name="Edit Items Placeholder">
+ <menuitem name="Stretch" action="Stretch"/>
+ <menuitem name="Unstretch" action="Unstretch"/>
+ </placeholder>
+ </menu>
+ <menu action="View">
+ <placeholder name="View Items Placeholder">
+ <menu action="Arrange Items">
+ <menuitem name="Manual Layout" action="Manual Layout"/>
+ <placeholder name="Auto Layout">
+ <menuitem name="Sort by Name" action="Sort by Name"/>
+ <menuitem name="Sort by Size" action="Sort by Size"/>
+ <menuitem name="Sort by Type" action="Sort by Type"/>
+ <menuitem name="Sort by Modification Date" action="Sort by Modification Date"/>
+ <menuitem name="Sort by Emblems" action="Sort by Emblems"/>
+ <menuitem name="Sort by Trash Time" action="Sort by Trash Time"/>
+ </placeholder>
+ <separator name="Layout separator"/>
+ <menuitem name="Tighter Layout" action="Tighter Layout"/>
+ <menuitem name="Reversed Order" action="Reversed Order"/>
+ </menu>
+ <menuitem name="Clean Up" action="Clean Up"/>
+ <menuitem name="Keep Aligned" action="Keep Aligned"/>
+ </placeholder>
+
+ </menu>
+</menubar>
+<popup name="background">
+ <placeholder name="Before Zoom Items">
+ <placeholder name="View Items">
+ <menu action="Arrange Items">
+ <menuitem name="Manual Layout" action="Manual Layout"/>
+ <placeholder name="Auto Layout">
+ <menuitem name="Sort by Name" action="Sort by Name"/>
+ <menuitem name="Sort by Size" action="Sort by Size"/>
+ <menuitem name="Sort by Type" action="Sort by Type"/>
+ <menuitem name="Sort by Modification Date" action="Sort by Modification Date"/>
+ <menuitem name="Sort by Emblems" action="Sort by Emblems"/>
+ <menuitem name="Sort by Trash Time" action="Sort by Trash Time"/>
+ </placeholder>
+ <separator name="Layout separator"/>
+ <menuitem name="Tighter Layout" action="Tighter Layout"/>
+ <menuitem name="Reversed Order" action="Reversed Order"/>
+ </menu>
+ <menuitem name="Clean Up" action="Clean Up"/>
+ <menuitem name="Keep Aligned" action="Keep Aligned"/>
+ </placeholder>
+ </placeholder>
+</popup>
+<popup name="selection">
+ <placeholder name="Icon Appearance Items"/>
+</popup>
+</ui>
diff --git a/src/file-manager/caja-list-view-ui.xml b/src/file-manager/caja-list-view-ui.xml
new file mode 100644
index 00000000..ad9e6255
--- /dev/null
+++ b/src/file-manager/caja-list-view-ui.xml
@@ -0,0 +1,9 @@
+<ui>
+<menubar name="MenuBar">
+ <menu action="View">
+ <placeholder name="View Items Placeholder">
+ <menuitem name="Visible Columns" action="Visible Columns"/>
+ </placeholder>
+ </menu>
+</menubar>
+</ui>
diff --git a/src/file-manager/fm-actions.h b/src/file-manager/fm-actions.h
new file mode 100644
index 00000000..077d82c1
--- /dev/null
+++ b/src/file-manager/fm-actions.h
@@ -0,0 +1,110 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* fm-actions.h
+ *
+ * Copyright (C) 2004 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Alexander Larsson < [email protected]>
+ */
+
+#ifndef FM_ACTIONS_H
+#define FM_ACTIONS_H
+
+#define FM_ACTION_OPEN "Open"
+#define FM_ACTION_OPEN_ALTERNATE "OpenAlternate"
+#define FM_ACTION_OPEN_IN_NEW_TAB "OpenInNewTab"
+#define FM_ACTION_OPEN_FOLDER_WINDOW "OpenFolderWindow"
+#define FM_ACTION_LOCATION_OPEN_ALTERNATE "LocationOpenAlternate"
+#define FM_ACTION_LOCATION_OPEN_IN_NEW_TAB "LocationOpenInNewTab"
+#define FM_ACTION_LOCATION_OPEN_FOLDER_WINDOW "LocationOpenFolderWindow"
+#define FM_ACTION_OTHER_APPLICATION1 "OtherApplication1"
+#define FM_ACTION_OTHER_APPLICATION2 "OtherApplication2"
+#define FM_ACTION_NEW_FOLDER "New Folder"
+#define FM_ACTION_PROPERTIES "Properties"
+#define FM_ACTION_PROPERTIES_ACCEL "PropertiesAccel"
+#define FM_ACTION_LOCATION_PROPERTIES "LocationProperties"
+#define FM_ACTION_NO_TEMPLATES "No Templates"
+#define FM_ACTION_EMPTY_TRASH "Empty Trash"
+#define FM_ACTION_SAVE_SEARCH "Save Search"
+#define FM_ACTION_SAVE_SEARCH_AS "Save Search As"
+#define FM_ACTION_CUT "Cut"
+#define FM_ACTION_LOCATION_CUT "LocationCut"
+#define FM_ACTION_COPY "Copy"
+#define FM_ACTION_LOCATION_COPY "LocationCopy"
+#define FM_ACTION_PASTE "Paste"
+#define FM_ACTION_PASTE_FILES_INTO "Paste Files Into"
+#define FM_ACTION_COPY_TO_NEXT_PANE "Copy to next pane"
+#define FM_ACTION_MOVE_TO_NEXT_PANE "Move to next pane"
+#define FM_ACTION_COPY_TO_HOME "Copy to Home"
+#define FM_ACTION_MOVE_TO_HOME "Move to Home"
+#define FM_ACTION_COPY_TO_DESKTOP "Copy to Desktop"
+#define FM_ACTION_MOVE_TO_DESKTOP "Move to Desktop"
+#define FM_ACTION_LOCATION_PASTE_FILES_INTO "LocationPasteFilesInto"
+#define FM_ACTION_NEW_LAUNCHER "New Launcher"
+#define FM_ACTION_NEW_LAUNCHER_DESKTOP "New Launcher Desktop"
+#define FM_ACTION_RENAME "Rename"
+#define FM_ACTION_DUPLICATE "Duplicate"
+#define FM_ACTION_CREATE_LINK "Create Link"
+#define FM_ACTION_SELECT_ALL "Select All"
+#define FM_ACTION_INVERT_SELECTION "Invert Selection"
+#define FM_ACTION_SELECT_PATTERN "Select Pattern"
+#define FM_ACTION_TRASH "Trash"
+#define FM_ACTION_LOCATION_TRASH "LocationTrash"
+#define FM_ACTION_DELETE "Delete"
+#define FM_ACTION_LOCATION_DELETE "LocationDelete"
+#define FM_ACTION_RESTORE_FROM_TRASH "Restore From Trash"
+#define FM_ACTION_LOCATION_RESTORE_FROM_TRASH "LocationRestoreFromTrash"
+#define FM_ACTION_SHOW_HIDDEN_FILES "Show Hidden Files"
+#define FM_ACTION_CONNECT_TO_SERVER_LINK "Connect To Server Link"
+#define FM_ACTION_MOUNT_VOLUME "Mount Volume"
+#define FM_ACTION_UNMOUNT_VOLUME "Unmount Volume"
+#define FM_ACTION_EJECT_VOLUME "Eject Volume"
+#define FM_ACTION_FORMAT_VOLUME "Format Volume"
+#define FM_ACTION_START_VOLUME "Start Volume"
+#define FM_ACTION_STOP_VOLUME "Stop Volume"
+#define FM_ACTION_POLL "Poll"
+#define FM_ACTION_SELF_MOUNT_VOLUME "Self Mount Volume"
+#define FM_ACTION_SELF_UNMOUNT_VOLUME "Self Unmount Volume"
+#define FM_ACTION_SELF_EJECT_VOLUME "Self Eject Volume"
+#define FM_ACTION_SELF_FORMAT_VOLUME "Self Format Volume"
+#define FM_ACTION_SELF_START_VOLUME "Self Start Volume"
+#define FM_ACTION_SELF_STOP_VOLUME "Self Stop Volume"
+#define FM_ACTION_SELF_POLL "Self Poll"
+#define FM_ACTION_LOCATION_MOUNT_VOLUME "Location Mount Volume"
+#define FM_ACTION_LOCATION_UNMOUNT_VOLUME "Location Unmount Volume"
+#define FM_ACTION_LOCATION_EJECT_VOLUME "Location Eject Volume"
+#define FM_ACTION_LOCATION_FORMAT_VOLUME "Location Format Volume"
+#define FM_ACTION_LOCATION_START_VOLUME "Location Start Volume"
+#define FM_ACTION_LOCATION_STOP_VOLUME "Location Stop Volume"
+#define FM_ACTION_LOCATION_POLL "Location Poll"
+#define FM_ACTION_SCRIPTS "Scripts"
+#define FM_ACTION_NEW_DOCUMENTS "New Documents"
+#define FM_ACTION_NEW_EMPTY_FILE "New Empty File"
+#define FM_ACTION_EMPTY_TRASH_CONDITIONAL "Empty Trash Conditional"
+#define FM_ACTION_MANUAL_LAYOUT "Manual Layout"
+#define FM_ACTION_TIGHTER_LAYOUT "Tighter Layout"
+#define FM_ACTION_REVERSED_ORDER "Reversed Order"
+#define FM_ACTION_CLEAN_UP "Clean Up"
+#define FM_ACTION_KEEP_ALIGNED "Keep Aligned"
+#define FM_ACTION_ARRANGE_ITEMS "Arrange Items"
+#define FM_ACTION_STRETCH "Stretch"
+#define FM_ACTION_UNSTRETCH "Unstretch"
+#define FM_ACTION_ZOOM_ITEMS "Zoom Items"
+#define FM_ACTION_SORT_TRASH_TIME "Sort by Trash Time"
+
+#endif /* FM_ACTIONS_H */
diff --git a/src/file-manager/fm-desktop-icon-view.c b/src/file-manager/fm-desktop-icon-view.c
new file mode 100644
index 00000000..4a1bab93
--- /dev/null
+++ b/src/file-manager/fm-desktop-icon-view.c
@@ -0,0 +1,881 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* fm-desktop-icon-view.c - implementation of icon view for managing the desktop.
+
+ Copyright (C) 2000, 2001 Eazel, Inc.mou
+
+ The Mate Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Mate Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Mike Engber <[email protected]>
+ Gene Z. Ragan <[email protected]>
+ Miguel de Icaza <[email protected]>
+*/
+
+#include <config.h>
+#include "fm-icon-container.h"
+#include "fm-desktop-icon-view.h"
+#include "fm-actions.h"
+
+#include <X11/Xatom.h>
+#include <gtk/gtk.h>
+#include <dirent.h>
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-mate-extensions.h>
+#include <eel/eel-gtk-extensions.h>
+#include <eel/eel-gtk-macros.h>
+#include <eel/eel-stock-dialogs.h>
+#include <eel/eel-string.h>
+#include <eel/eel-vfs-extensions.h>
+#include <fcntl.h>
+#include <gdk/gdkx.h>
+#include <glib/gi18n.h>
+#include <libcaja-private/caja-desktop-icon-file.h>
+#include <libcaja-private/caja-directory-background.h>
+#include <libcaja-private/caja-directory-notify.h>
+#include <libcaja-private/caja-file-changes-queue.h>
+#include <libcaja-private/caja-file-operations.h>
+#include <libcaja-private/caja-file-utilities.h>
+#include <libcaja-private/caja-ui-utilities.h>
+#include <libcaja-private/caja-global-preferences.h>
+#include <libcaja-private/caja-view-factory.h>
+#include <libcaja-private/caja-link.h>
+#include <libcaja-private/caja-metadata.h>
+#include <libcaja-private/caja-monitor.h>
+#include <libcaja-private/caja-program-choosing.h>
+#include <libcaja-private/caja-trash-monitor.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+/* Timeout to check the desktop directory for updates */
+#define RESCAN_TIMEOUT 4
+
+struct FMDesktopIconViewDetails
+{
+ GdkWindow *root_window;
+ GtkActionGroup *desktop_action_group;
+ guint desktop_merge_id;
+
+ /* For the desktop rescanning
+ */
+ gulong delayed_init_signal;
+ guint reload_desktop_timeout;
+ gboolean pending_rescan;
+};
+
+static void fm_desktop_icon_view_init (FMDesktopIconView *desktop_icon_view);
+static void fm_desktop_icon_view_class_init (FMDesktopIconViewClass *klass);
+static void default_zoom_level_changed (gpointer user_data);
+static gboolean real_supports_auto_layout (FMIconView *view);
+static gboolean real_supports_scaling (FMIconView *view);
+static gboolean real_supports_keep_aligned (FMIconView *view);
+static gboolean real_supports_labels_beside_icons (FMIconView *view);
+static void real_merge_menus (FMDirectoryView *view);
+static void real_update_menus (FMDirectoryView *view);
+static gboolean real_supports_zooming (FMDirectoryView *view);
+static void fm_desktop_icon_view_update_icon_container_fonts (FMDesktopIconView *view);
+
+EEL_CLASS_BOILERPLATE (FMDesktopIconView,
+ fm_desktop_icon_view,
+ FM_TYPE_ICON_VIEW)
+
+static char *desktop_directory;
+static time_t desktop_dir_modify_time;
+
+static void
+desktop_directory_changed_callback (gpointer callback_data)
+{
+ g_free (desktop_directory);
+ desktop_directory = caja_get_desktop_directory ();
+}
+
+static void
+lockdown_disable_command_line_changed_callback (gpointer callback_data)
+{
+ fm_directory_view_update_menus (FM_DIRECTORY_VIEW (callback_data));
+}
+
+static CajaIconContainer *
+get_icon_container (FMDesktopIconView *icon_view)
+{
+ g_return_val_if_fail (FM_IS_DESKTOP_ICON_VIEW (icon_view), NULL);
+ g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (gtk_bin_get_child (GTK_BIN (icon_view))), NULL);
+
+ return CAJA_ICON_CONTAINER (gtk_bin_get_child (GTK_BIN (icon_view)));
+}
+
+static void
+icon_container_set_workarea (CajaIconContainer *icon_container,
+ GdkScreen *screen,
+ long *workareas,
+ int n_items)
+{
+ int left, right, top, bottom;
+ int screen_width, screen_height;
+ int i;
+
+ left = right = top = bottom = 0;
+
+ screen_width = gdk_screen_get_width (screen);
+ screen_height = gdk_screen_get_height (screen);
+
+ for (i = 0; i < n_items; i += 4)
+ {
+ int x = workareas [i];
+ int y = workareas [i + 1];
+ int width = workareas [i + 2];
+ int height = workareas [i + 3];
+
+ if ((x + width) > screen_width || (y + height) > screen_height)
+ continue;
+
+ left = MAX (left, x);
+ right = MAX (right, screen_width - width - x);
+ top = MAX (top, y);
+ bottom = MAX (bottom, screen_height - height - y);
+ }
+
+ caja_icon_container_set_margins (icon_container,
+ left, right, top, bottom);
+}
+
+static void
+net_workarea_changed (FMDesktopIconView *icon_view,
+ GdkWindow *window)
+{
+ long *nworkareas = NULL;
+ long *workareas = NULL;
+ GdkAtom type_returned;
+ int format_returned;
+ int length_returned;
+ CajaIconContainer *icon_container;
+ GdkScreen *screen;
+
+ g_return_if_fail (FM_IS_DESKTOP_ICON_VIEW (icon_view));
+
+ icon_container = get_icon_container (icon_view);
+
+ /* Find the number of desktops so we know how long the
+ * workareas array is going to be (each desktop will have four
+ * elements in the workareas array describing
+ * x,y,width,height) */
+ gdk_error_trap_push ();
+ if (!gdk_property_get (window,
+ gdk_atom_intern ("_NET_NUMBER_OF_DESKTOPS", FALSE),
+ gdk_x11_xatom_to_atom (XA_CARDINAL),
+ 0, 4, FALSE,
+ &type_returned,
+ &format_returned,
+ &length_returned,
+ (guchar **) &nworkareas))
+ {
+ g_warning("Can not calculate _NET_NUMBER_OF_DESKTOPS");
+ }
+ if (gdk_error_trap_pop()
+ || nworkareas == NULL
+ || type_returned != gdk_x11_xatom_to_atom (XA_CARDINAL)
+ || format_returned != 32)
+ g_warning("Can not calculate _NET_NUMBER_OF_DESKTOPS");
+
+ /* Note : gdk_property_get() is broken (API documents admit
+ * this). As a length argument, it expects the number of
+ * _bytes_ of data you require. Internally, gdk_property_get
+ * converts that value to a count of 32 bit (4 byte) elements.
+ * However, the length returned is in bytes, but is calculated
+ * via the count of returned elements * sizeof(long). This
+ * means on a 64 bit system, the number of bytes you have to
+ * request does not correspond to the number of bytes you get
+ * back, and is the reason for the workaround below.
+ */
+ gdk_error_trap_push ();
+ if (nworkareas == NULL || (*nworkareas < 1)
+ || !gdk_property_get (window,
+ gdk_atom_intern ("_NET_WORKAREA", FALSE),
+ gdk_x11_xatom_to_atom (XA_CARDINAL),
+ 0, ((*nworkareas) * 4 * 4), FALSE,
+ &type_returned,
+ &format_returned,
+ &length_returned,
+ (guchar **) &workareas))
+ {
+ g_warning("Can not get _NET_WORKAREA");
+ workareas = NULL;
+ }
+
+ if (gdk_error_trap_pop ()
+ || workareas == NULL
+ || type_returned != gdk_x11_xatom_to_atom (XA_CARDINAL)
+ || ((*nworkareas) * 4 * sizeof(long)) != length_returned
+ || format_returned != 32)
+ {
+ g_warning("Can not determine workarea, guessing at layout");
+ caja_icon_container_set_margins (icon_container,
+ 0, 0, 0, 0);
+ }
+ else
+ {
+ screen = gdk_drawable_get_screen (GDK_DRAWABLE (window));
+
+ icon_container_set_workarea (
+ icon_container, screen, workareas, length_returned / sizeof (long));
+ }
+
+ if (nworkareas != NULL)
+ g_free (nworkareas);
+
+ if (workareas != NULL)
+ g_free (workareas);
+}
+
+static GdkFilterReturn
+desktop_icon_view_property_filter (GdkXEvent *gdk_xevent,
+ GdkEvent *event,
+ gpointer data)
+{
+ XEvent *xevent = gdk_xevent;
+ FMDesktopIconView *icon_view;
+
+ icon_view = FM_DESKTOP_ICON_VIEW (data);
+
+ switch (xevent->type)
+ {
+ case PropertyNotify:
+ if (xevent->xproperty.atom == gdk_x11_get_xatom_by_name ("_NET_WORKAREA"))
+ net_workarea_changed (icon_view, event->any.window);
+ break;
+ default:
+ break;
+ }
+
+ return GDK_FILTER_CONTINUE;
+}
+
+static void
+fm_desktop_icon_view_destroy (GtkObject *object)
+{
+ FMDesktopIconView *icon_view;
+ GtkUIManager *ui_manager;
+
+ icon_view = FM_DESKTOP_ICON_VIEW (object);
+
+ /* Remove desktop rescan timeout. */
+ if (icon_view->details->reload_desktop_timeout != 0)
+ {
+ g_source_remove (icon_view->details->reload_desktop_timeout);
+ icon_view->details->reload_desktop_timeout = 0;
+ }
+
+ ui_manager = fm_directory_view_get_ui_manager (FM_DIRECTORY_VIEW (icon_view));
+ if (ui_manager != NULL)
+ {
+ caja_ui_unmerge_ui (ui_manager,
+ &icon_view->details->desktop_merge_id,
+ &icon_view->details->desktop_action_group);
+ }
+
+ GTK_OBJECT_CLASS (parent_class)->destroy (object);
+}
+
+static void
+fm_desktop_icon_view_finalize (GObject *object)
+{
+ FMDesktopIconView *icon_view;
+
+ icon_view = FM_DESKTOP_ICON_VIEW (object);
+
+ eel_preferences_remove_callback (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_ZOOM_LEVEL,
+ default_zoom_level_changed,
+ icon_view);
+
+ eel_preferences_remove_callback (CAJA_PREFERENCES_LOCKDOWN_COMMAND_LINE,
+ lockdown_disable_command_line_changed_callback,
+ icon_view);
+
+ g_free (icon_view->details);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+fm_desktop_icon_view_class_init (FMDesktopIconViewClass *class)
+{
+ G_OBJECT_CLASS (class)->finalize = fm_desktop_icon_view_finalize;
+
+ GTK_OBJECT_CLASS (class)->destroy = fm_desktop_icon_view_destroy;
+
+ FM_DIRECTORY_VIEW_CLASS (class)->merge_menus = real_merge_menus;
+ FM_DIRECTORY_VIEW_CLASS (class)->update_menus = real_update_menus;
+ FM_DIRECTORY_VIEW_CLASS (class)->supports_zooming = real_supports_zooming;
+
+ FM_ICON_VIEW_CLASS (class)->supports_auto_layout = real_supports_auto_layout;
+ FM_ICON_VIEW_CLASS (class)->supports_scaling = real_supports_scaling;
+ FM_ICON_VIEW_CLASS (class)->supports_keep_aligned = real_supports_keep_aligned;
+ FM_ICON_VIEW_CLASS (class)->supports_labels_beside_icons = real_supports_labels_beside_icons;
+}
+
+static void
+fm_desktop_icon_view_handle_middle_click (CajaIconContainer *icon_container,
+ GdkEventButton *event,
+ FMDesktopIconView *desktop_icon_view)
+{
+ XButtonEvent x_event;
+
+ /* During a mouse click we have the pointer and keyboard grab.
+ * We will send a fake event to the root window which will cause it
+ * to try to get the grab so we need to let go ourselves.
+ */
+ gdk_pointer_ungrab (GDK_CURRENT_TIME);
+ gdk_keyboard_ungrab (GDK_CURRENT_TIME);
+
+ /* Stop the event because we don't want anyone else dealing with it. */
+ gdk_flush ();
+ g_signal_stop_emission_by_name (icon_container, "middle_click");
+
+ /* build an X event to represent the middle click. */
+ x_event.type = ButtonPress;
+ x_event.send_event = True;
+ x_event.display = GDK_DISPLAY ();
+ x_event.window = GDK_ROOT_WINDOW ();
+ x_event.root = GDK_ROOT_WINDOW ();
+ x_event.subwindow = 0;
+ x_event.time = event->time;
+ x_event.x = event->x;
+ x_event.y = event->y;
+ x_event.x_root = event->x_root;
+ x_event.y_root = event->y_root;
+ x_event.state = event->state;
+ x_event.button = event->button;
+ x_event.same_screen = True;
+
+ /* Send it to the root window, the window manager will handle it. */
+ XSendEvent (GDK_DISPLAY (), GDK_ROOT_WINDOW (), True,
+ ButtonPressMask, (XEvent *) &x_event);
+}
+
+static void
+unrealized_callback (GtkWidget *widget, FMDesktopIconView *desktop_icon_view)
+{
+ g_return_if_fail (desktop_icon_view->details->root_window != NULL);
+
+ /* Remove the property filter */
+ gdk_window_remove_filter (desktop_icon_view->details->root_window,
+ desktop_icon_view_property_filter,
+ desktop_icon_view);
+ desktop_icon_view->details->root_window = NULL;
+}
+
+static void
+realized_callback (GtkWidget *widget, FMDesktopIconView *desktop_icon_view)
+{
+ GdkWindow *root_window;
+ GdkScreen *screen;
+ GtkAllocation allocation;
+
+ g_return_if_fail (desktop_icon_view->details->root_window == NULL);
+
+ screen = gtk_widget_get_screen (widget);
+
+ /* Ugly HACK for the problem that the views realize at the
+ * wrong size and then get resized. (This is a problem with
+ * MateComponentPlug.) This was leading to problems where initial
+ * layout was done at 60x60 stacking all desktop icons in
+ * the top left corner.
+ */
+ allocation.x = 0;
+ allocation.y = 0;
+ allocation.width = gdk_screen_get_width (screen);
+ allocation.height = gdk_screen_get_height (screen);
+ gtk_widget_size_allocate (GTK_WIDGET(get_icon_container(desktop_icon_view)),
+ &allocation);
+
+ root_window = gdk_screen_get_root_window (screen);
+
+ desktop_icon_view->details->root_window = root_window;
+
+ /* Read out the workarea geometry and update the icon container accordingly */
+ net_workarea_changed (desktop_icon_view, root_window);
+
+ /* Setup the property filter */
+ gdk_window_set_events (root_window, GDK_PROPERTY_CHANGE_MASK);
+ gdk_window_add_filter (root_window,
+ desktop_icon_view_property_filter,
+ desktop_icon_view);
+}
+
+static CajaZoomLevel
+get_default_zoom_level (void)
+{
+ static gboolean auto_storage_added = FALSE;
+ static CajaZoomLevel default_zoom_level = CAJA_ZOOM_LEVEL_STANDARD;
+
+ if (!auto_storage_added)
+ {
+ auto_storage_added = TRUE;
+ eel_preferences_add_auto_enum (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_ZOOM_LEVEL,
+ (int *) &default_zoom_level);
+ }
+
+ return CLAMP (default_zoom_level, CAJA_ZOOM_LEVEL_SMALLEST, CAJA_ZOOM_LEVEL_LARGEST);
+}
+
+static void
+default_zoom_level_changed (gpointer user_data)
+{
+ CajaZoomLevel new_level;
+ FMDesktopIconView *desktop_icon_view;
+
+ desktop_icon_view = FM_DESKTOP_ICON_VIEW (user_data);
+ new_level = get_default_zoom_level ();
+
+ caja_icon_container_set_zoom_level (get_icon_container (desktop_icon_view),
+ new_level);
+}
+
+static gboolean
+do_desktop_rescan (gpointer data)
+{
+ FMDesktopIconView *desktop_icon_view;
+ struct stat buf;
+
+ desktop_icon_view = FM_DESKTOP_ICON_VIEW (data);
+ if (desktop_icon_view->details->pending_rescan)
+ {
+ return TRUE;
+ }
+
+ if (stat (desktop_directory, &buf) == -1)
+ {
+ return TRUE;
+ }
+
+ if (buf.st_ctime == desktop_dir_modify_time)
+ {
+ return TRUE;
+ }
+
+ desktop_icon_view->details->pending_rescan = TRUE;
+
+ caja_directory_force_reload (
+ fm_directory_view_get_model (
+ FM_DIRECTORY_VIEW (desktop_icon_view)));
+ return TRUE;
+}
+
+static void
+done_loading (GtkObject *DirectoryView, FMDesktopIconView *desktop_icon_view)
+{
+ struct stat buf;
+
+ desktop_icon_view->details->pending_rescan = FALSE;
+ if (stat (desktop_directory, &buf) == -1)
+ {
+ return;
+ }
+
+ desktop_dir_modify_time = buf.st_ctime;
+}
+
+/* This function is used because the CajaDirectory model does not
+ * exist always in the desktop_icon_view, so we wait until it has been
+ * instantiated.
+ */
+static void
+delayed_init (FMDesktopIconView *desktop_icon_view)
+{
+ /* Keep track of the load time. */
+ g_signal_connect_object (fm_directory_view_get_model (FM_DIRECTORY_VIEW (desktop_icon_view)),
+ "done_loading",
+ G_CALLBACK (done_loading), desktop_icon_view, 0);
+
+ /* Monitor desktop directory. */
+ desktop_icon_view->details->reload_desktop_timeout =
+ g_timeout_add_seconds (RESCAN_TIMEOUT, do_desktop_rescan, desktop_icon_view);
+
+ g_signal_handler_disconnect (desktop_icon_view,
+ desktop_icon_view->details->delayed_init_signal);
+
+ desktop_icon_view->details->delayed_init_signal = 0;
+}
+
+static void
+font_changed_callback (gpointer callback_data)
+{
+ g_return_if_fail (FM_IS_DESKTOP_ICON_VIEW (callback_data));
+
+ fm_desktop_icon_view_update_icon_container_fonts (FM_DESKTOP_ICON_VIEW (callback_data));
+}
+
+static void
+fm_desktop_icon_view_update_icon_container_fonts (FMDesktopIconView *icon_view)
+{
+ CajaIconContainer *icon_container;
+ char *font;
+
+ icon_container = get_icon_container (icon_view);
+ g_assert (icon_container != NULL);
+
+ font = eel_preferences_get (CAJA_PREFERENCES_DESKTOP_FONT);
+
+ caja_icon_container_set_font (icon_container, font);
+
+ g_free (font);
+}
+
+static void
+fm_desktop_icon_view_init (FMDesktopIconView *desktop_icon_view)
+{
+ CajaIconContainer *icon_container;
+ GtkAllocation allocation;
+ GtkAdjustment *hadj, *vadj;
+
+ if (desktop_directory == NULL)
+ {
+ eel_preferences_add_callback (CAJA_PREFERENCES_DESKTOP_IS_HOME_DIR,
+ desktop_directory_changed_callback,
+ NULL);
+ desktop_directory_changed_callback (NULL);
+ }
+
+ fm_icon_view_filter_by_screen (FM_ICON_VIEW (desktop_icon_view), TRUE);
+ icon_container = get_icon_container (desktop_icon_view);
+ caja_icon_container_set_use_drop_shadows (icon_container, TRUE);
+ fm_icon_container_set_sort_desktop (FM_ICON_CONTAINER (icon_container), TRUE);
+
+ /* Set up details */
+ desktop_icon_view->details = g_new0 (FMDesktopIconViewDetails, 1);
+
+ /* Do a reload on the desktop if we don't have FAM, a smarter
+ * way to keep track of the items on the desktop.
+ */
+ if (!caja_monitor_active ())
+ {
+ desktop_icon_view->details->delayed_init_signal = g_signal_connect_object
+ (desktop_icon_view, "begin_loading",
+ G_CALLBACK (delayed_init), desktop_icon_view, 0);
+ }
+
+ caja_icon_container_set_is_fixed_size (icon_container, TRUE);
+ caja_icon_container_set_is_desktop (icon_container, TRUE);
+ caja_icon_container_set_store_layout_timestamps (icon_container, TRUE);
+
+ /* Set allocation to be at 0, 0 */
+ gtk_widget_get_allocation (GTK_WIDGET (icon_container), &allocation);
+ allocation.x = 0;
+ allocation.y = 0;
+ gtk_widget_set_allocation (GTK_WIDGET (icon_container), &allocation);
+
+ gtk_widget_queue_resize (GTK_WIDGET (icon_container));
+
+ hadj = gtk_layout_get_hadjustment (GTK_LAYOUT (icon_container));
+ vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (icon_container));
+
+ eel_gtk_adjustment_set_value (hadj, 0);
+ eel_gtk_adjustment_set_value (vadj, 0);
+
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (desktop_icon_view),
+ GTK_SHADOW_NONE);
+
+ fm_directory_view_ignore_hidden_file_preferences
+ (FM_DIRECTORY_VIEW (desktop_icon_view));
+
+ fm_directory_view_set_show_foreign (FM_DIRECTORY_VIEW (desktop_icon_view),
+ FALSE);
+
+ /* Set our default layout mode */
+ caja_icon_container_set_layout_mode (icon_container,
+ gtk_widget_get_direction (GTK_WIDGET(icon_container)) == GTK_TEXT_DIR_RTL ?
+ CAJA_ICON_LAYOUT_T_B_R_L :
+ CAJA_ICON_LAYOUT_T_B_L_R);
+
+ g_signal_connect_object (icon_container, "middle_click",
+ G_CALLBACK (fm_desktop_icon_view_handle_middle_click), desktop_icon_view, 0);
+ g_signal_connect_object (desktop_icon_view, "realize",
+ G_CALLBACK (realized_callback), desktop_icon_view, 0);
+ g_signal_connect_object (desktop_icon_view, "unrealize",
+ G_CALLBACK (unrealized_callback), desktop_icon_view, 0);
+
+ eel_preferences_add_callback (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_ZOOM_LEVEL,
+ default_zoom_level_changed,
+ desktop_icon_view);
+
+ eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_DESKTOP_FONT,
+ font_changed_callback,
+ desktop_icon_view, G_OBJECT (desktop_icon_view));
+
+ default_zoom_level_changed (desktop_icon_view);
+ fm_desktop_icon_view_update_icon_container_fonts (desktop_icon_view);
+
+ eel_preferences_add_callback (CAJA_PREFERENCES_LOCKDOWN_COMMAND_LINE,
+ lockdown_disable_command_line_changed_callback,
+ desktop_icon_view);
+
+}
+
+static void
+action_new_launcher_callback (GtkAction *action, gpointer data)
+{
+ char *desktop_directory;
+
+ g_assert (FM_DIRECTORY_VIEW (data));
+
+ desktop_directory = caja_get_desktop_directory ();
+
+ caja_launch_application_from_command (gtk_widget_get_screen (GTK_WIDGET (data)),
+ "mate-desktop-item-edit",
+ "mate-desktop-item-edit",
+ FALSE,
+ "--create-new", desktop_directory, NULL);
+ g_free (desktop_directory);
+
+}
+
+static void
+action_change_background_callback (GtkAction *action,
+ gpointer data)
+{
+ g_assert (FM_DIRECTORY_VIEW (data));
+
+ caja_launch_application_from_command (gtk_widget_get_screen (GTK_WIDGET (data)),
+ _("Background"),
+ "mate-appearance-properties",
+ FALSE,
+ "--show-page=background", NULL);
+}
+
+static void
+action_empty_trash_conditional_callback (GtkAction *action,
+ gpointer data)
+{
+ g_assert (FM_IS_DIRECTORY_VIEW (data));
+
+ caja_file_operations_empty_trash (GTK_WIDGET (data));
+}
+
+static gboolean
+trash_link_is_selection (FMDirectoryView *view)
+{
+ GList *selection;
+ CajaDesktopLink *link;
+ gboolean result;
+
+ result = FALSE;
+
+ selection = fm_directory_view_get_selection (view);
+
+ if (eel_g_list_exactly_one_item (selection) &&
+ CAJA_IS_DESKTOP_ICON_FILE (selection->data))
+ {
+ link = caja_desktop_icon_file_get_link (CAJA_DESKTOP_ICON_FILE (selection->data));
+ /* link may be NULL if the link was recently removed (unmounted) */
+ if (link != NULL &&
+ caja_desktop_link_get_link_type (link) == CAJA_DESKTOP_LINK_TRASH)
+ {
+ result = TRUE;
+ }
+ if (link)
+ {
+ g_object_unref (link);
+ }
+ }
+
+ caja_file_list_free (selection);
+
+ return result;
+}
+
+static void
+real_update_menus (FMDirectoryView *view)
+{
+ FMDesktopIconView *desktop_view;
+ char *label;
+ gboolean disable_command_line;
+ gboolean include_empty_trash;
+ GtkAction *action;
+
+ g_assert (FM_IS_DESKTOP_ICON_VIEW (view));
+
+ EEL_CALL_PARENT (FM_DIRECTORY_VIEW_CLASS, update_menus, (view));
+
+ desktop_view = FM_DESKTOP_ICON_VIEW (view);
+
+ /* New Launcher */
+ disable_command_line = eel_preferences_get_boolean (CAJA_PREFERENCES_LOCKDOWN_COMMAND_LINE);
+ action = gtk_action_group_get_action (desktop_view->details->desktop_action_group,
+ FM_ACTION_NEW_LAUNCHER_DESKTOP);
+ gtk_action_set_visible (action,
+ !disable_command_line);
+
+ /* Empty Trash */
+ include_empty_trash = trash_link_is_selection (view);
+ action = gtk_action_group_get_action (desktop_view->details->desktop_action_group,
+ FM_ACTION_EMPTY_TRASH_CONDITIONAL);
+ gtk_action_set_visible (action,
+ include_empty_trash);
+ if (include_empty_trash)
+ {
+ label = g_strdup (_("E_mpty Trash"));
+ g_object_set (action , "label", label, NULL);
+ gtk_action_set_sensitive (action,
+ !caja_trash_monitor_is_empty ());
+ g_free (label);
+ }
+}
+
+static const GtkActionEntry desktop_view_entries[] =
+{
+ /* name, stock id */
+ {
+ "New Launcher Desktop", NULL,
+ /* label, accelerator */
+ N_("Create L_auncher..."), NULL,
+ /* tooltip */
+ N_("Create a new launcher"),
+ G_CALLBACK (action_new_launcher_callback)
+ },
+ /* name, stock id */
+ {
+ "Change Background", NULL,
+ /* label, accelerator */
+ N_("Change Desktop _Background"), NULL,
+ /* tooltip */
+ N_("Show a window that lets you set your desktop background's pattern or color"),
+ G_CALLBACK (action_change_background_callback)
+ },
+ /* name, stock id */
+ {
+ "Empty Trash Conditional", NULL,
+ /* label, accelerator */
+ N_("Empty Trash"), NULL,
+ /* tooltip */
+ N_("Delete all items in the Trash"),
+ G_CALLBACK (action_empty_trash_conditional_callback)
+ },
+};
+
+static void
+real_merge_menus (FMDirectoryView *view)
+{
+ FMDesktopIconView *desktop_view;
+ GtkUIManager *ui_manager;
+ GtkActionGroup *action_group;
+ const char *ui;
+
+ EEL_CALL_PARENT (FM_DIRECTORY_VIEW_CLASS, merge_menus, (view));
+
+ desktop_view = FM_DESKTOP_ICON_VIEW (view);
+
+ ui_manager = fm_directory_view_get_ui_manager (view);
+
+ action_group = gtk_action_group_new ("DesktopViewActions");
+ gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
+ desktop_view->details->desktop_action_group = action_group;
+ gtk_action_group_add_actions (action_group,
+ desktop_view_entries, G_N_ELEMENTS (desktop_view_entries),
+ view);
+
+ gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
+ g_object_unref (action_group); /* owned by ui manager */
+
+ ui = caja_ui_string_get ("caja-desktop-icon-view-ui.xml");
+ desktop_view->details->desktop_merge_id =
+ gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, NULL);
+}
+
+static gboolean
+real_supports_auto_layout (FMIconView *view)
+{
+ /* Can't use auto-layout on the desktop, because doing so
+ * would cause all sorts of complications involving the
+ * fixed-size window.
+ */
+ return FALSE;
+}
+
+static gboolean
+real_supports_scaling (FMIconView *view)
+{
+ return TRUE;
+}
+
+static gboolean
+real_supports_keep_aligned (FMIconView *view)
+{
+ return TRUE;
+}
+
+static gboolean
+real_supports_labels_beside_icons (FMIconView *view)
+{
+ return FALSE;
+}
+
+static gboolean
+real_supports_zooming (FMDirectoryView *view)
+{
+ /* Can't zoom on the desktop, because doing so would cause all
+ * sorts of complications involving the fixed-size window.
+ */
+ return FALSE;
+}
+
+static CajaView *
+fm_desktop_icon_view_create (CajaWindowSlotInfo *slot)
+{
+ FMIconView *view;
+
+ view = g_object_new (FM_TYPE_DESKTOP_ICON_VIEW,
+ "window-slot", slot,
+ NULL);
+ return CAJA_VIEW (view);
+}
+
+static gboolean
+fm_desktop_icon_view_supports_uri (const char *uri,
+ GFileType file_type,
+ const char *mime_type)
+{
+ if (g_str_has_prefix (uri, EEL_DESKTOP_URI))
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static CajaViewInfo fm_desktop_icon_view =
+{
+ FM_DESKTOP_ICON_VIEW_ID,
+ "Desktop View",
+ "_Desktop",
+ N_("The desktop view encountered an error."),
+ N_("The desktop view encountered an error while starting up."),
+ "Display this location with the desktop view.",
+ fm_desktop_icon_view_create,
+ fm_desktop_icon_view_supports_uri
+};
+
+void
+fm_desktop_icon_view_register (void)
+{
+ fm_desktop_icon_view.error_label = _(fm_desktop_icon_view.error_label);
+ fm_desktop_icon_view.startup_error_label = _(fm_desktop_icon_view.startup_error_label);
+
+ caja_view_factory_register (&fm_desktop_icon_view);
+}
diff --git a/src/file-manager/fm-desktop-icon-view.h b/src/file-manager/fm-desktop-icon-view.h
new file mode 100644
index 00000000..f5702296
--- /dev/null
+++ b/src/file-manager/fm-desktop-icon-view.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* fm-icon-view.h - interface for icon view of directory.
+
+ Copyright (C) 2000 Eazel, Inc.
+
+ The Mate Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Mate Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Mike Engber <[email protected]>
+*/
+
+#ifndef FM_DESKTOP_ICON_VIEW_H
+#define FM_DESKTOP_ICON_VIEW_H
+
+#include "fm-icon-view.h"
+
+#define FM_TYPE_DESKTOP_ICON_VIEW fm_desktop_icon_view_get_type()
+#define FM_DESKTOP_ICON_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_DESKTOP_ICON_VIEW, FMDesktopIconView))
+#define FM_DESKTOP_ICON_VIEW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_DESKTOP_ICON_VIEW, FMDesktopIconViewClass))
+#define FM_IS_DESKTOP_ICON_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_DESKTOP_ICON_VIEW))
+#define FM_IS_DESKTOP_ICON_VIEW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_DESKTOP_ICON_VIEW))
+#define FM_DESKTOP_ICON_VIEW_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_DESKTOP_ICON_VIEW, FMDesktopIconViewClass))
+
+#define FM_DESKTOP_ICON_VIEW_ID "OAFIID:Caja_File_Manager_Desktop_Icon_View"
+
+typedef struct FMDesktopIconViewDetails FMDesktopIconViewDetails;
+typedef struct
+{
+ FMIconView parent;
+ FMDesktopIconViewDetails *details;
+} FMDesktopIconView;
+
+typedef struct
+{
+ FMIconViewClass parent_class;
+} FMDesktopIconViewClass;
+
+/* GObject support */
+GType fm_desktop_icon_view_get_type (void);
+void fm_desktop_icon_view_register (void);
+
+#endif /* FM_DESKTOP_ICON_VIEW_H */
diff --git a/src/file-manager/fm-directory-view.c b/src/file-manager/fm-directory-view.c
new file mode 100644
index 00000000..a192aa58
--- /dev/null
+++ b/src/file-manager/fm-directory-view.c
@@ -0,0 +1,10936 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* fm-directory-view.c
+ *
+ * Copyright (C) 1999, 2000 Free Software Foundation
+ * Copyright (C) 2000, 2001 Eazel, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Ettore Perazzoli,
+ * John Sullivan <[email protected]>,
+ * Darin Adler <[email protected]>,
+ * Pavel Cisler <[email protected]>,
+ * David Emory Watson <[email protected]>
+ */
+
+#include <config.h>
+#include <math.h>
+#include "fm-directory-view.h"
+#include "fm-list-view.h"
+#include "fm-desktop-icon-view.h"
+
+#include "fm-actions.h"
+#include "fm-error-reporting.h"
+#include "fm-properties-window.h"
+#include "libcaja-private/caja-open-with-dialog.h"
+
+#include <eel/eel-background.h>
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-mate-extensions.h>
+#include <eel/eel-gtk-extensions.h>
+#include <eel/eel-gtk-macros.h>
+#include <eel/eel-stock-dialogs.h>
+#include <eel/eel-string.h>
+#include <eel/eel-vfs-extensions.h>
+#include <eel/eel-marshal.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gio/gio.h>
+#include <libcaja-private/caja-recent.h>
+#include <libcaja-extension/caja-menu-provider.h>
+#include <libcaja-private/caja-clipboard.h>
+#include <libcaja-private/caja-clipboard-monitor.h>
+#include <libcaja-private/caja-debug-log.h>
+#include <libcaja-private/caja-desktop-icon-file.h>
+#include <libcaja-private/caja-desktop-directory.h>
+#include <libcaja-private/caja-search-directory.h>
+#include <libcaja-private/caja-directory-background.h>
+#include <libcaja-private/caja-directory.h>
+#include <libcaja-private/caja-dnd.h>
+#include <libcaja-private/caja-file-attributes.h>
+#include <libcaja-private/caja-file-changes-queue.h>
+#include <libcaja-private/caja-file-dnd.h>
+#include <libcaja-private/caja-file-operations.h>
+#include <libcaja-private/caja-file-utilities.h>
+#include <libcaja-private/caja-file-private.h> /* for caja_file_get_existing_by_uri */
+#include <libcaja-private/caja-global-preferences.h>
+#include <libcaja-private/caja-link.h>
+#include <libcaja-private/caja-marshal.h>
+#include <libcaja-private/caja-metadata.h>
+#include <libcaja-private/caja-mime-actions.h>
+#include <libcaja-private/caja-module.h>
+#include <libcaja-private/caja-program-choosing.h>
+#include <libcaja-private/caja-trash-monitor.h>
+#include <libcaja-private/caja-ui-utilities.h>
+#include <libcaja-private/caja-signaller.h>
+#include <libcaja-private/caja-autorun.h>
+#include <libcaja-private/caja-icon-names.h>
+
+/* Minimum starting update inverval */
+#define UPDATE_INTERVAL_MIN 100
+/* Maximum update interval */
+#define UPDATE_INTERVAL_MAX 2000
+/* Amount of miliseconds the update interval is increased */
+#define UPDATE_INTERVAL_INC 250
+/* Interval at which the update interval is increased */
+#define UPDATE_INTERVAL_TIMEOUT_INTERVAL 250
+/* Milliseconds that have to pass without a change to reset the update interval */
+#define UPDATE_INTERVAL_RESET 1000
+
+#define SILENT_WINDOW_OPEN_LIMIT 5
+
+#define DUPLICATE_HORIZONTAL_ICON_OFFSET 70
+#define DUPLICATE_VERTICAL_ICON_OFFSET 30
+
+#define MAX_QUEUED_UPDATES 500
+
+#define FM_DIRECTORY_VIEW_MENU_PATH_APPLICATIONS_SUBMENU_PLACEHOLDER "/MenuBar/File/Open Placeholder/Open With/Applications Placeholder"
+#define FM_DIRECTORY_VIEW_MENU_PATH_APPLICATIONS_PLACEHOLDER "/MenuBar/File/Open Placeholder/Applications Placeholder"
+#define FM_DIRECTORY_VIEW_MENU_PATH_SCRIPTS_PLACEHOLDER "/MenuBar/File/Open Placeholder/Scripts/Scripts Placeholder"
+#define FM_DIRECTORY_VIEW_MENU_PATH_EXTENSION_ACTIONS_PLACEHOLDER "/MenuBar/Edit/Extension Actions"
+#define FM_DIRECTORY_VIEW_MENU_PATH_NEW_DOCUMENTS_PLACEHOLDER "/MenuBar/File/New Items Placeholder/New Documents/New Documents Placeholder"
+#define FM_DIRECTORY_VIEW_MENU_PATH_OPEN "/MenuBar/File/Open Placeholder/Open"
+
+#define FM_DIRECTORY_VIEW_POPUP_PATH_SELECTION "/selection"
+#define FM_DIRECTORY_VIEW_POPUP_PATH_APPLICATIONS_SUBMENU_PLACEHOLDER "/selection/Open Placeholder/Open With/Applications Placeholder"
+#define FM_DIRECTORY_VIEW_POPUP_PATH_APPLICATIONS_PLACEHOLDER "/selection/Open Placeholder/Applications Placeholder"
+#define FM_DIRECTORY_VIEW_POPUP_PATH_SCRIPTS_PLACEHOLDER "/selection/Open Placeholder/Scripts/Scripts Placeholder"
+#define FM_DIRECTORY_VIEW_POPUP_PATH_EXTENSION_ACTIONS "/selection/Extension Actions"
+#define FM_DIRECTORY_VIEW_POPUP_PATH_OPEN "/selection/Open Placeholder/Open"
+
+#define FM_DIRECTORY_VIEW_POPUP_PATH_BACKGROUND "/background"
+#define FM_DIRECTORY_VIEW_POPUP_PATH_BACKGROUND_SCRIPTS_PLACEHOLDER "/background/Before Zoom Items/New Object Items/Scripts/Scripts Placeholder"
+#define FM_DIRECTORY_VIEW_POPUP_PATH_BACKGROUND_NEW_DOCUMENTS_PLACEHOLDER "/background/Before Zoom Items/New Object Items/New Documents/New Documents Placeholder"
+
+#define FM_DIRECTORY_VIEW_POPUP_PATH_LOCATION "/location"
+
+#define MAX_MENU_LEVELS 5
+#define TEMPLATE_LIMIT 30
+
+enum {
+ ADD_FILE,
+ BEGIN_FILE_CHANGES,
+ BEGIN_LOADING,
+ CLEAR,
+ END_FILE_CHANGES,
+ FLUSH_ADDED_FILES,
+ END_LOADING,
+ FILE_CHANGED,
+ LOAD_ERROR,
+ MOVE_COPY_ITEMS,
+ REMOVE_FILE,
+ TRASH,
+ DELETE,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_WINDOW_SLOT
+};
+
+
+static guint signals[LAST_SIGNAL];
+
+static GdkAtom copied_files_atom;
+
+static gboolean show_delete_command_auto_value;
+static gboolean confirm_trash_auto_value;
+
+static char *scripts_directory_uri;
+static int scripts_directory_uri_length;
+
+struct FMDirectoryViewDetails
+{
+ CajaWindowInfo *window;
+ CajaWindowSlotInfo *slot;
+ CajaDirectory *model;
+ CajaFile *directory_as_file;
+ CajaFile *location_popup_directory_as_file;
+ GdkEventButton *location_popup_event;
+ GtkActionGroup *dir_action_group;
+ guint dir_merge_id;
+
+ GList *scripts_directory_list;
+ GtkActionGroup *scripts_action_group;
+ guint scripts_merge_id;
+
+ GList *templates_directory_list;
+ GtkActionGroup *templates_action_group;
+ guint templates_merge_id;
+
+ GtkActionGroup *extensions_menu_action_group;
+ guint extensions_menu_merge_id;
+
+ guint display_selection_idle_id;
+ guint update_menus_timeout_id;
+ guint update_status_idle_id;
+ guint reveal_selection_idle_id;
+
+ guint display_pending_source_id;
+ guint changes_timeout_id;
+
+ guint update_interval;
+ guint64 last_queued;
+
+ guint files_added_handler_id;
+ guint files_changed_handler_id;
+ guint load_error_handler_id;
+ guint done_loading_handler_id;
+ guint file_changed_handler_id;
+
+ guint delayed_rename_file_id;
+
+ GList *new_added_files;
+ GList *new_changed_files;
+
+ GHashTable *non_ready_files;
+
+ GList *old_added_files;
+ GList *old_changed_files;
+
+ GList *pending_locations_selected;
+
+ /* whether we are in the active slot */
+ gboolean active;
+
+ /* loading indicates whether this view has begun loading a directory.
+ * This flag should need not be set inside subclasses. FMDirectoryView automatically
+ * sets 'loading' to TRUE before it begins loading a directory's contents and to FALSE
+ * after it finishes loading the directory and its view.
+ */
+ gboolean loading;
+ gboolean menu_states_untrustworthy;
+ gboolean scripts_invalid;
+ gboolean templates_invalid;
+ gboolean reported_load_error;
+
+ /* flag to indicate that no file updates should be dispatched to subclasses.
+ * This is a workaround for bug #87701 that prevents the list view from
+ * losing focus when the underlying GtkTreeView is updated.
+ */
+ gboolean updates_frozen;
+ guint updates_queued;
+ gboolean needs_reload;
+
+ gboolean sort_directories_first;
+
+ gboolean show_foreign_files;
+ gboolean show_hidden_files;
+ gboolean show_backup_files;
+ gboolean ignore_hidden_file_preferences;
+
+ gboolean batching_selection_level;
+ gboolean selection_changed_while_batched;
+
+ gboolean selection_was_removed;
+
+ gboolean metadata_for_directory_as_file_pending;
+ gboolean metadata_for_files_in_directory_pending;
+
+ gboolean selection_change_is_due_to_shell;
+ gboolean send_selection_change_to_shell;
+
+ GtkActionGroup *open_with_action_group;
+ guint open_with_merge_id;
+
+ GList *subdirectory_list;
+
+ gboolean allow_moves;
+
+ GdkPoint context_menu_position;
+};
+
+typedef struct {
+ CajaFile *file;
+ CajaDirectory *directory;
+} FileAndDirectory;
+
+/* forward declarations */
+
+static gboolean display_selection_info_idle_callback (gpointer data);
+static void fm_directory_view_class_init (FMDirectoryViewClass *klass);
+static void fm_directory_view_init (FMDirectoryView *view);
+static void fm_directory_view_duplicate_selection (FMDirectoryView *view,
+ GList *files,
+ GArray *item_locations);
+static void fm_directory_view_create_links_for_files (FMDirectoryView *view,
+ GList *files,
+ GArray *item_locations);
+static void trash_or_delete_files (GtkWindow *parent_window,
+ const GList *files,
+ gboolean delete_if_all_already_in_trash,
+ FMDirectoryView *view);
+static void load_directory (FMDirectoryView *view,
+ CajaDirectory *directory);
+static void fm_directory_view_merge_menus (FMDirectoryView *view);
+static void fm_directory_view_unmerge_menus (FMDirectoryView *view);
+static void fm_directory_view_init_show_hidden_files (FMDirectoryView *view);
+static void fm_directory_view_load_location (CajaView *caja_view,
+ const char *location);
+static void fm_directory_view_stop_loading (CajaView *caja_view);
+static void fm_directory_view_drop_proxy_received_uris (FMDirectoryView *view,
+ const GList *source_uri_list,
+ const char *target_uri,
+ GdkDragAction action);
+static void fm_directory_view_drop_proxy_received_netscape_url (FMDirectoryView *view,
+ const char *netscape_url,
+ const char *target_uri,
+ GdkDragAction action);
+static void clipboard_changed_callback (CajaClipboardMonitor *monitor,
+ FMDirectoryView *view);
+static void open_one_in_new_window (gpointer data,
+ gpointer callback_data);
+static void open_one_in_folder_window (gpointer data,
+ gpointer callback_data);
+static void schedule_update_menus (FMDirectoryView *view);
+static void schedule_update_menus_callback (gpointer callback_data);
+static void remove_update_menus_timeout_callback (FMDirectoryView *view);
+static void schedule_update_status (FMDirectoryView *view);
+static void remove_update_status_idle_callback (FMDirectoryView *view);
+static void reset_update_interval (FMDirectoryView *view);
+static void schedule_idle_display_of_pending_files (FMDirectoryView *view);
+static void unschedule_display_of_pending_files (FMDirectoryView *view);
+static void disconnect_model_handlers (FMDirectoryView *view);
+static void metadata_for_directory_as_file_ready_callback (CajaFile *file,
+ gpointer callback_data);
+static void metadata_for_files_in_directory_ready_callback (CajaDirectory *directory,
+ GList *files,
+ gpointer callback_data);
+static void fm_directory_view_trash_state_changed_callback (CajaTrashMonitor *trash,
+ gboolean state,
+ gpointer callback_data);
+static void fm_directory_view_select_file (FMDirectoryView *view,
+ CajaFile *file);
+
+static GdkDragAction ask_link_action (FMDirectoryView *view);
+static void update_templates_directory (FMDirectoryView *view);
+static void user_dirs_changed (FMDirectoryView *view);
+static void fm_directory_view_set_is_active (FMDirectoryView *view,
+ gboolean is_active);
+
+static gboolean file_list_all_are_folders (GList *file_list);
+
+static void action_open_scripts_folder_callback (GtkAction *action,
+ gpointer callback_data);
+static void action_cut_files_callback (GtkAction *action,
+ gpointer callback_data);
+static void action_copy_files_callback (GtkAction *action,
+ gpointer callback_data);
+static void action_paste_files_callback (GtkAction *action,
+ gpointer callback_data);
+static void action_copy_to_next_pane_callback (GtkAction *action,
+ gpointer callback_data);
+static void action_move_to_next_pane_callback (GtkAction *action,
+ gpointer callback_data);
+static void action_rename_callback (GtkAction *action,
+ gpointer callback_data);
+static void action_rename_select_all_callback (GtkAction *action,
+ gpointer callback_data);
+static void action_paste_files_into_callback (GtkAction *action,
+ gpointer callback_data);
+static void action_connect_to_server_link_callback (GtkAction *action,
+ gpointer data);
+static void action_mount_volume_callback (GtkAction *action,
+ gpointer data);
+static void action_unmount_volume_callback (GtkAction *action,
+ gpointer data);
+static void action_format_volume_callback (GtkAction *action,
+ gpointer data);
+static void action_start_volume_callback (GtkAction *action,
+ gpointer data);
+static void action_stop_volume_callback (GtkAction *action,
+ gpointer data);
+static void action_detect_media_callback (GtkAction *action,
+ gpointer data);
+
+/* location popup-related actions */
+
+static void action_location_open_alternate_callback (GtkAction *action,
+ gpointer callback_data);
+static void action_location_open_folder_window_callback (GtkAction *action,
+ gpointer callback_data);
+
+static void action_location_cut_callback (GtkAction *action,
+ gpointer callback_data);
+static void action_location_copy_callback (GtkAction *action,
+ gpointer callback_data);
+static void action_location_trash_callback (GtkAction *action,
+ gpointer callback_data);
+static void action_location_delete_callback (GtkAction *action,
+ gpointer callback_data);
+static void action_location_properties_callback (GtkAction *action,
+ gpointer callback_data);
+
+static void unschedule_pop_up_location_context_menu (FMDirectoryView *view);
+
+static inline void fm_directory_view_widget_to_file_operation_position (FMDirectoryView *view,
+ GdkPoint *position);
+static void fm_directory_view_widget_to_file_operation_position_xy (FMDirectoryView *view,
+ int *x, int *y);
+
+EEL_CLASS_BOILERPLATE (FMDirectoryView, fm_directory_view, GTK_TYPE_SCROLLED_WINDOW)
+
+EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, add_file)
+EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, bump_zoom_level)
+EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, can_zoom_in)
+EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, can_zoom_out)
+EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, clear)
+EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, file_changed)
+EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, get_background_widget)
+EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, get_selection)
+EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, get_selection_for_file_transfer)
+EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, get_item_count)
+EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, is_empty)
+EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, reset_to_defaults)
+EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, restore_default_zoom_level)
+EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, select_all)
+EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, set_selection)
+EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, zoom_to_level)
+EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, get_zoom_level)
+EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, invert_selection)
+
+typedef struct {
+ GAppInfo *application;
+ GList *files;
+ FMDirectoryView *directory_view;
+} ApplicationLaunchParameters;
+
+typedef struct {
+ CajaFile *file;
+ FMDirectoryView *directory_view;
+} ScriptLaunchParameters;
+
+typedef struct {
+ CajaFile *file;
+ FMDirectoryView *directory_view;
+} CreateTemplateParameters;
+
+static ApplicationLaunchParameters *
+application_launch_parameters_new (GAppInfo *application,
+ GList *files,
+ FMDirectoryView *directory_view)
+{
+ ApplicationLaunchParameters *result;
+
+ result = g_new0 (ApplicationLaunchParameters, 1);
+ result->application = g_object_ref (application);
+ result->files = caja_file_list_copy (files);
+
+ if (directory_view != NULL) {
+ g_object_ref (directory_view);
+ result->directory_view = directory_view;
+ }
+
+ return result;
+}
+
+static void
+application_launch_parameters_free (ApplicationLaunchParameters *parameters)
+{
+ g_object_unref (parameters->application);
+ caja_file_list_free (parameters->files);
+
+ if (parameters->directory_view != NULL) {
+ g_object_unref (parameters->directory_view);
+ }
+
+ g_free (parameters);
+}
+
+static GList *
+file_and_directory_list_to_files (GList *fad_list)
+{
+ GList *res, *l;
+ FileAndDirectory *fad;
+
+ res = NULL;
+ for (l = fad_list; l != NULL; l = l->next) {
+ fad = l->data;
+ res = g_list_prepend (res, caja_file_ref (fad->file));
+ }
+ return g_list_reverse (res);
+}
+
+
+static GList *
+file_and_directory_list_from_files (CajaDirectory *directory, GList *files)
+{
+ GList *res, *l;
+ FileAndDirectory *fad;
+
+ res = NULL;
+ for (l = files; l != NULL; l = l->next) {
+ fad = g_new0 (FileAndDirectory, 1);
+ fad->directory = caja_directory_ref (directory);
+ fad->file = caja_file_ref (l->data);
+ res = g_list_prepend (res, fad);
+ }
+ return g_list_reverse (res);
+}
+
+static void
+file_and_directory_free (FileAndDirectory *fad)
+{
+ caja_directory_unref (fad->directory);
+ caja_file_unref (fad->file);
+ g_free (fad);
+}
+
+
+static void
+file_and_directory_list_free (GList *list)
+{
+ GList *l;
+
+ for (l = list; l != NULL; l = l->next) {
+ file_and_directory_free (l->data);
+ }
+
+ g_list_free (list);
+}
+
+static gboolean
+file_and_directory_equal (gconstpointer v1,
+ gconstpointer v2)
+{
+ const FileAndDirectory *fad1, *fad2;
+ fad1 = v1;
+ fad2 = v2;
+
+ return (fad1->file == fad2->file &&
+ fad1->directory == fad2->directory);
+}
+
+static guint
+file_and_directory_hash (gconstpointer v)
+{
+ const FileAndDirectory *fad;
+
+ fad = v;
+ return GPOINTER_TO_UINT (fad->file) ^ GPOINTER_TO_UINT (fad->directory);
+}
+
+
+
+
+static ScriptLaunchParameters *
+script_launch_parameters_new (CajaFile *file,
+ FMDirectoryView *directory_view)
+{
+ ScriptLaunchParameters *result;
+
+ result = g_new0 (ScriptLaunchParameters, 1);
+ g_object_ref (directory_view);
+ result->directory_view = directory_view;
+ caja_file_ref (file);
+ result->file = file;
+
+ return result;
+}
+
+static void
+script_launch_parameters_free (ScriptLaunchParameters *parameters)
+{
+ g_object_unref (parameters->directory_view);
+ caja_file_unref (parameters->file);
+ g_free (parameters);
+}
+
+static CreateTemplateParameters *
+create_template_parameters_new (CajaFile *file,
+ FMDirectoryView *directory_view)
+{
+ CreateTemplateParameters *result;
+
+ result = g_new0 (CreateTemplateParameters, 1);
+ g_object_ref (directory_view);
+ result->directory_view = directory_view;
+ caja_file_ref (file);
+ result->file = file;
+
+ return result;
+}
+
+static void
+create_templates_parameters_free (CreateTemplateParameters *parameters)
+{
+ g_object_unref (parameters->directory_view);
+ caja_file_unref (parameters->file);
+ g_free (parameters);
+}
+
+CajaWindowInfo *
+fm_directory_view_get_caja_window (FMDirectoryView *view)
+{
+ g_assert (view->details->window != NULL);
+
+ return view->details->window;
+}
+
+CajaWindowSlotInfo *
+fm_directory_view_get_caja_window_slot (FMDirectoryView *view)
+{
+ g_assert (view->details->slot != NULL);
+
+ return view->details->slot;
+}
+
+/* Returns the GtkWindow that this directory view occupies, or NULL
+ * if at the moment this directory view is not in a GtkWindow or the
+ * GtkWindow cannot be determined. Primarily used for parenting dialogs.
+ */
+GtkWindow *
+fm_directory_view_get_containing_window (FMDirectoryView *view)
+{
+ GtkWidget *window;
+
+ g_assert (FM_IS_DIRECTORY_VIEW (view));
+
+ window = gtk_widget_get_ancestor (GTK_WIDGET (view), GTK_TYPE_WINDOW);
+ if (window == NULL) {
+ return NULL;
+ }
+
+ return GTK_WINDOW (window);
+}
+
+static gboolean
+fm_directory_view_confirm_multiple (GtkWindow *parent_window,
+ int count,
+ gboolean tabs)
+{
+ GtkDialog *dialog;
+ char *prompt;
+ char *detail;
+ int response;
+
+ if (count <= SILENT_WINDOW_OPEN_LIMIT) {
+ return TRUE;
+ }
+
+ prompt = _("Are you sure you want to open all files?");
+ if (tabs) {
+ detail = g_strdup_printf (ngettext("This will open %'d separate tab.",
+ "This will open %'d separate tabs.", count), count);
+ } else {
+ detail = g_strdup_printf (ngettext("This will open %'d separate window.",
+ "This will open %'d separate windows.", count), count);
+ }
+ dialog = eel_show_yes_no_dialog (prompt, detail,
+ GTK_STOCK_OK, GTK_STOCK_CANCEL,
+ parent_window);
+ g_free (detail);
+
+ response = gtk_dialog_run (dialog);
+ gtk_object_destroy (GTK_OBJECT (dialog));
+
+ return response == GTK_RESPONSE_YES;
+}
+
+static gboolean
+selection_contains_one_item_in_menu_callback (FMDirectoryView *view, GList *selection)
+{
+ if (eel_g_list_exactly_one_item (selection)) {
+ return TRUE;
+ }
+
+ /* If we've requested a menu update that hasn't yet occurred, then
+ * the mismatch here doesn't surprise us, and we won't complain.
+ * Otherwise, we will complain.
+ */
+ if (!view->details->menu_states_untrustworthy) {
+ g_warning ("Expected one selected item, found %'d. No action will be performed.",
+ g_list_length (selection));
+ }
+
+ return FALSE;
+}
+
+static gboolean
+selection_not_empty_in_menu_callback (FMDirectoryView *view, GList *selection)
+{
+ if (selection != NULL) {
+ return TRUE;
+ }
+
+ /* If we've requested a menu update that hasn't yet occurred, then
+ * the mismatch here doesn't surprise us, and we won't complain.
+ * Otherwise, we will complain.
+ */
+ if (!view->details->menu_states_untrustworthy) {
+ g_warning ("Empty selection found when selection was expected. No action will be performed.");
+ }
+
+ return FALSE;
+}
+
+static char *
+get_view_directory (FMDirectoryView *view)
+{
+ char *uri, *path;
+ GFile *f;
+
+ uri = caja_directory_get_uri (view->details->model);
+ if (eel_uri_is_desktop (uri)) {
+ g_free (uri);
+ uri = caja_get_desktop_directory_uri ();
+
+ }
+ f = g_file_new_for_uri (uri);
+ path = g_file_get_path (f);
+ g_object_unref (f);
+ g_free (uri);
+
+ return path;
+}
+
+void
+fm_directory_view_activate_files (FMDirectoryView *view,
+ GList *files,
+ CajaWindowOpenMode mode,
+ CajaWindowOpenFlags flags,
+ gboolean confirm_multiple)
+{
+ char *path;
+
+ path = get_view_directory (view);
+ caja_mime_activate_files (fm_directory_view_get_containing_window (view),
+ view->details->slot,
+ files,
+ path,
+ mode,
+ flags,
+ confirm_multiple);
+
+ g_free (path);
+}
+
+void
+fm_directory_view_activate_file (FMDirectoryView *view,
+ CajaFile *file,
+ CajaWindowOpenMode mode,
+ CajaWindowOpenFlags flags)
+{
+ char *path;
+
+ path = get_view_directory (view);
+ caja_mime_activate_file (fm_directory_view_get_containing_window (view),
+ view->details->slot,
+ file,
+ path,
+ mode,
+ flags);
+
+ g_free (path);
+}
+
+static void
+action_open_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ GList *selection;
+ FMDirectoryView *view;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+
+ selection = fm_directory_view_get_selection (view);
+ fm_directory_view_activate_files (view,
+ selection,
+ CAJA_WINDOW_OPEN_ACCORDING_TO_MODE,
+ 0,
+ TRUE);
+ caja_file_list_free (selection);
+}
+
+static void
+action_open_close_parent_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ GList *selection;
+ FMDirectoryView *view;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+
+ selection = fm_directory_view_get_selection (view);
+ fm_directory_view_activate_files (view,
+ selection,
+ CAJA_WINDOW_OPEN_ACCORDING_TO_MODE,
+ CAJA_WINDOW_OPEN_FLAG_CLOSE_BEHIND,
+ TRUE);
+ caja_file_list_free (selection);
+}
+
+
+static void
+action_open_alternate_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ FMDirectoryView *view;
+ GList *selection;
+ GtkWindow *window;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+ selection = fm_directory_view_get_selection (view);
+
+ window = fm_directory_view_get_containing_window (view);
+
+ if (fm_directory_view_confirm_multiple (window, g_list_length (selection), FALSE)) {
+ g_list_foreach (selection, open_one_in_new_window, view);
+ }
+
+ caja_file_list_free (selection);
+}
+
+static void
+action_open_new_tab_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ FMDirectoryView *view;
+ GList *selection;
+ GtkWindow *window;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+ selection = fm_directory_view_get_selection (view);
+
+ window = fm_directory_view_get_containing_window (view);
+
+ if (fm_directory_view_confirm_multiple (window, g_list_length (selection), TRUE)) {
+ fm_directory_view_activate_files (view,
+ selection,
+ CAJA_WINDOW_OPEN_ACCORDING_TO_MODE,
+ CAJA_WINDOW_OPEN_FLAG_NEW_TAB,
+ FALSE);
+ }
+
+ caja_file_list_free (selection);
+}
+
+static void
+action_open_folder_window_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ FMDirectoryView *view;
+ GList *selection;
+ GtkWindow *window;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+ selection = fm_directory_view_get_selection (view);
+
+ window = fm_directory_view_get_containing_window (view);
+
+ if (fm_directory_view_confirm_multiple (window, g_list_length (selection), FALSE)) {
+ g_list_foreach (selection, open_one_in_folder_window, view);
+ }
+
+ caja_file_list_free (selection);
+}
+
+static void
+open_location (FMDirectoryView *directory_view,
+ const char *new_uri,
+ CajaWindowOpenMode mode,
+ CajaWindowOpenFlags flags)
+{
+ GtkWindow *window;
+ GFile *location;
+
+ g_assert (FM_IS_DIRECTORY_VIEW (directory_view));
+ g_assert (new_uri != NULL);
+
+ window = fm_directory_view_get_containing_window (directory_view);
+ caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
+ "directory view open_location window=%p: %s", window, new_uri);
+ location = g_file_new_for_uri (new_uri);
+ caja_window_slot_info_open_location (directory_view->details->slot,
+ location, mode, flags, NULL);
+ g_object_unref (location);
+}
+
+static void
+application_selected_cb (CajaOpenWithDialog *dialog,
+ GAppInfo *app,
+ gpointer user_data)
+{
+ GtkWindow *parent_window;
+ CajaFile *file;
+ GList files;
+
+ parent_window = GTK_WINDOW (user_data);
+
+ file = g_object_get_data (G_OBJECT (dialog), "directory-view:file");
+
+ files.next = NULL;
+ files.prev = NULL;
+ files.data = file;
+ caja_launch_application (app, &files, parent_window);
+}
+
+static void
+choose_program (FMDirectoryView *view,
+ CajaFile *file)
+{
+ GtkWidget *dialog;
+ char *uri;
+ char *mime_type;
+
+ g_assert (FM_IS_DIRECTORY_VIEW (view));
+ g_assert (CAJA_IS_FILE (file));
+
+ caja_file_ref (file);
+ uri = caja_file_get_uri (file);
+ mime_type = caja_file_get_mime_type (file);
+
+ dialog = caja_open_with_dialog_new (uri, mime_type, NULL);
+ g_object_set_data_full (G_OBJECT (dialog),
+ "directory-view:file",
+ g_object_ref (file),
+ (GDestroyNotify)g_object_unref);
+
+ gtk_window_set_screen (GTK_WINDOW (dialog),
+ gtk_widget_get_screen (GTK_WIDGET (view)));
+ gtk_widget_show (dialog);
+
+ g_signal_connect_object (dialog,
+ "application_selected",
+ G_CALLBACK (application_selected_cb),
+ fm_directory_view_get_containing_window (view),
+ 0);
+
+ g_free (uri);
+ g_free (mime_type);
+ caja_file_unref (file);
+}
+
+static void
+open_with_other_program (FMDirectoryView *view)
+{
+ GList *selection;
+
+ g_assert (FM_IS_DIRECTORY_VIEW (view));
+
+ selection = fm_directory_view_get_selection (view);
+
+ if (selection_contains_one_item_in_menu_callback (view, selection)) {
+ choose_program (view, CAJA_FILE (selection->data));
+ }
+
+ caja_file_list_free (selection);
+}
+
+static void
+action_other_application_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ g_assert (FM_IS_DIRECTORY_VIEW (callback_data));
+
+ open_with_other_program (FM_DIRECTORY_VIEW (callback_data));
+}
+
+static void
+trash_or_delete_selected_files (FMDirectoryView *view)
+{
+ GList *selection;
+
+ /* This might be rapidly called multiple times for the same selection
+ * when using keybindings. So we remember if the current selection
+ * was already removed (but the view doesn't know about it yet).
+ */
+ if (!view->details->selection_was_removed) {
+ selection = fm_directory_view_get_selection_for_file_transfer (view);
+ trash_or_delete_files (fm_directory_view_get_containing_window (view),
+ selection, TRUE,
+ view);
+ caja_file_list_free (selection);
+ view->details->selection_was_removed = TRUE;
+ }
+}
+
+static gboolean
+real_trash (FMDirectoryView *view)
+{
+ GtkAction *action;
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_TRASH);
+ if (gtk_action_get_sensitive (action) &&
+ gtk_action_get_visible (action)) {
+ trash_or_delete_selected_files (view);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void
+action_trash_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ trash_or_delete_selected_files (FM_DIRECTORY_VIEW (callback_data));
+}
+
+static void
+delete_selected_files (FMDirectoryView *view)
+{
+ GList *selection;
+ GList *node;
+ GList *locations;
+
+ selection = fm_directory_view_get_selection_for_file_transfer (view);
+ if (selection == NULL) {
+ return;
+ }
+
+ locations = NULL;
+ for (node = selection; node != NULL; node = node->next) {
+ locations = g_list_prepend (locations,
+ caja_file_get_location ((CajaFile *) node->data));
+ }
+ locations = g_list_reverse (locations);
+
+ caja_file_operations_delete (locations, fm_directory_view_get_containing_window (view), NULL, NULL);
+
+ eel_g_object_list_free (locations);
+ caja_file_list_free (selection);
+}
+
+static void
+action_delete_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ delete_selected_files (FM_DIRECTORY_VIEW (callback_data));
+}
+
+static void
+action_restore_from_trash_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ FMDirectoryView *view;
+ GList *selection;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+
+ selection = fm_directory_view_get_selection_for_file_transfer (view);
+ caja_restore_files_from_trash (selection,
+ fm_directory_view_get_containing_window (view));
+
+ caja_file_list_free (selection);
+
+}
+
+static gboolean
+real_delete (FMDirectoryView *view)
+{
+ GtkAction *action;
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_DELETE);
+ if (gtk_action_get_sensitive (action) &&
+ gtk_action_get_visible (action)) {
+ delete_selected_files (view);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void
+action_duplicate_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ FMDirectoryView *view;
+ GList *selection;
+ GArray *selected_item_locations;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+ selection = fm_directory_view_get_selection_for_file_transfer (view);
+ if (selection_not_empty_in_menu_callback (view, selection)) {
+ /* FIXME bugzilla.gnome.org 45061:
+ * should change things here so that we use a get_icon_locations (view, selection).
+ * Not a problem in this case but in other places the selection may change by
+ * the time we go and retrieve the icon positions, relying on the selection
+ * staying intact to ensure the right sequence and count of positions is fragile.
+ */
+ selected_item_locations = fm_directory_view_get_selected_icon_locations (view);
+ fm_directory_view_duplicate_selection (view, selection, selected_item_locations);
+ g_array_free (selected_item_locations, TRUE);
+ }
+
+ caja_file_list_free (selection);
+}
+
+static void
+action_create_link_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ FMDirectoryView *view;
+ GList *selection;
+ GArray *selected_item_locations;
+
+ g_assert (FM_IS_DIRECTORY_VIEW (callback_data));
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+ selection = fm_directory_view_get_selection (view);
+ if (selection_not_empty_in_menu_callback (view, selection)) {
+ selected_item_locations = fm_directory_view_get_selected_icon_locations (view);
+ fm_directory_view_create_links_for_files (view, selection, selected_item_locations);
+ g_array_free (selected_item_locations, TRUE);
+ }
+
+ caja_file_list_free (selection);
+}
+
+static void
+action_select_all_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ g_assert (FM_IS_DIRECTORY_VIEW (callback_data));
+
+ fm_directory_view_select_all (callback_data);
+}
+
+static void
+action_invert_selection_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ g_assert (FM_IS_DIRECTORY_VIEW (callback_data));
+
+ fm_directory_view_invert_selection (callback_data);
+}
+
+
+static void
+pattern_select_response_cb (GtkWidget *dialog, int response, gpointer user_data)
+{
+ FMDirectoryView *view;
+ CajaDirectory *directory;
+ GtkWidget *entry;
+ GList *selection;
+ GError *error;
+
+ view = FM_DIRECTORY_VIEW (user_data);
+
+ switch (response) {
+ case GTK_RESPONSE_OK :
+ entry = g_object_get_data (G_OBJECT (dialog), "entry");
+ directory = fm_directory_view_get_model (view);
+ selection = caja_directory_match_pattern (directory,
+ gtk_entry_get_text (GTK_ENTRY (entry)));
+
+ if (selection) {
+ fm_directory_view_set_selection (view, selection);
+ caja_file_list_free (selection);
+
+ fm_directory_view_reveal_selection(view);
+ }
+ /* fall through */
+ case GTK_RESPONSE_NONE :
+ case GTK_RESPONSE_DELETE_EVENT :
+ case GTK_RESPONSE_CANCEL :
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+ break;
+ case GTK_RESPONSE_HELP :
+ error = NULL;
+ gtk_show_uri (gtk_window_get_screen (GTK_WINDOW (dialog)),
+ "ghelp:user-guide#caja-select-pattern",
+ gtk_get_current_event_time (), &error);
+ if (error) {
+ eel_show_error_dialog (_("There was an error displaying help."), error->message,
+ GTK_WINDOW (dialog));
+ g_error_free (error);
+ }
+ break;
+ default :
+ g_assert_not_reached ();
+ }
+}
+
+static void
+select_pattern (FMDirectoryView *view)
+{
+ GtkWidget *dialog;
+ GtkWidget *label;
+ GtkWidget *example;
+ GtkWidget *table;
+ GtkWidget *entry;
+ GList *ret;
+ char *example_pattern;
+
+ ret = NULL;
+ dialog = gtk_dialog_new_with_buttons (_("Select Items Matching"),
+ fm_directory_view_get_containing_window (view),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_STOCK_HELP,
+ GTK_RESPONSE_HELP,
+ GTK_STOCK_CANCEL,
+ GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OK,
+ GTK_RESPONSE_OK,
+ NULL);
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog),
+ GTK_RESPONSE_OK);
+ gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE);
+ gtk_container_set_border_width (GTK_CONTAINER (dialog), 5);
+ gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), 2);
+
+ label = gtk_label_new_with_mnemonic (_("_Pattern:"));
+ example = gtk_label_new (NULL);
+ example_pattern = g_strdup_printf ("<b>%s</b><i>%s</i>",
+ _("Examples: "),
+ "*.png, file\?\?.txt, pict*.\?\?\?");
+ gtk_label_set_markup (GTK_LABEL (example), example_pattern);
+ g_free (example_pattern);
+ gtk_misc_set_alignment (GTK_MISC (example), 0.0, 0.5);
+ entry = gtk_entry_new ();
+ gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE);
+
+ table = gtk_table_new (2, 2, FALSE);
+
+ gtk_table_attach (GTK_TABLE (table), label,
+ 0, 1,
+ 0, 1,
+ GTK_FILL, GTK_FILL,
+ 5, 5);
+
+ gtk_table_attach (GTK_TABLE (table), entry,
+ 1, 2,
+ 0, 1,
+ GTK_EXPAND | GTK_FILL, GTK_FILL,
+ 5, 5);
+
+ gtk_table_attach (GTK_TABLE (table), example,
+ 1, 2,
+ 1, 2,
+ GTK_FILL, GTK_FILL,
+ 5, 0);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry);
+ gtk_widget_show_all (table);
+ gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), table);
+ g_object_set_data (G_OBJECT (dialog), "entry", entry);
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (pattern_select_response_cb),
+ view);
+ gtk_widget_show_all (dialog);
+}
+
+static void
+action_select_pattern_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ g_assert (FM_IS_DIRECTORY_VIEW (callback_data));
+
+ select_pattern(callback_data);
+}
+
+static void
+action_reset_to_defaults_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ g_assert (FM_IS_DIRECTORY_VIEW (callback_data));
+
+ fm_directory_view_reset_to_defaults (callback_data);
+}
+
+
+static void
+hidden_files_mode_changed (CajaWindow *window,
+ gpointer callback_data)
+{
+ FMDirectoryView *directory_view;
+
+ directory_view = FM_DIRECTORY_VIEW (callback_data);
+
+ fm_directory_view_init_show_hidden_files (directory_view);
+}
+
+static void
+action_save_search_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ CajaSearchDirectory *search;
+ FMDirectoryView *directory_view;
+
+ directory_view = FM_DIRECTORY_VIEW (callback_data);
+
+ if (directory_view->details->model &&
+ CAJA_IS_SEARCH_DIRECTORY (directory_view->details->model)) {
+ search = CAJA_SEARCH_DIRECTORY (directory_view->details->model);
+ caja_search_directory_save_search (search);
+
+ /* Save search is disabled */
+ schedule_update_menus (directory_view);
+ }
+}
+
+static void
+query_name_entry_changed_cb (GtkWidget *entry, GtkWidget *button)
+{
+ const char *text;
+ gboolean sensitive;
+
+ text = gtk_entry_get_text (GTK_ENTRY (entry));
+
+ sensitive = (text != NULL) && (*text != 0);
+
+ gtk_widget_set_sensitive (button, sensitive);
+}
+
+
+static void
+action_save_search_as_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ FMDirectoryView *directory_view;
+ CajaSearchDirectory *search;
+ CajaQuery *query;
+ GtkWidget *dialog, *table, *label, *entry, *chooser, *save_button;
+ const char *entry_text;
+ char *filename, *filename_utf8, *dirname, *path, *uri;
+ GFile *location;
+
+ directory_view = FM_DIRECTORY_VIEW (callback_data);
+
+ if (directory_view->details->model &&
+ CAJA_IS_SEARCH_DIRECTORY (directory_view->details->model)) {
+ search = CAJA_SEARCH_DIRECTORY (directory_view->details->model);
+
+ query = caja_search_directory_get_query (search);
+
+ dialog = gtk_dialog_new_with_buttons (_("Save Search as"),
+ fm_directory_view_get_containing_window (directory_view),
+ GTK_DIALOG_NO_SEPARATOR,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ NULL);
+ save_button = gtk_dialog_add_button (GTK_DIALOG (dialog),
+ GTK_STOCK_SAVE, GTK_RESPONSE_OK);
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog),
+ GTK_RESPONSE_OK);
+ gtk_container_set_border_width (GTK_CONTAINER (dialog), 5);
+ gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), 2);
+ gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+
+ table = gtk_table_new (2, 2, FALSE);
+ gtk_container_set_border_width (GTK_CONTAINER (table), 5);
+ gtk_table_set_row_spacings (GTK_TABLE (table), 6);
+ gtk_table_set_col_spacings (GTK_TABLE (table), 12);
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), table, TRUE, TRUE, 0);
+ gtk_widget_show (table);
+
+ label = gtk_label_new_with_mnemonic (_("Search _name:"));
+ gtk_misc_set_alignment (GTK_MISC(label), 0.0, 0.5);
+ gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
+ gtk_widget_show (label);
+ entry = gtk_entry_new ();
+ gtk_table_attach (GTK_TABLE (table), entry, 1, 2, 0, 1, GTK_FILL | GTK_EXPAND, 0, 0, 0);
+ gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry);
+
+ gtk_widget_set_sensitive (save_button, FALSE);
+ g_signal_connect (entry, "changed",
+ G_CALLBACK (query_name_entry_changed_cb), save_button);
+
+ gtk_widget_show (entry);
+ label = gtk_label_new_with_mnemonic (_("_Folder:"));
+ gtk_misc_set_alignment (GTK_MISC(label), 0.0, 0.5);
+ gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2, GTK_FILL, 0, 0, 0);
+ gtk_widget_show (label);
+
+ chooser = gtk_file_chooser_button_new (_("Select Folder to Save Search In"),
+ GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER);
+ gtk_table_attach (GTK_TABLE (table), chooser, 1, 2, 1, 2, GTK_FILL | GTK_EXPAND, 0, 0, 0);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), chooser);
+ gtk_widget_show (chooser);
+
+ gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (chooser), TRUE);
+
+ gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (chooser),
+ g_get_home_dir ());
+
+ if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) {
+ entry_text = gtk_entry_get_text (GTK_ENTRY (entry));
+ if (g_str_has_suffix (entry_text, CAJA_SAVED_SEARCH_EXTENSION)) {
+ filename_utf8 = g_strdup (entry_text);
+ } else {
+ filename_utf8 = g_strconcat (entry_text, CAJA_SAVED_SEARCH_EXTENSION, NULL);
+ }
+
+ filename = g_filename_from_utf8 (filename_utf8, -1, NULL, NULL, NULL);
+ g_free (filename_utf8);
+
+ dirname = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (chooser));
+
+ path = g_build_filename (dirname, filename, NULL);
+ g_free (filename);
+ g_free (dirname);
+
+ uri = g_filename_to_uri (path, NULL, NULL);
+ g_free (path);
+
+ caja_search_directory_save_to_file (search, uri);
+ location = g_file_new_for_uri (uri);
+ caja_file_changes_queue_file_added (location);
+ g_object_unref (location);
+ caja_file_changes_consume_changes (TRUE);
+ g_free (uri);
+ }
+
+ gtk_widget_destroy (dialog);
+ }
+}
+
+
+static void
+action_empty_trash_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ g_assert (FM_IS_DIRECTORY_VIEW (callback_data));
+
+ caja_file_operations_empty_trash (GTK_WIDGET (callback_data));
+}
+
+static void
+action_new_folder_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ g_assert (FM_IS_DIRECTORY_VIEW (callback_data));
+
+ fm_directory_view_new_folder (FM_DIRECTORY_VIEW (callback_data));
+}
+
+static void
+action_new_empty_file_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ g_assert (FM_IS_DIRECTORY_VIEW (callback_data));
+
+ fm_directory_view_new_file (FM_DIRECTORY_VIEW (callback_data), NULL, NULL);
+}
+
+static void
+action_new_launcher_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ char *parent_uri;
+ FMDirectoryView *view;
+ GtkWindow *window;
+
+ g_assert (FM_IS_DIRECTORY_VIEW (callback_data));
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+
+ parent_uri = fm_directory_view_get_backing_uri (view);
+
+ window = fm_directory_view_get_containing_window (view);
+ caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
+ "directory view create new launcher in window=%p: %s", window, parent_uri);
+ caja_launch_application_from_command (gtk_widget_get_screen (GTK_WIDGET (view)),
+ "mate-desktop-item-edit",
+ "mate-desktop-item-edit",
+ FALSE,
+ "--create-new", parent_uri, NULL);
+
+ g_free (parent_uri);
+}
+
+static void
+action_properties_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ FMDirectoryView *view;
+ GList *selection;
+ GList *files;
+
+ g_assert (FM_IS_DIRECTORY_VIEW (callback_data));
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+ selection = fm_directory_view_get_selection (view);
+ if (g_list_length (selection) == 0) {
+ if (view->details->directory_as_file != NULL) {
+ files = g_list_append (NULL, caja_file_ref (view->details->directory_as_file));
+
+ fm_properties_window_present (files, GTK_WIDGET (view));
+
+ caja_file_list_free (files);
+ }
+ } else {
+ fm_properties_window_present (selection, GTK_WIDGET (view));
+ }
+ caja_file_list_free (selection);
+}
+
+static void
+action_location_properties_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ FMDirectoryView *view;
+ GList *files;
+
+ g_assert (FM_IS_DIRECTORY_VIEW (callback_data));
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+ g_assert (CAJA_IS_FILE (view->details->location_popup_directory_as_file));
+
+ files = g_list_append (NULL, caja_file_ref (view->details->location_popup_directory_as_file));
+
+ fm_properties_window_present (files, GTK_WIDGET (view));
+
+ caja_file_list_free (files);
+}
+
+static gboolean
+all_files_in_trash (GList *files)
+{
+ GList *node;
+
+ /* Result is ambiguous if called on NULL, so disallow. */
+ g_return_val_if_fail (files != NULL, FALSE);
+
+ for (node = files; node != NULL; node = node->next) {
+ if (!caja_file_is_in_trash (CAJA_FILE (node->data))) {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+all_selected_items_in_trash (FMDirectoryView *view)
+{
+ GList *selection;
+ gboolean result;
+
+ /* If the contents share a parent directory, we need only
+ * check that parent directory. Otherwise we have to inspect
+ * each selected item.
+ */
+ selection = fm_directory_view_get_selection (view);
+ result = (selection == NULL) ? FALSE : all_files_in_trash (selection);
+ caja_file_list_free (selection);
+
+ return result;
+}
+
+static gboolean
+we_are_in_vfolder_desktop_dir (FMDirectoryView *view)
+{
+ CajaFile *file;
+ char *mime_type;
+
+ g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE);
+
+ if (view->details->model == NULL) {
+ return FALSE;
+ }
+
+ file = caja_directory_get_corresponding_file (view->details->model);
+ mime_type = caja_file_get_mime_type (file);
+ caja_file_unref (file);
+
+ if (mime_type != NULL
+ && strcmp (mime_type, "x-directory/vfolder-desktop") == 0) {
+ g_free (mime_type);
+ return TRUE;
+ } else {
+ g_free (mime_type);
+ return FALSE;
+ }
+}
+
+/* Preferences changed callbacks */
+static void
+text_attribute_names_changed_callback (gpointer callback_data)
+{
+ FMDirectoryView *view;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+
+ EEL_CALL_METHOD
+ (FM_DIRECTORY_VIEW_CLASS, view,
+ text_attribute_names_changed, (view));
+}
+
+static void
+image_display_policy_changed_callback (gpointer callback_data)
+{
+ FMDirectoryView *view;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+
+ EEL_CALL_METHOD
+ (FM_DIRECTORY_VIEW_CLASS, view,
+ image_display_policy_changed, (view));
+}
+
+static void
+click_policy_changed_callback (gpointer callback_data)
+{
+ FMDirectoryView *view;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+
+ EEL_CALL_METHOD
+ (FM_DIRECTORY_VIEW_CLASS, view,
+ click_policy_changed, (view));
+}
+
+gboolean
+fm_directory_view_should_sort_directories_first (FMDirectoryView *view)
+{
+ return view->details->sort_directories_first;
+}
+
+static void
+sort_directories_first_changed_callback (gpointer callback_data)
+{
+ FMDirectoryView *view;
+ gboolean preference_value;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+
+ preference_value =
+ eel_preferences_get_boolean (CAJA_PREFERENCES_SORT_DIRECTORIES_FIRST);
+
+ if (preference_value != view->details->sort_directories_first) {
+ view->details->sort_directories_first = preference_value;
+ EEL_CALL_METHOD
+ (FM_DIRECTORY_VIEW_CLASS, view,
+ sort_directories_first_changed, (view));
+ }
+}
+
+static void
+lockdown_disable_command_line_changed_callback (gpointer callback_data)
+{
+ FMDirectoryView *view;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+ schedule_update_menus (view);
+}
+
+static void set_up_scripts_directory_global(void)
+{
+ if (scripts_directory_uri != NULL)
+ {
+ return;
+ }
+
+ char* scripts_directory_path;
+ const char* override = g_getenv ("MATE22_USER_DIR"); //TODO: quitar?
+
+ if (override)
+ {
+ scripts_directory_path = g_build_filename(override, "caja", "scripts", NULL);
+ }
+ else
+ {
+ scripts_directory_path = g_build_filename(g_get_home_dir(), ".config", "caja", "scripts", NULL);
+ }
+
+ if (g_mkdir_with_parents(scripts_directory_path, 0755) == 0)
+ {
+ scripts_directory_uri = g_filename_to_uri(scripts_directory_path, NULL, NULL);
+ scripts_directory_uri_length = strlen(scripts_directory_uri);
+
+ /* Emulación de GNOME Nautilus scripts
+ */
+ char* nautilus_scripts_path = g_build_filename(g_get_home_dir(), ".gnome2", "nautilus-scripts", NULL);
+
+ if (g_file_test(nautilus_scripts_path, G_FILE_TEST_IS_DIR) == TRUE)
+ {
+ char* nautilus_syslink = g_build_filename(g_get_home_dir(), ".config", "caja", "scripts", "nautilus", NULL);
+ // G_FILE_TEST_IS_REGULAR
+ /* En caso de que exista el enlace, o algún otro tipo de archivo con
+ * el mismo nombre, ignoramos. Incluso si es una carpeta. */
+ if (g_file_test(nautilus_syslink, G_FILE_TEST_IS_SYMLINK) == FALSE &&
+ g_file_test(nautilus_syslink, G_FILE_TEST_EXISTS) == FALSE &&
+ g_file_test(nautilus_syslink, G_FILE_TEST_IS_DIR) == FALSE)
+ {
+ /* Nos fijamos si es necesario crear un enlace */
+ GDir* dir = g_dir_open(nautilus_scripts_path, 0, NULL);
+
+ if (dir)
+ {
+ /* Con tener más de un elemento en la carpeta, podemos hacer
+ * el enlace */
+ int count = 0;
+
+ while (g_dir_read_name(dir) != NULL)
+ {
+ count++;
+ }
+
+ if (count > 0)
+ {
+ /* creamos un enlace a la carpeta de nautilus */
+ symlink(nautilus_scripts_path, nautilus_syslink);
+ }
+
+ g_dir_close(dir);
+ }
+ }
+
+ g_free(nautilus_syslink);
+ }
+
+ g_free(nautilus_scripts_path);
+ }
+
+ g_free(scripts_directory_path);
+}
+
+static void
+scripts_added_or_changed_callback (CajaDirectory *directory,
+ GList *files,
+ gpointer callback_data)
+{
+ FMDirectoryView *view;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+
+ view->details->scripts_invalid = TRUE;
+ if (view->details->active) {
+ schedule_update_menus (view);
+ }
+}
+
+static void
+templates_added_or_changed_callback (CajaDirectory *directory,
+ GList *files,
+ gpointer callback_data)
+{
+ FMDirectoryView *view;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+
+ view->details->templates_invalid = TRUE;
+ if (view->details->active) {
+ schedule_update_menus (view);
+ }
+}
+
+static void
+add_directory_to_directory_list (FMDirectoryView *view,
+ CajaDirectory *directory,
+ GList **directory_list,
+ GCallback changed_callback)
+{
+ CajaFileAttributes attributes;
+
+ if (g_list_find (*directory_list, directory) == NULL) {
+ caja_directory_ref (directory);
+
+ attributes =
+ CAJA_FILE_ATTRIBUTES_FOR_ICON |
+ CAJA_FILE_ATTRIBUTE_INFO |
+ CAJA_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT;
+
+ caja_directory_file_monitor_add (directory, directory_list,
+ FALSE, FALSE, attributes,
+ (CajaDirectoryCallback)changed_callback, view);
+
+ g_signal_connect_object (directory, "files_added",
+ G_CALLBACK (changed_callback), view, 0);
+ g_signal_connect_object (directory, "files_changed",
+ G_CALLBACK (changed_callback), view, 0);
+
+ *directory_list = g_list_append (*directory_list, directory);
+ }
+}
+
+static void
+remove_directory_from_directory_list (FMDirectoryView *view,
+ CajaDirectory *directory,
+ GList **directory_list,
+ GCallback changed_callback)
+{
+ *directory_list = g_list_remove (*directory_list, directory);
+
+ g_signal_handlers_disconnect_by_func (directory,
+ G_CALLBACK (changed_callback),
+ view);
+
+ caja_directory_file_monitor_remove (directory, directory_list);
+
+ caja_directory_unref (directory);
+}
+
+
+static void
+add_directory_to_scripts_directory_list (FMDirectoryView *view,
+ CajaDirectory *directory)
+{
+ add_directory_to_directory_list (view, directory,
+ &view->details->scripts_directory_list,
+ G_CALLBACK (scripts_added_or_changed_callback));
+}
+
+static void
+remove_directory_from_scripts_directory_list (FMDirectoryView *view,
+ CajaDirectory *directory)
+{
+ remove_directory_from_directory_list (view, directory,
+ &view->details->scripts_directory_list,
+ G_CALLBACK (scripts_added_or_changed_callback));
+}
+
+static void
+add_directory_to_templates_directory_list (FMDirectoryView *view,
+ CajaDirectory *directory)
+{
+ add_directory_to_directory_list (view, directory,
+ &view->details->templates_directory_list,
+ G_CALLBACK (templates_added_or_changed_callback));
+}
+
+static void
+remove_directory_from_templates_directory_list (FMDirectoryView *view,
+ CajaDirectory *directory)
+{
+ remove_directory_from_directory_list (view, directory,
+ &view->details->templates_directory_list,
+ G_CALLBACK (templates_added_or_changed_callback));
+}
+
+static void
+slot_active (CajaWindowSlot *slot,
+ FMDirectoryView *view)
+{
+ g_assert (!view->details->active);
+ view->details->active = TRUE;
+
+ fm_directory_view_merge_menus (view);
+ schedule_update_menus (view);
+}
+
+static void
+slot_inactive (CajaWindowSlot *slot,
+ FMDirectoryView *view)
+{
+ g_assert (view->details->active ||
+ gtk_widget_get_parent (GTK_WIDGET (view)) == NULL);
+ view->details->active = FALSE;
+
+ fm_directory_view_unmerge_menus (view);
+ remove_update_menus_timeout_callback (view);
+}
+
+static void
+fm_directory_view_grab_focus (CajaView *view)
+{
+ /* focus the child of the scrolled window if it exists */
+ GtkWidget *child;
+ child = gtk_bin_get_child (GTK_BIN (view));
+ if (child) {
+ gtk_widget_grab_focus (GTK_WIDGET (child));
+ }
+}
+
+static void
+view_iface_update_menus (CajaView *view)
+{
+ fm_directory_view_update_menus (FM_DIRECTORY_VIEW (view));
+}
+
+static GtkWidget *
+fm_directory_view_get_widget (CajaView *view)
+{
+ return GTK_WIDGET (view);
+}
+
+static int
+fm_directory_view_get_selection_count (CajaView *view)
+{
+ /* FIXME: This could be faster if we special cased it in subclasses */
+ GList *files;
+ int len;
+
+ files = fm_directory_view_get_selection (FM_DIRECTORY_VIEW (view));
+ len = g_list_length (files);
+ caja_file_list_free (files);
+
+ return len;
+}
+
+static GList *
+fm_directory_view_get_selection_locations (CajaView *view)
+{
+ GList *files;
+ GList *locations;
+ GFile *location;
+ GList *l;
+
+ files = fm_directory_view_get_selection (FM_DIRECTORY_VIEW (view));
+ locations = NULL;
+ for (l = files; l != NULL; l = l->next) {
+ location = caja_file_get_location (CAJA_FILE (l->data));
+ locations = g_list_prepend (locations, location);
+ }
+ caja_file_list_free (files);
+
+ return g_list_reverse (locations);
+}
+
+static GList *
+file_list_from_location_list (const GList *uri_list)
+{
+ GList *file_list;
+ const GList *node;
+
+ file_list = NULL;
+ for (node = uri_list; node != NULL; node = node->next) {
+ file_list = g_list_prepend
+ (file_list,
+ caja_file_get (node->data));
+ }
+ return g_list_reverse (file_list);
+}
+
+static void
+fm_directory_view_set_selection_locations (CajaView *caja_view,
+ GList *selection_locations)
+{
+ GList *selection;
+ FMDirectoryView *view;
+
+ view = FM_DIRECTORY_VIEW (caja_view);
+
+ if (!view->details->loading) {
+ /* If we aren't still loading, set the selection right now,
+ * and reveal the new selection.
+ */
+ selection = file_list_from_location_list (selection_locations);
+ view->details->selection_change_is_due_to_shell = TRUE;
+ fm_directory_view_set_selection (view, selection);
+ view->details->selection_change_is_due_to_shell = FALSE;
+ fm_directory_view_reveal_selection (view);
+ caja_file_list_free (selection);
+ } else {
+ /* If we are still loading, set the list of pending URIs instead.
+ * done_loading() will eventually select the pending URIs and reveal them.
+ */
+ eel_g_object_list_free (view->details->pending_locations_selected);
+ view->details->pending_locations_selected =
+ eel_g_object_list_copy (selection_locations);
+ }
+}
+
+
+void
+fm_directory_view_init_view_iface (CajaViewIface *iface)
+{
+ iface->grab_focus = fm_directory_view_grab_focus;
+ iface->update_menus = view_iface_update_menus;
+
+ iface->get_widget = fm_directory_view_get_widget;
+ iface->load_location = fm_directory_view_load_location;
+ iface->stop_loading = fm_directory_view_stop_loading;
+
+ iface->get_selection_count = fm_directory_view_get_selection_count;
+ iface->get_selection = fm_directory_view_get_selection_locations;
+ iface->set_selection = fm_directory_view_set_selection_locations;
+ iface->set_is_active = (gpointer)fm_directory_view_set_is_active;
+
+ iface->supports_zooming = (gpointer)fm_directory_view_supports_zooming;
+ iface->bump_zoom_level = (gpointer)fm_directory_view_bump_zoom_level;
+ iface->zoom_to_level = (gpointer)fm_directory_view_zoom_to_level;
+ iface->restore_default_zoom_level = (gpointer)fm_directory_view_restore_default_zoom_level;
+ iface->can_zoom_in = (gpointer)fm_directory_view_can_zoom_in;
+ iface->can_zoom_out = (gpointer)fm_directory_view_can_zoom_out;
+ iface->get_zoom_level = (gpointer)fm_directory_view_get_zoom_level;
+
+ iface->pop_up_location_context_menu = (gpointer)fm_directory_view_pop_up_location_context_menu;
+ iface->drop_proxy_received_uris = (gpointer)fm_directory_view_drop_proxy_received_uris;
+ iface->drop_proxy_received_netscape_url = (gpointer)fm_directory_view_drop_proxy_received_netscape_url;
+}
+
+static void
+fm_directory_view_init (FMDirectoryView *view)
+{
+ static gboolean setup_autos = FALSE;
+ CajaDirectory *scripts_directory;
+ CajaDirectory *templates_directory;
+ char *templates_uri;
+
+ if (!setup_autos) {
+ setup_autos = TRUE;
+ eel_preferences_add_auto_boolean (CAJA_PREFERENCES_CONFIRM_TRASH,
+ &confirm_trash_auto_value);
+ eel_preferences_add_auto_boolean (CAJA_PREFERENCES_ENABLE_DELETE,
+ &show_delete_command_auto_value);
+ }
+
+ view->details = g_new0 (FMDirectoryViewDetails, 1);
+
+ /* Default to true; desktop-icon-view sets to false */
+ view->details->show_foreign_files = TRUE;
+
+ view->details->non_ready_files =
+ g_hash_table_new_full (file_and_directory_hash,
+ file_and_directory_equal,
+ (GDestroyNotify)file_and_directory_free,
+ NULL);
+
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (view),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_hadjustment (GTK_SCROLLED_WINDOW (view), NULL);
+ gtk_scrolled_window_set_vadjustment (GTK_SCROLLED_WINDOW (view), NULL);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (view), GTK_SHADOW_ETCHED_IN);
+
+ set_up_scripts_directory_global ();
+ scripts_directory = caja_directory_get_by_uri (scripts_directory_uri);
+ add_directory_to_scripts_directory_list (view, scripts_directory);
+ caja_directory_unref (scripts_directory);
+
+ if (caja_should_use_templates_directory ()) {
+ templates_uri = caja_get_templates_directory_uri ();
+ templates_directory = caja_directory_get_by_uri (templates_uri);
+ g_free (templates_uri);
+ add_directory_to_templates_directory_list (view, templates_directory);
+ caja_directory_unref (templates_directory);
+ }
+ update_templates_directory (view);
+ g_signal_connect_object (caja_signaller_get_current (),
+ "user_dirs_changed",
+ G_CALLBACK (user_dirs_changed),
+ view, G_CONNECT_SWAPPED);
+
+ view->details->sort_directories_first =
+ eel_preferences_get_boolean (CAJA_PREFERENCES_SORT_DIRECTORIES_FIRST);
+
+ g_signal_connect_object (caja_trash_monitor_get (), "trash_state_changed",
+ G_CALLBACK (fm_directory_view_trash_state_changed_callback), view, 0);
+
+ /* React to clipboard changes */
+ g_signal_connect_object (caja_clipboard_monitor_get (), "clipboard_changed",
+ G_CALLBACK (clipboard_changed_callback), view, 0);
+
+ /* Register to menu provider extension signal managing menu updates */
+ g_signal_connect_object (caja_signaller_get_current (), "popup_menu_changed",
+ G_CALLBACK (fm_directory_view_update_menus), view, G_CONNECT_SWAPPED);
+
+ gtk_widget_show (GTK_WIDGET (view));
+
+ eel_preferences_add_callback (CAJA_PREFERENCES_CONFIRM_TRASH,
+ schedule_update_menus_callback, view);
+ eel_preferences_add_callback (CAJA_PREFERENCES_ENABLE_DELETE,
+ schedule_update_menus_callback, view);
+ eel_preferences_add_callback (CAJA_PREFERENCES_ICON_VIEW_CAPTIONS,
+ text_attribute_names_changed_callback, view);
+ eel_preferences_add_callback (CAJA_PREFERENCES_SHOW_IMAGE_FILE_THUMBNAILS,
+ image_display_policy_changed_callback, view);
+ eel_preferences_add_callback (CAJA_PREFERENCES_CLICK_POLICY,
+ click_policy_changed_callback, view);
+ eel_preferences_add_callback (CAJA_PREFERENCES_SORT_DIRECTORIES_FIRST,
+ sort_directories_first_changed_callback, view);
+ eel_preferences_add_callback (CAJA_PREFERENCES_LOCKDOWN_COMMAND_LINE,
+ lockdown_disable_command_line_changed_callback, view);
+}
+
+static void
+real_unmerge_menus (FMDirectoryView *view)
+{
+ GtkUIManager *ui_manager;
+
+ if (view->details->window == NULL) {
+ return;
+ }
+
+ ui_manager = caja_window_info_get_ui_manager (view->details->window);
+
+ caja_ui_unmerge_ui (ui_manager,
+ &view->details->dir_merge_id,
+ &view->details->dir_action_group);
+ caja_ui_unmerge_ui (ui_manager,
+ &view->details->extensions_menu_merge_id,
+ &view->details->extensions_menu_action_group);
+ caja_ui_unmerge_ui (ui_manager,
+ &view->details->open_with_merge_id,
+ &view->details->open_with_action_group);
+ caja_ui_unmerge_ui (ui_manager,
+ &view->details->scripts_merge_id,
+ &view->details->scripts_action_group);
+ caja_ui_unmerge_ui (ui_manager,
+ &view->details->templates_merge_id,
+ &view->details->templates_action_group);
+}
+
+static void
+fm_directory_view_destroy (GtkObject *object)
+{
+ FMDirectoryView *view;
+ GList *node, *next;
+
+ view = FM_DIRECTORY_VIEW (object);
+
+ disconnect_model_handlers (view);
+
+ fm_directory_view_unmerge_menus (view);
+
+ /* We don't own the window, so no unref */
+ view->details->slot = NULL;
+ view->details->window = NULL;
+
+ fm_directory_view_stop (view);
+ fm_directory_view_clear (view);
+
+ for (node = view->details->scripts_directory_list; node != NULL; node = next) {
+ next = node->next;
+ remove_directory_from_scripts_directory_list (view, node->data);
+ }
+
+ for (node = view->details->templates_directory_list; node != NULL; node = next) {
+ next = node->next;
+ remove_directory_from_templates_directory_list (view, node->data);
+ }
+
+ while (view->details->subdirectory_list != NULL) {
+ fm_directory_view_remove_subdirectory (view,
+ view->details->subdirectory_list->data);
+ }
+
+ remove_update_menus_timeout_callback (view);
+ remove_update_status_idle_callback (view);
+
+ if (view->details->display_selection_idle_id != 0) {
+ g_source_remove (view->details->display_selection_idle_id);
+ view->details->display_selection_idle_id = 0;
+ }
+
+ if (view->details->reveal_selection_idle_id != 0) {
+ g_source_remove (view->details->reveal_selection_idle_id);
+ view->details->reveal_selection_idle_id = 0;
+ }
+
+ if (view->details->delayed_rename_file_id != 0) {
+ g_source_remove (view->details->delayed_rename_file_id);
+ view->details->delayed_rename_file_id = 0;
+ }
+
+ if (view->details->model) {
+ caja_directory_unref (view->details->model);
+ view->details->model = NULL;
+ }
+
+ if (view->details->directory_as_file) {
+ caja_file_unref (view->details->directory_as_file);
+ view->details->directory_as_file = NULL;
+ }
+
+ EEL_CALL_PARENT (GTK_OBJECT_CLASS, destroy, (object));
+}
+
+static void
+fm_directory_view_finalize (GObject *object)
+{
+ FMDirectoryView *view;
+
+ view = FM_DIRECTORY_VIEW (object);
+
+ eel_preferences_remove_callback (CAJA_PREFERENCES_CONFIRM_TRASH,
+ schedule_update_menus_callback, view);
+ eel_preferences_remove_callback (CAJA_PREFERENCES_ENABLE_DELETE,
+ schedule_update_menus_callback, view);
+ eel_preferences_remove_callback (CAJA_PREFERENCES_ICON_VIEW_CAPTIONS,
+ text_attribute_names_changed_callback, view);
+ eel_preferences_remove_callback (CAJA_PREFERENCES_SHOW_IMAGE_FILE_THUMBNAILS,
+ image_display_policy_changed_callback, view);
+ eel_preferences_remove_callback (CAJA_PREFERENCES_CLICK_POLICY,
+ click_policy_changed_callback, view);
+ eel_preferences_remove_callback (CAJA_PREFERENCES_SORT_DIRECTORIES_FIRST,
+ sort_directories_first_changed_callback, view);
+ eel_preferences_remove_callback (CAJA_PREFERENCES_LOCKDOWN_COMMAND_LINE,
+ lockdown_disable_command_line_changed_callback, view);
+
+ unschedule_pop_up_location_context_menu (view);
+ if (view->details->location_popup_event != NULL) {
+ gdk_event_free ((GdkEvent *) view->details->location_popup_event);
+ }
+
+ g_hash_table_destroy (view->details->non_ready_files);
+
+ g_free (view->details);
+
+ EEL_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
+}
+
+/**
+ * fm_directory_view_display_selection_info:
+ *
+ * Display information about the current selection, and notify the view frame of the changed selection.
+ * @view: FMDirectoryView for which to display selection info.
+ *
+ **/
+void
+fm_directory_view_display_selection_info (FMDirectoryView *view)
+{
+ GList *selection;
+ goffset non_folder_size;
+ gboolean non_folder_size_known;
+ guint non_folder_count, folder_count, folder_item_count;
+ gboolean folder_item_count_known;
+ guint file_item_count;
+ GList *p;
+ char *first_item_name;
+ char *non_folder_str;
+ char *folder_count_str;
+ char *folder_item_count_str;
+ char *status_string;
+ char *free_space_str;
+ char *obj_selected_free_space_str;
+ CajaFile *file;
+
+ g_return_if_fail (FM_IS_DIRECTORY_VIEW (view));
+
+ selection = fm_directory_view_get_selection (view);
+
+ folder_item_count_known = TRUE;
+ folder_count = 0;
+ folder_item_count = 0;
+ non_folder_count = 0;
+ non_folder_size_known = FALSE;
+ non_folder_size = 0;
+ first_item_name = NULL;
+ folder_count_str = NULL;
+ non_folder_str = NULL;
+ folder_item_count_str = NULL;
+ free_space_str = NULL;
+ obj_selected_free_space_str = NULL;
+
+ for (p = selection; p != NULL; p = p->next) {
+ file = p->data;
+ if (caja_file_is_directory (file)) {
+ folder_count++;
+ if (caja_file_get_directory_item_count (file, &file_item_count, NULL)) {
+ folder_item_count += file_item_count;
+ } else {
+ folder_item_count_known = FALSE;
+ }
+ } else {
+ non_folder_count++;
+ if (!caja_file_can_get_size (file)) {
+ non_folder_size_known = TRUE;
+ non_folder_size += caja_file_get_size (file);
+ }
+ }
+
+ if (first_item_name == NULL) {
+ first_item_name = caja_file_get_display_name (file);
+ }
+ }
+
+ caja_file_list_free (selection);
+
+ /* Break out cases for localization's sake. But note that there are still pieces
+ * being assembled in a particular order, which may be a problem for some localizers.
+ */
+
+ if (folder_count != 0) {
+ if (folder_count == 1 && non_folder_count == 0) {
+ folder_count_str = g_strdup_printf (_("\"%s\" selected"), first_item_name);
+ } else {
+ folder_count_str = g_strdup_printf (ngettext("%'d folder selected",
+ "%'d folders selected",
+ folder_count),
+ folder_count);
+ }
+
+ if (folder_count == 1) {
+ if (!folder_item_count_known) {
+ folder_item_count_str = g_strdup ("");
+ } else {
+ folder_item_count_str = g_strdup_printf (ngettext(" (containing %'d item)",
+ " (containing %'d items)",
+ folder_item_count),
+ folder_item_count);
+ }
+ }
+ else {
+ if (!folder_item_count_known) {
+ folder_item_count_str = g_strdup ("");
+ } else {
+ /* translators: this is preceded with a string of form 'N folders' (N more than 1) */
+ folder_item_count_str = g_strdup_printf (ngettext(" (containing a total of %'d item)",
+ " (containing a total of %'d items)",
+ folder_item_count),
+ folder_item_count);
+ }
+
+ }
+ }
+
+ if (non_folder_count != 0) {
+ char *items_string;
+
+ if (folder_count == 0) {
+ if (non_folder_count == 1) {
+ items_string = g_strdup_printf (_("\"%s\" selected"),
+ first_item_name);
+ } else {
+ items_string = g_strdup_printf (ngettext("%'d item selected",
+ "%'d items selected",
+ non_folder_count),
+ non_folder_count);
+ }
+ } else {
+ /* Folders selected also, use "other" terminology */
+ items_string = g_strdup_printf (ngettext("%'d other item selected",
+ "%'d other items selected",
+ non_folder_count),
+ non_folder_count);
+ }
+
+ if (non_folder_size_known) {
+ char *size_string;
+
+ #if GLIB_CHECK_VERSION(2, 30, 0)
+ size_string = g_format_size(non_folder_size);
+ #else
+ size_string = g_format_size_for_display(non_folder_size);
+ #endif
+
+ /* This is marked for translation in case a localiser
+ * needs to use something other than parentheses. The
+ * first message gives the number of items selected;
+ * the message in parentheses the size of those items.
+ */
+ non_folder_str = g_strdup_printf (_("%s (%s)"),
+ items_string,
+ size_string);
+
+ g_free (size_string);
+ g_free (items_string);
+ } else {
+ non_folder_str = items_string;
+ }
+ }
+
+ free_space_str = caja_file_get_volume_free_space (view->details->directory_as_file);
+ if (free_space_str != NULL) {
+ obj_selected_free_space_str = g_strdup_printf (_("Free space: %s"), free_space_str);
+ }
+ if (folder_count == 0 && non_folder_count == 0) {
+ char *item_count_str;
+ guint item_count;
+
+ item_count = fm_directory_view_get_item_count (view);
+
+ item_count_str = g_strdup_printf (ngettext ("%'u item", "%'u items", item_count), item_count);
+
+ if (free_space_str != NULL) {
+ status_string = g_strdup_printf (_("%s, Free space: %s"), item_count_str, free_space_str);
+ g_free (item_count_str);
+ } else {
+ status_string = item_count_str;
+ }
+
+ } else if (folder_count == 0) {
+ if (free_space_str == NULL) {
+ status_string = g_strdup (non_folder_str);
+ } else {
+ /* Marking this for translation, since you
+ * might want to change "," to something else.
+ * After the comma the amount of free space will
+ * be shown.
+ */
+ status_string = g_strdup_printf (_("%s, %s"),
+ non_folder_str,
+ obj_selected_free_space_str);
+ }
+ } else if (non_folder_count == 0) {
+ if (free_space_str == NULL) {
+ /* No use marking this for translation, since you
+ * can't reorder the strings, which is the main thing
+ * you'd want to do.
+ */
+ status_string = g_strdup_printf ("%s%s",
+ folder_count_str,
+ folder_item_count_str);
+ } else {
+ /* Marking this for translation, since you
+ * might want to change "," to something else.
+ * After the comma the amount of free space will
+ * be shown.
+ */
+ status_string = g_strdup_printf (_("%s%s, %s"),
+ folder_count_str,
+ folder_item_count_str,
+ obj_selected_free_space_str);
+ }
+ } else {
+ if (obj_selected_free_space_str == NULL) {
+ /* This is marked for translation in case a localizer
+ * needs to change ", " to something else. The comma
+ * is between the message about the number of folders
+ * and the number of items in those folders and the
+ * message about the number of other items and the
+ * total size of those items.
+ */
+ status_string = g_strdup_printf (_("%s%s, %s"),
+ folder_count_str,
+ folder_item_count_str,
+ non_folder_str);
+ } else {
+ /* This is marked for translation in case a localizer
+ * needs to change ", " to something else. The first comma
+ * is between the message about the number of folders
+ * and the number of items in those folders and the
+ * message about the number of other items and the
+ * total size of those items. After the second comma
+ * the free space is written.
+ */
+ status_string = g_strdup_printf (_("%s%s, %s, %s"),
+ folder_count_str,
+ folder_item_count_str,
+ non_folder_str,
+ obj_selected_free_space_str);
+ }
+ }
+
+ g_free (free_space_str);
+ g_free (obj_selected_free_space_str);
+ g_free (first_item_name);
+ g_free (folder_count_str);
+ g_free (folder_item_count_str);
+ g_free (non_folder_str);
+
+ caja_window_slot_info_set_status (view->details->slot,
+ status_string);
+ g_free (status_string);
+}
+
+void
+fm_directory_view_send_selection_change (FMDirectoryView *view)
+{
+ caja_window_info_report_selection_changed (view->details->window);
+
+ view->details->send_selection_change_to_shell = FALSE;
+}
+
+gboolean
+fm_directory_view_get_allow_moves (FMDirectoryView *view)
+{
+ return view->details->allow_moves;
+}
+
+static void
+fm_directory_view_load_location (CajaView *caja_view,
+ const char *location)
+{
+ CajaDirectory *directory;
+ FMDirectoryView *directory_view;
+
+ directory_view = FM_DIRECTORY_VIEW (caja_view);
+
+ if (eel_uri_is_search (location)) {
+ directory_view->details->allow_moves = FALSE;
+ } else {
+ directory_view->details->allow_moves = TRUE;
+ }
+
+ directory = caja_directory_get_by_uri (location);
+ load_directory (directory_view, directory);
+ caja_directory_unref (directory);
+}
+
+static void
+fm_directory_view_stop_loading (CajaView *caja_view)
+{
+ fm_directory_view_stop (FM_DIRECTORY_VIEW (caja_view));
+}
+
+static void
+fm_directory_view_file_limit_reached (FMDirectoryView *view)
+{
+ g_assert (FM_IS_DIRECTORY_VIEW (view));
+
+ EEL_CALL_METHOD (FM_DIRECTORY_VIEW_CLASS, view,
+ file_limit_reached, (view));
+}
+
+static void
+real_file_limit_reached (FMDirectoryView *view)
+{
+ CajaFile *file;
+ GtkDialog *dialog;
+ char *directory_name;
+ char *message;
+
+ g_assert (FM_IS_DIRECTORY_VIEW (view));
+
+ file = fm_directory_view_get_directory_as_file (view);
+ directory_name = caja_file_get_display_name (file);
+
+ /* Note that the number of items actually displayed varies somewhat due
+ * to the way files are collected in batches. So you can't assume that
+ * no more than the constant limit are displayed.
+ */
+ message = g_strdup_printf (_("The folder \"%s\" contains more files than "
+ "Caja can handle."),
+ directory_name);
+ g_free (directory_name);
+
+ dialog = eel_show_warning_dialog (message,
+ _("Some files will not be displayed."),
+ fm_directory_view_get_containing_window (view));
+ g_free (message);
+}
+
+static void
+check_for_directory_hard_limit (FMDirectoryView *view)
+{
+ if (caja_directory_file_list_length_reached (view->details->model)) {
+ fm_directory_view_file_limit_reached (view);
+ }
+}
+
+static gboolean
+reveal_selection_idle_callback (gpointer data)
+{
+ FMDirectoryView *view;
+
+ view = FM_DIRECTORY_VIEW (data);
+
+ view->details->reveal_selection_idle_id = 0;
+ fm_directory_view_reveal_selection (view);
+
+ return FALSE;
+}
+
+static void
+done_loading (FMDirectoryView *view,
+ gboolean all_files_seen)
+{
+ GList *locations_selected, *selection;
+
+ if (!view->details->loading) {
+ return;
+ }
+
+ /* This can be called during destruction, in which case there
+ * is no CajaWindowInfo any more.
+ */
+ if (view->details->window != NULL) {
+ if (all_files_seen) {
+ caja_window_info_report_load_complete (view->details->window, CAJA_VIEW (view));
+ }
+
+ schedule_update_menus (view);
+ schedule_update_status (view);
+ check_for_directory_hard_limit (view);
+ reset_update_interval (view);
+
+ locations_selected = view->details->pending_locations_selected;
+ if (locations_selected != NULL && all_files_seen) {
+ view->details->pending_locations_selected = NULL;
+
+ selection = file_list_from_location_list (locations_selected);
+
+ view->details->selection_change_is_due_to_shell = TRUE;
+ fm_directory_view_set_selection (view, selection);
+ view->details->selection_change_is_due_to_shell = FALSE;
+ caja_file_list_free (selection);
+
+ if (FM_IS_LIST_VIEW (view)) {
+ /* HACK: We should be able to directly call reveal_selection here,
+ * but at this point the GtkTreeView hasn't allocated the new nodes
+ * yet, and it has a bug in the scroll calculation dealing with this
+ * special case. It would always make the selection the top row, even
+ * if no scrolling would be neccessary to reveal it. So we let it
+ * allocate before revealing.
+ */
+ if (view->details->reveal_selection_idle_id != 0) {
+ g_source_remove (view->details->reveal_selection_idle_id);
+ }
+ view->details->reveal_selection_idle_id =
+ g_idle_add (reveal_selection_idle_callback, view);
+ } else {
+ fm_directory_view_reveal_selection (view);
+ }
+ }
+ eel_g_object_list_free (locations_selected);
+ fm_directory_view_display_selection_info (view);
+ }
+
+ fm_directory_view_end_loading (view, all_files_seen);
+
+ view->details->loading = FALSE;
+}
+
+
+typedef struct {
+ GHashTable *debuting_files;
+ GList *added_files;
+} DebutingFilesData;
+
+static void
+debuting_files_data_free (DebutingFilesData *data)
+{
+ g_hash_table_unref (data->debuting_files);
+ caja_file_list_free (data->added_files);
+ g_free (data);
+}
+
+/* This signal handler watch for the arrival of the icons created
+ * as the result of a file operation. Once the last one is detected
+ * it selects and reveals them all.
+ */
+static void
+debuting_files_add_file_callback (FMDirectoryView *view,
+ CajaFile *new_file,
+ CajaDirectory *directory,
+ DebutingFilesData *data)
+{
+ GFile *location;
+
+ location = caja_file_get_location (new_file);
+
+ if (g_hash_table_remove (data->debuting_files, location)) {
+ caja_file_ref (new_file);
+ data->added_files = g_list_prepend (data->added_files, new_file);
+
+ if (g_hash_table_size (data->debuting_files) == 0) {
+ fm_directory_view_set_selection (view, data->added_files);
+ fm_directory_view_reveal_selection (view);
+ g_signal_handlers_disconnect_by_func (view,
+ G_CALLBACK (debuting_files_add_file_callback),
+ data);
+ }
+ }
+
+ g_object_unref (location);
+}
+
+typedef struct {
+ GList *added_files;
+ FMDirectoryView *directory_view;
+} CopyMoveDoneData;
+
+static void
+copy_move_done_data_free (CopyMoveDoneData *data)
+{
+ g_assert (data != NULL);
+
+ eel_remove_weak_pointer (&data->directory_view);
+ caja_file_list_free (data->added_files);
+ g_free (data);
+}
+
+static void
+pre_copy_move_add_file_callback (FMDirectoryView *view,
+ CajaFile *new_file,
+ CajaDirectory *directory,
+ CopyMoveDoneData *data)
+{
+ caja_file_ref (new_file);
+ data->added_files = g_list_prepend (data->added_files, new_file);
+}
+
+/* This needs to be called prior to caja_file_operations_copy_move.
+ * It hooks up a signal handler to catch any icons that get added before
+ * the copy_done_callback is invoked. The return value should be passed
+ * as the data for uri_copy_move_done_callback.
+ */
+static CopyMoveDoneData *
+pre_copy_move (FMDirectoryView *directory_view)
+{
+ CopyMoveDoneData *copy_move_done_data;
+
+ copy_move_done_data = g_new0 (CopyMoveDoneData, 1);
+ copy_move_done_data->directory_view = directory_view;
+
+ eel_add_weak_pointer (&copy_move_done_data->directory_view);
+
+ /* We need to run after the default handler adds the folder we want to
+ * operate on. The ADD_FILE signal is registered as G_SIGNAL_RUN_LAST, so we
+ * must use connect_after.
+ */
+ g_signal_connect (directory_view, "add_file",
+ G_CALLBACK (pre_copy_move_add_file_callback), copy_move_done_data);
+
+ return copy_move_done_data;
+}
+
+/* This function is used to pull out any debuting uris that were added
+ * and (as a side effect) remove them from the debuting uri hash table.
+ */
+static gboolean
+copy_move_done_partition_func (gpointer data, gpointer callback_data)
+{
+ GFile *location;
+ gboolean result;
+
+ location = caja_file_get_location (CAJA_FILE (data));
+ result = g_hash_table_remove ((GHashTable *) callback_data, location);
+ g_object_unref (location);
+
+ return result;
+}
+
+static gboolean
+remove_not_really_moved_files (gpointer key,
+ gpointer value,
+ gpointer callback_data)
+{
+ GList **added_files;
+ GFile *loc;
+
+ loc = key;
+
+ if (GPOINTER_TO_INT (value)) {
+ return FALSE;
+ }
+
+ added_files = callback_data;
+ *added_files = g_list_prepend (*added_files,
+ caja_file_get (loc));
+ return TRUE;
+}
+
+
+/* When this function is invoked, the file operation is over, but all
+ * the icons may not have been added to the directory view yet, so
+ * we can't select them yet.
+ *
+ * We're passed a hash table of the uri's to look out for, we hook
+ * up a signal handler to await their arrival.
+ */
+static void
+copy_move_done_callback (GHashTable *debuting_files, gpointer data)
+{
+ FMDirectoryView *directory_view;
+ CopyMoveDoneData *copy_move_done_data;
+ DebutingFilesData *debuting_files_data;
+
+ copy_move_done_data = (CopyMoveDoneData *) data;
+ directory_view = copy_move_done_data->directory_view;
+
+ if (directory_view != NULL) {
+ g_assert (FM_IS_DIRECTORY_VIEW (directory_view));
+
+ debuting_files_data = g_new (DebutingFilesData, 1);
+ debuting_files_data->debuting_files = g_hash_table_ref (debuting_files);
+ debuting_files_data->added_files = eel_g_list_partition
+ (copy_move_done_data->added_files,
+ copy_move_done_partition_func,
+ debuting_files,
+ &copy_move_done_data->added_files);
+
+ /* We're passed the same data used by pre_copy_move_add_file_callback, so disconnecting
+ * it will free data. We've already siphoned off the added_files we need, and stashed the
+ * directory_view pointer.
+ */
+ g_signal_handlers_disconnect_by_func (directory_view,
+ G_CALLBACK (pre_copy_move_add_file_callback),
+ data);
+
+ /* Any items in the debuting_files hash table that have
+ * "FALSE" as their value aren't really being copied
+ * or moved, so we can't wait for an add_file signal
+ * to come in for those.
+ */
+ g_hash_table_foreach_remove (debuting_files,
+ remove_not_really_moved_files,
+ &debuting_files_data->added_files);
+
+ if (g_hash_table_size (debuting_files) == 0) {
+ /* on the off-chance that all the icons have already been added */
+ if (debuting_files_data->added_files != NULL) {
+ fm_directory_view_set_selection (directory_view,
+ debuting_files_data->added_files);
+ fm_directory_view_reveal_selection (directory_view);
+ }
+ debuting_files_data_free (debuting_files_data);
+ } else {
+ /* We need to run after the default handler adds the folder we want to
+ * operate on. The ADD_FILE signal is registered as G_SIGNAL_RUN_LAST, so we
+ * must use connect_after.
+ */
+ g_signal_connect_data (GTK_OBJECT (directory_view),
+ "add_file",
+ G_CALLBACK (debuting_files_add_file_callback),
+ debuting_files_data,
+ (GClosureNotify) debuting_files_data_free,
+ G_CONNECT_AFTER);
+ }
+ }
+
+ copy_move_done_data_free (copy_move_done_data);
+}
+
+static gboolean
+real_file_still_belongs (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory)
+{
+ if (view->details->model != directory &&
+ g_list_find (view->details->subdirectory_list, directory) == NULL) {
+ return FALSE;
+ }
+
+ return caja_directory_contains_file (directory, file);
+}
+
+static gboolean
+still_should_show_file (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory)
+{
+ return fm_directory_view_should_show_file (view, file)
+ && EEL_INVOKE_METHOD (FM_DIRECTORY_VIEW_CLASS, view, file_still_belongs, (view, file, directory));
+}
+
+static gboolean
+ready_to_load (CajaFile *file)
+{
+ return caja_file_check_if_ready (file,
+ CAJA_FILE_ATTRIBUTES_FOR_ICON);
+}
+
+static int
+compare_files_cover (gconstpointer a, gconstpointer b, gpointer callback_data)
+{
+ const FileAndDirectory *fad1, *fad2;
+ FMDirectoryView *view;
+
+ view = callback_data;
+ fad1 = a; fad2 = b;
+
+ if (fad1->directory < fad2->directory) {
+ return -1;
+ } else if (fad1->directory > fad2->directory) {
+ return 1;
+ } else {
+ return EEL_INVOKE_METHOD (FM_DIRECTORY_VIEW_CLASS, view, compare_files,
+ (view, fad1->file, fad2->file));
+ }
+}
+static void
+sort_files (FMDirectoryView *view, GList **list)
+{
+ *list = g_list_sort_with_data (*list, compare_files_cover, view);
+
+}
+
+/* Go through all the new added and changed files.
+ * Put any that are not ready to load in the non_ready_files hash table.
+ * Add all the rest to the old_added_files and old_changed_files lists.
+ * Sort the old_*_files lists if anything was added to them.
+ */
+static void
+process_new_files (FMDirectoryView *view)
+{
+ GList *new_added_files, *new_changed_files, *old_added_files, *old_changed_files;
+ GHashTable *non_ready_files;
+ GList *node, *next;
+ FileAndDirectory *pending;
+ gboolean in_non_ready;
+
+ new_added_files = view->details->new_added_files;
+ view->details->new_added_files = NULL;
+ new_changed_files = view->details->new_changed_files;
+ view->details->new_changed_files = NULL;
+
+ non_ready_files = view->details->non_ready_files;
+
+ old_added_files = view->details->old_added_files;
+ old_changed_files = view->details->old_changed_files;
+
+ /* Newly added files go into the old_added_files list if they're
+ * ready, and into the hash table if they're not.
+ */
+ for (node = new_added_files; node != NULL; node = next) {
+ next = node->next;
+ pending = (FileAndDirectory *)node->data;
+ in_non_ready = g_hash_table_lookup (non_ready_files, pending) != NULL;
+ if (fm_directory_view_should_show_file (view, pending->file)) {
+ if (ready_to_load (pending->file)) {
+ if (in_non_ready) {
+ g_hash_table_remove (non_ready_files, pending);
+ }
+ new_added_files = g_list_delete_link (new_added_files, node);
+ old_added_files = g_list_prepend (old_added_files, pending);
+ } else {
+ if (!in_non_ready) {
+ new_added_files = g_list_delete_link (new_added_files, node);
+ g_hash_table_insert (non_ready_files, pending, pending);
+ }
+ }
+ }
+ }
+ file_and_directory_list_free (new_added_files);
+
+ /* Newly changed files go into the old_added_files list if they're ready
+ * and were seen non-ready in the past, into the old_changed_files list
+ * if they are read and were not seen non-ready in the past, and into
+ * the hash table if they're not ready.
+ */
+ for (node = new_changed_files; node != NULL; node = next) {
+ next = node->next;
+ pending = (FileAndDirectory *)node->data;
+ if (!still_should_show_file (view, pending->file, pending->directory) || ready_to_load (pending->file)) {
+ if (g_hash_table_lookup (non_ready_files, pending) != NULL) {
+ g_hash_table_remove (non_ready_files, pending);
+ if (still_should_show_file (view, pending->file, pending->directory)) {
+ new_changed_files = g_list_delete_link (new_changed_files, node);
+ old_added_files = g_list_prepend (old_added_files, pending);
+ }
+ } else if (fm_directory_view_should_show_file (view, pending->file)) {
+ new_changed_files = g_list_delete_link (new_changed_files, node);
+ old_changed_files = g_list_prepend (old_changed_files, pending);
+ }
+ }
+ }
+ file_and_directory_list_free (new_changed_files);
+
+ /* If any files were added to old_added_files, then resort it. */
+ if (old_added_files != view->details->old_added_files) {
+ view->details->old_added_files = old_added_files;
+ sort_files (view, &view->details->old_added_files);
+ }
+
+ /* Resort old_changed_files too, since file attributes
+ * relevant to sorting could have changed.
+ */
+ if (old_changed_files != view->details->old_changed_files) {
+ view->details->old_changed_files = old_changed_files;
+ sort_files (view, &view->details->old_changed_files);
+ }
+
+}
+
+static void
+process_old_files (FMDirectoryView *view)
+{
+ GList *files_added, *files_changed, *node;
+ FileAndDirectory *pending;
+ GList *selection, *files;
+ gboolean send_selection_change;
+
+ files_added = view->details->old_added_files;
+ files_changed = view->details->old_changed_files;
+
+ send_selection_change = FALSE;
+
+ if (files_added != NULL || files_changed != NULL) {
+ g_signal_emit (view, signals[BEGIN_FILE_CHANGES], 0);
+
+ for (node = files_added; node != NULL; node = node->next) {
+ pending = node->data;
+ g_signal_emit (view,
+ signals[ADD_FILE], 0, pending->file, pending->directory);
+ }
+
+ for (node = files_changed; node != NULL; node = node->next) {
+ pending = node->data;
+ g_signal_emit (view,
+ signals[still_should_show_file (view, pending->file, pending->directory)
+ ? FILE_CHANGED : REMOVE_FILE], 0,
+ pending->file, pending->directory);
+ }
+
+ g_signal_emit (view, signals[END_FILE_CHANGES], 0);
+
+ if (files_changed != NULL) {
+ selection = fm_directory_view_get_selection (view);
+ files = file_and_directory_list_to_files (files_changed);
+ send_selection_change = eel_g_lists_sort_and_check_for_intersection
+ (&files, &selection);
+ caja_file_list_free (files);
+ caja_file_list_free (selection);
+ }
+
+ file_and_directory_list_free (view->details->old_added_files);
+ view->details->old_added_files = NULL;
+
+ file_and_directory_list_free (view->details->old_changed_files);
+ view->details->old_changed_files = NULL;
+ }
+
+ if (send_selection_change) {
+ /* Send a selection change since some file names could
+ * have changed.
+ */
+ fm_directory_view_send_selection_change (view);
+ }
+}
+
+static void
+display_pending_files (FMDirectoryView *view)
+{
+
+ /* Don't dispatch any updates while the view is frozen. */
+ if (view->details->updates_frozen) {
+ return;
+ }
+
+ process_new_files (view);
+ process_old_files (view);
+
+ if (view->details->model != NULL
+ && caja_directory_are_all_files_seen (view->details->model)
+ && g_hash_table_size (view->details->non_ready_files) == 0) {
+ done_loading (view, TRUE);
+ }
+}
+
+void
+fm_directory_view_freeze_updates (FMDirectoryView *view)
+{
+ view->details->updates_frozen = TRUE;
+ view->details->updates_queued = 0;
+ view->details->needs_reload = FALSE;
+}
+
+void
+fm_directory_view_unfreeze_updates (FMDirectoryView *view)
+{
+ view->details->updates_frozen = FALSE;
+
+ if (view->details->needs_reload) {
+ view->details->needs_reload = FALSE;
+ if (view->details->model != NULL) {
+ load_directory (view, view->details->model);
+ }
+ } else {
+ schedule_idle_display_of_pending_files (view);
+ }
+}
+
+static gboolean
+display_selection_info_idle_callback (gpointer data)
+{
+ FMDirectoryView *view;
+
+ view = FM_DIRECTORY_VIEW (data);
+
+ g_object_ref (G_OBJECT (view));
+
+ view->details->display_selection_idle_id = 0;
+ fm_directory_view_display_selection_info (view);
+ if (view->details->send_selection_change_to_shell) {
+ fm_directory_view_send_selection_change (view);
+ }
+
+ g_object_unref (G_OBJECT (view));
+
+ return FALSE;
+}
+
+static void
+remove_update_menus_timeout_callback (FMDirectoryView *view)
+{
+ if (view->details->update_menus_timeout_id != 0) {
+ g_source_remove (view->details->update_menus_timeout_id);
+ view->details->update_menus_timeout_id = 0;
+ }
+}
+
+static void
+update_menus_if_pending (FMDirectoryView *view)
+{
+ if (!view->details->menu_states_untrustworthy) {
+ return;
+ }
+
+ remove_update_menus_timeout_callback (view);
+ fm_directory_view_update_menus (view);
+}
+
+static gboolean
+update_menus_timeout_callback (gpointer data)
+{
+ FMDirectoryView *view;
+
+ view = FM_DIRECTORY_VIEW (data);
+
+ g_object_ref (G_OBJECT (view));
+
+ view->details->update_menus_timeout_id = 0;
+ fm_directory_view_update_menus (view);
+
+ g_object_unref (G_OBJECT (view));
+
+ return FALSE;
+}
+
+static gboolean
+display_pending_callback (gpointer data)
+{
+ FMDirectoryView *view;
+
+ view = FM_DIRECTORY_VIEW (data);
+
+ g_object_ref (G_OBJECT (view));
+
+ view->details->display_pending_source_id = 0;
+
+ display_pending_files (view);
+
+ g_object_unref (G_OBJECT (view));
+
+ return FALSE;
+}
+
+static void
+schedule_idle_display_of_pending_files (FMDirectoryView *view)
+{
+ /* Get rid of a pending source as it might be a timeout */
+ unschedule_display_of_pending_files (view);
+
+ /* We want higher priority than the idle that handles the relayout
+ to avoid a resort on each add. But we still want to allow repaints
+ and other hight prio events while we have pending files to show. */
+ view->details->display_pending_source_id =
+ g_idle_add_full (G_PRIORITY_DEFAULT_IDLE - 20,
+ display_pending_callback, view, NULL);
+}
+
+static void
+schedule_timeout_display_of_pending_files (FMDirectoryView *view, guint interval)
+{
+ /* No need to schedule an update if there's already one pending. */
+ if (view->details->display_pending_source_id != 0) {
+ return;
+ }
+
+ view->details->display_pending_source_id =
+ g_timeout_add (interval, display_pending_callback, view);
+}
+
+static void
+unschedule_display_of_pending_files (FMDirectoryView *view)
+{
+ /* Get rid of source if it's active. */
+ if (view->details->display_pending_source_id != 0) {
+ g_source_remove (view->details->display_pending_source_id);
+ view->details->display_pending_source_id = 0;
+ }
+}
+
+static void
+queue_pending_files (FMDirectoryView *view,
+ CajaDirectory *directory,
+ GList *files,
+ GList **pending_list)
+{
+ if (files == NULL) {
+ return;
+ }
+
+ /* Don't queue any more updates if we need to reload anyway */
+ if (view->details->needs_reload) {
+ return;
+ }
+
+ if (view->details->updates_frozen) {
+ view->details->updates_queued += g_list_length (files);
+ /* Mark the directory for reload when there are too much queued
+ * changes to prevent the pending list from growing infinitely.
+ */
+ if (view->details->updates_queued > MAX_QUEUED_UPDATES) {
+ view->details->needs_reload = TRUE;
+ return;
+ }
+ }
+
+
+
+ *pending_list = g_list_concat (file_and_directory_list_from_files (directory, files),
+ *pending_list);
+
+ if (! view->details->loading || caja_directory_are_all_files_seen (directory)) {
+ schedule_timeout_display_of_pending_files (view, view->details->update_interval);
+ }
+}
+
+static void
+remove_changes_timeout_callback (FMDirectoryView *view)
+{
+ if (view->details->changes_timeout_id != 0) {
+ g_source_remove (view->details->changes_timeout_id);
+ view->details->changes_timeout_id = 0;
+ }
+}
+
+static void
+reset_update_interval (FMDirectoryView *view)
+{
+ view->details->update_interval = UPDATE_INTERVAL_MIN;
+ remove_changes_timeout_callback (view);
+ /* Reschedule a pending timeout to idle */
+ if (view->details->display_pending_source_id != 0) {
+ schedule_idle_display_of_pending_files (view);
+ }
+}
+
+static gboolean
+changes_timeout_callback (gpointer data)
+{
+ gint64 now;
+ gint64 time_delta;
+ gboolean ret;
+ FMDirectoryView *view;
+
+ view = FM_DIRECTORY_VIEW (data);
+
+ g_object_ref (G_OBJECT (view));
+
+ now = eel_get_system_time();
+ time_delta = now - view->details->last_queued;
+
+ if (time_delta < UPDATE_INTERVAL_RESET*1000) {
+ if (view->details->update_interval < UPDATE_INTERVAL_MAX &&
+ view->details->loading) {
+ /* Increase */
+ view->details->update_interval += UPDATE_INTERVAL_INC;
+ }
+ ret = TRUE;
+ } else {
+ /* Reset */
+ reset_update_interval (view);
+ ret = FALSE;
+ }
+
+ g_object_unref (G_OBJECT (view));
+
+ return ret;
+}
+
+static void
+schedule_changes (FMDirectoryView *view)
+{
+ /* Remember when the change was queued */
+ view->details->last_queued = eel_get_system_time();
+
+ /* No need to schedule if there are already changes pending or during loading */
+ if (view->details->changes_timeout_id != 0 ||
+ view->details->loading) {
+ return;
+ }
+
+ view->details->changes_timeout_id =
+ g_timeout_add (UPDATE_INTERVAL_TIMEOUT_INTERVAL, changes_timeout_callback, view);
+}
+
+static void
+files_added_callback (CajaDirectory *directory,
+ GList *files,
+ gpointer callback_data)
+{
+ FMDirectoryView *view;
+ GtkWindow *window;
+ char *uri;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+
+ window = fm_directory_view_get_containing_window (view);
+ uri = fm_directory_view_get_uri (view);
+ caja_debug_log_with_file_list (FALSE, CAJA_DEBUG_LOG_DOMAIN_ASYNC, files,
+ "files added in window %p: %s",
+ window,
+ uri ? uri : "(no directory)");
+ g_free (uri);
+
+ schedule_changes (view);
+
+ queue_pending_files (view, directory, files, &view->details->new_added_files);
+
+ /* The number of items could have changed */
+ schedule_update_status (view);
+}
+
+static void
+files_changed_callback (CajaDirectory *directory,
+ GList *files,
+ gpointer callback_data)
+{
+ FMDirectoryView *view;
+ GtkWindow *window;
+ char *uri;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+
+ window = fm_directory_view_get_containing_window (view);
+ uri = fm_directory_view_get_uri (view);
+ caja_debug_log_with_file_list (FALSE, CAJA_DEBUG_LOG_DOMAIN_ASYNC, files,
+ "files changed in window %p: %s",
+ window,
+ uri ? uri : "(no directory)");
+ g_free (uri);
+
+ schedule_changes (view);
+
+ queue_pending_files (view, directory, files, &view->details->new_changed_files);
+
+ /* The free space or the number of items could have changed */
+ schedule_update_status (view);
+
+ /* A change in MIME type could affect the Open with menu, for
+ * one thing, so we need to update menus when files change.
+ */
+ schedule_update_menus (view);
+}
+
+static void
+done_loading_callback (CajaDirectory *directory,
+ gpointer callback_data)
+{
+ FMDirectoryView *view;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+
+ process_new_files (view);
+ if (g_hash_table_size (view->details->non_ready_files) == 0) {
+ /* Unschedule a pending update and schedule a new one with the minimal
+ * update interval. This gives the view a short chance at gathering the
+ * (cached) deep counts.
+ */
+ unschedule_display_of_pending_files (view);
+ schedule_timeout_display_of_pending_files (view, UPDATE_INTERVAL_MIN);
+ }
+}
+
+static void
+load_error_callback (CajaDirectory *directory,
+ GError *error,
+ gpointer callback_data)
+{
+ FMDirectoryView *view;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+
+ /* FIXME: By doing a stop, we discard some pending files. Is
+ * that OK?
+ */
+ fm_directory_view_stop (view);
+
+ /* Emit a signal to tell subclasses that a load error has
+ * occurred, so they can handle it in the UI.
+ */
+ g_signal_emit (view,
+ signals[LOAD_ERROR], 0, error);
+}
+
+static void
+real_load_error (FMDirectoryView *view, GError *error)
+{
+ /* Report only one error per failed directory load (from the UI
+ * point of view, not from the CajaDirectory point of view).
+ * Otherwise you can get multiple identical errors caused by
+ * unrelated code that just happens to try to iterate this
+ * directory.
+ */
+ if (!view->details->reported_load_error) {
+ fm_report_error_loading_directory
+ (fm_directory_view_get_directory_as_file (view),
+ error,
+ fm_directory_view_get_containing_window (view));
+ }
+ view->details->reported_load_error = TRUE;
+}
+
+void
+fm_directory_view_add_subdirectory (FMDirectoryView *view,
+ CajaDirectory*directory)
+{
+ CajaFileAttributes attributes;
+
+ g_assert (!g_list_find (view->details->subdirectory_list, directory));
+
+ caja_directory_ref (directory);
+
+ attributes =
+ CAJA_FILE_ATTRIBUTES_FOR_ICON |
+ CAJA_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT |
+ CAJA_FILE_ATTRIBUTE_INFO |
+ CAJA_FILE_ATTRIBUTE_LINK_INFO |
+ CAJA_FILE_ATTRIBUTE_MOUNT |
+ CAJA_FILE_ATTRIBUTE_EXTENSION_INFO;
+
+ caja_directory_file_monitor_add (directory,
+ &view->details->model,
+ view->details->show_hidden_files,
+ view->details->show_backup_files,
+ attributes,
+ files_added_callback, view);
+
+ g_signal_connect
+ (directory, "files_added",
+ G_CALLBACK (files_added_callback), view);
+ g_signal_connect
+ (directory, "files_changed",
+ G_CALLBACK (files_changed_callback), view);
+
+ view->details->subdirectory_list = g_list_prepend (
+ view->details->subdirectory_list, directory);
+}
+
+void
+fm_directory_view_remove_subdirectory (FMDirectoryView *view,
+ CajaDirectory*directory)
+{
+ g_assert (g_list_find (view->details->subdirectory_list, directory));
+
+ view->details->subdirectory_list = g_list_remove (
+ view->details->subdirectory_list, directory);
+
+ g_signal_handlers_disconnect_by_func (directory,
+ G_CALLBACK (files_added_callback),
+ view);
+ g_signal_handlers_disconnect_by_func (directory,
+ G_CALLBACK (files_changed_callback),
+ view);
+
+ caja_directory_file_monitor_remove (directory, &view->details->model);
+
+ caja_directory_unref (directory);
+}
+
+/**
+ * fm_directory_view_clear:
+ *
+ * Emit the signal to clear the contents of the view. Subclasses must
+ * override the signal handler for this signal. This is normally called
+ * only by FMDirectoryView.
+ * @view: FMDirectoryView to empty.
+ *
+ **/
+void
+fm_directory_view_clear (FMDirectoryView *view)
+{
+ g_return_if_fail (FM_IS_DIRECTORY_VIEW (view));
+
+ g_signal_emit (view, signals[CLEAR], 0);
+}
+
+/**
+ * fm_directory_view_begin_loading:
+ *
+ * Emit the signal to prepare for loading the contents of a new location.
+ * Subclasses might want to override the signal handler for this signal.
+ * This is normally called only by FMDirectoryView.
+ * @view: FMDirectoryView that is switching to view a new location.
+ *
+ **/
+void
+fm_directory_view_begin_loading (FMDirectoryView *view)
+{
+ g_return_if_fail (FM_IS_DIRECTORY_VIEW (view));
+
+ g_signal_emit (view, signals[BEGIN_LOADING], 0);
+}
+
+/**
+ * fm_directory_view_end_loading:
+ *
+ * Emit the signal after loading the contents of a new location.
+ * Subclasses might want to override the signal handler for this signal.
+ * This is normally called only by FMDirectoryView.
+ * @view: FMDirectoryView that is switching to view a new location.
+ *
+ **/
+void
+fm_directory_view_end_loading (FMDirectoryView *view,
+ gboolean all_files_seen)
+{
+ g_return_if_fail (FM_IS_DIRECTORY_VIEW (view));
+
+ g_signal_emit (view, signals[END_LOADING], 0, all_files_seen);
+}
+
+/**
+ * fm_directory_view_get_loading:
+ * @view: an #FMDirectoryView.
+ *
+ * Return value: #gboolean inicating whether @view is currently loaded.
+ *
+ **/
+gboolean
+fm_directory_view_get_loading (FMDirectoryView *view)
+{
+ g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE);
+
+ return view->details->loading;
+}
+
+/**
+ * fm_directory_view_bump_zoom_level:
+ *
+ * bump the current zoom level by invoking the relevant subclass through the slot
+ *
+ **/
+void
+fm_directory_view_bump_zoom_level (FMDirectoryView *view, int zoom_increment)
+{
+ g_return_if_fail (FM_IS_DIRECTORY_VIEW (view));
+
+ if (!fm_directory_view_supports_zooming (view)) {
+ return;
+ }
+
+ EEL_CALL_METHOD
+ (FM_DIRECTORY_VIEW_CLASS, view,
+ bump_zoom_level, (view, zoom_increment));
+}
+
+/**
+ * fm_directory_view_zoom_to_level:
+ *
+ * Set the current zoom level by invoking the relevant subclass through the slot
+ *
+ **/
+void
+fm_directory_view_zoom_to_level (FMDirectoryView *view,
+ CajaZoomLevel zoom_level)
+{
+ g_return_if_fail (FM_IS_DIRECTORY_VIEW (view));
+
+ if (!fm_directory_view_supports_zooming (view)) {
+ return;
+ }
+
+ EEL_CALL_METHOD
+ (FM_DIRECTORY_VIEW_CLASS, view,
+ zoom_to_level, (view, zoom_level));
+}
+
+
+CajaZoomLevel
+fm_directory_view_get_zoom_level (FMDirectoryView *view)
+{
+ g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), CAJA_ZOOM_LEVEL_STANDARD);
+
+ if (!fm_directory_view_supports_zooming (view)) {
+ return CAJA_ZOOM_LEVEL_STANDARD;
+ }
+
+ return EEL_CALL_METHOD_WITH_RETURN_VALUE
+ (FM_DIRECTORY_VIEW_CLASS, view,
+ get_zoom_level, (view));
+}
+
+/**
+ * fm_directory_view_restore_default_zoom_level:
+ *
+ * restore to the default zoom level by invoking the relevant subclass through the slot
+ *
+ **/
+void
+fm_directory_view_restore_default_zoom_level (FMDirectoryView *view)
+{
+ g_return_if_fail (FM_IS_DIRECTORY_VIEW (view));
+
+ if (!fm_directory_view_supports_zooming (view)) {
+ return;
+ }
+
+ EEL_CALL_METHOD
+ (FM_DIRECTORY_VIEW_CLASS, view,
+ restore_default_zoom_level, (view));
+}
+
+/**
+ * fm_directory_view_can_zoom_in:
+ *
+ * Determine whether the view can be zoomed any closer.
+ * @view: The zoomable FMDirectoryView.
+ *
+ * Return value: TRUE if @view can be zoomed any closer, FALSE otherwise.
+ *
+ **/
+gboolean
+fm_directory_view_can_zoom_in (FMDirectoryView *view)
+{
+ g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE);
+
+ if (!fm_directory_view_supports_zooming (view)) {
+ return FALSE;
+ }
+
+ return EEL_CALL_METHOD_WITH_RETURN_VALUE
+ (FM_DIRECTORY_VIEW_CLASS, view,
+ can_zoom_in, (view));
+}
+
+/**
+ * fm_directory_view_can_rename_file
+ *
+ * Determine whether a file can be renamed.
+ * @file: A CajaFile
+ *
+ * Return value: TRUE if @file can be renamed, FALSE otherwise.
+ *
+ **/
+static gboolean
+fm_directory_view_can_rename_file (FMDirectoryView *view, CajaFile *file)
+{
+ return EEL_CALL_METHOD_WITH_RETURN_VALUE
+ (FM_DIRECTORY_VIEW_CLASS, view,
+ can_rename_file, (view, file));
+}
+
+/**
+ * fm_directory_view_can_zoom_out:
+ *
+ * Determine whether the view can be zoomed any further away.
+ * @view: The zoomable FMDirectoryView.
+ *
+ * Return value: TRUE if @view can be zoomed any further away, FALSE otherwise.
+ *
+ **/
+gboolean
+fm_directory_view_can_zoom_out (FMDirectoryView *view)
+{
+ g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE);
+
+ if (!fm_directory_view_supports_zooming (view)) {
+ return FALSE;
+ }
+
+ return EEL_CALL_METHOD_WITH_RETURN_VALUE
+ (FM_DIRECTORY_VIEW_CLASS, view,
+ can_zoom_out, (view));
+}
+
+GtkWidget *
+fm_directory_view_get_background_widget (FMDirectoryView *view)
+{
+ g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), NULL);
+
+ return EEL_CALL_METHOD_WITH_RETURN_VALUE
+ (FM_DIRECTORY_VIEW_CLASS, view,
+ get_background_widget, (view));
+}
+
+EelBackground *
+fm_directory_view_get_background (FMDirectoryView *view)
+{
+ return eel_get_widget_background (fm_directory_view_get_background_widget (view));
+}
+
+static void
+real_set_is_active (FMDirectoryView *view,
+ gboolean is_active)
+{
+ EelBackground *bg;
+
+ bg = fm_directory_view_get_background (view);
+ eel_background_set_active (bg, is_active);
+}
+
+static void
+fm_directory_view_set_is_active (FMDirectoryView *view,
+ gboolean is_active)
+{
+ g_return_if_fail (FM_IS_DIRECTORY_VIEW (view));
+
+ EEL_CALL_METHOD (FM_DIRECTORY_VIEW_CLASS, view,
+ set_is_active, (view, is_active));
+}
+
+/**
+ * fm_directory_view_get_selection:
+ *
+ * Get a list of CajaFile pointers that represents the
+ * currently-selected items in this view. Subclasses must override
+ * the signal handler for the 'get_selection' signal. Callers are
+ * responsible for g_free-ing the list (but not its data).
+ * @view: FMDirectoryView whose selected items are of interest.
+ *
+ * Return value: GList of CajaFile pointers representing the selection.
+ *
+ **/
+GList *
+fm_directory_view_get_selection (FMDirectoryView *view)
+{
+ g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), NULL);
+
+ return EEL_CALL_METHOD_WITH_RETURN_VALUE
+ (FM_DIRECTORY_VIEW_CLASS, view,
+ get_selection, (view));
+}
+
+void
+fm_directory_view_invert_selection (FMDirectoryView *view)
+{
+ g_return_if_fail (FM_IS_DIRECTORY_VIEW (view));
+
+ EEL_CALL_METHOD
+ (FM_DIRECTORY_VIEW_CLASS, view,
+ invert_selection, (view));
+}
+
+GList *
+fm_directory_view_get_selection_for_file_transfer (FMDirectoryView *view)
+{
+ g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), NULL);
+
+ return EEL_CALL_METHOD_WITH_RETURN_VALUE
+ (FM_DIRECTORY_VIEW_CLASS, view,
+ get_selection_for_file_transfer, (view));
+}
+
+guint
+fm_directory_view_get_item_count (FMDirectoryView *view)
+{
+ g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), 0);
+
+ return EEL_CALL_METHOD_WITH_RETURN_VALUE
+ (FM_DIRECTORY_VIEW_CLASS, view,
+ get_item_count, (view));
+}
+
+GtkUIManager *
+fm_directory_view_get_ui_manager (FMDirectoryView *view)
+{
+ if (view->details->window == NULL) {
+ return NULL;
+ }
+ return caja_window_info_get_ui_manager (view->details->window);
+}
+
+/**
+ * fm_directory_view_get_model:
+ *
+ * Get the model for this FMDirectoryView.
+ * @view: FMDirectoryView of interest.
+ *
+ * Return value: CajaDirectory for this view.
+ *
+ **/
+CajaDirectory *
+fm_directory_view_get_model (FMDirectoryView *view)
+{
+ g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), NULL);
+
+ return view->details->model;
+}
+
+GdkAtom
+fm_directory_view_get_copied_files_atom (FMDirectoryView *view)
+{
+ g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), GDK_NONE);
+
+ return copied_files_atom;
+}
+
+static void
+prepend_uri_one (gpointer data, gpointer callback_data)
+{
+ CajaFile *file;
+ GList **result;
+
+ g_assert (CAJA_IS_FILE (data));
+ g_assert (callback_data != NULL);
+
+ result = (GList **) callback_data;
+ file = (CajaFile *) data;
+ *result = g_list_prepend (*result, caja_file_get_uri (file));
+}
+
+static void
+offset_drop_points (GArray *relative_item_points,
+ int x_offset, int y_offset)
+{
+ guint index;
+
+ if (relative_item_points == NULL) {
+ return;
+ }
+
+ for (index = 0; index < relative_item_points->len; index++) {
+ g_array_index (relative_item_points, GdkPoint, index).x += x_offset;
+ g_array_index (relative_item_points, GdkPoint, index).y += y_offset;
+ }
+}
+
+static void
+fm_directory_view_create_links_for_files (FMDirectoryView *view, GList *files,
+ GArray *relative_item_points)
+{
+ GList *uris;
+ char *dir_uri;
+ CopyMoveDoneData *copy_move_done_data;
+ g_assert (relative_item_points->len == 0
+ || g_list_length (files) == relative_item_points->len);
+
+ g_assert (FM_IS_DIRECTORY_VIEW (view));
+ g_assert (files != NULL);
+
+ /* create a list of URIs */
+ uris = NULL;
+ g_list_foreach (files, prepend_uri_one, &uris);
+ uris = g_list_reverse (uris);
+
+ g_assert (g_list_length (uris) == g_list_length (files));
+
+ /* offset the drop locations a bit so that we don't pile
+ * up the icons on top of each other
+ */
+ offset_drop_points (relative_item_points,
+ DUPLICATE_HORIZONTAL_ICON_OFFSET,
+ DUPLICATE_VERTICAL_ICON_OFFSET);
+
+ copy_move_done_data = pre_copy_move (view);
+ dir_uri = fm_directory_view_get_backing_uri (view);
+ caja_file_operations_copy_move (uris, relative_item_points, dir_uri, GDK_ACTION_LINK,
+ GTK_WIDGET (view), copy_move_done_callback, copy_move_done_data);
+ g_free (dir_uri);
+ eel_g_list_free_deep (uris);
+}
+
+static void
+fm_directory_view_duplicate_selection (FMDirectoryView *view, GList *files,
+ GArray *relative_item_points)
+{
+ GList *uris;
+ CopyMoveDoneData *copy_move_done_data;
+
+ g_assert (FM_IS_DIRECTORY_VIEW (view));
+ g_assert (files != NULL);
+ g_assert (g_list_length (files) == relative_item_points->len
+ || relative_item_points->len == 0);
+
+ /* create a list of URIs */
+ uris = NULL;
+ g_list_foreach (files, prepend_uri_one, &uris);
+ uris = g_list_reverse (uris);
+
+ g_assert (g_list_length (uris) == g_list_length (files));
+
+ /* offset the drop locations a bit so that we don't pile
+ * up the icons on top of each other
+ */
+ offset_drop_points (relative_item_points,
+ DUPLICATE_HORIZONTAL_ICON_OFFSET,
+ DUPLICATE_VERTICAL_ICON_OFFSET);
+
+ copy_move_done_data = pre_copy_move (view);
+ caja_file_operations_copy_move (uris, relative_item_points, NULL, GDK_ACTION_COPY,
+ GTK_WIDGET (view), copy_move_done_callback, copy_move_done_data);
+ eel_g_list_free_deep (uris);
+}
+
+/* special_link_in_selection
+ *
+ * Return TRUE if one of our special links is in the selection.
+ * Special links include the following:
+ * CAJA_DESKTOP_LINK_TRASH, CAJA_DESKTOP_LINK_HOME, CAJA_DESKTOP_LINK_MOUNT
+ */
+
+static gboolean
+special_link_in_selection (FMDirectoryView *view)
+{
+ gboolean saw_link;
+ GList *selection, *node;
+ CajaFile *file;
+
+ g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE);
+
+ saw_link = FALSE;
+
+ selection = fm_directory_view_get_selection (FM_DIRECTORY_VIEW (view));
+
+ for (node = selection; node != NULL; node = node->next) {
+ file = CAJA_FILE (node->data);
+
+ saw_link = CAJA_IS_DESKTOP_ICON_FILE (file);
+
+ if (saw_link) {
+ break;
+ }
+ }
+
+ caja_file_list_free (selection);
+
+ return saw_link;
+}
+
+/* desktop_or_home_dir_in_selection
+ *
+ * Return TRUE if either the desktop or the home directory is in the selection.
+ */
+
+static gboolean
+desktop_or_home_dir_in_selection (FMDirectoryView *view)
+{
+ gboolean saw_desktop_or_home_dir;
+ GList *selection, *node;
+ CajaFile *file;
+
+ g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE);
+
+ saw_desktop_or_home_dir = FALSE;
+
+ selection = fm_directory_view_get_selection (FM_DIRECTORY_VIEW (view));
+
+ for (node = selection; node != NULL; node = node->next) {
+ file = CAJA_FILE (node->data);
+
+ saw_desktop_or_home_dir =
+ caja_file_is_home (file)
+ || caja_file_is_desktop_directory (file);
+
+ if (saw_desktop_or_home_dir) {
+ break;
+ }
+ }
+
+ caja_file_list_free (selection);
+
+ return saw_desktop_or_home_dir;
+}
+
+static void
+trash_or_delete_done_cb (GHashTable *debuting_uris,
+ gboolean user_cancel,
+ FMDirectoryView *view)
+{
+ if (user_cancel) {
+ view->details->selection_was_removed = FALSE;
+ }
+}
+
+static void
+trash_or_delete_files (GtkWindow *parent_window,
+ const GList *files,
+ gboolean delete_if_all_already_in_trash,
+ FMDirectoryView *view)
+{
+ GList *locations;
+ const GList *node;
+
+ locations = NULL;
+ for (node = files; node != NULL; node = node->next) {
+ locations = g_list_prepend (locations,
+ caja_file_get_location ((CajaFile *) node->data));
+ }
+
+ locations = g_list_reverse (locations);
+
+ caja_file_operations_trash_or_delete (locations,
+ parent_window,
+ (CajaDeleteCallback) trash_or_delete_done_cb,
+ view);
+ eel_g_object_list_free (locations);
+}
+
+static gboolean
+can_rename_file (FMDirectoryView *view, CajaFile *file)
+{
+ return caja_file_can_rename (file);
+}
+
+static void
+start_renaming_file (FMDirectoryView *view,
+ CajaFile *file,
+ gboolean select_all)
+{
+ if (file != NULL) {
+ fm_directory_view_select_file (view, file);
+ }
+}
+
+typedef struct {
+ FMDirectoryView *view;
+ CajaFile *new_file;
+} RenameData;
+
+static gboolean
+delayed_rename_file_hack_callback (RenameData *data)
+{
+ FMDirectoryView *view;
+ CajaFile *new_file;
+
+ view = data->view;
+ new_file = data->new_file;
+
+ if (view->details->window != NULL &&
+ view->details->active) {
+ EEL_CALL_METHOD (FM_DIRECTORY_VIEW_CLASS, view, start_renaming_file, (view, new_file, FALSE));
+ fm_directory_view_reveal_selection (view);
+ }
+
+ return FALSE;
+}
+
+static void
+delayed_rename_file_hack_removed (RenameData *data)
+{
+ g_object_unref (data->view);
+ caja_file_unref (data->new_file);
+ g_free (data);
+}
+
+
+static void
+rename_file (FMDirectoryView *view, CajaFile *new_file)
+{
+ RenameData *data;
+
+ /* HACK!!!!
+ This is a work around bug in listview. After the rename is
+ enabled we will get file changes due to info about the new
+ file being read, which will cause the model to change. When
+ the model changes GtkTreeView clears the editing. This hack just
+ delays editing for some time to try to avoid this problem.
+ A major problem is that the selection of the row causes us
+ to load the slow mimetype for the file, which leads to a
+ file_changed. So, before we delay we select the row.
+ */
+ if (FM_IS_LIST_VIEW (view)) {
+ fm_directory_view_select_file (view, new_file);
+
+ data = g_new (RenameData, 1);
+ data->view = g_object_ref (view);
+ data->new_file = caja_file_ref (new_file);
+ if (view->details->delayed_rename_file_id != 0) {
+ g_source_remove (view->details->delayed_rename_file_id);
+ }
+ view->details->delayed_rename_file_id =
+ g_timeout_add_full (G_PRIORITY_DEFAULT,
+ 100, (GSourceFunc)delayed_rename_file_hack_callback,
+ data, (GDestroyNotify) delayed_rename_file_hack_removed);
+
+ return;
+ }
+
+ /* no need to select because start_renaming_file selects
+ * fm_directory_view_select_file (view, new_file);
+ */
+ EEL_CALL_METHOD (FM_DIRECTORY_VIEW_CLASS, view, start_renaming_file, (view, new_file, FALSE));
+ fm_directory_view_reveal_selection (view);
+}
+
+static void
+reveal_newly_added_folder (FMDirectoryView *view, CajaFile *new_file,
+ CajaDirectory *directory, GFile *target_location)
+{
+ GFile *location;
+
+ location = caja_file_get_location (new_file);
+ if (g_file_equal (location, target_location)) {
+ g_signal_handlers_disconnect_by_func (view,
+ G_CALLBACK (reveal_newly_added_folder),
+ (void *) target_location);
+ rename_file (view, new_file);
+ }
+ g_object_unref (location);
+}
+
+typedef struct {
+ FMDirectoryView *directory_view;
+ GHashTable *added_locations;
+} NewFolderData;
+
+
+static void
+track_newly_added_locations (FMDirectoryView *view, CajaFile *new_file,
+ CajaDirectory *directory, gpointer user_data)
+{
+ NewFolderData *data;
+
+ data = user_data;
+
+ g_hash_table_insert (data->added_locations, caja_file_get_location (new_file), NULL);
+}
+
+static void
+new_folder_done (GFile *new_folder, gpointer user_data)
+{
+ FMDirectoryView *directory_view;
+ CajaFile *file;
+ char screen_string[32];
+ GdkScreen *screen;
+ NewFolderData *data;
+
+ data = (NewFolderData *)user_data;
+
+ directory_view = data->directory_view;
+
+ if (directory_view == NULL) {
+ goto fail;
+ }
+
+ g_signal_handlers_disconnect_by_func (directory_view,
+ G_CALLBACK (track_newly_added_locations),
+ (void *) data);
+
+ if (new_folder == NULL) {
+ goto fail;
+ }
+
+ screen = gtk_widget_get_screen (GTK_WIDGET (directory_view));
+ g_snprintf (screen_string, sizeof (screen_string), "%d", gdk_screen_get_number (screen));
+
+
+ file = caja_file_get (new_folder);
+ caja_file_set_metadata
+ (file, CAJA_METADATA_KEY_SCREEN,
+ NULL,
+ screen_string);
+
+ if (g_hash_table_lookup_extended (data->added_locations, new_folder, NULL, NULL)) {
+ /* The file was already added */
+ rename_file (directory_view, file);
+ } else {
+ /* We need to run after the default handler adds the folder we want to
+ * operate on. The ADD_FILE signal is registered as G_SIGNAL_RUN_LAST, so we
+ * must use connect_after.
+ */
+ g_signal_connect_data (directory_view,
+ "add_file",
+ G_CALLBACK (reveal_newly_added_folder),
+ g_object_ref (new_folder),
+ (GClosureNotify)g_object_unref,
+ G_CONNECT_AFTER);
+ }
+ caja_file_unref (file);
+
+ fail:
+ g_hash_table_destroy (data->added_locations);
+ eel_remove_weak_pointer (&data->directory_view);
+ g_free (data);
+}
+
+
+static NewFolderData *
+new_folder_data_new (FMDirectoryView *directory_view)
+{
+ NewFolderData *data;
+
+ data = g_new (NewFolderData, 1);
+ data->directory_view = directory_view;
+ data->added_locations = g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal,
+ g_object_unref, NULL);
+ eel_add_weak_pointer (&data->directory_view);
+
+ return data;
+}
+
+static GdkPoint *
+context_menu_to_file_operation_position (FMDirectoryView *directory_view)
+{
+ g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (directory_view), NULL);
+
+ if (fm_directory_view_using_manual_layout (directory_view)
+ && directory_view->details->context_menu_position.x >= 0
+ && directory_view->details->context_menu_position.y >= 0) {
+ EEL_CALL_METHOD (FM_DIRECTORY_VIEW_CLASS, directory_view,
+ widget_to_file_operation_position,
+ (directory_view, &directory_view->details->context_menu_position));
+ return &directory_view->details->context_menu_position;
+ } else {
+ return NULL;
+ }
+}
+
+static void
+update_context_menu_position_from_event (FMDirectoryView *view,
+ GdkEventButton *event)
+{
+ g_return_if_fail (FM_IS_DIRECTORY_VIEW (view));
+
+ if (event != NULL) {
+ view->details->context_menu_position.x = event->x;
+ view->details->context_menu_position.y = event->y;
+ } else {
+ view->details->context_menu_position.x = -1;
+ view->details->context_menu_position.y = -1;
+ }
+}
+
+void
+fm_directory_view_new_folder (FMDirectoryView *directory_view)
+{
+ char *parent_uri;
+ NewFolderData *data;
+ GdkPoint *pos;
+
+ data = new_folder_data_new (directory_view);
+
+ g_signal_connect_data (directory_view,
+ "add_file",
+ G_CALLBACK (track_newly_added_locations),
+ data,
+ (GClosureNotify)NULL,
+ G_CONNECT_AFTER);
+
+ pos = context_menu_to_file_operation_position (directory_view);
+
+ parent_uri = fm_directory_view_get_backing_uri (directory_view);
+ caja_file_operations_new_folder (GTK_WIDGET (directory_view),
+ pos, parent_uri,
+ new_folder_done, data);
+
+ g_free (parent_uri);
+}
+
+static NewFolderData *
+setup_new_folder_data (FMDirectoryView *directory_view)
+{
+ NewFolderData *data;
+
+ data = new_folder_data_new (directory_view);
+
+ g_signal_connect_data (directory_view,
+ "add_file",
+ G_CALLBACK (track_newly_added_locations),
+ data,
+ (GClosureNotify)NULL,
+ G_CONNECT_AFTER);
+
+ return data;
+}
+
+static void
+fm_directory_view_new_file_with_initial_contents (FMDirectoryView *directory_view,
+ const char *parent_uri,
+ const char *filename,
+ const char *initial_contents,
+ int length,
+ GdkPoint *pos)
+{
+ NewFolderData *data;
+
+ g_assert (parent_uri != NULL);
+
+ data = setup_new_folder_data (directory_view);
+
+ if (pos == NULL) {
+ pos = context_menu_to_file_operation_position (directory_view);
+ }
+
+ caja_file_operations_new_file (GTK_WIDGET (directory_view),
+ pos, parent_uri, filename,
+ initial_contents, length,
+ new_folder_done, data);
+}
+
+void
+fm_directory_view_new_file (FMDirectoryView *directory_view,
+ const char *parent_uri,
+ CajaFile *source)
+{
+ GdkPoint *pos;
+ NewFolderData *data;
+ char *source_uri;
+ char *container_uri;
+
+ container_uri = NULL;
+ if (parent_uri == NULL) {
+ container_uri = fm_directory_view_get_backing_uri (directory_view);
+ g_assert (container_uri != NULL);
+ }
+
+ if (source == NULL) {
+ fm_directory_view_new_file_with_initial_contents (directory_view,
+ parent_uri != NULL ? parent_uri : container_uri,
+ NULL,
+ NULL,
+ 0,
+ NULL);
+ g_free (container_uri);
+ return;
+ }
+
+ g_return_if_fail (caja_file_is_local (source));
+
+ pos = context_menu_to_file_operation_position (directory_view);
+
+ data = setup_new_folder_data (directory_view);
+
+ source_uri = caja_file_get_uri (source);
+
+ caja_file_operations_new_file_from_template (GTK_WIDGET (directory_view),
+ pos,
+ parent_uri != NULL ? parent_uri : container_uri,
+ NULL,
+ source_uri,
+ new_folder_done, data);
+
+ g_free (source_uri);
+ g_free (container_uri);
+}
+
+/* handle the open command */
+
+static void
+open_one_in_new_window (gpointer data, gpointer callback_data)
+{
+ g_assert (CAJA_IS_FILE (data));
+ g_assert (FM_IS_DIRECTORY_VIEW (callback_data));
+
+ fm_directory_view_activate_file (FM_DIRECTORY_VIEW (callback_data),
+ CAJA_FILE (data),
+ CAJA_WINDOW_OPEN_IN_NAVIGATION,
+ 0);
+}
+
+static void
+open_one_in_folder_window (gpointer data, gpointer callback_data)
+{
+ g_assert (CAJA_IS_FILE (data));
+ g_assert (FM_IS_DIRECTORY_VIEW (callback_data));
+
+ fm_directory_view_activate_file (FM_DIRECTORY_VIEW (callback_data),
+ CAJA_FILE (data),
+ CAJA_WINDOW_OPEN_IN_SPATIAL,
+ 0);
+}
+
+CajaFile *
+fm_directory_view_get_directory_as_file (FMDirectoryView *view)
+{
+ g_assert (FM_IS_DIRECTORY_VIEW (view));
+
+ return view->details->directory_as_file;
+}
+
+static void
+open_with_launch_application_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ ApplicationLaunchParameters *launch_parameters;
+
+ launch_parameters = (ApplicationLaunchParameters *) callback_data;
+ caja_launch_application
+ (launch_parameters->application,
+ launch_parameters->files,
+ fm_directory_view_get_containing_window (launch_parameters->directory_view));
+}
+
+static char *
+escape_action_name (const char *action_name,
+ const char *prefix)
+{
+ GString *s;
+
+ if (action_name == NULL) {
+ return NULL;
+ }
+
+ s = g_string_new (prefix);
+
+ while (*action_name != 0) {
+ switch (*action_name) {
+ case '\\':
+ g_string_append (s, "\\\\");
+ break;
+ case '/':
+ g_string_append (s, "\\s");
+ break;
+ case '&':
+ g_string_append (s, "\\a");
+ break;
+ case '"':
+ g_string_append (s, "\\q");
+ break;
+ default:
+ g_string_append_c (s, *action_name);
+ }
+
+ action_name ++;
+ }
+ return g_string_free (s, FALSE);
+}
+
+static char *
+escape_action_path (const char *action_path)
+{
+ GString *s;
+
+ if (action_path == NULL) {
+ return NULL;
+ }
+
+ s = g_string_sized_new (strlen (action_path) + 2);
+
+ while (*action_path != 0) {
+ switch (*action_path) {
+ case '\\':
+ g_string_append (s, "\\\\");
+ break;
+ case '&':
+ g_string_append (s, "\\a");
+ break;
+ case '"':
+ g_string_append (s, "\\q");
+ break;
+ default:
+ g_string_append_c (s, *action_path);
+ }
+
+ action_path ++;
+ }
+ return g_string_free (s, FALSE);
+}
+
+
+static void
+add_submenu (GtkUIManager *ui_manager,
+ GtkActionGroup *action_group,
+ guint merge_id,
+ const char *parent_path,
+ const char *uri,
+ const char *label,
+ GdkPixbuf *pixbuf,
+ gboolean add_action)
+{
+ char *escaped_label;
+ char *action_name;
+ char *submenu_name;
+ char *escaped_submenu_name;
+ GtkAction *action;
+
+ if (parent_path != NULL) {
+ action_name = escape_action_name (uri, "submenu_");
+ submenu_name = g_path_get_basename (uri);
+ escaped_submenu_name = escape_action_path (submenu_name);
+ escaped_label = eel_str_double_underscores (label);
+
+ if (add_action) {
+ action = gtk_action_new (action_name,
+ escaped_label,
+ NULL,
+ NULL);
+ if (pixbuf != NULL) {
+ g_object_set_data_full (G_OBJECT (action), "menu-icon",
+ g_object_ref (pixbuf),
+ g_object_unref);
+ }
+
+ g_object_set (action, "hide-if-empty", FALSE, NULL);
+
+ gtk_action_group_add_action (action_group,
+ action);
+ g_object_unref (action);
+ }
+
+ gtk_ui_manager_add_ui (ui_manager,
+ merge_id,
+ parent_path,
+ escaped_submenu_name,
+ action_name,
+ GTK_UI_MANAGER_MENU,
+ FALSE);
+ g_free (action_name);
+ g_free (escaped_label);
+ g_free (submenu_name);
+ g_free (escaped_submenu_name);
+ }
+}
+
+static void
+add_application_to_open_with_menu (FMDirectoryView *view,
+ GAppInfo *application,
+ GList *files,
+ int index,
+ const char *menu_placeholder,
+ const char *popup_placeholder,
+ const gboolean submenu)
+{
+ ApplicationLaunchParameters *launch_parameters;
+ char *tip;
+ char *label;
+ char *action_name;
+ char *escaped_app;
+ char *path;
+ GtkAction *action;
+ GIcon *app_icon;
+ GtkWidget *menuitem;
+
+ launch_parameters = application_launch_parameters_new
+ (application, files, view);
+ escaped_app = eel_str_double_underscores (g_app_info_get_display_name (application));
+ if (submenu)
+ label = g_strdup_printf ("%s", escaped_app);
+ else
+ label = g_strdup_printf (_("Open With %s"), escaped_app);
+
+ tip = g_strdup_printf (ngettext ("Use \"%s\" to open the selected item",
+ "Use \"%s\" to open the selected items",
+ g_list_length (files)),
+ escaped_app);
+ g_free (escaped_app);
+
+ action_name = g_strdup_printf ("open_with_%d", index);
+
+ action = gtk_action_new (action_name,
+ label,
+ tip,
+ NULL);
+
+ app_icon = g_app_info_get_icon (application);
+ if (app_icon != NULL) {
+ g_object_ref (app_icon);
+ } else {
+ app_icon = g_themed_icon_new ("application-x-executable");
+ }
+
+ gtk_action_set_gicon (action, app_icon);
+ g_object_unref (app_icon);
+
+ g_signal_connect_data (action, "activate",
+ G_CALLBACK (open_with_launch_application_callback),
+ launch_parameters,
+ (GClosureNotify)application_launch_parameters_free, 0);
+
+ gtk_action_group_add_action (view->details->open_with_action_group,
+ action);
+ g_object_unref (action);
+
+ gtk_ui_manager_add_ui (caja_window_info_get_ui_manager (view->details->window),
+ view->details->open_with_merge_id,
+ menu_placeholder,
+ action_name,
+ action_name,
+ GTK_UI_MANAGER_MENUITEM,
+ FALSE);
+
+ path = g_strdup_printf ("%s/%s", menu_placeholder, action_name);
+ menuitem = gtk_ui_manager_get_widget (
+ caja_window_info_get_ui_manager (view->details->window),
+ path);
+ gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (menuitem), TRUE);
+ g_free (path);
+
+ gtk_ui_manager_add_ui (caja_window_info_get_ui_manager (view->details->window),
+ view->details->open_with_merge_id,
+ popup_placeholder,
+ action_name,
+ action_name,
+ GTK_UI_MANAGER_MENUITEM,
+ FALSE);
+
+ path = g_strdup_printf ("%s/%s", popup_placeholder, action_name);
+ menuitem = gtk_ui_manager_get_widget (
+ caja_window_info_get_ui_manager (view->details->window),
+ path);
+ gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (menuitem), TRUE);
+
+ g_free (path);
+ g_free (action_name);
+ g_free (label);
+ g_free (tip);
+}
+
+static void
+get_x_content_async_callback (char **content,
+ gpointer user_data)
+{
+ FMDirectoryView *view;
+
+ view = FM_DIRECTORY_VIEW (user_data);
+
+ if (view->details->window != NULL) {
+ schedule_update_menus (view);
+ }
+ g_object_unref (view);
+}
+
+static void
+add_x_content_apps (FMDirectoryView *view, CajaFile *file, GList **applications)
+{
+ GMount *mount;
+ char **x_content_types;
+ unsigned int n;
+
+ g_return_if_fail (applications != NULL);
+
+ mount = caja_file_get_mount (file);
+
+ if (mount == NULL) {
+ return;
+ }
+
+ x_content_types = caja_autorun_get_cached_x_content_types_for_mount (mount);
+ if (x_content_types != NULL) {
+ for (n = 0; x_content_types[n] != NULL; n++) {
+ char *x_content_type = x_content_types[n];
+ GList *app_info_for_x_content_type;
+
+ app_info_for_x_content_type = g_app_info_get_all_for_type (x_content_type);
+ *applications = g_list_concat (*applications, app_info_for_x_content_type);
+ }
+ g_strfreev (x_content_types);
+ } else {
+ caja_autorun_get_x_content_types_for_mount_async (mount,
+ get_x_content_async_callback,
+ NULL,
+ g_object_ref (view));
+
+ }
+
+ g_object_unref (mount);
+}
+
+static void
+reset_open_with_menu (FMDirectoryView *view, GList *selection)
+{
+ GList *applications, *node;
+ CajaFile *file;
+ gboolean submenu_visible, filter_default;
+ int num_applications;
+ int index;
+ gboolean other_applications_visible;
+ gboolean open_with_chooser_visible;
+ GtkUIManager *ui_manager;
+ GtkAction *action;
+ GAppInfo *default_app;
+
+ /* Clear any previous inserted items in the applications and viewers placeholders */
+
+ ui_manager = caja_window_info_get_ui_manager (view->details->window);
+ caja_ui_unmerge_ui (ui_manager,
+ &view->details->open_with_merge_id,
+ &view->details->open_with_action_group);
+
+ caja_ui_prepare_merge_ui (ui_manager,
+ "OpenWithGroup",
+ &view->details->open_with_merge_id,
+ &view->details->open_with_action_group);
+
+ num_applications = 0;
+
+ other_applications_visible = (selection != NULL);
+ filter_default = (selection != NULL);
+
+ for (node = selection; node != NULL; node = node->next) {
+
+ file = CAJA_FILE (node->data);
+
+ other_applications_visible &=
+ (!caja_mime_file_opens_in_view (file) ||
+ caja_file_is_directory (file));
+ }
+
+ default_app = NULL;
+ if (filter_default) {
+ default_app = caja_mime_get_default_application_for_files (selection);
+ }
+
+ applications = NULL;
+ if (other_applications_visible) {
+ applications = caja_mime_get_applications_for_files (selection);
+ }
+
+ if (g_list_length (selection) == 1) {
+ add_x_content_apps (view, CAJA_FILE (selection->data), &applications);
+ }
+
+
+ num_applications = g_list_length (applications);
+
+ if (file_list_all_are_folders (selection)) {
+ submenu_visible = (num_applications > 2);
+ } else {
+ submenu_visible = (num_applications > 3);
+ }
+
+ for (node = applications, index = 0; node != NULL; node = node->next, index++) {
+ GAppInfo *application;
+ char *menu_path;
+ char *popup_path;
+
+ application = node->data;
+
+ if (default_app != NULL && g_app_info_equal (default_app, application)) {
+ continue;
+ }
+
+ if (submenu_visible) {
+ menu_path = FM_DIRECTORY_VIEW_MENU_PATH_APPLICATIONS_SUBMENU_PLACEHOLDER;
+ popup_path = FM_DIRECTORY_VIEW_POPUP_PATH_APPLICATIONS_SUBMENU_PLACEHOLDER;
+ } else {
+ menu_path = FM_DIRECTORY_VIEW_MENU_PATH_APPLICATIONS_PLACEHOLDER;
+ popup_path = FM_DIRECTORY_VIEW_POPUP_PATH_APPLICATIONS_PLACEHOLDER;
+ }
+
+ gtk_ui_manager_add_ui (caja_window_info_get_ui_manager (view->details->window),
+ view->details->open_with_merge_id,
+ menu_path,
+ "separator",
+ NULL,
+ GTK_UI_MANAGER_SEPARATOR,
+ FALSE);
+
+ add_application_to_open_with_menu (view,
+ node->data,
+ selection,
+ index,
+ menu_path, popup_path, submenu_visible);
+ }
+ eel_g_object_list_free (applications);
+ if (default_app != NULL) {
+ g_object_unref (default_app);
+ }
+
+ open_with_chooser_visible = other_applications_visible &&
+ g_list_length (selection) == 1;
+
+ if (submenu_visible) {
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_OTHER_APPLICATION1);
+ gtk_action_set_visible (action, open_with_chooser_visible);
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_OTHER_APPLICATION2);
+ gtk_action_set_visible (action, FALSE);
+ } else {
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_OTHER_APPLICATION1);
+ gtk_action_set_visible (action, FALSE);
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_OTHER_APPLICATION2);
+ gtk_action_set_visible (action, open_with_chooser_visible);
+ }
+}
+
+static GList *
+get_all_extension_menu_items (GtkWidget *window,
+ GList *selection)
+{
+ GList *items;
+ GList *providers;
+ GList *l;
+
+ providers = caja_module_get_extensions_for_type (CAJA_TYPE_MENU_PROVIDER);
+ items = NULL;
+
+ for (l = providers; l != NULL; l = l->next) {
+ CajaMenuProvider *provider;
+ GList *file_items;
+
+ provider = CAJA_MENU_PROVIDER (l->data);
+ file_items = caja_menu_provider_get_file_items (provider,
+ window,
+ selection);
+ items = g_list_concat (items, file_items);
+ }
+
+ caja_module_extension_list_free (providers);
+
+ return items;
+}
+
+typedef struct
+{
+ CajaMenuItem *item;
+ FMDirectoryView *view;
+ GList *selection;
+ GtkAction *action;
+} ExtensionActionCallbackData;
+
+
+static void
+extension_action_callback_data_free (ExtensionActionCallbackData *data)
+{
+ g_object_unref (data->item);
+ caja_file_list_free (data->selection);
+
+ g_free (data);
+}
+
+static gboolean
+search_in_menu_items (GList* items, const char *item_name)
+{
+ GList* list;
+
+ for (list = items; list != NULL; list = list->next) {
+ CajaMenu* menu;
+ char *name;
+
+ g_object_get (list->data, "name", &name, NULL);
+ if (strcmp (name, item_name) == 0) {
+ g_free (name);
+ return TRUE;
+ }
+ g_free (name);
+
+ menu = NULL;
+ g_object_get (list->data, "menu", &menu, NULL);
+ if (menu != NULL) {
+ gboolean ret;
+ GList* submenus;
+
+ submenus = caja_menu_get_items (menu);
+ ret = search_in_menu_items (submenus, item_name);
+ caja_menu_item_list_free (submenus);
+ g_object_unref (menu);
+ if (ret) {
+ return TRUE;
+ }
+ }
+ }
+ return FALSE;
+}
+
+static void
+extension_action_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ ExtensionActionCallbackData *data;
+ char *item_name;
+ gboolean is_valid;
+ GList *l;
+ GList *items;
+
+ data = callback_data;
+
+ /* Make sure the selected menu item is valid for the final sniffed
+ * mime type */
+ g_object_get (data->item, "name", &item_name, NULL);
+ items = get_all_extension_menu_items (gtk_widget_get_toplevel (GTK_WIDGET (data->view)),
+ data->selection);
+
+ is_valid = search_in_menu_items (items, item_name);
+
+ for (l = items; l != NULL; l = l->next) {
+ g_object_unref (l->data);
+ }
+ g_list_free (items);
+
+ g_free (item_name);
+
+ if (is_valid) {
+ caja_menu_item_activate (data->item);
+ }
+}
+
+static GdkPixbuf *
+get_menu_icon (const char *icon_name)
+{
+ CajaIconInfo *info;
+ GdkPixbuf *pixbuf;
+ int size;
+
+ size = caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU);
+
+ if (g_path_is_absolute (icon_name)) {
+ info = caja_icon_info_lookup_from_path (icon_name, size);
+ } else {
+ info = caja_icon_info_lookup_from_name (icon_name, size);
+ }
+ pixbuf = caja_icon_info_get_pixbuf_nodefault_at_size (info, size);
+ g_object_unref (info);
+
+ return pixbuf;
+}
+
+static GdkPixbuf *
+get_menu_icon_for_file (CajaFile *file)
+{
+ CajaIconInfo *info;
+ GdkPixbuf *pixbuf;
+ int size;
+
+ size = caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU);
+
+ info = caja_file_get_icon (file, size, 0);
+ pixbuf = caja_icon_info_get_pixbuf_nodefault_at_size (info, size);
+ g_object_unref (info);
+
+ return pixbuf;
+}
+
+static GtkAction *
+add_extension_action_for_files (FMDirectoryView *view,
+ CajaMenuItem *item,
+ GList *files)
+{
+ char *name, *label, *tip, *icon;
+ gboolean sensitive, priority;
+ GtkAction *action;
+ GdkPixbuf *pixbuf;
+ ExtensionActionCallbackData *data;
+
+ g_object_get (G_OBJECT (item),
+ "name", &name, "label", &label,
+ "tip", &tip, "icon", &icon,
+ "sensitive", &sensitive,
+ "priority", &priority,
+ NULL);
+
+ action = gtk_action_new (name,
+ label,
+ tip,
+ icon);
+
+ if (icon != NULL) {
+ pixbuf = get_menu_icon (icon);
+ if (pixbuf != NULL) {
+ g_object_set_data_full (G_OBJECT (action), "menu-icon",
+ pixbuf,
+ g_object_unref);
+ }
+ }
+
+ gtk_action_set_sensitive (action, sensitive);
+ g_object_set (action, "is-important", priority, NULL);
+
+ data = g_new0 (ExtensionActionCallbackData, 1);
+ data->item = g_object_ref (item);
+ data->view = view;
+ data->selection = caja_file_list_copy (files);
+ data->action = action;
+
+ g_signal_connect_data (action, "activate",
+ G_CALLBACK (extension_action_callback),
+ data,
+ (GClosureNotify)extension_action_callback_data_free, 0);
+
+ gtk_action_group_add_action (view->details->extensions_menu_action_group,
+ GTK_ACTION (action));
+ g_object_unref (action);
+
+ g_free (name);
+ g_free (label);
+ g_free (tip);
+ g_free (icon);
+
+ return action;
+}
+
+static void
+add_extension_menu_items (FMDirectoryView *view,
+ GList *files,
+ GList *menu_items,
+ const char *subdirectory)
+{
+ GtkUIManager *ui_manager;
+ GList *l;
+
+ ui_manager = caja_window_info_get_ui_manager (view->details->window);
+
+ for (l = menu_items; l; l = l->next) {
+ CajaMenuItem *item;
+ CajaMenu *menu;
+ GtkAction *action;
+ char *path;
+
+ item = CAJA_MENU_ITEM (l->data);
+
+ g_object_get (item, "menu", &menu, NULL);
+
+ action = add_extension_action_for_files (view, item, files);
+
+ path = g_build_path ("/", FM_DIRECTORY_VIEW_POPUP_PATH_EXTENSION_ACTIONS, subdirectory, NULL);
+ gtk_ui_manager_add_ui (ui_manager,
+ view->details->extensions_menu_merge_id,
+ path,
+ gtk_action_get_name (action),
+ gtk_action_get_name (action),
+ (menu != NULL) ? GTK_UI_MANAGER_MENU : GTK_UI_MANAGER_MENUITEM,
+ FALSE);
+ g_free (path);
+
+ path = g_build_path ("/", FM_DIRECTORY_VIEW_MENU_PATH_EXTENSION_ACTIONS_PLACEHOLDER, subdirectory, NULL);
+ gtk_ui_manager_add_ui (ui_manager,
+ view->details->extensions_menu_merge_id,
+ path,
+ gtk_action_get_name (action),
+ gtk_action_get_name (action),
+ (menu != NULL) ? GTK_UI_MANAGER_MENU : GTK_UI_MANAGER_MENUITEM,
+ FALSE);
+ g_free (path);
+
+ /* recursively fill the menu */
+ if (menu != NULL) {
+ char *subdir;
+ GList *children;
+
+ children = caja_menu_get_items (menu);
+
+ subdir = g_build_path ("/", subdirectory, gtk_action_get_name (action), NULL);
+ add_extension_menu_items (view,
+ files,
+ children,
+ subdir);
+
+ caja_menu_item_list_free (children);
+ g_free (subdir);
+ }
+ }
+}
+
+static void
+reset_extension_actions_menu (FMDirectoryView *view, GList *selection)
+{
+ GList *items;
+ GtkUIManager *ui_manager;
+
+ /* Clear any previous inserted items in the extension actions placeholder */
+ ui_manager = caja_window_info_get_ui_manager (view->details->window);
+
+ caja_ui_unmerge_ui (ui_manager,
+ &view->details->extensions_menu_merge_id,
+ &view->details->extensions_menu_action_group);
+
+ caja_ui_prepare_merge_ui (ui_manager,
+ "DirExtensionsMenuGroup",
+ &view->details->extensions_menu_merge_id,
+ &view->details->extensions_menu_action_group);
+
+ items = get_all_extension_menu_items (gtk_widget_get_toplevel (GTK_WIDGET (view)),
+ selection);
+ if (items != NULL) {
+ add_extension_menu_items (view, selection, items, "");
+
+ g_list_foreach (items, (GFunc) g_object_unref, NULL);
+ g_list_free (items);
+ }
+}
+
+static char *
+change_to_view_directory (FMDirectoryView *view)
+{
+ char *path;
+ char *old_path;
+
+ old_path = g_get_current_dir ();
+
+ path = get_view_directory (view);
+
+ /* FIXME: What to do about non-local directories? */
+ if (path != NULL) {
+ g_chdir (path);
+ }
+
+ g_free (path);
+
+ return old_path;
+}
+
+static char **
+get_file_names_as_parameter_array (GList *selection,
+ CajaDirectory *model)
+{
+ CajaFile *file;
+ char **parameters;
+ GList *node;
+ GFile *file_location;
+ GFile *model_location;
+ int i;
+
+ if (model == NULL) {
+ return NULL;
+ }
+
+ parameters = g_new (char *, g_list_length (selection) + 1);
+
+ model_location = caja_directory_get_location (model);
+
+ for (node = selection, i = 0; node != NULL; node = node->next, i++) {
+ file = CAJA_FILE (node->data);
+
+ if (!caja_file_is_local (file)) {
+ parameters[i] = NULL;
+ g_strfreev (parameters);
+ return NULL;
+ }
+
+ file_location = caja_file_get_location (CAJA_FILE (node->data));
+ parameters[i] = g_file_get_relative_path (model_location, file_location);
+ if (parameters[i] == NULL) {
+ parameters[i] = g_file_get_path (file_location);
+ }
+ g_object_unref (file_location);
+ }
+
+ g_object_unref (model_location);
+
+ parameters[i] = NULL;
+ return parameters;
+}
+
+static char *
+get_file_paths_or_uris_as_newline_delimited_string (GList *selection, gboolean get_paths)
+{
+ char *path;
+ char *uri;
+ char *result;
+ CajaDesktopLink *link;
+ GString *expanding_string;
+ GList *node;
+ GFile *location;
+
+ expanding_string = g_string_new ("");
+ for (node = selection; node != NULL; node = node->next) {
+ uri = NULL;
+ if (CAJA_IS_DESKTOP_ICON_FILE (node->data)) {
+ link = caja_desktop_icon_file_get_link (CAJA_DESKTOP_ICON_FILE (node->data));
+ if (link != NULL) {
+ location = caja_desktop_link_get_activation_location (link);
+ uri = g_file_get_uri (location);
+ g_object_unref (location);
+ g_object_unref (G_OBJECT (link));
+ }
+ } else {
+ uri = caja_file_get_uri (CAJA_FILE (node->data));
+ }
+ if (uri == NULL) {
+ continue;
+ }
+
+ if (get_paths) {
+ path = g_filename_from_uri (uri, NULL, NULL);
+ if (path != NULL) {
+ g_string_append (expanding_string, path);
+ g_free (path);
+ g_string_append (expanding_string, "\n");
+ }
+ } else {
+ g_string_append (expanding_string, uri);
+ g_string_append (expanding_string, "\n");
+ }
+ g_free (uri);
+ }
+
+ result = expanding_string->str;
+ g_string_free (expanding_string, FALSE);
+
+ return result;
+}
+
+static char *
+get_file_paths_as_newline_delimited_string (GList *selection)
+{
+ return get_file_paths_or_uris_as_newline_delimited_string (selection, TRUE);
+}
+
+static char *
+get_file_uris_as_newline_delimited_string (GList *selection)
+{
+ return get_file_paths_or_uris_as_newline_delimited_string (selection, FALSE);
+}
+
+/* returns newly allocated strings for setting the environment variables */
+static void
+get_strings_for_environment_variables (FMDirectoryView *view, GList *selected_files,
+ char **file_paths, char **uris, char **uri)
+{
+ char *directory_uri;
+
+ /* We need to check that the directory uri starts with "file:" since
+ * caja_directory_is_local returns FALSE for nfs.
+ */
+ directory_uri = caja_directory_get_uri (view->details->model);
+ if (eel_str_has_prefix (directory_uri, "file:") ||
+ eel_uri_is_desktop (directory_uri) ||
+ eel_uri_is_trash (directory_uri)) {
+ *file_paths = get_file_paths_as_newline_delimited_string (selected_files);
+ } else {
+ *file_paths = g_strdup ("");
+ }
+ g_free (directory_uri);
+
+ *uris = get_file_uris_as_newline_delimited_string (selected_files);
+
+ *uri = caja_directory_get_uri (view->details->model);
+ if (eel_uri_is_desktop (*uri)) {
+ g_free (*uri);
+ *uri = caja_get_desktop_directory_uri ();
+ }
+}
+
+static FMDirectoryView *
+get_directory_view_of_extra_pane (FMDirectoryView *view)
+{
+ CajaWindowSlotInfo *slot;
+ CajaView *next_view;
+
+ slot = caja_window_info_get_extra_slot (fm_directory_view_get_caja_window (view));
+ if (slot != NULL) {
+ next_view = caja_window_slot_info_get_current_view (slot);
+
+ if (FM_IS_DIRECTORY_VIEW (next_view)) {
+ return FM_DIRECTORY_VIEW (next_view);
+ }
+ }
+ return NULL;
+}
+
+/*
+ * Set up some environment variables that scripts can use
+ * to take advantage of the current Caja state.
+ */
+static void set_script_environment_variables(FMDirectoryView* view, GList* selected_files)
+{
+ char* file_paths;
+ char* uris;
+ char* uri;
+ char* geometry_string;
+ FMDirectoryView* next_view;
+
+ get_strings_for_environment_variables(view, selected_files, &file_paths, &uris, &uri);
+
+ g_setenv("CAJA_SCRIPT_SELECTED_FILE_PATHS", file_paths, TRUE);
+ g_setenv("NAUTILUS_SCRIPT_SELECTED_FILE_PATHS", file_paths, TRUE); // compatibilidad GNOME
+
+ g_free(file_paths);
+
+ g_setenv("CAJA_SCRIPT_SELECTED_URIS", uris, TRUE);
+ g_setenv("NAUTILUS_SCRIPT_SELECTED_URIS", uris, TRUE); // compatibilidad GNOME
+
+ g_free(uris);
+
+ g_setenv("CAJA_SCRIPT_CURRENT_URI", uri, TRUE);
+ g_setenv("NAUTILUS_SCRIPT_CURRENT_URI", uri, TRUE); // compatibilidad GNOME
+
+
+ g_free(uri);
+
+ geometry_string = eel_gtk_window_get_geometry_string(GTK_WINDOW (fm_directory_view_get_containing_window (view)));
+
+ g_setenv("CAJA_SCRIPT_WINDOW_GEOMETRY", geometry_string, TRUE);
+ g_setenv("NAUTILUS_SCRIPT_WINDOW_GEOMETRY", geometry_string, TRUE); // compatibilidad GNOME
+
+ g_free(geometry_string);
+
+ /* next pane */
+ next_view = get_directory_view_of_extra_pane(view);
+
+ if (next_view)
+ {
+ GList* next_pane_selected_files = fm_directory_view_get_selection (next_view);
+
+ get_strings_for_environment_variables(next_view, next_pane_selected_files, &file_paths, &uris, &uri);
+
+ caja_file_list_free(next_pane_selected_files);
+ }
+ else
+ {
+ file_paths = g_strdup("");
+ uris = g_strdup("");
+ uri = g_strdup("");
+ }
+
+ g_setenv("CAJA_SCRIPT_NEXT_PANE_SELECTED_FILE_PATHS", file_paths, TRUE);
+ g_setenv("NAUTILUS_SCRIPT_NEXT_PANE_SELECTED_FILE_PATHS", file_paths, TRUE); // compatibilidad GNOME
+ g_free(file_paths);
+
+ g_setenv("CAJA_SCRIPT_NEXT_PANE_SELECTED_URIS", uris, TRUE);
+ g_setenv("NAUTILUS_SCRIPT_NEXT_PANE_SELECTED_URIS", uris, TRUE); // compatibilidad GNOME
+ g_free(uris);
+
+ g_setenv("CAJA_SCRIPT_NEXT_PANE_CURRENT_URI", uri, TRUE);
+ g_setenv("NAUTILUS_SCRIPT_NEXT_PANE_CURRENT_URI", uri, TRUE); // compatibilidad GNOME
+ g_free(uri);
+}
+
+/* Unset all the special script environment variables. */
+static void unset_script_environment_variables(void)
+{
+ g_unsetenv("CAJA_SCRIPT_SELECTED_FILE_PATHS");
+ g_unsetenv("NAUTILUS_SCRIPT_SELECTED_FILE_PATHS");
+
+ g_unsetenv("CAJA_SCRIPT_SELECTED_URIS");
+ g_unsetenv("NAUTILUS_SCRIPT_SELECTED_URIS");
+
+ g_unsetenv("CAJA_SCRIPT_CURRENT_URI");
+ g_unsetenv("NAUTILUS_SCRIPT_CURRENT_URI");
+
+ g_unsetenv("CAJA_SCRIPT_WINDOW_GEOMETRY");
+ g_unsetenv("NAUTILUS_SCRIPT_WINDOW_GEOMETRY");
+
+ g_unsetenv("CAJA_SCRIPT_NEXT_PANE_SELECTED_FILE_PATHS");
+ g_unsetenv("NAUTILUS_SCRIPT_NEXT_PANE_SELECTED_FILE_PATHS");
+
+ g_unsetenv("CAJA_SCRIPT_NEXT_PANE_SELECTED_URIS");
+ g_unsetenv("NAUTILUS_SCRIPT_NEXT_PANE_SELECTED_URIS");
+
+ g_unsetenv("CAJA_SCRIPT_NEXT_PANE_CURRENT_URI");
+ g_unsetenv("NAUTILUS_SCRIPT_NEXT_PANE_CURRENT_URI");
+}
+
+static void
+run_script_callback (GtkAction *action, gpointer callback_data)
+{
+ ScriptLaunchParameters *launch_parameters;
+ GdkScreen *screen;
+ GList *selected_files;
+ char *file_uri;
+ char *local_file_path;
+ char *quoted_path;
+ char *old_working_dir;
+ char **parameters, *name;
+ GtkWindow *window;
+
+ launch_parameters = (ScriptLaunchParameters *) callback_data;
+
+ file_uri = caja_file_get_uri (launch_parameters->file);
+ local_file_path = g_filename_from_uri (file_uri, NULL, NULL);
+ g_assert (local_file_path != NULL);
+ g_free (file_uri);
+
+ quoted_path = g_shell_quote (local_file_path);
+ g_free (local_file_path);
+
+ old_working_dir = change_to_view_directory (launch_parameters->directory_view);
+
+ selected_files = fm_directory_view_get_selection (launch_parameters->directory_view);
+ set_script_environment_variables (launch_parameters->directory_view, selected_files);
+
+ parameters = get_file_names_as_parameter_array (selected_files,
+ launch_parameters->directory_view->details->model);
+
+ screen = gtk_widget_get_screen (GTK_WIDGET (launch_parameters->directory_view));
+
+ name = caja_file_get_name (launch_parameters->file);
+ /* FIXME: handle errors with dialog? Or leave up to each script? */
+ window = fm_directory_view_get_containing_window (launch_parameters->directory_view);
+ caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
+ "directory view run_script_callback, window=%p, name=\"%s\", script_path=\"%s\" (omitting script parameters)",
+ window, name, local_file_path);
+ caja_launch_application_from_command_array (screen, name, quoted_path, FALSE,
+ (const char * const *) parameters);
+ g_free (name);
+ g_strfreev (parameters);
+
+ caja_file_list_free (selected_files);
+ unset_script_environment_variables ();
+ g_chdir (old_working_dir);
+ g_free (old_working_dir);
+ g_free (quoted_path);
+}
+
+static void
+add_script_to_scripts_menus (FMDirectoryView *directory_view,
+ CajaFile *file,
+ const char *menu_path,
+ const char *popup_path,
+ const char *popup_bg_path)
+{
+ ScriptLaunchParameters *launch_parameters;
+ char *tip;
+ char *name;
+ char *uri;
+ char *action_name;
+ char *escaped_label;
+ GdkPixbuf *pixbuf;
+ GtkUIManager *ui_manager;
+ GtkAction *action;
+
+ name = caja_file_get_display_name (file);
+ uri = caja_file_get_uri (file);
+ tip = g_strdup_printf (_("Run \"%s\" on any selected items"), name);
+
+ launch_parameters = script_launch_parameters_new (file, directory_view);
+
+ action_name = escape_action_name (uri, "script_");
+ escaped_label = eel_str_double_underscores (name);
+
+ action = gtk_action_new (action_name,
+ escaped_label,
+ tip,
+ NULL);
+
+ pixbuf = get_menu_icon_for_file (file);
+ if (pixbuf != NULL) {
+ g_object_set_data_full (G_OBJECT (action), "menu-icon",
+ pixbuf,
+ g_object_unref);
+ }
+
+ g_signal_connect_data (action, "activate",
+ G_CALLBACK (run_script_callback),
+ launch_parameters,
+ (GClosureNotify)script_launch_parameters_free, 0);
+
+ gtk_action_group_add_action_with_accel (directory_view->details->scripts_action_group,
+ action, NULL);
+ g_object_unref (action);
+
+ ui_manager = caja_window_info_get_ui_manager (directory_view->details->window);
+
+ gtk_ui_manager_add_ui (ui_manager,
+ directory_view->details->scripts_merge_id,
+ menu_path,
+ action_name,
+ action_name,
+ GTK_UI_MANAGER_MENUITEM,
+ FALSE);
+ gtk_ui_manager_add_ui (ui_manager,
+ directory_view->details->scripts_merge_id,
+ popup_path,
+ action_name,
+ action_name,
+ GTK_UI_MANAGER_MENUITEM,
+ FALSE);
+ gtk_ui_manager_add_ui (ui_manager,
+ directory_view->details->scripts_merge_id,
+ popup_bg_path,
+ action_name,
+ action_name,
+ GTK_UI_MANAGER_MENUITEM,
+ FALSE);
+
+ g_free (name);
+ g_free (uri);
+ g_free (tip);
+ g_free (action_name);
+ g_free (escaped_label);
+}
+
+static void
+add_submenu_to_directory_menus (FMDirectoryView *directory_view,
+ GtkActionGroup *action_group,
+ guint merge_id,
+ CajaFile *file,
+ const char *menu_path,
+ const char *popup_path,
+ const char *popup_bg_path)
+{
+ char *name;
+ GdkPixbuf *pixbuf;
+ char *uri;
+ GtkUIManager *ui_manager;
+
+ ui_manager = caja_window_info_get_ui_manager (directory_view->details->window);
+ uri = caja_file_get_uri (file);
+ name = caja_file_get_display_name (file);
+ pixbuf = get_menu_icon_for_file (file);
+ add_submenu (ui_manager, action_group, merge_id, menu_path, uri, name, pixbuf, TRUE);
+ add_submenu (ui_manager, action_group, merge_id, popup_path, uri, name, pixbuf, FALSE);
+ add_submenu (ui_manager, action_group, merge_id, popup_bg_path, uri, name, pixbuf, FALSE);
+ if (pixbuf) {
+ g_object_unref (pixbuf);
+ }
+ g_free (name);
+ g_free (uri);
+}
+
+static gboolean
+directory_belongs_in_scripts_menu (const char *uri)
+{
+ int num_levels;
+ int i;
+
+ if (!eel_str_has_prefix (uri, scripts_directory_uri)) {
+ return FALSE;
+ }
+
+ num_levels = 0;
+ for (i = scripts_directory_uri_length; uri[i] != '\0'; i++) {
+ if (uri[i] == '/') {
+ num_levels++;
+ }
+ }
+
+ if (num_levels > MAX_MENU_LEVELS) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+update_directory_in_scripts_menu (FMDirectoryView *view, CajaDirectory *directory)
+{
+ char *menu_path, *popup_path, *popup_bg_path;
+ GList *file_list, *filtered, *node;
+ gboolean any_scripts;
+ CajaFile *file;
+ CajaDirectory *dir;
+ char *uri;
+ char *escaped_path;
+
+ uri = caja_directory_get_uri (directory);
+ escaped_path = escape_action_path (uri + scripts_directory_uri_length);
+ g_free (uri);
+ menu_path = g_strconcat (FM_DIRECTORY_VIEW_MENU_PATH_SCRIPTS_PLACEHOLDER,
+ escaped_path,
+ NULL);
+ popup_path = g_strconcat (FM_DIRECTORY_VIEW_POPUP_PATH_SCRIPTS_PLACEHOLDER,
+ escaped_path,
+ NULL);
+ popup_bg_path = g_strconcat (FM_DIRECTORY_VIEW_POPUP_PATH_BACKGROUND_SCRIPTS_PLACEHOLDER,
+ escaped_path,
+ NULL);
+ g_free (escaped_path);
+
+ file_list = caja_directory_get_file_list (directory);
+ filtered = caja_file_list_filter_hidden_and_backup (file_list, FALSE, FALSE);
+ caja_file_list_free (file_list);
+
+ file_list = caja_file_list_sort_by_display_name (filtered);
+
+ any_scripts = FALSE;
+ for (node = file_list; node != NULL; node = node->next) {
+ file = node->data;
+
+ if (caja_file_is_launchable (file)) {
+ add_script_to_scripts_menus (view, file, menu_path, popup_path, popup_bg_path);
+ any_scripts = TRUE;
+ } else if (caja_file_is_directory (file)) {
+ uri = caja_file_get_uri (file);
+ if (directory_belongs_in_scripts_menu (uri)) {
+ dir = caja_directory_get_by_uri (uri);
+ add_directory_to_scripts_directory_list (view, dir);
+ caja_directory_unref (dir);
+
+ add_submenu_to_directory_menus (view,
+ view->details->scripts_action_group,
+ view->details->scripts_merge_id,
+ file, menu_path, popup_path, popup_bg_path);
+
+ any_scripts = TRUE;
+ }
+ g_free (uri);
+ }
+ }
+
+ caja_file_list_free (file_list);
+
+ g_free (popup_path);
+ g_free (popup_bg_path);
+ g_free (menu_path);
+
+ return any_scripts;
+}
+
+static void
+update_scripts_menu (FMDirectoryView *view)
+{
+ gboolean any_scripts;
+ GList *sorted_copy, *node;
+ CajaDirectory *directory;
+ char *uri;
+ GtkUIManager *ui_manager;
+ GtkAction *action;
+
+ /* There is a race condition here. If we don't mark the scripts menu as
+ valid before we begin our task then we can lose script menu updates that
+ occur before we finish. */
+ view->details->scripts_invalid = FALSE;
+
+ ui_manager = caja_window_info_get_ui_manager (view->details->window);
+ caja_ui_unmerge_ui (ui_manager,
+ &view->details->scripts_merge_id,
+ &view->details->scripts_action_group);
+
+ caja_ui_prepare_merge_ui (ui_manager,
+ "ScriptsGroup",
+ &view->details->scripts_merge_id,
+ &view->details->scripts_action_group);
+
+ /* As we walk through the directories, remove any that no longer belong. */
+ any_scripts = FALSE;
+ sorted_copy = caja_directory_list_sort_by_uri
+ (caja_directory_list_copy (view->details->scripts_directory_list));
+ for (node = sorted_copy; node != NULL; node = node->next) {
+ directory = node->data;
+
+ uri = caja_directory_get_uri (directory);
+ if (!directory_belongs_in_scripts_menu (uri)) {
+ remove_directory_from_scripts_directory_list (view, directory);
+ } else if (update_directory_in_scripts_menu (view, directory)) {
+ any_scripts = TRUE;
+ }
+ g_free (uri);
+ }
+ caja_directory_list_free (sorted_copy);
+
+ action = gtk_action_group_get_action (view->details->dir_action_group, FM_ACTION_SCRIPTS);
+ gtk_action_set_visible (action, any_scripts);
+}
+
+static void
+create_template_callback (GtkAction *action, gpointer callback_data)
+{
+ CreateTemplateParameters *parameters;
+
+ parameters = callback_data;
+
+ fm_directory_view_new_file (parameters->directory_view, NULL, parameters->file);
+}
+
+static void
+add_template_to_templates_menus (FMDirectoryView *directory_view,
+ CajaFile *file,
+ const char *menu_path,
+ const char *popup_bg_path)
+{
+ char *tmp, *tip, *uri, *name;
+ char *escaped_label;
+ GdkPixbuf *pixbuf;
+ char *action_name;
+ CreateTemplateParameters *parameters;
+ GtkUIManager *ui_manager;
+ GtkAction *action;
+
+ tmp = caja_file_get_display_name (file);
+ name = eel_filename_strip_extension (tmp);
+ g_free (tmp);
+
+ uri = caja_file_get_uri (file);
+ tip = g_strdup_printf (_("Create Document from template \"%s\""), name);
+
+ action_name = escape_action_name (uri, "template_");
+ escaped_label = eel_str_double_underscores (name);
+
+ parameters = create_template_parameters_new (file, directory_view);
+
+ action = gtk_action_new (action_name,
+ escaped_label,
+ tip,
+ NULL);
+
+ pixbuf = get_menu_icon_for_file (file);
+ if (pixbuf != NULL) {
+ g_object_set_data_full (G_OBJECT (action), "menu-icon",
+ pixbuf,
+ g_object_unref);
+ }
+
+ g_signal_connect_data (action, "activate",
+ G_CALLBACK (create_template_callback),
+ parameters,
+ (GClosureNotify)create_templates_parameters_free, 0);
+
+ gtk_action_group_add_action (directory_view->details->templates_action_group,
+ action);
+ g_object_unref (action);
+
+ ui_manager = caja_window_info_get_ui_manager (directory_view->details->window);
+
+ gtk_ui_manager_add_ui (ui_manager,
+ directory_view->details->templates_merge_id,
+ menu_path,
+ action_name,
+ action_name,
+ GTK_UI_MANAGER_MENUITEM,
+ FALSE);
+
+ gtk_ui_manager_add_ui (ui_manager,
+ directory_view->details->templates_merge_id,
+ popup_bg_path,
+ action_name,
+ action_name,
+ GTK_UI_MANAGER_MENUITEM,
+ FALSE);
+
+ g_free (escaped_label);
+ g_free (name);
+ g_free (tip);
+ g_free (uri);
+ g_free (action_name);
+}
+
+static void
+update_templates_directory (FMDirectoryView *view)
+{
+ CajaDirectory *templates_directory;
+ GList *node, *next;
+ char *templates_uri;
+
+ for (node = view->details->templates_directory_list; node != NULL; node = next) {
+ next = node->next;
+ remove_directory_from_templates_directory_list (view, node->data);
+ }
+
+ if (caja_should_use_templates_directory ()) {
+ templates_uri = caja_get_templates_directory_uri ();
+ templates_directory = caja_directory_get_by_uri (templates_uri);
+ g_free (templates_uri);
+ add_directory_to_templates_directory_list (view, templates_directory);
+ caja_directory_unref (templates_directory);
+ }
+}
+
+static void
+user_dirs_changed (FMDirectoryView *view)
+{
+ update_templates_directory (view);
+ view->details->templates_invalid = TRUE;
+ schedule_update_menus (view);
+}
+
+static gboolean
+directory_belongs_in_templates_menu (const char *templates_directory_uri,
+ const char *uri)
+{
+ int num_levels;
+ int i;
+
+ if (templates_directory_uri == NULL) {
+ return FALSE;
+ }
+
+ if (!g_str_has_prefix (uri, templates_directory_uri)) {
+ return FALSE;
+ }
+
+ num_levels = 0;
+ for (i = strlen (templates_directory_uri); uri[i] != '\0'; i++) {
+ if (uri[i] == '/') {
+ num_levels++;
+ }
+ }
+
+ if (num_levels > MAX_MENU_LEVELS) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+update_directory_in_templates_menu (FMDirectoryView *view,
+ const char *templates_directory_uri,
+ CajaDirectory *directory)
+{
+ char *menu_path, *popup_bg_path;
+ GList *file_list, *filtered, *node;
+ gboolean any_templates;
+ CajaFile *file;
+ CajaDirectory *dir;
+ char *escaped_path;
+ char *uri;
+ int num;
+
+ /* We know this directory belongs to the template dir, so it must exist */
+ g_assert (templates_directory_uri);
+
+ uri = caja_directory_get_uri (directory);
+ escaped_path = escape_action_path (uri + strlen (templates_directory_uri));
+ g_free (uri);
+ menu_path = g_strconcat (FM_DIRECTORY_VIEW_MENU_PATH_NEW_DOCUMENTS_PLACEHOLDER,
+ escaped_path,
+ NULL);
+ popup_bg_path = g_strconcat (FM_DIRECTORY_VIEW_POPUP_PATH_BACKGROUND_NEW_DOCUMENTS_PLACEHOLDER,
+ escaped_path,
+ NULL);
+ g_free (escaped_path);
+
+ file_list = caja_directory_get_file_list (directory);
+ filtered = caja_file_list_filter_hidden_and_backup (file_list, FALSE, FALSE);
+ caja_file_list_free (file_list);
+
+ file_list = caja_file_list_sort_by_display_name (filtered);
+
+ num = 0;
+ any_templates = FALSE;
+ for (node = file_list; num < TEMPLATE_LIMIT && node != NULL; node = node->next, num++) {
+ file = node->data;
+
+ if (caja_file_is_directory (file)) {
+ uri = caja_file_get_uri (file);
+ if (directory_belongs_in_templates_menu (templates_directory_uri, uri)) {
+ dir = caja_directory_get_by_uri (uri);
+ add_directory_to_templates_directory_list (view, dir);
+ caja_directory_unref (dir);
+
+ add_submenu_to_directory_menus (view,
+ view->details->templates_action_group,
+ view->details->templates_merge_id,
+ file, menu_path, NULL, popup_bg_path);
+
+ any_templates = TRUE;
+ }
+ g_free (uri);
+ } else if (caja_file_can_read (file)) {
+ add_template_to_templates_menus (view, file, menu_path, popup_bg_path);
+ any_templates = TRUE;
+ }
+ }
+
+ caja_file_list_free (file_list);
+
+ g_free (popup_bg_path);
+ g_free (menu_path);
+
+ return any_templates;
+}
+
+
+
+static void
+update_templates_menu (FMDirectoryView *view)
+{
+ gboolean any_templates;
+ GList *sorted_copy, *node;
+ CajaDirectory *directory;
+ GtkUIManager *ui_manager;
+ char *uri;
+ GtkAction *action;
+ char *templates_directory_uri;
+
+ if (caja_should_use_templates_directory ()) {
+ templates_directory_uri = caja_get_templates_directory_uri ();
+ } else {
+ templates_directory_uri = NULL;
+ }
+
+ /* There is a race condition here. If we don't mark the scripts menu as
+ valid before we begin our task then we can lose template menu updates that
+ occur before we finish. */
+ view->details->templates_invalid = FALSE;
+
+ ui_manager = caja_window_info_get_ui_manager (view->details->window);
+ caja_ui_unmerge_ui (ui_manager,
+ &view->details->templates_merge_id,
+ &view->details->templates_action_group);
+
+ caja_ui_prepare_merge_ui (ui_manager,
+ "TemplatesGroup",
+ &view->details->templates_merge_id,
+ &view->details->templates_action_group);
+
+ /* As we walk through the directories, remove any that no longer belong. */
+ any_templates = FALSE;
+ sorted_copy = caja_directory_list_sort_by_uri
+ (caja_directory_list_copy (view->details->templates_directory_list));
+ for (node = sorted_copy; node != NULL; node = node->next) {
+ directory = node->data;
+
+ uri = caja_directory_get_uri (directory);
+ if (!directory_belongs_in_templates_menu (templates_directory_uri, uri)) {
+ remove_directory_from_templates_directory_list (view, directory);
+ } else if (update_directory_in_templates_menu (view,
+ templates_directory_uri,
+ directory)) {
+ any_templates = TRUE;
+ }
+ g_free (uri);
+ }
+ caja_directory_list_free (sorted_copy);
+
+ action = gtk_action_group_get_action (view->details->dir_action_group, FM_ACTION_NO_TEMPLATES);
+ gtk_action_set_visible (action, !any_templates);
+
+ g_free (templates_directory_uri);
+}
+
+
+static void
+action_open_scripts_folder_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ FMDirectoryView *view;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+
+ open_location (view, scripts_directory_uri, CAJA_WINDOW_OPEN_ACCORDING_TO_MODE, 0);
+
+ eel_show_info_dialog_with_details
+ (_("All executable files in this folder will appear in the "
+ "Scripts menu."),
+ _("Choosing a script from the menu will run "
+ "that script with any selected items as input."),
+ _("All executable files in this folder will appear in the "
+ "Scripts menu. Choosing a script from the menu will run "
+ "that script.\n\n"
+ "When executed from a local folder, scripts will be passed "
+ "the selected file names. When executed from a remote folder "
+ "(e.g. a folder showing web or ftp content), scripts will "
+ "be passed no parameters.\n\n"
+ "In all cases, the following environment variables will be "
+ "set by Caja, which the scripts may use:\n\n"
+ "CAJA_SCRIPT_SELECTED_FILE_PATHS: newline-delimited paths for selected files (only if local)\n\n"
+ "CAJA_SCRIPT_SELECTED_URIS: newline-delimited URIs for selected files\n\n"
+ "CAJA_SCRIPT_CURRENT_URI: URI for current location\n\n"
+ "CAJA_SCRIPT_WINDOW_GEOMETRY: position and size of current window\n\n"
+ "CAJA_SCRIPT_NEXT_PANE_SELECTED_FILE_PATHS: newline-delimited paths for selected files in the inactive pane of a split-view window (only if local)\n\n"
+ "CAJA_SCRIPT_NEXT_PANE_SELECTED_URIS: newline-delimited URIs for selected files in the inactive pane of a split-view window\n\n"
+ "CAJA_SCRIPT_NEXT_PANE_CURRENT_URI: URI for current location in the inactive pane of a split-view window"),
+ fm_directory_view_get_containing_window (view));
+}
+
+static GtkMenu *
+create_popup_menu (FMDirectoryView *view, const char *popup_path)
+{
+ GtkWidget *menu;
+
+ menu = gtk_ui_manager_get_widget (caja_window_info_get_ui_manager (view->details->window),
+ popup_path);
+ gtk_menu_set_screen (GTK_MENU (menu),
+ gtk_widget_get_screen (GTK_WIDGET (view)));
+ gtk_widget_show (GTK_WIDGET (menu));
+
+ return GTK_MENU (menu);
+}
+
+static void
+copy_or_cut_files (FMDirectoryView *view,
+ GList *clipboard_contents,
+ gboolean cut)
+{
+ int count;
+ char *status_string, *name;
+ CajaClipboardInfo info;
+ GtkTargetList *target_list;
+ GtkTargetEntry *targets;
+ int n_targets;
+
+ info.files = clipboard_contents;
+ info.cut = cut;
+
+ target_list = gtk_target_list_new (NULL, 0);
+ gtk_target_list_add (target_list, copied_files_atom, 0, 0);
+ gtk_target_list_add_uri_targets (target_list, 0);
+ gtk_target_list_add_text_targets (target_list, 0);
+
+ targets = gtk_target_table_new_from_list (target_list, &n_targets);
+ gtk_target_list_unref (target_list);
+
+ gtk_clipboard_set_with_data (caja_clipboard_get (GTK_WIDGET (view)),
+ targets, n_targets,
+ caja_get_clipboard_callback, caja_clear_clipboard_callback,
+ NULL);
+ gtk_target_table_free (targets, n_targets);
+
+ caja_clipboard_monitor_set_clipboard_info (caja_clipboard_monitor_get (), &info);
+
+ count = g_list_length (clipboard_contents);
+ if (count == 1) {
+ name = caja_file_get_display_name (clipboard_contents->data);
+ if (cut) {
+ status_string = g_strdup_printf (_("\"%s\" will be moved "
+ "if you select the Paste command"),
+ name);
+ } else {
+ status_string = g_strdup_printf (_("\"%s\" will be copied "
+ "if you select the Paste command"),
+ name);
+ }
+ g_free (name);
+ } else {
+ if (cut) {
+ status_string = g_strdup_printf (ngettext("The %'d selected item will be moved "
+ "if you select the Paste command",
+ "The %'d selected items will be moved "
+ "if you select the Paste command",
+ count),
+ count);
+ } else {
+ status_string = g_strdup_printf (ngettext("The %'d selected item will be copied "
+ "if you select the Paste command",
+ "The %'d selected items will be copied "
+ "if you select the Paste command",
+ count),
+ count);
+ }
+ }
+
+ caja_window_slot_info_set_status (view->details->slot,
+ status_string);
+ g_free (status_string);
+}
+
+static void
+action_copy_files_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ FMDirectoryView *view;
+ GList *selection;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+
+ selection = fm_directory_view_get_selection_for_file_transfer (view);
+ copy_or_cut_files (view, selection, FALSE);
+ caja_file_list_free (selection);
+}
+
+static void
+move_copy_selection_to_location (FMDirectoryView *view,
+ int copy_action,
+ char *target_uri)
+{
+ GList *selection, *uris, *l;
+
+ selection = fm_directory_view_get_selection_for_file_transfer (view);
+ if (selection == NULL) {
+ return;
+ }
+
+ uris = NULL;
+ for (l = selection; l != NULL; l = l->next) {
+ uris = g_list_prepend (uris,
+ caja_file_get_uri ((CajaFile *) l->data));
+ }
+ uris = g_list_reverse (uris);
+
+ fm_directory_view_move_copy_items (uris, NULL, target_uri,
+ copy_action,
+ 0, 0,
+ view);
+
+ eel_g_list_free_deep (uris);
+ caja_file_list_free (selection);
+}
+
+static void
+move_copy_selection_to_next_pane (FMDirectoryView *view,
+ int copy_action)
+{
+ CajaWindowSlotInfo *slot;
+ char *dest_location;
+
+ slot = caja_window_info_get_extra_slot (fm_directory_view_get_caja_window (view));
+ g_return_if_fail (slot != NULL);
+
+ dest_location = caja_window_slot_info_get_current_location (slot);
+ g_return_if_fail (dest_location != NULL);
+
+ move_copy_selection_to_location (view, copy_action, dest_location);
+}
+
+static void
+action_copy_to_next_pane_callback (GtkAction *action, gpointer callback_data)
+{
+ FMDirectoryView *view;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+ move_copy_selection_to_next_pane (view,
+ GDK_ACTION_COPY);
+}
+
+static void
+action_move_to_next_pane_callback (GtkAction *action, gpointer callback_data)
+{
+ CajaWindowSlotInfo *slot;
+ char *dest_location;
+ FMDirectoryView *view;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+
+ slot = caja_window_info_get_extra_slot (fm_directory_view_get_caja_window (view));
+ g_return_if_fail (slot != NULL);
+
+ dest_location = caja_window_slot_info_get_current_location (slot);
+ g_return_if_fail (dest_location != NULL);
+
+ move_copy_selection_to_location (view, GDK_ACTION_MOVE, dest_location);
+}
+
+static void
+action_copy_to_home_callback (GtkAction *action, gpointer callback_data)
+{
+ FMDirectoryView *view;
+ char *dest_location;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+
+ dest_location = caja_get_home_directory_uri ();
+ move_copy_selection_to_location (view, GDK_ACTION_COPY, dest_location);
+ g_free (dest_location);
+}
+
+static void
+action_move_to_home_callback (GtkAction *action, gpointer callback_data)
+{
+ FMDirectoryView *view;
+ char *dest_location;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+
+ dest_location = caja_get_home_directory_uri ();
+ move_copy_selection_to_location (view, GDK_ACTION_MOVE, dest_location);
+ g_free (dest_location);
+}
+
+static void
+action_copy_to_desktop_callback (GtkAction *action, gpointer callback_data)
+{
+ FMDirectoryView *view;
+ char *dest_location;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+
+ dest_location = caja_get_desktop_directory_uri ();
+ move_copy_selection_to_location (view, GDK_ACTION_COPY, dest_location);
+ g_free (dest_location);
+}
+
+static void
+action_move_to_desktop_callback (GtkAction *action, gpointer callback_data)
+{
+ FMDirectoryView *view;
+ char *dest_location;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+
+ dest_location = caja_get_desktop_directory_uri ();
+ move_copy_selection_to_location (view, GDK_ACTION_MOVE, dest_location);
+ g_free (dest_location);
+}
+
+static void
+action_cut_files_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ FMDirectoryView *view;
+ GList *selection;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+
+ selection = fm_directory_view_get_selection_for_file_transfer (view);
+ copy_or_cut_files (view, selection, TRUE);
+ caja_file_list_free (selection);
+}
+
+static void
+paste_clipboard_data (FMDirectoryView *view,
+ GtkSelectionData *selection_data,
+ char *destination_uri)
+{
+ gboolean cut;
+ GList *item_uris;
+
+ cut = FALSE;
+ item_uris = caja_clipboard_get_uri_list_from_selection_data (selection_data, &cut,
+ copied_files_atom);
+
+ if (item_uris == NULL|| destination_uri == NULL) {
+ caja_window_slot_info_set_status (view->details->slot,
+ _("There is nothing on the clipboard to paste."));
+ } else {
+ fm_directory_view_move_copy_items (item_uris, NULL, destination_uri,
+ cut ? GDK_ACTION_MOVE : GDK_ACTION_COPY,
+ 0, 0,
+ view);
+
+ /* If items are cut then remove from clipboard */
+ if (cut) {
+ gtk_clipboard_clear (caja_clipboard_get (GTK_WIDGET (view)));
+ }
+
+ eel_g_list_free_deep (item_uris);
+ }
+}
+
+static void
+paste_clipboard_received_callback (GtkClipboard *clipboard,
+ GtkSelectionData *selection_data,
+ gpointer data)
+{
+ FMDirectoryView *view;
+ char *view_uri;
+
+ view = FM_DIRECTORY_VIEW (data);
+
+ view_uri = fm_directory_view_get_backing_uri (view);
+
+ if (view->details->window != NULL) {
+ paste_clipboard_data (view, selection_data, view_uri);
+ }
+
+ g_free (view_uri);
+
+ g_object_unref (view);
+}
+
+typedef struct {
+ FMDirectoryView *view;
+ CajaFile *target;
+} PasteIntoData;
+
+static void
+paste_into_clipboard_received_callback (GtkClipboard *clipboard,
+ GtkSelectionData *selection_data,
+ gpointer callback_data)
+{
+ PasteIntoData *data;
+ FMDirectoryView *view;
+ char *directory_uri;
+
+ data = (PasteIntoData *) callback_data;
+
+ view = FM_DIRECTORY_VIEW (data->view);
+
+ if (view->details->window != NULL) {
+ directory_uri = caja_file_get_activation_uri (data->target);
+
+ paste_clipboard_data (view, selection_data, directory_uri);
+
+ g_free (directory_uri);
+ }
+
+ g_object_unref (view);
+ caja_file_unref (data->target);
+ g_free (data);
+}
+
+static void
+action_paste_files_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ FMDirectoryView *view;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+
+ g_object_ref (view);
+ gtk_clipboard_request_contents (caja_clipboard_get (GTK_WIDGET (view)),
+ copied_files_atom,
+ paste_clipboard_received_callback,
+ view);
+}
+
+static void
+paste_into (FMDirectoryView *view,
+ CajaFile *target)
+{
+ PasteIntoData *data;
+
+ g_assert (FM_IS_DIRECTORY_VIEW (view));
+ g_assert (CAJA_IS_FILE (target));
+
+ data = g_new (PasteIntoData, 1);
+
+ data->view = g_object_ref (view);
+ data->target = caja_file_ref (target);
+
+ gtk_clipboard_request_contents (caja_clipboard_get (GTK_WIDGET (view)),
+ copied_files_atom,
+ paste_into_clipboard_received_callback,
+ data);
+}
+
+static void
+action_paste_files_into_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ FMDirectoryView *view;
+ GList *selection;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+ selection = fm_directory_view_get_selection (view);
+ if (selection != NULL) {
+ paste_into (view, CAJA_FILE (selection->data));
+ caja_file_list_free (selection);
+ }
+
+}
+
+static void
+real_action_rename (FMDirectoryView *view,
+ gboolean select_all)
+{
+ CajaFile *file;
+ GList *selection;
+
+ g_assert (FM_IS_DIRECTORY_VIEW (view));
+
+ selection = fm_directory_view_get_selection (view);
+
+ if (selection_not_empty_in_menu_callback (view, selection)) {
+ file = CAJA_FILE (selection->data);
+ if (!select_all) {
+ /* directories don't have a file extension, so
+ * they are always pre-selected as a whole */
+ select_all = caja_file_is_directory (file);
+ }
+ EEL_CALL_METHOD (FM_DIRECTORY_VIEW_CLASS, view, start_renaming_file, (view, file, select_all));
+ }
+
+ caja_file_list_free (selection);
+}
+
+static void
+action_rename_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ real_action_rename (FM_DIRECTORY_VIEW (callback_data), FALSE);
+}
+
+static void
+action_rename_select_all_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ real_action_rename (FM_DIRECTORY_VIEW (callback_data), TRUE);
+}
+
+static void
+file_mount_callback (CajaFile *file,
+ GFile *result_location,
+ GError *error,
+ gpointer callback_data)
+{
+ if (error != NULL &&
+ (error->domain != G_IO_ERROR ||
+ (error->code != G_IO_ERROR_CANCELLED &&
+ error->code != G_IO_ERROR_FAILED_HANDLED &&
+ error->code != G_IO_ERROR_ALREADY_MOUNTED))) {
+ eel_show_error_dialog (_("Unable to mount location"),
+ error->message, NULL);
+ }
+}
+
+static void
+file_unmount_callback (CajaFile *file,
+ GFile *result_location,
+ GError *error,
+ gpointer callback_data)
+{
+ FMDirectoryView *view;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+ fm_directory_view_set_initiated_unmount (view, FALSE);
+ g_object_unref (view);
+
+ if (error != NULL &&
+ (error->domain != G_IO_ERROR ||
+ (error->code != G_IO_ERROR_CANCELLED &&
+ error->code != G_IO_ERROR_FAILED_HANDLED))) {
+ eel_show_error_dialog (_("Unable to unmount location"),
+ error->message, NULL);
+ }
+}
+
+static void
+file_eject_callback (CajaFile *file,
+ GFile *result_location,
+ GError *error,
+ gpointer callback_data)
+{
+ FMDirectoryView *view;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+ fm_directory_view_set_initiated_unmount (view, FALSE);
+ g_object_unref (view);
+
+ if (error != NULL &&
+ (error->domain != G_IO_ERROR ||
+ (error->code != G_IO_ERROR_CANCELLED &&
+ error->code != G_IO_ERROR_FAILED_HANDLED))) {
+ eel_show_error_dialog (_("Unable to eject location"),
+ error->message, NULL);
+ }
+}
+
+static void
+file_stop_callback (CajaFile *file,
+ GFile *result_location,
+ GError *error,
+ gpointer callback_data)
+{
+ if (error != NULL &&
+ (error->domain != G_IO_ERROR ||
+ (error->code != G_IO_ERROR_CANCELLED &&
+ error->code != G_IO_ERROR_FAILED_HANDLED))) {
+ eel_show_error_dialog (_("Unable to stop drive"),
+ error->message, NULL);
+ }
+}
+
+static void
+action_mount_volume_callback (GtkAction *action,
+ gpointer data)
+{
+ CajaFile *file;
+ GList *selection, *l;
+ FMDirectoryView *view;
+ GMountOperation *mount_op;
+
+ view = FM_DIRECTORY_VIEW (data);
+
+ selection = fm_directory_view_get_selection (view);
+ for (l = selection; l != NULL; l = l->next) {
+ file = CAJA_FILE (l->data);
+
+ if (caja_file_can_mount (file)) {
+ mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view));
+ g_mount_operation_set_password_save (mount_op, G_PASSWORD_SAVE_FOR_SESSION);
+ caja_file_mount (file, mount_op, NULL,
+ file_mount_callback, NULL);
+ g_object_unref (mount_op);
+ }
+ }
+ caja_file_list_free (selection);
+}
+
+static void
+action_unmount_volume_callback (GtkAction *action,
+ gpointer data)
+{
+ CajaFile *file;
+ GList *selection, *l;
+ FMDirectoryView *view;
+
+ view = FM_DIRECTORY_VIEW (data);
+
+ selection = fm_directory_view_get_selection (view);
+
+ for (l = selection; l != NULL; l = l->next) {
+ file = CAJA_FILE (l->data);
+ if (caja_file_can_unmount (file)) {
+ GMountOperation *mount_op;
+ mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view));
+ fm_directory_view_set_initiated_unmount (view, TRUE);
+ caja_file_unmount (file, mount_op, NULL,
+ file_unmount_callback, g_object_ref (view));
+ g_object_unref (mount_op);
+ }
+ }
+ caja_file_list_free (selection);
+}
+
+static void
+action_format_volume_callback (GtkAction *action,
+ gpointer data)
+{
+#ifdef TODO_GIO
+ CajaFile *file;
+ GList *selection, *l;
+ FMDirectoryView *view;
+
+ view = FM_DIRECTORY_VIEW (data);
+
+ selection = fm_directory_view_get_selection (view);
+ for (l = selection; l != NULL; l = l->next) {
+ file = CAJA_FILE (l->data);
+
+ if (something) {
+ g_spawn_command_line_async ("gfloppy", NULL);
+ }
+ }
+ caja_file_list_free (selection);
+#endif
+}
+
+static void
+action_eject_volume_callback (GtkAction *action,
+ gpointer data)
+{
+ CajaFile *file;
+ GList *selection, *l;
+ FMDirectoryView *view;
+
+ view = FM_DIRECTORY_VIEW (data);
+
+ selection = fm_directory_view_get_selection (view);
+ for (l = selection; l != NULL; l = l->next) {
+ file = CAJA_FILE (l->data);
+
+ if (caja_file_can_eject (file)) {
+ GMountOperation *mount_op;
+ mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view));
+ fm_directory_view_set_initiated_unmount (view, TRUE);
+ caja_file_eject (file, mount_op, NULL,
+ file_eject_callback, g_object_ref (view));
+ g_object_unref (mount_op);
+ }
+ }
+ caja_file_list_free (selection);
+}
+
+static void
+file_start_callback (CajaFile *file,
+ GFile *result_location,
+ GError *error,
+ gpointer callback_data)
+{
+ if (error != NULL &&
+ (error->domain != G_IO_ERROR ||
+ (error->code != G_IO_ERROR_CANCELLED &&
+ error->code != G_IO_ERROR_FAILED_HANDLED &&
+ error->code != G_IO_ERROR_ALREADY_MOUNTED))) {
+ eel_show_error_dialog (_("Unable to start location"),
+ error->message, NULL);
+ }
+}
+
+static void
+action_start_volume_callback (GtkAction *action,
+ gpointer data)
+{
+ CajaFile *file;
+ GList *selection, *l;
+ FMDirectoryView *view;
+ GMountOperation *mount_op;
+
+ view = FM_DIRECTORY_VIEW (data);
+
+ selection = fm_directory_view_get_selection (view);
+ for (l = selection; l != NULL; l = l->next) {
+ file = CAJA_FILE (l->data);
+
+ if (caja_file_can_start (file) || caja_file_can_start_degraded (file)) {
+ mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view));
+ caja_file_start (file, mount_op, NULL,
+ file_start_callback, NULL);
+ g_object_unref (mount_op);
+ }
+ }
+ caja_file_list_free (selection);
+}
+
+static void
+action_stop_volume_callback (GtkAction *action,
+ gpointer data)
+{
+ CajaFile *file;
+ GList *selection, *l;
+ FMDirectoryView *view;
+
+ view = FM_DIRECTORY_VIEW (data);
+
+ selection = fm_directory_view_get_selection (view);
+ for (l = selection; l != NULL; l = l->next) {
+ file = CAJA_FILE (l->data);
+
+ if (caja_file_can_stop (file)) {
+ GMountOperation *mount_op;
+ mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view));
+ caja_file_stop (file, mount_op, NULL,
+ file_stop_callback, NULL);
+ g_object_unref (mount_op);
+ }
+ }
+ caja_file_list_free (selection);
+}
+
+static void
+action_detect_media_callback (GtkAction *action,
+ gpointer data)
+{
+ CajaFile *file;
+ GList *selection, *l;
+ FMDirectoryView *view;
+
+ view = FM_DIRECTORY_VIEW (data);
+
+ selection = fm_directory_view_get_selection (view);
+ for (l = selection; l != NULL; l = l->next) {
+ file = CAJA_FILE (l->data);
+
+ if (caja_file_can_poll_for_media (file) && !caja_file_is_media_check_automatic (file)) {
+ caja_file_poll_for_media (file);
+ }
+ }
+ caja_file_list_free (selection);
+}
+
+static void
+action_self_mount_volume_callback (GtkAction *action,
+ gpointer data)
+{
+ CajaFile *file;
+ FMDirectoryView *view;
+ GMountOperation *mount_op;
+
+ view = FM_DIRECTORY_VIEW (data);
+
+ file = fm_directory_view_get_directory_as_file (view);
+ if (file == NULL) {
+ return;
+ }
+
+ mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view));
+ g_mount_operation_set_password_save (mount_op, G_PASSWORD_SAVE_FOR_SESSION);
+ caja_file_mount (file, mount_op, NULL, file_mount_callback, NULL);
+ g_object_unref (mount_op);
+}
+
+static void
+action_self_unmount_volume_callback (GtkAction *action,
+ gpointer data)
+{
+ CajaFile *file;
+ FMDirectoryView *view;
+ GMountOperation *mount_op;
+
+ view = FM_DIRECTORY_VIEW (data);
+
+ file = fm_directory_view_get_directory_as_file (view);
+ if (file == NULL) {
+ return;
+ }
+
+ mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view));
+ fm_directory_view_set_initiated_unmount (view, TRUE);
+ caja_file_unmount (file, mount_op, NULL, file_unmount_callback, g_object_ref (view));
+ g_object_unref (mount_op);
+}
+
+static void
+action_self_eject_volume_callback (GtkAction *action,
+ gpointer data)
+{
+ CajaFile *file;
+ FMDirectoryView *view;
+ GMountOperation *mount_op;
+
+ view = FM_DIRECTORY_VIEW (data);
+
+ file = fm_directory_view_get_directory_as_file (view);
+ if (file == NULL) {
+ return;
+ }
+
+ mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view));
+ fm_directory_view_set_initiated_unmount (view, TRUE);
+ caja_file_eject (file, mount_op, NULL, file_eject_callback, g_object_ref (view));
+ g_object_unref (mount_op);
+}
+
+static void
+action_self_format_volume_callback (GtkAction *action,
+ gpointer data)
+{
+ CajaFile *file;
+ FMDirectoryView *view;
+
+ view = FM_DIRECTORY_VIEW (data);
+
+ file = fm_directory_view_get_directory_as_file (view);
+ if (file == NULL) {
+ return;
+ }
+
+#ifdef TODO_GIO
+ if (something) {
+ g_spawn_command_line_async ("gfloppy", NULL);
+ }
+#endif
+}
+
+static void
+action_self_start_volume_callback (GtkAction *action,
+ gpointer data)
+{
+ CajaFile *file;
+ FMDirectoryView *view;
+ GMountOperation *mount_op;
+
+ view = FM_DIRECTORY_VIEW (data);
+
+ file = fm_directory_view_get_directory_as_file (view);
+ if (file == NULL) {
+ return;
+ }
+
+ mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view));
+ caja_file_start (file, mount_op, NULL, file_start_callback, NULL);
+ g_object_unref (mount_op);
+}
+
+static void
+action_self_stop_volume_callback (GtkAction *action,
+ gpointer data)
+{
+ CajaFile *file;
+ FMDirectoryView *view;
+ GMountOperation *mount_op;
+
+ view = FM_DIRECTORY_VIEW (data);
+
+ file = fm_directory_view_get_directory_as_file (view);
+ if (file == NULL) {
+ return;
+ }
+
+ mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view));
+ caja_file_stop (file, mount_op, NULL,
+ file_stop_callback, NULL);
+ g_object_unref (mount_op);
+}
+
+static void
+action_self_detect_media_callback (GtkAction *action,
+ gpointer data)
+{
+ CajaFile *file;
+ FMDirectoryView *view;
+
+ view = FM_DIRECTORY_VIEW (data);
+
+ file = fm_directory_view_get_directory_as_file (view);
+ if (file == NULL) {
+ return;
+ }
+
+ caja_file_poll_for_media (file);
+}
+
+static void
+action_location_mount_volume_callback (GtkAction *action,
+ gpointer data)
+{
+ CajaFile *file;
+ FMDirectoryView *view;
+ GMountOperation *mount_op;
+
+ view = FM_DIRECTORY_VIEW (data);
+
+ file = view->details->location_popup_directory_as_file;
+ if (file == NULL) {
+ return;
+ }
+
+ mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view));
+ g_mount_operation_set_password_save (mount_op, G_PASSWORD_SAVE_FOR_SESSION);
+ caja_file_mount (file, mount_op, NULL, file_mount_callback, NULL);
+ g_object_unref (mount_op);
+}
+
+static void
+action_location_unmount_volume_callback (GtkAction *action,
+ gpointer data)
+{
+ CajaFile *file;
+ FMDirectoryView *view;
+ GMountOperation *mount_op;
+
+ view = FM_DIRECTORY_VIEW (data);
+
+ file = view->details->location_popup_directory_as_file;
+ if (file == NULL) {
+ return;
+ }
+
+ mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view));
+ fm_directory_view_set_initiated_unmount (view, TRUE);
+ caja_file_unmount (file, mount_op, NULL,
+ file_unmount_callback, g_object_ref (view));
+ g_object_unref (mount_op);
+}
+
+static void
+action_location_eject_volume_callback (GtkAction *action,
+ gpointer data)
+{
+ CajaFile *file;
+ FMDirectoryView *view;
+ GMountOperation *mount_op;
+
+ view = FM_DIRECTORY_VIEW (data);
+
+ file = view->details->location_popup_directory_as_file;
+ if (file == NULL) {
+ return;
+ }
+
+ mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view));
+ fm_directory_view_set_initiated_unmount (view, TRUE);
+ caja_file_eject (file, mount_op, NULL,
+ file_eject_callback, g_object_ref (view));
+ g_object_unref (mount_op);
+}
+
+static void
+action_location_format_volume_callback (GtkAction *action,
+ gpointer data)
+{
+ CajaFile *file;
+ FMDirectoryView *view;
+
+ view = FM_DIRECTORY_VIEW (data);
+
+ file = view->details->location_popup_directory_as_file;
+ if (file == NULL) {
+ return;
+ }
+
+#ifdef TODO_GIO
+ if (something) {
+ g_spawn_command_line_async ("gfloppy", NULL);
+ }
+#endif
+}
+
+static void
+action_location_start_volume_callback (GtkAction *action,
+ gpointer data)
+{
+ CajaFile *file;
+ FMDirectoryView *view;
+ GMountOperation *mount_op;
+
+ view = FM_DIRECTORY_VIEW (data);
+
+ file = view->details->location_popup_directory_as_file;
+ if (file == NULL) {
+ return;
+ }
+
+ mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view));
+ caja_file_start (file, mount_op, NULL, file_start_callback, NULL);
+ g_object_unref (mount_op);
+}
+
+static void
+action_location_stop_volume_callback (GtkAction *action,
+ gpointer data)
+{
+ CajaFile *file;
+ FMDirectoryView *view;
+ GMountOperation *mount_op;
+
+ view = FM_DIRECTORY_VIEW (data);
+
+ file = view->details->location_popup_directory_as_file;
+ if (file == NULL) {
+ return;
+ }
+
+ mount_op = gtk_mount_operation_new (fm_directory_view_get_containing_window (view));
+ caja_file_stop (file, mount_op, NULL,
+ file_stop_callback, NULL);
+ g_object_unref (mount_op);
+}
+
+static void
+action_location_detect_media_callback (GtkAction *action,
+ gpointer data)
+{
+ CajaFile *file;
+ FMDirectoryView *view;
+
+ view = FM_DIRECTORY_VIEW (data);
+
+ file = view->details->location_popup_directory_as_file;
+ if (file == NULL) {
+ return;
+ }
+
+ caja_file_poll_for_media (file);
+}
+
+static void
+connect_to_server_response_callback (GtkDialog *dialog,
+ int response_id,
+ gpointer data)
+{
+ GtkEntry *entry;
+ char *uri;
+ const char *name;
+ char *icon;
+
+ entry = GTK_ENTRY (data);
+
+ switch (response_id) {
+ case GTK_RESPONSE_OK:
+ uri = g_object_get_data (G_OBJECT (dialog), "link-uri");
+ icon = g_object_get_data (G_OBJECT (dialog), "link-icon");
+ name = gtk_entry_get_text (entry);
+#ifdef GIO_CONVERSION_DONE
+ mate_vfs_connect_to_server (uri, (char *)name, icon);
+#endif
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+ break;
+ case GTK_RESPONSE_NONE:
+ case GTK_RESPONSE_DELETE_EVENT:
+ case GTK_RESPONSE_CANCEL:
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+ break;
+ default :
+ g_assert_not_reached ();
+ }
+}
+
+static void
+entry_activate_callback (GtkEntry *entry,
+ gpointer user_data)
+{
+ GtkDialog *dialog;
+
+ dialog = GTK_DIALOG (user_data);
+ gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+}
+
+static void
+action_connect_to_server_link_callback (GtkAction *action,
+ gpointer data)
+{
+ CajaFile *file;
+ GList *selection;
+ FMDirectoryView *view;
+ char *uri;
+ CajaIconInfo *icon;
+ const char *icon_name;
+ char *name;
+ GtkWidget *dialog;
+ GtkWidget *label;
+ GtkWidget *entry;
+ GtkWidget *box;
+ char *title;
+
+ view = FM_DIRECTORY_VIEW (data);
+
+ selection = fm_directory_view_get_selection (view);
+
+ if (!eel_g_list_exactly_one_item (selection)) {
+ caja_file_list_free (selection);
+ return;
+ }
+
+ file = CAJA_FILE (selection->data);
+
+ uri = caja_file_get_activation_uri (file);
+ icon = caja_file_get_icon (file, CAJA_ICON_SIZE_STANDARD, 0);
+ icon_name = caja_icon_info_get_used_name (icon);
+ name = caja_file_get_display_name (file);
+
+ if (uri != NULL) {
+ title = g_strdup_printf (_("Connect to Server %s"), name);
+ dialog = gtk_dialog_new_with_buttons (title,
+ fm_directory_view_get_containing_window (view),
+ GTK_DIALOG_NO_SEPARATOR,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ _("_Connect"), GTK_RESPONSE_OK,
+ NULL);
+
+ g_object_set_data_full (G_OBJECT (dialog), "link-uri", g_strdup (uri), g_free);
+ g_object_set_data_full (G_OBJECT (dialog), "link-icon", g_strdup (icon_name), g_free);
+
+ gtk_container_set_border_width (GTK_CONTAINER (dialog), 5);
+ gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), 2);
+
+ box = gtk_hbox_new (FALSE, 12);
+ gtk_widget_show (box);
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
+ box, TRUE, TRUE, 0);
+
+ label = gtk_label_new_with_mnemonic (_("Link _name:"));
+ gtk_widget_show (label);
+
+ gtk_box_pack_start (GTK_BOX (box), label, TRUE, TRUE, 12);
+
+ entry = gtk_entry_new ();
+ if (name) {
+ gtk_entry_set_text (GTK_ENTRY (entry), name);
+ }
+ g_signal_connect (entry,
+ "activate",
+ G_CALLBACK (entry_activate_callback),
+ dialog);
+
+ gtk_widget_show (entry);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry);
+
+ gtk_box_pack_start (GTK_BOX (box), entry, TRUE, TRUE, 12);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog),
+ GTK_RESPONSE_OK);
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (connect_to_server_response_callback),
+ entry);
+ gtk_widget_show (dialog);
+ }
+
+ g_free (uri);
+ g_object_unref (icon);
+ g_free (name);
+}
+
+static void
+action_location_open_alternate_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ FMDirectoryView *view;
+ CajaFile *file;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+
+ file = view->details->location_popup_directory_as_file;
+ if (file == NULL) {
+ return;
+ }
+
+ fm_directory_view_activate_file (view,
+ file,
+ CAJA_WINDOW_OPEN_IN_NAVIGATION,
+ 0);
+}
+
+static void
+action_location_open_in_new_tab_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ FMDirectoryView *view;
+ CajaFile *file;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+
+ file = view->details->location_popup_directory_as_file;
+ if (file == NULL) {
+ return;
+ }
+
+ fm_directory_view_activate_file (view,
+ file,
+ CAJA_WINDOW_OPEN_ACCORDING_TO_MODE,
+ CAJA_WINDOW_OPEN_FLAG_NEW_TAB);
+}
+
+static void
+action_location_open_folder_window_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ FMDirectoryView *view;
+ CajaFile *file;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+
+ file = view->details->location_popup_directory_as_file;
+ g_return_if_fail (file != NULL);
+
+ fm_directory_view_activate_file (view,
+ file,
+ CAJA_WINDOW_OPEN_IN_SPATIAL,
+ 0);
+}
+
+static void
+action_location_cut_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ FMDirectoryView *view;
+ CajaFile *file;
+ GList *files;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+
+ file = view->details->location_popup_directory_as_file;
+ g_return_if_fail (file != NULL);
+
+ files = g_list_append (NULL, file);
+ copy_or_cut_files (view, files, TRUE);
+ g_list_free (files);
+}
+
+static void
+action_location_copy_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ FMDirectoryView *view;
+ CajaFile *file;
+ GList *files;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+
+ file = view->details->location_popup_directory_as_file;
+ g_return_if_fail (file != NULL);
+
+ files = g_list_append (NULL, file);
+ copy_or_cut_files (view, files, FALSE);
+ g_list_free (files);
+}
+
+static void
+action_location_paste_files_into_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ FMDirectoryView *view;
+ CajaFile *file;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+
+ file = view->details->location_popup_directory_as_file;
+ g_return_if_fail (file != NULL);
+
+ paste_into (view, file);
+}
+
+static void
+action_location_trash_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ FMDirectoryView *view;
+ CajaFile *file;
+ GList *files;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+
+ file = view->details->location_popup_directory_as_file;
+ g_return_if_fail (file != NULL);
+
+ files = g_list_append (NULL, file);
+ trash_or_delete_files (fm_directory_view_get_containing_window (view),
+ files, TRUE,
+ view);
+ g_list_free (files);
+}
+
+static void
+action_location_delete_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ FMDirectoryView *view;
+ CajaFile *file;
+ GFile *location;
+ GList *files;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+
+ file = view->details->location_popup_directory_as_file;
+ g_return_if_fail (file != NULL);
+
+ location = caja_file_get_location (file);
+
+ files = g_list_append (NULL, location);
+ caja_file_operations_delete (files, fm_directory_view_get_containing_window (view),
+ NULL, NULL);
+
+ eel_g_object_list_free (files);
+}
+
+static void
+action_location_restore_from_trash_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ FMDirectoryView *view;
+ CajaFile *file;
+ GList l;
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+ file = view->details->location_popup_directory_as_file;
+
+ l.prev = NULL;
+ l.next = NULL;
+ l.data = file;
+ caja_restore_files_from_trash (&l,
+ fm_directory_view_get_containing_window (view));
+}
+
+static void
+fm_directory_view_init_show_hidden_files (FMDirectoryView *view)
+{
+ CajaWindowShowHiddenFilesMode mode;
+ gboolean show_hidden_changed;
+ gboolean show_hidden_default_setting;
+
+ if (view->details->ignore_hidden_file_preferences) {
+ return;
+ }
+
+ show_hidden_changed = FALSE;
+ mode = caja_window_info_get_hidden_files_mode (view->details->window);
+
+ if (mode == CAJA_WINDOW_SHOW_HIDDEN_FILES_DEFAULT) {
+ show_hidden_default_setting = eel_preferences_get_boolean (CAJA_PREFERENCES_SHOW_HIDDEN_FILES);
+ if (show_hidden_default_setting != view->details->show_hidden_files) {
+ view->details->show_hidden_files = show_hidden_default_setting;
+ view->details->show_backup_files = show_hidden_default_setting;
+ show_hidden_changed = TRUE;
+ }
+ } else {
+ if (mode == CAJA_WINDOW_SHOW_HIDDEN_FILES_ENABLE) {
+ show_hidden_changed = !view->details->show_hidden_files;
+ view->details->show_hidden_files = TRUE;
+ view->details->show_backup_files = TRUE;
+ } else {
+ show_hidden_changed = view->details->show_hidden_files;
+ view->details->show_hidden_files = FALSE;
+ view->details->show_backup_files = FALSE;
+ }
+ }
+
+ if (show_hidden_changed && (view->details->model != NULL)) {
+ load_directory (view, view->details->model);
+ }
+
+}
+
+static const GtkActionEntry directory_view_entries[] = {
+ /* name, stock id, label */ { "New Documents", "document-new", N_("Create _Document") },
+ /* name, stock id, label */ { "Open With", NULL, N_("Open Wit_h"),
+ NULL, N_("Choose a program with which to open the selected item") },
+ /* name, stock id */ { "Properties", GTK_STOCK_PROPERTIES,
+ /* label, accelerator */ N_("_Properties"), "<alt>Return",
+ /* tooltip */ N_("View or modify the properties of each selected item"),
+ G_CALLBACK (action_properties_callback) },
+ /* name, stock id */ { "PropertiesAccel", NULL,
+ /* label, accelerator */ "PropertiesAccel", "<control>I",
+ /* tooltip */ NULL,
+ G_CALLBACK (action_properties_callback) },
+ /* name, stock id */ { "New Folder", "folder-new",
+ /* label, accelerator */ N_("Create _Folder"), "<control><shift>N",
+ /* tooltip */ N_("Create a new empty folder inside this folder"),
+ G_CALLBACK (action_new_folder_callback) },
+ /* name, stock id, label */ { "No Templates", NULL, N_("No templates installed") },
+ /* name, stock id */ { "New Empty File", NULL,
+ /* translators: this is used to indicate that a file doesn't contain anything */
+ /* label, accelerator */ N_("_Empty File"), NULL,
+ /* tooltip */ N_("Create a new empty file inside this folder"),
+ G_CALLBACK (action_new_empty_file_callback) },
+ /* name, stock id */ { "New Launcher", NULL,
+ /* label, accelerator */ N_("Create L_auncher..."), NULL,
+ /* tooltip */ N_("Create a new launcher"),
+ G_CALLBACK (action_new_launcher_callback) },
+ /* name, stock id */ { "Open", NULL,
+ /* label, accelerator */ N_("_Open"), "<control>o",
+ /* tooltip */ N_("Open the selected item in this window"),
+ G_CALLBACK (action_open_callback) },
+ /* name, stock id */ { "OpenAccel", NULL,
+ /* label, accelerator */ "OpenAccel", "<alt>Down",
+ /* tooltip */ NULL,
+ G_CALLBACK (action_open_callback) },
+ /* name, stock id */ { "OpenAlternate", NULL,
+ /* label, accelerator */ N_("Open in Navigation Window"), "<control><shift>o",
+ /* tooltip */ N_("Open each selected item in a navigation window"),
+ G_CALLBACK (action_open_alternate_callback) },
+ /* name, stock id */ { "OpenInNewTab", NULL,
+ /* label, accelerator */ N_("Open in New _Tab"), "<control><shift>o",
+ /* tooltip */ N_("Open each selected item in a new tab"),
+ G_CALLBACK (action_open_new_tab_callback) },
+ /* name, stock id */ { "OpenFolderWindow", NULL,
+ /* label, accelerator */ N_("Open in _Folder Window"), NULL,
+ /* tooltip */ N_("Open each selected item in a folder window"),
+ G_CALLBACK (action_open_folder_window_callback) },
+ /* name, stock id */ { "OtherApplication1", NULL,
+ /* label, accelerator */ N_("Other _Application..."), NULL,
+ /* tooltip */ N_("Choose another application with which to open the selected item"),
+ G_CALLBACK (action_other_application_callback) },
+ /* name, stock id */ { "OtherApplication2", NULL,
+ /* label, accelerator */ N_("Open With Other _Application..."), NULL,
+ /* tooltip */ N_("Choose another application with which to open the selected item"),
+ G_CALLBACK (action_other_application_callback) },
+ /* name, stock id */ { "Open Scripts Folder", NULL,
+ /* label, accelerator */ N_("_Open Scripts Folder"), NULL,
+ /* tooltip */ N_("Show the folder containing the scripts that appear in this menu"),
+ G_CALLBACK (action_open_scripts_folder_callback) },
+ /* name, stock id */ { "Empty Trash", NULL,
+ /* label, accelerator */ N_("E_mpty Trash"), NULL,
+ /* tooltip */ N_("Delete all items in the Trash"),
+ G_CALLBACK (action_empty_trash_callback) },
+ /* name, stock id */ { "Cut", GTK_STOCK_CUT,
+ /* label, accelerator */ NULL, NULL,
+ /* tooltip */ N_("Prepare the selected files to be moved with a Paste command"),
+ G_CALLBACK (action_cut_files_callback) },
+ /* name, stock id */ { "Copy", GTK_STOCK_COPY,
+ /* label, accelerator */ NULL, NULL,
+ /* tooltip */ N_("Prepare the selected files to be copied with a Paste command"),
+ G_CALLBACK (action_copy_files_callback) },
+ /* name, stock id */ { "Paste", GTK_STOCK_PASTE,
+ /* label, accelerator */ NULL, NULL,
+ /* tooltip */ N_("Move or copy files previously selected by a Cut or Copy command"),
+ G_CALLBACK (action_paste_files_callback) },
+ /* We make accelerator "" instead of null here to not inherit the stock
+ accelerator for paste */
+ /* name, stock id */ { "Paste Files Into", GTK_STOCK_PASTE,
+ /* label, accelerator */ N_("_Paste Into Folder"), "",
+ /* tooltip */ N_("Move or copy files previously selected by a Cut or Copy command into the selected folder"),
+ G_CALLBACK (action_paste_files_into_callback) },
+ /* name, stock id, label */ { "CopyToMenu", NULL, N_("Cop_y to") },
+ /* name, stock id, label */ { "MoveToMenu", NULL, N_("M_ove to") },
+ /* name, stock id */ { "Select All", NULL,
+ /* label, accelerator */ N_("Select _All"), "<control>A",
+ /* tooltip */ N_("Select all items in this window"),
+ G_CALLBACK (action_select_all_callback) },
+ /* name, stock id */ { "Select Pattern", NULL,
+ /* label, accelerator */ N_("Select I_tems Matching..."), "<control>S",
+ /* tooltip */ N_("Select items in this window matching a given pattern"),
+ G_CALLBACK (action_select_pattern_callback) },
+ /* name, stock id */ { "Invert Selection", NULL,
+ /* label, accelerator */ N_("_Invert Selection"), "<control><shift>I",
+ /* tooltip */ N_("Select all and only the items that are not currently selected"),
+ G_CALLBACK (action_invert_selection_callback) },
+ /* name, stock id */ { "Duplicate", NULL,
+ /* label, accelerator */ N_("D_uplicate"), NULL,
+ /* tooltip */ N_("Duplicate each selected item"),
+ G_CALLBACK (action_duplicate_callback) },
+ /* name, stock id */ { "Create Link", NULL,
+ /* label, accelerator */ N_("Ma_ke Link"), "<control>M",
+ /* tooltip */ N_("Create a symbolic link for each selected item"),
+ G_CALLBACK (action_create_link_callback) },
+ /* name, stock id */ { "Rename", NULL,
+ /* label, accelerator */ N_("_Rename..."), "F2",
+ /* tooltip */ N_("Rename selected item"),
+ G_CALLBACK (action_rename_callback) },
+ /* name, stock id */ { "RenameSelectAll", NULL,
+ /* label, accelerator */ "RenameSelectAll", "<shift>F2",
+ /* tooltip */ NULL,
+ G_CALLBACK (action_rename_select_all_callback) },
+ /* name, stock id */ { "Trash", NULL,
+ /* label, accelerator */ N_("Mo_ve to Trash"), NULL,
+ /* tooltip */ N_("Move each selected item to the Trash"),
+ G_CALLBACK (action_trash_callback) },
+ /* name, stock id */ { "Delete", NULL,
+ /* label, accelerator */ N_("_Delete"), "<shift>Delete",
+ /* tooltip */ N_("Delete each selected item, without moving to the Trash"),
+ G_CALLBACK (action_delete_callback) },
+ /* name, stock id */ { "Restore From Trash", NULL,
+ /* label, accelerator */ N_("_Restore"), NULL,
+ NULL,
+ G_CALLBACK (action_restore_from_trash_callback) },
+ /*
+ * multiview-TODO: decide whether "Reset to Defaults" should
+ * be window-wide, and not just view-wide.
+ * Since this also resets the "Show hidden files" mode,
+ * it is a mixture of both ATM.
+ */
+ /* name, stock id */ { "Reset to Defaults", NULL,
+ /* label, accelerator */ N_("Reset View to _Defaults"), NULL,
+ /* tooltip */ N_("Reset sorting order and zoom level to match preferences for this view"),
+ G_CALLBACK (action_reset_to_defaults_callback) },
+ /* name, stock id */ { "Connect To Server Link", NULL,
+ /* label, accelerator */ N_("Connect To This Server"), NULL,
+ /* tooltip */ N_("Make a permanent connection to this server"),
+ G_CALLBACK (action_connect_to_server_link_callback) },
+ /* name, stock id */ { "Mount Volume", NULL,
+ /* label, accelerator */ N_("_Mount"), NULL,
+ /* tooltip */ N_("Mount the selected volume"),
+ G_CALLBACK (action_mount_volume_callback) },
+ /* name, stock id */ { "Unmount Volume", NULL,
+ /* label, accelerator */ N_("_Unmount"), NULL,
+ /* tooltip */ N_("Unmount the selected volume"),
+ G_CALLBACK (action_unmount_volume_callback) },
+ /* name, stock id */ { "Eject Volume", NULL,
+ /* label, accelerator */ N_("_Eject"), NULL,
+ /* tooltip */ N_("Eject the selected volume"),
+ G_CALLBACK (action_eject_volume_callback) },
+ /* name, stock id */ { "Format Volume", NULL,
+ /* label, accelerator */ N_("_Format"), NULL,
+ /* tooltip */ N_("Format the selected volume"),
+ G_CALLBACK (action_format_volume_callback) },
+ /* name, stock id */ { "Start Volume", NULL,
+ /* label, accelerator */ N_("_Start"), NULL,
+ /* tooltip */ N_("Start the selected volume"),
+ G_CALLBACK (action_start_volume_callback) },
+ /* name, stock id */ { "Stop Volume", NULL,
+ /* label, accelerator */ N_("_Stop"), NULL,
+ /* tooltip */ N_("Stop the selected volume"),
+ G_CALLBACK (action_stop_volume_callback) },
+ /* name, stock id */ { "Poll", NULL,
+ /* label, accelerator */ N_("_Detect Media"), NULL,
+ /* tooltip */ N_("Detect media in the selected drive"),
+ G_CALLBACK (action_detect_media_callback) },
+ /* name, stock id */ { "Self Mount Volume", NULL,
+ /* label, accelerator */ N_("_Mount"), NULL,
+ /* tooltip */ N_("Mount the volume associated with the open folder"),
+ G_CALLBACK (action_self_mount_volume_callback) },
+ /* name, stock id */ { "Self Unmount Volume", NULL,
+ /* label, accelerator */ N_("_Unmount"), NULL,
+ /* tooltip */ N_("Unmount the volume associated with the open folder"),
+ G_CALLBACK (action_self_unmount_volume_callback) },
+ /* name, stock id */ { "Self Eject Volume", NULL,
+ /* label, accelerator */ N_("_Eject"), NULL,
+ /* tooltip */ N_("Eject the volume associated with the open folder"),
+ G_CALLBACK (action_self_eject_volume_callback) },
+ /* name, stock id */ { "Self Format Volume", NULL,
+ /* label, accelerator */ N_("_Format"), NULL,
+ /* tooltip */ N_("Format the volume associated with the open folder"),
+ G_CALLBACK (action_self_format_volume_callback) },
+ /* name, stock id */ { "Self Start Volume", NULL,
+ /* label, accelerator */ N_("_Start"), NULL,
+ /* tooltip */ N_("Start the volume associated with the open folder"),
+ G_CALLBACK (action_self_start_volume_callback) },
+ /* name, stock id */ { "Self Stop Volume", NULL,
+ /* label, accelerator */ N_("_Stop"), NULL,
+ /* tooltip */ N_("Stop the volume associated with the open folder"),
+ G_CALLBACK (action_self_stop_volume_callback) },
+ /* name, stock id */ { "Self Poll", NULL,
+ /* label, accelerator */ N_("_Detect Media"), NULL,
+ /* tooltip */ N_("Detect media in the selected drive"),
+ G_CALLBACK (action_self_detect_media_callback) },
+ /* name, stock id */ { "OpenCloseParent", NULL,
+ /* label, accelerator */ N_("Open File and Close window"), "<alt><shift>Down",
+ /* tooltip */ NULL,
+ G_CALLBACK (action_open_close_parent_callback) },
+ /* name, stock id */ { "Save Search", NULL,
+ /* label, accelerator */ N_("Sa_ve Search"), NULL,
+ /* tooltip */ N_("Save the edited search"),
+ G_CALLBACK (action_save_search_callback) },
+ /* name, stock id */ { "Save Search As", NULL,
+ /* label, accelerator */ N_("Sa_ve Search As..."), NULL,
+ /* tooltip */ N_("Save the current search as a file"),
+ G_CALLBACK (action_save_search_as_callback) },
+
+ /* Location-specific actions */
+ /* name, stock id */ { FM_ACTION_LOCATION_OPEN_ALTERNATE, NULL,
+ /* label, accelerator */ N_("Open in Navigation Window"), "",
+ /* tooltip */ N_("Open this folder in a navigation window"),
+ G_CALLBACK (action_location_open_alternate_callback) },
+ /* name, stock id */ { FM_ACTION_LOCATION_OPEN_IN_NEW_TAB, NULL,
+ /* label, accelerator */ N_("Open in New _Tab"), "",
+ /* tooltip */ N_("Open this folder in a new tab"),
+ G_CALLBACK (action_location_open_in_new_tab_callback) },
+
+ /* name, stock id */ { FM_ACTION_LOCATION_OPEN_FOLDER_WINDOW, NULL,
+ /* label, accelerator */ N_("Open in _Folder Window"), "",
+ /* tooltip */ N_("Open this folder in a folder window"),
+ G_CALLBACK (action_location_open_folder_window_callback) },
+
+ /* name, stock id */ { FM_ACTION_LOCATION_CUT, GTK_STOCK_CUT,
+ /* label, accelerator */ NULL, "",
+ /* tooltip */ N_("Prepare this folder to be moved with a Paste command"),
+ G_CALLBACK (action_location_cut_callback) },
+ /* name, stock id */ { FM_ACTION_LOCATION_COPY, GTK_STOCK_COPY,
+ /* label, accelerator */ NULL, "",
+ /* tooltip */ N_("Prepare this folder to be copied with a Paste command"),
+ G_CALLBACK (action_location_copy_callback) },
+ /* name, stock id */ { FM_ACTION_LOCATION_PASTE_FILES_INTO, GTK_STOCK_PASTE,
+ /* label, accelerator */ N_("_Paste Into Folder"), "",
+ /* tooltip */ N_("Move or copy files previously selected by a Cut or Copy command into this folder"),
+ G_CALLBACK (action_location_paste_files_into_callback) },
+
+ /* name, stock id */ { FM_ACTION_LOCATION_TRASH, NULL,
+ /* label, accelerator */ N_("Mo_ve to Trash"), "",
+ /* tooltip */ N_("Move this folder to the Trash"),
+ G_CALLBACK (action_location_trash_callback) },
+ /* name, stock id */ { FM_ACTION_LOCATION_DELETE, CAJA_ICON_DELETE,
+ /* label, accelerator */ N_("_Delete"), "",
+ /* tooltip */ N_("Delete this folder, without moving to the Trash"),
+ G_CALLBACK (action_location_delete_callback) },
+ /* name, stock id */ { FM_ACTION_LOCATION_RESTORE_FROM_TRASH, NULL,
+ /* label, accelerator */ N_("_Restore"), NULL, NULL,
+ G_CALLBACK (action_location_restore_from_trash_callback) },
+
+ /* name, stock id */ { "Location Mount Volume", NULL,
+ /* label, accelerator */ N_("_Mount"), NULL,
+ /* tooltip */ N_("Mount the volume associated with this folder"),
+ G_CALLBACK (action_location_mount_volume_callback) },
+ /* name, stock id */ { "Location Unmount Volume", NULL,
+ /* label, accelerator */ N_("_Unmount"), NULL,
+ /* tooltip */ N_("Unmount the volume associated with this folder"),
+ G_CALLBACK (action_location_unmount_volume_callback) },
+ /* name, stock id */ { "Location Eject Volume", NULL,
+ /* label, accelerator */ N_("_Eject"), NULL,
+ /* tooltip */ N_("Eject the volume associated with this folder"),
+ G_CALLBACK (action_location_eject_volume_callback) },
+ /* name, stock id */ { "Location Format Volume", NULL,
+ /* label, accelerator */ N_("_Format"), NULL,
+ /* tooltip */ N_("Format the volume associated with this folder"),
+ G_CALLBACK (action_location_format_volume_callback) },
+ /* name, stock id */ { "Location Start Volume", NULL,
+ /* label, accelerator */ N_("_Start"), NULL,
+ /* tooltip */ N_("Start the volume associated with this folder"),
+ G_CALLBACK (action_location_start_volume_callback) },
+ /* name, stock id */ { "Location Stop Volume", NULL,
+ /* label, accelerator */ N_("_Stop"), NULL,
+ /* tooltip */ N_("Stop the volume associated with this folder"),
+ G_CALLBACK (action_location_stop_volume_callback) },
+ /* name, stock id */ { "Location Poll", NULL,
+ /* label, accelerator */ N_("_Detect Media"), NULL,
+ /* tooltip */ N_("Detect media in the selected drive"),
+ G_CALLBACK (action_location_detect_media_callback) },
+
+ /* name, stock id */ { "LocationProperties", GTK_STOCK_PROPERTIES,
+ /* label, accelerator */ N_("_Properties"), NULL,
+ /* tooltip */ N_("View or modify the properties of this folder"),
+ G_CALLBACK (action_location_properties_callback) },
+
+ /* name, stock id, label */ {FM_ACTION_COPY_TO_NEXT_PANE, NULL, N_("_Other pane"),
+ NULL, N_("Copy the current selection to the other pane in the window"),
+ G_CALLBACK (action_copy_to_next_pane_callback) },
+ /* name, stock id, label */ {FM_ACTION_MOVE_TO_NEXT_PANE, NULL, N_("_Other pane"),
+ NULL, N_("Move the current selection to the other pane in the window"),
+ G_CALLBACK (action_move_to_next_pane_callback) },
+ /* name, stock id, label */ {FM_ACTION_COPY_TO_HOME, CAJA_ICON_HOME,
+ N_("_Home Folder"), NULL,
+ N_("Copy the current selection to the home folder"),
+ G_CALLBACK (action_copy_to_home_callback) },
+ /* name, stock id, label */ {FM_ACTION_MOVE_TO_HOME, CAJA_ICON_HOME,
+ N_("_Home Folder"), NULL,
+ N_("Move the current selection to the home folder"),
+ G_CALLBACK (action_move_to_home_callback) },
+ /* name, stock id, label */ {FM_ACTION_COPY_TO_DESKTOP, CAJA_ICON_DESKTOP,
+ N_("_Desktop"), NULL,
+ N_("Copy the current selection to the desktop"),
+ G_CALLBACK (action_copy_to_desktop_callback) },
+ /* name, stock id, label */ {FM_ACTION_MOVE_TO_DESKTOP, CAJA_ICON_DESKTOP,
+ N_("_Desktop"), NULL,
+ N_("Move the current selection to the desktop"),
+ G_CALLBACK (action_move_to_desktop_callback) },
+};
+
+static void
+connect_proxy (FMDirectoryView *view,
+ GtkAction *action,
+ GtkWidget *proxy,
+ GtkActionGroup *action_group)
+{
+ GdkPixbuf *pixbuf;
+ GtkWidget *image;
+
+ if (strcmp (gtk_action_get_name (action), FM_ACTION_NEW_EMPTY_FILE) == 0 &&
+ GTK_IS_IMAGE_MENU_ITEM (proxy)) {
+ pixbuf = get_menu_icon ("text-x-generic");
+ if (pixbuf != NULL) {
+ image = gtk_image_new_from_pixbuf (pixbuf);
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (proxy), image);
+
+ g_object_unref (pixbuf);
+ }
+ }
+}
+
+static void
+pre_activate (FMDirectoryView *view,
+ GtkAction *action,
+ GtkActionGroup *action_group)
+{
+ GdkEvent *event;
+ GtkWidget *proxy;
+ gboolean activated_from_popup;
+
+ /* check whether action was activated through a popup menu.
+ * If not, unset the last stored context menu popup position */
+ activated_from_popup = FALSE;
+
+ event = gtk_get_current_event ();
+ proxy = gtk_get_event_widget (event);
+
+ if (proxy != NULL) {
+ GtkWidget *toplevel;
+ GdkWindowTypeHint hint;
+
+ toplevel = gtk_widget_get_toplevel (proxy);
+
+ if (GTK_IS_WINDOW (toplevel)) {
+ hint = gtk_window_get_type_hint (GTK_WINDOW (toplevel));
+
+ if (hint == GDK_WINDOW_TYPE_HINT_POPUP_MENU) {
+ activated_from_popup = TRUE;
+ }
+ }
+ }
+
+ if (!activated_from_popup) {
+ update_context_menu_position_from_event (view, NULL);
+ }
+}
+
+static void
+real_merge_menus (FMDirectoryView *view)
+{
+ GtkActionGroup *action_group;
+ GtkUIManager *ui_manager;
+ GtkAction *action;
+ const char *ui;
+ char *tooltip;
+
+ ui_manager = caja_window_info_get_ui_manager (view->details->window);
+
+ action_group = gtk_action_group_new ("DirViewActions");
+ gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
+ view->details->dir_action_group = action_group;
+ gtk_action_group_add_actions (action_group,
+ directory_view_entries, G_N_ELEMENTS (directory_view_entries),
+ view);
+
+ /* Translators: %s is a directory */
+ tooltip = g_strdup_printf(_("Run or manage scripts from %s"), "~/.config/caja/scripts");
+ /* Create a script action here specially because its tooltip is dynamic */
+ action = gtk_action_new ("Scripts", _("_Scripts"), tooltip, NULL);
+ gtk_action_group_add_action (action_group, action);
+ g_object_unref (action);
+ g_free (tooltip);
+
+ action = gtk_action_group_get_action (action_group, FM_ACTION_NO_TEMPLATES);
+ gtk_action_set_sensitive (action, FALSE);
+
+ g_signal_connect_object (action_group, "connect-proxy",
+ G_CALLBACK (connect_proxy), G_OBJECT (view),
+ G_CONNECT_SWAPPED);
+ g_signal_connect_object (action_group, "pre-activate",
+ G_CALLBACK (pre_activate), G_OBJECT (view),
+ G_CONNECT_SWAPPED);
+
+ /* Insert action group at end so clipboard action group ends up before it */
+ gtk_ui_manager_insert_action_group (ui_manager, action_group, -1);
+ g_object_unref (action_group); /* owned by ui manager */
+
+ ui = caja_ui_string_get ("caja-directory-view-ui.xml");
+ view->details->dir_merge_id = gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, NULL);
+ g_signal_connect_object (fm_directory_view_get_background (view), "settings_changed",
+ G_CALLBACK (schedule_update_menus), G_OBJECT (view),
+ G_CONNECT_SWAPPED);
+
+ view->details->scripts_invalid = TRUE;
+ view->details->templates_invalid = TRUE;
+}
+
+
+static gboolean
+can_paste_into_file (CajaFile *file)
+{
+ if (caja_file_is_directory (file) &&
+ caja_file_can_write (file)) {
+ return TRUE;
+ }
+ if (caja_file_has_activation_uri (file)) {
+ GFile *location;
+ CajaFile *activation_file;
+ gboolean res;
+
+ location = caja_file_get_activation_location (file);
+ activation_file = caja_file_get (location);
+ g_object_unref (location);
+
+ /* The target location might not have data for it read yet,
+ and we can't want to do sync I/O, so treat the unknown
+ case as can-write */
+ res = (caja_file_get_file_type (activation_file) == G_FILE_TYPE_UNKNOWN) ||
+ (caja_file_get_file_type (activation_file) == G_FILE_TYPE_DIRECTORY &&
+ caja_file_can_write (activation_file));
+
+ caja_file_unref (activation_file);
+
+ return res;
+ }
+
+ return FALSE;
+}
+
+static void
+clipboard_targets_received (GtkClipboard *clipboard,
+ GdkAtom *targets,
+ int n_targets,
+ gpointer user_data)
+{
+ FMDirectoryView *view;
+ gboolean can_paste;
+ int i;
+ GList *selection;
+ int count;
+ GtkAction *action;
+
+ view = FM_DIRECTORY_VIEW (user_data);
+ can_paste = FALSE;
+
+ if (view->details->window == NULL ||
+ !view->details->active) {
+ /* We've been destroyed or became inactive since call */
+ g_object_unref (view);
+ return;
+ }
+
+ if (targets) {
+ for (i=0; i < n_targets; i++) {
+ if (targets[i] == copied_files_atom) {
+ can_paste = TRUE;
+ }
+ }
+ }
+
+
+ selection = fm_directory_view_get_selection (view);
+ count = g_list_length (selection);
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_PASTE);
+ gtk_action_set_sensitive (action,
+ can_paste && !fm_directory_view_is_read_only (view));
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_PASTE_FILES_INTO);
+ gtk_action_set_sensitive (action,
+ can_paste && count == 1 &&
+ can_paste_into_file (CAJA_FILE (selection->data)));
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_LOCATION_PASTE_FILES_INTO);
+ g_object_set_data (G_OBJECT (action),
+ "can-paste-according-to-clipboard",
+ GINT_TO_POINTER (can_paste));
+ gtk_action_set_sensitive (action,
+ GPOINTER_TO_INT (g_object_get_data (G_OBJECT (action),
+ "can-paste-according-to-clipboard")) &&
+ GPOINTER_TO_INT (g_object_get_data (G_OBJECT (action),
+ "can-paste-according-to-destination")));
+
+ caja_file_list_free (selection);
+
+ g_object_unref (view);
+}
+
+static gboolean
+showing_trash_directory (FMDirectoryView *view)
+{
+ CajaFile *file;
+
+ file = fm_directory_view_get_directory_as_file (view);
+ if (file != NULL) {
+ return caja_file_is_in_trash (file);
+ }
+ return FALSE;
+}
+
+static gboolean
+should_show_empty_trash (FMDirectoryView *view)
+{
+ return (showing_trash_directory (view) || caja_window_info_get_window_type (view->details->window) == CAJA_WINDOW_NAVIGATION);
+}
+
+static gboolean
+file_list_all_are_folders (GList *file_list)
+{
+ GList *l;
+ CajaFile *file, *linked_file;
+ char *activation_uri;
+ gboolean is_dir;
+
+ for (l = file_list; l != NULL; l = l->next) {
+ file = CAJA_FILE (l->data);
+ if (caja_file_is_caja_link (file) &&
+ !CAJA_IS_DESKTOP_ICON_FILE (file)) {
+ if (caja_file_is_launcher (file)) {
+ return FALSE;
+ }
+
+ activation_uri = caja_file_get_activation_uri (file);
+
+ if (activation_uri == NULL) {
+ g_free (activation_uri);
+ return FALSE;
+ }
+
+ linked_file = caja_file_get_existing_by_uri (activation_uri);
+
+ /* We might not actually know the type of the linked file yet,
+ * however we don't want to schedule a read, since that might do things
+ * like ask for password etc. This is a bit unfortunate, but I don't
+ * know any way around it, so we do various heuristics here
+ * to get things mostly right
+ */
+ is_dir =
+ (linked_file != NULL &&
+ caja_file_is_directory (linked_file)) ||
+ (activation_uri != NULL &&
+ activation_uri[strlen (activation_uri) - 1] == '/');
+
+ caja_file_unref (linked_file);
+ g_free (activation_uri);
+
+ if (!is_dir) {
+ return FALSE;
+ }
+ } else if (!(caja_file_is_directory (file) ||
+ CAJA_IS_DESKTOP_ICON_FILE (file))) {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static void
+file_should_show_foreach (CajaFile *file,
+ gboolean *show_mount,
+ gboolean *show_unmount,
+ gboolean *show_eject,
+ gboolean *show_connect,
+ gboolean *show_format,
+ gboolean *show_start,
+ gboolean *show_stop,
+ gboolean *show_poll,
+ GDriveStartStopType *start_stop_type)
+{
+ char *uri;
+
+ *show_mount = FALSE;
+ *show_unmount = FALSE;
+ *show_eject = FALSE;
+ *show_connect = FALSE;
+ *show_format = FALSE;
+ *show_start = FALSE;
+ *show_stop = FALSE;
+ *show_poll = FALSE;
+
+ if (caja_file_can_eject (file)) {
+ *show_eject = TRUE;
+ }
+
+ if (caja_file_can_mount (file)) {
+ *show_mount = TRUE;
+
+#ifdef TODO_GIO
+ if (something &&
+ g_find_program_in_path ("gfloppy")) {
+ *show_format = TRUE;
+ }
+#endif
+ }
+
+ if (caja_file_can_start (file) || caja_file_can_start_degraded (file)) {
+ *show_start = TRUE;
+ }
+
+ if (caja_file_can_stop (file)) {
+ *show_stop = TRUE;
+ }
+
+ /* Dot not show both Unmount and Eject/Safe Removal; too confusing to
+ * have too many menu entries */
+ if (caja_file_can_unmount (file) && !*show_eject && !*show_stop) {
+ *show_unmount = TRUE;
+ }
+
+ if (caja_file_can_poll_for_media (file) && !caja_file_is_media_check_automatic (file)) {
+ *show_poll = TRUE;
+ }
+
+ *start_stop_type = caja_file_get_start_stop_type (file);
+
+ if (caja_file_is_caja_link (file)) {
+ uri = caja_file_get_activation_uri (file);
+ if (uri != NULL &&
+ (eel_istr_has_prefix (uri, "ftp:") ||
+ eel_istr_has_prefix (uri, "ssh:") ||
+ eel_istr_has_prefix (uri, "sftp:") ||
+ eel_istr_has_prefix (uri, "dav:") ||
+ eel_istr_has_prefix (uri, "davs:"))) {
+ *show_connect = TRUE;
+ }
+ g_free (uri);
+ }
+}
+
+static void
+file_should_show_self (CajaFile *file,
+ gboolean *show_mount,
+ gboolean *show_unmount,
+ gboolean *show_eject,
+ gboolean *show_format,
+ gboolean *show_start,
+ gboolean *show_stop,
+ gboolean *show_poll,
+ GDriveStartStopType *start_stop_type)
+{
+ *show_mount = FALSE;
+ *show_unmount = FALSE;
+ *show_eject = FALSE;
+ *show_format = FALSE;
+ *show_start = FALSE;
+ *show_stop = FALSE;
+ *show_poll = FALSE;
+
+ if (file == NULL) {
+ return;
+ }
+
+ if (caja_file_can_eject (file)) {
+ *show_eject = TRUE;
+ }
+
+ if (caja_file_can_mount (file)) {
+ *show_mount = TRUE;
+ }
+
+#ifdef TODO_GIO
+ if (something && g_find_program_in_path ("gfloppy")) {
+ *show_format = TRUE;
+ }
+#endif
+
+ if (caja_file_can_start (file) || caja_file_can_start_degraded (file)) {
+ *show_start = TRUE;
+ }
+
+ if (caja_file_can_stop (file)) {
+ *show_stop = TRUE;
+ }
+
+ /* Dot not show both Unmount and Eject/Safe Removal; too confusing to
+ * have too many menu entries */
+ if (caja_file_can_unmount (file) && !*show_eject && !*show_stop) {
+ *show_unmount = TRUE;
+ }
+
+ if (caja_file_can_poll_for_media (file) && !caja_file_is_media_check_automatic (file)) {
+ *show_poll = TRUE;
+ }
+
+ *start_stop_type = caja_file_get_start_stop_type (file);
+
+}
+
+static gboolean
+files_are_all_directories (GList *files)
+{
+ CajaFile *file;
+ GList *l;
+ gboolean all_directories;
+
+ all_directories = TRUE;
+
+ for (l = files; l != NULL; l = l->next) {
+ file = CAJA_FILE (l->data);
+ all_directories &= caja_file_is_directory (file);
+ }
+
+ return all_directories;
+}
+
+static gboolean
+files_is_none_directory (GList *files)
+{
+ CajaFile *file;
+ GList *l;
+ gboolean no_directory;
+
+ no_directory = TRUE;
+
+ for (l = files; l != NULL; l = l->next) {
+ file = CAJA_FILE (l->data);
+ no_directory &= !caja_file_is_directory (file);
+ }
+
+ return no_directory;
+}
+
+static void
+update_restore_from_trash_action (GtkAction *action,
+ GList *files,
+ gboolean is_self)
+{
+ CajaFile *original_file;
+ CajaFile *original_dir;
+ GHashTable *original_dirs_hash;
+ GList *original_dirs;
+ GFile *original_location;
+ char *tooltip, *original_name;
+
+ original_file = NULL;
+ original_dir = NULL;
+ original_dirs = NULL;
+ original_dirs_hash = NULL;
+ original_location = NULL;
+ original_name = NULL;
+
+ if (files != NULL) {
+ if (g_list_length (files) == 1) {
+ original_file = caja_file_get_trash_original_file (files->data);
+ } else {
+ original_dirs_hash = caja_trashed_files_get_original_directories (files, NULL);
+ if (original_dirs_hash != NULL) {
+ original_dirs = g_hash_table_get_keys (original_dirs_hash);
+ if (g_list_length (original_dirs) == 1) {
+ original_dir = caja_file_ref (CAJA_FILE (original_dirs->data));
+ }
+ }
+ }
+ }
+
+ if (original_file != NULL || original_dirs != NULL) {
+ gtk_action_set_visible (action, TRUE);
+
+ if (original_file != NULL) {
+ original_location = caja_file_get_location (original_file);
+ } else if (original_dir != NULL) {
+ original_location = caja_file_get_location (original_dir);
+ }
+
+ if (original_location != NULL) {
+ original_name = g_file_get_parse_name (original_location);
+ }
+
+ if (is_self) {
+ g_assert (g_list_length (files) == 1);
+ g_assert (original_location != NULL);
+ tooltip = g_strdup_printf (_("Move the open folder out of the trash to \"%s\""), original_name);
+ } else if (files_are_all_directories (files)) {
+ if (original_name != NULL) {
+ tooltip = g_strdup_printf (ngettext ("Move the selected folder out of the trash to \"%s\"",
+ "Move the selected folders out of the trash to \"%s\"",
+ g_list_length (files)), original_name);
+ } else {
+ tooltip = g_strdup_printf (ngettext ("Move the selected folder out of the trash",
+ "Move the selected folders out of the trash",
+ g_list_length (files)));
+ }
+ } else if (files_is_none_directory (files)) {
+ if (original_name != NULL) {
+ tooltip = g_strdup_printf (ngettext ("Move the selected file out of the trash to \"%s\"",
+ "Move the selected files out of the trash to \"%s\"",
+ g_list_length (files)), original_name);
+ } else {
+ tooltip = g_strdup_printf (ngettext ("Move the selected file out of the trash",
+ "Move the selected files out of the trash",
+ g_list_length (files)));
+ }
+ } else {
+ if (original_name != NULL) {
+ tooltip = g_strdup_printf (ngettext ("Move the selected item out of the trash to \"%s\"",
+ "Move the selected items out of the trash to \"%s\"",
+ g_list_length (files)), original_name);
+ } else {
+ tooltip = g_strdup_printf (ngettext ("Move the selected item out of the trash",
+ "Move the selected items out of the trash",
+ g_list_length (files)));
+ }
+ }
+ g_free (original_name);
+
+ g_object_set (action, "tooltip", tooltip, NULL);
+
+ if (original_location != NULL) {
+ g_object_unref (original_location);
+ }
+ } else {
+ gtk_action_set_visible (action, FALSE);
+ }
+
+ caja_file_unref (original_file);
+ caja_file_unref (original_dir);
+ g_list_free (original_dirs);
+
+ if (original_dirs_hash != NULL) {
+ g_hash_table_destroy (original_dirs_hash);
+ }
+}
+
+static void
+real_update_menus_volumes (FMDirectoryView *view,
+ GList *selection,
+ gint selection_count)
+{
+ GList *l;
+ CajaFile *file;
+ gboolean show_mount;
+ gboolean show_unmount;
+ gboolean show_eject;
+ gboolean show_connect;
+ gboolean show_format;
+ gboolean show_start;
+ gboolean show_stop;
+ gboolean show_poll;
+ GDriveStartStopType start_stop_type;
+ gboolean show_self_mount;
+ gboolean show_self_unmount;
+ gboolean show_self_eject;
+ gboolean show_self_format;
+ gboolean show_self_start;
+ gboolean show_self_stop;
+ gboolean show_self_poll;
+ GDriveStartStopType self_start_stop_type;
+ GtkAction *action;
+
+ show_mount = (selection != NULL);
+ show_unmount = (selection != NULL);
+ show_eject = (selection != NULL);
+ show_connect = (selection != NULL && selection_count == 1);
+ show_format = (selection != NULL && selection_count == 1);
+ show_start = (selection != NULL && selection_count == 1);
+ show_stop = (selection != NULL && selection_count == 1);
+ show_poll = (selection != NULL && selection_count == 1);
+ start_stop_type = G_DRIVE_START_STOP_TYPE_UNKNOWN;
+ self_start_stop_type = G_DRIVE_START_STOP_TYPE_UNKNOWN;
+
+ for (l = selection; l != NULL && (show_mount || show_unmount
+ || show_eject || show_connect
+ || show_format || show_start
+ || show_stop || show_poll);
+ l = l->next) {
+ gboolean show_mount_one;
+ gboolean show_unmount_one;
+ gboolean show_eject_one;
+ gboolean show_connect_one;
+ gboolean show_format_one;
+ gboolean show_start_one;
+ gboolean show_stop_one;
+ gboolean show_poll_one;
+
+ file = CAJA_FILE (l->data);
+ file_should_show_foreach (file,
+ &show_mount_one,
+ &show_unmount_one,
+ &show_eject_one,
+ &show_connect_one,
+ &show_format_one,
+ &show_start_one,
+ &show_stop_one,
+ &show_poll_one,
+ &start_stop_type);
+
+ show_mount &= show_mount_one;
+ show_unmount &= show_unmount_one;
+ show_eject &= show_eject_one;
+ show_connect &= show_connect_one;
+ show_format &= show_format_one;
+ show_start &= show_start_one;
+ show_stop &= show_stop_one;
+ show_poll &= show_poll_one;
+ }
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_CONNECT_TO_SERVER_LINK);
+ gtk_action_set_visible (action, show_connect);
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_MOUNT_VOLUME);
+ gtk_action_set_visible (action, show_mount);
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_UNMOUNT_VOLUME);
+ gtk_action_set_visible (action, show_unmount);
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_EJECT_VOLUME);
+ gtk_action_set_visible (action, show_eject);
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_FORMAT_VOLUME);
+ gtk_action_set_visible (action, show_format);
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_START_VOLUME);
+ gtk_action_set_visible (action, show_start);
+ if (show_start) {
+ switch (start_stop_type) {
+ default:
+ case G_DRIVE_START_STOP_TYPE_UNKNOWN:
+ gtk_action_set_label (action, _("_Start"));
+ gtk_action_set_tooltip (action, _("Start the selected drive"));
+ break;
+ case G_DRIVE_START_STOP_TYPE_SHUTDOWN:
+ gtk_action_set_label (action, _("_Start"));
+ gtk_action_set_tooltip (action, _("Start the selected drive"));
+ break;
+ case G_DRIVE_START_STOP_TYPE_NETWORK:
+ gtk_action_set_label (action, _("_Connect"));
+ gtk_action_set_tooltip (action, _("Connect to the selected drive"));
+ break;
+ case G_DRIVE_START_STOP_TYPE_MULTIDISK:
+ gtk_action_set_label (action, _("_Start Multi-disk Drive"));
+ gtk_action_set_tooltip (action, _("Start the selected multi-disk drive"));
+ break;
+ case G_DRIVE_START_STOP_TYPE_PASSWORD:
+ gtk_action_set_label (action, _("U_nlock Drive"));
+ gtk_action_set_tooltip (action, _("Unlock the selected drive"));
+ break;
+ }
+ }
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_STOP_VOLUME);
+ gtk_action_set_visible (action, show_stop);
+ if (show_stop) {
+ switch (start_stop_type) {
+ default:
+ case G_DRIVE_START_STOP_TYPE_UNKNOWN:
+ gtk_action_set_label (action, _("_Stop"));
+ gtk_action_set_tooltip (action, _("Stop the selected drive"));
+ break;
+ case G_DRIVE_START_STOP_TYPE_SHUTDOWN:
+ gtk_action_set_label (action, _("_Safely Remove Drive"));
+ gtk_action_set_tooltip (action, _("Safely remove the selected drive"));
+ break;
+ case G_DRIVE_START_STOP_TYPE_NETWORK:
+ gtk_action_set_label (action, _("_Disconnect"));
+ gtk_action_set_tooltip (action, _("Disconnect the selected drive"));
+ break;
+ case G_DRIVE_START_STOP_TYPE_MULTIDISK:
+ gtk_action_set_label (action, _("_Stop Multi-disk Drive"));
+ gtk_action_set_tooltip (action, _("Stop the selected multi-disk drive"));
+ break;
+ case G_DRIVE_START_STOP_TYPE_PASSWORD:
+ gtk_action_set_label (action, _("_Lock Drive"));
+ gtk_action_set_tooltip (action, _("Lock the selected drive"));
+ break;
+ }
+ }
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_POLL);
+ gtk_action_set_visible (action, show_poll);
+
+ show_self_mount = show_self_unmount = show_self_eject =
+ show_self_format = show_self_start = show_self_stop = show_self_poll = FALSE;
+
+ file = fm_directory_view_get_directory_as_file (view);
+ file_should_show_self (file,
+ &show_self_mount,
+ &show_self_unmount,
+ &show_self_eject,
+ &show_self_format,
+ &show_self_start,
+ &show_self_stop,
+ &show_self_poll,
+ &self_start_stop_type);
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_SELF_MOUNT_VOLUME);
+ gtk_action_set_visible (action, show_self_mount);
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_SELF_UNMOUNT_VOLUME);
+ gtk_action_set_visible (action, show_self_unmount);
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_SELF_EJECT_VOLUME);
+ gtk_action_set_visible (action, show_self_eject);
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_SELF_FORMAT_VOLUME);
+ gtk_action_set_visible (action, show_self_format);
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_SELF_START_VOLUME);
+ gtk_action_set_visible (action, show_self_start);
+ if (show_self_start) {
+ switch (self_start_stop_type) {
+ default:
+ case G_DRIVE_START_STOP_TYPE_UNKNOWN:
+ gtk_action_set_label (action, _("_Start"));
+ gtk_action_set_tooltip (action, _("Start the drive associated with the open folder"));
+ break;
+ case G_DRIVE_START_STOP_TYPE_SHUTDOWN:
+ gtk_action_set_label (action, _("_Start"));
+ gtk_action_set_tooltip (action, _("Start the drive associated with the open folder"));
+ break;
+ case G_DRIVE_START_STOP_TYPE_NETWORK:
+ gtk_action_set_label (action, _("_Connect"));
+ gtk_action_set_tooltip (action, _("Connect to the drive associated with the open folder"));
+ break;
+ case G_DRIVE_START_STOP_TYPE_MULTIDISK:
+ gtk_action_set_label (action, _("_Start Multi-disk Drive"));
+ gtk_action_set_tooltip (action, _("Start the multi-disk drive associated with the open folder"));
+ break;
+ case G_DRIVE_START_STOP_TYPE_PASSWORD:
+ gtk_action_set_label (action, _("_Unlock Drive"));
+ gtk_action_set_tooltip (action, _("Unlock the drive associated with the open folder"));
+ break;
+ }
+ }
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_SELF_STOP_VOLUME);
+ gtk_action_set_visible (action, show_self_stop);
+ if (show_self_stop) {
+ switch (self_start_stop_type) {
+ default:
+ case G_DRIVE_START_STOP_TYPE_UNKNOWN:
+ gtk_action_set_label (action, _("_Stop"));
+ gtk_action_set_tooltip (action, _("_Stop the drive associated with the open folder"));
+ break;
+ case G_DRIVE_START_STOP_TYPE_SHUTDOWN:
+ gtk_action_set_label (action, _("_Safely Remove Drive"));
+ gtk_action_set_tooltip (action, _("Safely remove the drive associated with the open folder"));
+ break;
+ case G_DRIVE_START_STOP_TYPE_NETWORK:
+ gtk_action_set_label (action, _("_Disconnect"));
+ gtk_action_set_tooltip (action, _("Disconnect the drive associated with the open folder"));
+ break;
+ case G_DRIVE_START_STOP_TYPE_MULTIDISK:
+ gtk_action_set_label (action, _("_Stop Multi-disk Drive"));
+ gtk_action_set_tooltip (action, _("Stop the multi-disk drive associated with the open folder"));
+ break;
+ case G_DRIVE_START_STOP_TYPE_PASSWORD:
+ gtk_action_set_label (action, _("_Lock Drive"));
+ gtk_action_set_tooltip (action, _("Lock the drive associated with the open folder"));
+ break;
+ }
+ }
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_SELF_POLL);
+ gtk_action_set_visible (action, show_self_poll);
+
+}
+
+static void
+real_update_location_menu_volumes (FMDirectoryView *view)
+{
+ GtkAction *action;
+ CajaFile *file;
+ gboolean show_mount;
+ gboolean show_unmount;
+ gboolean show_eject;
+ gboolean show_connect;
+ gboolean show_format;
+ gboolean show_start;
+ gboolean show_stop;
+ gboolean show_poll;
+ GDriveStartStopType start_stop_type;
+
+ g_assert (FM_IS_DIRECTORY_VIEW (view));
+ g_assert (CAJA_IS_FILE (view->details->location_popup_directory_as_file));
+
+ file = CAJA_FILE (view->details->location_popup_directory_as_file);
+ file_should_show_foreach (file,
+ &show_mount,
+ &show_unmount,
+ &show_eject,
+ &show_connect,
+ &show_format,
+ &show_start,
+ &show_stop,
+ &show_poll,
+ &start_stop_type);
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_LOCATION_MOUNT_VOLUME);
+ gtk_action_set_visible (action, show_mount);
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_LOCATION_UNMOUNT_VOLUME);
+ gtk_action_set_visible (action, show_unmount);
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_LOCATION_EJECT_VOLUME);
+ gtk_action_set_visible (action, show_eject);
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_LOCATION_FORMAT_VOLUME);
+ gtk_action_set_visible (action, show_format);
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_LOCATION_START_VOLUME);
+ gtk_action_set_visible (action, show_start);
+ if (show_start) {
+ switch (start_stop_type) {
+ default:
+ case G_DRIVE_START_STOP_TYPE_UNKNOWN:
+ gtk_action_set_label (action, _("_Start"));
+ gtk_action_set_tooltip (action, _("Start the selected drive"));
+ break;
+ case G_DRIVE_START_STOP_TYPE_SHUTDOWN:
+ gtk_action_set_label (action, _("_Start"));
+ gtk_action_set_tooltip (action, _("Start the selected drive"));
+ break;
+ case G_DRIVE_START_STOP_TYPE_NETWORK:
+ gtk_action_set_label (action, _("_Connect"));
+ gtk_action_set_tooltip (action, _("Connect to the selected drive"));
+ break;
+ case G_DRIVE_START_STOP_TYPE_MULTIDISK:
+ gtk_action_set_label (action, _("_Start Multi-disk Drive"));
+ gtk_action_set_tooltip (action, _("Start the selected multi-disk drive"));
+ break;
+ case G_DRIVE_START_STOP_TYPE_PASSWORD:
+ gtk_action_set_label (action, _("_Unlock Drive"));
+ gtk_action_set_tooltip (action, _("Unlock the selected drive"));
+ break;
+ }
+ }
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_LOCATION_STOP_VOLUME);
+ gtk_action_set_visible (action, show_stop);
+ if (show_stop) {
+ switch (start_stop_type) {
+ default:
+ case G_DRIVE_START_STOP_TYPE_UNKNOWN:
+ gtk_action_set_label (action, _("_Stop"));
+ gtk_action_set_tooltip (action, _("Stop the selected volume"));
+ break;
+ case G_DRIVE_START_STOP_TYPE_SHUTDOWN:
+ gtk_action_set_label (action, _("_Safely Remove Drive"));
+ gtk_action_set_tooltip (action, _("Safely remove the selected drive"));
+ break;
+ case G_DRIVE_START_STOP_TYPE_NETWORK:
+ gtk_action_set_label (action, _("_Disconnect"));
+ gtk_action_set_tooltip (action, _("Disconnect the selected drive"));
+ break;
+ case G_DRIVE_START_STOP_TYPE_MULTIDISK:
+ gtk_action_set_label (action, _("_Stop Multi-disk Drive"));
+ gtk_action_set_tooltip (action, _("Stop the selected multi-disk drive"));
+ break;
+ case G_DRIVE_START_STOP_TYPE_PASSWORD:
+ gtk_action_set_label (action, _("_Lock Drive"));
+ gtk_action_set_tooltip (action, _("Lock the selected drive"));
+ break;
+ }
+ }
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_LOCATION_POLL);
+ gtk_action_set_visible (action, show_poll);
+}
+
+/* TODO: we should split out this routine into two functions:
+ * Update on clipboard changes
+ * Update on selection changes
+ */
+static void
+real_update_paste_menu (FMDirectoryView *view,
+ GList *selection,
+ gint selection_count)
+{
+ gboolean can_paste_files_into;
+ gboolean selection_is_read_only;
+ gboolean is_read_only;
+ GtkAction *action;
+
+ selection_is_read_only = selection_count == 1 &&
+ (!caja_file_can_write (CAJA_FILE (selection->data)) &&
+ !caja_file_has_activation_uri (CAJA_FILE (selection->data)));
+
+ is_read_only = fm_directory_view_is_read_only (view);
+
+ can_paste_files_into = (selection_count == 1 &&
+ can_paste_into_file (CAJA_FILE (selection->data)));
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_PASTE);
+ gtk_action_set_sensitive (action, !is_read_only);
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_PASTE_FILES_INTO);
+ gtk_action_set_visible (action, can_paste_files_into);
+ gtk_action_set_sensitive (action, !selection_is_read_only);
+
+ /* Ask the clipboard */
+ g_object_ref (view); /* Need to keep the object alive until we get the reply */
+ gtk_clipboard_request_targets (caja_clipboard_get (GTK_WIDGET (view)),
+ clipboard_targets_received,
+ view);
+}
+
+static void
+real_update_location_menu (FMDirectoryView *view)
+{
+ GtkAction *action;
+ CajaFile *file;
+ gboolean is_special_link;
+ gboolean is_desktop_or_home_dir;
+ gboolean can_delete_file, show_delete;
+ gboolean show_separate_delete_command;
+ gboolean show_open_folder_window;
+ gboolean show_open_in_new_tab;
+ GList l;
+ char *label;
+ char *tip;
+
+ show_open_folder_window = FALSE;
+ show_open_in_new_tab = FALSE;
+
+ if (caja_window_info_get_window_type (view->details->window) == CAJA_WINDOW_NAVIGATION) {
+ if (eel_preferences_get_boolean (CAJA_PREFERENCES_ALWAYS_USE_BROWSER)) {
+ label = _("Open in New _Window");
+ } else {
+ label = _("Browse in New _Window");
+ show_open_folder_window = TRUE;
+ }
+
+ show_open_in_new_tab = TRUE;
+ } else {
+ label = g_strdup (ngettext ("_Browse Folder",
+ "_Browse Folders", 1));
+ }
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_LOCATION_OPEN_ALTERNATE);
+ g_object_set (action,
+ "label", label,
+ NULL);
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_LOCATION_OPEN_IN_NEW_TAB);
+ gtk_action_set_visible (action, show_open_in_new_tab);
+
+ if (show_open_in_new_tab) {
+ if (eel_preferences_get_boolean (CAJA_PREFERENCES_ALWAYS_USE_BROWSER)) {
+ label = _("Open in New _Tab");
+ } else {
+ label = _("Browse in New _Tab");
+ }
+ g_object_set (action,
+ "label", label,
+ NULL);
+ }
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_LOCATION_OPEN_FOLDER_WINDOW);
+ gtk_action_set_visible (action, show_open_folder_window);
+
+ file = view->details->location_popup_directory_as_file;
+ g_assert (CAJA_IS_FILE (file));
+ g_assert (caja_file_check_if_ready (file, CAJA_FILE_ATTRIBUTE_INFO |
+ CAJA_FILE_ATTRIBUTE_MOUNT |
+ CAJA_FILE_ATTRIBUTE_FILESYSTEM_INFO));
+
+ is_special_link = CAJA_IS_DESKTOP_ICON_FILE (file);
+ is_desktop_or_home_dir = caja_file_is_home (file)
+ || caja_file_is_desktop_directory (file);
+
+ can_delete_file =
+ caja_file_can_delete (file) &&
+ !is_special_link &&
+ !is_desktop_or_home_dir;
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_LOCATION_CUT);
+ gtk_action_set_sensitive (action, can_delete_file);
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_LOCATION_PASTE_FILES_INTO);
+ g_object_set_data (G_OBJECT (action),
+ "can-paste-according-to-destination",
+ GINT_TO_POINTER (can_paste_into_file (file)));
+ gtk_action_set_sensitive (action,
+ GPOINTER_TO_INT (g_object_get_data (G_OBJECT (action),
+ "can-paste-according-to-clipboard")) &&
+ GPOINTER_TO_INT (g_object_get_data (G_OBJECT (action),
+ "can-paste-according-to-destination")));
+
+ show_delete = TRUE;
+
+ if (file != NULL &&
+ caja_file_is_in_trash (file)) {
+ if (caja_file_is_self_owned (file)) {
+ show_delete = FALSE;
+ }
+
+ label = _("_Delete Permanently");
+ tip = _("Delete the open folder permanently");
+ show_separate_delete_command = FALSE;
+ } else {
+ label = _("Mo_ve to Trash");
+ tip = _("Move the open folder to the Trash");
+ show_separate_delete_command = show_delete_command_auto_value;
+ }
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_LOCATION_TRASH);
+ g_object_set (action,
+ "label", label,
+ "tooltip", tip,
+ "icon-name", (file != NULL &&
+ caja_file_is_in_trash (file)) ?
+ CAJA_ICON_DELETE : CAJA_ICON_TRASH_FULL,
+ NULL);
+ gtk_action_set_sensitive (action, can_delete_file);
+ gtk_action_set_visible (action, show_delete);
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_LOCATION_DELETE);
+ gtk_action_set_visible (action, show_separate_delete_command);
+ if (show_separate_delete_command) {
+ gtk_action_set_sensitive (action, can_delete_file);
+ g_object_set (action,
+ "icon-name", CAJA_ICON_DELETE,
+ "sensitive", can_delete_file,
+ NULL);
+ }
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_LOCATION_RESTORE_FROM_TRASH);
+ l.prev = NULL;
+ l.next = NULL;
+ l.data = file;
+ update_restore_from_trash_action (action, &l, TRUE);
+
+ real_update_location_menu_volumes (view);
+
+ /* we silently assume that fm_directory_view_supports_properties always returns the same value.
+ * Therefore, we don't update the sensitivity of FM_ACTION_LOCATION_PROPERTIES */
+}
+
+static void
+clipboard_changed_callback (CajaClipboardMonitor *monitor, FMDirectoryView *view)
+{
+ GList *selection;
+ gint selection_count;
+
+ if (!view->details->active) {
+ return;
+ }
+
+ selection = fm_directory_view_get_selection (view);
+ selection_count = g_list_length (selection);
+
+ real_update_paste_menu (view, selection, selection_count);
+
+ caja_file_list_free (selection);
+
+}
+
+static gboolean
+can_delete_all (GList *files)
+{
+ CajaFile *file;
+ GList *l;
+
+ for (l = files; l != NULL; l = l->next) {
+ file = l->data;
+ if (!caja_file_can_delete (file)) {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static gboolean
+has_writable_extra_pane (FMDirectoryView *view)
+{
+ FMDirectoryView *other_view;
+
+ other_view = get_directory_view_of_extra_pane (view);
+ if (other_view != NULL) {
+ return !fm_directory_view_is_read_only (other_view);
+ }
+ return FALSE;
+}
+
+static void
+real_update_menus (FMDirectoryView *view)
+{
+ GList *selection, *l;
+ gint selection_count;
+ const char *tip, *label;
+ char *label_with_underscore;
+ gboolean selection_contains_special_link;
+ gboolean selection_contains_desktop_or_home_dir;
+ gboolean can_create_files;
+ gboolean can_delete_files;
+ gboolean can_copy_files;
+ gboolean can_link_files;
+ gboolean can_duplicate_files;
+ gboolean show_separate_delete_command;
+ gboolean vfolder_directory;
+ gboolean disable_command_line;
+ gboolean show_open_alternate;
+ gboolean can_open;
+ gboolean show_app;
+ gboolean show_save_search;
+ gboolean save_search_sensitive;
+ gboolean show_save_search_as;
+ gboolean show_open_folder_window;
+ GtkAction *action;
+ GAppInfo *app;
+ GIcon *app_icon;
+ GtkWidget *menuitem;
+ gboolean next_pane_is_writable;
+ gboolean show_properties;
+
+ selection = fm_directory_view_get_selection (view);
+ selection_count = g_list_length (selection);
+
+ selection_contains_special_link = special_link_in_selection (view);
+ selection_contains_desktop_or_home_dir = desktop_or_home_dir_in_selection (view);
+
+ can_create_files = fm_directory_view_supports_creating_files (view);
+ can_delete_files =
+ can_delete_all (selection) &&
+ selection_count != 0 &&
+ !selection_contains_special_link &&
+ !selection_contains_desktop_or_home_dir;
+ can_copy_files = selection_count != 0
+ && !selection_contains_special_link;
+
+ can_duplicate_files = can_create_files && can_copy_files;
+ can_link_files = can_create_files && can_copy_files;
+
+ vfolder_directory = we_are_in_vfolder_desktop_dir (view);
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_RENAME);
+ gtk_action_set_sensitive (action,
+ selection_count == 1 &&
+ fm_directory_view_can_rename_file (view, selection->data));
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_NEW_FOLDER);
+ gtk_action_set_sensitive (action, can_create_files);
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_OPEN);
+ gtk_action_set_sensitive (action, selection_count != 0);
+
+ can_open = show_app = selection_count != 0;
+
+ for (l = selection; l != NULL; l = l->next) {
+ CajaFile *file;
+
+ file = CAJA_FILE (selection->data);
+
+ if (!caja_mime_file_opens_in_external_app (file)) {
+ show_app = FALSE;
+ }
+
+ if (!show_app) {
+ break;
+ }
+ }
+
+ label_with_underscore = NULL;
+
+ app = NULL;
+ app_icon = NULL;
+
+ if (can_open && show_app) {
+ app = caja_mime_get_default_application_for_files (selection);
+ }
+
+ if (app != NULL) {
+ char *escaped_app;
+
+ escaped_app = eel_str_double_underscores (g_app_info_get_display_name (app));
+ label_with_underscore = g_strdup_printf (_("_Open With %s"),
+ escaped_app);
+
+ app_icon = g_app_info_get_icon (app);
+ if (app_icon != NULL) {
+ g_object_ref (app_icon);
+ }
+
+ g_free (escaped_app);
+ g_object_unref (app);
+ }
+
+ g_object_set (action, "label",
+ label_with_underscore ? label_with_underscore : _("_Open"),
+ NULL);
+
+ menuitem = gtk_ui_manager_get_widget (
+ caja_window_info_get_ui_manager (view->details->window),
+ FM_DIRECTORY_VIEW_MENU_PATH_OPEN);
+
+ /* Only force displaying the icon if it is an application icon */
+ gtk_image_menu_item_set_always_show_image (
+ GTK_IMAGE_MENU_ITEM (menuitem), app_icon != NULL);
+
+ menuitem = gtk_ui_manager_get_widget (
+ caja_window_info_get_ui_manager (view->details->window),
+ FM_DIRECTORY_VIEW_POPUP_PATH_OPEN);
+
+ /* Only force displaying the icon if it is an application icon */
+ gtk_image_menu_item_set_always_show_image (
+ GTK_IMAGE_MENU_ITEM (menuitem), app_icon != NULL);
+
+ if (app_icon == NULL) {
+ app_icon = g_themed_icon_new (GTK_STOCK_OPEN);
+ }
+
+ gtk_action_set_gicon (action, app_icon);
+ g_object_unref (app_icon);
+
+ gtk_action_set_visible (action, can_open);
+
+ g_free (label_with_underscore);
+
+ show_open_alternate = file_list_all_are_folders (selection) &&
+ selection_count > 0 &&
+ !(caja_window_info_get_window_type (view->details->window) == CAJA_WINDOW_DESKTOP &&
+ eel_preferences_get_boolean (CAJA_PREFERENCES_ALWAYS_USE_BROWSER));
+ show_open_folder_window = FALSE;
+ if (caja_window_info_get_window_type (view->details->window) == CAJA_WINDOW_NAVIGATION) {
+ if (eel_preferences_get_boolean (CAJA_PREFERENCES_ALWAYS_USE_BROWSER)) {
+ if (selection_count == 0 || selection_count == 1) {
+ label_with_underscore = g_strdup (_("Open in New _Window"));
+ } else {
+ label_with_underscore = g_strdup_printf (ngettext("Open in %'d New _Window",
+ "Open in %'d New _Windows",
+ selection_count),
+ selection_count);
+ }
+ } else {
+ if (selection_count == 0 || selection_count == 1) {
+ label_with_underscore = g_strdup (_("Browse in New _Window"));
+ } else {
+ label_with_underscore = g_strdup_printf (ngettext("Browse in %'d New _Window",
+ "Browse in %'d New _Windows",
+ selection_count),
+ selection_count);
+ }
+ show_open_folder_window = show_open_alternate;
+ }
+ } else {
+ label_with_underscore = g_strdup (ngettext ("_Browse Folder",
+ "_Browse Folders",
+ selection_count));
+ }
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_OPEN_ALTERNATE);
+ g_object_set (action, "label",
+ label_with_underscore,
+ NULL);
+ g_free (label_with_underscore);
+
+ gtk_action_set_sensitive (action, selection_count != 0);
+ gtk_action_set_visible (action, show_open_alternate);
+
+ /* Open in New Tab action */
+ if (caja_window_info_get_window_type (view->details->window) == CAJA_WINDOW_NAVIGATION) {
+
+ if (eel_preferences_get_boolean (CAJA_PREFERENCES_ALWAYS_USE_BROWSER)) {
+ if (selection_count == 0 || selection_count == 1) {
+ label_with_underscore = g_strdup (_("Open in New _Tab"));
+ } else {
+ label_with_underscore = g_strdup_printf (ngettext("Open in %'d New _Tab",
+ "Open in %'d New _Tabs",
+ selection_count),
+ selection_count);
+ }
+ } else {
+ if (selection_count == 0 || selection_count == 1) {
+ label_with_underscore = g_strdup (_("Browse in New _Tab"));
+ } else {
+ label_with_underscore = g_strdup_printf (ngettext("Browse in %'d New _Tab",
+ "Browse in %'d New _Tabs",
+ selection_count),
+ selection_count);
+ }
+ }
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_OPEN_IN_NEW_TAB);
+ gtk_action_set_sensitive (action, selection_count != 0);
+ gtk_action_set_visible (action, show_open_alternate);
+ g_object_set (action, "label",
+ label_with_underscore,
+ NULL);
+ g_free (label_with_underscore);
+ } else {
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_OPEN_IN_NEW_TAB);
+ gtk_action_set_visible (action, FALSE);
+ }
+
+ /* next pane actions, only in navigation mode */
+ if (caja_window_info_get_window_type (view->details->window) != CAJA_WINDOW_NAVIGATION) {
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_COPY_TO_NEXT_PANE);
+ gtk_action_set_visible (action, FALSE);
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_MOVE_TO_NEXT_PANE);
+ gtk_action_set_visible (action, FALSE);
+ }
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_OPEN_FOLDER_WINDOW);
+ gtk_action_set_visible (action, show_open_folder_window);
+
+ /* Broken into its own function just for convenience */
+ reset_open_with_menu (view, selection);
+ reset_extension_actions_menu (view, selection);
+
+ if (all_selected_items_in_trash (view)) {
+ label = _("_Delete Permanently");
+ tip = _("Delete all selected items permanently");
+ show_separate_delete_command = FALSE;
+ } else {
+ label = _("Mo_ve to Trash");
+ tip = _("Move each selected item to the Trash");
+ show_separate_delete_command = show_delete_command_auto_value;
+ }
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_TRASH);
+ g_object_set (action,
+ "label", label,
+ "tooltip", tip,
+ "icon-name", all_selected_items_in_trash (view) ?
+ CAJA_ICON_DELETE : CAJA_ICON_TRASH_FULL,
+ NULL);
+ gtk_action_set_sensitive (action, can_delete_files);
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_DELETE);
+ gtk_action_set_visible (action, show_separate_delete_command);
+
+ if (show_separate_delete_command) {
+ g_object_set (action,
+ "label", _("_Delete"),
+ "icon-name", CAJA_ICON_DELETE,
+ NULL);
+ }
+ gtk_action_set_sensitive (action, can_delete_files);
+
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_RESTORE_FROM_TRASH);
+ update_restore_from_trash_action (action, selection, FALSE);
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_DUPLICATE);
+ gtk_action_set_sensitive (action, can_duplicate_files);
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_CREATE_LINK);
+ gtk_action_set_sensitive (action, can_link_files);
+ g_object_set (action, "label",
+ ngettext ("Ma_ke Link",
+ "Ma_ke Links",
+ selection_count),
+ NULL);
+
+ show_properties = (!FM_IS_DESKTOP_ICON_VIEW (view) || selection_count > 0) &&
+ fm_directory_view_supports_properties (view);
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_PROPERTIES);
+
+ gtk_action_set_sensitive (action, show_properties);
+
+ if (selection_count == 0) {
+ gtk_action_set_tooltip (action, _("View or modify the properties of the open folder"));
+ } else {
+ gtk_action_set_tooltip (action, _("View or modify the properties of each selected item"));
+ }
+
+ gtk_action_set_visible (action, show_properties);
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_PROPERTIES_ACCEL);
+
+ gtk_action_set_sensitive (action, show_properties);
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_EMPTY_TRASH);
+ g_object_set (action,
+ "label", _("E_mpty Trash"),
+ NULL);
+ gtk_action_set_sensitive (action, !caja_trash_monitor_is_empty ());
+ gtk_action_set_visible (action, should_show_empty_trash (view));
+
+ show_save_search = FALSE;
+ save_search_sensitive = FALSE;
+ show_save_search_as = FALSE;
+ if (view->details->model &&
+ CAJA_IS_SEARCH_DIRECTORY (view->details->model)) {
+ CajaSearchDirectory *search;
+
+ search = CAJA_SEARCH_DIRECTORY (view->details->model);
+ if (caja_search_directory_is_saved_search (search)) {
+ show_save_search = TRUE;
+ save_search_sensitive = caja_search_directory_is_modified (search);
+ } else {
+ show_save_search_as = TRUE;
+ }
+ }
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_SAVE_SEARCH);
+ gtk_action_set_visible (action, show_save_search);
+ gtk_action_set_sensitive (action, save_search_sensitive);
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_SAVE_SEARCH_AS);
+ gtk_action_set_visible (action, show_save_search_as);
+
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_SELECT_ALL);
+ gtk_action_set_sensitive (action, !fm_directory_view_is_empty (view));
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_SELECT_PATTERN);
+ gtk_action_set_sensitive (action, !fm_directory_view_is_empty (view));
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_INVERT_SELECTION);
+ gtk_action_set_sensitive (action, !fm_directory_view_is_empty (view));
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_CUT);
+ gtk_action_set_sensitive (action, can_delete_files);
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_COPY);
+ gtk_action_set_sensitive (action, can_copy_files);
+
+ real_update_paste_menu (view, selection, selection_count);
+
+ disable_command_line = eel_preferences_get_boolean (CAJA_PREFERENCES_LOCKDOWN_COMMAND_LINE);
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_NEW_LAUNCHER);
+ gtk_action_set_visible (action, vfolder_directory && !disable_command_line);
+ gtk_action_set_sensitive (action, can_create_files);
+
+ real_update_menus_volumes (view, selection, selection_count);
+
+ caja_file_list_free (selection);
+
+ if (view->details->scripts_invalid) {
+ update_scripts_menu (view);
+ }
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_NEW_DOCUMENTS);
+ gtk_action_set_sensitive (action, can_create_files);
+
+ if (can_create_files && view->details->templates_invalid) {
+ update_templates_menu (view);
+ }
+
+ next_pane_is_writable = has_writable_extra_pane (view);
+
+ /* next pane: works if file is copyable, and next pane is writable */
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_COPY_TO_NEXT_PANE);
+ gtk_action_set_sensitive (action, can_copy_files && next_pane_is_writable);
+
+ /* move to next pane: works if file is cuttable, and next pane is writable */
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_MOVE_TO_NEXT_PANE);
+ gtk_action_set_sensitive (action, can_delete_files && next_pane_is_writable);
+
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_COPY_TO_HOME);
+ gtk_action_set_sensitive (action, can_copy_files);
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_COPY_TO_DESKTOP);
+ gtk_action_set_sensitive (action, can_copy_files);
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_MOVE_TO_HOME);
+ gtk_action_set_sensitive (action, can_delete_files);
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_MOVE_TO_DESKTOP);
+ gtk_action_set_sensitive (action, can_delete_files);
+
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ "CopyToMenu");
+ gtk_action_set_sensitive (action, can_copy_files);
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ "MoveToMenu");
+ gtk_action_set_sensitive (action, can_delete_files);
+}
+
+/**
+ * fm_directory_view_pop_up_selection_context_menu
+ *
+ * Pop up a context menu appropriate to the selected items.
+ * @view: FMDirectoryView of interest.
+ * @event: The event that triggered this context menu.
+ *
+ * Return value: CajaDirectory for this view.
+ *
+ **/
+void
+fm_directory_view_pop_up_selection_context_menu (FMDirectoryView *view,
+ GdkEventButton *event)
+{
+ g_assert (FM_IS_DIRECTORY_VIEW (view));
+
+ /* Make the context menu items not flash as they update to proper disabled,
+ * etc. states by forcing menus to update now.
+ */
+ update_menus_if_pending (view);
+
+ update_context_menu_position_from_event (view, event);
+
+ eel_pop_up_context_menu (create_popup_menu
+ (view, FM_DIRECTORY_VIEW_POPUP_PATH_SELECTION),
+ EEL_DEFAULT_POPUP_MENU_DISPLACEMENT,
+ EEL_DEFAULT_POPUP_MENU_DISPLACEMENT,
+ event);
+}
+
+/**
+ * fm_directory_view_pop_up_background_context_menu
+ *
+ * Pop up a context menu appropriate to the view globally at the last right click location.
+ * @view: FMDirectoryView of interest.
+ *
+ * Return value: CajaDirectory for this view.
+ *
+ **/
+void
+fm_directory_view_pop_up_background_context_menu (FMDirectoryView *view,
+ GdkEventButton *event)
+{
+ g_assert (FM_IS_DIRECTORY_VIEW (view));
+
+ /* Make the context menu items not flash as they update to proper disabled,
+ * etc. states by forcing menus to update now.
+ */
+ update_menus_if_pending (view);
+
+ update_context_menu_position_from_event (view, event);
+
+
+ eel_pop_up_context_menu (create_popup_menu
+ (view, FM_DIRECTORY_VIEW_POPUP_PATH_BACKGROUND),
+ EEL_DEFAULT_POPUP_MENU_DISPLACEMENT,
+ EEL_DEFAULT_POPUP_MENU_DISPLACEMENT,
+ event);
+}
+
+static void
+real_pop_up_location_context_menu (FMDirectoryView *view)
+{
+ /* always update the menu before showing it. Shouldn't be too expensive. */
+ real_update_location_menu (view);
+
+ update_context_menu_position_from_event (view, view->details->location_popup_event);
+
+ eel_pop_up_context_menu (create_popup_menu
+ (view, FM_DIRECTORY_VIEW_POPUP_PATH_LOCATION),
+ EEL_DEFAULT_POPUP_MENU_DISPLACEMENT,
+ EEL_DEFAULT_POPUP_MENU_DISPLACEMENT,
+ view->details->location_popup_event);
+}
+
+static void
+location_popup_file_attributes_ready (CajaFile *file,
+ gpointer data)
+{
+ FMDirectoryView *view;
+
+ view = FM_DIRECTORY_VIEW (data);
+ g_assert (FM_IS_DIRECTORY_VIEW (view));
+
+ g_assert (file == view->details->location_popup_directory_as_file);
+
+ real_pop_up_location_context_menu (view);
+}
+
+static void
+unschedule_pop_up_location_context_menu (FMDirectoryView *view)
+{
+ if (view->details->location_popup_directory_as_file != NULL) {
+ g_assert (CAJA_IS_FILE (view->details->location_popup_directory_as_file));
+ caja_file_cancel_call_when_ready (view->details->location_popup_directory_as_file,
+ location_popup_file_attributes_ready,
+ view);
+ caja_file_unref (view->details->location_popup_directory_as_file);
+ view->details->location_popup_directory_as_file = NULL;
+ }
+}
+
+static void
+schedule_pop_up_location_context_menu (FMDirectoryView *view,
+ GdkEventButton *event,
+ CajaFile *file)
+{
+ g_assert (CAJA_IS_FILE (file));
+
+ if (view->details->location_popup_event != NULL) {
+ gdk_event_free ((GdkEvent *) view->details->location_popup_event);
+ }
+ view->details->location_popup_event = (GdkEventButton *) gdk_event_copy ((GdkEvent *)event);
+
+ if (file == view->details->location_popup_directory_as_file) {
+ if (caja_file_check_if_ready (file, CAJA_FILE_ATTRIBUTE_INFO |
+ CAJA_FILE_ATTRIBUTE_MOUNT |
+ CAJA_FILE_ATTRIBUTE_FILESYSTEM_INFO)) {
+ real_pop_up_location_context_menu (view);
+ }
+ } else {
+ unschedule_pop_up_location_context_menu (view);
+
+ view->details->location_popup_directory_as_file = caja_file_ref (file);
+ caja_file_call_when_ready (view->details->location_popup_directory_as_file,
+ CAJA_FILE_ATTRIBUTE_INFO |
+ CAJA_FILE_ATTRIBUTE_MOUNT |
+ CAJA_FILE_ATTRIBUTE_FILESYSTEM_INFO,
+ location_popup_file_attributes_ready,
+ view);
+ }
+}
+
+/**
+ * fm_directory_view_pop_up_location_context_menu
+ *
+ * Pop up a context menu appropriate to the view globally.
+ * @view: FMDirectoryView of interest.
+ * @event: GdkEventButton triggering the popup.
+ * @location: The location the popup-menu should be created for,
+ * or NULL for the currently displayed location.
+ *
+ **/
+void
+fm_directory_view_pop_up_location_context_menu (FMDirectoryView *view,
+ GdkEventButton *event,
+ const char *location)
+{
+ CajaFile *file;
+
+ g_assert (FM_IS_DIRECTORY_VIEW (view));
+
+ if (location != NULL) {
+ file = caja_file_get_by_uri (location);
+ } else {
+ file = caja_file_ref (view->details->directory_as_file);
+ }
+
+ if (file != NULL) {
+ schedule_pop_up_location_context_menu (view, event, file);
+ caja_file_unref (file);
+ }
+}
+
+static void
+fm_directory_view_drop_proxy_received_uris (FMDirectoryView *view,
+ const GList *source_uri_list,
+ const char *target_uri,
+ GdkDragAction action)
+{
+ char *container_uri;
+
+ container_uri = NULL;
+ if (target_uri == NULL) {
+ container_uri = fm_directory_view_get_backing_uri (view);
+ g_assert (container_uri != NULL);
+ }
+
+ if (action == GDK_ACTION_ASK) {
+ action = caja_drag_drop_action_ask
+ (GTK_WIDGET (view),
+ GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK);
+ if (action == 0) {
+ return;
+ }
+ }
+
+ caja_clipboard_clear_if_colliding_uris (GTK_WIDGET (view),
+ source_uri_list,
+ fm_directory_view_get_copied_files_atom (view));
+
+ fm_directory_view_move_copy_items (source_uri_list, NULL,
+ target_uri != NULL ? target_uri : container_uri,
+ action, 0, 0, view);
+
+ g_free (container_uri);
+}
+
+static void
+fm_directory_view_drop_proxy_received_netscape_url (FMDirectoryView *view,
+ const char *netscape_url,
+ const char *target_uri,
+ GdkDragAction action)
+{
+ fm_directory_view_handle_netscape_url_drop (view,
+ netscape_url,
+ target_uri,
+ action, 0, 0);
+}
+
+static void
+schedule_update_menus (FMDirectoryView *view)
+{
+ g_assert (FM_IS_DIRECTORY_VIEW (view));
+
+ /* Don't schedule updates after destroy (#349551),
+ * or if we are not active.
+ */
+ if (view->details->window == NULL ||
+ !view->details->active) {
+ return;
+ }
+
+ view->details->menu_states_untrustworthy = TRUE;
+
+ /* Schedule a menu update with the current update interval */
+ if (view->details->update_menus_timeout_id == 0) {
+ view->details->update_menus_timeout_id
+ = g_timeout_add (view->details->update_interval, update_menus_timeout_callback, view);
+ }
+}
+
+static void
+remove_update_status_idle_callback (FMDirectoryView *view)
+{
+ if (view->details->update_status_idle_id != 0) {
+ g_source_remove (view->details->update_status_idle_id);
+ view->details->update_status_idle_id = 0;
+ }
+}
+
+static gboolean
+update_status_idle_callback (gpointer data)
+{
+ FMDirectoryView *view;
+
+ view = FM_DIRECTORY_VIEW (data);
+ fm_directory_view_display_selection_info (view);
+ view->details->update_status_idle_id = 0;
+ return FALSE;
+}
+
+static void
+schedule_update_status (FMDirectoryView *view)
+{
+ g_assert (FM_IS_DIRECTORY_VIEW (view));
+
+ /* Make sure we haven't already destroyed it */
+ if (view->details->window == NULL) {
+ return;
+ }
+
+ if (view->details->loading) {
+ /* Don't update status bar while loading the dir */
+ return;
+ }
+
+ if (view->details->update_status_idle_id == 0) {
+ view->details->update_status_idle_id =
+ g_idle_add_full (G_PRIORITY_DEFAULT_IDLE - 20,
+ update_status_idle_callback, view, NULL);
+ }
+}
+
+/**
+ * fm_directory_view_notify_selection_changed:
+ *
+ * Notify this view that the selection has changed. This is normally
+ * called only by subclasses.
+ * @view: FMDirectoryView whose selection has changed.
+ *
+ **/
+void
+fm_directory_view_notify_selection_changed (FMDirectoryView *view)
+{
+ GList *selection;
+ GtkWindow *window;
+
+ g_return_if_fail (FM_IS_DIRECTORY_VIEW (view));
+
+ if (caja_debug_log_is_domain_enabled (CAJA_DEBUG_LOG_DOMAIN_USER)) {
+ selection = fm_directory_view_get_selection (view);
+
+ window = fm_directory_view_get_containing_window (view);
+ caja_debug_log_with_file_list (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, selection,
+ "selection changed in window %p",
+ window);
+ caja_file_list_free (selection);
+ }
+
+ view->details->selection_was_removed = FALSE;
+
+ if (!view->details->selection_change_is_due_to_shell) {
+ view->details->send_selection_change_to_shell = TRUE;
+ }
+
+ /* Schedule a display of the new selection. */
+ if (view->details->display_selection_idle_id == 0) {
+ view->details->display_selection_idle_id
+ = g_idle_add (display_selection_info_idle_callback,
+ view);
+ }
+
+ if (view->details->batching_selection_level != 0) {
+ view->details->selection_changed_while_batched = TRUE;
+ } else {
+ /* Here is the work we do only when we're not
+ * batching selection changes. In other words, it's the slower
+ * stuff that we don't want to slow down selection techniques
+ * such as rubberband-selecting in icon view.
+ */
+
+ /* Schedule an update of menu item states to match selection */
+ schedule_update_menus (view);
+ }
+}
+
+static void
+file_changed_callback (CajaFile *file, gpointer callback_data)
+{
+ FMDirectoryView *view = FM_DIRECTORY_VIEW (callback_data);
+
+ schedule_changes (view);
+
+ schedule_update_menus (view);
+ schedule_update_status (view);
+
+ /* We might have different capabilities, so we need to update
+ * relative icon emblems . (Writeable etc).
+ * Don't do this for trash, as it never changes writability
+ * but does change a lot for the file count attribute.
+ */
+ if (!caja_file_is_in_trash (file)) {
+ EEL_CALL_METHOD
+ (FM_DIRECTORY_VIEW_CLASS, view, emblems_changed, (view));
+ }
+}
+
+/**
+ * load_directory:
+ *
+ * Switch the displayed location to a new uri. If the uri is not valid,
+ * the location will not be switched; user feedback will be provided instead.
+ * @view: FMDirectoryView whose location will be changed.
+ * @uri: A string representing the uri to switch to.
+ *
+ **/
+static void
+load_directory (FMDirectoryView *view,
+ CajaDirectory *directory)
+{
+ CajaDirectory *old_directory;
+ CajaFile *old_file;
+ CajaFileAttributes attributes;
+
+ g_assert (FM_IS_DIRECTORY_VIEW (view));
+ g_assert (CAJA_IS_DIRECTORY (directory));
+
+ fm_directory_view_stop (view);
+ fm_directory_view_clear (view);
+
+ view->details->loading = TRUE;
+
+ /* Update menus when directory is empty, before going to new
+ * location, so they won't have any false lingering knowledge
+ * of old selection.
+ */
+ schedule_update_menus (view);
+
+ while (view->details->subdirectory_list != NULL) {
+ fm_directory_view_remove_subdirectory (view,
+ view->details->subdirectory_list->data);
+ }
+
+ disconnect_model_handlers (view);
+
+ old_directory = view->details->model;
+ caja_directory_ref (directory);
+ view->details->model = directory;
+ caja_directory_unref (old_directory);
+
+ old_file = view->details->directory_as_file;
+ view->details->directory_as_file =
+ caja_directory_get_corresponding_file (directory);
+ caja_file_unref (old_file);
+
+ view->details->reported_load_error = FALSE;
+
+ /* FIXME bugzilla.gnome.org 45062: In theory, we also need to monitor metadata here (as
+ * well as doing a call when ready), in case external forces
+ * change the directory's file metadata.
+ */
+ attributes =
+ CAJA_FILE_ATTRIBUTE_INFO |
+ CAJA_FILE_ATTRIBUTE_MOUNT |
+ CAJA_FILE_ATTRIBUTE_FILESYSTEM_INFO;
+ view->details->metadata_for_directory_as_file_pending = TRUE;
+ view->details->metadata_for_files_in_directory_pending = TRUE;
+ caja_file_call_when_ready
+ (view->details->directory_as_file,
+ attributes,
+ metadata_for_directory_as_file_ready_callback, view);
+ caja_directory_call_when_ready
+ (view->details->model,
+ attributes,
+ FALSE,
+ metadata_for_files_in_directory_ready_callback, view);
+
+ /* If capabilities change, then we need to update the menus
+ * because of New Folder, and relative emblems.
+ */
+ attributes =
+ CAJA_FILE_ATTRIBUTE_INFO |
+ CAJA_FILE_ATTRIBUTE_FILESYSTEM_INFO;
+ caja_file_monitor_add (view->details->directory_as_file,
+ &view->details->directory_as_file,
+ attributes);
+
+ view->details->file_changed_handler_id = g_signal_connect
+ (view->details->directory_as_file, "changed",
+ G_CALLBACK (file_changed_callback), view);
+}
+
+static void
+finish_loading (FMDirectoryView *view)
+{
+ CajaFileAttributes attributes;
+
+ caja_window_info_report_load_underway (view->details->window,
+ CAJA_VIEW (view));
+
+ /* Tell interested parties that we've begun loading this directory now.
+ * Subclasses use this to know that the new metadata is now available.
+ */
+ fm_directory_view_begin_loading (view);
+
+ /* Assume we have now all information to show window */
+ caja_window_info_view_visible (view->details->window, CAJA_VIEW (view));
+
+ if (caja_directory_are_all_files_seen (view->details->model)) {
+ /* Unschedule a pending update and schedule a new one with the minimal
+ * update interval. This gives the view a short chance at gathering the
+ * (cached) deep counts.
+ */
+ unschedule_display_of_pending_files (view);
+ schedule_timeout_display_of_pending_files (view, UPDATE_INTERVAL_MIN);
+ }
+
+ /* Start loading. */
+
+ /* Connect handlers to learn about loading progress. */
+ view->details->done_loading_handler_id = g_signal_connect
+ (view->details->model, "done_loading",
+ G_CALLBACK (done_loading_callback), view);
+ view->details->load_error_handler_id = g_signal_connect
+ (view->details->model, "load_error",
+ G_CALLBACK (load_error_callback), view);
+
+ /* Monitor the things needed to get the right icon. Also
+ * monitor a directory's item count because the "size"
+ * attribute is based on that, and the file's metadata
+ * and possible custom name.
+ */
+ attributes =
+ CAJA_FILE_ATTRIBUTES_FOR_ICON |
+ CAJA_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT |
+ CAJA_FILE_ATTRIBUTE_INFO |
+ CAJA_FILE_ATTRIBUTE_LINK_INFO |
+ CAJA_FILE_ATTRIBUTE_MOUNT |
+ CAJA_FILE_ATTRIBUTE_EXTENSION_INFO;
+
+ caja_directory_file_monitor_add (view->details->model,
+ &view->details->model,
+ view->details->show_hidden_files,
+ view->details->show_backup_files,
+ attributes,
+ files_added_callback, view);
+
+ view->details->files_added_handler_id = g_signal_connect
+ (view->details->model, "files_added",
+ G_CALLBACK (files_added_callback), view);
+ view->details->files_changed_handler_id = g_signal_connect
+ (view->details->model, "files_changed",
+ G_CALLBACK (files_changed_callback), view);
+}
+
+static void
+finish_loading_if_all_metadata_loaded (FMDirectoryView *view)
+{
+ if (!view->details->metadata_for_directory_as_file_pending &&
+ !view->details->metadata_for_files_in_directory_pending) {
+ finish_loading (view);
+ }
+}
+
+static void
+metadata_for_directory_as_file_ready_callback (CajaFile *file,
+ gpointer callback_data)
+{
+ FMDirectoryView *view;
+
+ view = callback_data;
+
+ g_assert (FM_IS_DIRECTORY_VIEW (view));
+ g_assert (view->details->directory_as_file == file);
+ g_assert (view->details->metadata_for_directory_as_file_pending);
+
+ view->details->metadata_for_directory_as_file_pending = FALSE;
+
+ finish_loading_if_all_metadata_loaded (view);
+}
+
+static void
+metadata_for_files_in_directory_ready_callback (CajaDirectory *directory,
+ GList *files,
+ gpointer callback_data)
+{
+ FMDirectoryView *view;
+
+ view = callback_data;
+
+ g_assert (FM_IS_DIRECTORY_VIEW (view));
+ g_assert (view->details->model == directory);
+ g_assert (view->details->metadata_for_files_in_directory_pending);
+
+ view->details->metadata_for_files_in_directory_pending = FALSE;
+
+ finish_loading_if_all_metadata_loaded (view);
+}
+
+char **
+fm_directory_view_get_emblem_names_to_exclude (FMDirectoryView *view)
+{
+ g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), NULL);
+
+ return EEL_CALL_METHOD_WITH_RETURN_VALUE
+ (FM_DIRECTORY_VIEW_CLASS, view,
+ get_emblem_names_to_exclude, (view));
+}
+
+static char **
+real_get_emblem_names_to_exclude (FMDirectoryView *view)
+{
+ char **excludes;
+ int i;
+
+ g_assert (FM_IS_DIRECTORY_VIEW (view));
+
+ excludes = g_new (char *, 3);
+
+ i = 0;
+ excludes[i++] = g_strdup (CAJA_FILE_EMBLEM_NAME_TRASH);
+
+ if (!caja_file_can_write (view->details->directory_as_file)) {
+ excludes[i++] = g_strdup (CAJA_FILE_EMBLEM_NAME_CANT_WRITE);
+ }
+
+ excludes[i++] = NULL;
+
+ return excludes;
+}
+
+/**
+ * fm_directory_view_merge_menus:
+ *
+ * Add this view's menus to the window's menu bar.
+ * @view: FMDirectoryView in question.
+ */
+static void
+fm_directory_view_merge_menus (FMDirectoryView *view)
+{
+ g_return_if_fail (FM_IS_DIRECTORY_VIEW (view));
+
+ EEL_CALL_METHOD
+ (FM_DIRECTORY_VIEW_CLASS, view,
+ merge_menus, (view));
+}
+
+static void
+fm_directory_view_unmerge_menus (FMDirectoryView *view)
+{
+ g_return_if_fail (FM_IS_DIRECTORY_VIEW (view));
+
+ EEL_CALL_METHOD
+ (FM_DIRECTORY_VIEW_CLASS, view,
+ unmerge_menus, (view));
+}
+
+static void
+disconnect_handler (GObject *object, int *id)
+{
+ if (*id != 0) {
+ g_signal_handler_disconnect (object, *id);
+ *id = 0;
+ }
+}
+
+static void
+disconnect_directory_handler (FMDirectoryView *view, int *id)
+{
+ disconnect_handler (G_OBJECT (view->details->model), id);
+}
+
+static void
+disconnect_directory_as_file_handler (FMDirectoryView *view, int *id)
+{
+ disconnect_handler (G_OBJECT (view->details->directory_as_file), id);
+}
+
+static void
+disconnect_model_handlers (FMDirectoryView *view)
+{
+ if (view->details->model == NULL) {
+ return;
+ }
+ disconnect_directory_handler (view, &view->details->files_added_handler_id);
+ disconnect_directory_handler (view, &view->details->files_changed_handler_id);
+ disconnect_directory_handler (view, &view->details->done_loading_handler_id);
+ disconnect_directory_handler (view, &view->details->load_error_handler_id);
+ disconnect_directory_as_file_handler (view, &view->details->file_changed_handler_id);
+ caja_file_cancel_call_when_ready (view->details->directory_as_file,
+ metadata_for_directory_as_file_ready_callback,
+ view);
+ caja_directory_cancel_callback (view->details->model,
+ metadata_for_files_in_directory_ready_callback,
+ view);
+ caja_directory_file_monitor_remove (view->details->model,
+ &view->details->model);
+ caja_file_monitor_remove (view->details->directory_as_file,
+ &view->details->directory_as_file);
+}
+
+/**
+ * fm_directory_view_reset_to_defaults:
+ *
+ * set sorting order, zoom level, etc. to match defaults
+ *
+ **/
+void
+fm_directory_view_reset_to_defaults (FMDirectoryView *view)
+{
+ CajaWindowShowHiddenFilesMode mode;
+
+ g_return_if_fail (FM_IS_DIRECTORY_VIEW (view));
+
+ EEL_CALL_METHOD
+ (FM_DIRECTORY_VIEW_CLASS, view,
+ reset_to_defaults, (view));
+ mode = caja_window_info_get_hidden_files_mode (view->details->window);
+ if (mode != CAJA_WINDOW_SHOW_HIDDEN_FILES_DEFAULT) {
+ caja_window_info_set_hidden_files_mode (view->details->window,
+ CAJA_WINDOW_SHOW_HIDDEN_FILES_DEFAULT);
+ }
+}
+
+/**
+ * fm_directory_view_select_all:
+ *
+ * select all the items in the view
+ *
+ **/
+void
+fm_directory_view_select_all (FMDirectoryView *view)
+{
+ g_return_if_fail (FM_IS_DIRECTORY_VIEW (view));
+
+ EEL_CALL_METHOD
+ (FM_DIRECTORY_VIEW_CLASS, view,
+ select_all, (view));
+}
+
+/**
+ * fm_directory_view_set_selection:
+ *
+ * set the selection to the items identified in @selection. @selection
+ * should be a list of CajaFiles
+ *
+ **/
+void
+fm_directory_view_set_selection (FMDirectoryView *view, GList *selection)
+{
+ g_return_if_fail (FM_IS_DIRECTORY_VIEW (view));
+
+ EEL_CALL_METHOD
+ (FM_DIRECTORY_VIEW_CLASS, view,
+ set_selection, (view, selection));
+}
+
+static void
+fm_directory_view_select_file (FMDirectoryView *view, CajaFile *file)
+{
+ GList file_list;
+
+ file_list.data = file;
+ file_list.next = NULL;
+ file_list.prev = NULL;
+ fm_directory_view_set_selection (view, &file_list);
+}
+
+/**
+ * fm_directory_view_get_selected_icon_locations:
+ *
+ * return an array of locations of selected icons if available
+ * Return value: GArray of GdkPoints
+ *
+ **/
+GArray *
+fm_directory_view_get_selected_icon_locations (FMDirectoryView *view)
+{
+ g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), NULL);
+
+ return EEL_CALL_METHOD_WITH_RETURN_VALUE
+ (FM_DIRECTORY_VIEW_CLASS, view,
+ get_selected_icon_locations, (view));
+}
+
+/**
+ * fm_directory_view_reveal_selection:
+ *
+ * Scroll as necessary to reveal the selected items.
+ **/
+void
+fm_directory_view_reveal_selection (FMDirectoryView *view)
+{
+ g_return_if_fail (FM_IS_DIRECTORY_VIEW (view));
+
+ EEL_CALL_METHOD
+ (FM_DIRECTORY_VIEW_CLASS, view,
+ reveal_selection, (view));
+}
+
+static gboolean
+remove_all (gpointer key, gpointer value, gpointer callback_data)
+{
+ return TRUE;
+}
+
+/**
+ * fm_directory_view_stop:
+ *
+ * Stop the current ongoing process, such as switching to a new uri.
+ * @view: FMDirectoryView in question.
+ *
+ **/
+void
+fm_directory_view_stop (FMDirectoryView *view)
+{
+ g_return_if_fail (FM_IS_DIRECTORY_VIEW (view));
+
+ unschedule_display_of_pending_files (view);
+ reset_update_interval (view);
+
+ /* Free extra undisplayed files */
+ file_and_directory_list_free (view->details->new_added_files);
+ view->details->new_added_files = NULL;
+ file_and_directory_list_free (view->details->new_changed_files);
+ view->details->new_changed_files = NULL;
+ g_hash_table_foreach_remove (view->details->non_ready_files, remove_all, NULL);
+ file_and_directory_list_free (view->details->old_added_files);
+ view->details->old_added_files = NULL;
+ file_and_directory_list_free (view->details->old_changed_files);
+ view->details->old_changed_files = NULL;
+ eel_g_object_list_free (view->details->pending_locations_selected);
+ view->details->pending_locations_selected = NULL;
+
+ if (view->details->model != NULL) {
+ caja_directory_file_monitor_remove (view->details->model, view);
+ }
+ done_loading (view, FALSE);
+}
+
+gboolean
+fm_directory_view_is_read_only (FMDirectoryView *view)
+{
+ g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE);
+
+ return EEL_CALL_METHOD_WITH_RETURN_VALUE
+ (FM_DIRECTORY_VIEW_CLASS, view,
+ is_read_only, (view));
+}
+
+gboolean
+fm_directory_view_is_empty (FMDirectoryView *view)
+{
+ g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE);
+
+ return EEL_CALL_METHOD_WITH_RETURN_VALUE
+ (FM_DIRECTORY_VIEW_CLASS, view,
+ is_empty, (view));
+}
+
+gboolean
+fm_directory_view_is_editable (FMDirectoryView *view)
+{
+ CajaDirectory *directory;
+
+ directory = fm_directory_view_get_model (view);
+
+ if (directory != NULL) {
+ return caja_directory_is_editable (directory);
+ }
+
+ return TRUE;
+}
+
+void
+fm_directory_view_set_initiated_unmount (FMDirectoryView *view,
+ gboolean initiated_unmount)
+{
+ if (view->details->window != NULL) {
+ caja_window_info_set_initiated_unmount(view->details->window,
+ initiated_unmount);
+ }
+}
+
+static gboolean
+real_is_read_only (FMDirectoryView *view)
+{
+ CajaFile *file;
+
+ if (!fm_directory_view_is_editable (view)) {
+ return TRUE;
+ }
+
+ file = fm_directory_view_get_directory_as_file (view);
+ if (file != NULL) {
+ return !caja_file_can_write (file);
+ }
+ return FALSE;
+}
+
+gboolean
+fm_directory_view_supports_creating_files (FMDirectoryView *view)
+{
+ g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE);
+
+ return EEL_CALL_METHOD_WITH_RETURN_VALUE
+ (FM_DIRECTORY_VIEW_CLASS, view,
+ supports_creating_files, (view));
+}
+
+gboolean
+fm_directory_view_accepts_dragged_files (FMDirectoryView *view)
+{
+ g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE);
+
+ return EEL_CALL_METHOD_WITH_RETURN_VALUE
+ (FM_DIRECTORY_VIEW_CLASS, view,
+ accepts_dragged_files, (view));
+}
+
+/**
+ * fm_directory_view_should_show_file
+ *
+ * Returns whether or not this file should be displayed based on
+ * current filtering options.
+ */
+gboolean
+fm_directory_view_should_show_file (FMDirectoryView *view, CajaFile *file)
+{
+ return caja_file_should_show (file,
+ view->details->show_hidden_files,
+ view->details->show_backup_files,
+ view->details->show_foreign_files);
+}
+
+static gboolean
+real_supports_creating_files (FMDirectoryView *view)
+{
+ g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE);
+
+ return !fm_directory_view_is_read_only (view) && !showing_trash_directory (view);
+}
+
+static gboolean
+real_accepts_dragged_files (FMDirectoryView *view)
+{
+ g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE);
+
+ return !fm_directory_view_is_read_only (view);
+}
+
+gboolean
+fm_directory_view_supports_properties (FMDirectoryView *view)
+{
+ g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE);
+
+ return EEL_CALL_METHOD_WITH_RETURN_VALUE
+ (FM_DIRECTORY_VIEW_CLASS, view,
+ supports_properties, (view));
+}
+
+static gboolean
+real_supports_properties (FMDirectoryView *view)
+{
+ g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE);
+
+ return TRUE;
+}
+
+gboolean
+fm_directory_view_supports_zooming (FMDirectoryView *view)
+{
+ g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE);
+
+ return EEL_CALL_METHOD_WITH_RETURN_VALUE
+ (FM_DIRECTORY_VIEW_CLASS, view,
+ supports_zooming, (view));
+}
+
+static gboolean
+real_supports_zooming (FMDirectoryView *view)
+{
+ g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE);
+
+ return TRUE;
+}
+
+gboolean
+fm_directory_view_using_manual_layout (FMDirectoryView *view)
+{
+ g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE);
+
+ return EEL_CALL_METHOD_WITH_RETURN_VALUE
+ (FM_DIRECTORY_VIEW_CLASS, view,
+ using_manual_layout, (view));
+}
+
+static gboolean
+real_using_manual_layout (FMDirectoryView *view)
+{
+ g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE);
+
+ return FALSE;
+}
+
+/**
+ * fm_directory_view_update_menus:
+ *
+ * Update the sensitivity and wording of dynamic menu items.
+ * @view: FMDirectoryView in question.
+ */
+void
+fm_directory_view_update_menus (FMDirectoryView *view)
+{
+ g_return_if_fail (FM_IS_DIRECTORY_VIEW (view));
+
+ if (!view->details->active) {
+ return;
+ }
+
+
+ EEL_CALL_METHOD
+ (FM_DIRECTORY_VIEW_CLASS, view,
+ update_menus, (view));
+
+ view->details->menu_states_untrustworthy = FALSE;
+}
+
+static void
+schedule_update_menus_callback (gpointer callback_data)
+{
+ schedule_update_menus (FM_DIRECTORY_VIEW (callback_data));
+}
+
+void
+fm_directory_view_ignore_hidden_file_preferences (FMDirectoryView *view)
+{
+ g_return_if_fail (view->details->model == NULL);
+
+ if (view->details->ignore_hidden_file_preferences) {
+ return;
+ }
+
+ view->details->show_hidden_files = FALSE;
+ view->details->show_backup_files = FALSE;
+ view->details->ignore_hidden_file_preferences = TRUE;
+}
+
+void
+fm_directory_view_set_show_foreign (FMDirectoryView *view,
+ gboolean show_foreign)
+{
+ view->details->show_foreign_files = show_foreign;
+}
+
+char *
+fm_directory_view_get_uri (FMDirectoryView *view)
+{
+ g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), NULL);
+ if (view->details->model == NULL) {
+ return NULL;
+ }
+ return caja_directory_get_uri (view->details->model);
+}
+
+/* Get the real directory where files will be stored and created */
+char *
+fm_directory_view_get_backing_uri (FMDirectoryView *view)
+{
+ CajaDirectory *directory;
+ char *uri;
+
+ g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), NULL);
+
+ if (view->details->model == NULL) {
+ return NULL;
+ }
+
+ directory = view->details->model;
+
+ if (CAJA_IS_DESKTOP_DIRECTORY (directory)) {
+ directory = caja_desktop_directory_get_real_directory (CAJA_DESKTOP_DIRECTORY (directory));
+ } else {
+ caja_directory_ref (directory);
+ }
+
+ uri = caja_directory_get_uri (directory);
+
+ caja_directory_unref (directory);
+
+ return uri;
+}
+
+void
+fm_directory_view_move_copy_items (const GList *item_uris,
+ GArray *relative_item_points,
+ const char *target_uri,
+ int copy_action,
+ int x, int y,
+ FMDirectoryView *view)
+{
+ CajaFile *target_file;
+
+ g_assert (relative_item_points == NULL
+ || relative_item_points->len == 0
+ || g_list_length ((GList *)item_uris) == relative_item_points->len);
+
+ /* add the drop location to the icon offsets */
+ offset_drop_points (relative_item_points, x, y);
+
+ target_file = caja_file_get_existing_by_uri (target_uri);
+ /* special-case "command:" here instead of starting a move/copy */
+ if (target_file != NULL && caja_file_is_launcher (target_file)) {
+ caja_file_unref (target_file);
+ caja_launch_desktop_file (
+ gtk_widget_get_screen (GTK_WIDGET (view)),
+ target_uri, item_uris,
+ fm_directory_view_get_containing_window (view));
+ return;
+ } else if (copy_action == GDK_ACTION_COPY &&
+ caja_is_file_roller_installed () &&
+ target_file != NULL &&
+ caja_file_is_archive (target_file)) {
+ char *command, *quoted_uri, *tmp;
+ const GList *l;
+ GdkScreen *screen;
+
+ /* Handle dropping onto a file-roller archiver file, instead of starting a move/copy */
+
+ caja_file_unref (target_file);
+
+ quoted_uri = g_shell_quote (target_uri);
+ command = g_strconcat ("file-roller -a ", quoted_uri, NULL);
+ g_free (quoted_uri);
+
+ for (l = item_uris; l != NULL; l = l->next) {
+ quoted_uri = g_shell_quote ((char *) l->data);
+
+ tmp = g_strconcat (command, " ", quoted_uri, NULL);
+ g_free (command);
+ command = tmp;
+
+ g_free (quoted_uri);
+ }
+
+ screen = gtk_widget_get_screen (GTK_WIDGET (view));
+ if (screen == NULL) {
+ screen = gdk_screen_get_default ();
+ }
+ gdk_spawn_command_line_on_screen (screen, command, NULL);
+ g_free (command);
+
+ return;
+ }
+ caja_file_unref (target_file);
+
+ caja_file_operations_copy_move
+ (item_uris, relative_item_points,
+ target_uri, copy_action, GTK_WIDGET (view),
+ copy_move_done_callback, pre_copy_move (view));
+}
+
+gboolean
+fm_directory_view_can_accept_item (CajaFile *target_item,
+ const char *item_uri,
+ FMDirectoryView *view)
+{
+ g_return_val_if_fail (CAJA_IS_FILE (target_item), FALSE);
+ g_return_val_if_fail (item_uri != NULL, FALSE);
+ g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), FALSE);
+
+ return caja_drag_can_accept_item (target_item, item_uri);
+}
+
+static void
+fm_directory_view_trash_state_changed_callback (CajaTrashMonitor *trash_monitor,
+ gboolean state, gpointer callback_data)
+{
+ FMDirectoryView *view;
+
+ view = (FMDirectoryView *) callback_data;
+ g_assert (FM_IS_DIRECTORY_VIEW (view));
+
+ schedule_update_menus (view);
+}
+
+void
+fm_directory_view_start_batching_selection_changes (FMDirectoryView *view)
+{
+ g_return_if_fail (FM_IS_DIRECTORY_VIEW (view));
+
+ ++view->details->batching_selection_level;
+ view->details->selection_changed_while_batched = FALSE;
+}
+
+void
+fm_directory_view_stop_batching_selection_changes (FMDirectoryView *view)
+{
+ g_return_if_fail (FM_IS_DIRECTORY_VIEW (view));
+ g_return_if_fail (view->details->batching_selection_level > 0);
+
+ if (--view->details->batching_selection_level == 0) {
+ if (view->details->selection_changed_while_batched) {
+ fm_directory_view_notify_selection_changed (view);
+ }
+ }
+}
+
+static void
+revert_slashes (char *string)
+{
+ while (*string != 0) {
+ if (*string == '/') {
+ *string = '\\';
+ }
+ string++;
+ }
+}
+
+
+static GdkDragAction
+ask_link_action (FMDirectoryView *view)
+{
+ int button_pressed;
+ GdkDragAction result;
+ GtkWindow *parent_window;
+ GtkWidget *dialog;
+
+ parent_window = NULL;
+
+ /* Don't use desktop window as parent, since that means
+ we show up an all desktops etc */
+ if (! FM_IS_DESKTOP_ICON_VIEW (view)) {
+ parent_window = GTK_WINDOW (fm_directory_view_get_containing_window (view));
+ }
+
+ dialog = gtk_message_dialog_new (parent_window,
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_QUESTION,
+ GTK_BUTTONS_NONE,
+ _("Download location?"));
+
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ _("You can download it or make a link to it."));
+
+ gtk_dialog_add_button (GTK_DIALOG (dialog),
+ _("Make a _Link"), 0);
+ gtk_dialog_add_button (GTK_DIALOG (dialog),
+ GTK_STOCK_CANCEL, 1);
+ gtk_dialog_add_button (GTK_DIALOG (dialog),
+ _("_Download"), 2);
+
+ gtk_window_set_title (GTK_WINDOW (dialog), ""); /* as per HIG */
+ gtk_window_set_focus_on_map (GTK_WINDOW (dialog), TRUE);
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), 2);
+
+ gtk_window_present (GTK_WINDOW (dialog));
+
+ button_pressed = gtk_dialog_run (GTK_DIALOG (dialog));
+
+ gtk_widget_destroy (dialog);
+
+ switch (button_pressed) {
+ case 0:
+ result = GDK_ACTION_LINK;
+ break;
+ case 1:
+ case GTK_RESPONSE_DELETE_EVENT:
+ result = 0;
+ break;
+ case 2:
+ result = GDK_ACTION_COPY;
+ break;
+ default:
+ g_assert_not_reached ();
+ result = 0;
+ }
+
+ return result;
+}
+
+typedef struct {
+ FMDirectoryView *view;
+ GCancellable *cancellable;
+ char *encoded_url;
+ char *target_uri;
+ int x;
+ int y;
+ guint timeout;
+} NetscapeUrlDropAsk;
+
+static void
+handle_netscape_url_drop_ask_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NetscapeUrlDropAsk *data;
+ GdkDragAction action;
+ GFileInfo *info;
+ GFile *f;
+ const char *mime_type;
+
+ data = user_data;
+ f = G_FILE (source_object);
+
+ info = g_file_query_info_finish (f, res, NULL);
+ mime_type = NULL;
+
+ if (info) {
+ mime_type = g_file_info_get_content_type (info);
+ }
+
+ if (mime_type != NULL &&
+ (g_content_type_equals (mime_type, "text/html") ||
+ g_content_type_equals (mime_type, "text/xml") ||
+ g_content_type_equals (mime_type, "application/xhtml+xml"))) {
+ action = GDK_ACTION_LINK;
+ } else if (mime_type != NULL &&
+ g_content_type_equals (mime_type, "text/plain")) {
+ action = ask_link_action (data->view);
+ } else {
+ action = GDK_ACTION_COPY;
+ }
+ if (info) {
+ g_object_unref (info);
+ }
+
+ if (action != 0) {
+ fm_directory_view_handle_netscape_url_drop (data->view,
+ data->encoded_url,
+ data->target_uri,
+ action,
+ data->x, data->y);
+ }
+
+ g_object_unref (data->view);
+ g_object_unref (data->cancellable);
+ if (data->timeout != 0) {
+ g_source_remove (data->timeout);
+ }
+ g_free (data->encoded_url);
+ g_free (data->target_uri);
+ g_free (data);
+}
+
+static gboolean
+handle_netscape_url_drop_timeout (gpointer user_data)
+{
+ NetscapeUrlDropAsk *data;
+
+ data = user_data;
+
+ g_cancellable_cancel (data->cancellable);
+ data->timeout = 0;
+
+ return FALSE;
+}
+
+static inline void
+fm_directory_view_widget_to_file_operation_position (FMDirectoryView *view,
+ GdkPoint *position)
+{
+ EEL_CALL_METHOD (FM_DIRECTORY_VIEW_CLASS, view,
+ widget_to_file_operation_position,
+ (view, position));
+}
+
+static void
+fm_directory_view_widget_to_file_operation_position_xy (FMDirectoryView *view,
+ int *x, int *y)
+{
+ GdkPoint position;
+
+ position.x = *x;
+ position.y = *y;
+ fm_directory_view_widget_to_file_operation_position (view, &position);
+ *x = position.x;
+ *y = position.y;
+}
+
+void
+fm_directory_view_handle_netscape_url_drop (FMDirectoryView *view,
+ const char *encoded_url,
+ const char *target_uri,
+ GdkDragAction action,
+ int x,
+ int y)
+{
+ GdkPoint point;
+ GdkScreen *screen;
+ int screen_num;
+ char *url, *title;
+ char *link_name, *link_display_name;
+ char *container_uri;
+ GArray *points;
+ char **bits;
+ GList *uri_list = NULL;
+ GFile *f;
+
+ if (encoded_url == NULL) {
+ return;
+ }
+
+ container_uri = NULL;
+ if (target_uri == NULL) {
+ container_uri = fm_directory_view_get_backing_uri (view);
+ g_assert (container_uri != NULL);
+ }
+
+ f = g_file_new_for_uri (target_uri != NULL ? target_uri : container_uri);
+ if (!g_file_is_native (f)) {
+ eel_show_warning_dialog (_("Drag and drop is not supported."),
+ _("Drag and drop is only supported on local file systems."),
+ fm_directory_view_get_containing_window (view));
+ g_object_unref (f);
+ g_free (container_uri);
+ return;
+ }
+ g_object_unref (f);
+
+ /* _NETSCAPE_URL_ works like this: $URL\n$TITLE */
+ bits = g_strsplit (encoded_url, "\n", 0);
+ switch (g_strv_length (bits)) {
+ case 0:
+ g_strfreev (bits);
+ g_free (container_uri);
+ return;
+ case 1:
+ url = bits[0];
+ title = NULL;
+ break;
+ default:
+ url = bits[0];
+ title = bits[1];
+ }
+
+ if (action == GDK_ACTION_ASK) {
+ NetscapeUrlDropAsk *data;
+
+ f = g_file_new_for_uri (url);
+ data = g_new0 (NetscapeUrlDropAsk, 1);
+ data->view = g_object_ref (view);
+ data->cancellable = g_cancellable_new ();
+ data->encoded_url = g_strdup (encoded_url);
+ data->target_uri = g_strdup (target_uri);
+ data->x = x;
+ data->y = y;
+ /* Ensure we wait at most 1 second for mimetype */
+ data->timeout = g_timeout_add (1000,
+ handle_netscape_url_drop_timeout,
+ data);
+ g_file_query_info_async (f,
+ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
+ 0, data->cancellable,
+ handle_netscape_url_drop_ask_cb,
+ data);
+
+ g_free (container_uri);
+ return;
+ }
+
+ fm_directory_view_widget_to_file_operation_position_xy (view, &x, &y);
+
+ /* We don't support GDK_ACTION_ASK or GDK_ACTION_PRIVATE
+ * and we don't support combinations either. */
+ if ((action != GDK_ACTION_DEFAULT) &&
+ (action != GDK_ACTION_COPY) &&
+ (action != GDK_ACTION_MOVE) &&
+ (action != GDK_ACTION_LINK)) {
+ eel_show_warning_dialog (_("Drag and drop is not supported."),
+ _("An invalid drag type was used."),
+ fm_directory_view_get_containing_window (view));
+ g_free (container_uri);
+ return;
+ }
+
+ if (action == GDK_ACTION_LINK) {
+ if (eel_str_is_empty (title)) {
+ GFile *f;
+
+ f = g_file_new_for_uri (url);
+ link_name = g_file_get_basename (f);
+ g_object_unref (f);
+ } else {
+ link_name = g_strdup (title);
+ }
+
+ if (!eel_str_is_empty (link_name)) {
+ link_display_name = g_strdup_printf (_("Link to %s"), link_name);
+
+ /* The filename can't contain slashes, strip em.
+ (the basename of http://foo/ is http://foo/) */
+ revert_slashes (link_name);
+
+ point.x = x;
+ point.y = y;
+
+ screen = gtk_widget_get_screen (GTK_WIDGET (view));
+ screen_num = gdk_screen_get_number (screen);
+
+ caja_link_local_create (target_uri != NULL ? target_uri : container_uri,
+ link_name,
+ link_display_name,
+ "mate-fs-bookmark",
+ url,
+ &point,
+ screen_num,
+ TRUE);
+
+ g_free (link_display_name);
+ }
+ g_free (link_name);
+ } else {
+ GdkPoint tmp_point = { 0, 0 };
+
+ /* pass in a 1-item array of icon positions, relative to x, y */
+ points = g_array_new (FALSE, TRUE, sizeof (GdkPoint));
+ g_array_append_val (points, tmp_point);
+
+ uri_list = g_list_append (uri_list, url);
+
+ fm_directory_view_move_copy_items (uri_list, points,
+ target_uri != NULL ? target_uri : container_uri,
+ action, x, y, view);
+
+ g_list_free (uri_list);
+ g_array_free (points, TRUE);
+ }
+
+ g_strfreev (bits);
+
+ g_free (container_uri);
+}
+
+void
+fm_directory_view_handle_uri_list_drop (FMDirectoryView *view,
+ const char *item_uris,
+ const char *target_uri,
+ GdkDragAction action,
+ int x,
+ int y)
+{
+ gchar **uri_list;
+ GList *real_uri_list = NULL;
+ char *container_uri;
+ int n_uris, i;
+ GArray *points;
+
+ if (item_uris == NULL) {
+ return;
+ }
+
+ container_uri = NULL;
+ if (target_uri == NULL) {
+ container_uri = fm_directory_view_get_backing_uri (view);
+ g_assert (container_uri != NULL);
+ }
+
+ if (action == GDK_ACTION_ASK) {
+ action = caja_drag_drop_action_ask
+ (GTK_WIDGET (view),
+ GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK);
+ if (action == 0) {
+ g_free (container_uri);
+ return;
+ }
+ }
+
+ /* We don't support GDK_ACTION_ASK or GDK_ACTION_PRIVATE
+ * and we don't support combinations either. */
+ if ((action != GDK_ACTION_DEFAULT) &&
+ (action != GDK_ACTION_COPY) &&
+ (action != GDK_ACTION_MOVE) &&
+ (action != GDK_ACTION_LINK)) {
+ eel_show_warning_dialog (_("Drag and drop is not supported."),
+ _("An invalid drag type was used."),
+ fm_directory_view_get_containing_window (view));
+ g_free (container_uri);
+ return;
+ }
+
+ n_uris = 0;
+ uri_list = g_uri_list_extract_uris (item_uris);
+ for (i = 0; uri_list[i] != NULL; i++) {
+ real_uri_list = g_list_append (real_uri_list, uri_list[i]);
+ n_uris++;
+ }
+ g_free (uri_list);
+
+ /* do nothing if no real uris are left */
+ if (n_uris == 0) {
+ g_free (container_uri);
+ return;
+ }
+
+ if (n_uris == 1) {
+ GdkPoint tmp_point = { 0, 0 };
+
+ /* pass in a 1-item array of icon positions, relative to x, y */
+ points = g_array_new (FALSE, TRUE, sizeof (GdkPoint));
+ g_array_append_val (points, tmp_point);
+ } else {
+ points = NULL;
+ }
+
+ fm_directory_view_widget_to_file_operation_position_xy (view, &x, &y);
+
+ fm_directory_view_move_copy_items (real_uri_list, points,
+ target_uri != NULL ? target_uri : container_uri,
+ action, x, y, view);
+
+ eel_g_list_free_deep (real_uri_list);
+
+ if (points != NULL)
+ g_array_free (points, TRUE);
+
+ g_free (container_uri);
+}
+
+void
+fm_directory_view_handle_text_drop (FMDirectoryView *view,
+ const char *text,
+ const char *target_uri,
+ GdkDragAction action,
+ int x,
+ int y)
+{
+ int length;
+ char *container_uri;
+ GdkPoint pos;
+
+ if (text == NULL) {
+ return;
+ }
+
+ g_return_if_fail (action == GDK_ACTION_COPY);
+
+ container_uri = NULL;
+ if (target_uri == NULL) {
+ container_uri = fm_directory_view_get_backing_uri (view);
+ g_assert (container_uri != NULL);
+ }
+
+ length = strlen (text);
+
+ pos.x = x;
+ pos.y = y;
+ fm_directory_view_widget_to_file_operation_position (view, &pos);
+
+ fm_directory_view_new_file_with_initial_contents (
+ view, target_uri != NULL ? target_uri : container_uri,
+ /* Translator: This is the filename used for when you dnd text to a directory */
+ _("dropped text.txt"),
+ text, length, &pos);
+
+ g_free (container_uri);
+}
+
+void
+fm_directory_view_handle_raw_drop (FMDirectoryView *view,
+ const char *raw_data,
+ int length,
+ const char *target_uri,
+ const char *direct_save_uri,
+ GdkDragAction action,
+ int x,
+ int y)
+{
+ char *container_uri, *filename;
+ GFile *direct_save_full;
+ GdkPoint pos;
+
+ if (raw_data == NULL) {
+ return;
+ }
+
+ g_return_if_fail (action == GDK_ACTION_COPY);
+
+ container_uri = NULL;
+ if (target_uri == NULL) {
+ container_uri = fm_directory_view_get_backing_uri (view);
+ g_assert (container_uri != NULL);
+ }
+
+ pos.x = x;
+ pos.y = y;
+ fm_directory_view_widget_to_file_operation_position (view, &pos);
+
+ filename = NULL;
+ if (direct_save_uri != NULL) {
+ direct_save_full = g_file_new_for_uri (direct_save_uri);
+ filename = g_file_get_basename (direct_save_full);
+ }
+ if (filename == NULL) {
+ /* Translator: This is the filename used for when you dnd raw
+ * data to a directory, if the source didn't supply a name.
+ */
+ filename = _("dropped data");
+ }
+
+ fm_directory_view_new_file_with_initial_contents (
+ view, target_uri != NULL ? target_uri : container_uri,
+ filename, raw_data, length, &pos);
+
+ g_free (container_uri);
+}
+
+gboolean
+fm_directory_view_get_active (FMDirectoryView *view)
+{
+ g_assert (FM_IS_DIRECTORY_VIEW (view));
+ return view->details->active;
+}
+
+static GArray *
+real_get_selected_icon_locations (FMDirectoryView *view)
+{
+ /* By default, just return an empty list. */
+ return g_array_new (FALSE, TRUE, sizeof (GdkPoint));
+}
+
+static void
+fm_directory_view_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ FMDirectoryView *directory_view;
+ CajaWindowSlotInfo *slot;
+ CajaWindowInfo *window;
+
+ directory_view = FM_DIRECTORY_VIEW (object);
+
+ switch (prop_id) {
+ case PROP_WINDOW_SLOT:
+ g_assert (directory_view->details->slot == NULL);
+
+ slot = CAJA_WINDOW_SLOT_INFO (g_value_get_object (value));
+ window = caja_window_slot_info_get_window (slot);
+
+ directory_view->details->slot = slot;
+ directory_view->details->window = window;
+
+ g_signal_connect_object (directory_view->details->slot,
+ "active", G_CALLBACK (slot_active),
+ directory_view, 0);
+ g_signal_connect_object (directory_view->details->slot,
+ "inactive", G_CALLBACK (slot_inactive),
+ directory_view, 0);
+
+ g_signal_connect_object (directory_view->details->window,
+ "hidden-files-mode-changed", G_CALLBACK (hidden_files_mode_changed),
+ directory_view, 0);
+ fm_directory_view_init_show_hidden_files (directory_view);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+
+gboolean
+fm_directory_view_handle_scroll_event (FMDirectoryView *directory_view,
+ GdkEventScroll *event)
+{
+ if (event->state & GDK_CONTROL_MASK) {
+ switch (event->direction) {
+ case GDK_SCROLL_UP:
+ /* Zoom In */
+ fm_directory_view_bump_zoom_level (directory_view, 1);
+ return TRUE;
+
+ case GDK_SCROLL_DOWN:
+ /* Zoom Out */
+ fm_directory_view_bump_zoom_level (directory_view, -1);
+ return TRUE;
+
+ case GDK_SCROLL_LEFT:
+ case GDK_SCROLL_RIGHT:
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+ }
+
+ return FALSE;
+}
+
+/* handle Shift+Scroll, which will cause a zoom-in/out */
+static gboolean
+fm_directory_view_scroll_event (GtkWidget *widget,
+ GdkEventScroll *event)
+{
+ FMDirectoryView *directory_view;
+
+ directory_view = FM_DIRECTORY_VIEW (widget);
+ if (fm_directory_view_handle_scroll_event (directory_view, event)) {
+ return TRUE;
+ }
+
+ return GTK_WIDGET_CLASS (parent_class)->scroll_event (widget, event);
+}
+
+
+static void
+fm_directory_view_parent_set (GtkWidget *widget,
+ GtkWidget *old_parent)
+{
+ FMDirectoryView *view;
+ GtkWidget *parent;
+
+ view = FM_DIRECTORY_VIEW (widget);
+
+ parent = gtk_widget_get_parent (widget);
+ g_assert (parent == NULL || old_parent == NULL);
+
+ if (GTK_WIDGET_CLASS (parent_class)->parent_set != NULL) {
+ GTK_WIDGET_CLASS (parent_class)->parent_set (widget, old_parent);
+ }
+
+ if (parent != NULL) {
+ g_assert (old_parent == NULL);
+
+ if (view->details->slot ==
+ caja_window_info_get_active_slot (view->details->window)) {
+ view->details->active = TRUE;
+
+ fm_directory_view_merge_menus (view);
+ schedule_update_menus (view);
+ }
+ } else {
+ fm_directory_view_unmerge_menus (view);
+ remove_update_menus_timeout_callback (view);
+ }
+}
+
+static void
+fm_directory_view_class_init (FMDirectoryViewClass *klass)
+{
+ GtkWidgetClass *widget_class;
+ GtkScrolledWindowClass *scrolled_window_class;
+ GtkBindingSet *binding_set;
+
+ widget_class = GTK_WIDGET_CLASS (klass);
+ scrolled_window_class = GTK_SCROLLED_WINDOW_CLASS (klass);
+
+ G_OBJECT_CLASS (klass)->finalize = fm_directory_view_finalize;
+ G_OBJECT_CLASS (klass)->set_property = fm_directory_view_set_property;
+
+ GTK_OBJECT_CLASS (klass)->destroy = fm_directory_view_destroy;
+
+ widget_class->scroll_event = fm_directory_view_scroll_event;
+ widget_class->parent_set = fm_directory_view_parent_set;
+
+ /* Get rid of the strange 3-pixel gap that GtkScrolledWindow
+ * uses by default. It does us no good.
+ */
+ scrolled_window_class->scrollbar_spacing = 0;
+
+ signals[ADD_FILE] =
+ g_signal_new ("add_file",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (FMDirectoryViewClass, add_file),
+ NULL, NULL,
+ caja_marshal_VOID__OBJECT_OBJECT,
+ G_TYPE_NONE, 2, CAJA_TYPE_FILE, CAJA_TYPE_DIRECTORY);
+ signals[BEGIN_FILE_CHANGES] =
+ g_signal_new ("begin_file_changes",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (FMDirectoryViewClass, begin_file_changes),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ signals[BEGIN_LOADING] =
+ g_signal_new ("begin_loading",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (FMDirectoryViewClass, begin_loading),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ signals[CLEAR] =
+ g_signal_new ("clear",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (FMDirectoryViewClass, clear),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ signals[END_FILE_CHANGES] =
+ g_signal_new ("end_file_changes",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (FMDirectoryViewClass, end_file_changes),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ signals[FLUSH_ADDED_FILES] =
+ g_signal_new ("flush_added_files",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (FMDirectoryViewClass, flush_added_files),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ signals[END_LOADING] =
+ g_signal_new ("end_loading",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (FMDirectoryViewClass, end_loading),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__BOOLEAN,
+ G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
+ signals[FILE_CHANGED] =
+ g_signal_new ("file_changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (FMDirectoryViewClass, file_changed),
+ NULL, NULL,
+ caja_marshal_VOID__OBJECT_OBJECT,
+ G_TYPE_NONE, 2, CAJA_TYPE_FILE, CAJA_TYPE_DIRECTORY);
+ signals[LOAD_ERROR] =
+ g_signal_new ("load_error",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (FMDirectoryViewClass, load_error),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1, G_TYPE_POINTER);
+ signals[REMOVE_FILE] =
+ g_signal_new ("remove_file",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (FMDirectoryViewClass, remove_file),
+ NULL, NULL,
+ caja_marshal_VOID__OBJECT_OBJECT,
+ G_TYPE_NONE, 2, CAJA_TYPE_FILE, CAJA_TYPE_DIRECTORY);
+
+ klass->accepts_dragged_files = real_accepts_dragged_files;
+ klass->file_limit_reached = real_file_limit_reached;
+ klass->file_still_belongs = real_file_still_belongs;
+ klass->get_emblem_names_to_exclude = real_get_emblem_names_to_exclude;
+ klass->get_selected_icon_locations = real_get_selected_icon_locations;
+ klass->is_read_only = real_is_read_only;
+ klass->load_error = real_load_error;
+ klass->can_rename_file = can_rename_file;
+ klass->start_renaming_file = start_renaming_file;
+ klass->supports_creating_files = real_supports_creating_files;
+ klass->supports_properties = real_supports_properties;
+ klass->supports_zooming = real_supports_zooming;
+ klass->using_manual_layout = real_using_manual_layout;
+ klass->merge_menus = real_merge_menus;
+ klass->unmerge_menus = real_unmerge_menus;
+ klass->update_menus = real_update_menus;
+ klass->set_is_active = real_set_is_active;
+
+ /* Function pointers that subclasses must override */
+ EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, add_file);
+ EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, bump_zoom_level);
+ EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, can_zoom_in);
+ EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, can_zoom_out);
+ EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, clear);
+ EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, file_changed);
+ EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, get_background_widget);
+ EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, get_selection);
+ EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, get_selection_for_file_transfer);
+ EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, get_item_count);
+ EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, is_empty);
+ EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, reset_to_defaults);
+ EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, restore_default_zoom_level);
+ EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, select_all);
+ EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, set_selection);
+ EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, invert_selection);
+ EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, zoom_to_level);
+ EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, get_zoom_level);
+
+ copied_files_atom = gdk_atom_intern ("x-special/mate-copied-files", FALSE);
+
+ g_object_class_install_property (G_OBJECT_CLASS (klass),
+ PROP_WINDOW_SLOT,
+ g_param_spec_object ("window-slot",
+ "Window Slot",
+ "The parent window slot reference",
+ CAJA_TYPE_WINDOW_SLOT_INFO,
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ signals[TRASH] =
+ g_signal_new ("trash",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (FMDirectoryViewClass, trash),
+ g_signal_accumulator_true_handled, NULL,
+ eel_marshal_BOOLEAN__VOID,
+ G_TYPE_BOOLEAN, 0);
+ signals[DELETE] =
+ g_signal_new ("delete",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (FMDirectoryViewClass, delete),
+ g_signal_accumulator_true_handled, NULL,
+ eel_marshal_BOOLEAN__VOID,
+ G_TYPE_BOOLEAN, 0);
+
+ binding_set = gtk_binding_set_by_class (klass);
+ gtk_binding_entry_add_signal (binding_set, GDK_Delete, 0,
+ "trash", 0);
+ gtk_binding_entry_add_signal (binding_set, GDK_KP_Delete, 0,
+ "trash", 0);
+ gtk_binding_entry_add_signal (binding_set, GDK_KP_Delete, GDK_SHIFT_MASK,
+ "delete", 0);
+
+ klass->trash = real_trash;
+ klass->delete = real_delete;
+}
diff --git a/src/file-manager/fm-directory-view.h b/src/file-manager/fm-directory-view.h
new file mode 100644
index 00000000..f38cbad1
--- /dev/null
+++ b/src/file-manager/fm-directory-view.h
@@ -0,0 +1,495 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* fm-directory-view.h
+ *
+ * Copyright (C) 1999, 2000 Free Software Foundaton
+ * Copyright (C) 2000, 2001 Eazel, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Ettore Perazzoli
+ * Darin Adler <[email protected]>
+ * John Sullivan <[email protected]>
+ * Pavel Cisler <[email protected]>
+ */
+
+#ifndef FM_DIRECTORY_VIEW_H
+#define FM_DIRECTORY_VIEW_H
+
+#include <gtk/gtk.h>
+#include <eel/eel-background.h>
+#include <libcaja-private/caja-directory.h>
+#include <libcaja-private/caja-file.h>
+#include <libcaja-private/caja-icon-container.h>
+#include <libcaja-private/caja-link.h>
+#include <libcaja-private/caja-view.h>
+#include <libcaja-private/caja-window-info.h>
+#include <libcaja-private/caja-window-slot-info.h>
+#include <gio/gio.h>
+
+typedef struct FMDirectoryView FMDirectoryView;
+typedef struct FMDirectoryViewClass FMDirectoryViewClass;
+
+#define FM_TYPE_DIRECTORY_VIEW fm_directory_view_get_type()
+#define FM_DIRECTORY_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_DIRECTORY_VIEW, FMDirectoryView))
+#define FM_DIRECTORY_VIEW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_DIRECTORY_VIEW, FMDirectoryViewClass))
+#define FM_IS_DIRECTORY_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_DIRECTORY_VIEW))
+#define FM_IS_DIRECTORY_VIEW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_DIRECTORY_VIEW))
+#define FM_DIRECTORY_VIEW_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_DIRECTORY_VIEW, FMDirectoryViewClass))
+
+typedef struct FMDirectoryViewDetails FMDirectoryViewDetails;
+
+struct FMDirectoryView
+{
+ GtkScrolledWindow parent;
+ FMDirectoryViewDetails *details;
+};
+
+struct FMDirectoryViewClass
+{
+ GtkScrolledWindowClass parent_class;
+
+ /* The 'clear' signal is emitted to empty the view of its contents.
+ * It must be replaced by each subclass.
+ */
+ void (* clear) (FMDirectoryView *view);
+
+ /* The 'begin_file_changes' signal is emitted before a set of files
+ * are added to the view. It can be replaced by a subclass to do any
+ * necessary preparation for a set of new files. The default
+ * implementation does nothing.
+ */
+ void (* begin_file_changes) (FMDirectoryView *view);
+
+ /* The 'add_file' signal is emitted to add one file to the view.
+ * It must be replaced by each subclass.
+ */
+ void (* add_file) (FMDirectoryView *view,
+ CajaFile *file,
+ CajaDirectory *directory);
+ void (* remove_file) (FMDirectoryView *view,
+ CajaFile *file,
+ CajaDirectory *directory);
+
+ /* The 'file_changed' signal is emitted to signal a change in a file,
+ * including the file being removed.
+ * It must be replaced by each subclass.
+ */
+ void (* file_changed) (FMDirectoryView *view,
+ CajaFile *file,
+ CajaDirectory *directory);
+
+ /* The 'end_file_changes' signal is emitted after a set of files
+ * are added to the view. It can be replaced by a subclass to do any
+ * necessary cleanup (typically, cleanup for code in begin_file_changes).
+ * The default implementation does nothing.
+ */
+ void (* end_file_changes) (FMDirectoryView *view);
+
+ void (* flush_added_files) (FMDirectoryView *view);
+
+ /* The 'begin_loading' signal is emitted before any of the contents
+ * of a directory are added to the view. It can be replaced by a
+ * subclass to do any necessary preparation to start dealing with a
+ * new directory. The default implementation does nothing.
+ */
+ void (* begin_loading) (FMDirectoryView *view);
+
+ /* The 'end_loading' signal is emitted after all of the contents
+ * of a directory are added to the view. It can be replaced by a
+ * subclass to do any necessary clean-up. The default implementation
+ * does nothing.
+ *
+ * If all_files_seen is true, the handler may assume that
+ * no load error ocurred, and all files of the underlying
+ * directory were loaded.
+ *
+ * Otherwise, end_loading was emitted due to cancellation,
+ * which usually means that not all files are available.
+ */
+ void (* end_loading) (FMDirectoryView *view,
+ gboolean all_files_seen);
+
+ /* The 'load_error' signal is emitted when the directory model
+ * reports an error in the process of monitoring the directory's
+ * contents. The load error indicates that the process of
+ * loading the contents has ended, but the directory is still
+ * being monitored. The default implementation handles common
+ * load failures like ACCESS_DENIED.
+ */
+ void (* load_error) (FMDirectoryView *view,
+ GError *error);
+
+ /* Function pointers that don't have corresponding signals */
+
+ /* reset_to_defaults is a function pointer that subclasses must
+ * override to set sort order, zoom level, etc to match default
+ * values.
+ */
+ void (* reset_to_defaults) (FMDirectoryView *view);
+
+ /* get_selection is not a signal; it is just a function pointer for
+ * subclasses to replace (override). Subclasses must replace it
+ * with a function that returns a newly-allocated GList of
+ * CajaFile pointers.
+ */
+ GList * (* get_selection) (FMDirectoryView *view);
+
+ /* get_selection_for_file_transfer is a function pointer for
+ * subclasses to replace (override). Subclasses must replace it
+ * with a function that returns a newly-allocated GList of
+ * CajaFile pointers. The difference from get_selection is
+ * that any files in the selection that also has a parent folder
+ * in the selection is not included.
+ */
+ GList * (* get_selection_for_file_transfer)(FMDirectoryView *view);
+
+ /* select_all is a function pointer that subclasses must override to
+ * select all of the items in the view */
+ void (* select_all) (FMDirectoryView *view);
+
+ /* set_selection is a function pointer that subclasses must
+ * override to select the specified items (and unselect all
+ * others). The argument is a list of CajaFiles. */
+
+ void (* set_selection) (FMDirectoryView *view,
+ GList *selection);
+
+ /* invert_selection is a function pointer that subclasses must
+ * override to invert selection. */
+
+ void (* invert_selection) (FMDirectoryView *view);
+
+ /* Return an array of locations of selected icons in their view. */
+ GArray * (* get_selected_icon_locations) (FMDirectoryView *view);
+
+ guint (* get_item_count) (FMDirectoryView *view);
+
+ /* bump_zoom_level is a function pointer that subclasses must override
+ * to change the zoom level of an object. */
+ void (* bump_zoom_level) (FMDirectoryView *view,
+ int zoom_increment);
+
+ /* zoom_to_level is a function pointer that subclasses must override
+ * to set the zoom level of an object to the specified level. */
+ void (* zoom_to_level) (FMDirectoryView *view,
+ CajaZoomLevel level);
+
+ CajaZoomLevel (* get_zoom_level) (FMDirectoryView *view);
+
+ /* restore_default_zoom_level is a function pointer that subclasses must override
+ * to restore the zoom level of an object to a default setting. */
+ void (* restore_default_zoom_level) (FMDirectoryView *view);
+
+ /* can_zoom_in is a function pointer that subclasses must override to
+ * return whether the view is at maximum size (furthest-in zoom level) */
+ gboolean (* can_zoom_in) (FMDirectoryView *view);
+
+ /* can_zoom_out is a function pointer that subclasses must override to
+ * return whether the view is at minimum size (furthest-out zoom level) */
+ gboolean (* can_zoom_out) (FMDirectoryView *view);
+
+ /* reveal_selection is a function pointer that subclasses may
+ * override to make sure the selected items are sufficiently
+ * apparent to the user (e.g., scrolled into view). By default,
+ * this does nothing.
+ */
+ void (* reveal_selection) (FMDirectoryView *view);
+
+ /* get_background is a function pointer that subclasses must
+ * override to return the EelBackground for this view.
+ */
+ GtkWidget * (* get_background_widget) (FMDirectoryView *view);
+
+ /* merge_menus is a function pointer that subclasses can override to
+ * add their own menu items to the window's menu bar.
+ * If overridden, subclasses must call parent class's function.
+ */
+ void (* merge_menus) (FMDirectoryView *view);
+ void (* unmerge_menus) (FMDirectoryView *view);
+
+ /* update_menus is a function pointer that subclasses can override to
+ * update the sensitivity or wording of menu items in the menu bar.
+ * It is called (at least) whenever the selection changes. If overridden,
+ * subclasses must call parent class's function.
+ */
+ void (* update_menus) (FMDirectoryView *view);
+
+ /* sort_files is a function pointer that subclasses can override
+ * to provide a sorting order to determine which files should be
+ * presented when only a partial list is provided.
+ */
+ int (* compare_files) (FMDirectoryView *view,
+ CajaFile *a,
+ CajaFile *b);
+
+ /* get_emblem_names_to_exclude is a function pointer that subclasses
+ * may override to specify a set of emblem names that should not
+ * be displayed with each file. By default, all emblems returned by
+ * CajaFile are displayed.
+ */
+ char ** (* get_emblem_names_to_exclude) (FMDirectoryView *view);
+
+ /* file_limit_reached is a function pointer that subclasses may
+ * override to control what happens when a directory is loaded
+ * that has more files than Caja can handle. The default
+ * implmentation puts up a dialog box that is appropriate to
+ * display when the user explicitly tried to visit a location that
+ * they would think of as a normal directory.
+ */
+ void (* file_limit_reached) (FMDirectoryView *view);
+
+ /* supports_properties is a function pointer that subclasses may
+ * override to control whether the "Show Properties" menu item
+ * should be enabled for selected items. The default implementation
+ * returns TRUE.
+ */
+ gboolean (* supports_properties) (FMDirectoryView *view);
+
+ /* supports_zooming is a function pointer that subclasses may
+ * override to control whether or not the zooming control and
+ * menu items should be enabled. The default implementation
+ * returns TRUE.
+ */
+ gboolean (* supports_zooming) (FMDirectoryView *view);
+
+ /* using_manual_layout is a function pointer that subclasses may
+ * override to control whether or not items can be freely positioned
+ * on the user-visible area.
+ * Note that this value is not guaranteed to be constant within the
+ * view's lifecycle. */
+ gboolean (* using_manual_layout) (FMDirectoryView *view);
+
+ /* is_read_only is a function pointer that subclasses may
+ * override to control whether or not the user is allowed to
+ * change the contents of the currently viewed directory. The
+ * default implementation checks the permissions of the
+ * directory.
+ */
+ gboolean (* is_read_only) (FMDirectoryView *view);
+
+ /* is_empty is a function pointer that subclasses must
+ * override to report whether the view contains any items.
+ */
+ gboolean (* is_empty) (FMDirectoryView *view);
+
+ /* supports_creating_files is a function pointer that subclasses may
+ * override to control whether or not new items can be created.
+ * be accepted. The default implementation checks whether the
+ * user has write permissions for the viewed directory, and whether
+ * the viewed directory is in the trash.
+ */
+ gboolean (* supports_creating_files) (FMDirectoryView *view);
+
+ /* accepts_dragged_files is a function pointer that subclasses may
+ * override to control whether or not files can be dropped in this
+ * location. The default implementation returns TRUE.
+ */
+ gboolean (* accepts_dragged_files) (FMDirectoryView *view);
+
+ gboolean (* can_rename_file) (FMDirectoryView *view,
+ CajaFile *file);
+ /* select_all specifies whether the whole filename should be selected
+ * or only its basename (i.e. everything except the extension)
+ * */
+ void (* start_renaming_file) (FMDirectoryView *view,
+ CajaFile *file,
+ gboolean select_all);
+
+ gboolean (* file_still_belongs) (FMDirectoryView *view,
+ CajaFile *file,
+ CajaDirectory *directory);
+
+ /* convert *point from widget's coordinate system to a coordinate
+ * system used for specifying file operation positions, which is view-specific.
+ *
+ * This is used by the the icon view, which converts the screen position to a zoom
+ * level-independent coordinate system.
+ */
+ void (* widget_to_file_operation_position) (FMDirectoryView *view,
+ GdkPoint *position);
+
+ /* Preference change callbacks, overriden by icon and list views.
+ * Icon and list views respond by synchronizing to the new preference
+ * values and forcing an update if appropriate.
+ */
+ void (* text_attribute_names_changed) (FMDirectoryView *view);
+ void (* embedded_text_policy_changed) (FMDirectoryView *view);
+ void (* image_display_policy_changed) (FMDirectoryView *view);
+ void (* click_policy_changed) (FMDirectoryView *view);
+ void (* sort_directories_first_changed) (FMDirectoryView *view);
+
+ void (* emblems_changed) (FMDirectoryView *view);
+
+ void (* set_is_active) (FMDirectoryView *view,
+ gboolean is_active);
+
+ /* Signals used only for keybindings */
+ gboolean (* trash) (FMDirectoryView *view);
+ gboolean (* delete) (FMDirectoryView *view);
+};
+
+/* GObject support */
+GType fm_directory_view_get_type (void);
+
+/* Functions callable from the user interface and elsewhere. */
+CajaWindowInfo *fm_directory_view_get_caja_window (FMDirectoryView *view);
+CajaWindowSlotInfo *fm_directory_view_get_caja_window_slot (FMDirectoryView *view);
+char * fm_directory_view_get_uri (FMDirectoryView *view);
+char * fm_directory_view_get_backing_uri (FMDirectoryView *view);
+gboolean fm_directory_view_can_accept_item (CajaFile *target_item,
+ const char *item_uri,
+ FMDirectoryView *view);
+void fm_directory_view_display_selection_info (FMDirectoryView *view);
+GList * fm_directory_view_get_selection (FMDirectoryView *view);
+GList * fm_directory_view_get_selection_for_file_transfer (FMDirectoryView *view);
+void fm_directory_view_invert_selection (FMDirectoryView *view);
+void fm_directory_view_stop (FMDirectoryView *view);
+guint fm_directory_view_get_item_count (FMDirectoryView *view);
+gboolean fm_directory_view_can_zoom_in (FMDirectoryView *view);
+gboolean fm_directory_view_can_zoom_out (FMDirectoryView *view);
+GtkWidget * fm_directory_view_get_background_widget (FMDirectoryView *view);
+void fm_directory_view_bump_zoom_level (FMDirectoryView *view,
+ int zoom_increment);
+void fm_directory_view_zoom_to_level (FMDirectoryView *view,
+ CajaZoomLevel zoom_level);
+CajaZoomLevel fm_directory_view_get_zoom_level (FMDirectoryView *view);
+void fm_directory_view_restore_default_zoom_level (FMDirectoryView *view);
+void fm_directory_view_reset_to_defaults (FMDirectoryView *view);
+void fm_directory_view_select_all (FMDirectoryView *view);
+void fm_directory_view_set_selection (FMDirectoryView *view,
+ GList *selection);
+GArray * fm_directory_view_get_selected_icon_locations (FMDirectoryView *view);
+void fm_directory_view_reveal_selection (FMDirectoryView *view);
+gboolean fm_directory_view_is_empty (FMDirectoryView *view);
+gboolean fm_directory_view_is_read_only (FMDirectoryView *view);
+gboolean fm_directory_view_supports_creating_files (FMDirectoryView *view);
+gboolean fm_directory_view_accepts_dragged_files (FMDirectoryView *view);
+gboolean fm_directory_view_supports_properties (FMDirectoryView *view);
+gboolean fm_directory_view_supports_zooming (FMDirectoryView *view);
+gboolean fm_directory_view_using_manual_layout (FMDirectoryView *view);
+void fm_directory_view_move_copy_items (const GList *item_uris,
+ GArray *relative_item_points,
+ const char *target_uri,
+ int copy_action,
+ int x,
+ int y,
+ FMDirectoryView *view);
+GdkAtom fm_directory_view_get_copied_files_atom (FMDirectoryView *view);
+gboolean fm_directory_view_get_active (FMDirectoryView *view);
+
+/* Wrappers for signal emitters. These are normally called
+ * only by FMDirectoryView itself. They have corresponding signals
+ * that observers might want to connect with.
+ */
+void fm_directory_view_clear (FMDirectoryView *view);
+void fm_directory_view_begin_loading (FMDirectoryView *view);
+void fm_directory_view_end_loading (FMDirectoryView *view,
+ gboolean all_files_seen);
+
+gboolean fm_directory_view_get_loading (FMDirectoryView *view);
+
+/* Hooks for subclasses to call. These are normally called only by
+ * FMDirectoryView and its subclasses
+ */
+void fm_directory_view_activate_files (FMDirectoryView *view,
+ GList *files,
+ CajaWindowOpenMode mode,
+ CajaWindowOpenFlags flags,
+ gboolean confirm_multiple);
+void fm_directory_view_activate_file (FMDirectoryView *view,
+ CajaFile *file,
+ CajaWindowOpenMode mode,
+ CajaWindowOpenFlags flags);
+void fm_directory_view_start_batching_selection_changes (FMDirectoryView *view);
+void fm_directory_view_stop_batching_selection_changes (FMDirectoryView *view);
+void fm_directory_view_queue_file_change (FMDirectoryView *view,
+ CajaFile *file);
+void fm_directory_view_notify_selection_changed (FMDirectoryView *view);
+GtkUIManager * fm_directory_view_get_ui_manager (FMDirectoryView *view);
+char ** fm_directory_view_get_emblem_names_to_exclude (FMDirectoryView *view);
+CajaDirectory *fm_directory_view_get_model (FMDirectoryView *view);
+GtkWindow *fm_directory_view_get_containing_window (FMDirectoryView *view);
+CajaFile *fm_directory_view_get_directory_as_file (FMDirectoryView *view);
+EelBackground * fm_directory_view_get_background (FMDirectoryView *view);
+gboolean fm_directory_view_get_allow_moves (FMDirectoryView *view);
+void fm_directory_view_pop_up_background_context_menu (FMDirectoryView *view,
+ GdkEventButton *event);
+void fm_directory_view_pop_up_selection_context_menu (FMDirectoryView *view,
+ GdkEventButton *event);
+void fm_directory_view_pop_up_location_context_menu (FMDirectoryView *view,
+ GdkEventButton *event,
+ const char *location);
+void fm_directory_view_send_selection_change (FMDirectoryView *view);
+gboolean fm_directory_view_should_show_file (FMDirectoryView *view,
+ CajaFile *file);
+gboolean fm_directory_view_should_sort_directories_first (FMDirectoryView *view);
+void fm_directory_view_update_menus (FMDirectoryView *view);
+void fm_directory_view_new_folder (FMDirectoryView *view);
+void fm_directory_view_new_file (FMDirectoryView *view,
+ const char *parent_uri,
+ CajaFile *source);
+void fm_directory_view_ignore_hidden_file_preferences (FMDirectoryView *view);
+void fm_directory_view_set_show_foreign (FMDirectoryView *view,
+ gboolean show_foreign);
+void fm_directory_view_init_view_iface (CajaViewIface *iface);
+gboolean fm_directory_view_handle_scroll_event (FMDirectoryView *view,
+ GdkEventScroll *event);
+void fm_directory_view_handle_netscape_url_drop (FMDirectoryView *view,
+ const char *encoded_url,
+ const char *target_uri,
+ GdkDragAction action,
+ int x,
+ int y);
+void fm_directory_view_handle_uri_list_drop (FMDirectoryView *view,
+ const char *item_uris,
+ const char *target_uri,
+ GdkDragAction action,
+ int x,
+ int y);
+void fm_directory_view_handle_text_drop (FMDirectoryView *view,
+ const char *text,
+ const char *target_uri,
+ GdkDragAction action,
+ int x,
+ int y);
+void fm_directory_view_handle_raw_drop (FMDirectoryView *view,
+ const char *raw_data,
+ int length,
+ const char *target_uri,
+ const char *direct_save_uri,
+ GdkDragAction action,
+ int x,
+ int y);
+void fm_directory_view_freeze_updates (FMDirectoryView *view);
+void fm_directory_view_unfreeze_updates (FMDirectoryView *view);
+void fm_directory_view_add_subdirectory (FMDirectoryView *view,
+ CajaDirectory*directory);
+void fm_directory_view_remove_subdirectory (FMDirectoryView *view,
+ CajaDirectory*directory);
+
+gboolean fm_directory_view_is_editable (FMDirectoryView *view);
+void fm_directory_view_set_initiated_unmount (FMDirectoryView *view,
+ gboolean inititated_unmount);
+
+/* operations affecting two directory views */
+void fm_directory_view_move_copy_items_between_views (FMDirectoryView *source, FMDirectoryView *target, gboolean copy);
+
+#endif /* FM_DIRECTORY_VIEW_H */
diff --git a/src/file-manager/fm-ditem-page.c b/src/file-manager/fm-ditem-page.c
new file mode 100644
index 00000000..af88b753
--- /dev/null
+++ b/src/file-manager/fm-ditem-page.c
@@ -0,0 +1,563 @@
+/*
+ * fm-ditem-page.c: Desktop item editing support
+ *
+ * Copyright (C) 2004 James Willcox
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this library; if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Authors: James Willcox <[email protected]>
+ *
+ */
+
+#include <config.h>
+#include "fm-ditem-page.h"
+
+#include <string.h>
+
+#include <eel/eel-glib-extensions.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <libcaja-extension/caja-extension-types.h>
+#include <libcaja-extension/caja-file-info.h>
+#include <libcaja-private/caja-file.h>
+#include <libcaja-private/caja-file-attributes.h>
+
+#define MAIN_GROUP "Desktop Entry"
+
+typedef struct ItemEntry
+{
+ const char *field;
+ const char *description;
+ char *current_value;
+ gboolean localized;
+ gboolean filename;
+} ItemEntry;
+
+enum
+{
+ TARGET_URI_LIST
+};
+
+static const GtkTargetEntry target_table[] =
+{
+ { "text/uri-list", 0, TARGET_URI_LIST }
+};
+
+static gboolean
+_g_key_file_load_from_gfile (GKeyFile *key_file,
+ GFile *file,
+ GKeyFileFlags flags,
+ GError **error)
+{
+ char *data;
+ gsize len;
+ gboolean res;
+
+ if (!g_file_load_contents (file, NULL, &data, &len, NULL, error))
+ {
+ return FALSE;
+ }
+
+ res = g_key_file_load_from_data (key_file, data, len, flags, error);
+
+ g_free (data);
+
+ return res;
+}
+
+static gboolean
+_g_key_file_save_to_uri (GKeyFile *key_file,
+ const char *uri,
+ GError **error)
+{
+ GFile *file;
+ char *data;
+ gsize len;
+
+ data = g_key_file_to_data (key_file, &len, error);
+ if (data == NULL)
+ {
+ return FALSE;
+ }
+ file = g_file_new_for_uri (uri);
+ if (!g_file_replace_contents (file,
+ data, len,
+ NULL, FALSE,
+ G_FILE_CREATE_NONE,
+ NULL, NULL, error))
+ {
+ g_object_unref (file);
+ g_free (data);
+ return FALSE;
+ }
+ g_object_unref (file);
+ g_free (data);
+ return TRUE;
+}
+
+static GKeyFile *
+_g_key_file_new_from_file (GFile *file,
+ GKeyFileFlags flags,
+ GError **error)
+{
+ GKeyFile *key_file;
+
+ key_file = g_key_file_new ();
+ if (!_g_key_file_load_from_gfile (key_file, file, flags, error))
+ {
+ g_key_file_free (key_file);
+ key_file = NULL;
+ }
+ return key_file;
+}
+
+static GKeyFile *
+_g_key_file_new_from_uri (const char *uri,
+ GKeyFileFlags flags,
+ GError **error)
+{
+ GKeyFile *key_file;
+ GFile *file;
+
+ file = g_file_new_for_uri (uri);
+ key_file = _g_key_file_new_from_file (file, flags, error);
+ g_object_unref (file);
+ return key_file;
+}
+
+static ItemEntry *
+item_entry_new (const char *field,
+ const char *description,
+ gboolean localized,
+ gboolean filename)
+{
+ ItemEntry *entry;
+
+ entry = g_new0 (ItemEntry, 1);
+ entry->field = field;
+ entry->description = description;
+ entry->localized = localized;
+ entry->filename = filename;
+
+ return entry;
+}
+
+static void
+item_entry_free (ItemEntry *entry)
+{
+ g_free (entry->current_value);
+ g_free (entry);
+}
+
+static void
+fm_ditem_page_url_drag_data_received (GtkWidget *widget, GdkDragContext *context,
+ int x, int y,
+ GtkSelectionData *selection_data,
+ guint info, guint time,
+ GtkEntry *entry)
+{
+ char **uris;
+ gboolean exactly_one;
+ char *path;
+
+ uris = g_strsplit (gtk_selection_data_get_data (selection_data), "\r\n", 0);
+ exactly_one = uris[0] != NULL && (uris[1] == NULL || uris[1][0] == '\0');
+
+ if (!exactly_one)
+ {
+ g_strfreev (uris);
+ return;
+ }
+
+ path = g_filename_from_uri (uris[0], NULL, NULL);
+ if (path != NULL)
+ {
+ gtk_entry_set_text (entry, path);
+ g_free (path);
+ }
+ else
+ {
+ gtk_entry_set_text (entry, uris[0]);
+ }
+
+ g_strfreev (uris);
+}
+
+static void
+fm_ditem_page_exec_drag_data_received (GtkWidget *widget, GdkDragContext *context,
+ int x, int y,
+ GtkSelectionData *selection_data,
+ guint info, guint time,
+ GtkEntry *entry)
+{
+ char **uris;
+ gboolean exactly_one;
+ CajaFile *file;
+ GKeyFile *key_file;
+ char *uri, *type, *exec;
+
+ uris = g_strsplit (gtk_selection_data_get_data (selection_data), "\r\n", 0);
+ exactly_one = uris[0] != NULL && (uris[1] == NULL || uris[1][0] == '\0');
+
+ if (!exactly_one)
+ {
+ g_strfreev (uris);
+ return;
+ }
+
+ file = caja_file_get_by_uri (uris[0]);
+
+ g_return_if_fail (file != NULL);
+
+ uri = caja_file_get_uri (file);
+ if (caja_file_is_mime_type (file, "application/x-desktop"))
+ {
+ key_file = _g_key_file_new_from_uri (uri, G_KEY_FILE_NONE, NULL);
+ if (key_file != NULL)
+ {
+ type = g_key_file_get_string (key_file, MAIN_GROUP, "Type", NULL);
+ if (type != NULL && strcmp (type, "Application") == 0)
+ {
+ exec = g_key_file_get_string (key_file, MAIN_GROUP, "Exec", NULL);
+ if (exec != NULL)
+ {
+ g_free (uri);
+ uri = exec;
+ }
+ }
+ g_free (type);
+ g_key_file_free (key_file);
+ }
+ }
+ gtk_entry_set_text (entry,
+ uri?uri:"");
+ gtk_widget_grab_focus (GTK_WIDGET (entry));
+
+ g_free (uri);
+
+ caja_file_unref (file);
+
+ g_strfreev (uris);
+}
+
+static void
+save_entry (GtkEntry *entry, GKeyFile *key_file, const char *uri)
+{
+ GError *error;
+ ItemEntry *item_entry;
+ const char *val;
+ gchar **languages;
+
+ item_entry = g_object_get_data (G_OBJECT (entry), "item_entry");
+ val = gtk_entry_get_text (entry);
+
+ if (strcmp (val, item_entry->current_value) == 0)
+ {
+ return; /* No actual change, don't update file */
+ }
+
+ g_free (item_entry->current_value);
+ item_entry->current_value = g_strdup (val);
+
+ if (item_entry->localized)
+ {
+ languages = (gchar **) g_get_language_names ();
+ g_key_file_set_locale_string (key_file, MAIN_GROUP, item_entry->field, languages[0], val);
+ }
+ else
+ {
+ g_key_file_set_string (key_file, MAIN_GROUP, item_entry->field, val);
+ }
+
+ error = NULL;
+
+ if (!_g_key_file_save_to_uri (key_file, uri, &error))
+ {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ }
+}
+
+static void
+entry_activate_cb (GtkWidget *entry,
+ GtkWidget *container)
+{
+ const char *uri;
+ GKeyFile *key_file;
+
+ uri = g_object_get_data (G_OBJECT (container), "uri");
+ key_file = g_object_get_data (G_OBJECT (container), "keyfile");
+ save_entry (GTK_ENTRY (entry), key_file, uri);
+}
+
+static gboolean
+entry_focus_out_cb (GtkWidget *entry,
+ GdkEventFocus *event,
+ GtkWidget *container)
+{
+ const char *uri;
+ GKeyFile *key_file;
+
+ uri = g_object_get_data (G_OBJECT (container), "uri");
+ key_file = g_object_get_data (G_OBJECT (container), "keyfile");
+ save_entry (GTK_ENTRY (entry), key_file, uri);
+ return FALSE;
+}
+
+static GtkWidget *
+build_table (GtkWidget *container,
+ GKeyFile *key_file,
+ GtkSizeGroup *label_size_group,
+ GList *entries)
+{
+ GtkWidget *table;
+ GtkWidget *label;
+ GtkWidget *entry;
+ GList *l;
+ char *val;
+ int i;
+
+ table = gtk_table_new (g_list_length (entries) + 1, 2, FALSE);
+ gtk_table_set_row_spacings (GTK_TABLE (table), 6);
+ gtk_table_set_col_spacings (GTK_TABLE (table), 12);
+ i = 0;
+
+ for (l = entries; l; l = l->next)
+ {
+ ItemEntry *item_entry = (ItemEntry *)l->data;
+ char *label_text;
+
+ label_text = g_strdup_printf ("%s:", item_entry->description);
+ label = gtk_label_new (label_text);
+ gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
+ g_free (label_text);
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_size_group_add_widget (label_size_group, label);
+
+ entry = gtk_entry_new ();
+
+ if (item_entry->localized)
+ {
+ val = g_key_file_get_locale_string (key_file,
+ MAIN_GROUP,
+ item_entry->field,
+ NULL, NULL);
+ }
+ else
+ {
+ val = g_key_file_get_string (key_file,
+ MAIN_GROUP,
+ item_entry->field,
+ NULL);
+ }
+
+ item_entry->current_value = g_strdup (val?val:"");
+ gtk_entry_set_text (GTK_ENTRY (entry), item_entry->current_value);
+ g_free (val);
+
+ gtk_table_attach (GTK_TABLE (table), label,
+ 0, 1, i, i+1, GTK_FILL, GTK_FILL,
+ 0, 0);
+ gtk_table_attach (GTK_TABLE (table), entry,
+ 1, 2, i, i+1, GTK_EXPAND|GTK_FILL, GTK_EXPAND|GTK_FILL,
+ 0, 0);
+ g_signal_connect (entry, "activate",
+ G_CALLBACK (entry_activate_cb),
+ container);
+ g_signal_connect (entry, "focus_out_event",
+ G_CALLBACK (entry_focus_out_cb),
+ container);
+
+ g_object_set_data_full (G_OBJECT (entry), "item_entry", item_entry,
+ (GDestroyNotify)item_entry_free);
+
+ if (item_entry->filename)
+ {
+ gtk_drag_dest_set (GTK_WIDGET (entry),
+ GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP,
+ target_table, G_N_ELEMENTS (target_table),
+ GDK_ACTION_COPY | GDK_ACTION_MOVE);
+
+ g_signal_connect (entry, "drag_data_received",
+ G_CALLBACK (fm_ditem_page_url_drag_data_received),
+ entry);
+ }
+ else if (strcmp (item_entry->field, "Exec") == 0)
+ {
+ gtk_drag_dest_set (GTK_WIDGET (entry),
+ GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP,
+ target_table, G_N_ELEMENTS (target_table),
+ GDK_ACTION_COPY | GDK_ACTION_MOVE);
+
+ g_signal_connect (entry, "drag_data_received",
+ G_CALLBACK (fm_ditem_page_exec_drag_data_received),
+ entry);
+ }
+
+ i++;
+ }
+
+ /* append dummy row */
+ label = gtk_label_new ("");
+ gtk_table_attach (GTK_TABLE (table), label,
+ 0, 1, i, i+1, GTK_FILL, GTK_FILL,
+ 0, 0);
+ gtk_size_group_add_widget (label_size_group, label);
+
+
+ gtk_widget_show_all (table);
+ return table;
+}
+
+static void
+create_page (GKeyFile *key_file, GtkWidget *box)
+{
+ GtkWidget *table;
+ GList *entries;
+ GtkSizeGroup *label_size_group;
+ char *type;
+
+ entries = NULL;
+
+ type = g_key_file_get_string (key_file, MAIN_GROUP, "Type", NULL);
+
+ if (g_strcmp0 (type, "Link") == 0)
+ {
+ entries = g_list_prepend (entries,
+ item_entry_new ("Comment",
+ _("Comment"), TRUE, FALSE));
+ entries = g_list_prepend (entries,
+ item_entry_new ("URL",
+ _("URL"), FALSE, TRUE));
+ entries = g_list_prepend (entries,
+ item_entry_new ("GenericName",
+ _("Description"), TRUE, FALSE));
+ }
+ else if (g_strcmp0 (type, "Application") == 0)
+ {
+ entries = g_list_prepend (entries,
+ item_entry_new ("Comment",
+ _("Comment"), TRUE, FALSE));
+ entries = g_list_prepend (entries,
+ item_entry_new ("Exec",
+ _("Command"), FALSE, FALSE));
+ entries = g_list_prepend (entries,
+ item_entry_new ("GenericName",
+ _("Description"), TRUE, FALSE));
+ }
+ else
+ {
+ /* we only handle launchers and links */
+
+ /* ensure that we build an empty table with a dummy row at the end */
+ goto build_table;
+ }
+ g_free (type);
+
+build_table:
+ label_size_group = g_object_get_data (G_OBJECT (box), "label-size-group");
+
+ table = build_table (box, key_file, label_size_group, entries);
+ g_list_free (entries);
+
+ gtk_box_pack_start (GTK_BOX (box), table, FALSE, TRUE, 0);
+ gtk_widget_show_all (GTK_WIDGET (box));
+}
+
+
+static void
+ditem_read_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GKeyFile *key_file;
+ GtkWidget *box;
+ gsize file_size;
+ char *file_contents;
+
+ box = GTK_WIDGET (user_data);
+
+ if (g_file_load_contents_finish (G_FILE (source_object),
+ res,
+ &file_contents, &file_size,
+ NULL, NULL))
+ {
+ key_file = g_key_file_new ();
+ g_object_set_data_full (G_OBJECT (box), "keyfile", key_file, (GDestroyNotify)g_key_file_free);
+ if (g_key_file_load_from_data (key_file, file_contents, file_size, 0, NULL))
+ {
+ create_page (key_file, box);
+ }
+ g_free (file_contents);
+
+ }
+ g_object_unref (box);
+}
+
+static void
+fm_ditem_page_create_begin (const char *uri,
+ GtkWidget *box)
+{
+ GFile *location;
+
+ location = g_file_new_for_uri (uri);
+ g_object_set_data_full (G_OBJECT (box), "uri", g_strdup (uri), g_free);
+ g_file_load_contents_async (location, NULL, ditem_read_cb, g_object_ref (box));
+ g_object_unref (location);
+}
+
+GtkWidget *
+fm_ditem_page_make_box (GtkSizeGroup *label_size_group,
+ GList *files)
+{
+ CajaFileInfo *info;
+ char *uri;
+ GtkWidget *box;
+
+ g_assert (fm_ditem_page_should_show (files));
+
+ box = gtk_vbox_new (FALSE, 6);
+ g_object_set_data_full (G_OBJECT (box), "label-size-group",
+ label_size_group, (GDestroyNotify) g_object_unref);
+
+ info = CAJA_FILE_INFO (files->data);
+
+ uri = caja_file_info_get_uri (info);
+ fm_ditem_page_create_begin (uri, box);
+ g_free (uri);
+
+ return box;
+}
+
+gboolean
+fm_ditem_page_should_show (GList *files)
+{
+ CajaFileInfo *info;
+
+ if (!files || files->next)
+ {
+ return FALSE;
+ }
+
+ info = CAJA_FILE_INFO (files->data);
+
+ if (!caja_file_info_is_mime_type (info, "application/x-desktop"))
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
diff --git a/src/file-manager/fm-ditem-page.h b/src/file-manager/fm-ditem-page.h
new file mode 100644
index 00000000..bf55d002
--- /dev/null
+++ b/src/file-manager/fm-ditem-page.h
@@ -0,0 +1,51 @@
+/*
+ * fm-ditem-page.h - A property page for desktop items
+ *
+ * Copyright (C) 2004 James Willcox
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this library; if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Authors: James Willcox <[email protected]>
+ *
+ */
+
+#ifndef FM_DITEM_PAGE_H
+#define FM_DITEM_PAGE_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+ /* This is a mis-nomer. Launcher editables initially were displayed on separate
+ * a property notebook page, which implemented the CajaPropertyPageProvider
+ * interface.
+ *
+ * Nowadays, they are displayed on the "Basic" page, so just the setup
+ * routines are left.
+ */
+
+ GtkWidget *fm_ditem_page_make_box (GtkSizeGroup *label_size_group,
+ GList *files);
+ gboolean fm_ditem_page_should_show (GList *files);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/file-manager/fm-empty-view.c b/src/file-manager/fm-empty-view.c
new file mode 100644
index 00000000..d85c702c
--- /dev/null
+++ b/src/file-manager/fm-empty-view.c
@@ -0,0 +1,412 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* fm-empty-view.c - implementation of empty view of directory.
+
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+ The Mate Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Mate Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Christian Neumair <[email protected]>
+*/
+
+#include <config.h>
+#include "fm-empty-view.h"
+
+#include <string.h>
+#include <libcaja-private/caja-file-utilities.h>
+#include <libcaja-private/caja-view.h>
+#include <libcaja-private/caja-view-factory.h>
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-gtk-macros.h>
+#include <eel/eel-vfs-extensions.h>
+
+struct FMEmptyViewDetails
+{
+ int number_of_files;
+};
+
+static GList *fm_empty_view_get_selection (FMDirectoryView *view);
+static GList *fm_empty_view_get_selection_for_file_transfer (FMDirectoryView *view);
+static void fm_empty_view_scroll_to_file (CajaView *view,
+ const char *uri);
+static void fm_empty_view_iface_init (CajaViewIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (FMEmptyView, fm_empty_view, FM_TYPE_DIRECTORY_VIEW,
+ G_IMPLEMENT_INTERFACE (CAJA_TYPE_VIEW,
+ fm_empty_view_iface_init));
+
+/* for EEL_CALL_PARENT */
+#define parent_class fm_empty_view_parent_class
+
+static void
+fm_empty_view_add_file (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory)
+{
+ static GTimer *timer = NULL;
+ static gdouble cumu = 0, elaps;
+ FM_EMPTY_VIEW (view)->details->number_of_files++;
+ GdkPixbuf *icon;
+
+ if (!timer) timer = g_timer_new ();
+
+ g_timer_start (timer);
+ icon = caja_file_get_icon_pixbuf (file, caja_get_icon_size_for_zoom_level (CAJA_ZOOM_LEVEL_STANDARD), TRUE, 0);
+
+ elaps = g_timer_elapsed (timer, NULL);
+ g_timer_stop (timer);
+
+ g_object_unref (icon);
+
+ cumu += elaps;
+ g_message ("entire loading: %.3f, cumulative %.3f", elaps, cumu);
+}
+
+
+static void
+fm_empty_view_begin_loading (FMDirectoryView *view)
+{
+}
+
+static void
+fm_empty_view_clear (FMDirectoryView *view)
+{
+}
+
+
+static void
+fm_empty_view_file_changed (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory)
+{
+}
+
+static GtkWidget *
+fm_empty_view_get_background_widget (FMDirectoryView *view)
+{
+ return GTK_WIDGET (view);
+}
+
+static GList *
+fm_empty_view_get_selection (FMDirectoryView *view)
+{
+ return NULL;
+}
+
+
+static GList *
+fm_empty_view_get_selection_for_file_transfer (FMDirectoryView *view)
+{
+ return NULL;
+}
+
+static guint
+fm_empty_view_get_item_count (FMDirectoryView *view)
+{
+ return FM_EMPTY_VIEW (view)->details->number_of_files;
+}
+
+static gboolean
+fm_empty_view_is_empty (FMDirectoryView *view)
+{
+ return FM_EMPTY_VIEW (view)->details->number_of_files == 0;
+}
+
+static void
+fm_empty_view_end_file_changes (FMDirectoryView *view)
+{
+}
+
+static void
+fm_empty_view_remove_file (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory)
+{
+ FM_EMPTY_VIEW (view)->details->number_of_files--;
+ g_assert (FM_EMPTY_VIEW (view)->details->number_of_files >= 0);
+}
+
+static void
+fm_empty_view_set_selection (FMDirectoryView *view, GList *selection)
+{
+ fm_directory_view_notify_selection_changed (view);
+}
+
+static void
+fm_empty_view_select_all (FMDirectoryView *view)
+{
+}
+
+static void
+fm_empty_view_reveal_selection (FMDirectoryView *view)
+{
+}
+
+static void
+fm_empty_view_merge_menus (FMDirectoryView *view)
+{
+ EEL_CALL_PARENT (FM_DIRECTORY_VIEW_CLASS, merge_menus, (view));
+}
+
+static void
+fm_empty_view_update_menus (FMDirectoryView *view)
+{
+ EEL_CALL_PARENT (FM_DIRECTORY_VIEW_CLASS, update_menus, (view));
+}
+
+/* Reset sort criteria and zoom level to match defaults */
+static void
+fm_empty_view_reset_to_defaults (FMDirectoryView *view)
+{
+}
+
+static void
+fm_empty_view_bump_zoom_level (FMDirectoryView *view, int zoom_increment)
+{
+}
+
+static CajaZoomLevel
+fm_empty_view_get_zoom_level (FMDirectoryView *view)
+{
+ return CAJA_ZOOM_LEVEL_STANDARD;
+}
+
+static void
+fm_empty_view_zoom_to_level (FMDirectoryView *view,
+ CajaZoomLevel zoom_level)
+{
+}
+
+static void
+fm_empty_view_restore_default_zoom_level (FMDirectoryView *view)
+{
+}
+
+static gboolean
+fm_empty_view_can_zoom_in (FMDirectoryView *view)
+{
+ return FALSE;
+}
+
+static gboolean
+fm_empty_view_can_zoom_out (FMDirectoryView *view)
+{
+ return FALSE;
+}
+
+static void
+fm_empty_view_start_renaming_file (FMDirectoryView *view,
+ CajaFile *file,
+ gboolean select_all)
+{
+}
+
+static void
+fm_empty_view_click_policy_changed (FMDirectoryView *directory_view)
+{
+}
+
+
+static int
+fm_empty_view_compare_files (FMDirectoryView *view, CajaFile *file1, CajaFile *file2)
+{
+ if (file1 < file2)
+ {
+ return -1;
+ }
+
+ if (file1 > file2)
+ {
+ return +1;
+ }
+
+ return 0;
+}
+
+static gboolean
+fm_empty_view_using_manual_layout (FMDirectoryView *view)
+{
+ return FALSE;
+}
+
+static void
+fm_empty_view_end_loading (FMDirectoryView *view,
+ gboolean all_files_seen)
+{
+}
+
+static void
+fm_empty_view_finalize (GObject *object)
+{
+ FMEmptyView *empty_view;
+
+ empty_view = FM_EMPTY_VIEW (object);
+ g_free (empty_view->details);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+fm_empty_view_emblems_changed (FMDirectoryView *directory_view)
+{
+}
+
+static char *
+fm_empty_view_get_first_visible_file (CajaView *view)
+{
+ return NULL;
+}
+
+static void
+fm_empty_view_scroll_to_file (CajaView *view,
+ const char *uri)
+{
+}
+
+static void
+fm_empty_view_grab_focus (CajaView *view)
+{
+ gtk_widget_grab_focus (GTK_WIDGET (view));
+}
+
+static void
+fm_empty_view_sort_directories_first_changed (FMDirectoryView *view)
+{
+}
+
+static void
+fm_empty_view_class_init (FMEmptyViewClass *class)
+{
+ FMDirectoryViewClass *fm_directory_view_class;
+
+ fm_directory_view_class = FM_DIRECTORY_VIEW_CLASS (class);
+
+ G_OBJECT_CLASS (class)->finalize = fm_empty_view_finalize;
+
+ fm_directory_view_class->add_file = fm_empty_view_add_file;
+ fm_directory_view_class->begin_loading = fm_empty_view_begin_loading;
+ fm_directory_view_class->bump_zoom_level = fm_empty_view_bump_zoom_level;
+ fm_directory_view_class->can_zoom_in = fm_empty_view_can_zoom_in;
+ fm_directory_view_class->can_zoom_out = fm_empty_view_can_zoom_out;
+ fm_directory_view_class->click_policy_changed = fm_empty_view_click_policy_changed;
+ fm_directory_view_class->clear = fm_empty_view_clear;
+ fm_directory_view_class->file_changed = fm_empty_view_file_changed;
+ fm_directory_view_class->get_background_widget = fm_empty_view_get_background_widget;
+ fm_directory_view_class->get_selection = fm_empty_view_get_selection;
+ fm_directory_view_class->get_selection_for_file_transfer = fm_empty_view_get_selection_for_file_transfer;
+ fm_directory_view_class->get_item_count = fm_empty_view_get_item_count;
+ fm_directory_view_class->is_empty = fm_empty_view_is_empty;
+ fm_directory_view_class->remove_file = fm_empty_view_remove_file;
+ fm_directory_view_class->merge_menus = fm_empty_view_merge_menus;
+ fm_directory_view_class->update_menus = fm_empty_view_update_menus;
+ fm_directory_view_class->reset_to_defaults = fm_empty_view_reset_to_defaults;
+ fm_directory_view_class->restore_default_zoom_level = fm_empty_view_restore_default_zoom_level;
+ fm_directory_view_class->reveal_selection = fm_empty_view_reveal_selection;
+ fm_directory_view_class->select_all = fm_empty_view_select_all;
+ fm_directory_view_class->set_selection = fm_empty_view_set_selection;
+ fm_directory_view_class->compare_files = fm_empty_view_compare_files;
+ fm_directory_view_class->sort_directories_first_changed = fm_empty_view_sort_directories_first_changed;
+ fm_directory_view_class->start_renaming_file = fm_empty_view_start_renaming_file;
+ fm_directory_view_class->get_zoom_level = fm_empty_view_get_zoom_level;
+ fm_directory_view_class->zoom_to_level = fm_empty_view_zoom_to_level;
+ fm_directory_view_class->emblems_changed = fm_empty_view_emblems_changed;
+ fm_directory_view_class->end_file_changes = fm_empty_view_end_file_changes;
+ fm_directory_view_class->using_manual_layout = fm_empty_view_using_manual_layout;
+ fm_directory_view_class->end_loading = fm_empty_view_end_loading;
+}
+
+static const char *
+fm_empty_view_get_id (CajaView *view)
+{
+ return FM_EMPTY_VIEW_ID;
+}
+
+
+static void
+fm_empty_view_iface_init (CajaViewIface *iface)
+{
+ fm_directory_view_init_view_iface (iface);
+
+ iface->get_view_id = fm_empty_view_get_id;
+ iface->get_first_visible_file = fm_empty_view_get_first_visible_file;
+ iface->scroll_to_file = fm_empty_view_scroll_to_file;
+ iface->get_title = NULL;
+ iface->grab_focus = fm_empty_view_grab_focus;
+}
+
+
+static void
+fm_empty_view_init (FMEmptyView *empty_view)
+{
+ empty_view->details = g_new0 (FMEmptyViewDetails, 1);
+}
+
+static CajaView *
+fm_empty_view_create (CajaWindowSlotInfo *slot)
+{
+ FMEmptyView *view;
+
+ g_assert (CAJA_IS_WINDOW_SLOT_INFO (slot));
+
+ view = g_object_new (FM_TYPE_EMPTY_VIEW,
+ "window-slot", slot,
+ NULL);
+
+ return CAJA_VIEW (view);
+}
+
+static gboolean
+fm_empty_view_supports_uri (const char *uri,
+ GFileType file_type,
+ const char *mime_type)
+{
+ if (file_type == G_FILE_TYPE_DIRECTORY)
+ {
+ return TRUE;
+ }
+ if (strcmp (mime_type, CAJA_SAVED_SEARCH_MIMETYPE) == 0)
+ {
+ return TRUE;
+ }
+ if (g_str_has_prefix (uri, "trash:"))
+ {
+ return TRUE;
+ }
+ if (g_str_has_prefix (uri, EEL_SEARCH_URI))
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static CajaViewInfo fm_empty_view =
+{
+ FM_EMPTY_VIEW_ID,
+ "Empty",
+ "Empty View",
+ "_Empty View",
+ "The empty view encountered an error.",
+ "Display this location with the empty view.",
+ fm_empty_view_create,
+ fm_empty_view_supports_uri
+};
+
+void
+fm_empty_view_register (void)
+{
+ fm_empty_view.id = fm_empty_view.id;
+ fm_empty_view.view_combo_label = fm_empty_view.view_combo_label;
+ fm_empty_view.view_menu_label_with_mnemonic = fm_empty_view.view_menu_label_with_mnemonic;
+ fm_empty_view.error_label = fm_empty_view.error_label;
+ fm_empty_view.display_location_label = fm_empty_view.display_location_label;
+
+ caja_view_factory_register (&fm_empty_view);
+}
diff --git a/src/file-manager/fm-empty-view.h b/src/file-manager/fm-empty-view.h
new file mode 100644
index 00000000..795dd27a
--- /dev/null
+++ b/src/file-manager/fm-empty-view.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* fm-empty-view.h - interface for empty view of directory.
+
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+ The Mate Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Mate Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Christian Neumair <[email protected]>
+*/
+
+#ifndef FM_EMPTY_VIEW_H
+#define FM_EMPTY_VIEW_H
+
+#include "fm-directory-view.h"
+
+#define FM_TYPE_EMPTY_VIEW fm_empty_view_get_type()
+#define FM_EMPTY_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_EMPTY_VIEW, FMEmptyView))
+#define FM_EMPTY_VIEW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_EMPTY_VIEW, FMEmptyViewClass))
+#define FM_IS_EMPTY_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_EMPTY_VIEW))
+#define FM_IS_EMPTY_VIEW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_EMPTY_VIEW))
+#define FM_EMPTY_VIEW_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_EMPTY_VIEW, FMEmptyViewClass))
+
+#define FM_EMPTY_VIEW_ID "OAFIID:Caja_File_Manager_Empty_View"
+
+typedef struct FMEmptyViewDetails FMEmptyViewDetails;
+
+typedef struct
+{
+ FMDirectoryView parent_instance;
+ FMEmptyViewDetails *details;
+} FMEmptyView;
+
+typedef struct
+{
+ FMDirectoryViewClass parent_class;
+} FMEmptyViewClass;
+
+GType fm_empty_view_get_type (void);
+void fm_empty_view_register (void);
+
+#endif /* FM_EMPTY_VIEW_H */
diff --git a/src/file-manager/fm-error-reporting.c b/src/file-manager/fm-error-reporting.c
new file mode 100644
index 00000000..f301e9a9
--- /dev/null
+++ b/src/file-manager/fm-error-reporting.c
@@ -0,0 +1,380 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* fm-error-reporting.h - implementation of file manager functions that report
+ errors to the user.
+
+ Copyright (C) 2000 Eazel, Inc.
+
+ The Mate Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Mate Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: John Sullivan <[email protected]>
+*/
+
+#include <config.h>
+#include "fm-error-reporting.h"
+
+#include <string.h>
+#include <glib/gi18n.h>
+#include <libcaja-private/caja-debug-log.h>
+#include <libcaja-private/caja-file.h>
+#include <eel/eel-string.h>
+#include <eel/eel-stock-dialogs.h>
+
+#define NEW_NAME_TAG "Caja: new name"
+#define MAXIMUM_DISPLAYED_FILE_NAME_LENGTH 50
+
+static void finish_rename (CajaFile *file, gboolean stop_timer, GError *error);
+
+void
+fm_report_error_loading_directory (CajaFile *file,
+ GError *error,
+ GtkWindow *parent_window)
+{
+ char *file_name;
+ char *message;
+
+ if (error == NULL ||
+ error->message == NULL)
+ {
+ return;
+ }
+
+ if (error->domain == G_IO_ERROR &&
+ error->code == G_IO_ERROR_NOT_MOUNTED)
+ {
+ /* This case is retried automatically */
+ return;
+ }
+
+ file_name = caja_file_get_display_name (file);
+
+ if (error->domain == G_IO_ERROR)
+ {
+ switch (error->code)
+ {
+ case G_IO_ERROR_PERMISSION_DENIED:
+ message = g_strdup_printf (_("You do not have the permissions necessary to view the contents of \"%s\"."),
+ file_name);
+ break;
+ case G_IO_ERROR_NOT_FOUND:
+ message = g_strdup_printf (_("\"%s\" could not be found. Perhaps it has recently been deleted."),
+ file_name);
+ break;
+ default:
+ message = g_strdup_printf (_("Sorry, could not display all the contents of \"%s\": %s"), file_name,
+ error->message);
+ }
+ }
+ else
+ {
+ message = g_strdup (error->message);
+ }
+
+ eel_show_error_dialog (_("The folder contents could not be displayed."), message, parent_window);
+
+ g_free (file_name);
+ g_free (message);
+}
+
+void
+fm_report_error_renaming_file (CajaFile *file,
+ const char *new_name,
+ GError *error,
+ GtkWindow *parent_window)
+{
+ char *original_name, *original_name_truncated;
+ char *new_name_truncated;
+ char *message;
+
+ /* Truncate names for display since very long file names with no spaces
+ * in them won't get wrapped, and can create insanely wide dialog boxes.
+ */
+ original_name = caja_file_get_display_name (file);
+ original_name_truncated = eel_str_middle_truncate (original_name, MAXIMUM_DISPLAYED_FILE_NAME_LENGTH);
+ g_free (original_name);
+
+ new_name_truncated = eel_str_middle_truncate (new_name, MAXIMUM_DISPLAYED_FILE_NAME_LENGTH);
+
+ message = NULL;
+ if (error->domain == G_IO_ERROR)
+ {
+ switch (error->code)
+ {
+ case G_IO_ERROR_EXISTS:
+ message = g_strdup_printf (_("The name \"%s\" is already used in this folder. "
+ "Please use a different name."),
+ new_name_truncated);
+ break;
+ case G_IO_ERROR_NOT_FOUND:
+ message = g_strdup_printf (_("There is no \"%s\" in this folder. "
+ "Perhaps it was just moved or deleted?"),
+ original_name_truncated);
+ break;
+ case G_IO_ERROR_PERMISSION_DENIED:
+ message = g_strdup_printf (_("You do not have the permissions necessary to rename \"%s\"."),
+ original_name_truncated);
+ break;
+ case G_IO_ERROR_INVALID_FILENAME:
+ if (strchr (new_name, '/') != NULL)
+ {
+ message = g_strdup_printf (_("The name \"%s\" is not valid because it contains the character \"/\". "
+ "Please use a different name."),
+ new_name_truncated);
+ }
+ else
+ {
+ message = g_strdup_printf (_("The name \"%s\" is not valid. "
+ "Please use a different name."),
+ new_name_truncated);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (message == NULL)
+ {
+ /* We should invent decent error messages for every case we actually experience. */
+ g_warning ("Hit unhandled case %s:%d in fm_report_error_renaming_file",
+ g_quark_to_string (error->domain), error->code);
+ /* fall through */
+ message = g_strdup_printf (_("Sorry, could not rename \"%s\" to \"%s\": %s"),
+ original_name_truncated, new_name_truncated,
+ error->message);
+ }
+
+ g_free (original_name_truncated);
+ g_free (new_name_truncated);
+
+ eel_show_error_dialog (_("The item could not be renamed."), message, parent_window);
+ g_free (message);
+}
+
+void
+fm_report_error_setting_group (CajaFile *file,
+ GError *error,
+ GtkWindow *parent_window)
+{
+ char *file_name;
+ char *message;
+
+ if (error == NULL)
+ {
+ return;
+ }
+
+ file_name = caja_file_get_display_name (file);
+
+ message = NULL;
+ if (error->domain == G_IO_ERROR)
+ {
+ switch (error->code)
+ {
+ case G_IO_ERROR_PERMISSION_DENIED:
+ message = g_strdup_printf (_("You do not have the permissions necessary to change the group of \"%s\"."),
+ file_name);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (message == NULL)
+ {
+ /* We should invent decent error messages for every case we actually experience. */
+ g_warning ("Hit unhandled case %s:%d in fm_report_error_setting_group",
+ g_quark_to_string (error->domain), error->code);
+ /* fall through */
+ message = g_strdup_printf (_("Sorry, could not change the group of \"%s\": %s"), file_name,
+ error->message);
+ }
+
+
+ eel_show_error_dialog (_("The group could not be changed."), message, parent_window);
+
+ g_free (file_name);
+ g_free (message);
+}
+
+void
+fm_report_error_setting_owner (CajaFile *file,
+ GError *error,
+ GtkWindow *parent_window)
+{
+ char *file_name;
+ char *message;
+
+ if (error == NULL)
+ {
+ return;
+ }
+
+ file_name = caja_file_get_display_name (file);
+
+ message = g_strdup_printf (_("Sorry, could not change the owner of \"%s\": %s"), file_name, error->message);
+
+ eel_show_error_dialog (_("The owner could not be changed."), message, parent_window);
+
+ g_free (file_name);
+ g_free (message);
+}
+
+void
+fm_report_error_setting_permissions (CajaFile *file,
+ GError *error,
+ GtkWindow *parent_window)
+{
+ char *file_name;
+ char *message;
+
+ if (error == NULL)
+ {
+ return;
+ }
+
+ file_name = caja_file_get_display_name (file);
+
+ message = g_strdup_printf (_("Sorry, could not change the permissions of \"%s\": %s"), file_name, error->message);
+
+ eel_show_error_dialog (_("The permissions could not be changed."), message, parent_window);
+
+ g_free (file_name);
+ g_free (message);
+}
+
+typedef struct _FMRenameData
+{
+ char *name;
+ CajaFileOperationCallback callback;
+ gpointer callback_data;
+} FMRenameData;
+
+static void
+fm_rename_data_free (FMRenameData *data)
+{
+ g_free (data->name);
+ g_free (data);
+}
+
+static void
+rename_callback (CajaFile *file, GFile *result_location,
+ GError *error, gpointer callback_data)
+{
+ FMRenameData *data;
+
+ g_assert (CAJA_IS_FILE (file));
+ g_assert (callback_data == NULL);
+
+ data = g_object_get_data (G_OBJECT (file), NEW_NAME_TAG);
+ g_assert (data != NULL);
+
+ if (error &&
+ !(error->domain == G_IO_ERROR && error->code == G_IO_ERROR_CANCELLED))
+ {
+ /* If rename failed, notify the user. */
+ fm_report_error_renaming_file (file, data->name, error, NULL);
+ }
+
+ finish_rename (file, TRUE, error);
+}
+
+static void
+cancel_rename_callback (gpointer callback_data)
+{
+ GError *error;
+
+ error = g_error_new (G_IO_ERROR, G_IO_ERROR_CANCELLED, "Cancelled");
+ finish_rename (CAJA_FILE (callback_data), FALSE, error);
+ g_error_free (error);
+}
+
+static void
+finish_rename (CajaFile *file, gboolean stop_timer, GError *error)
+{
+ FMRenameData *data;
+
+ data = g_object_get_data (G_OBJECT (file), NEW_NAME_TAG);
+ if (data == NULL)
+ {
+ return;
+ }
+
+ /* Cancel both the rename and the timed wait. */
+ caja_file_cancel (file, rename_callback, NULL);
+ if (stop_timer)
+ {
+ eel_timed_wait_stop (cancel_rename_callback, file);
+ }
+
+ if (data->callback != NULL)
+ {
+ data->callback (file, NULL, error, data->callback_data);
+ }
+
+ /* Let go of file name. */
+ g_object_set_data (G_OBJECT (file), NEW_NAME_TAG, NULL);
+}
+
+void
+fm_rename_file (CajaFile *file,
+ const char *new_name,
+ CajaFileOperationCallback callback,
+ gpointer callback_data)
+{
+ char *old_name, *wait_message;
+ FMRenameData *data;
+ char *uri;
+ GError *error;
+
+ g_return_if_fail (CAJA_IS_FILE (file));
+ g_return_if_fail (new_name != NULL);
+
+ /* Stop any earlier rename that's already in progress. */
+ error = g_error_new (G_IO_ERROR, G_IO_ERROR_CANCELLED, "Cancelled");
+ finish_rename (file, TRUE, error);
+ g_error_free (error);
+
+ data = g_new0 (FMRenameData, 1);
+ data->name = g_strdup (new_name);
+ data->callback = callback;
+ data->callback_data = callback_data;
+
+ /* Attach the new name to the file. */
+ g_object_set_data_full (G_OBJECT (file),
+ NEW_NAME_TAG,
+ data, (GDestroyNotify)fm_rename_data_free);
+
+ /* Start the timed wait to cancel the rename. */
+ old_name = caja_file_get_display_name (file);
+ wait_message = g_strdup_printf (_("Renaming \"%s\" to \"%s\"."),
+ old_name,
+ new_name);
+ g_free (old_name);
+ eel_timed_wait_start (cancel_rename_callback, file, wait_message,
+ NULL); /* FIXME bugzilla.gnome.org 42395: Parent this? */
+ g_free (wait_message);
+
+ uri = caja_file_get_uri (file);
+ caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
+ "rename file old=\"%s\", new=\"%s\"",
+ uri, new_name);
+ g_free (uri);
+
+ /* Start the rename. */
+ caja_file_rename (file, new_name,
+ rename_callback, NULL);
+}
diff --git a/src/file-manager/fm-error-reporting.h b/src/file-manager/fm-error-reporting.h
new file mode 100644
index 00000000..556e5ae2
--- /dev/null
+++ b/src/file-manager/fm-error-reporting.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* fm-error-reporting.h - interface for file manager functions that report
+ errors to the user.
+
+ Copyright (C) 2000 Eazel, Inc.
+
+ The Mate Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Mate Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: John Sullivan <[email protected]>
+*/
+
+#ifndef FM_ERROR_REPORTING_H
+#define FM_ERROR_REPORTING_H
+
+#include <gtk/gtk.h>
+#include <libcaja-private/caja-file.h>
+
+void fm_report_error_loading_directory (CajaFile *file,
+ GError *error,
+ GtkWindow *parent_window);
+void fm_report_error_renaming_file (CajaFile *file,
+ const char *new_name,
+ GError *error,
+ GtkWindow *parent_window);
+void fm_report_error_setting_permissions (CajaFile *file,
+ GError *error,
+ GtkWindow *parent_window);
+void fm_report_error_setting_owner (CajaFile *file,
+ GError *error,
+ GtkWindow *parent_window);
+void fm_report_error_setting_group (CajaFile *file,
+ GError *error,
+ GtkWindow *parent_window);
+
+/* FIXME bugzilla.gnome.org 42394: Should this file be renamed or should this function be moved? */
+void fm_rename_file (CajaFile *file,
+ const char *new_name,
+ CajaFileOperationCallback callback,
+ gpointer callback_data);
+
+#endif /* FM_ERROR_REPORTING_H */
diff --git a/src/file-manager/fm-icon-container.c b/src/file-manager/fm-icon-container.c
new file mode 100644
index 00000000..86e54669
--- /dev/null
+++ b/src/file-manager/fm-icon-container.c
@@ -0,0 +1,625 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* fm-icon-container.h - the container widget for file manager icons
+
+ Copyright (C) 2002 Sun Microsystems, Inc.
+
+ The Mate Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Mate Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Michael Meeks <[email protected]>
+*/
+#include <config.h>
+
+#include <string.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <libcaja-private/caja-global-preferences.h>
+#include <libcaja-private/caja-file-attributes.h>
+#include <libcaja-private/caja-thumbnails.h>
+#include <libcaja-private/caja-desktop-icon-file.h>
+
+#include "fm-icon-container.h"
+
+#define ICON_TEXT_ATTRIBUTES_NUM_ITEMS 3
+#define ICON_TEXT_ATTRIBUTES_DEFAULT_TOKENS "size,date_modified,type"
+
+G_DEFINE_TYPE (FMIconContainer, fm_icon_container, CAJA_TYPE_ICON_CONTAINER);
+
+static GQuark attribute_none_q;
+
+static FMIconView *
+get_icon_view (CajaIconContainer *container)
+{
+ /* Type unsafe comparison for performance */
+ return ((FMIconContainer *)container)->view;
+}
+
+static CajaIconInfo *
+fm_icon_container_get_icon_images (CajaIconContainer *container,
+ CajaIconData *data,
+ int size,
+ GList **emblem_pixbufs,
+ char **embedded_text,
+ gboolean for_drag_accept,
+ gboolean need_large_embeddded_text,
+ gboolean *embedded_text_needs_loading,
+ gboolean *has_window_open)
+{
+ FMIconView *icon_view;
+ char **emblems_to_ignore;
+ CajaFile *file;
+ gboolean use_embedding;
+ CajaFileIconFlags flags;
+ guint emblem_size;
+
+ file = (CajaFile *) data;
+
+ g_assert (CAJA_IS_FILE (file));
+ icon_view = get_icon_view (container);
+ g_return_val_if_fail (icon_view != NULL, NULL);
+
+ use_embedding = FALSE;
+ if (embedded_text)
+ {
+ *embedded_text = caja_file_peek_top_left_text (file, need_large_embeddded_text, embedded_text_needs_loading);
+ use_embedding = *embedded_text != NULL;
+ }
+
+ if (emblem_pixbufs != NULL)
+ {
+ emblem_size = caja_icon_get_emblem_size_for_icon_size (size);
+ /* don't return images larger than the actual icon size */
+ emblem_size = MIN (emblem_size, size);
+
+ if (emblem_size > 0)
+ {
+ emblems_to_ignore = fm_directory_view_get_emblem_names_to_exclude
+ (FM_DIRECTORY_VIEW (icon_view));
+ *emblem_pixbufs = caja_file_get_emblem_pixbufs (file,
+ emblem_size,
+ FALSE,
+ emblems_to_ignore);
+ g_strfreev (emblems_to_ignore);
+ }
+ }
+
+ *has_window_open = caja_file_has_open_window (file);
+
+ flags = CAJA_FILE_ICON_FLAGS_USE_MOUNT_ICON_AS_EMBLEM;
+ if (!fm_icon_view_is_compact (icon_view) ||
+ caja_icon_container_get_zoom_level (container) > CAJA_ZOOM_LEVEL_STANDARD)
+ {
+ flags |= CAJA_FILE_ICON_FLAGS_USE_THUMBNAILS;
+ if (fm_icon_view_is_compact (icon_view))
+ {
+ flags |= CAJA_FILE_ICON_FLAGS_FORCE_THUMBNAIL_SIZE;
+ }
+ }
+
+ if (use_embedding)
+ {
+ flags |= CAJA_FILE_ICON_FLAGS_EMBEDDING_TEXT;
+ }
+ if (for_drag_accept)
+ {
+ flags |= CAJA_FILE_ICON_FLAGS_FOR_DRAG_ACCEPT;
+ }
+
+ return caja_file_get_icon (file, size, flags);
+}
+
+static char *
+fm_icon_container_get_icon_description (CajaIconContainer *container,
+ CajaIconData *data)
+{
+ CajaFile *file;
+ char *mime_type;
+ const char *description;
+
+ file = CAJA_FILE (data);
+ g_assert (CAJA_IS_FILE (file));
+
+ if (CAJA_IS_DESKTOP_ICON_FILE (file))
+ {
+ return NULL;
+ }
+
+ mime_type = caja_file_get_mime_type (file);
+ description = g_content_type_get_description (mime_type);
+ g_free (mime_type);
+ return g_strdup (description);
+}
+
+static void
+fm_icon_container_start_monitor_top_left (CajaIconContainer *container,
+ CajaIconData *data,
+ gconstpointer client,
+ gboolean large_text)
+{
+ CajaFile *file;
+ CajaFileAttributes attributes;
+
+ file = (CajaFile *) data;
+
+ g_assert (CAJA_IS_FILE (file));
+
+ attributes = CAJA_FILE_ATTRIBUTE_TOP_LEFT_TEXT;
+ if (large_text)
+ {
+ attributes |= CAJA_FILE_ATTRIBUTE_LARGE_TOP_LEFT_TEXT;
+ }
+ caja_file_monitor_add (file, client, attributes);
+}
+
+static void
+fm_icon_container_stop_monitor_top_left (CajaIconContainer *container,
+ CajaIconData *data,
+ gconstpointer client)
+{
+ CajaFile *file;
+
+ file = (CajaFile *) data;
+
+ g_assert (CAJA_IS_FILE (file));
+
+ caja_file_monitor_remove (file, client);
+}
+
+static void
+fm_icon_container_prioritize_thumbnailing (CajaIconContainer *container,
+ CajaIconData *data)
+{
+ CajaFile *file;
+ char *uri;
+
+ file = (CajaFile *) data;
+
+ g_assert (CAJA_IS_FILE (file));
+
+ if (caja_file_is_thumbnailing (file))
+ {
+ uri = caja_file_get_uri (file);
+ caja_thumbnail_prioritize (uri);
+ g_free (uri);
+ }
+}
+
+/*
+ * Get the preference for which caption text should appear
+ * beneath icons.
+ */
+static GQuark *
+fm_icon_container_get_icon_text_attributes_from_preferences (void)
+{
+ static GQuark *attributes = NULL;
+
+ if (attributes == NULL)
+ {
+ eel_preferences_add_auto_string_array_as_quarks (CAJA_PREFERENCES_ICON_VIEW_CAPTIONS,
+ &attributes);
+ }
+
+ /* We don't need to sanity check the attributes list even though it came
+ * from preferences.
+ *
+ * There are 2 ways that the values in the list could be bad.
+ *
+ * 1) The user picks "bad" values. "bad" values are those that result in
+ * there being duplicate attributes in the list.
+ *
+ * 2) Value stored in MateConf are tampered with. Its possible physically do
+ * this by pulling the rug underneath MateConf and manually editing its
+ * config files. Its also possible to use a third party MateConf key
+ * editor and store garbage for the keys in question.
+ *
+ * Thankfully, the Caja preferences machinery deals with both of
+ * these cases.
+ *
+ * In the first case, the preferences dialog widgetry prevents
+ * duplicate attributes by making "bad" choices insensitive.
+ *
+ * In the second case, the preferences getter (and also the auto storage) for
+ * string_array values are always valid members of the enumeration associated
+ * with the preference.
+ *
+ * So, no more error checking on attributes is needed here and we can return
+ * a the auto stored value.
+ */
+ return attributes;
+}
+
+static int
+quarkv_length (GQuark *attributes)
+{
+ int i;
+ i = 0;
+ while (attributes[i] != 0)
+ {
+ i++;
+ }
+ return i;
+}
+
+/**
+ * fm_icon_view_get_icon_text_attribute_names:
+ *
+ * Get a list representing which text attributes should be displayed
+ * beneath an icon. The result is dependent on zoom level and possibly
+ * user configuration. Don't free the result.
+ * @view: FMIconView to query.
+ *
+ **/
+static GQuark *
+fm_icon_container_get_icon_text_attribute_names (CajaIconContainer *container,
+ int *len)
+{
+ GQuark *attributes;
+ int piece_count;
+
+ const int pieces_by_level[] =
+ {
+ 0, /* CAJA_ZOOM_LEVEL_SMALLEST */
+ 0, /* CAJA_ZOOM_LEVEL_SMALLER */
+ 0, /* CAJA_ZOOM_LEVEL_SMALL */
+ 1, /* CAJA_ZOOM_LEVEL_STANDARD */
+ 2, /* CAJA_ZOOM_LEVEL_LARGE */
+ 2, /* CAJA_ZOOM_LEVEL_LARGER */
+ 3 /* CAJA_ZOOM_LEVEL_LARGEST */
+ };
+
+ piece_count = pieces_by_level[caja_icon_container_get_zoom_level (container)];
+
+ attributes = fm_icon_container_get_icon_text_attributes_from_preferences ();
+
+ *len = MIN (piece_count, quarkv_length (attributes));
+
+ return attributes;
+}
+
+/* This callback returns the text, both the editable part, and the
+ * part below that is not editable.
+ */
+static void
+fm_icon_container_get_icon_text (CajaIconContainer *container,
+ CajaIconData *data,
+ char **editable_text,
+ char **additional_text,
+ gboolean include_invisible)
+{
+ char *actual_uri;
+ gchar *description;
+ GQuark *attributes;
+ char *text_array[4];
+ int i, j, num_attributes;
+ FMIconView *icon_view;
+ CajaFile *file;
+ gboolean use_additional;
+
+ file = CAJA_FILE (data);
+
+ g_assert (CAJA_IS_FILE (file));
+ g_assert (editable_text != NULL);
+ icon_view = get_icon_view (container);
+ g_return_if_fail (icon_view != NULL);
+
+ use_additional = (additional_text != NULL);
+
+ /* In the smallest zoom mode, no text is drawn. */
+ if (caja_icon_container_get_zoom_level (container) == CAJA_ZOOM_LEVEL_SMALLEST &&
+ !include_invisible)
+ {
+ *editable_text = NULL;
+ }
+ else
+ {
+ /* Strip the suffix for caja object xml files. */
+ *editable_text = caja_file_get_display_name (file);
+ }
+
+ if (!use_additional)
+ {
+ return;
+ }
+
+ if (fm_icon_view_is_compact (icon_view))
+ {
+ *additional_text = NULL;
+ return;
+ }
+
+ if (CAJA_IS_DESKTOP_ICON_FILE (file))
+ {
+ /* Don't show the normal extra information for desktop icons, it doesn't
+ * make sense. */
+ *additional_text = NULL;
+ return;
+ }
+
+ /* Handle link files specially. */
+ if (caja_file_is_caja_link (file))
+ {
+ /* FIXME bugzilla.gnome.org 42531: Does sync. I/O and works only locally. */
+ *additional_text = NULL;
+ if (caja_file_is_local (file))
+ {
+ actual_uri = caja_file_get_uri (file);
+ description = caja_link_local_get_additional_text (actual_uri);
+ if (description)
+ *additional_text = g_strdup_printf (" \n%s\n ", description);
+ g_free (description);
+ g_free (actual_uri);
+ }
+ /* Don't show the normal extra information for desktop files, it doesn't
+ * make sense. */
+ return;
+ }
+
+ /* Find out what attributes go below each icon. */
+ attributes = fm_icon_container_get_icon_text_attribute_names (container,
+ &num_attributes);
+
+ /* Get the attributes. */
+ j = 0;
+ for (i = 0; i < num_attributes; ++i)
+ {
+ if (attributes[i] == attribute_none_q)
+ {
+ continue;
+ }
+
+ text_array[j++] =
+ caja_file_get_string_attribute_with_default_q (file, attributes[i]);
+ }
+ text_array[j] = NULL;
+
+ /* Return them. */
+ if (j == 0)
+ {
+ *additional_text = NULL;
+ }
+ else if (j == 1)
+ {
+ /* Only one item, avoid the strdup + free */
+ *additional_text = text_array[0];
+ }
+ else
+ {
+ *additional_text = g_strjoinv ("\n", text_array);
+
+ for (i = 0; i < j; i++)
+ {
+ g_free (text_array[i]);
+ }
+ }
+}
+
+/* Sort as follows:
+ * 0) computer link
+ * 1) home link
+ * 2) network link
+ * 3) mount links
+ * 4) other
+ * 5) trash link
+ */
+typedef enum
+{
+ SORT_COMPUTER_LINK,
+ SORT_HOME_LINK,
+ SORT_NETWORK_LINK,
+ SORT_MOUNT_LINK,
+ SORT_OTHER,
+ SORT_TRASH_LINK
+} SortCategory;
+
+static SortCategory
+get_sort_category (CajaFile *file)
+{
+ CajaDesktopLink *link;
+ SortCategory category;
+
+ category = SORT_OTHER;
+
+ if (CAJA_IS_DESKTOP_ICON_FILE (file))
+ {
+ link = caja_desktop_icon_file_get_link (CAJA_DESKTOP_ICON_FILE (file));
+ if (link != NULL)
+ {
+ switch (caja_desktop_link_get_link_type (link))
+ {
+ case CAJA_DESKTOP_LINK_COMPUTER:
+ category = SORT_COMPUTER_LINK;
+ break;
+ case CAJA_DESKTOP_LINK_HOME:
+ category = SORT_HOME_LINK;
+ break;
+ case CAJA_DESKTOP_LINK_MOUNT:
+ category = SORT_MOUNT_LINK;
+ break;
+ case CAJA_DESKTOP_LINK_TRASH:
+ category = SORT_TRASH_LINK;
+ break;
+ case CAJA_DESKTOP_LINK_NETWORK:
+ category = SORT_NETWORK_LINK;
+ break;
+ default:
+ category = SORT_OTHER;
+ break;
+ }
+ g_object_unref (link);
+ }
+ }
+
+ return category;
+}
+
+static int
+fm_desktop_icon_container_icons_compare (CajaIconContainer *container,
+ CajaIconData *data_a,
+ CajaIconData *data_b)
+{
+ CajaFile *file_a;
+ CajaFile *file_b;
+ FMDirectoryView *directory_view;
+ SortCategory category_a, category_b;
+
+ file_a = (CajaFile *) data_a;
+ file_b = (CajaFile *) data_b;
+
+ directory_view = FM_DIRECTORY_VIEW (FM_ICON_CONTAINER (container)->view);
+ g_return_val_if_fail (directory_view != NULL, 0);
+
+ category_a = get_sort_category (file_a);
+ category_b = get_sort_category (file_b);
+
+ if (category_a == category_b)
+ {
+ return caja_file_compare_for_sort
+ (file_a, file_b, CAJA_FILE_SORT_BY_DISPLAY_NAME,
+ fm_directory_view_should_sort_directories_first (directory_view),
+ FALSE);
+ }
+
+ if (category_a < category_b)
+ {
+ return -1;
+ }
+ else
+ {
+ return +1;
+ }
+}
+
+static int
+fm_icon_container_compare_icons (CajaIconContainer *container,
+ CajaIconData *icon_a,
+ CajaIconData *icon_b)
+{
+ FMIconView *icon_view;
+
+ icon_view = get_icon_view (container);
+ g_return_val_if_fail (icon_view != NULL, 0);
+
+ if (FM_ICON_CONTAINER (container)->sort_for_desktop)
+ {
+ return fm_desktop_icon_container_icons_compare
+ (container, icon_a, icon_b);
+ }
+
+ /* Type unsafe comparisons for performance */
+ return fm_icon_view_compare_files (icon_view,
+ (CajaFile *)icon_a,
+ (CajaFile *)icon_b);
+}
+
+static int
+fm_icon_container_compare_icons_by_name (CajaIconContainer *container,
+ CajaIconData *icon_a,
+ CajaIconData *icon_b)
+{
+ return caja_file_compare_for_sort
+ (CAJA_FILE (icon_a),
+ CAJA_FILE (icon_b),
+ CAJA_FILE_SORT_BY_DISPLAY_NAME,
+ FALSE, FALSE);
+}
+
+static void
+fm_icon_container_freeze_updates (CajaIconContainer *container)
+{
+ FMIconView *icon_view;
+ icon_view = get_icon_view (container);
+ g_return_if_fail (icon_view != NULL);
+ fm_directory_view_freeze_updates (FM_DIRECTORY_VIEW (icon_view));
+}
+
+static void
+fm_icon_container_unfreeze_updates (CajaIconContainer *container)
+{
+ FMIconView *icon_view;
+ icon_view = get_icon_view (container);
+ g_return_if_fail (icon_view != NULL);
+ fm_directory_view_unfreeze_updates (FM_DIRECTORY_VIEW (icon_view));
+}
+
+static void
+fm_icon_container_dispose (GObject *object)
+{
+ FMIconContainer *icon_container;
+
+ icon_container = FM_ICON_CONTAINER (object);
+
+ icon_container->view = NULL;
+
+ G_OBJECT_CLASS (fm_icon_container_parent_class)->dispose (object);
+}
+
+static void
+fm_icon_container_class_init (FMIconContainerClass *klass)
+{
+ CajaIconContainerClass *ic_class;
+
+ ic_class = &klass->parent_class;
+
+ attribute_none_q = g_quark_from_static_string ("none");
+
+ ic_class->get_icon_text = fm_icon_container_get_icon_text;
+ ic_class->get_icon_images = fm_icon_container_get_icon_images;
+ ic_class->get_icon_description = fm_icon_container_get_icon_description;
+ ic_class->start_monitor_top_left = fm_icon_container_start_monitor_top_left;
+ ic_class->stop_monitor_top_left = fm_icon_container_stop_monitor_top_left;
+ ic_class->prioritize_thumbnailing = fm_icon_container_prioritize_thumbnailing;
+
+ ic_class->compare_icons = fm_icon_container_compare_icons;
+ ic_class->compare_icons_by_name = fm_icon_container_compare_icons_by_name;
+ ic_class->freeze_updates = fm_icon_container_freeze_updates;
+ ic_class->unfreeze_updates = fm_icon_container_unfreeze_updates;
+
+ G_OBJECT_CLASS (klass)->dispose = fm_icon_container_dispose;
+}
+
+static void
+fm_icon_container_init (FMIconContainer *icon_container)
+{
+}
+
+CajaIconContainer *
+fm_icon_container_construct (FMIconContainer *icon_container, FMIconView *view)
+{
+ AtkObject *atk_obj;
+
+ g_return_val_if_fail (FM_IS_ICON_VIEW (view), NULL);
+
+ icon_container->view = view;
+ atk_obj = gtk_widget_get_accessible (GTK_WIDGET (icon_container));
+ atk_object_set_name (atk_obj, _("Icon View"));
+
+ return CAJA_ICON_CONTAINER (icon_container);
+}
+
+CajaIconContainer *
+fm_icon_container_new (FMIconView *view)
+{
+ return fm_icon_container_construct
+ (g_object_new (FM_TYPE_ICON_CONTAINER, NULL),
+ view);
+}
+
+void
+fm_icon_container_set_sort_desktop (FMIconContainer *container,
+ gboolean desktop)
+{
+ container->sort_for_desktop = desktop;
+}
diff --git a/src/file-manager/fm-icon-container.h b/src/file-manager/fm-icon-container.h
new file mode 100644
index 00000000..a0527acf
--- /dev/null
+++ b/src/file-manager/fm-icon-container.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* fm-icon-container.h - the container widget for file manager icons
+
+ Copyright (C) 2002 Sun Microsystems, Inc.
+
+ The Mate Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Mate Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Michael Meeks <[email protected]>
+*/
+
+#ifndef FM_ICON_CONTAINER_H
+#define FM_ICON_CONTAINER_H
+
+#include <libcaja-private/caja-icon-container.h>
+#include "fm-icon-view.h"
+
+typedef struct FMIconContainer FMIconContainer;
+typedef struct FMIconContainerClass FMIconContainerClass;
+
+#define FM_TYPE_ICON_CONTAINER fm_icon_container_get_type()
+#define FM_ICON_CONTAINER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_ICON_CONTAINER, FMIconContainer))
+#define FM_ICON_CONTAINER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_ICON_CONTAINER, FMIconContainerClass))
+#define FM_IS_ICON_CONTAINER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_ICON_CONTAINER))
+#define FM_IS_ICON_CONTAINER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_ICON_CONTAINER))
+#define FM_ICON_CONTAINER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_ICON_CONTAINER, FMIconContainerClass))
+
+typedef struct FMIconContainerDetails FMIconContainerDetails;
+
+struct FMIconContainer
+{
+ CajaIconContainer parent;
+
+ FMIconView *view;
+ gboolean sort_for_desktop;
+};
+
+struct FMIconContainerClass
+{
+ CajaIconContainerClass parent_class;
+};
+
+GType fm_icon_container_get_type (void);
+CajaIconContainer *fm_icon_container_construct (FMIconContainer *icon_container,
+ FMIconView *view);
+CajaIconContainer *fm_icon_container_new (FMIconView *view);
+void fm_icon_container_set_sort_desktop (FMIconContainer *container,
+ gboolean desktop);
+
+#endif /* FM_ICON_CONTAINER_H */
diff --git a/src/file-manager/fm-icon-view.c b/src/file-manager/fm-icon-view.c
new file mode 100644
index 00000000..7d8687a2
--- /dev/null
+++ b/src/file-manager/fm-icon-view.c
@@ -0,0 +1,3439 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* fm-icon-view.c - implementation of icon view of directory.
+
+ Copyright (C) 2000, 2001 Eazel, Inc.
+
+ The Mate Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Mate Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: John Sullivan <[email protected]>
+*/
+
+#include <config.h>
+#include "fm-icon-view.h"
+
+#include "fm-actions.h"
+#include "fm-icon-container.h"
+#include "fm-desktop-icon-view.h"
+#include "fm-error-reporting.h"
+#include <stdlib.h>
+#include <eel/eel-background.h>
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-gtk-extensions.h>
+#include <eel/eel-gtk-macros.h>
+#include <eel/eel-stock-dialogs.h>
+#include <eel/eel-string.h>
+#include <eel/eel-vfs-extensions.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <libcaja-private/caja-clipboard-monitor.h>
+#include <libcaja-private/caja-directory-background.h>
+#include <libcaja-private/caja-directory.h>
+#include <libcaja-private/caja-dnd.h>
+#include <libcaja-private/caja-file-utilities.h>
+#include <libcaja-private/caja-ui-utilities.h>
+#include <libcaja-private/caja-global-preferences.h>
+#include <libcaja-private/caja-icon-container.h>
+#include <libcaja-private/caja-icon-dnd.h>
+#include <libcaja-private/caja-link.h>
+#include <libcaja-private/caja-metadata.h>
+#include <libcaja-private/caja-view-factory.h>
+#include <libcaja-private/caja-clipboard.h>
+#include <libcaja-private/caja-desktop-icon-file.h>
+#include <locale.h>
+#include <signal.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "caja-audio-mime-types.h"
+
+#define POPUP_PATH_ICON_APPEARANCE "/selection/Icon Appearance Items"
+
+enum
+{
+ PROP_0,
+ PROP_COMPACT
+};
+
+typedef struct
+{
+ const CajaFileSortType sort_type;
+ const char *metadata_text;
+ const char *action;
+ const char *menu_label;
+ const char *menu_hint;
+} SortCriterion;
+
+typedef enum
+{
+ MENU_ITEM_TYPE_STANDARD,
+ MENU_ITEM_TYPE_CHECK,
+ MENU_ITEM_TYPE_RADIO,
+ MENU_ITEM_TYPE_TREE
+} MenuItemType;
+
+struct FMIconViewDetails
+{
+ GList *icons_not_positioned;
+
+ guint react_to_icon_change_idle_id;
+
+ const SortCriterion *sort;
+ gboolean sort_reversed;
+
+ GtkActionGroup *icon_action_group;
+ guint icon_merge_id;
+
+ int audio_preview_timeout;
+ CajaFile *audio_preview_file;
+ int audio_preview_child_watch;
+ GPid audio_preview_child_pid;
+
+ gboolean filter_by_screen;
+ int num_screens;
+
+ gboolean compact;
+
+ gulong clipboard_handler_id;
+};
+
+
+/* Note that the first item in this list is the default sort,
+ * and that the items show up in the menu in the order they
+ * appear in this list.
+ */
+static const SortCriterion sort_criteria[] =
+{
+ {
+ CAJA_FILE_SORT_BY_DISPLAY_NAME,
+ "name",
+ "Sort by Name",
+ N_("by _Name"),
+ N_("Keep icons sorted by name in rows")
+ },
+ {
+ CAJA_FILE_SORT_BY_SIZE,
+ "size",
+ "Sort by Size",
+ N_("by _Size"),
+ N_("Keep icons sorted by size in rows")
+ },
+ {
+ CAJA_FILE_SORT_BY_TYPE,
+ "type",
+ "Sort by Type",
+ N_("by _Type"),
+ N_("Keep icons sorted by type in rows")
+ },
+ {
+ CAJA_FILE_SORT_BY_MTIME,
+ "modification date",
+ "Sort by Modification Date",
+ N_("by Modification _Date"),
+ N_("Keep icons sorted by modification date in rows")
+ },
+ {
+ CAJA_FILE_SORT_BY_EMBLEMS,
+ "emblems",
+ "Sort by Emblems",
+ N_("by _Emblems"),
+ N_("Keep icons sorted by emblems in rows")
+ },
+ {
+ CAJA_FILE_SORT_BY_TRASHED_TIME,
+ "trashed",
+ "Sort by Trash Time",
+ N_("by T_rash Time"),
+ N_("Keep icons sorted by trash time in rows")
+ }
+};
+
+static gboolean default_sort_in_reverse_order = FALSE;
+static int preview_sound_auto_value;
+
+static void fm_icon_view_set_directory_sort_by (FMIconView *icon_view,
+ CajaFile *file,
+ const char *sort_by);
+static void fm_icon_view_set_zoom_level (FMIconView *view,
+ CajaZoomLevel new_level,
+ gboolean always_emit);
+static void fm_icon_view_update_click_mode (FMIconView *icon_view);
+static void fm_icon_view_set_directory_tighter_layout (FMIconView *icon_view,
+ CajaFile *file,
+ gboolean tighter_layout);
+static gboolean fm_icon_view_supports_manual_layout (FMIconView *icon_view);
+static gboolean fm_icon_view_supports_scaling (FMIconView *icon_view);
+static void fm_icon_view_reveal_selection (FMDirectoryView *view);
+static const SortCriterion *get_sort_criterion_by_sort_type (CajaFileSortType sort_type);
+static void set_sort_criterion_by_sort_type (FMIconView *icon_view,
+ CajaFileSortType sort_type);
+static gboolean set_sort_reversed (FMIconView *icon_view,
+ gboolean new_value);
+static void switch_to_manual_layout (FMIconView *view);
+static void preview_audio (FMIconView *icon_view,
+ CajaFile *file,
+ gboolean start_flag);
+static void update_layout_menus (FMIconView *view);
+static CajaFileSortType get_default_sort_order (CajaFile *file,
+ gboolean *reversed);
+
+
+static void fm_icon_view_iface_init (CajaViewIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (FMIconView, fm_icon_view, FM_TYPE_DIRECTORY_VIEW,
+ G_IMPLEMENT_INTERFACE (CAJA_TYPE_VIEW,
+ fm_icon_view_iface_init));
+
+static void
+fm_icon_view_destroy (GtkObject *object)
+{
+ FMIconView *icon_view;
+
+ icon_view = FM_ICON_VIEW (object);
+
+ if (icon_view->details->react_to_icon_change_idle_id != 0)
+ {
+ g_source_remove (icon_view->details->react_to_icon_change_idle_id);
+ icon_view->details->react_to_icon_change_idle_id = 0;
+ }
+
+ if (icon_view->details->clipboard_handler_id != 0)
+ {
+ g_signal_handler_disconnect (caja_clipboard_monitor_get (),
+ icon_view->details->clipboard_handler_id);
+ icon_view->details->clipboard_handler_id = 0;
+ }
+
+ /* kill any sound preview process that is ongoing */
+ preview_audio (icon_view, NULL, FALSE);
+
+ if (icon_view->details->icons_not_positioned)
+ {
+ caja_file_list_free (icon_view->details->icons_not_positioned);
+ icon_view->details->icons_not_positioned = NULL;
+ }
+
+ GTK_OBJECT_CLASS (fm_icon_view_parent_class)->destroy (object);
+}
+
+
+static void
+fm_icon_view_finalize (GObject *object)
+{
+ FMIconView *icon_view;
+
+ icon_view = FM_ICON_VIEW (object);
+
+ g_free (icon_view->details);
+
+ G_OBJECT_CLASS (fm_icon_view_parent_class)->finalize (object);
+}
+
+static CajaIconContainer *
+get_icon_container (FMIconView *icon_view)
+{
+ return CAJA_ICON_CONTAINER (gtk_bin_get_child (GTK_BIN (icon_view)));
+}
+
+static gboolean
+get_stored_icon_position_callback (CajaIconContainer *container,
+ CajaFile *file,
+ CajaIconPosition *position,
+ FMIconView *icon_view)
+{
+ char *position_string, *scale_string;
+ gboolean position_good;
+ char c;
+
+ g_assert (CAJA_IS_ICON_CONTAINER (container));
+ g_assert (CAJA_IS_FILE (file));
+ g_assert (position != NULL);
+ g_assert (FM_IS_ICON_VIEW (icon_view));
+
+ if (!fm_icon_view_supports_manual_layout (icon_view))
+ {
+ return FALSE;
+ }
+
+ /* Get the current position of this icon from the metadata. */
+ position_string = caja_file_get_metadata
+ (file, CAJA_METADATA_KEY_ICON_POSITION, "");
+ position_good = sscanf
+ (position_string, " %d , %d %c",
+ &position->x, &position->y, &c) == 2;
+ g_free (position_string);
+
+ /* If it is the desktop directory, maybe the mate-libs metadata has information about it */
+
+ /* Disable scaling if not on the desktop */
+ if (fm_icon_view_supports_scaling (icon_view))
+ {
+ /* Get the scale of the icon from the metadata. */
+ scale_string = caja_file_get_metadata
+ (file, CAJA_METADATA_KEY_ICON_SCALE, "1");
+ position->scale = g_ascii_strtod (scale_string, NULL);
+ if (errno != 0)
+ {
+ position->scale = 1.0;
+ }
+
+ g_free (scale_string);
+ }
+ else
+ {
+ position->scale = 1.0;
+ }
+
+ return position_good;
+}
+
+static void
+real_set_sort_criterion (FMIconView *icon_view,
+ const SortCriterion *sort,
+ gboolean clear)
+{
+ CajaFile *file;
+
+ file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (icon_view));
+
+ if (clear)
+ {
+ caja_file_set_metadata (file,
+ CAJA_METADATA_KEY_ICON_VIEW_SORT_BY, NULL, NULL);
+ caja_file_set_metadata (file,
+ CAJA_METADATA_KEY_ICON_VIEW_SORT_REVERSED, NULL, NULL);
+ icon_view->details->sort =
+ get_sort_criterion_by_sort_type (get_default_sort_order
+ (file, &icon_view->details->sort_reversed));
+ }
+ else
+ {
+ /* Store the new sort setting. */
+ fm_icon_view_set_directory_sort_by (icon_view,
+ file,
+ sort->metadata_text);
+ }
+
+ /* Update the layout menus to match the new sort setting. */
+ update_layout_menus (icon_view);
+}
+
+static void
+set_sort_criterion (FMIconView *icon_view, const SortCriterion *sort)
+{
+ if (sort == NULL ||
+ icon_view->details->sort == sort)
+ {
+ return;
+ }
+
+ icon_view->details->sort = sort;
+
+ real_set_sort_criterion (icon_view, sort, FALSE);
+}
+
+static void
+clear_sort_criterion (FMIconView *icon_view)
+{
+ real_set_sort_criterion (icon_view, NULL, TRUE);
+}
+
+static void
+action_stretch_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ g_assert (FM_IS_ICON_VIEW (callback_data));
+
+ caja_icon_container_show_stretch_handles
+ (get_icon_container (FM_ICON_VIEW (callback_data)));
+}
+
+static void
+action_unstretch_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ g_assert (FM_IS_ICON_VIEW (callback_data));
+
+ caja_icon_container_unstretch
+ (get_icon_container (FM_ICON_VIEW (callback_data)));
+}
+
+static void
+fm_icon_view_clean_up (FMIconView *icon_view)
+{
+ EEL_CALL_METHOD (FM_ICON_VIEW_CLASS, icon_view, clean_up, (icon_view));
+}
+
+static void
+fm_icon_view_real_clean_up (FMIconView *icon_view)
+{
+ CajaIconContainer *icon_container;
+ gboolean saved_sort_reversed;
+
+ icon_container = get_icon_container (icon_view);
+
+ /* Hardwire Clean Up to always be by name, in forward order */
+ saved_sort_reversed = icon_view->details->sort_reversed;
+
+ set_sort_reversed (icon_view, FALSE);
+ set_sort_criterion (icon_view, &sort_criteria[0]);
+
+ caja_icon_container_sort (icon_container);
+ caja_icon_container_freeze_icon_positions (icon_container);
+
+ set_sort_reversed (icon_view, saved_sort_reversed);
+}
+
+static void
+action_clean_up_callback (GtkAction *action, gpointer callback_data)
+{
+ fm_icon_view_clean_up (FM_ICON_VIEW (callback_data));
+}
+
+static void
+set_tighter_layout (FMIconView *icon_view, gboolean new_value)
+{
+ fm_icon_view_set_directory_tighter_layout (icon_view,
+ fm_directory_view_get_directory_as_file
+ (FM_DIRECTORY_VIEW (icon_view)),
+ new_value);
+ caja_icon_container_set_tighter_layout (get_icon_container (icon_view),
+ new_value);
+}
+
+static void
+action_tighter_layout_callback (GtkAction *action,
+ gpointer user_data)
+{
+ g_assert (FM_IS_ICON_VIEW (user_data));
+
+ set_tighter_layout (FM_ICON_VIEW (user_data),
+ gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
+}
+
+
+static gboolean
+fm_icon_view_using_auto_layout (FMIconView *icon_view)
+{
+ return caja_icon_container_is_auto_layout
+ (get_icon_container (icon_view));
+}
+
+static gboolean
+fm_icon_view_using_tighter_layout (FMIconView *icon_view)
+{
+ return caja_icon_container_is_tighter_layout
+ (get_icon_container (icon_view));
+}
+
+static void
+action_sort_radio_callback (GtkAction *action,
+ GtkRadioAction *current,
+ FMIconView *view)
+{
+ CajaFileSortType sort_type;
+
+ sort_type = gtk_radio_action_get_current_value (current);
+
+ /* Note that id might be a toggle item.
+ * Ignore non-sort ids so that they don't cause sorting.
+ */
+ if (sort_type == CAJA_FILE_SORT_NONE)
+ {
+ switch_to_manual_layout (view);
+ }
+ else
+ {
+ set_sort_criterion_by_sort_type (view, sort_type);
+ }
+}
+
+static void
+list_covers (CajaIconData *data, gpointer callback_data)
+{
+ GSList **file_list;
+
+ file_list = callback_data;
+
+ *file_list = g_slist_prepend (*file_list, data);
+}
+
+static void
+unref_cover (CajaIconData *data, gpointer callback_data)
+{
+ caja_file_unref (CAJA_FILE (data));
+}
+
+static void
+fm_icon_view_clear (FMDirectoryView *view)
+{
+ CajaIconContainer *icon_container;
+ GSList *file_list;
+
+ g_return_if_fail (FM_IS_ICON_VIEW (view));
+
+ icon_container = get_icon_container (FM_ICON_VIEW (view));
+ if (!icon_container)
+ return;
+
+ /* Clear away the existing icons. */
+ file_list = NULL;
+ caja_icon_container_for_each (icon_container, list_covers, &file_list);
+ caja_icon_container_clear (icon_container);
+ g_slist_foreach (file_list, (GFunc)unref_cover, NULL);
+ g_slist_free (file_list);
+}
+
+
+static gboolean
+should_show_file_on_screen (FMDirectoryView *view, CajaFile *file)
+{
+ char *screen_string;
+ int screen_num;
+ FMIconView *icon_view;
+ GdkScreen *screen;
+
+ icon_view = FM_ICON_VIEW (view);
+
+ if (!fm_directory_view_should_show_file (view, file))
+ {
+ return FALSE;
+ }
+
+ /* Get the screen for this icon from the metadata. */
+ screen_string = caja_file_get_metadata
+ (file, CAJA_METADATA_KEY_SCREEN, "0");
+ screen_num = atoi (screen_string);
+ g_free (screen_string);
+ screen = gtk_widget_get_screen (GTK_WIDGET (view));
+
+ if (screen_num != gdk_screen_get_number (screen) &&
+ (screen_num < icon_view->details->num_screens ||
+ gdk_screen_get_number (screen) > 0))
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+fm_icon_view_remove_file (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory)
+{
+ FMIconView *icon_view;
+
+ /* This used to assert that 'directory == fm_directory_view_get_model (view)', but that
+ * resulted in a lot of crash reports (bug #352592). I don't see how that trace happens.
+ * It seems that somehow we get a files_changed event sent to the view from a directory
+ * that isn't the model, but the code disables the monitor and signal callback handlers when
+ * changing directories. Maybe we can get some more information when this happens.
+ * Further discussion in bug #368178.
+ */
+ if (directory != fm_directory_view_get_model (view))
+ {
+ char *file_uri, *dir_uri, *model_uri;
+ file_uri = caja_file_get_uri (file);
+ dir_uri = caja_directory_get_uri (directory);
+ model_uri = caja_directory_get_uri (fm_directory_view_get_model (view));
+ g_warning ("fm_icon_view_remove_file() - directory not icon view model, shouldn't happen.\n"
+ "file: %p:%s, dir: %p:%s, model: %p:%s, view loading: %d\n"
+ "If you see this, please add this info to http://bugzilla.gnome.org/show_bug.cgi?id=368178",
+ file, file_uri, directory, dir_uri, fm_directory_view_get_model (view), model_uri, fm_directory_view_get_loading (view));
+ g_free (file_uri);
+ g_free (dir_uri);
+ g_free (model_uri);
+ }
+
+ icon_view = FM_ICON_VIEW (view);
+
+ if (caja_icon_container_remove (get_icon_container (icon_view),
+ CAJA_ICON_CONTAINER_ICON_DATA (file)))
+ {
+ if (file == icon_view->details->audio_preview_file)
+ {
+ preview_audio (icon_view, NULL, FALSE);
+ }
+
+ caja_file_unref (file);
+ }
+}
+
+static void
+fm_icon_view_add_file (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory)
+{
+ FMIconView *icon_view;
+ CajaIconContainer *icon_container;
+
+ g_assert (directory == fm_directory_view_get_model (view));
+
+ icon_view = FM_ICON_VIEW (view);
+ icon_container = get_icon_container (icon_view);
+
+ if (icon_view->details->filter_by_screen &&
+ !should_show_file_on_screen (view, file))
+ {
+ return;
+ }
+
+ /* Reset scroll region for the first icon added when loading a directory. */
+ if (fm_directory_view_get_loading (view) && caja_icon_container_is_empty (icon_container))
+ {
+ caja_icon_container_reset_scroll_region (icon_container);
+ }
+
+ if (caja_icon_container_add (icon_container,
+ CAJA_ICON_CONTAINER_ICON_DATA (file)))
+ {
+ caja_file_ref (file);
+ }
+}
+
+static void
+fm_icon_view_flush_added_files (FMDirectoryView *view)
+{
+ caja_icon_container_layout_now (get_icon_container (FM_ICON_VIEW (view)));
+}
+
+static void
+fm_icon_view_file_changed (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory)
+{
+ FMIconView *icon_view;
+
+ g_assert (directory == fm_directory_view_get_model (view));
+
+ g_return_if_fail (view != NULL);
+ icon_view = FM_ICON_VIEW (view);
+
+ if (!icon_view->details->filter_by_screen)
+ {
+ caja_icon_container_request_update
+ (get_icon_container (icon_view),
+ CAJA_ICON_CONTAINER_ICON_DATA (file));
+ return;
+ }
+
+ if (!should_show_file_on_screen (view, file))
+ {
+ fm_icon_view_remove_file (view, file, directory);
+ }
+ else
+ {
+
+ caja_icon_container_request_update
+ (get_icon_container (icon_view),
+ CAJA_ICON_CONTAINER_ICON_DATA (file));
+ }
+}
+
+static gboolean
+fm_icon_view_supports_auto_layout (FMIconView *view)
+{
+ g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE);
+
+ return EEL_CALL_METHOD_WITH_RETURN_VALUE
+ (FM_ICON_VIEW_CLASS, view,
+ supports_auto_layout, (view));
+}
+
+static gboolean
+fm_icon_view_supports_scaling (FMIconView *view)
+{
+ g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE);
+
+ return EEL_CALL_METHOD_WITH_RETURN_VALUE
+ (FM_ICON_VIEW_CLASS, view,
+ supports_scaling, (view));
+}
+
+static gboolean
+fm_icon_view_supports_manual_layout (FMIconView *view)
+{
+ g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE);
+
+ return EEL_CALL_METHOD_WITH_RETURN_VALUE
+ (FM_ICON_VIEW_CLASS, view,
+ supports_manual_layout, (view));
+}
+
+static gboolean
+fm_icon_view_supports_keep_aligned (FMIconView *view)
+{
+ g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE);
+
+ return EEL_CALL_METHOD_WITH_RETURN_VALUE
+ (FM_ICON_VIEW_CLASS, view,
+ supports_keep_aligned, (view));
+}
+
+static gboolean
+fm_icon_view_supports_labels_beside_icons (FMIconView *view)
+{
+ g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE);
+
+ return EEL_CALL_METHOD_WITH_RETURN_VALUE
+ (FM_ICON_VIEW_CLASS, view,
+ supports_labels_beside_icons, (view));
+}
+
+static gboolean
+fm_icon_view_supports_tighter_layout (FMIconView *view)
+{
+ return !fm_icon_view_is_compact (view);
+}
+
+static void
+update_layout_menus (FMIconView *view)
+{
+ gboolean is_auto_layout;
+ GtkAction *action;
+ const char *action_name;
+ CajaFile *file;
+
+ if (view->details->icon_action_group == NULL)
+ {
+ return;
+ }
+
+ is_auto_layout = fm_icon_view_using_auto_layout (view);
+ file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (view));
+
+ if (fm_icon_view_supports_auto_layout (view))
+ {
+ /* Mark sort criterion. */
+ action_name = is_auto_layout ? view->details->sort->action : FM_ACTION_MANUAL_LAYOUT;
+ action = gtk_action_group_get_action (view->details->icon_action_group,
+ action_name);
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE);
+
+ action = gtk_action_group_get_action (view->details->icon_action_group,
+ FM_ACTION_TIGHTER_LAYOUT);
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
+ fm_icon_view_using_tighter_layout (view));
+ gtk_action_set_sensitive (action, fm_icon_view_supports_tighter_layout (view));
+ gtk_action_set_visible (action, fm_icon_view_supports_tighter_layout (view));
+
+ action = gtk_action_group_get_action (view->details->icon_action_group,
+ FM_ACTION_REVERSED_ORDER);
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
+ view->details->sort_reversed);
+ gtk_action_set_sensitive (action, is_auto_layout);
+
+ action = gtk_action_group_get_action (view->details->icon_action_group,
+ FM_ACTION_SORT_TRASH_TIME);
+
+ if (file != NULL && caja_file_is_in_trash (file))
+ {
+ gtk_action_set_visible (action, TRUE);
+ }
+ else
+ {
+ gtk_action_set_visible (action, FALSE);
+ }
+ }
+
+ action = gtk_action_group_get_action (view->details->icon_action_group,
+ FM_ACTION_MANUAL_LAYOUT);
+ gtk_action_set_visible (action,
+ fm_icon_view_supports_manual_layout (view));
+
+ /* Clean Up is only relevant for manual layout */
+ action = gtk_action_group_get_action (view->details->icon_action_group,
+ FM_ACTION_CLEAN_UP);
+ gtk_action_set_sensitive (action, !is_auto_layout);
+
+ if (FM_IS_DESKTOP_ICON_VIEW (view))
+ {
+ gtk_action_set_label (action, _("_Organize Desktop by Name"));
+ }
+
+ action = gtk_action_group_get_action (view->details->icon_action_group,
+ FM_ACTION_KEEP_ALIGNED);
+ gtk_action_set_visible (action,
+ fm_icon_view_supports_keep_aligned (view));
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
+ caja_icon_container_is_keep_aligned (get_icon_container (view)));
+ gtk_action_set_sensitive (action, !is_auto_layout);
+}
+
+
+static char *
+fm_icon_view_get_directory_sort_by (FMIconView *icon_view,
+ CajaFile *file)
+{
+ if (!fm_icon_view_supports_auto_layout (icon_view))
+ {
+ return g_strdup ("name");
+ }
+
+ return EEL_CALL_METHOD_WITH_RETURN_VALUE
+ (FM_ICON_VIEW_CLASS, icon_view,
+ get_directory_sort_by, (icon_view, file));
+}
+
+static CajaFileSortType default_sort_order = CAJA_FILE_SORT_BY_DISPLAY_NAME;
+
+static CajaFileSortType
+get_default_sort_order (CajaFile *file, gboolean *reversed)
+{
+ static gboolean auto_storaged_added = FALSE;
+ CajaFileSortType retval;
+
+ if (auto_storaged_added == FALSE)
+ {
+ auto_storaged_added = TRUE;
+ eel_preferences_add_auto_enum (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_SORT_ORDER,
+ (int *) &default_sort_order);
+ eel_preferences_add_auto_boolean (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_SORT_IN_REVERSE_ORDER,
+ &default_sort_in_reverse_order);
+
+ }
+
+ retval = caja_file_get_default_sort_type (file, reversed);
+
+ if (retval == CAJA_FILE_SORT_NONE)
+ {
+
+ if (reversed != NULL)
+ {
+ *reversed = default_sort_in_reverse_order;
+ }
+
+ retval = CLAMP (default_sort_order, CAJA_FILE_SORT_BY_DISPLAY_NAME,
+ CAJA_FILE_SORT_BY_EMBLEMS);
+ }
+
+ return retval;
+}
+
+static char *
+fm_icon_view_real_get_directory_sort_by (FMIconView *icon_view,
+ CajaFile *file)
+{
+ const SortCriterion *default_sort_criterion;
+ default_sort_criterion = get_sort_criterion_by_sort_type (get_default_sort_order (file, NULL));
+ g_return_val_if_fail (default_sort_criterion != NULL, NULL);
+
+ return caja_file_get_metadata
+ (file, CAJA_METADATA_KEY_ICON_VIEW_SORT_BY,
+ default_sort_criterion->metadata_text);
+}
+
+static void
+fm_icon_view_set_directory_sort_by (FMIconView *icon_view,
+ CajaFile *file,
+ const char *sort_by)
+{
+ if (!fm_icon_view_supports_auto_layout (icon_view))
+ {
+ return;
+ }
+
+ EEL_CALL_METHOD (FM_ICON_VIEW_CLASS, icon_view,
+ set_directory_sort_by, (icon_view, file, sort_by));
+}
+
+static void
+fm_icon_view_real_set_directory_sort_by (FMIconView *icon_view,
+ CajaFile *file,
+ const char *sort_by)
+{
+ const SortCriterion *default_sort_criterion;
+ default_sort_criterion = get_sort_criterion_by_sort_type (get_default_sort_order (file, NULL));
+ g_return_if_fail (default_sort_criterion != NULL);
+
+ caja_file_set_metadata
+ (file, CAJA_METADATA_KEY_ICON_VIEW_SORT_BY,
+ default_sort_criterion->metadata_text,
+ sort_by);
+}
+
+static gboolean
+fm_icon_view_get_directory_sort_reversed (FMIconView *icon_view,
+ CajaFile *file)
+{
+ if (!fm_icon_view_supports_auto_layout (icon_view))
+ {
+ return FALSE;
+ }
+
+ return EEL_CALL_METHOD_WITH_RETURN_VALUE
+ (FM_ICON_VIEW_CLASS, icon_view,
+ get_directory_sort_reversed, (icon_view, file));
+}
+
+static gboolean
+fm_icon_view_real_get_directory_sort_reversed (FMIconView *icon_view,
+ CajaFile *file)
+{
+ gboolean reversed;
+
+ get_default_sort_order (file, &reversed);
+ return caja_file_get_boolean_metadata
+ (file,
+ CAJA_METADATA_KEY_ICON_VIEW_SORT_REVERSED,
+ reversed);
+}
+
+static void
+fm_icon_view_set_directory_sort_reversed (FMIconView *icon_view,
+ CajaFile *file,
+ gboolean sort_reversed)
+{
+ if (!fm_icon_view_supports_auto_layout (icon_view))
+ {
+ return;
+ }
+
+ EEL_CALL_METHOD (FM_ICON_VIEW_CLASS, icon_view,
+ set_directory_sort_reversed,
+ (icon_view, file, sort_reversed));
+}
+
+static void
+fm_icon_view_real_set_directory_sort_reversed (FMIconView *icon_view,
+ CajaFile *file,
+ gboolean sort_reversed)
+{
+ gboolean reversed;
+
+ get_default_sort_order (file, &reversed);
+ caja_file_set_boolean_metadata
+ (file,
+ CAJA_METADATA_KEY_ICON_VIEW_SORT_REVERSED,
+ reversed, sort_reversed);
+}
+
+static gboolean
+get_default_directory_keep_aligned (void)
+{
+ return TRUE;
+}
+
+static gboolean
+fm_icon_view_get_directory_keep_aligned (FMIconView *icon_view,
+ CajaFile *file)
+{
+ if (!fm_icon_view_supports_keep_aligned (icon_view))
+ {
+ return FALSE;
+ }
+
+ return caja_file_get_boolean_metadata
+ (file,
+ CAJA_METADATA_KEY_ICON_VIEW_KEEP_ALIGNED,
+ get_default_directory_keep_aligned ());
+}
+
+static void
+fm_icon_view_set_directory_keep_aligned (FMIconView *icon_view,
+ CajaFile *file,
+ gboolean keep_aligned)
+{
+ if (!fm_icon_view_supports_keep_aligned (icon_view))
+ {
+ return;
+ }
+
+ caja_file_set_boolean_metadata
+ (file, CAJA_METADATA_KEY_ICON_VIEW_KEEP_ALIGNED,
+ get_default_directory_keep_aligned (),
+ keep_aligned);
+}
+
+/* maintainence of auto layout boolean */
+static gboolean default_directory_manual_layout = FALSE;
+
+static gboolean
+get_default_directory_manual_layout (void)
+{
+ static gboolean auto_storaged_added = FALSE;
+
+ if (auto_storaged_added == FALSE)
+ {
+ auto_storaged_added = TRUE;
+ eel_preferences_add_auto_boolean (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_USE_MANUAL_LAYOUT,
+ &default_directory_manual_layout);
+ }
+
+ return default_directory_manual_layout;
+}
+
+static gboolean
+fm_icon_view_get_directory_auto_layout (FMIconView *icon_view,
+ CajaFile *file)
+{
+ if (!fm_icon_view_supports_auto_layout (icon_view))
+ {
+ return FALSE;
+ }
+
+ if (!fm_icon_view_supports_manual_layout (icon_view))
+ {
+ return TRUE;
+ }
+
+ return EEL_CALL_METHOD_WITH_RETURN_VALUE
+ (FM_ICON_VIEW_CLASS, icon_view,
+ get_directory_auto_layout, (icon_view, file));
+}
+
+static gboolean
+fm_icon_view_real_get_directory_auto_layout (FMIconView *icon_view,
+ CajaFile *file)
+{
+
+
+ return caja_file_get_boolean_metadata
+ (file, CAJA_METADATA_KEY_ICON_VIEW_AUTO_LAYOUT, !get_default_directory_manual_layout ());
+}
+
+static void
+fm_icon_view_set_directory_auto_layout (FMIconView *icon_view,
+ CajaFile *file,
+ gboolean auto_layout)
+{
+ if (!fm_icon_view_supports_auto_layout (icon_view) ||
+ !fm_icon_view_supports_manual_layout (icon_view))
+ {
+ return;
+ }
+
+ EEL_CALL_METHOD (FM_ICON_VIEW_CLASS, icon_view,
+ set_directory_auto_layout, (icon_view, file, auto_layout));
+}
+
+static void
+fm_icon_view_real_set_directory_auto_layout (FMIconView *icon_view,
+ CajaFile *file,
+ gboolean auto_layout)
+{
+ if (!fm_icon_view_supports_manual_layout (icon_view))
+ {
+ return;
+ }
+
+ caja_file_set_boolean_metadata
+ (file, CAJA_METADATA_KEY_ICON_VIEW_AUTO_LAYOUT,
+ !get_default_directory_manual_layout (),
+ auto_layout);
+}
+/* maintainence of tighter layout boolean */
+
+static gboolean
+fm_icon_view_get_directory_tighter_layout (FMIconView *icon_view,
+ CajaFile *file)
+{
+ return EEL_CALL_METHOD_WITH_RETURN_VALUE
+ (FM_ICON_VIEW_CLASS, icon_view,
+ get_directory_tighter_layout, (icon_view, file));
+}
+
+static gboolean default_directory_tighter_layout = FALSE;
+
+static gboolean
+get_default_directory_tighter_layout (void)
+{
+ static gboolean auto_storaged_added = FALSE;
+
+ if (auto_storaged_added == FALSE)
+ {
+ auto_storaged_added = TRUE;
+ eel_preferences_add_auto_boolean (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_USE_TIGHTER_LAYOUT,
+ &default_directory_tighter_layout);
+ }
+
+ return default_directory_tighter_layout;
+}
+
+static gboolean
+fm_icon_view_real_get_directory_tighter_layout (FMIconView *icon_view,
+ CajaFile *file)
+{
+ if (!fm_icon_view_supports_tighter_layout (icon_view))
+ {
+ return FALSE;
+ }
+
+ return caja_file_get_boolean_metadata
+ (file,
+ CAJA_METADATA_KEY_ICON_VIEW_TIGHTER_LAYOUT,
+ get_default_directory_tighter_layout ());
+}
+
+static void
+fm_icon_view_set_directory_tighter_layout (FMIconView *icon_view,
+ CajaFile *file,
+ gboolean tighter_layout)
+{
+ EEL_CALL_METHOD (FM_ICON_VIEW_CLASS, icon_view,
+ set_directory_tighter_layout, (icon_view, file, tighter_layout));
+}
+
+static void
+fm_icon_view_real_set_directory_tighter_layout (FMIconView *icon_view,
+ CajaFile *file,
+ gboolean tighter_layout)
+{
+ if (!fm_icon_view_supports_tighter_layout (icon_view))
+ {
+ return;
+ }
+
+ caja_file_set_boolean_metadata
+ (file, CAJA_METADATA_KEY_ICON_VIEW_TIGHTER_LAYOUT,
+ get_default_directory_tighter_layout (),
+ tighter_layout);
+}
+
+static gboolean
+real_supports_auto_layout (FMIconView *view)
+{
+ g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE);
+
+ return TRUE;
+}
+
+static gboolean
+real_supports_scaling (FMIconView *view)
+{
+ g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE);
+
+ return FALSE;
+}
+
+static gboolean
+real_supports_manual_layout (FMIconView *view)
+{
+ g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE);
+
+ return !fm_icon_view_is_compact (view);
+}
+
+static gboolean
+real_supports_keep_aligned (FMIconView *view)
+{
+ g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE);
+
+ return FALSE;
+}
+
+static gboolean
+real_supports_labels_beside_icons (FMIconView *view)
+{
+ g_return_val_if_fail (FM_IS_ICON_VIEW (view), TRUE);
+
+ return TRUE;
+}
+
+static gboolean
+set_sort_reversed (FMIconView *icon_view, gboolean new_value)
+{
+ if (icon_view->details->sort_reversed == new_value)
+ {
+ return FALSE;
+ }
+ icon_view->details->sort_reversed = new_value;
+
+ /* Store the new sort setting. */
+ fm_icon_view_set_directory_sort_reversed (icon_view, fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (icon_view)), new_value);
+
+ /* Update the layout menus to match the new sort-order setting. */
+ update_layout_menus (icon_view);
+
+ return TRUE;
+}
+
+static const SortCriterion *
+get_sort_criterion_by_metadata_text (const char *metadata_text)
+{
+ guint i;
+
+ /* Figure out what the new sort setting should be. */
+ for (i = 0; i < G_N_ELEMENTS (sort_criteria); i++)
+ {
+ if (strcmp (sort_criteria[i].metadata_text, metadata_text) == 0)
+ {
+ return &sort_criteria[i];
+ }
+ }
+ return NULL;
+}
+
+static const SortCriterion *
+get_sort_criterion_by_sort_type (CajaFileSortType sort_type)
+{
+ guint i;
+
+ /* Figure out what the new sort setting should be. */
+ for (i = 0; i < G_N_ELEMENTS (sort_criteria); i++)
+ {
+ if (sort_type == sort_criteria[i].sort_type)
+ {
+ return &sort_criteria[i];
+ }
+ }
+
+ return NULL;
+}
+
+static CajaZoomLevel default_zoom_level = CAJA_ZOOM_LEVEL_STANDARD;
+static CajaZoomLevel default_compact_zoom_level = CAJA_ZOOM_LEVEL_STANDARD;
+#define DEFAULT_ZOOM_LEVEL(icon_view) icon_view->details->compact ? default_compact_zoom_level : default_zoom_level
+
+static CajaZoomLevel
+get_default_zoom_level (FMIconView *icon_view)
+{
+ static gboolean auto_storage_added = FALSE;
+
+ if (!auto_storage_added)
+ {
+ auto_storage_added = TRUE;
+ eel_preferences_add_auto_enum (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_ZOOM_LEVEL,
+ (int *) &default_zoom_level);
+ eel_preferences_add_auto_enum (CAJA_PREFERENCES_COMPACT_VIEW_DEFAULT_ZOOM_LEVEL,
+ (int *) &default_compact_zoom_level);
+ }
+
+ return CLAMP (DEFAULT_ZOOM_LEVEL(icon_view), CAJA_ZOOM_LEVEL_SMALLEST, CAJA_ZOOM_LEVEL_LARGEST);
+}
+
+static void
+set_labels_beside_icons (FMIconView *icon_view)
+{
+ gboolean labels_beside;
+
+ if (fm_icon_view_supports_labels_beside_icons (icon_view))
+ {
+ labels_beside = fm_icon_view_is_compact (icon_view) ||
+ eel_preferences_get_boolean (CAJA_PREFERENCES_ICON_VIEW_LABELS_BESIDE_ICONS);
+
+ if (labels_beside)
+ {
+ caja_icon_container_set_label_position
+ (get_icon_container (icon_view),
+ CAJA_ICON_LABEL_POSITION_BESIDE);
+ }
+ else
+ {
+ caja_icon_container_set_label_position
+ (get_icon_container (icon_view),
+ CAJA_ICON_LABEL_POSITION_UNDER);
+ }
+ }
+}
+
+static void
+set_columns_same_width (FMIconView *icon_view)
+{
+ gboolean all_columns_same_width;
+
+ if (fm_icon_view_is_compact (icon_view))
+ {
+ all_columns_same_width = eel_preferences_get_boolean (CAJA_PREFERENCES_COMPACT_VIEW_ALL_COLUMNS_SAME_WIDTH);
+ caja_icon_container_set_all_columns_same_width (get_icon_container (icon_view), all_columns_same_width);
+ }
+}
+
+static void
+fm_icon_view_begin_loading (FMDirectoryView *view)
+{
+ FMIconView *icon_view;
+ GtkWidget *icon_container;
+ CajaFile *file;
+ int level;
+ char *sort_name;
+
+ g_return_if_fail (FM_IS_ICON_VIEW (view));
+
+ icon_view = FM_ICON_VIEW (view);
+ file = fm_directory_view_get_directory_as_file (view);
+ icon_container = GTK_WIDGET (get_icon_container (icon_view));
+
+ caja_icon_container_begin_loading (CAJA_ICON_CONTAINER (icon_container));
+
+ caja_icon_container_set_allow_moves (CAJA_ICON_CONTAINER (icon_container),
+ fm_directory_view_get_allow_moves (view));
+
+ /* kill any sound preview process that is ongoing */
+ preview_audio (icon_view, NULL, FALSE);
+
+ /* FIXME bugzilla.gnome.org 45060: Should use methods instead
+ * of hardcoding desktop knowledge in here.
+ */
+ if (FM_IS_DESKTOP_ICON_VIEW (view))
+ {
+ caja_connect_desktop_background_to_file_metadata (CAJA_ICON_CONTAINER (icon_container), file);
+ }
+ else
+ {
+ GdkDragAction default_action;
+
+ if (caja_window_info_get_window_type (fm_directory_view_get_caja_window (view)) == CAJA_WINDOW_NAVIGATION)
+ {
+ default_action = CAJA_DND_ACTION_SET_AS_GLOBAL_BACKGROUND;
+ }
+ else
+ {
+ default_action = CAJA_DND_ACTION_SET_AS_FOLDER_BACKGROUND;
+ }
+
+ caja_connect_background_to_file_metadata
+ (icon_container,
+ file,
+ default_action);
+ }
+
+
+ /* Set up the zoom level from the metadata. */
+ if (fm_directory_view_supports_zooming (FM_DIRECTORY_VIEW (icon_view)))
+ {
+ if (icon_view->details->compact)
+ {
+ level = caja_file_get_integer_metadata
+ (file,
+ CAJA_METADATA_KEY_COMPACT_VIEW_ZOOM_LEVEL,
+ get_default_zoom_level (icon_view));
+ }
+ else
+ {
+ level = caja_file_get_integer_metadata
+ (file,
+ CAJA_METADATA_KEY_ICON_VIEW_ZOOM_LEVEL,
+ get_default_zoom_level (icon_view));
+ }
+
+ fm_icon_view_set_zoom_level (icon_view, level, TRUE);
+ }
+
+ /* Set the sort mode.
+ * It's OK not to resort the icons because the
+ * container doesn't have any icons at this point.
+ */
+ sort_name = fm_icon_view_get_directory_sort_by (icon_view, file);
+ set_sort_criterion (icon_view, get_sort_criterion_by_metadata_text (sort_name));
+ g_free (sort_name);
+
+ /* Set the sort direction from the metadata. */
+ set_sort_reversed (icon_view, fm_icon_view_get_directory_sort_reversed (icon_view, file));
+
+ caja_icon_container_set_keep_aligned
+ (get_icon_container (icon_view),
+ fm_icon_view_get_directory_keep_aligned (icon_view, file));
+ caja_icon_container_set_tighter_layout
+ (get_icon_container (icon_view),
+ fm_icon_view_get_directory_tighter_layout (icon_view, file));
+
+ set_labels_beside_icons (icon_view);
+ set_columns_same_width (icon_view);
+
+ /* We must set auto-layout last, because it invokes the layout_changed
+ * callback, which works incorrectly if the other layout criteria are
+ * not already set up properly (see bug 6500, e.g.)
+ */
+ caja_icon_container_set_auto_layout
+ (get_icon_container (icon_view),
+ fm_icon_view_get_directory_auto_layout (icon_view, file));
+
+ /* e.g. keep aligned may have changed */
+ update_layout_menus (icon_view);
+}
+
+static void
+icon_view_notify_clipboard_info (CajaClipboardMonitor *monitor,
+ CajaClipboardInfo *info,
+ FMIconView *icon_view)
+{
+ GList *icon_data;
+
+ icon_data = NULL;
+ if (info && info->cut)
+ {
+ icon_data = info->files;
+ }
+
+ caja_icon_container_set_highlighted_for_clipboard (
+ get_icon_container (icon_view), icon_data);
+}
+
+static void
+fm_icon_view_end_loading (FMDirectoryView *view,
+ gboolean all_files_seen)
+{
+ FMIconView *icon_view;
+ GtkWidget *icon_container;
+ CajaClipboardMonitor *monitor;
+ CajaClipboardInfo *info;
+
+ icon_view = FM_ICON_VIEW (view);
+
+ icon_container = GTK_WIDGET (get_icon_container (icon_view));
+ caja_icon_container_end_loading (CAJA_ICON_CONTAINER (icon_container), all_files_seen);
+
+ monitor = caja_clipboard_monitor_get ();
+ info = caja_clipboard_monitor_get_clipboard_info (monitor);
+
+ icon_view_notify_clipboard_info (monitor, info, icon_view);
+}
+
+static CajaZoomLevel
+fm_icon_view_get_zoom_level (FMDirectoryView *view)
+{
+ g_return_val_if_fail (FM_IS_ICON_VIEW (view), CAJA_ZOOM_LEVEL_STANDARD);
+
+ return caja_icon_container_get_zoom_level (get_icon_container (FM_ICON_VIEW (view)));
+}
+
+static void
+fm_icon_view_set_zoom_level (FMIconView *view,
+ CajaZoomLevel new_level,
+ gboolean always_emit)
+{
+ CajaIconContainer *icon_container;
+
+ g_return_if_fail (FM_IS_ICON_VIEW (view));
+ g_return_if_fail (new_level >= CAJA_ZOOM_LEVEL_SMALLEST &&
+ new_level <= CAJA_ZOOM_LEVEL_LARGEST);
+
+ icon_container = get_icon_container (view);
+ if (caja_icon_container_get_zoom_level (icon_container) == new_level)
+ {
+ if (always_emit)
+ {
+ g_signal_emit_by_name (view, "zoom_level_changed");
+ }
+ return;
+ }
+
+ if (view->details->compact)
+ {
+ caja_file_set_integer_metadata
+ (fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (view)),
+ CAJA_METADATA_KEY_COMPACT_VIEW_ZOOM_LEVEL,
+ get_default_zoom_level (view),
+ new_level);
+ }
+ else
+ {
+ caja_file_set_integer_metadata
+ (fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (view)),
+ CAJA_METADATA_KEY_ICON_VIEW_ZOOM_LEVEL,
+ get_default_zoom_level (view),
+ new_level);
+ }
+
+ caja_icon_container_set_zoom_level (icon_container, new_level);
+
+ g_signal_emit_by_name (view, "zoom_level_changed");
+
+ if (fm_directory_view_get_active (FM_DIRECTORY_VIEW (view)))
+ {
+ fm_directory_view_update_menus (FM_DIRECTORY_VIEW (view));
+ }
+}
+
+static void
+fm_icon_view_bump_zoom_level (FMDirectoryView *view, int zoom_increment)
+{
+ FMIconView *icon_view;
+ CajaZoomLevel new_level;
+
+ g_return_if_fail (FM_IS_ICON_VIEW (view));
+
+ icon_view = FM_ICON_VIEW (view);
+ new_level = fm_icon_view_get_zoom_level (view) + zoom_increment;
+
+ if (new_level >= CAJA_ZOOM_LEVEL_SMALLEST &&
+ new_level <= CAJA_ZOOM_LEVEL_LARGEST)
+ {
+ fm_directory_view_zoom_to_level (view, new_level);
+ }
+}
+
+static void
+fm_icon_view_zoom_to_level (FMDirectoryView *view,
+ CajaZoomLevel zoom_level)
+{
+ FMIconView *icon_view;
+
+ g_assert (FM_IS_ICON_VIEW (view));
+
+ icon_view = FM_ICON_VIEW (view);
+ fm_icon_view_set_zoom_level (icon_view, zoom_level, FALSE);
+}
+
+static void
+fm_icon_view_restore_default_zoom_level (FMDirectoryView *view)
+{
+ FMIconView *icon_view;
+
+ g_return_if_fail (FM_IS_ICON_VIEW (view));
+
+ icon_view = FM_ICON_VIEW (view);
+ fm_directory_view_zoom_to_level
+ (view, get_default_zoom_level (icon_view));
+}
+
+static gboolean
+fm_icon_view_can_zoom_in (FMDirectoryView *view)
+{
+ g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE);
+
+ return fm_icon_view_get_zoom_level (view)
+ < CAJA_ZOOM_LEVEL_LARGEST;
+}
+
+static gboolean
+fm_icon_view_can_zoom_out (FMDirectoryView *view)
+{
+ g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE);
+
+ return fm_icon_view_get_zoom_level (view)
+ > CAJA_ZOOM_LEVEL_SMALLEST;
+}
+
+static GtkWidget *
+fm_icon_view_get_background_widget (FMDirectoryView *view)
+{
+ g_return_val_if_fail (FM_IS_ICON_VIEW (view), NULL);
+
+ return GTK_WIDGET (get_icon_container (FM_ICON_VIEW (view)));
+}
+
+static gboolean
+fm_icon_view_is_empty (FMDirectoryView *view)
+{
+ g_assert (FM_IS_ICON_VIEW (view));
+
+ return caja_icon_container_is_empty
+ (get_icon_container (FM_ICON_VIEW (view)));
+}
+
+static GList *
+fm_icon_view_get_selection (FMDirectoryView *view)
+{
+ GList *list;
+
+ g_return_val_if_fail (FM_IS_ICON_VIEW (view), NULL);
+
+ list = caja_icon_container_get_selection
+ (get_icon_container (FM_ICON_VIEW (view)));
+ caja_file_list_ref (list);
+ return list;
+}
+
+static void
+count_item (CajaIconData *icon_data,
+ gpointer callback_data)
+{
+ guint *count;
+
+ count = callback_data;
+ (*count)++;
+}
+
+static guint
+fm_icon_view_get_item_count (FMDirectoryView *view)
+{
+ guint count;
+
+ g_return_val_if_fail (FM_IS_ICON_VIEW (view), 0);
+
+ count = 0;
+
+ caja_icon_container_for_each
+ (get_icon_container (FM_ICON_VIEW (view)),
+ count_item, &count);
+
+ return count;
+}
+
+static void
+set_sort_criterion_by_sort_type (FMIconView *icon_view,
+ CajaFileSortType sort_type)
+{
+ const SortCriterion *sort;
+
+ g_assert (FM_IS_ICON_VIEW (icon_view));
+
+ sort = get_sort_criterion_by_sort_type (sort_type);
+ g_return_if_fail (sort != NULL);
+
+ if (sort == icon_view->details->sort
+ && fm_icon_view_using_auto_layout (icon_view))
+ {
+ return;
+ }
+
+ set_sort_criterion (icon_view, sort);
+ caja_icon_container_sort (get_icon_container (icon_view));
+ fm_icon_view_reveal_selection (FM_DIRECTORY_VIEW (icon_view));
+}
+
+
+static void
+action_reversed_order_callback (GtkAction *action,
+ gpointer user_data)
+{
+ FMIconView *icon_view;
+
+ icon_view = FM_ICON_VIEW (user_data);
+
+ if (set_sort_reversed (icon_view,
+ gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))))
+ {
+ caja_icon_container_sort (get_icon_container (icon_view));
+ fm_icon_view_reveal_selection (FM_DIRECTORY_VIEW (icon_view));
+ }
+}
+
+static void
+action_keep_aligned_callback (GtkAction *action,
+ gpointer user_data)
+{
+ FMIconView *icon_view;
+ CajaFile *file;
+ gboolean keep_aligned;
+
+ icon_view = FM_ICON_VIEW (user_data);
+
+ keep_aligned = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
+
+ file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (icon_view));
+ fm_icon_view_set_directory_keep_aligned (icon_view,
+ file,
+ keep_aligned);
+
+ caja_icon_container_set_keep_aligned (get_icon_container (icon_view),
+ keep_aligned);
+}
+
+static void
+switch_to_manual_layout (FMIconView *icon_view)
+{
+ if (!fm_icon_view_using_auto_layout (icon_view))
+ {
+ return;
+ }
+
+ icon_view->details->sort = &sort_criteria[0];
+
+ caja_icon_container_set_auto_layout
+ (get_icon_container (icon_view), FALSE);
+}
+
+static void
+layout_changed_callback (CajaIconContainer *container,
+ FMIconView *icon_view)
+{
+ CajaFile *file;
+
+ g_assert (FM_IS_ICON_VIEW (icon_view));
+ g_assert (container == get_icon_container (icon_view));
+
+ file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (icon_view));
+
+ if (file != NULL)
+ {
+ fm_icon_view_set_directory_auto_layout
+ (icon_view,
+ file,
+ fm_icon_view_using_auto_layout (icon_view));
+ fm_icon_view_set_directory_tighter_layout
+ (icon_view,
+ file,
+ fm_icon_view_using_tighter_layout (icon_view));
+ }
+
+ update_layout_menus (icon_view);
+}
+
+static gboolean
+fm_icon_view_can_rename_file (FMDirectoryView *view, CajaFile *file)
+{
+ if (!(fm_icon_view_get_zoom_level (view) > CAJA_ZOOM_LEVEL_SMALLEST))
+ {
+ return FALSE;
+ }
+
+ return FM_DIRECTORY_VIEW_CLASS(fm_icon_view_parent_class)->can_rename_file (view, file);
+}
+
+static void
+fm_icon_view_start_renaming_file (FMDirectoryView *view,
+ CajaFile *file,
+ gboolean select_all)
+{
+ /* call parent class to make sure the right icon is selected */
+ FM_DIRECTORY_VIEW_CLASS(fm_icon_view_parent_class)->start_renaming_file (view, file, select_all);
+
+ /* start renaming */
+ caja_icon_container_start_renaming_selected_item
+ (get_icon_container (FM_ICON_VIEW (view)), select_all);
+}
+
+static const GtkActionEntry icon_view_entries[] =
+{
+ /* name, stock id, label */ { "Arrange Items", NULL, N_("Arran_ge Items") },
+ /* name, stock id */ { "Stretch", NULL,
+ /* label, accelerator */ N_("Resize Icon..."), NULL,
+ /* tooltip */ N_("Make the selected icon resizable"),
+ G_CALLBACK (action_stretch_callback)
+ },
+ /* name, stock id */ { "Unstretch", NULL,
+ /* label, accelerator */ N_("Restore Icons' Original Si_zes"), NULL,
+ /* tooltip */ N_("Restore each selected icon to its original size"),
+ G_CALLBACK (action_unstretch_callback)
+ },
+ /* name, stock id */ { "Clean Up", NULL,
+ /* label, accelerator */ N_("_Organize by Name"), NULL,
+ /* tooltip */ N_("Reposition icons to better fit in the window and avoid overlapping"),
+ G_CALLBACK (action_clean_up_callback)
+ },
+};
+
+static const GtkToggleActionEntry icon_view_toggle_entries[] =
+{
+ /* name, stock id */ { "Tighter Layout", NULL,
+ /* label, accelerator */ N_("Compact _Layout"), NULL,
+ /* tooltip */ N_("Toggle using a tighter layout scheme"),
+ G_CALLBACK (action_tighter_layout_callback),
+ 0
+ },
+ /* name, stock id */ { "Reversed Order", NULL,
+ /* label, accelerator */ N_("Re_versed Order"), NULL,
+ /* tooltip */ N_("Display icons in the opposite order"),
+ G_CALLBACK (action_reversed_order_callback),
+ 0
+ },
+ /* name, stock id */ { "Keep Aligned", NULL,
+ /* label, accelerator */ N_("_Keep Aligned"), NULL,
+ /* tooltip */ N_("Keep icons lined up on a grid"),
+ G_CALLBACK (action_keep_aligned_callback),
+ 0
+ },
+};
+
+static const GtkRadioActionEntry arrange_radio_entries[] =
+{
+ {
+ "Manual Layout", NULL,
+ N_("_Manually"), NULL,
+ N_("Leave icons wherever they are dropped"),
+ CAJA_FILE_SORT_NONE
+ },
+ {
+ "Sort by Name", NULL,
+ N_("By _Name"), NULL,
+ N_("Keep icons sorted by name in rows"),
+ CAJA_FILE_SORT_BY_DISPLAY_NAME
+ },
+ {
+ "Sort by Size", NULL,
+ N_("By _Size"), NULL,
+ N_("Keep icons sorted by size in rows"),
+ CAJA_FILE_SORT_BY_SIZE
+ },
+ {
+ "Sort by Type", NULL,
+ N_("By _Type"), NULL,
+ N_("Keep icons sorted by type in rows"),
+ CAJA_FILE_SORT_BY_TYPE
+ },
+ {
+ "Sort by Modification Date", NULL,
+ N_("By Modification _Date"), NULL,
+ N_("Keep icons sorted by modification date in rows"),
+ CAJA_FILE_SORT_BY_MTIME
+ },
+ {
+ "Sort by Emblems", NULL,
+ N_("By _Emblems"), NULL,
+ N_("Keep icons sorted by emblems in rows"),
+ CAJA_FILE_SORT_BY_EMBLEMS
+ },
+ {
+ "Sort by Trash Time", NULL,
+ N_("By T_rash Time"), NULL,
+ N_("Keep icons sorted by trash time in rows"),
+ CAJA_FILE_SORT_BY_TRASHED_TIME
+ },
+};
+
+static void
+fm_icon_view_merge_menus (FMDirectoryView *view)
+{
+ FMIconView *icon_view;
+ GtkUIManager *ui_manager;
+ GtkActionGroup *action_group;
+ GtkAction *action;
+ const char *ui;
+
+ g_assert (FM_IS_ICON_VIEW (view));
+
+ FM_DIRECTORY_VIEW_CLASS (fm_icon_view_parent_class)->merge_menus (view);
+
+ icon_view = FM_ICON_VIEW (view);
+
+ ui_manager = fm_directory_view_get_ui_manager (FM_DIRECTORY_VIEW (icon_view));
+
+ action_group = gtk_action_group_new ("IconViewActions");
+ gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
+ icon_view->details->icon_action_group = action_group;
+ gtk_action_group_add_actions (action_group,
+ icon_view_entries, G_N_ELEMENTS (icon_view_entries),
+ icon_view);
+ gtk_action_group_add_toggle_actions (action_group,
+ icon_view_toggle_entries, G_N_ELEMENTS (icon_view_toggle_entries),
+ icon_view);
+ gtk_action_group_add_radio_actions (action_group,
+ arrange_radio_entries,
+ G_N_ELEMENTS (arrange_radio_entries),
+ -1,
+ G_CALLBACK (action_sort_radio_callback),
+ icon_view);
+
+ gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
+ g_object_unref (action_group); /* owned by ui manager */
+
+ ui = caja_ui_string_get ("caja-icon-view-ui.xml");
+ icon_view->details->icon_merge_id =
+ gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, NULL);
+
+ /* Do one-time state-setting here; context-dependent state-setting
+ * is done in update_menus.
+ */
+ if (!fm_icon_view_supports_auto_layout (icon_view))
+ {
+ action = gtk_action_group_get_action (action_group,
+ FM_ACTION_ARRANGE_ITEMS);
+ gtk_action_set_visible (action, FALSE);
+ }
+
+ if (fm_icon_view_supports_scaling (icon_view))
+ {
+ gtk_ui_manager_add_ui (ui_manager,
+ icon_view->details->icon_merge_id,
+ POPUP_PATH_ICON_APPEARANCE,
+ FM_ACTION_STRETCH,
+ FM_ACTION_STRETCH,
+ GTK_UI_MANAGER_MENUITEM,
+ FALSE);
+ gtk_ui_manager_add_ui (ui_manager,
+ icon_view->details->icon_merge_id,
+ POPUP_PATH_ICON_APPEARANCE,
+ FM_ACTION_UNSTRETCH,
+ FM_ACTION_UNSTRETCH,
+ GTK_UI_MANAGER_MENUITEM,
+ FALSE);
+ }
+
+ update_layout_menus (icon_view);
+}
+
+static void
+fm_icon_view_unmerge_menus (FMDirectoryView *view)
+{
+ FMIconView *icon_view;
+ GtkUIManager *ui_manager;
+
+ icon_view = FM_ICON_VIEW (view);
+
+ FM_DIRECTORY_VIEW_CLASS (fm_icon_view_parent_class)->unmerge_menus (view);
+
+ ui_manager = fm_directory_view_get_ui_manager (view);
+ if (ui_manager != NULL)
+ {
+ caja_ui_unmerge_ui (ui_manager,
+ &icon_view->details->icon_merge_id,
+ &icon_view->details->icon_action_group);
+ }
+}
+
+static void
+fm_icon_view_update_menus (FMDirectoryView *view)
+{
+ FMIconView *icon_view;
+ GList *selection;
+ int selection_count;
+ GtkAction *action;
+ CajaIconContainer *icon_container;
+ gboolean editable;
+
+ icon_view = FM_ICON_VIEW (view);
+
+ FM_DIRECTORY_VIEW_CLASS (fm_icon_view_parent_class)->update_menus(view);
+
+ selection = fm_directory_view_get_selection (view);
+ selection_count = g_list_length (selection);
+ icon_container = get_icon_container (icon_view);
+
+ action = gtk_action_group_get_action (icon_view->details->icon_action_group,
+ FM_ACTION_STRETCH);
+ gtk_action_set_sensitive (action,
+ selection_count == 1
+ && icon_container != NULL
+ && !caja_icon_container_has_stretch_handles (icon_container));
+
+ gtk_action_set_visible (action,
+ fm_icon_view_supports_scaling (icon_view));
+
+ action = gtk_action_group_get_action (icon_view->details->icon_action_group,
+ FM_ACTION_UNSTRETCH);
+ g_object_set (action, "label",
+ eel_g_list_more_than_one_item (selection)
+ ? _("Restore Icons' Original Si_zes")
+ : _("Restore Icon's Original Si_ze"),
+ NULL);
+ gtk_action_set_sensitive (action,
+ icon_container != NULL
+ && caja_icon_container_is_stretched (icon_container));
+
+ gtk_action_set_visible (action,
+ fm_icon_view_supports_scaling (icon_view));
+
+ caja_file_list_free (selection);
+
+ editable = fm_directory_view_is_editable (view);
+ action = gtk_action_group_get_action (icon_view->details->icon_action_group,
+ FM_ACTION_MANUAL_LAYOUT);
+ gtk_action_set_sensitive (action, editable);
+}
+
+static void
+fm_icon_view_reset_to_defaults (FMDirectoryView *view)
+{
+ CajaIconContainer *icon_container;
+ FMIconView *icon_view;
+
+ icon_view = FM_ICON_VIEW (view);
+ icon_container = get_icon_container (icon_view);
+
+ clear_sort_criterion (icon_view);
+ caja_icon_container_set_keep_aligned
+ (icon_container, get_default_directory_keep_aligned ());
+ caja_icon_container_set_tighter_layout
+ (icon_container, get_default_directory_tighter_layout ());
+
+ caja_icon_container_sort (icon_container);
+
+ /* Switch to manual layout of the default calls for it.
+ * This needs to happen last for the sort order menus
+ * to be in sync.
+ */
+ if (get_default_directory_manual_layout ())
+ {
+ switch_to_manual_layout (icon_view);
+ }
+
+ update_layout_menus (icon_view);
+
+ fm_icon_view_restore_default_zoom_level (view);
+}
+
+static void
+fm_icon_view_select_all (FMDirectoryView *view)
+{
+ CajaIconContainer *icon_container;
+
+ g_return_if_fail (FM_IS_ICON_VIEW (view));
+
+ icon_container = get_icon_container (FM_ICON_VIEW (view));
+ caja_icon_container_select_all (icon_container);
+}
+
+static void
+fm_icon_view_reveal_selection (FMDirectoryView *view)
+{
+ GList *selection;
+
+ g_return_if_fail (FM_IS_ICON_VIEW (view));
+
+ selection = fm_directory_view_get_selection (view);
+
+ /* Make sure at least one of the selected items is scrolled into view */
+ if (selection != NULL)
+ {
+ caja_icon_container_reveal
+ (get_icon_container (FM_ICON_VIEW (view)),
+ selection->data);
+ }
+
+ caja_file_list_free (selection);
+}
+
+static GArray *
+fm_icon_view_get_selected_icon_locations (FMDirectoryView *view)
+{
+ g_return_val_if_fail (FM_IS_ICON_VIEW (view), NULL);
+
+ return caja_icon_container_get_selected_icon_locations
+ (get_icon_container (FM_ICON_VIEW (view)));
+}
+
+
+static void
+fm_icon_view_set_selection (FMDirectoryView *view, GList *selection)
+{
+ g_return_if_fail (FM_IS_ICON_VIEW (view));
+
+ caja_icon_container_set_selection
+ (get_icon_container (FM_ICON_VIEW (view)), selection);
+}
+
+static void
+fm_icon_view_invert_selection (FMDirectoryView *view)
+{
+ g_return_if_fail (FM_IS_ICON_VIEW (view));
+
+ caja_icon_container_invert_selection
+ (get_icon_container (FM_ICON_VIEW (view)));
+}
+
+static gboolean
+fm_icon_view_using_manual_layout (FMDirectoryView *view)
+{
+ g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE);
+
+ return !fm_icon_view_using_auto_layout (FM_ICON_VIEW (view));
+}
+
+static void
+fm_icon_view_widget_to_file_operation_position (FMDirectoryView *view,
+ GdkPoint *position)
+{
+ g_assert (FM_IS_ICON_VIEW (view));
+
+ caja_icon_container_widget_to_file_operation_position
+ (get_icon_container (FM_ICON_VIEW (view)), position);
+}
+
+static void
+icon_container_activate_callback (CajaIconContainer *container,
+ GList *file_list,
+ FMIconView *icon_view)
+{
+ g_assert (FM_IS_ICON_VIEW (icon_view));
+ g_assert (container == get_icon_container (icon_view));
+
+ fm_directory_view_activate_files (FM_DIRECTORY_VIEW (icon_view),
+ file_list,
+ CAJA_WINDOW_OPEN_ACCORDING_TO_MODE, 0,
+ TRUE);
+}
+
+static void
+icon_container_activate_alternate_callback (CajaIconContainer *container,
+ GList *file_list,
+ FMIconView *icon_view)
+{
+ GdkEvent *event;
+ GdkEventButton *button_event;
+ GdkEventKey *key_event;
+ gboolean open_in_tab;
+ CajaWindowInfo *window_info;
+ CajaWindowOpenFlags flags;
+
+ g_assert (FM_IS_ICON_VIEW (icon_view));
+ g_assert (container == get_icon_container (icon_view));
+
+ open_in_tab = FALSE;
+
+ window_info = fm_directory_view_get_caja_window (FM_DIRECTORY_VIEW (icon_view));
+
+ if (caja_window_info_get_window_type (window_info) == CAJA_WINDOW_NAVIGATION)
+ {
+ event = gtk_get_current_event ();
+ if (event->type == GDK_BUTTON_PRESS ||
+ event->type == GDK_BUTTON_RELEASE ||
+ event->type == GDK_2BUTTON_PRESS ||
+ event->type == GDK_3BUTTON_PRESS)
+ {
+ button_event = (GdkEventButton *) event;
+ open_in_tab = (button_event->state & GDK_SHIFT_MASK) == 0;
+ }
+ else if (event->type == GDK_KEY_PRESS ||
+ event->type == GDK_KEY_RELEASE)
+ {
+ key_event = (GdkEventKey *) event;
+ open_in_tab = !((key_event->state & GDK_SHIFT_MASK) != 0 &&
+ (key_event->state & GDK_CONTROL_MASK) != 0);
+ }
+ else
+ {
+ open_in_tab = TRUE;
+ }
+ }
+
+ flags = CAJA_WINDOW_OPEN_FLAG_CLOSE_BEHIND;
+ if (open_in_tab)
+ {
+ flags |= CAJA_WINDOW_OPEN_FLAG_NEW_TAB;
+ }
+ else
+ {
+ flags |= CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW;
+ }
+
+ fm_directory_view_activate_files (FM_DIRECTORY_VIEW (icon_view),
+ file_list,
+ CAJA_WINDOW_OPEN_ACCORDING_TO_MODE,
+ flags,
+ TRUE);
+}
+
+static void
+band_select_started_callback (CajaIconContainer *container,
+ FMIconView *icon_view)
+{
+ g_assert (FM_IS_ICON_VIEW (icon_view));
+ g_assert (container == get_icon_container (icon_view));
+
+ fm_directory_view_start_batching_selection_changes (FM_DIRECTORY_VIEW (icon_view));
+}
+
+static void
+band_select_ended_callback (CajaIconContainer *container,
+ FMIconView *icon_view)
+{
+ g_assert (FM_IS_ICON_VIEW (icon_view));
+ g_assert (container == get_icon_container (icon_view));
+
+ fm_directory_view_stop_batching_selection_changes (FM_DIRECTORY_VIEW (icon_view));
+}
+
+/* handle the preview signal by inspecting the mime type. For now, we only preview local sound files. */
+
+static char **
+get_preview_argv (char *uri)
+{
+ char *command;
+ char **argv;
+ int i;
+
+ command = g_find_program_in_path ("totem-audio-preview");
+ if (command)
+ {
+ argv = g_new (char *, 3);
+ argv[0] = command;
+ argv[1] = g_strdup (uri);
+ argv[2] = NULL;
+
+ return argv;
+ }
+
+ command = g_find_program_in_path ("gst-launch-0.10");
+ if (command)
+ {
+ argv = g_new (char *, 10);
+ i = 0;
+ argv[i++] = command;
+ argv[i++] = g_strdup ("playbin");
+ argv[i++] = g_strconcat ("uri=", uri, NULL);
+ /* do not display videos */
+ argv[i++] = g_strdup ("current-video=-1");
+ argv[i++] = NULL;
+ return argv;
+ }
+
+ return NULL;
+}
+
+static void
+audio_child_died (GPid pid,
+ gint status,
+ gpointer data)
+{
+ FMIconView *icon_view;
+
+ icon_view = FM_ICON_VIEW (data);
+
+ icon_view->details->audio_preview_child_watch = 0;
+ icon_view->details->audio_preview_child_pid = 0;
+}
+
+/* here's the timer task that actually plays the file using mpg123, ogg123 or play. */
+/* FIXME bugzilla.gnome.org 41258: we should get the application from our mime-type stuff */
+static gboolean
+play_file (gpointer callback_data)
+{
+ CajaFile *file;
+ FMIconView *icon_view;
+ GPid child_pid;
+ char **argv;
+ GError *error;
+ char *uri;
+
+ icon_view = FM_ICON_VIEW (callback_data);
+
+ /* Stop timeout */
+ icon_view->details->audio_preview_timeout = 0;
+
+ file = icon_view->details->audio_preview_file;
+ uri = caja_file_get_uri (file);
+ argv = get_preview_argv (uri);
+ g_free (uri);
+ if (argv == NULL)
+ {
+ return FALSE;
+ }
+
+ error = NULL;
+ if (!g_spawn_async_with_pipes (NULL,
+ argv,
+ NULL,
+ G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL,
+ NULL,
+ NULL /* user_data */,
+ &child_pid,
+ NULL, NULL, NULL,
+ &error))
+ {
+ g_strfreev (argv);
+ g_warning ("Error spawning sound preview: %s\n", error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+ g_strfreev (argv);
+
+ icon_view->details->audio_preview_child_watch =
+ g_child_watch_add (child_pid,
+ audio_child_died, NULL);
+ icon_view->details->audio_preview_child_pid = child_pid;
+
+ return FALSE;
+}
+
+/* FIXME bugzilla.gnome.org 42530: Hardcoding this here sucks. We should be using components
+ * for open ended things like this.
+ */
+
+/* this routine is invoked from the preview signal handler to preview a sound file. We
+ want to wait a suitable delay until we actually do it, so set up a timer task to actually
+ start playing. If we move out before the task files, we remove it. */
+
+static void
+preview_audio (FMIconView *icon_view, CajaFile *file, gboolean start_flag)
+{
+ /* Stop current audio playback */
+ if (icon_view->details->audio_preview_child_pid != 0)
+ {
+ kill (icon_view->details->audio_preview_child_pid, SIGTERM);
+ g_source_remove (icon_view->details->audio_preview_child_watch);
+ waitpid (icon_view->details->audio_preview_child_pid, NULL, 0);
+ icon_view->details->audio_preview_child_pid = 0;
+ }
+
+ if (icon_view->details->audio_preview_timeout != 0)
+ {
+ g_source_remove (icon_view->details->audio_preview_timeout);
+ icon_view->details->audio_preview_timeout = 0;
+ }
+
+ if (start_flag)
+ {
+ icon_view->details->audio_preview_file = file;
+ icon_view->details->audio_preview_timeout = g_timeout_add_seconds (1, play_file, icon_view);
+ }
+}
+
+static gboolean
+sound_preview_type_supported (CajaFile *file)
+{
+ char *mime_type;
+ guint i;
+
+ mime_type = caja_file_get_mime_type (file);
+ if (mime_type == NULL)
+ {
+ return FALSE;
+ }
+ for (i = 0; i < G_N_ELEMENTS (audio_mime_types); i++)
+ {
+ if (g_content_type_is_a (mime_type, audio_mime_types[i]))
+ {
+ g_free (mime_type);
+ return TRUE;
+ }
+ }
+
+ g_free (mime_type);
+ return FALSE;
+}
+
+
+static gboolean
+should_preview_sound (CajaFile *file)
+{
+ GFile *location;
+ GFilesystemPreviewType use_preview;
+
+ use_preview = caja_file_get_filesystem_use_preview (file);
+
+ location = caja_file_get_location (file);
+ if (g_file_has_uri_scheme (location, "burn"))
+ {
+ g_object_unref (location);
+ return FALSE;
+ }
+ g_object_unref (location);
+
+ /* Check user performance preference */
+ if (preview_sound_auto_value == CAJA_SPEED_TRADEOFF_NEVER)
+ {
+ return FALSE;
+ }
+
+ if (preview_sound_auto_value == CAJA_SPEED_TRADEOFF_ALWAYS)
+ {
+ if (use_preview == G_FILESYSTEM_PREVIEW_TYPE_NEVER)
+ {
+ return FALSE;
+ }
+ else
+ {
+ return TRUE;
+ }
+ }
+
+ if (use_preview == G_FILESYSTEM_PREVIEW_TYPE_NEVER)
+ {
+ /* file system says to never preview anything */
+ return FALSE;
+ }
+ else if (use_preview == G_FILESYSTEM_PREVIEW_TYPE_IF_LOCAL)
+ {
+ /* file system says we should treat file as if it's local */
+ return TRUE;
+ }
+ else
+ {
+ /* only local files */
+ return caja_file_is_local (file);
+ }
+}
+
+static int
+icon_container_preview_callback (CajaIconContainer *container,
+ CajaFile *file,
+ gboolean start_flag,
+ FMIconView *icon_view)
+{
+ int result;
+ char *file_name, *message;
+
+ result = 0;
+
+ /* preview files based on the mime_type. */
+ /* at first, we just handle sounds */
+ if (should_preview_sound (file))
+ {
+ if (sound_preview_type_supported (file))
+ {
+ result = 1;
+ preview_audio (icon_view, file, start_flag);
+ }
+ }
+
+ /* Display file name in status area at low zoom levels, since
+ * the name is not displayed or hard to read in the icon view.
+ */
+ if (fm_icon_view_get_zoom_level (FM_DIRECTORY_VIEW (icon_view)) <= CAJA_ZOOM_LEVEL_SMALLER)
+ {
+ if (start_flag)
+ {
+ file_name = caja_file_get_display_name (file);
+ message = g_strdup_printf (_("pointing at \"%s\""), file_name);
+ g_free (file_name);
+ caja_window_slot_info_set_status
+ (fm_directory_view_get_caja_window_slot (FM_DIRECTORY_VIEW (icon_view)),
+ message);
+ g_free (message);
+ }
+ else
+ {
+ fm_directory_view_display_selection_info (FM_DIRECTORY_VIEW(icon_view));
+ }
+ }
+
+ return result;
+}
+
+static void
+renaming_icon_callback (CajaIconContainer *container,
+ GtkWidget *widget,
+ gpointer callback_data)
+{
+ FMDirectoryView *directory_view;
+
+ directory_view = FM_DIRECTORY_VIEW (callback_data);
+ caja_clipboard_set_up_editable
+ (GTK_EDITABLE (widget),
+ fm_directory_view_get_ui_manager (directory_view),
+ FALSE);
+}
+
+int
+fm_icon_view_compare_files (FMIconView *icon_view,
+ CajaFile *a,
+ CajaFile *b)
+{
+ return caja_file_compare_for_sort
+ (a, b, icon_view->details->sort->sort_type,
+ /* Use type-unsafe cast for performance */
+ fm_directory_view_should_sort_directories_first ((FMDirectoryView *)icon_view),
+ icon_view->details->sort_reversed);
+}
+
+static int
+compare_files (FMDirectoryView *icon_view,
+ CajaFile *a,
+ CajaFile *b)
+{
+ return fm_icon_view_compare_files ((FMIconView *)icon_view, a, b);
+}
+
+
+void
+fm_icon_view_filter_by_screen (FMIconView *icon_view,
+ gboolean filter)
+{
+ icon_view->details->filter_by_screen = filter;
+ icon_view->details->num_screens = gdk_display_get_n_screens (gtk_widget_get_display (GTK_WIDGET (icon_view)));
+}
+
+static void
+fm_icon_view_screen_changed (GtkWidget *widget,
+ GdkScreen *previous_screen)
+{
+ FMDirectoryView *view;
+ GList *files, *l;
+ CajaFile *file;
+ CajaDirectory *directory;
+ CajaIconContainer *icon_container;
+
+ if (GTK_WIDGET_CLASS (fm_icon_view_parent_class)->screen_changed)
+ {
+ GTK_WIDGET_CLASS (fm_icon_view_parent_class)->screen_changed (widget, previous_screen);
+ }
+
+ view = FM_DIRECTORY_VIEW (widget);
+ if (FM_ICON_VIEW (view)->details->filter_by_screen)
+ {
+ icon_container = get_icon_container (FM_ICON_VIEW (view));
+
+ directory = fm_directory_view_get_model (view);
+ files = caja_directory_get_file_list (directory);
+
+ for (l = files; l != NULL; l = l->next)
+ {
+ file = l->data;
+
+ if (!should_show_file_on_screen (view, file))
+ {
+ fm_icon_view_remove_file (view, file, directory);
+ }
+ else
+ {
+ if (caja_icon_container_add (icon_container,
+ CAJA_ICON_CONTAINER_ICON_DATA (file)))
+ {
+ caja_file_ref (file);
+ }
+ }
+ }
+
+ caja_file_list_unref (files);
+ g_list_free (files);
+ }
+}
+
+static gboolean
+fm_icon_view_scroll_event (GtkWidget *widget,
+ GdkEventScroll *scroll_event)
+{
+ FMIconView *icon_view;
+ GdkEvent *event_copy;
+ GdkEventScroll *scroll_event_copy;
+ gboolean ret;
+
+ icon_view = FM_ICON_VIEW (widget);
+
+ if (icon_view->details->compact &&
+ (scroll_event->direction == GDK_SCROLL_UP ||
+ scroll_event->direction == GDK_SCROLL_DOWN))
+ {
+ ret = fm_directory_view_handle_scroll_event (FM_DIRECTORY_VIEW (icon_view), scroll_event);
+ if (!ret)
+ {
+ /* in column-wise layout, re-emit vertical mouse scroll events as horizontal ones,
+ * if they don't bump zoom */
+ event_copy = gdk_event_copy ((GdkEvent *) scroll_event);
+
+ scroll_event_copy = (GdkEventScroll *) event_copy;
+ if (scroll_event_copy->direction == GDK_SCROLL_UP)
+ {
+ scroll_event_copy->direction = GDK_SCROLL_LEFT;
+ }
+ else
+ {
+ scroll_event_copy->direction = GDK_SCROLL_RIGHT;
+ }
+
+ ret = gtk_widget_event (widget, event_copy);
+ gdk_event_free (event_copy);
+ }
+
+ return ret;
+ }
+
+ return GTK_WIDGET_CLASS (fm_icon_view_parent_class)->scroll_event (widget, scroll_event);
+}
+
+static void
+selection_changed_callback (CajaIconContainer *container,
+ FMIconView *icon_view)
+{
+ g_assert (FM_IS_ICON_VIEW (icon_view));
+ g_assert (container == get_icon_container (icon_view));
+
+ fm_directory_view_notify_selection_changed (FM_DIRECTORY_VIEW (icon_view));
+}
+
+static void
+icon_container_context_click_selection_callback (CajaIconContainer *container,
+ GdkEventButton *event,
+ FMIconView *icon_view)
+{
+ g_assert (CAJA_IS_ICON_CONTAINER (container));
+ g_assert (FM_IS_ICON_VIEW (icon_view));
+
+ fm_directory_view_pop_up_selection_context_menu
+ (FM_DIRECTORY_VIEW (icon_view), event);
+}
+
+static void
+icon_container_context_click_background_callback (CajaIconContainer *container,
+ GdkEventButton *event,
+ FMIconView *icon_view)
+{
+ g_assert (CAJA_IS_ICON_CONTAINER (container));
+ g_assert (FM_IS_ICON_VIEW (icon_view));
+
+ fm_directory_view_pop_up_background_context_menu
+ (FM_DIRECTORY_VIEW (icon_view), event);
+}
+
+static gboolean
+fm_icon_view_react_to_icon_change_idle_callback (gpointer data)
+{
+ FMIconView *icon_view;
+
+ g_assert (FM_IS_ICON_VIEW (data));
+
+ icon_view = FM_ICON_VIEW (data);
+ icon_view->details->react_to_icon_change_idle_id = 0;
+
+ /* Rebuild the menus since some of them (e.g. Restore Stretched Icons)
+ * may be different now.
+ */
+ fm_directory_view_update_menus (FM_DIRECTORY_VIEW (icon_view));
+
+ /* Don't call this again (unless rescheduled) */
+ return FALSE;
+}
+
+static void
+icon_position_changed_callback (CajaIconContainer *container,
+ CajaFile *file,
+ const CajaIconPosition *position,
+ FMIconView *icon_view)
+{
+ char *position_string;
+ char scale_string[G_ASCII_DTOSTR_BUF_SIZE];
+
+ g_assert (FM_IS_ICON_VIEW (icon_view));
+ g_assert (container == get_icon_container (icon_view));
+ g_assert (CAJA_IS_FILE (file));
+
+ /* Schedule updating menus for the next idle. Doing it directly here
+ * noticeably slows down icon stretching. The other work here to
+ * store the icon position and scale does not seem to noticeably
+ * slow down icon stretching. It would be trickier to move to an
+ * idle call, because we'd have to keep track of potentially multiple
+ * sets of file/geometry info.
+ */
+ if (fm_directory_view_get_active (FM_DIRECTORY_VIEW (icon_view)) &&
+ icon_view->details->react_to_icon_change_idle_id == 0)
+ {
+ icon_view->details->react_to_icon_change_idle_id
+ = g_idle_add (fm_icon_view_react_to_icon_change_idle_callback,
+ icon_view);
+ }
+
+ /* Store the new position of the icon in the metadata. */
+ if (!fm_icon_view_using_auto_layout (icon_view))
+ {
+ position_string = g_strdup_printf
+ ("%d,%d", position->x, position->y);
+ caja_file_set_metadata
+ (file, CAJA_METADATA_KEY_ICON_POSITION,
+ NULL, position_string);
+ g_free (position_string);
+ }
+
+
+ g_ascii_dtostr (scale_string, sizeof (scale_string), position->scale);
+ caja_file_set_metadata
+ (file, CAJA_METADATA_KEY_ICON_SCALE,
+ "1.0", scale_string);
+}
+
+/* Attempt to change the filename to the new text. Notify user if operation fails. */
+static void
+fm_icon_view_icon_text_changed_callback (CajaIconContainer *container,
+ CajaFile *file,
+ char *new_name,
+ FMIconView *icon_view)
+{
+ g_assert (CAJA_IS_FILE (file));
+ g_assert (new_name != NULL);
+
+ /* Don't allow a rename with an empty string. Revert to original
+ * without notifying the user.
+ */
+ if (new_name[0] == '\0')
+ {
+ return;
+ }
+ fm_rename_file (file, new_name, NULL, NULL);
+}
+
+static char *
+get_icon_uri_callback (CajaIconContainer *container,
+ CajaFile *file,
+ FMIconView *icon_view)
+{
+ g_assert (CAJA_IS_ICON_CONTAINER (container));
+ g_assert (CAJA_IS_FILE (file));
+ g_assert (FM_IS_ICON_VIEW (icon_view));
+
+ return caja_file_get_uri (file);
+}
+
+static char *
+get_icon_drop_target_uri_callback (CajaIconContainer *container,
+ CajaFile *file,
+ FMIconView *icon_view)
+{
+ g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (container), NULL);
+ g_return_val_if_fail (CAJA_IS_FILE (file), NULL);
+ g_return_val_if_fail (FM_IS_ICON_VIEW (icon_view), NULL);
+
+ return caja_file_get_drop_target_uri (file);
+}
+
+/* Preferences changed callbacks */
+static void
+fm_icon_view_text_attribute_names_changed (FMDirectoryView *directory_view)
+{
+ g_assert (FM_IS_ICON_VIEW (directory_view));
+
+ caja_icon_container_request_update_all (get_icon_container (FM_ICON_VIEW (directory_view)));
+}
+
+static void
+fm_icon_view_embedded_text_policy_changed (FMDirectoryView *directory_view)
+{
+ g_assert (FM_IS_ICON_VIEW (directory_view));
+
+ caja_icon_container_request_update_all (get_icon_container (FM_ICON_VIEW (directory_view)));
+}
+
+static void
+fm_icon_view_image_display_policy_changed (FMDirectoryView *directory_view)
+{
+ g_assert (FM_IS_ICON_VIEW (directory_view));
+
+ caja_icon_container_request_update_all (get_icon_container (FM_ICON_VIEW (directory_view)));
+}
+
+static void
+fm_icon_view_click_policy_changed (FMDirectoryView *directory_view)
+{
+ g_assert (FM_IS_ICON_VIEW (directory_view));
+
+ fm_icon_view_update_click_mode (FM_ICON_VIEW (directory_view));
+}
+
+static void
+fm_icon_view_emblems_changed (FMDirectoryView *directory_view)
+{
+ g_assert (FM_IS_ICON_VIEW (directory_view));
+
+ caja_icon_container_request_update_all (get_icon_container (FM_ICON_VIEW (directory_view)));
+}
+
+static void
+default_sort_order_changed_callback (gpointer callback_data)
+{
+ FMIconView *icon_view;
+ CajaFile *file;
+ char *sort_name;
+ CajaIconContainer *icon_container;
+
+ g_return_if_fail (FM_IS_ICON_VIEW (callback_data));
+
+ icon_view = FM_ICON_VIEW (callback_data);
+
+ file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (icon_view));
+ sort_name = fm_icon_view_get_directory_sort_by (icon_view, file);
+ set_sort_criterion (icon_view, get_sort_criterion_by_metadata_text (sort_name));
+ g_free (sort_name);
+
+ icon_container = get_icon_container (icon_view);
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (icon_container));
+
+ caja_icon_container_request_update_all (icon_container);
+}
+
+static void
+default_sort_in_reverse_order_changed_callback (gpointer callback_data)
+{
+ FMIconView *icon_view;
+ CajaFile *file;
+ CajaIconContainer *icon_container;
+
+ g_return_if_fail (FM_IS_ICON_VIEW (callback_data));
+
+ icon_view = FM_ICON_VIEW (callback_data);
+
+ file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (icon_view));
+ set_sort_reversed (icon_view, fm_icon_view_get_directory_sort_reversed (icon_view, file));
+ icon_container = get_icon_container (icon_view);
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (icon_container));
+
+ caja_icon_container_request_update_all (icon_container);
+}
+
+static void
+default_use_tighter_layout_changed_callback (gpointer callback_data)
+{
+ FMIconView *icon_view;
+ CajaFile *file;
+ CajaIconContainer *icon_container;
+
+ g_return_if_fail (FM_IS_ICON_VIEW (callback_data));
+
+ icon_view = FM_ICON_VIEW (callback_data);
+
+ file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (icon_view));
+ icon_container = get_icon_container (icon_view);
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (icon_container));
+
+ caja_icon_container_set_tighter_layout (
+ icon_container,
+ fm_icon_view_get_directory_tighter_layout (icon_view, file));
+
+ caja_icon_container_request_update_all (icon_container);
+}
+
+static void
+default_use_manual_layout_changed_callback (gpointer callback_data)
+{
+ FMIconView *icon_view;
+ CajaFile *file;
+ CajaIconContainer *icon_container;
+
+ g_return_if_fail (FM_IS_ICON_VIEW (callback_data));
+
+ icon_view = FM_ICON_VIEW (callback_data);
+
+ file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (icon_view));
+ icon_container = get_icon_container (icon_view);
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (icon_container));
+
+ caja_icon_container_set_auto_layout (
+ icon_container,
+ fm_icon_view_get_directory_auto_layout (icon_view, file));
+
+ caja_icon_container_request_update_all (icon_container);
+}
+
+static void
+default_zoom_level_changed_callback (gpointer callback_data)
+{
+ FMIconView *icon_view;
+ CajaFile *file;
+ int level;
+
+ g_return_if_fail (FM_IS_ICON_VIEW (callback_data));
+
+ icon_view = FM_ICON_VIEW (callback_data);
+
+ if (fm_directory_view_supports_zooming (FM_DIRECTORY_VIEW (icon_view)))
+ {
+ file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (icon_view));
+
+ if (fm_icon_view_is_compact (icon_view))
+ {
+ level = caja_file_get_integer_metadata (file,
+ CAJA_METADATA_KEY_COMPACT_VIEW_ZOOM_LEVEL,
+ get_default_zoom_level (icon_view));
+ }
+ else
+ {
+ level = caja_file_get_integer_metadata (file,
+ CAJA_METADATA_KEY_ICON_VIEW_ZOOM_LEVEL,
+ get_default_zoom_level (icon_view));
+ }
+ fm_directory_view_zoom_to_level (FM_DIRECTORY_VIEW (icon_view), level);
+ }
+}
+
+static void
+labels_beside_icons_changed_callback (gpointer callback_data)
+{
+ FMIconView *icon_view;
+
+ g_return_if_fail (FM_IS_ICON_VIEW (callback_data));
+
+ icon_view = FM_ICON_VIEW (callback_data);
+
+ set_labels_beside_icons (icon_view);
+}
+
+static void
+all_columns_same_width_changed_callback (gpointer callback_data)
+{
+ FMIconView *icon_view;
+
+ g_assert (FM_IS_ICON_VIEW (callback_data));
+
+ icon_view = FM_ICON_VIEW (callback_data);
+
+ set_columns_same_width (icon_view);
+}
+
+
+static void
+fm_icon_view_sort_directories_first_changed (FMDirectoryView *directory_view)
+{
+ FMIconView *icon_view;
+
+ icon_view = FM_ICON_VIEW (directory_view);
+
+ if (fm_icon_view_using_auto_layout (icon_view))
+ {
+ caja_icon_container_sort
+ (get_icon_container (icon_view));
+ }
+}
+
+/* GtkObject methods. */
+
+static gboolean
+icon_view_can_accept_item (CajaIconContainer *container,
+ CajaFile *target_item,
+ const char *item_uri,
+ FMDirectoryView *view)
+{
+ return fm_directory_view_can_accept_item (target_item, item_uri, view);
+}
+
+static char *
+icon_view_get_container_uri (CajaIconContainer *container,
+ FMDirectoryView *view)
+{
+ return fm_directory_view_get_uri (view);
+}
+
+static void
+icon_view_move_copy_items (CajaIconContainer *container,
+ const GList *item_uris,
+ GArray *relative_item_points,
+ const char *target_dir,
+ int copy_action,
+ int x, int y,
+ FMDirectoryView *view)
+{
+ caja_clipboard_clear_if_colliding_uris (GTK_WIDGET (view),
+ item_uris,
+ fm_directory_view_get_copied_files_atom (view));
+ fm_directory_view_move_copy_items (item_uris, relative_item_points, target_dir,
+ copy_action, x, y, view);
+}
+
+static void
+fm_icon_view_update_click_mode (FMIconView *icon_view)
+{
+ CajaIconContainer *icon_container;
+ int click_mode;
+
+ icon_container = get_icon_container (icon_view);
+ g_assert (icon_container != NULL);
+
+ click_mode = eel_preferences_get_enum (CAJA_PREFERENCES_CLICK_POLICY);
+
+ caja_icon_container_set_single_click_mode (icon_container,
+ click_mode == CAJA_CLICK_POLICY_SINGLE);
+}
+
+static gboolean
+get_stored_layout_timestamp (CajaIconContainer *container,
+ CajaIconData *icon_data,
+ time_t *timestamp,
+ FMIconView *view)
+{
+ CajaFile *file;
+ CajaDirectory *directory;
+
+ if (icon_data == NULL)
+ {
+ directory = fm_directory_view_get_model (FM_DIRECTORY_VIEW (view));
+ if (directory == NULL)
+ {
+ return FALSE;
+ }
+
+ file = caja_directory_get_corresponding_file (directory);
+ *timestamp = caja_file_get_time_metadata (file,
+ CAJA_METADATA_KEY_ICON_VIEW_LAYOUT_TIMESTAMP);
+ caja_file_unref (file);
+ }
+ else
+ {
+ *timestamp = caja_file_get_time_metadata (CAJA_FILE (icon_data),
+ CAJA_METADATA_KEY_ICON_POSITION_TIMESTAMP);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+store_layout_timestamp (CajaIconContainer *container,
+ CajaIconData *icon_data,
+ const time_t *timestamp,
+ FMIconView *view)
+{
+ CajaFile *file;
+ CajaDirectory *directory;
+
+ if (icon_data == NULL)
+ {
+ directory = fm_directory_view_get_model (FM_DIRECTORY_VIEW (view));
+ if (directory == NULL)
+ {
+ return FALSE;
+ }
+
+ file = caja_directory_get_corresponding_file (directory);
+ caja_file_set_time_metadata (file,
+ CAJA_METADATA_KEY_ICON_VIEW_LAYOUT_TIMESTAMP,
+ (time_t) *timestamp);
+ caja_file_unref (file);
+ }
+ else
+ {
+ caja_file_set_time_metadata (CAJA_FILE (icon_data),
+ CAJA_METADATA_KEY_ICON_POSITION_TIMESTAMP,
+ (time_t) *timestamp);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+focus_in_event_callback (GtkWidget *widget, GdkEventFocus *event, gpointer user_data)
+{
+ CajaWindowSlotInfo *slot_info;
+ FMIconView *icon_view = FM_ICON_VIEW (user_data);
+
+ /* make the corresponding slot (and the pane that contains it) active */
+ slot_info = fm_directory_view_get_caja_window_slot (FM_DIRECTORY_VIEW (icon_view));
+ caja_window_slot_info_make_hosting_pane_active (slot_info);
+
+ return FALSE;
+}
+
+static CajaIconContainer *
+create_icon_container (FMIconView *icon_view)
+{
+ CajaIconContainer *icon_container;
+
+ icon_container = fm_icon_container_new (icon_view);
+
+ gtk_widget_set_can_focus (GTK_WIDGET (icon_container), TRUE);
+
+ g_signal_connect_object (icon_container, "focus_in_event",
+ G_CALLBACK (focus_in_event_callback), icon_view, 0);
+ g_signal_connect_object (icon_container, "activate",
+ G_CALLBACK (icon_container_activate_callback), icon_view, 0);
+ g_signal_connect_object (icon_container, "activate_alternate",
+ G_CALLBACK (icon_container_activate_alternate_callback), icon_view, 0);
+ g_signal_connect_object (icon_container, "band_select_started",
+ G_CALLBACK (band_select_started_callback), icon_view, 0);
+ g_signal_connect_object (icon_container, "band_select_ended",
+ G_CALLBACK (band_select_ended_callback), icon_view, 0);
+ g_signal_connect_object (icon_container, "context_click_selection",
+ G_CALLBACK (icon_container_context_click_selection_callback), icon_view, 0);
+ g_signal_connect_object (icon_container, "context_click_background",
+ G_CALLBACK (icon_container_context_click_background_callback), icon_view, 0);
+ g_signal_connect_object (icon_container, "icon_position_changed",
+ G_CALLBACK (icon_position_changed_callback), icon_view, 0);
+ g_signal_connect_object (icon_container, "icon_text_changed",
+ G_CALLBACK (fm_icon_view_icon_text_changed_callback), icon_view, 0);
+ g_signal_connect_object (icon_container, "selection_changed",
+ G_CALLBACK (selection_changed_callback), icon_view, 0);
+ /* FIXME: many of these should move into fm-icon-container as virtual methods */
+ g_signal_connect_object (icon_container, "get_icon_uri",
+ G_CALLBACK (get_icon_uri_callback), icon_view, 0);
+ g_signal_connect_object (icon_container, "get_icon_drop_target_uri",
+ G_CALLBACK (get_icon_drop_target_uri_callback), icon_view, 0);
+ g_signal_connect_object (icon_container, "move_copy_items",
+ G_CALLBACK (icon_view_move_copy_items), icon_view, 0);
+ g_signal_connect_object (icon_container, "get_container_uri",
+ G_CALLBACK (icon_view_get_container_uri), icon_view, 0);
+ g_signal_connect_object (icon_container, "can_accept_item",
+ G_CALLBACK (icon_view_can_accept_item), icon_view, 0);
+ g_signal_connect_object (icon_container, "get_stored_icon_position",
+ G_CALLBACK (get_stored_icon_position_callback), icon_view, 0);
+ g_signal_connect_object (icon_container, "layout_changed",
+ G_CALLBACK (layout_changed_callback), icon_view, 0);
+ g_signal_connect_object (icon_container, "preview",
+ G_CALLBACK (icon_container_preview_callback), icon_view, 0);
+ g_signal_connect_object (icon_container, "renaming_icon",
+ G_CALLBACK (renaming_icon_callback), icon_view, 0);
+ g_signal_connect_object (icon_container, "icon_stretch_started",
+ G_CALLBACK (fm_directory_view_update_menus), icon_view,
+ G_CONNECT_SWAPPED);
+ g_signal_connect_object (icon_container, "icon_stretch_ended",
+ G_CALLBACK (fm_directory_view_update_menus), icon_view,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (icon_container, "get_stored_layout_timestamp",
+ G_CALLBACK (get_stored_layout_timestamp), icon_view, 0);
+ g_signal_connect_object (icon_container, "store_layout_timestamp",
+ G_CALLBACK (store_layout_timestamp), icon_view, 0);
+
+ gtk_container_add (GTK_CONTAINER (icon_view),
+ GTK_WIDGET (icon_container));
+
+ fm_icon_view_update_click_mode (icon_view);
+
+ gtk_widget_show (GTK_WIDGET (icon_container));
+
+ return icon_container;
+}
+
+/* Handles an URL received from Mozilla */
+static void
+icon_view_handle_netscape_url (CajaIconContainer *container, const char *encoded_url,
+ const char *target_uri,
+ GdkDragAction action, int x, int y, FMIconView *view)
+{
+ fm_directory_view_handle_netscape_url_drop (FM_DIRECTORY_VIEW (view),
+ encoded_url, target_uri, action, x, y);
+}
+
+static void
+icon_view_handle_uri_list (CajaIconContainer *container, const char *item_uris,
+ const char *target_uri,
+ GdkDragAction action, int x, int y, FMIconView *view)
+{
+ fm_directory_view_handle_uri_list_drop (FM_DIRECTORY_VIEW (view),
+ item_uris, target_uri, action, x, y);
+}
+
+static void
+icon_view_handle_text (CajaIconContainer *container, const char *text,
+ const char *target_uri,
+ GdkDragAction action, int x, int y, FMIconView *view)
+{
+ fm_directory_view_handle_text_drop (FM_DIRECTORY_VIEW (view),
+ text, target_uri, action, x, y);
+}
+
+static void
+icon_view_handle_raw (CajaIconContainer *container, const char *raw_data,
+ int length, const char *target_uri, const char *direct_save_uri,
+ GdkDragAction action, int x, int y, FMIconView *view)
+{
+ fm_directory_view_handle_raw_drop (FM_DIRECTORY_VIEW (view),
+ raw_data, length, target_uri, direct_save_uri, action, x, y);
+}
+
+static char *
+icon_view_get_first_visible_file (CajaView *view)
+{
+ CajaFile *file;
+ FMIconView *icon_view;
+
+ icon_view = FM_ICON_VIEW (view);
+
+ file = CAJA_FILE (caja_icon_container_get_first_visible_icon (get_icon_container (icon_view)));
+
+ if (file)
+ {
+ return caja_file_get_uri (file);
+ }
+
+ return NULL;
+}
+
+static void
+icon_view_scroll_to_file (CajaView *view,
+ const char *uri)
+{
+ CajaFile *file;
+ FMIconView *icon_view;
+
+ icon_view = FM_ICON_VIEW (view);
+
+ if (uri != NULL)
+ {
+ /* Only if existing, since we don't want to add the file to
+ the directory if it has been removed since then */
+ file = caja_file_get_existing_by_uri (uri);
+ if (file != NULL)
+ {
+ caja_icon_container_scroll_to_icon (get_icon_container (icon_view),
+ CAJA_ICON_CONTAINER_ICON_DATA (file));
+ caja_file_unref (file);
+ }
+ }
+}
+
+static void
+fm_icon_view_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ FMIconView *icon_view;
+
+ icon_view = FM_ICON_VIEW (object);
+
+ switch (prop_id)
+ {
+ case PROP_COMPACT:
+ icon_view->details->compact = g_value_get_boolean (value);
+ if (icon_view->details->compact)
+ {
+ caja_icon_container_set_layout_mode (get_icon_container (icon_view),
+ gtk_widget_get_direction (GTK_WIDGET(icon_view)) == GTK_TEXT_DIR_RTL ?
+ CAJA_ICON_LAYOUT_T_B_R_L :
+ CAJA_ICON_LAYOUT_T_B_L_R);
+ caja_icon_container_set_forced_icon_size (get_icon_container (icon_view),
+ CAJA_ICON_SIZE_SMALLEST);
+ }
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+
+
+static void
+fm_icon_view_class_init (FMIconViewClass *klass)
+{
+ FMDirectoryViewClass *fm_directory_view_class;
+
+ fm_directory_view_class = FM_DIRECTORY_VIEW_CLASS (klass);
+
+ G_OBJECT_CLASS (klass)->set_property = fm_icon_view_set_property;
+ G_OBJECT_CLASS (klass)->finalize = fm_icon_view_finalize;
+
+ GTK_OBJECT_CLASS (klass)->destroy = fm_icon_view_destroy;
+
+ GTK_WIDGET_CLASS (klass)->screen_changed = fm_icon_view_screen_changed;
+ GTK_WIDGET_CLASS (klass)->scroll_event = fm_icon_view_scroll_event;
+
+ fm_directory_view_class->add_file = fm_icon_view_add_file;
+ fm_directory_view_class->flush_added_files = fm_icon_view_flush_added_files;
+ fm_directory_view_class->begin_loading = fm_icon_view_begin_loading;
+ fm_directory_view_class->bump_zoom_level = fm_icon_view_bump_zoom_level;
+ fm_directory_view_class->can_rename_file = fm_icon_view_can_rename_file;
+ fm_directory_view_class->can_zoom_in = fm_icon_view_can_zoom_in;
+ fm_directory_view_class->can_zoom_out = fm_icon_view_can_zoom_out;
+ fm_directory_view_class->clear = fm_icon_view_clear;
+ fm_directory_view_class->end_loading = fm_icon_view_end_loading;
+ fm_directory_view_class->file_changed = fm_icon_view_file_changed;
+ fm_directory_view_class->get_background_widget = fm_icon_view_get_background_widget;
+ fm_directory_view_class->get_selected_icon_locations = fm_icon_view_get_selected_icon_locations;
+ fm_directory_view_class->get_selection = fm_icon_view_get_selection;
+ fm_directory_view_class->get_selection_for_file_transfer = fm_icon_view_get_selection;
+ fm_directory_view_class->get_item_count = fm_icon_view_get_item_count;
+ fm_directory_view_class->is_empty = fm_icon_view_is_empty;
+ fm_directory_view_class->remove_file = fm_icon_view_remove_file;
+ fm_directory_view_class->reset_to_defaults = fm_icon_view_reset_to_defaults;
+ fm_directory_view_class->restore_default_zoom_level = fm_icon_view_restore_default_zoom_level;
+ fm_directory_view_class->reveal_selection = fm_icon_view_reveal_selection;
+ fm_directory_view_class->select_all = fm_icon_view_select_all;
+ fm_directory_view_class->set_selection = fm_icon_view_set_selection;
+ fm_directory_view_class->invert_selection = fm_icon_view_invert_selection;
+ fm_directory_view_class->compare_files = compare_files;
+ fm_directory_view_class->zoom_to_level = fm_icon_view_zoom_to_level;
+ fm_directory_view_class->get_zoom_level = fm_icon_view_get_zoom_level;
+ fm_directory_view_class->click_policy_changed = fm_icon_view_click_policy_changed;
+ fm_directory_view_class->embedded_text_policy_changed = fm_icon_view_embedded_text_policy_changed;
+ fm_directory_view_class->emblems_changed = fm_icon_view_emblems_changed;
+ fm_directory_view_class->image_display_policy_changed = fm_icon_view_image_display_policy_changed;
+ fm_directory_view_class->merge_menus = fm_icon_view_merge_menus;
+ fm_directory_view_class->unmerge_menus = fm_icon_view_unmerge_menus;
+ fm_directory_view_class->sort_directories_first_changed = fm_icon_view_sort_directories_first_changed;
+ fm_directory_view_class->start_renaming_file = fm_icon_view_start_renaming_file;
+ fm_directory_view_class->text_attribute_names_changed = fm_icon_view_text_attribute_names_changed;
+ fm_directory_view_class->update_menus = fm_icon_view_update_menus;
+ fm_directory_view_class->using_manual_layout = fm_icon_view_using_manual_layout;
+ fm_directory_view_class->widget_to_file_operation_position = fm_icon_view_widget_to_file_operation_position;
+
+ klass->clean_up = fm_icon_view_real_clean_up;
+ klass->supports_auto_layout = real_supports_auto_layout;
+ klass->supports_scaling = real_supports_scaling;
+ klass->supports_manual_layout = real_supports_manual_layout;
+ klass->supports_keep_aligned = real_supports_keep_aligned;
+ klass->supports_labels_beside_icons = real_supports_labels_beside_icons;
+ klass->get_directory_auto_layout = fm_icon_view_real_get_directory_auto_layout;
+ klass->get_directory_sort_by = fm_icon_view_real_get_directory_sort_by;
+ klass->get_directory_sort_reversed = fm_icon_view_real_get_directory_sort_reversed;
+ klass->get_directory_tighter_layout = fm_icon_view_real_get_directory_tighter_layout;
+ klass->set_directory_auto_layout = fm_icon_view_real_set_directory_auto_layout;
+ klass->set_directory_sort_by = fm_icon_view_real_set_directory_sort_by;
+ klass->set_directory_sort_reversed = fm_icon_view_real_set_directory_sort_reversed;
+ klass->set_directory_tighter_layout = fm_icon_view_real_set_directory_tighter_layout;
+
+ g_object_class_install_property (G_OBJECT_CLASS (klass),
+ PROP_COMPACT,
+ g_param_spec_boolean ("compact",
+ "Compact",
+ "Whether this view provides a compact listing",
+ FALSE,
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+}
+
+static const char *
+fm_icon_view_get_id (CajaView *view)
+{
+ if (FM_IS_DESKTOP_ICON_VIEW (view))
+ {
+ return FM_DESKTOP_ICON_VIEW_ID;
+ }
+
+ if (fm_icon_view_is_compact (FM_ICON_VIEW (view)))
+ {
+ return FM_COMPACT_VIEW_ID;
+ }
+
+ return FM_ICON_VIEW_ID;
+}
+
+static void
+fm_icon_view_iface_init (CajaViewIface *iface)
+{
+ fm_directory_view_init_view_iface (iface);
+
+ iface->get_view_id = fm_icon_view_get_id;
+ iface->get_first_visible_file = icon_view_get_first_visible_file;
+ iface->scroll_to_file = icon_view_scroll_to_file;
+ iface->get_title = NULL;
+}
+
+static void
+fm_icon_view_init (FMIconView *icon_view)
+{
+ static gboolean setup_sound_preview = FALSE;
+ CajaIconContainer *icon_container;
+
+ g_return_if_fail (gtk_bin_get_child (GTK_BIN (icon_view)) == NULL);
+
+ icon_view->details = g_new0 (FMIconViewDetails, 1);
+ icon_view->details->sort = &sort_criteria[0];
+ icon_view->details->filter_by_screen = FALSE;
+
+ icon_container = create_icon_container (icon_view);
+
+ /* Set our default layout mode */
+ caja_icon_container_set_layout_mode (icon_container,
+ gtk_widget_get_direction (GTK_WIDGET(icon_container)) == GTK_TEXT_DIR_RTL ?
+ CAJA_ICON_LAYOUT_R_L_T_B :
+ CAJA_ICON_LAYOUT_L_R_T_B);
+
+ if (!setup_sound_preview)
+ {
+ eel_preferences_add_auto_enum (CAJA_PREFERENCES_PREVIEW_SOUND,
+ &preview_sound_auto_value);
+
+ setup_sound_preview = TRUE;
+ }
+
+ eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_SORT_ORDER,
+ default_sort_order_changed_callback,
+ icon_view, G_OBJECT (icon_view));
+ eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_SORT_IN_REVERSE_ORDER,
+ default_sort_in_reverse_order_changed_callback,
+ icon_view, G_OBJECT (icon_view));
+ eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_USE_TIGHTER_LAYOUT,
+ default_use_tighter_layout_changed_callback,
+ icon_view, G_OBJECT (icon_view));
+ eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_USE_MANUAL_LAYOUT,
+ default_use_manual_layout_changed_callback,
+ icon_view, G_OBJECT (icon_view));
+ eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_ICON_VIEW_DEFAULT_ZOOM_LEVEL,
+ default_zoom_level_changed_callback,
+ icon_view, G_OBJECT (icon_view));
+ eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_ICON_VIEW_LABELS_BESIDE_ICONS,
+ labels_beside_icons_changed_callback,
+ icon_view, G_OBJECT (icon_view));
+
+ eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_COMPACT_VIEW_DEFAULT_ZOOM_LEVEL,
+ default_zoom_level_changed_callback,
+ icon_view, G_OBJECT (icon_view));
+ eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_COMPACT_VIEW_ALL_COLUMNS_SAME_WIDTH,
+ all_columns_same_width_changed_callback,
+ icon_view, G_OBJECT (icon_view));
+
+ g_signal_connect_object (get_icon_container (icon_view), "handle_netscape_url",
+ G_CALLBACK (icon_view_handle_netscape_url), icon_view, 0);
+ g_signal_connect_object (get_icon_container (icon_view), "handle_uri_list",
+ G_CALLBACK (icon_view_handle_uri_list), icon_view, 0);
+ g_signal_connect_object (get_icon_container (icon_view), "handle_text",
+ G_CALLBACK (icon_view_handle_text), icon_view, 0);
+ g_signal_connect_object (get_icon_container (icon_view), "handle_raw",
+ G_CALLBACK (icon_view_handle_raw), icon_view, 0);
+
+ icon_view->details->clipboard_handler_id =
+ g_signal_connect (caja_clipboard_monitor_get (),
+ "clipboard_info",
+ G_CALLBACK (icon_view_notify_clipboard_info), icon_view);
+}
+
+static CajaView *
+fm_icon_view_create (CajaWindowSlotInfo *slot)
+{
+ FMIconView *view;
+
+ view = g_object_new (FM_TYPE_ICON_VIEW,
+ "window-slot", slot,
+ "compact", FALSE,
+ NULL);
+ return CAJA_VIEW (view);
+}
+
+static CajaView *
+fm_compact_view_create (CajaWindowSlotInfo *slot)
+{
+ FMIconView *view;
+
+ view = g_object_new (FM_TYPE_ICON_VIEW,
+ "window-slot", slot,
+ "compact", TRUE,
+ NULL);
+ return CAJA_VIEW (view);
+}
+
+static gboolean
+fm_icon_view_supports_uri (const char *uri,
+ GFileType file_type,
+ const char *mime_type)
+{
+ if (file_type == G_FILE_TYPE_DIRECTORY)
+ {
+ return TRUE;
+ }
+ if (strcmp (mime_type, CAJA_SAVED_SEARCH_MIMETYPE) == 0)
+ {
+ return TRUE;
+ }
+ if (g_str_has_prefix (uri, "trash:"))
+ {
+ return TRUE;
+ }
+ if (g_str_has_prefix (uri, EEL_SEARCH_URI))
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+#define TRANSLATE_VIEW_INFO(view_info) \
+ view_info.view_combo_label = _(view_info.view_combo_label); \
+ view_info.view_menu_label_with_mnemonic = _(view_info.view_menu_label_with_mnemonic); \
+ view_info.error_label = _(view_info.error_label); \
+ view_info.startup_error_label = _(view_info.startup_error_label); \
+ view_info.display_location_label = _(view_info.display_location_label); \
+
+
+static CajaViewInfo fm_icon_view =
+{
+ FM_ICON_VIEW_ID,
+ /* translators: this is used in the view selection dropdown
+ * of navigation windows and in the preferences dialog */
+ N_("Icon View"),
+ /* translators: this is used in the view menu */
+ N_("_Icons"),
+ N_("The icon view encountered an error."),
+ N_("The icon view encountered an error while starting up."),
+ N_("Display this location with the icon view."),
+ fm_icon_view_create,
+ fm_icon_view_supports_uri
+};
+
+static CajaViewInfo fm_compact_view =
+{
+ FM_COMPACT_VIEW_ID,
+ /* translators: this is used in the view selection dropdown
+ * of navigation windows and in the preferences dialog */
+ N_("Compact View"),
+ /* translators: this is used in the view menu */
+ N_("_Compact"),
+ N_("The compact view encountered an error."),
+ N_("The compact view encountered an error while starting up."),
+ N_("Display this location with the compact view."),
+ fm_compact_view_create,
+ fm_icon_view_supports_uri
+};
+
+gboolean
+fm_icon_view_is_compact (FMIconView *view)
+{
+ return view->details->compact;
+}
+
+void
+fm_icon_view_register (void)
+{
+ TRANSLATE_VIEW_INFO (fm_icon_view)
+ caja_view_factory_register (&fm_icon_view);
+}
+
+void
+fm_compact_view_register (void)
+{
+ TRANSLATE_VIEW_INFO (fm_compact_view)
+ caja_view_factory_register (&fm_compact_view);
+}
+
diff --git a/src/file-manager/fm-icon-view.h b/src/file-manager/fm-icon-view.h
new file mode 100644
index 00000000..2ef11a56
--- /dev/null
+++ b/src/file-manager/fm-icon-view.h
@@ -0,0 +1,135 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* fm-icon-view.h - interface for icon view of directory.
+
+ Copyright (C) 2000 Eazel, Inc.
+
+ The Mate Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Mate Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: John Sullivan <[email protected]>
+*/
+
+#ifndef FM_ICON_VIEW_H
+#define FM_ICON_VIEW_H
+
+#include "fm-directory-view.h"
+
+typedef struct FMIconView FMIconView;
+typedef struct FMIconViewClass FMIconViewClass;
+
+#define FM_TYPE_ICON_VIEW fm_icon_view_get_type()
+#define FM_ICON_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_ICON_VIEW, FMIconView))
+#define FM_ICON_VIEW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_ICON_VIEW, FMIconViewClass))
+#define FM_IS_ICON_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_ICON_VIEW))
+#define FM_IS_ICON_VIEW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_ICON_VIEW))
+#define FM_ICON_VIEW_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_ICON_VIEW, FMIconViewClass))
+
+#define FM_ICON_VIEW_ID "OAFIID:Caja_File_Manager_Icon_View"
+#define FM_COMPACT_VIEW_ID "OAFIID:Caja_File_Manager_Compact_View"
+
+typedef struct FMIconViewDetails FMIconViewDetails;
+
+struct FMIconView
+{
+ FMDirectoryView parent;
+ FMIconViewDetails *details;
+};
+
+struct FMIconViewClass
+{
+ FMDirectoryViewClass parent_class;
+
+ /* Methods that can be overriden for settings you don't want to come from metadata.
+ */
+
+ /* Note: get_directory_sort_by must return a string that can/will be g_freed.
+ */
+ char * (* get_directory_sort_by) (FMIconView *icon_view,
+ CajaFile *file);
+ void (* set_directory_sort_by) (FMIconView *icon_view,
+ CajaFile *file,
+ const char* sort_by);
+
+ gboolean (* get_directory_sort_reversed) (FMIconView *icon_view,
+ CajaFile *file);
+ void (* set_directory_sort_reversed) (FMIconView *icon_view,
+ CajaFile *file,
+ gboolean sort_reversed);
+
+ gboolean (* get_directory_auto_layout) (FMIconView *icon_view,
+ CajaFile *file);
+ void (* set_directory_auto_layout) (FMIconView *icon_view,
+ CajaFile *file,
+ gboolean auto_layout);
+
+ gboolean (* get_directory_tighter_layout) (FMIconView *icon_view,
+ CajaFile *file);
+ void (* set_directory_tighter_layout) (FMIconView *icon_view,
+ CajaFile *file,
+ gboolean tighter_layout);
+
+ /* Override "clean_up" if your subclass has its own notion of where icons should be positioned */
+ void (* clean_up) (FMIconView *icon_view);
+
+ /* supports_auto_layout is a function pointer that subclasses may
+ * override to control whether or not the automatic layout options
+ * should be enabled. The default implementation returns TRUE.
+ */
+ gboolean (* supports_auto_layout) (FMIconView *view);
+
+ /* supports_manual_layout is a function pointer that subclasses may
+ * override to control whether or not the manual layout options
+ * should be enabled. The default implementation returns TRUE iff
+ * not in compact mode.
+ */
+ gboolean (* supports_manual_layout) (FMIconView *view);
+
+ /* supports_scaling is a function pointer that subclasses may
+ * override to control whether or not the manual layout supports
+ * scaling. The default implementation returns FALSE
+ */
+ gboolean (* supports_scaling) (FMIconView *view);
+
+ /* supports_auto_layout is a function pointer that subclasses may
+ * override to control whether snap-to-grid mode
+ * should be enabled. The default implementation returns FALSE.
+ */
+ gboolean (* supports_keep_aligned) (FMIconView *view);
+
+ /* supports_auto_layout is a function pointer that subclasses may
+ * override to control whether snap-to-grid mode
+ * should be enabled. The default implementation returns FALSE.
+ */
+ gboolean (* supports_labels_beside_icons) (FMIconView *view);
+};
+
+/* GObject support */
+GType fm_icon_view_get_type (void);
+int fm_icon_view_compare_files (FMIconView *icon_view,
+ CajaFile *a,
+ CajaFile *b);
+void fm_icon_view_filter_by_screen (FMIconView *icon_view, gboolean filter);
+gboolean fm_icon_view_is_compact (FMIconView *icon_view);
+
+void fm_icon_view_register (void);
+void fm_compact_view_register (void);
+
+#endif /* FM_ICON_VIEW_H */
diff --git a/src/file-manager/fm-list-model.c b/src/file-manager/fm-list-model.c
new file mode 100644
index 00000000..b5e12da2
--- /dev/null
+++ b/src/file-manager/fm-list-model.c
@@ -0,0 +1,1882 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* fm-list-model.h - a GtkTreeModel for file lists.
+
+ Copyright (C) 2001, 2002 Anders Carlsson
+ Copyright (C) 2003, Soeren Sandmann
+ Copyright (C) 2004, Novell, Inc.
+
+ The Mate Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Mate Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Anders Carlsson <[email protected]>, Soeren Sandmann ([email protected]), Dave Camp <[email protected]>
+*/
+
+#include <config.h>
+#include "fm-list-model.h"
+#include <libegg/eggtreemultidnd.h>
+
+#include <string.h>
+#include <eel/eel-gtk-macros.h>
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-gdk-pixbuf-extensions.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <libcaja-private/caja-dnd.h>
+#include <glib.h>
+
+
+enum
+{
+ SUBDIRECTORY_UNLOADED,
+ LAST_SIGNAL
+};
+
+static GQuark attribute_name_q,
+ attribute_modification_date_q,
+ attribute_date_modified_q;
+
+/* msec delay after Loading... dummy row turns into (empty) */
+#define LOADING_TO_EMPTY_DELAY 100
+
+static guint list_model_signals[LAST_SIGNAL] = { 0 };
+
+static int fm_list_model_file_entry_compare_func (gconstpointer a,
+ gconstpointer b,
+ gpointer user_data);
+static void fm_list_model_tree_model_init (GtkTreeModelIface *iface);
+static void fm_list_model_sortable_init (GtkTreeSortableIface *iface);
+static void fm_list_model_multi_drag_source_init (EggTreeMultiDragSourceIface *iface);
+
+struct FMListModelDetails
+{
+ GSequence *files;
+ GHashTable *directory_reverse_map; /* map from directory to GSequenceIter's */
+ GHashTable *top_reverse_map; /* map from files in top dir to GSequenceIter's */
+
+ int stamp;
+
+ GQuark sort_attribute;
+ GtkSortType order;
+
+ gboolean sort_directories_first;
+
+ GtkTreeView *drag_view;
+ int drag_begin_x;
+ int drag_begin_y;
+
+ GPtrArray *columns;
+
+ GList *highlight_files;
+};
+
+typedef struct
+{
+ FMListModel *model;
+
+ GList *path_list;
+} DragDataGetInfo;
+
+typedef struct FileEntry FileEntry;
+
+struct FileEntry
+{
+ CajaFile *file;
+ GHashTable *reverse_map; /* map from files to GSequenceIter's */
+ CajaDirectory *subdirectory;
+ FileEntry *parent;
+ GSequence *files;
+ GSequenceIter *ptr;
+ guint loaded : 1;
+};
+
+G_DEFINE_TYPE_WITH_CODE (FMListModel, fm_list_model, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL,
+ fm_list_model_tree_model_init)
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_SORTABLE,
+ fm_list_model_sortable_init)
+ G_IMPLEMENT_INTERFACE (EGG_TYPE_TREE_MULTI_DRAG_SOURCE,
+ fm_list_model_multi_drag_source_init));
+
+static const GtkTargetEntry drag_types [] =
+{
+ { CAJA_ICON_DND_MATE_ICON_LIST_TYPE, 0, CAJA_ICON_DND_MATE_ICON_LIST },
+ { CAJA_ICON_DND_URI_LIST_TYPE, 0, CAJA_ICON_DND_URI_LIST },
+};
+
+static GtkTargetList *drag_target_list = NULL;
+
+static void
+file_entry_free (FileEntry *file_entry)
+{
+ caja_file_unref (file_entry->file);
+ if (file_entry->reverse_map)
+ {
+ g_hash_table_destroy (file_entry->reverse_map);
+ file_entry->reverse_map = NULL;
+ }
+ if (file_entry->subdirectory != NULL)
+ {
+ caja_directory_unref (file_entry->subdirectory);
+ }
+ if (file_entry->files != NULL)
+ {
+ g_sequence_free (file_entry->files);
+ }
+ g_free (file_entry);
+}
+
+static GtkTreeModelFlags
+fm_list_model_get_flags (GtkTreeModel *tree_model)
+{
+ return GTK_TREE_MODEL_ITERS_PERSIST;
+}
+
+static int
+fm_list_model_get_n_columns (GtkTreeModel *tree_model)
+{
+ return FM_LIST_MODEL_NUM_COLUMNS + FM_LIST_MODEL (tree_model)->details->columns->len;
+}
+
+static GType
+fm_list_model_get_column_type (GtkTreeModel *tree_model, int index)
+{
+ switch (index)
+ {
+ case FM_LIST_MODEL_FILE_COLUMN:
+ return CAJA_TYPE_FILE;
+ case FM_LIST_MODEL_SUBDIRECTORY_COLUMN:
+ return CAJA_TYPE_DIRECTORY;
+ case FM_LIST_MODEL_SMALLEST_ICON_COLUMN:
+ case FM_LIST_MODEL_SMALLER_ICON_COLUMN:
+ case FM_LIST_MODEL_SMALL_ICON_COLUMN:
+ case FM_LIST_MODEL_STANDARD_ICON_COLUMN:
+ case FM_LIST_MODEL_LARGE_ICON_COLUMN:
+ case FM_LIST_MODEL_LARGER_ICON_COLUMN:
+ case FM_LIST_MODEL_LARGEST_ICON_COLUMN:
+ case FM_LIST_MODEL_SMALLEST_EMBLEM_COLUMN:
+ case FM_LIST_MODEL_SMALLER_EMBLEM_COLUMN:
+ case FM_LIST_MODEL_SMALL_EMBLEM_COLUMN:
+ case FM_LIST_MODEL_STANDARD_EMBLEM_COLUMN:
+ case FM_LIST_MODEL_LARGE_EMBLEM_COLUMN:
+ case FM_LIST_MODEL_LARGER_EMBLEM_COLUMN:
+ case FM_LIST_MODEL_LARGEST_EMBLEM_COLUMN:
+ return GDK_TYPE_PIXBUF;
+ case FM_LIST_MODEL_FILE_NAME_IS_EDITABLE_COLUMN:
+ return G_TYPE_BOOLEAN;
+ default:
+ if (index < FM_LIST_MODEL_NUM_COLUMNS + FM_LIST_MODEL (tree_model)->details->columns->len)
+ {
+ return G_TYPE_STRING;
+ }
+ else
+ {
+ return G_TYPE_INVALID;
+ }
+ }
+}
+
+static void
+fm_list_model_ptr_to_iter (FMListModel *model, GSequenceIter *ptr, GtkTreeIter *iter)
+{
+ g_assert (!g_sequence_iter_is_end (ptr));
+ if (iter != NULL)
+ {
+ iter->stamp = model->details->stamp;
+ iter->user_data = ptr;
+ }
+}
+
+static gboolean
+fm_list_model_get_iter (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreePath *path)
+{
+ FMListModel *model;
+ GSequence *files;
+ GSequenceIter *ptr;
+ FileEntry *file_entry;
+ int i, d;
+
+ model = (FMListModel *)tree_model;
+ ptr = NULL;
+
+ files = model->details->files;
+ for (d = 0; d < gtk_tree_path_get_depth (path); d++)
+ {
+ i = gtk_tree_path_get_indices (path)[d];
+
+ if (files == NULL || i >= g_sequence_get_length (files))
+ {
+ return FALSE;
+ }
+
+ ptr = g_sequence_get_iter_at_pos (files, i);
+ file_entry = g_sequence_get (ptr);
+ files = file_entry->files;
+ }
+
+ fm_list_model_ptr_to_iter (model, ptr, iter);
+
+ return TRUE;
+}
+
+static GtkTreePath *
+fm_list_model_get_path (GtkTreeModel *tree_model, GtkTreeIter *iter)
+{
+ GtkTreePath *path;
+ FMListModel *model;
+ GSequenceIter *ptr;
+ FileEntry *file_entry;
+
+
+ model = (FMListModel *)tree_model;
+
+ g_return_val_if_fail (iter->stamp == model->details->stamp, NULL);
+
+ if (g_sequence_iter_is_end (iter->user_data))
+ {
+ /* FIXME is this right? */
+ return NULL;
+ }
+
+ path = gtk_tree_path_new ();
+ ptr = iter->user_data;
+ while (ptr != NULL)
+ {
+ gtk_tree_path_prepend_index (path, g_sequence_iter_get_position (ptr));
+ file_entry = g_sequence_get (ptr);
+ if (file_entry->parent != NULL)
+ {
+ ptr = file_entry->parent->ptr;
+ }
+ else
+ {
+ ptr = NULL;
+ }
+ }
+
+ return path;
+}
+
+static void
+fm_list_model_get_value (GtkTreeModel *tree_model, GtkTreeIter *iter, int column, GValue *value)
+{
+ FMListModel *model;
+ FileEntry *file_entry;
+ CajaFile *file;
+ char *str;
+ GdkPixbuf *icon, *rendered_icon;
+ int icon_size;
+ guint emblem_size;
+ CajaZoomLevel zoom_level;
+ GList *emblem_pixbufs;
+ CajaFile *parent_file;
+ char *emblems_to_ignore[3];
+ int i;
+ CajaFileIconFlags flags;
+
+ model = (FMListModel *)tree_model;
+
+ g_return_if_fail (model->details->stamp == iter->stamp);
+ g_return_if_fail (!g_sequence_iter_is_end (iter->user_data));
+
+ file_entry = g_sequence_get (iter->user_data);
+ file = file_entry->file;
+
+ switch (column)
+ {
+ case FM_LIST_MODEL_FILE_COLUMN:
+ g_value_init (value, CAJA_TYPE_FILE);
+
+ g_value_set_object (value, file);
+ break;
+ case FM_LIST_MODEL_SUBDIRECTORY_COLUMN:
+ g_value_init (value, CAJA_TYPE_DIRECTORY);
+
+ g_value_set_object (value, file_entry->subdirectory);
+ break;
+ case FM_LIST_MODEL_SMALLEST_ICON_COLUMN:
+ case FM_LIST_MODEL_SMALLER_ICON_COLUMN:
+ case FM_LIST_MODEL_SMALL_ICON_COLUMN:
+ case FM_LIST_MODEL_STANDARD_ICON_COLUMN:
+ case FM_LIST_MODEL_LARGE_ICON_COLUMN:
+ case FM_LIST_MODEL_LARGER_ICON_COLUMN:
+ case FM_LIST_MODEL_LARGEST_ICON_COLUMN:
+ g_value_init (value, GDK_TYPE_PIXBUF);
+
+ if (file != NULL)
+ {
+ zoom_level = fm_list_model_get_zoom_level_from_column_id (column);
+ icon_size = caja_get_icon_size_for_zoom_level (zoom_level);
+
+ flags = CAJA_FILE_ICON_FLAGS_USE_THUMBNAILS |
+ CAJA_FILE_ICON_FLAGS_FORCE_THUMBNAIL_SIZE |
+ CAJA_FILE_ICON_FLAGS_USE_MOUNT_ICON_AS_EMBLEM;
+ if (model->details->drag_view != NULL)
+ {
+ GtkTreePath *path_a, *path_b;
+
+ gtk_tree_view_get_drag_dest_row (model->details->drag_view,
+ &path_a,
+ NULL);
+ if (path_a != NULL)
+ {
+ path_b = gtk_tree_model_get_path (tree_model, iter);
+
+ if (gtk_tree_path_compare (path_a, path_b) == 0)
+ {
+ flags |= CAJA_FILE_ICON_FLAGS_FOR_DRAG_ACCEPT;
+ }
+
+ gtk_tree_path_free (path_a);
+ gtk_tree_path_free (path_b);
+ }
+ }
+
+ icon = caja_file_get_icon_pixbuf (file, icon_size, TRUE, flags);
+
+ if (model->details->highlight_files != NULL &&
+ g_list_find_custom (model->details->highlight_files,
+ file, (GCompareFunc) caja_file_compare_location))
+ {
+ rendered_icon = eel_gdk_pixbuf_render (icon, 1, 255, 255, 0, 0);
+
+ if (rendered_icon != NULL)
+ {
+ g_object_unref (icon);
+ icon = rendered_icon;
+ }
+ }
+
+ g_value_set_object (value, icon);
+ g_object_unref (icon);
+ }
+ break;
+ case FM_LIST_MODEL_SMALLEST_EMBLEM_COLUMN:
+ case FM_LIST_MODEL_SMALLER_EMBLEM_COLUMN:
+ case FM_LIST_MODEL_SMALL_EMBLEM_COLUMN:
+ case FM_LIST_MODEL_STANDARD_EMBLEM_COLUMN:
+ case FM_LIST_MODEL_LARGE_EMBLEM_COLUMN:
+ case FM_LIST_MODEL_LARGER_EMBLEM_COLUMN:
+ case FM_LIST_MODEL_LARGEST_EMBLEM_COLUMN:
+ g_value_init (value, GDK_TYPE_PIXBUF);
+
+ if (file != NULL)
+ {
+ parent_file = caja_file_get_parent (file);
+ i = 0;
+ emblems_to_ignore[i++] = CAJA_FILE_EMBLEM_NAME_TRASH;
+ if (parent_file)
+ {
+ if (!caja_file_can_write (parent_file))
+ {
+ emblems_to_ignore[i++] = CAJA_FILE_EMBLEM_NAME_CANT_WRITE;
+ }
+ caja_file_unref (parent_file);
+ }
+ emblems_to_ignore[i++] = NULL;
+
+ zoom_level = fm_list_model_get_zoom_level_from_emblem_column_id (column);
+ icon_size = caja_get_icon_size_for_zoom_level (zoom_level);
+ emblem_size = caja_icon_get_emblem_size_for_icon_size (icon_size);
+ if (emblem_size != 0)
+ {
+ emblem_pixbufs = caja_file_get_emblem_pixbufs (file,
+ emblem_size,
+ TRUE,
+ emblems_to_ignore);
+ if (emblem_pixbufs != NULL)
+ {
+ icon = emblem_pixbufs->data;
+ g_value_set_object (value, icon);
+ }
+ eel_gdk_pixbuf_list_free (emblem_pixbufs);
+ }
+ }
+ break;
+ case FM_LIST_MODEL_FILE_NAME_IS_EDITABLE_COLUMN:
+ g_value_init (value, G_TYPE_BOOLEAN);
+
+ g_value_set_boolean (value, file != NULL && caja_file_can_rename (file));
+ break;
+ default:
+ if (column >= FM_LIST_MODEL_NUM_COLUMNS || column < FM_LIST_MODEL_NUM_COLUMNS + model->details->columns->len)
+ {
+ CajaColumn *caja_column;
+ GQuark attribute;
+ caja_column = model->details->columns->pdata[column - FM_LIST_MODEL_NUM_COLUMNS];
+
+ g_value_init (value, G_TYPE_STRING);
+ g_object_get (caja_column,
+ "attribute_q", &attribute,
+ NULL);
+ if (file != NULL)
+ {
+ str = caja_file_get_string_attribute_with_default_q (file,
+ attribute);
+ g_value_take_string (value, str);
+ }
+ else if (attribute == attribute_name_q)
+ {
+ if (file_entry->parent->loaded)
+ {
+ g_value_set_string (value, _("(Empty)"));
+ }
+ else
+ {
+ g_value_set_string (value, _("Loading..."));
+ }
+ }
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+ }
+}
+
+static gboolean
+fm_list_model_iter_next (GtkTreeModel *tree_model, GtkTreeIter *iter)
+{
+ FMListModel *model;
+
+ model = (FMListModel *)tree_model;
+
+ g_return_val_if_fail (model->details->stamp == iter->stamp, FALSE);
+
+ iter->user_data = g_sequence_iter_next (iter->user_data);
+
+ return !g_sequence_iter_is_end (iter->user_data);
+}
+
+static gboolean
+fm_list_model_iter_children (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *parent)
+{
+ FMListModel *model;
+ GSequence *files;
+ FileEntry *file_entry;
+
+ model = (FMListModel *)tree_model;
+
+ if (parent == NULL)
+ {
+ files = model->details->files;
+ }
+ else
+ {
+ file_entry = g_sequence_get (parent->user_data);
+ files = file_entry->files;
+ }
+
+ if (files == NULL || g_sequence_get_length (files) == 0)
+ {
+ return FALSE;
+ }
+
+ iter->stamp = model->details->stamp;
+ iter->user_data = g_sequence_get_begin_iter (files);
+
+ return TRUE;
+}
+
+static gboolean
+fm_list_model_iter_has_child (GtkTreeModel *tree_model, GtkTreeIter *iter)
+{
+ FileEntry *file_entry;
+
+ if (iter == NULL)
+ {
+ return !fm_list_model_is_empty (FM_LIST_MODEL (tree_model));
+ }
+
+ file_entry = g_sequence_get (iter->user_data);
+
+ return (file_entry->files != NULL && g_sequence_get_length (file_entry->files) > 0);
+}
+
+static int
+fm_list_model_iter_n_children (GtkTreeModel *tree_model, GtkTreeIter *iter)
+{
+ FMListModel *model;
+ GSequence *files;
+ FileEntry *file_entry;
+
+ model = (FMListModel *)tree_model;
+
+ if (iter == NULL)
+ {
+ files = model->details->files;
+ }
+ else
+ {
+ file_entry = g_sequence_get (iter->user_data);
+ files = file_entry->files;
+ }
+
+ return g_sequence_get_length (files);
+}
+
+static gboolean
+fm_list_model_iter_nth_child (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *parent, int n)
+{
+ FMListModel *model;
+ GSequenceIter *child;
+ GSequence *files;
+ FileEntry *file_entry;
+
+ model = (FMListModel *)tree_model;
+
+ if (parent != NULL)
+ {
+ file_entry = g_sequence_get (parent->user_data);
+ files = file_entry->files;
+ }
+ else
+ {
+ files = model->details->files;
+ }
+
+ child = g_sequence_get_iter_at_pos (files, n);
+
+ if (g_sequence_iter_is_end (child))
+ {
+ return FALSE;
+ }
+
+ iter->stamp = model->details->stamp;
+ iter->user_data = child;
+
+ return TRUE;
+}
+
+static gboolean
+fm_list_model_iter_parent (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *child)
+{
+ FMListModel *model;
+ FileEntry *file_entry;
+
+ model = (FMListModel *)tree_model;
+
+ file_entry = g_sequence_get (child->user_data);
+
+ if (file_entry->parent == NULL)
+ {
+ return FALSE;
+ }
+
+ iter->stamp = model->details->stamp;
+ iter->user_data = file_entry->parent->ptr;
+
+ return TRUE;
+}
+
+static GSequenceIter *
+lookup_file (FMListModel *model, CajaFile *file,
+ CajaDirectory *directory)
+{
+ FileEntry *file_entry;
+ GSequenceIter *ptr, *parent_ptr;
+
+ parent_ptr = NULL;
+ if (directory)
+ {
+ parent_ptr = g_hash_table_lookup (model->details->directory_reverse_map,
+ directory);
+ }
+
+ if (parent_ptr)
+ {
+ file_entry = g_sequence_get (parent_ptr);
+ ptr = g_hash_table_lookup (file_entry->reverse_map, file);
+ }
+ else
+ {
+ ptr = g_hash_table_lookup (model->details->top_reverse_map, file);
+ }
+
+ if (ptr)
+ {
+ g_assert (((FileEntry *)g_sequence_get (ptr))->file == file);
+ }
+
+ return ptr;
+}
+
+
+struct GetIters
+{
+ FMListModel *model;
+ CajaFile *file;
+ GList *iters;
+};
+
+static void
+dir_to_iters (struct GetIters *data,
+ GHashTable *reverse_map)
+{
+ GSequenceIter *ptr;
+
+ ptr = g_hash_table_lookup (reverse_map, data->file);
+ if (ptr)
+ {
+ GtkTreeIter *iter;
+ iter = g_new0 (GtkTreeIter, 1);
+ fm_list_model_ptr_to_iter (data->model, ptr, iter);
+ data->iters = g_list_prepend (data->iters, iter);
+ }
+}
+
+static void
+file_to_iter_cb (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ struct GetIters *data;
+ FileEntry *dir_file_entry;
+
+ data = user_data;
+ dir_file_entry = g_sequence_get ((GSequenceIter *)value);
+ dir_to_iters (data, dir_file_entry->reverse_map);
+}
+
+GList *
+fm_list_model_get_all_iters_for_file (FMListModel *model, CajaFile *file)
+{
+ struct GetIters data;
+
+ data.file = file;
+ data.model = model;
+ data.iters = NULL;
+
+ dir_to_iters (&data, model->details->top_reverse_map);
+ g_hash_table_foreach (model->details->directory_reverse_map,
+ file_to_iter_cb, &data);
+
+ return g_list_reverse (data.iters);
+}
+
+gboolean
+fm_list_model_get_first_iter_for_file (FMListModel *model,
+ CajaFile *file,
+ GtkTreeIter *iter)
+{
+ GList *list;
+ gboolean res;
+
+ res = FALSE;
+
+ list = fm_list_model_get_all_iters_for_file (model, file);
+ if (list != NULL)
+ {
+ res = TRUE;
+ *iter = *(GtkTreeIter *)list->data;
+ }
+ eel_g_list_free_deep (list);
+
+ return res;
+}
+
+
+gboolean
+fm_list_model_get_tree_iter_from_file (FMListModel *model, CajaFile *file,
+ CajaDirectory *directory,
+ GtkTreeIter *iter)
+{
+ GSequenceIter *ptr;
+
+ ptr = lookup_file (model, file, directory);
+ if (!ptr)
+ {
+ return FALSE;
+ }
+
+ fm_list_model_ptr_to_iter (model, ptr, iter);
+
+ return TRUE;
+}
+
+static int
+fm_list_model_file_entry_compare_func (gconstpointer a,
+ gconstpointer b,
+ gpointer user_data)
+{
+ FileEntry *file_entry1;
+ FileEntry *file_entry2;
+ FMListModel *model;
+ int result;
+
+ model = (FMListModel *)user_data;
+
+ file_entry1 = (FileEntry *)a;
+ file_entry2 = (FileEntry *)b;
+
+ if (file_entry1->file != NULL && file_entry2->file != NULL)
+ {
+ result = caja_file_compare_for_sort_by_attribute_q (file_entry1->file, file_entry2->file,
+ model->details->sort_attribute,
+ model->details->sort_directories_first,
+ (model->details->order == GTK_SORT_DESCENDING));
+ }
+ else if (file_entry1->file == NULL)
+ {
+ return -1;
+ }
+ else
+ {
+ return 1;
+ }
+
+ return result;
+}
+
+int
+fm_list_model_compare_func (FMListModel *model,
+ CajaFile *file1,
+ CajaFile *file2)
+{
+ int result;
+
+ result = caja_file_compare_for_sort_by_attribute_q (file1, file2,
+ model->details->sort_attribute,
+ model->details->sort_directories_first,
+ (model->details->order == GTK_SORT_DESCENDING));
+
+ return result;
+}
+
+static void
+fm_list_model_sort_file_entries (FMListModel *model, GSequence *files, GtkTreePath *path)
+{
+ GSequenceIter **old_order;
+ GtkTreeIter iter;
+ int *new_order;
+ int length;
+ int i;
+ FileEntry *file_entry;
+ gboolean has_iter;
+
+ length = g_sequence_get_length (files);
+
+ if (length <= 1)
+ {
+ return;
+ }
+
+ /* generate old order of GSequenceIter's */
+ old_order = g_new (GSequenceIter *, length);
+ for (i = 0; i < length; ++i)
+ {
+ GSequenceIter *ptr = g_sequence_get_iter_at_pos (files, i);
+
+ file_entry = g_sequence_get (ptr);
+ if (file_entry->files != NULL)
+ {
+ gtk_tree_path_append_index (path, i);
+ fm_list_model_sort_file_entries (model, file_entry->files, path);
+ gtk_tree_path_up (path);
+ }
+
+ old_order[i] = ptr;
+ }
+
+ /* sort */
+ g_sequence_sort (files, fm_list_model_file_entry_compare_func, model);
+
+ /* generate new order */
+ new_order = g_new (int, length);
+ /* Note: new_order[newpos] = oldpos */
+ for (i = 0; i < length; ++i)
+ {
+ new_order[g_sequence_iter_get_position (old_order[i])] = i;
+ }
+
+ /* Let the world know about our new order */
+
+ g_assert (new_order != NULL);
+
+ has_iter = FALSE;
+ if (gtk_tree_path_get_depth (path) != 0)
+ {
+ gboolean get_iter_result;
+ has_iter = TRUE;
+ get_iter_result = gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path);
+ g_assert (get_iter_result);
+ }
+
+ gtk_tree_model_rows_reordered (GTK_TREE_MODEL (model),
+ path, has_iter ? &iter : NULL, new_order);
+
+ g_free (old_order);
+ g_free (new_order);
+}
+
+static void
+fm_list_model_sort (FMListModel *model)
+{
+ GtkTreePath *path;
+
+ path = gtk_tree_path_new ();
+
+ fm_list_model_sort_file_entries (model, model->details->files, path);
+
+ gtk_tree_path_free (path);
+}
+
+static gboolean
+fm_list_model_get_sort_column_id (GtkTreeSortable *sortable,
+ gint *sort_column_id,
+ GtkSortType *order)
+{
+ FMListModel *model;
+ int id;
+
+ model = (FMListModel *)sortable;
+
+ id = fm_list_model_get_sort_column_id_from_attribute
+ (model, model->details->sort_attribute);
+
+ if (id == -1)
+ {
+ return FALSE;
+ }
+
+ if (sort_column_id != NULL)
+ {
+ *sort_column_id = id;
+ }
+
+ if (order != NULL)
+ {
+ *order = model->details->order;
+ }
+
+ return TRUE;
+}
+
+static void
+fm_list_model_set_sort_column_id (GtkTreeSortable *sortable, gint sort_column_id, GtkSortType order)
+{
+ FMListModel *model;
+
+ model = (FMListModel *)sortable;
+
+ model->details->sort_attribute = fm_list_model_get_attribute_from_sort_column_id (model, sort_column_id);
+
+ model->details->order = order;
+
+ fm_list_model_sort (model);
+ gtk_tree_sortable_sort_column_changed (sortable);
+}
+
+static gboolean
+fm_list_model_has_default_sort_func (GtkTreeSortable *sortable)
+{
+ return FALSE;
+}
+
+static gboolean
+fm_list_model_multi_row_draggable (EggTreeMultiDragSource *drag_source, GList *path_list)
+{
+ return TRUE;
+}
+
+static void
+each_path_get_data_binder (CajaDragEachSelectedItemDataGet data_get,
+ gpointer context,
+ gpointer data)
+{
+ DragDataGetInfo *info;
+ GList *l;
+ CajaFile *file;
+ GtkTreeRowReference *row;
+ GtkTreePath *path;
+ char *uri;
+ GdkRectangle cell_area;
+ GtkTreeViewColumn *column;
+
+ info = context;
+
+ g_return_if_fail (info->model->details->drag_view);
+
+ column = gtk_tree_view_get_column (info->model->details->drag_view, 0);
+
+ for (l = info->path_list; l != NULL; l = l->next)
+ {
+ row = l->data;
+
+ path = gtk_tree_row_reference_get_path (row);
+ file = fm_list_model_file_for_path (info->model, path);
+ if (file)
+ {
+ gtk_tree_view_get_cell_area
+ (info->model->details->drag_view,
+ path,
+ column,
+ &cell_area);
+
+ uri = caja_file_get_uri (file);
+
+ (*data_get) (uri,
+ 0,
+ cell_area.y - info->model->details->drag_begin_y,
+ cell_area.width, cell_area.height,
+ data);
+
+ g_free (uri);
+
+ caja_file_unref (file);
+ }
+
+ gtk_tree_path_free (path);
+ }
+}
+
+static gboolean
+fm_list_model_multi_drag_data_get (EggTreeMultiDragSource *drag_source,
+ GList *path_list,
+ GtkSelectionData *selection_data)
+{
+ FMListModel *model;
+ DragDataGetInfo context;
+ guint target_info;
+
+ model = FM_LIST_MODEL (drag_source);
+
+ context.model = model;
+ context.path_list = path_list;
+
+ if (!drag_target_list)
+ {
+ drag_target_list = fm_list_model_get_drag_target_list ();
+ }
+
+ if (gtk_target_list_find (drag_target_list,
+ gtk_selection_data_get_target (selection_data),
+ &target_info))
+ {
+ caja_drag_drag_data_get (NULL,
+ NULL,
+ selection_data,
+ target_info,
+ GDK_CURRENT_TIME,
+ &context,
+ each_path_get_data_binder);
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+static gboolean
+fm_list_model_multi_drag_data_delete (EggTreeMultiDragSource *drag_source, GList *path_list)
+{
+ return TRUE;
+}
+
+static void
+add_dummy_row (FMListModel *model, FileEntry *parent_entry)
+{
+ FileEntry *dummy_file_entry;
+ GtkTreeIter iter;
+ GtkTreePath *path;
+
+ dummy_file_entry = g_new0 (FileEntry, 1);
+ dummy_file_entry->parent = parent_entry;
+ dummy_file_entry->ptr = g_sequence_insert_sorted (parent_entry->files, dummy_file_entry,
+ fm_list_model_file_entry_compare_func, model);
+ iter.stamp = model->details->stamp;
+ iter.user_data = dummy_file_entry->ptr;
+
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
+ gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter);
+ gtk_tree_path_free (path);
+}
+
+gboolean
+fm_list_model_add_file (FMListModel *model, CajaFile *file,
+ CajaDirectory *directory)
+{
+ GtkTreeIter iter;
+ GtkTreePath *path;
+ FileEntry *file_entry;
+ GSequenceIter *ptr, *parent_ptr;
+ GSequence *files;
+ gboolean replace_dummy;
+ GHashTable *parent_hash;
+
+ parent_ptr = g_hash_table_lookup (model->details->directory_reverse_map,
+ directory);
+ if (parent_ptr)
+ {
+ file_entry = g_sequence_get (parent_ptr);
+ ptr = g_hash_table_lookup (file_entry->reverse_map, file);
+ }
+ else
+ {
+ file_entry = NULL;
+ ptr = g_hash_table_lookup (model->details->top_reverse_map, file);
+ }
+
+ if (ptr != NULL)
+ {
+ g_warning ("file already in tree (parent_ptr: %p)!!!\n", parent_ptr);
+ return FALSE;
+ }
+
+ file_entry = g_new0 (FileEntry, 1);
+ file_entry->file = caja_file_ref (file);
+ file_entry->parent = NULL;
+ file_entry->subdirectory = NULL;
+ file_entry->files = NULL;
+
+ files = model->details->files;
+ parent_hash = model->details->top_reverse_map;
+
+ replace_dummy = FALSE;
+
+ if (parent_ptr != NULL)
+ {
+ file_entry->parent = g_sequence_get (parent_ptr);
+ /* At this point we set loaded. Either we saw
+ * "done" and ignored it waiting for this, or we do this
+ * earlier, but then we replace the dummy row anyway,
+ * so it doesn't matter */
+ file_entry->parent->loaded = 1;
+ parent_hash = file_entry->parent->reverse_map;
+ files = file_entry->parent->files;
+ if (g_sequence_get_length (files) == 1)
+ {
+ GSequenceIter *dummy_ptr = g_sequence_get_iter_at_pos (files, 0);
+ FileEntry *dummy_entry = g_sequence_get (dummy_ptr);
+ if (dummy_entry->file == NULL)
+ {
+ /* replace the dummy loading entry */
+ model->details->stamp++;
+ g_sequence_remove (dummy_ptr);
+
+ replace_dummy = TRUE;
+ }
+ }
+ }
+
+
+ file_entry->ptr = g_sequence_insert_sorted (files, file_entry,
+ fm_list_model_file_entry_compare_func, model);
+
+ g_hash_table_insert (parent_hash, file, file_entry->ptr);
+
+ iter.stamp = model->details->stamp;
+ iter.user_data = file_entry->ptr;
+
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
+ if (replace_dummy)
+ {
+ gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter);
+ }
+ else
+ {
+ gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter);
+ }
+
+ if (caja_file_is_directory (file))
+ {
+ file_entry->files = g_sequence_new ((GDestroyNotify)file_entry_free);
+
+ add_dummy_row (model, file_entry);
+
+ gtk_tree_model_row_has_child_toggled (GTK_TREE_MODEL (model),
+ path, &iter);
+ }
+ gtk_tree_path_free (path);
+
+ return TRUE;
+}
+
+void
+fm_list_model_file_changed (FMListModel *model, CajaFile *file,
+ CajaDirectory *directory)
+{
+ FileEntry *parent_file_entry;
+ GtkTreeIter iter;
+ GtkTreePath *path, *parent_path;
+ GSequenceIter *ptr;
+ int pos_before, pos_after, length, i, old;
+ int *new_order;
+ gboolean has_iter;
+ GSequence *files;
+
+ ptr = lookup_file (model, file, directory);
+ if (!ptr)
+ {
+ return;
+ }
+
+
+ pos_before = g_sequence_iter_get_position (ptr);
+
+ g_sequence_sort_changed (ptr, fm_list_model_file_entry_compare_func, model);
+
+ pos_after = g_sequence_iter_get_position (ptr);
+
+ if (pos_before != pos_after)
+ {
+ /* The file moved, we need to send rows_reordered */
+
+ parent_file_entry = ((FileEntry *)g_sequence_get (ptr))->parent;
+
+ if (parent_file_entry == NULL)
+ {
+ has_iter = FALSE;
+ parent_path = gtk_tree_path_new ();
+ files = model->details->files;
+ }
+ else
+ {
+ has_iter = TRUE;
+ fm_list_model_ptr_to_iter (model, parent_file_entry->ptr, &iter);
+ parent_path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
+ files = parent_file_entry->files;
+ }
+
+ length = g_sequence_get_length (files);
+ new_order = g_new (int, length);
+ /* Note: new_order[newpos] = oldpos */
+ for (i = 0, old = 0; i < length; ++i)
+ {
+ if (i == pos_after)
+ {
+ new_order[i] = pos_before;
+ }
+ else
+ {
+ if (old == pos_before)
+ old++;
+ new_order[i] = old++;
+ }
+ }
+
+ gtk_tree_model_rows_reordered (GTK_TREE_MODEL (model),
+ parent_path, has_iter ? &iter : NULL, new_order);
+
+ gtk_tree_path_free (parent_path);
+ g_free (new_order);
+ }
+
+ fm_list_model_ptr_to_iter (model, ptr, &iter);
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
+ gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter);
+ gtk_tree_path_free (path);
+}
+
+gboolean
+fm_list_model_is_empty (FMListModel *model)
+{
+ return (g_sequence_get_length (model->details->files) == 0);
+}
+
+guint
+fm_list_model_get_length (FMListModel *model)
+{
+ return g_sequence_get_length (model->details->files);
+}
+
+static void
+fm_list_model_remove (FMListModel *model, GtkTreeIter *iter)
+{
+ GSequenceIter *ptr, *child_ptr;
+ FileEntry *file_entry, *child_file_entry, *parent_file_entry;
+ GtkTreePath *path;
+ GtkTreeIter parent_iter;
+
+ ptr = iter->user_data;
+ file_entry = g_sequence_get (ptr);
+
+ if (file_entry->files != NULL)
+ {
+ while (g_sequence_get_length (file_entry->files) > 0)
+ {
+ child_ptr = g_sequence_get_begin_iter (file_entry->files);
+ child_file_entry = g_sequence_get (child_ptr);
+ if (child_file_entry->file != NULL)
+ {
+ fm_list_model_remove_file (model,
+ child_file_entry->file,
+ file_entry->subdirectory);
+ }
+ else
+ {
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter);
+ gtk_tree_path_append_index (path, 0);
+ model->details->stamp++;
+ g_sequence_remove (child_ptr);
+ gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path);
+ gtk_tree_path_free (path);
+ }
+
+ /* the parent iter didn't actually change */
+ iter->stamp = model->details->stamp;
+ }
+
+ }
+
+ if (file_entry->file != NULL) /* Don't try to remove dummy row */
+ {
+ if (file_entry->parent != NULL)
+ {
+ g_hash_table_remove (file_entry->parent->reverse_map, file_entry->file);
+ }
+ else
+ {
+ g_hash_table_remove (model->details->top_reverse_map, file_entry->file);
+ }
+ }
+
+ parent_file_entry = file_entry->parent;
+ if (parent_file_entry && g_sequence_get_length (parent_file_entry->files) == 1 &&
+ file_entry->file != NULL)
+ {
+ /* this is the last non-dummy child, add a dummy node */
+ /* We need to do this before removing the last file to avoid
+ * collapsing the row.
+ */
+ add_dummy_row (model, parent_file_entry);
+ }
+
+ if (file_entry->subdirectory != NULL)
+ {
+ g_signal_emit (model,
+ list_model_signals[SUBDIRECTORY_UNLOADED], 0,
+ file_entry->subdirectory);
+ g_hash_table_remove (model->details->directory_reverse_map,
+ file_entry->subdirectory);
+ }
+
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter);
+
+ g_sequence_remove (ptr);
+ model->details->stamp++;
+ gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path);
+
+ gtk_tree_path_free (path);
+
+ if (parent_file_entry && g_sequence_get_length (parent_file_entry->files) == 0)
+ {
+ parent_iter.stamp = model->details->stamp;
+ parent_iter.user_data = parent_file_entry->ptr;
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &parent_iter);
+ gtk_tree_model_row_has_child_toggled (GTK_TREE_MODEL (model),
+ path, &parent_iter);
+ gtk_tree_path_free (path);
+ }
+}
+
+void
+fm_list_model_remove_file (FMListModel *model, CajaFile *file,
+ CajaDirectory *directory)
+{
+ GtkTreeIter iter;
+
+ if (fm_list_model_get_tree_iter_from_file (model, file, directory, &iter))
+ {
+ fm_list_model_remove (model, &iter);
+ }
+}
+
+static void
+fm_list_model_clear_directory (FMListModel *model, GSequence *files)
+{
+ GtkTreeIter iter;
+ FileEntry *file_entry;
+
+ while (g_sequence_get_length (files) > 0)
+ {
+ iter.user_data = g_sequence_get_begin_iter (files);
+
+ file_entry = g_sequence_get (iter.user_data);
+ if (file_entry->files != NULL)
+ {
+ fm_list_model_clear_directory (model, file_entry->files);
+ }
+
+ iter.stamp = model->details->stamp;
+ fm_list_model_remove (model, &iter);
+ }
+}
+
+void
+fm_list_model_clear (FMListModel *model)
+{
+ g_return_if_fail (model != NULL);
+
+ fm_list_model_clear_directory (model, model->details->files);
+}
+
+CajaFile *
+fm_list_model_file_for_path (FMListModel *model, GtkTreePath *path)
+{
+ CajaFile *file;
+ GtkTreeIter iter;
+
+ file = NULL;
+ if (gtk_tree_model_get_iter (GTK_TREE_MODEL (model),
+ &iter, path))
+ {
+ gtk_tree_model_get (GTK_TREE_MODEL (model),
+ &iter,
+ FM_LIST_MODEL_FILE_COLUMN, &file,
+ -1);
+ }
+ return file;
+}
+
+gboolean
+fm_list_model_load_subdirectory (FMListModel *model, GtkTreePath *path, CajaDirectory **directory)
+{
+ GtkTreeIter iter;
+ FileEntry *file_entry;
+ CajaDirectory *subdirectory;
+
+ if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path))
+ {
+ return FALSE;
+ }
+
+ file_entry = g_sequence_get (iter.user_data);
+ if (file_entry->file == NULL ||
+ file_entry->subdirectory != NULL)
+ {
+ return FALSE;
+ }
+
+ subdirectory = caja_directory_get_for_file (file_entry->file);
+
+ if (g_hash_table_lookup (model->details->directory_reverse_map,
+ subdirectory) != NULL)
+ {
+ caja_directory_unref (subdirectory);
+ g_warning ("Already in directory_reverse_map, failing\n");
+ return FALSE;
+ }
+
+ file_entry->subdirectory = subdirectory,
+ g_hash_table_insert (model->details->directory_reverse_map,
+ subdirectory, file_entry->ptr);
+ file_entry->reverse_map = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ /* Return a ref too */
+ caja_directory_ref (subdirectory);
+ *directory = subdirectory;
+
+ return TRUE;
+}
+
+/* removes all children of the subfolder and unloads the subdirectory */
+void
+fm_list_model_unload_subdirectory (FMListModel *model, GtkTreeIter *iter)
+{
+ GSequenceIter *child_ptr;
+ FileEntry *file_entry, *child_file_entry;
+ GtkTreeIter child_iter;
+
+ file_entry = g_sequence_get (iter->user_data);
+ if (file_entry->file == NULL ||
+ file_entry->subdirectory == NULL)
+ {
+ return;
+ }
+
+ file_entry->loaded = 0;
+
+ /* Remove all children */
+ while (g_sequence_get_length (file_entry->files) > 0)
+ {
+ child_ptr = g_sequence_get_begin_iter (file_entry->files);
+ child_file_entry = g_sequence_get (child_ptr);
+ if (child_file_entry->file == NULL)
+ {
+ /* Don't delete the dummy node */
+ break;
+ }
+ else
+ {
+ fm_list_model_ptr_to_iter (model, child_ptr, &child_iter);
+ fm_list_model_remove (model, &child_iter);
+ }
+ }
+
+ /* Emit unload signal */
+ g_signal_emit (model,
+ list_model_signals[SUBDIRECTORY_UNLOADED], 0,
+ file_entry->subdirectory);
+
+ /* actually unload */
+ g_hash_table_remove (model->details->directory_reverse_map,
+ file_entry->subdirectory);
+ caja_directory_unref (file_entry->subdirectory);
+ file_entry->subdirectory = NULL;
+
+ g_assert (g_hash_table_size (file_entry->reverse_map) == 0);
+ g_hash_table_destroy (file_entry->reverse_map);
+ file_entry->reverse_map = NULL;
+}
+
+
+
+void
+fm_list_model_set_should_sort_directories_first (FMListModel *model, gboolean sort_directories_first)
+{
+ if (model->details->sort_directories_first == sort_directories_first)
+ {
+ return;
+ }
+
+ model->details->sort_directories_first = sort_directories_first;
+ fm_list_model_sort (model);
+}
+
+int
+fm_list_model_get_sort_column_id_from_attribute (FMListModel *model,
+ GQuark attribute)
+{
+ guint i;
+
+ if (attribute == 0)
+ {
+ return -1;
+ }
+
+ /* Hack - the preferences dialog sets modification_date for some
+ * rather than date_modified for some reason. Make sure that
+ * works. */
+ if (attribute == attribute_modification_date_q)
+ {
+ attribute = attribute_date_modified_q;
+ }
+
+ for (i = 0; i < model->details->columns->len; i++)
+ {
+ CajaColumn *column;
+ GQuark column_attribute;
+
+ column =
+ CAJA_COLUMN (model->details->columns->pdata[i]);
+ g_object_get (G_OBJECT (column),
+ "attribute_q", &column_attribute,
+ NULL);
+ if (column_attribute == attribute)
+ {
+ return FM_LIST_MODEL_NUM_COLUMNS + i;
+ }
+ }
+
+ return -1;
+}
+
+GQuark
+fm_list_model_get_attribute_from_sort_column_id (FMListModel *model,
+ int sort_column_id)
+{
+ CajaColumn *column;
+ int index;
+ GQuark attribute;
+
+ index = sort_column_id - FM_LIST_MODEL_NUM_COLUMNS;
+
+ if (index < 0 || index >= model->details->columns->len)
+ {
+ g_warning ("unknown sort column id: %d", sort_column_id);
+ return 0;
+ }
+
+ column = CAJA_COLUMN (model->details->columns->pdata[index]);
+ g_object_get (G_OBJECT (column), "attribute_q", &attribute, NULL);
+
+ return attribute;
+}
+
+CajaZoomLevel
+fm_list_model_get_zoom_level_from_column_id (int column)
+{
+ switch (column)
+ {
+ case FM_LIST_MODEL_SMALLEST_ICON_COLUMN:
+ return CAJA_ZOOM_LEVEL_SMALLEST;
+ case FM_LIST_MODEL_SMALLER_ICON_COLUMN:
+ return CAJA_ZOOM_LEVEL_SMALLER;
+ case FM_LIST_MODEL_SMALL_ICON_COLUMN:
+ return CAJA_ZOOM_LEVEL_SMALL;
+ case FM_LIST_MODEL_STANDARD_ICON_COLUMN:
+ return CAJA_ZOOM_LEVEL_STANDARD;
+ case FM_LIST_MODEL_LARGE_ICON_COLUMN:
+ return CAJA_ZOOM_LEVEL_LARGE;
+ case FM_LIST_MODEL_LARGER_ICON_COLUMN:
+ return CAJA_ZOOM_LEVEL_LARGER;
+ case FM_LIST_MODEL_LARGEST_ICON_COLUMN:
+ return CAJA_ZOOM_LEVEL_LARGEST;
+ }
+
+ g_return_val_if_reached (CAJA_ZOOM_LEVEL_STANDARD);
+}
+
+int
+fm_list_model_get_column_id_from_zoom_level (CajaZoomLevel zoom_level)
+{
+ switch (zoom_level)
+ {
+ case CAJA_ZOOM_LEVEL_SMALLEST:
+ return FM_LIST_MODEL_SMALLEST_ICON_COLUMN;
+ case CAJA_ZOOM_LEVEL_SMALLER:
+ return FM_LIST_MODEL_SMALLER_ICON_COLUMN;
+ case CAJA_ZOOM_LEVEL_SMALL:
+ return FM_LIST_MODEL_SMALL_ICON_COLUMN;
+ case CAJA_ZOOM_LEVEL_STANDARD:
+ return FM_LIST_MODEL_STANDARD_ICON_COLUMN;
+ case CAJA_ZOOM_LEVEL_LARGE:
+ return FM_LIST_MODEL_LARGE_ICON_COLUMN;
+ case CAJA_ZOOM_LEVEL_LARGER:
+ return FM_LIST_MODEL_LARGER_ICON_COLUMN;
+ case CAJA_ZOOM_LEVEL_LARGEST:
+ return FM_LIST_MODEL_LARGEST_ICON_COLUMN;
+ }
+
+ g_return_val_if_reached (FM_LIST_MODEL_STANDARD_ICON_COLUMN);
+}
+
+CajaZoomLevel
+fm_list_model_get_zoom_level_from_emblem_column_id (int column)
+{
+ switch (column)
+ {
+ case FM_LIST_MODEL_SMALLEST_EMBLEM_COLUMN:
+ return CAJA_ZOOM_LEVEL_SMALLEST;
+ case FM_LIST_MODEL_SMALLER_EMBLEM_COLUMN:
+ return CAJA_ZOOM_LEVEL_SMALLER;
+ case FM_LIST_MODEL_SMALL_EMBLEM_COLUMN:
+ return CAJA_ZOOM_LEVEL_SMALL;
+ case FM_LIST_MODEL_STANDARD_EMBLEM_COLUMN:
+ return CAJA_ZOOM_LEVEL_STANDARD;
+ case FM_LIST_MODEL_LARGE_EMBLEM_COLUMN:
+ return CAJA_ZOOM_LEVEL_LARGE;
+ case FM_LIST_MODEL_LARGER_EMBLEM_COLUMN:
+ return CAJA_ZOOM_LEVEL_LARGER;
+ case FM_LIST_MODEL_LARGEST_EMBLEM_COLUMN:
+ return CAJA_ZOOM_LEVEL_LARGEST;
+ }
+
+ g_return_val_if_reached (CAJA_ZOOM_LEVEL_STANDARD);
+}
+
+int
+fm_list_model_get_emblem_column_id_from_zoom_level (CajaZoomLevel zoom_level)
+{
+ switch (zoom_level)
+ {
+ case CAJA_ZOOM_LEVEL_SMALLEST:
+ return FM_LIST_MODEL_SMALLEST_EMBLEM_COLUMN;
+ case CAJA_ZOOM_LEVEL_SMALLER:
+ return FM_LIST_MODEL_SMALLER_EMBLEM_COLUMN;
+ case CAJA_ZOOM_LEVEL_SMALL:
+ return FM_LIST_MODEL_SMALL_EMBLEM_COLUMN;
+ case CAJA_ZOOM_LEVEL_STANDARD:
+ return FM_LIST_MODEL_STANDARD_EMBLEM_COLUMN;
+ case CAJA_ZOOM_LEVEL_LARGE:
+ return FM_LIST_MODEL_LARGE_EMBLEM_COLUMN;
+ case CAJA_ZOOM_LEVEL_LARGER:
+ return FM_LIST_MODEL_LARGER_EMBLEM_COLUMN;
+ case CAJA_ZOOM_LEVEL_LARGEST:
+ return FM_LIST_MODEL_LARGEST_EMBLEM_COLUMN;
+ }
+
+ g_return_val_if_reached (FM_LIST_MODEL_STANDARD_EMBLEM_COLUMN);
+}
+
+void
+fm_list_model_set_drag_view (FMListModel *model,
+ GtkTreeView *view,
+ int drag_begin_x,
+ int drag_begin_y)
+{
+ g_return_if_fail (model != NULL);
+ g_return_if_fail (FM_IS_LIST_MODEL (model));
+ g_return_if_fail (!view || GTK_IS_TREE_VIEW (view));
+
+ model->details->drag_view = view;
+ model->details->drag_begin_x = drag_begin_x;
+ model->details->drag_begin_y = drag_begin_y;
+}
+
+GtkTargetList *
+fm_list_model_get_drag_target_list ()
+{
+ GtkTargetList *target_list;
+
+ target_list = gtk_target_list_new (drag_types, G_N_ELEMENTS (drag_types));
+ gtk_target_list_add_text_targets (target_list, CAJA_ICON_DND_TEXT);
+
+ return target_list;
+}
+
+int
+fm_list_model_add_column (FMListModel *model,
+ CajaColumn *column)
+{
+ g_ptr_array_add (model->details->columns, column);
+ g_object_ref (column);
+
+ return FM_LIST_MODEL_NUM_COLUMNS + (model->details->columns->len - 1);
+}
+
+int
+fm_list_model_get_column_number (FMListModel *model,
+ const char *column_name)
+{
+ int i;
+
+ for (i = 0; i < model->details->columns->len; i++)
+ {
+ CajaColumn *column;
+ char *name;
+
+ column = model->details->columns->pdata[i];
+
+ g_object_get (G_OBJECT (column), "name", &name, NULL);
+
+ if (!strcmp (name, column_name))
+ {
+ g_free (name);
+ return FM_LIST_MODEL_NUM_COLUMNS + i;
+ }
+ g_free (name);
+ }
+
+ return -1;
+}
+
+static void
+fm_list_model_dispose (GObject *object)
+{
+ FMListModel *model;
+ int i;
+
+ model = FM_LIST_MODEL (object);
+
+ if (model->details->columns)
+ {
+ for (i = 0; i < model->details->columns->len; i++)
+ {
+ g_object_unref (model->details->columns->pdata[i]);
+ }
+ g_ptr_array_free (model->details->columns, TRUE);
+ model->details->columns = NULL;
+ }
+
+ if (model->details->files)
+ {
+ g_sequence_free (model->details->files);
+ model->details->files = NULL;
+ }
+
+ if (model->details->top_reverse_map)
+ {
+ g_hash_table_destroy (model->details->top_reverse_map);
+ model->details->top_reverse_map = NULL;
+ }
+ if (model->details->directory_reverse_map)
+ {
+ g_hash_table_destroy (model->details->directory_reverse_map);
+ model->details->directory_reverse_map = NULL;
+ }
+
+ G_OBJECT_CLASS (fm_list_model_parent_class)->dispose (object);
+}
+
+static void
+fm_list_model_finalize (GObject *object)
+{
+ FMListModel *model;
+
+ model = FM_LIST_MODEL (object);
+
+ if (model->details->highlight_files != NULL)
+ {
+ caja_file_list_free (model->details->highlight_files);
+ model->details->highlight_files = NULL;
+ }
+
+ g_free (model->details);
+
+ G_OBJECT_CLASS (fm_list_model_parent_class)->finalize (object);
+}
+
+static void
+fm_list_model_init (FMListModel *model)
+{
+ model->details = g_new0 (FMListModelDetails, 1);
+ model->details->files = g_sequence_new ((GDestroyNotify)file_entry_free);
+ model->details->top_reverse_map = g_hash_table_new (g_direct_hash, g_direct_equal);
+ model->details->directory_reverse_map = g_hash_table_new (g_direct_hash, g_direct_equal);
+ model->details->stamp = g_random_int ();
+ model->details->sort_attribute = 0;
+ model->details->columns = g_ptr_array_new ();
+}
+
+static void
+fm_list_model_class_init (FMListModelClass *klass)
+{
+ GObjectClass *object_class;
+
+ attribute_name_q = g_quark_from_static_string ("name");
+ attribute_modification_date_q = g_quark_from_static_string ("modification_date");
+ attribute_date_modified_q = g_quark_from_static_string ("date_modified");
+
+ object_class = (GObjectClass *)klass;
+ object_class->finalize = fm_list_model_finalize;
+ object_class->dispose = fm_list_model_dispose;
+
+ list_model_signals[SUBDIRECTORY_UNLOADED] =
+ g_signal_new ("subdirectory_unloaded",
+ FM_TYPE_LIST_MODEL,
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (FMListModelClass, subdirectory_unloaded),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ CAJA_TYPE_DIRECTORY);
+}
+
+static void
+fm_list_model_tree_model_init (GtkTreeModelIface *iface)
+{
+ iface->get_flags = fm_list_model_get_flags;
+ iface->get_n_columns = fm_list_model_get_n_columns;
+ iface->get_column_type = fm_list_model_get_column_type;
+ iface->get_iter = fm_list_model_get_iter;
+ iface->get_path = fm_list_model_get_path;
+ iface->get_value = fm_list_model_get_value;
+ iface->iter_next = fm_list_model_iter_next;
+ iface->iter_children = fm_list_model_iter_children;
+ iface->iter_has_child = fm_list_model_iter_has_child;
+ iface->iter_n_children = fm_list_model_iter_n_children;
+ iface->iter_nth_child = fm_list_model_iter_nth_child;
+ iface->iter_parent = fm_list_model_iter_parent;
+}
+
+static void
+fm_list_model_sortable_init (GtkTreeSortableIface *iface)
+{
+ iface->get_sort_column_id = fm_list_model_get_sort_column_id;
+ iface->set_sort_column_id = fm_list_model_set_sort_column_id;
+ iface->has_default_sort_func = fm_list_model_has_default_sort_func;
+}
+
+static void
+fm_list_model_multi_drag_source_init (EggTreeMultiDragSourceIface *iface)
+{
+ iface->row_draggable = fm_list_model_multi_row_draggable;
+ iface->drag_data_get = fm_list_model_multi_drag_data_get;
+ iface->drag_data_delete = fm_list_model_multi_drag_data_delete;
+}
+
+void
+fm_list_model_subdirectory_done_loading (FMListModel *model, CajaDirectory *directory)
+{
+ GtkTreeIter iter;
+ GtkTreePath *path;
+ FileEntry *file_entry, *dummy_entry;
+ GSequenceIter *parent_ptr, *dummy_ptr;
+ GSequence *files;
+
+ if (model == NULL || model->details->directory_reverse_map == NULL)
+ {
+ return;
+ }
+ parent_ptr = g_hash_table_lookup (model->details->directory_reverse_map,
+ directory);
+ if (parent_ptr == NULL)
+ {
+ return;
+ }
+
+ file_entry = g_sequence_get (parent_ptr);
+ files = file_entry->files;
+
+ /* Only swap loading -> empty if we saw no files yet at "done",
+ * otherwise, toggle loading at first added file to the model.
+ */
+ if (!caja_directory_is_not_empty (directory) &&
+ g_sequence_get_length (files) == 1)
+ {
+ dummy_ptr = g_sequence_get_iter_at_pos (file_entry->files, 0);
+ dummy_entry = g_sequence_get (dummy_ptr);
+ if (dummy_entry->file == NULL)
+ {
+ /* was the dummy file */
+ file_entry->loaded = 1;
+
+ iter.stamp = model->details->stamp;
+ iter.user_data = dummy_ptr;
+
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
+ gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter);
+ gtk_tree_path_free (path);
+ }
+ }
+}
+
+static void
+refresh_row (gpointer data,
+ gpointer user_data)
+{
+ CajaFile *file;
+ FMListModel *model;
+ GList *iters, *l;
+ GtkTreePath *path;
+
+ model = user_data;
+ file = data;
+
+ iters = fm_list_model_get_all_iters_for_file (model, file);
+ for (l = iters; l != NULL; l = l->next)
+ {
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), l->data);
+ gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, l->data);
+
+ gtk_tree_path_free (path);
+ }
+
+ eel_g_list_free_deep (iters);
+}
+
+void
+fm_list_model_set_highlight_for_files (FMListModel *model,
+ GList *files)
+{
+ if (model->details->highlight_files != NULL)
+ {
+ g_list_foreach (model->details->highlight_files,
+ refresh_row, model);
+ caja_file_list_free (model->details->highlight_files);
+ model->details->highlight_files = NULL;
+ }
+
+ if (files != NULL)
+ {
+ model->details->highlight_files = caja_file_list_copy (files);
+ g_list_foreach (model->details->highlight_files,
+ refresh_row, model);
+
+ }
+}
diff --git a/src/file-manager/fm-list-model.h b/src/file-manager/fm-list-model.h
new file mode 100644
index 00000000..49f39078
--- /dev/null
+++ b/src/file-manager/fm-list-model.h
@@ -0,0 +1,148 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* fm-list-model.h - a GtkTreeModel for file lists.
+
+ Copyright (C) 2001, 2002 Anders Carlsson
+
+ The Mate Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Mate Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Anders Carlsson <[email protected]>
+*/
+
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+#include <libcaja-private/caja-file.h>
+#include <libcaja-private/caja-directory.h>
+#include <libcaja-extension/caja-column.h>
+
+#ifndef FM_LIST_MODEL_H
+#define FM_LIST_MODEL_H
+
+#define FM_TYPE_LIST_MODEL fm_list_model_get_type()
+#define FM_LIST_MODEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_LIST_MODEL, FMListModel))
+#define FM_LIST_MODEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_LIST_MODEL, FMListModelClass))
+#define FM_IS_LIST_MODEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_LIST_MODEL))
+#define FM_IS_LIST_MODEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_LIST_MODEL))
+#define FM_LIST_MODEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_LIST_MODEL, FMListModelClass))
+
+enum
+{
+ FM_LIST_MODEL_FILE_COLUMN,
+ FM_LIST_MODEL_SUBDIRECTORY_COLUMN,
+ FM_LIST_MODEL_SMALLEST_ICON_COLUMN,
+ FM_LIST_MODEL_SMALLER_ICON_COLUMN,
+ FM_LIST_MODEL_SMALL_ICON_COLUMN,
+ FM_LIST_MODEL_STANDARD_ICON_COLUMN,
+ FM_LIST_MODEL_LARGE_ICON_COLUMN,
+ FM_LIST_MODEL_LARGER_ICON_COLUMN,
+ FM_LIST_MODEL_LARGEST_ICON_COLUMN,
+ FM_LIST_MODEL_SMALLEST_EMBLEM_COLUMN,
+ FM_LIST_MODEL_SMALLER_EMBLEM_COLUMN,
+ FM_LIST_MODEL_SMALL_EMBLEM_COLUMN,
+ FM_LIST_MODEL_STANDARD_EMBLEM_COLUMN,
+ FM_LIST_MODEL_LARGE_EMBLEM_COLUMN,
+ FM_LIST_MODEL_LARGER_EMBLEM_COLUMN,
+ FM_LIST_MODEL_LARGEST_EMBLEM_COLUMN,
+ FM_LIST_MODEL_FILE_NAME_IS_EDITABLE_COLUMN,
+ FM_LIST_MODEL_NUM_COLUMNS
+};
+
+typedef struct FMListModelDetails FMListModelDetails;
+
+typedef struct FMListModel
+{
+ GObject parent_instance;
+ FMListModelDetails *details;
+} FMListModel;
+
+typedef struct
+{
+ GObjectClass parent_class;
+
+ void (* subdirectory_unloaded)(FMListModel *model,
+ CajaDirectory *subdirectory);
+} FMListModelClass;
+
+GType fm_list_model_get_type (void);
+gboolean fm_list_model_add_file (FMListModel *model,
+ CajaFile *file,
+ CajaDirectory *directory);
+void fm_list_model_file_changed (FMListModel *model,
+ CajaFile *file,
+ CajaDirectory *directory);
+gboolean fm_list_model_is_empty (FMListModel *model);
+guint fm_list_model_get_length (FMListModel *model);
+void fm_list_model_remove_file (FMListModel *model,
+ CajaFile *file,
+ CajaDirectory *directory);
+void fm_list_model_clear (FMListModel *model);
+gboolean fm_list_model_get_tree_iter_from_file (FMListModel *model,
+ CajaFile *file,
+ CajaDirectory *directory,
+ GtkTreeIter *iter);
+GList * fm_list_model_get_all_iters_for_file (FMListModel *model,
+ CajaFile *file);
+gboolean fm_list_model_get_first_iter_for_file (FMListModel *model,
+ CajaFile *file,
+ GtkTreeIter *iter);
+void fm_list_model_set_should_sort_directories_first (FMListModel *model,
+ gboolean sort_directories_first);
+
+int fm_list_model_get_sort_column_id_from_attribute (FMListModel *model,
+ GQuark attribute);
+GQuark fm_list_model_get_attribute_from_sort_column_id (FMListModel *model,
+ int sort_column_id);
+void fm_list_model_sort_files (FMListModel *model,
+ GList **files);
+
+CajaZoomLevel fm_list_model_get_zoom_level_from_column_id (int column);
+int fm_list_model_get_column_id_from_zoom_level (CajaZoomLevel zoom_level);
+CajaZoomLevel fm_list_model_get_zoom_level_from_emblem_column_id (int column);
+int fm_list_model_get_emblem_column_id_from_zoom_level (CajaZoomLevel zoom_level);
+
+CajaFile * fm_list_model_file_for_path (FMListModel *model, GtkTreePath *path);
+gboolean fm_list_model_load_subdirectory (FMListModel *model, GtkTreePath *path, CajaDirectory **directory);
+void fm_list_model_unload_subdirectory (FMListModel *model, GtkTreeIter *iter);
+
+void fm_list_model_set_drag_view (FMListModel *model,
+ GtkTreeView *view,
+ int begin_x,
+ int begin_y);
+
+GtkTargetList * fm_list_model_get_drag_target_list (void);
+
+int fm_list_model_compare_func (FMListModel *model,
+ CajaFile *file1,
+ CajaFile *file2);
+
+
+int fm_list_model_add_column (FMListModel *model,
+ CajaColumn *column);
+int fm_list_model_get_column_number (FMListModel *model,
+ const char *column_name);
+
+void fm_list_model_subdirectory_done_loading (FMListModel *model,
+ CajaDirectory *directory);
+
+void fm_list_model_set_highlight_for_files (FMListModel *model,
+ GList *files);
+
+#endif /* FM_LIST_MODEL_H */
diff --git a/src/file-manager/fm-list-view-private.h b/src/file-manager/fm-list-view-private.h
new file mode 100644
index 00000000..2225cfca
--- /dev/null
+++ b/src/file-manager/fm-list-view-private.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* fm-list-view-private.h - Private functions for both the list and search list
+ view to share
+
+ Copyright (C) 2000 Eazel, Inc.
+
+ The Mate Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Mate Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Rebecca Schulman <[email protected]>
+
+*/
+
+struct FMListViewColumn
+{
+ const char *attribute;
+ const char *title;
+ CajaFileSortType sort_criterion;
+ int minimum_width, default_width, maximum_width;
+ gboolean right_justified;
+};
+
+void fm_list_view_column_set (FMListViewColumn *column,
+ const char *attribute,
+ const char *title,
+ CajaFileSortType sort_criterion,
+ int minimum_width,
+ int default_width,
+ int maximum_width,
+ gboolean right_justified);
diff --git a/src/file-manager/fm-list-view.c b/src/file-manager/fm-list-view.c
new file mode 100644
index 00000000..1c82af9b
--- /dev/null
+++ b/src/file-manager/fm-list-view.c
@@ -0,0 +1,3415 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* fm-list-view.c - implementation of list view of directory.
+
+ Copyright (C) 2000 Eazel, Inc.
+ Copyright (C) 2001, 2002 Anders Carlsson <[email protected]>
+
+ The Mate Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Mate Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: John Sullivan <[email protected]>
+ Anders Carlsson <[email protected]>
+ David Emory Watson <[email protected]>
+*/
+
+#include <config.h>
+#include "fm-list-view.h"
+
+#include <string.h>
+#include "fm-error-reporting.h"
+#include "fm-list-model.h"
+#include <string.h>
+#include <eel/eel-vfs-extensions.h>
+#include <eel/eel-gdk-extensions.h>
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-gtk-macros.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+#include <libegg/eggtreemultidnd.h>
+#include <glib/gi18n.h>
+#include <glib-object.h>
+#include <libcaja-extension/caja-column-provider.h>
+#include <libcaja-private/caja-clipboard-monitor.h>
+#include <libcaja-private/caja-column-chooser.h>
+#include <libcaja-private/caja-column-utilities.h>
+#include <libcaja-private/caja-debug-log.h>
+#include <libcaja-private/caja-directory-background.h>
+#include <libcaja-private/caja-dnd.h>
+#include <libcaja-private/caja-file-dnd.h>
+#include <libcaja-private/caja-file-utilities.h>
+#include <libcaja-private/caja-ui-utilities.h>
+#include <libcaja-private/caja-global-preferences.h>
+#include <libcaja-private/caja-icon-dnd.h>
+#include <libcaja-private/caja-metadata.h>
+#include <libcaja-private/caja-module.h>
+#include <libcaja-private/caja-tree-view-drag-dest.h>
+#include <libcaja-private/caja-view-factory.h>
+#include <libcaja-private/caja-clipboard.h>
+#include <libcaja-private/caja-cell-renderer-pixbuf-emblem.h>
+#include <libcaja-private/caja-cell-renderer-text-ellipsized.h>
+
+struct FMListViewDetails
+{
+ GtkTreeView *tree_view;
+ FMListModel *model;
+ GtkActionGroup *list_action_group;
+ guint list_merge_id;
+
+ GtkTreeViewColumn *file_name_column;
+ int file_name_column_num;
+
+ GtkCellRendererPixbuf *pixbuf_cell;
+ GtkCellRendererText *file_name_cell;
+ GList *cells;
+ GtkCellEditable *editable_widget;
+
+ CajaZoomLevel zoom_level;
+
+ CajaTreeViewDragDest *drag_dest;
+
+ GtkTreePath *double_click_path[2]; /* Both clicks in a double click need to be on the same row */
+
+ GtkTreePath *new_selection_path; /* Path of the new selection after removing a file */
+
+ GtkTreePath *hover_path;
+
+ guint drag_button;
+ int drag_x;
+ int drag_y;
+
+ gboolean drag_started;
+
+ gboolean ignore_button_release;
+
+ gboolean row_selected_on_button_down;
+
+ gboolean menus_ready;
+
+ GHashTable *columns;
+ GtkWidget *column_editor;
+
+ char *original_name;
+
+ CajaFile *renaming_file;
+ gboolean rename_done;
+ guint renaming_file_activate_timeout;
+
+ gulong clipboard_handler_id;
+
+ GQuark last_sort_attr;
+};
+
+struct SelectionForeachData
+{
+ GList *list;
+ GtkTreeSelection *selection;
+};
+
+/*
+ * The row height should be large enough to not clip emblems.
+ * Computing this would be costly, so we just choose a number
+ * that works well with the set of emblems we've designed.
+ */
+#define LIST_VIEW_MINIMUM_ROW_HEIGHT 28
+
+/* We wait two seconds after row is collapsed to unload the subdirectory */
+#define COLLAPSE_TO_UNLOAD_DELAY 2
+
+/* Wait for the rename to end when activating a file being renamed */
+#define WAIT_FOR_RENAME_ON_ACTIVATE 200
+
+static int click_policy_auto_value;
+static char * default_sort_order_auto_value;
+static gboolean default_sort_reversed_auto_value;
+static CajaZoomLevel default_zoom_level_auto_value;
+static char ** default_visible_columns_auto_value;
+static char ** default_column_order_auto_value;
+static GdkCursor * hand_cursor = NULL;
+
+static GtkTargetList * source_target_list = NULL;
+
+static GList *fm_list_view_get_selection (FMDirectoryView *view);
+static GList *fm_list_view_get_selection_for_file_transfer (FMDirectoryView *view);
+static void fm_list_view_set_zoom_level (FMListView *view,
+ CajaZoomLevel new_level,
+ gboolean always_set_level);
+static void fm_list_view_scale_font_size (FMListView *view,
+ CajaZoomLevel new_level);
+static void fm_list_view_scroll_to_file (FMListView *view,
+ CajaFile *file);
+static void fm_list_view_iface_init (CajaViewIface *iface);
+static void fm_list_view_rename_callback (CajaFile *file,
+ GFile *result_location,
+ GError *error,
+ gpointer callback_data);
+
+
+G_DEFINE_TYPE_WITH_CODE (FMListView, fm_list_view, FM_TYPE_DIRECTORY_VIEW,
+ G_IMPLEMENT_INTERFACE (CAJA_TYPE_VIEW,
+ fm_list_view_iface_init));
+
+static const char * default_trash_visible_columns[] =
+{
+ "name", "size", "type", "trashed_on", "trash_orig_path", NULL
+};
+
+static const char * default_trash_columns_order[] =
+{
+ "name", "size", "type", "trashed_on", "trash_orig_path", NULL
+};
+
+/* for EEL_CALL_PARENT */
+#define parent_class fm_list_view_parent_class
+
+static const gchar*
+get_default_sort_order (CajaFile *file, gboolean *reversed)
+{
+ const gchar *retval;
+
+ retval = caja_file_get_default_sort_attribute (file, reversed);
+
+ if (retval == NULL)
+ {
+ retval = default_sort_order_auto_value;
+ *reversed = default_sort_reversed_auto_value;
+ }
+
+ return retval;
+}
+
+static void
+list_selection_changed_callback (GtkTreeSelection *selection, gpointer user_data)
+{
+ FMDirectoryView *view;
+
+ view = FM_DIRECTORY_VIEW (user_data);
+
+ fm_directory_view_notify_selection_changed (view);
+}
+
+/* Move these to eel? */
+
+static void
+tree_selection_foreach_set_boolean (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer callback_data)
+{
+ * (gboolean *) callback_data = TRUE;
+}
+
+static gboolean
+tree_selection_not_empty (GtkTreeSelection *selection)
+{
+ gboolean not_empty;
+
+ not_empty = FALSE;
+ gtk_tree_selection_selected_foreach (selection,
+ tree_selection_foreach_set_boolean,
+ &not_empty);
+ return not_empty;
+}
+
+static gboolean
+tree_view_has_selection (GtkTreeView *view)
+{
+ return tree_selection_not_empty (gtk_tree_view_get_selection (view));
+}
+
+static void
+activate_selected_items (FMListView *view)
+{
+ GList *file_list;
+
+ file_list = fm_list_view_get_selection (FM_DIRECTORY_VIEW (view));
+
+
+ if (view->details->renaming_file)
+ {
+ /* We're currently renaming a file, wait until the rename is
+ finished, or the activation uri will be wrong */
+ if (view->details->renaming_file_activate_timeout == 0)
+ {
+ view->details->renaming_file_activate_timeout =
+ g_timeout_add (WAIT_FOR_RENAME_ON_ACTIVATE, (GSourceFunc) activate_selected_items, view);
+ }
+ return;
+ }
+
+ if (view->details->renaming_file_activate_timeout != 0)
+ {
+ g_source_remove (view->details->renaming_file_activate_timeout);
+ view->details->renaming_file_activate_timeout = 0;
+ }
+
+ fm_directory_view_activate_files (FM_DIRECTORY_VIEW (view),
+ file_list,
+ CAJA_WINDOW_OPEN_ACCORDING_TO_MODE,
+ 0,
+ TRUE);
+ caja_file_list_free (file_list);
+
+}
+
+static void
+activate_selected_items_alternate (FMListView *view,
+ CajaFile *file,
+ gboolean open_in_tab)
+{
+ GList *file_list;
+ CajaWindowOpenFlags flags;
+
+ flags = CAJA_WINDOW_OPEN_FLAG_CLOSE_BEHIND;
+
+ if (open_in_tab)
+ {
+ flags |= CAJA_WINDOW_OPEN_FLAG_NEW_TAB;
+ }
+ else
+ {
+ flags |= CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW;
+ }
+
+ if (file != NULL)
+ {
+ caja_file_ref (file);
+ file_list = g_list_prepend (NULL, file);
+ }
+ else
+ {
+ file_list = fm_list_view_get_selection (FM_DIRECTORY_VIEW (view));
+ }
+ fm_directory_view_activate_files (FM_DIRECTORY_VIEW (view),
+ file_list,
+ CAJA_WINDOW_OPEN_ACCORDING_TO_MODE,
+ flags,
+ TRUE);
+ caja_file_list_free (file_list);
+
+}
+
+static gboolean
+button_event_modifies_selection (GdkEventButton *event)
+{
+ return (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) != 0;
+}
+
+static void
+fm_list_view_did_not_drag (FMListView *view,
+ GdkEventButton *event)
+{
+ GtkTreeView *tree_view;
+ GtkTreeSelection *selection;
+ GtkTreePath *path;
+
+ tree_view = view->details->tree_view;
+ selection = gtk_tree_view_get_selection (tree_view);
+
+ if (gtk_tree_view_get_path_at_pos (tree_view, event->x, event->y,
+ &path, NULL, NULL, NULL))
+ {
+ if ((event->button == 1 || event->button == 2)
+ && ((event->state & GDK_CONTROL_MASK) != 0 ||
+ (event->state & GDK_SHIFT_MASK) == 0)
+ && view->details->row_selected_on_button_down)
+ {
+ if (!button_event_modifies_selection (event))
+ {
+ gtk_tree_selection_unselect_all (selection);
+ gtk_tree_selection_select_path (selection, path);
+ }
+ else
+ {
+ gtk_tree_selection_unselect_path (selection, path);
+ }
+ }
+
+ if ((click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE)
+ && !button_event_modifies_selection(event))
+ {
+ if (event->button == 1)
+ {
+ activate_selected_items (view);
+ }
+ else if (event->button == 2)
+ {
+ activate_selected_items_alternate (view, NULL, TRUE);
+ }
+ }
+ gtk_tree_path_free (path);
+ }
+
+}
+
+static void
+drag_data_get_callback (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint time)
+{
+ GtkTreeView *tree_view;
+ GtkTreeModel *model;
+ GList *ref_list;
+
+ tree_view = GTK_TREE_VIEW (widget);
+
+ model = gtk_tree_view_get_model (tree_view);
+
+ if (model == NULL)
+ {
+ return;
+ }
+
+ ref_list = g_object_get_data (G_OBJECT (context), "drag-info");
+
+ if (ref_list == NULL)
+ {
+ return;
+ }
+
+ if (EGG_IS_TREE_MULTI_DRAG_SOURCE (model))
+ {
+ egg_tree_multi_drag_source_drag_data_get (EGG_TREE_MULTI_DRAG_SOURCE (model),
+ ref_list,
+ selection_data);
+ }
+}
+
+static void
+filtered_selection_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ struct SelectionForeachData *selection_data;
+ GtkTreeIter parent;
+ GtkTreeIter child;
+
+ selection_data = data;
+
+ /* If the parent folder is also selected, don't include this file in the
+ * file operation, since that would copy it to the toplevel target instead
+ * of keeping it as a child of the copied folder
+ */
+ child = *iter;
+ while (gtk_tree_model_iter_parent (model, &parent, &child))
+ {
+ if (gtk_tree_selection_iter_is_selected (selection_data->selection,
+ &parent))
+ {
+ return;
+ }
+ child = parent;
+ }
+
+ selection_data->list = g_list_prepend (selection_data->list,
+ gtk_tree_row_reference_new (model, path));
+}
+
+static GList *
+get_filtered_selection_refs (GtkTreeView *tree_view)
+{
+ struct SelectionForeachData selection_data;
+
+ selection_data.list = NULL;
+ selection_data.selection = gtk_tree_view_get_selection (tree_view);
+
+ gtk_tree_selection_selected_foreach (selection_data.selection,
+ filtered_selection_foreach,
+ &selection_data);
+ return g_list_reverse (selection_data.list);
+}
+
+static void
+ref_list_free (GList *ref_list)
+{
+ g_list_foreach (ref_list, (GFunc) gtk_tree_row_reference_free, NULL);
+ g_list_free (ref_list);
+}
+
+static void
+stop_drag_check (FMListView *view)
+{
+ view->details->drag_button = 0;
+}
+
+static GdkPixbuf *
+get_drag_pixbuf (FMListView *view)
+{
+ GtkTreeModel *model;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ GdkPixbuf *ret;
+ GdkRectangle cell_area;
+
+ ret = NULL;
+
+ if (gtk_tree_view_get_path_at_pos (view->details->tree_view,
+ view->details->drag_x,
+ view->details->drag_y,
+ &path, NULL, NULL, NULL))
+ {
+ model = gtk_tree_view_get_model (view->details->tree_view);
+ gtk_tree_model_get_iter (model, &iter, path);
+ gtk_tree_model_get (model, &iter,
+ fm_list_model_get_column_id_from_zoom_level (view->details->zoom_level),
+ &ret,
+ -1);
+
+ gtk_tree_view_get_cell_area (view->details->tree_view,
+ path,
+ view->details->file_name_column,
+ &cell_area);
+
+ gtk_tree_path_free (path);
+ }
+
+ return ret;
+}
+
+static void
+drag_begin_callback (GtkWidget *widget,
+ GdkDragContext *context,
+ FMListView *view)
+{
+ GList *ref_list;
+ GdkPixbuf *pixbuf;
+
+ pixbuf = get_drag_pixbuf (view);
+ if (pixbuf)
+ {
+ gtk_drag_set_icon_pixbuf (context,
+ pixbuf,
+ 0, 0);
+ g_object_unref (pixbuf);
+ }
+ else
+ {
+ gtk_drag_set_icon_default (context);
+ }
+
+ stop_drag_check (view);
+ view->details->drag_started = TRUE;
+
+ ref_list = get_filtered_selection_refs (GTK_TREE_VIEW (widget));
+ g_object_set_data_full (G_OBJECT (context),
+ "drag-info",
+ ref_list,
+ (GDestroyNotify)ref_list_free);
+}
+
+static gboolean
+motion_notify_callback (GtkWidget *widget,
+ GdkEventMotion *event,
+ gpointer callback_data)
+{
+ FMListView *view;
+ GdkDragContext *context;
+
+ view = FM_LIST_VIEW (callback_data);
+
+ if (event->window != gtk_tree_view_get_bin_window (GTK_TREE_VIEW (widget)))
+ {
+ return FALSE;
+ }
+
+ if (click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE)
+ {
+ GtkTreePath *old_hover_path;
+
+ old_hover_path = view->details->hover_path;
+ gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
+ event->x, event->y,
+ &view->details->hover_path,
+ NULL, NULL, NULL);
+
+ if ((old_hover_path != NULL) != (view->details->hover_path != NULL))
+ {
+ if (view->details->hover_path != NULL)
+ {
+ gdk_window_set_cursor (gtk_widget_get_window (widget), hand_cursor);
+ }
+ else
+ {
+ gdk_window_set_cursor (gtk_widget_get_window (widget), NULL);
+ }
+ }
+
+ if (old_hover_path != NULL)
+ {
+ gtk_tree_path_free (old_hover_path);
+ }
+ }
+
+ if (view->details->drag_button != 0)
+ {
+ if (!source_target_list)
+ {
+ source_target_list = fm_list_model_get_drag_target_list ();
+ }
+
+ if (gtk_drag_check_threshold (widget,
+ view->details->drag_x,
+ view->details->drag_y,
+ event->x,
+ event->y))
+ {
+ context = gtk_drag_begin
+ (widget,
+ source_target_list,
+ GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK | GDK_ACTION_ASK,
+ view->details->drag_button,
+ (GdkEvent*)event);
+ }
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+leave_notify_callback (GtkWidget *widget,
+ GdkEventCrossing *event,
+ gpointer callback_data)
+{
+ FMListView *view;
+
+ view = FM_LIST_VIEW (callback_data);
+
+ if (click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE &&
+ view->details->hover_path != NULL)
+ {
+ gtk_tree_path_free (view->details->hover_path);
+ view->details->hover_path = NULL;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+enter_notify_callback (GtkWidget *widget,
+ GdkEventCrossing *event,
+ gpointer callback_data)
+{
+ FMListView *view;
+
+ view = FM_LIST_VIEW (callback_data);
+
+ if (click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE)
+ {
+ if (view->details->hover_path != NULL)
+ {
+ gtk_tree_path_free (view->details->hover_path);
+ }
+
+ gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
+ event->x, event->y,
+ &view->details->hover_path,
+ NULL, NULL, NULL);
+
+ if (view->details->hover_path != NULL)
+ {
+ gdk_window_set_cursor (gtk_widget_get_window (widget), hand_cursor);
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+do_popup_menu (GtkWidget *widget, FMListView *view, GdkEventButton *event)
+{
+ if (tree_view_has_selection (GTK_TREE_VIEW (widget)))
+ {
+ fm_directory_view_pop_up_selection_context_menu (FM_DIRECTORY_VIEW (view), event);
+ }
+ else
+ {
+ fm_directory_view_pop_up_background_context_menu (FM_DIRECTORY_VIEW (view), event);
+ }
+}
+
+static gboolean
+button_press_callback (GtkWidget *widget, GdkEventButton *event, gpointer callback_data)
+{
+ FMListView *view;
+ GtkTreeView *tree_view;
+ GtkTreePath *path;
+ gboolean call_parent;
+ gboolean allow_drag;
+ GtkTreeSelection *selection;
+ GtkWidgetClass *tree_view_class;
+ gint64 current_time;
+ static gint64 last_click_time = 0;
+ static int click_count = 0;
+ int double_click_time;
+ int expander_size, horizontal_separator;
+ gboolean on_expander;
+
+ view = FM_LIST_VIEW (callback_data);
+ tree_view = GTK_TREE_VIEW (widget);
+ tree_view_class = GTK_WIDGET_GET_CLASS (tree_view);
+ selection = gtk_tree_view_get_selection (tree_view);
+
+ if (event->window != gtk_tree_view_get_bin_window (tree_view))
+ {
+ return FALSE;
+ }
+
+ fm_list_model_set_drag_view
+ (FM_LIST_MODEL (gtk_tree_view_get_model (tree_view)),
+ tree_view,
+ event->x, event->y);
+
+ g_object_get (G_OBJECT (gtk_widget_get_settings (widget)),
+ "gtk-double-click-time", &double_click_time,
+ NULL);
+
+ /* Determine click count */
+ current_time = eel_get_system_time ();
+ if (current_time - last_click_time < double_click_time * 1000)
+ {
+ click_count++;
+ }
+ else
+ {
+ click_count = 0;
+ }
+
+ /* Stash time for next compare */
+ last_click_time = current_time;
+
+ /* Ignore double click if we are in single click mode */
+ if (click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE && click_count >= 2)
+ {
+ return TRUE;
+ }
+
+ view->details->ignore_button_release = FALSE;
+
+ call_parent = TRUE;
+ allow_drag = FALSE;
+ if (gtk_tree_view_get_path_at_pos (tree_view, event->x, event->y,
+ &path, NULL, NULL, NULL))
+ {
+ gtk_widget_style_get (widget,
+ "expander-size", &expander_size,
+ "horizontal-separator", &horizontal_separator,
+ NULL);
+ /* TODO we should not hardcode this extra padding. It is
+ * EXPANDER_EXTRA_PADDING from GtkTreeView.
+ */
+ expander_size += 4;
+ on_expander = (event->x <= horizontal_separator / 2 +
+ gtk_tree_path_get_depth (path) * expander_size);
+
+ /* Keep track of path of last click so double clicks only happen
+ * on the same item */
+ if ((event->button == 1 || event->button == 2) &&
+ event->type == GDK_BUTTON_PRESS)
+ {
+ if (view->details->double_click_path[1])
+ {
+ gtk_tree_path_free (view->details->double_click_path[1]);
+ }
+ view->details->double_click_path[1] = view->details->double_click_path[0];
+ view->details->double_click_path[0] = gtk_tree_path_copy (path);
+ }
+ if (event->type == GDK_2BUTTON_PRESS)
+ {
+ /* Double clicking does not trigger a D&D action. */
+ view->details->drag_button = 0;
+ if (view->details->double_click_path[1] &&
+ gtk_tree_path_compare (view->details->double_click_path[0], view->details->double_click_path[1]) == 0 &&
+ !on_expander)
+ {
+ /* NOTE: Activation can actually destroy the view if we're switching */
+ if (!button_event_modifies_selection (event))
+ {
+ if ((event->button == 1 || event->button == 3))
+ {
+ activate_selected_items (view);
+ }
+ else if (event->button == 2)
+ {
+ activate_selected_items_alternate (view, NULL, TRUE);
+ }
+ }
+ else if (event->button == 1 &&
+ (event->state & GDK_SHIFT_MASK) != 0)
+ {
+ CajaFile *file;
+ file = fm_list_model_file_for_path (view->details->model, path);
+ if (file != NULL)
+ {
+ activate_selected_items_alternate (view, file, TRUE);
+ caja_file_unref (file);
+ }
+ }
+ }
+ else
+ {
+ tree_view_class->button_press_event (widget, event);
+ }
+ }
+ else
+ {
+
+ /* We're going to filter out some situations where
+ * we can't let the default code run because all
+ * but one row would be would be deselected. We don't
+ * want that; we want the right click menu or single
+ * click to apply to everything that's currently selected. */
+
+ if (event->button == 3 && gtk_tree_selection_path_is_selected (selection, path))
+ {
+ call_parent = FALSE;
+ }
+
+ if ((event->button == 1 || event->button == 2) &&
+ ((event->state & GDK_CONTROL_MASK) != 0 ||
+ (event->state & GDK_SHIFT_MASK) == 0))
+ {
+ view->details->row_selected_on_button_down = gtk_tree_selection_path_is_selected (selection, path);
+ if (view->details->row_selected_on_button_down)
+ {
+ call_parent = on_expander;
+ view->details->ignore_button_release = call_parent;
+ }
+ else if ((event->state & GDK_CONTROL_MASK) != 0)
+ {
+ GList *selected_rows;
+ GList *l;
+
+ call_parent = FALSE;
+ if ((event->state & GDK_SHIFT_MASK) != 0)
+ {
+ GtkTreePath *cursor;
+ gtk_tree_view_get_cursor (tree_view, &cursor, NULL);
+ if (cursor != NULL)
+ {
+ gtk_tree_selection_select_range (selection, cursor, path);
+ }
+ else
+ {
+ gtk_tree_selection_select_path (selection, path);
+ }
+ }
+ else
+ {
+ gtk_tree_selection_select_path (selection, path);
+ }
+ selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);
+
+ /* This unselects everything */
+ gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
+
+ /* So select it again */
+ l = selected_rows;
+ while (l != NULL)
+ {
+ GtkTreePath *p = l->data;
+ l = l->next;
+ gtk_tree_selection_select_path (selection, p);
+ gtk_tree_path_free (p);
+ }
+ g_list_free (selected_rows);
+ }
+ else
+ {
+ view->details->ignore_button_release = on_expander;
+ }
+ }
+
+ if (call_parent)
+ {
+ tree_view_class->button_press_event (widget, event);
+ }
+ else if (gtk_tree_selection_path_is_selected (selection, path))
+ {
+ gtk_widget_grab_focus (widget);
+ }
+
+ if ((event->button == 1 || event->button == 2) &&
+ event->type == GDK_BUTTON_PRESS)
+ {
+ view->details->drag_started = FALSE;
+ view->details->drag_button = event->button;
+ view->details->drag_x = event->x;
+ view->details->drag_y = event->y;
+ }
+
+ if (event->button == 3)
+ {
+ do_popup_menu (widget, view, event);
+ }
+ }
+
+ gtk_tree_path_free (path);
+ }
+ else
+ {
+ if ((event->button == 1 || event->button == 2) &&
+ event->type == GDK_BUTTON_PRESS)
+ {
+ if (view->details->double_click_path[1])
+ {
+ gtk_tree_path_free (view->details->double_click_path[1]);
+ }
+ view->details->double_click_path[1] = view->details->double_click_path[0];
+ view->details->double_click_path[0] = NULL;
+ }
+ /* Deselect if people click outside any row. It's OK to
+ let default code run; it won't reselect anything. */
+ gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (tree_view));
+ tree_view_class->button_press_event (widget, event);
+
+ if (event->button == 3)
+ {
+ do_popup_menu (widget, view, event);
+ }
+ }
+
+ /* We chained to the default handler in this method, so never
+ * let the default handler run */
+ return TRUE;
+}
+
+static gboolean
+button_release_callback (GtkWidget *widget,
+ GdkEventButton *event,
+ gpointer callback_data)
+{
+ FMListView *view;
+
+ view = FM_LIST_VIEW (callback_data);
+
+ if (event->button == view->details->drag_button)
+ {
+ stop_drag_check (view);
+ if (!view->details->drag_started &&
+ !view->details->ignore_button_release)
+ {
+ fm_list_view_did_not_drag (view, event);
+ }
+ }
+ return FALSE;
+}
+
+static gboolean
+popup_menu_callback (GtkWidget *widget, gpointer callback_data)
+{
+ FMListView *view;
+
+ view = FM_LIST_VIEW (callback_data);
+
+ do_popup_menu (widget, view, NULL);
+
+ return TRUE;
+}
+
+static void
+subdirectory_done_loading_callback (CajaDirectory *directory, FMListView *view)
+{
+ fm_list_model_subdirectory_done_loading (view->details->model, directory);
+}
+
+static void
+row_expanded_callback (GtkTreeView *treeview, GtkTreeIter *iter, GtkTreePath *path, gpointer callback_data)
+{
+ FMListView *view;
+ CajaDirectory *directory;
+
+ view = FM_LIST_VIEW (callback_data);
+
+ if (fm_list_model_load_subdirectory (view->details->model, path, &directory))
+ {
+ char *uri;
+
+ uri = caja_directory_get_uri (directory);
+ caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
+ "list view row expanded window=%p: %s",
+ fm_directory_view_get_containing_window (FM_DIRECTORY_VIEW (view)),
+ uri);
+ g_free (uri);
+
+ fm_directory_view_add_subdirectory (FM_DIRECTORY_VIEW (view), directory);
+
+ if (caja_directory_are_all_files_seen (directory))
+ {
+ fm_list_model_subdirectory_done_loading (view->details->model,
+ directory);
+ }
+ else
+ {
+ g_signal_connect_object (directory, "done_loading",
+ G_CALLBACK (subdirectory_done_loading_callback),
+ view, 0);
+ }
+
+ caja_directory_unref (directory);
+ }
+}
+
+struct UnloadDelayData
+{
+ CajaFile *file;
+ CajaDirectory *directory;
+ FMListView *view;
+};
+
+static gboolean
+unload_file_timeout (gpointer data)
+{
+ struct UnloadDelayData *unload_data = data;
+ GtkTreeIter iter;
+ FMListModel *model;
+ GtkTreePath *path;
+
+ if (unload_data->view != NULL)
+ {
+ model = unload_data->view->details->model;
+ if (fm_list_model_get_tree_iter_from_file (model,
+ unload_data->file,
+ unload_data->directory,
+ &iter))
+ {
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
+ if (!gtk_tree_view_row_expanded (unload_data->view->details->tree_view,
+ path))
+ {
+ fm_list_model_unload_subdirectory (model, &iter);
+ }
+ gtk_tree_path_free (path);
+ }
+ }
+
+ eel_remove_weak_pointer (&unload_data->view);
+
+ if (unload_data->directory)
+ {
+ caja_directory_unref (unload_data->directory);
+ }
+ caja_file_unref (unload_data->file);
+ g_free (unload_data);
+ return FALSE;
+}
+
+static void
+row_collapsed_callback (GtkTreeView *treeview, GtkTreeIter *iter, GtkTreePath *path, gpointer callback_data)
+{
+ FMListView *view;
+ CajaFile *file;
+ CajaDirectory *directory;
+ GtkTreeIter parent;
+ struct UnloadDelayData *unload_data;
+ GtkTreeModel *model;
+ char *uri;
+
+ view = FM_LIST_VIEW (callback_data);
+ model = GTK_TREE_MODEL (view->details->model);
+
+ gtk_tree_model_get (model, iter,
+ FM_LIST_MODEL_FILE_COLUMN, &file,
+ -1);
+
+ directory = NULL;
+ if (gtk_tree_model_iter_parent (model, &parent, iter))
+ {
+ gtk_tree_model_get (model, &parent,
+ FM_LIST_MODEL_SUBDIRECTORY_COLUMN, &directory,
+ -1);
+ }
+
+
+ uri = caja_file_get_uri (file);
+ caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
+ "list view row collapsed window=%p: %s",
+ fm_directory_view_get_containing_window (FM_DIRECTORY_VIEW (view)),
+ uri);
+ g_free (uri);
+
+ unload_data = g_new (struct UnloadDelayData, 1);
+ unload_data->view = view;
+ unload_data->file = file;
+ unload_data->directory = directory;
+
+ eel_add_weak_pointer (&unload_data->view);
+
+ g_timeout_add_seconds (COLLAPSE_TO_UNLOAD_DELAY,
+ unload_file_timeout,
+ unload_data);
+}
+
+static void
+row_activated_callback (GtkTreeView *treeview, GtkTreePath *path,
+ GtkTreeViewColumn *column, FMListView *view)
+{
+ activate_selected_items (view);
+}
+
+static void
+subdirectory_unloaded_callback (FMListModel *model,
+ CajaDirectory *directory,
+ gpointer callback_data)
+{
+ FMListView *view;
+
+ g_return_if_fail (FM_IS_LIST_MODEL (model));
+ g_return_if_fail (CAJA_IS_DIRECTORY (directory));
+
+ view = FM_LIST_VIEW(callback_data);
+
+ g_signal_handlers_disconnect_by_func (directory,
+ G_CALLBACK (subdirectory_done_loading_callback),
+ view);
+ fm_directory_view_remove_subdirectory (FM_DIRECTORY_VIEW (view), directory);
+}
+
+static gboolean
+key_press_callback (GtkWidget *widget, GdkEventKey *event, gpointer callback_data)
+{
+ FMDirectoryView *view;
+ GdkEventButton button_event = { 0 };
+ gboolean handled;
+ GtkTreeView *tree_view;
+ GtkTreePath *path;
+
+ tree_view = GTK_TREE_VIEW (widget);
+
+ view = FM_DIRECTORY_VIEW (callback_data);
+ handled = FALSE;
+
+ switch (event->keyval)
+ {
+ case GDK_F10:
+ if (event->state & GDK_CONTROL_MASK)
+ {
+ fm_directory_view_pop_up_background_context_menu (view, &button_event);
+ handled = TRUE;
+ }
+ break;
+ case GDK_Right:
+ gtk_tree_view_get_cursor (tree_view, &path, NULL);
+ if (path)
+ {
+ gtk_tree_view_expand_row (tree_view, path, FALSE);
+ gtk_tree_path_free (path);
+ }
+ handled = TRUE;
+ break;
+ case GDK_Left:
+ gtk_tree_view_get_cursor (tree_view, &path, NULL);
+ if (path)
+ {
+ gtk_tree_view_collapse_row (tree_view, path);
+ gtk_tree_path_free (path);
+ }
+ handled = TRUE;
+ break;
+ case GDK_space:
+ if (event->state & GDK_CONTROL_MASK)
+ {
+ handled = FALSE;
+ break;
+ }
+ if (!gtk_widget_has_focus (GTK_WIDGET (FM_LIST_VIEW (view)->details->tree_view)))
+ {
+ handled = FALSE;
+ break;
+ }
+ if ((event->state & GDK_SHIFT_MASK) != 0)
+ {
+ activate_selected_items_alternate (FM_LIST_VIEW (view), NULL, TRUE);
+ }
+ else
+ {
+ activate_selected_items (FM_LIST_VIEW (view));
+ }
+ handled = TRUE;
+ break;
+ case GDK_Return:
+ case GDK_KP_Enter:
+ if ((event->state & GDK_SHIFT_MASK) != 0)
+ {
+ activate_selected_items_alternate (FM_LIST_VIEW (view), NULL, TRUE);
+ }
+ else
+ {
+ activate_selected_items (FM_LIST_VIEW (view));
+ }
+ handled = TRUE;
+ break;
+ case GDK_v:
+ /* Eat Control + v to not enable type ahead */
+ if ((event->state & GDK_CONTROL_MASK) != 0)
+ {
+ handled = TRUE;
+ }
+ break;
+
+ default:
+ handled = FALSE;
+ }
+
+ return handled;
+}
+
+static void
+fm_list_view_reveal_selection (FMDirectoryView *view)
+{
+ GList *selection;
+
+ g_return_if_fail (FM_IS_LIST_VIEW (view));
+
+ selection = fm_directory_view_get_selection (view);
+
+ /* Make sure at least one of the selected items is scrolled into view */
+ if (selection != NULL)
+ {
+ FMListView *list_view;
+ CajaFile *file;
+ GtkTreeIter iter;
+ GtkTreePath *path;
+
+ list_view = FM_LIST_VIEW (view);
+ file = selection->data;
+ if (fm_list_model_get_first_iter_for_file (list_view->details->model, file, &iter))
+ {
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (list_view->details->model), &iter);
+
+ gtk_tree_view_scroll_to_cell (list_view->details->tree_view, path, NULL, FALSE, 0.0, 0.0);
+
+ gtk_tree_path_free (path);
+ }
+ }
+
+ caja_file_list_free (selection);
+}
+
+static gboolean
+sort_criterion_changes_due_to_user (GtkTreeView *tree_view)
+{
+ GList *columns, *p;
+ GtkTreeViewColumn *column;
+ GSignalInvocationHint *ihint;
+ unsigned int sort_signal_id;
+ gboolean ret;
+
+ sort_signal_id = g_signal_lookup ("clicked", gtk_tree_view_column_get_type ());
+
+ ret = FALSE;
+
+ columns = gtk_tree_view_get_columns (tree_view);
+ for (p = columns; p != NULL; p = p->next)
+ {
+ column = p->data;
+ ihint = g_signal_get_invocation_hint (column);
+ if (ihint != NULL)
+ {
+ ret = TRUE;
+ break;
+ }
+ }
+ g_list_free (columns);
+
+ return ret;
+}
+
+static void
+sort_column_changed_callback (GtkTreeSortable *sortable,
+ FMListView *view)
+{
+ CajaFile *file;
+ gint sort_column_id, default_sort_column_id;
+ GtkSortType reversed;
+ GQuark sort_attr, default_sort_attr;
+ char *reversed_attr, *default_reversed_attr;
+ gboolean default_sort_reversed;
+
+ file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (view));
+
+ gtk_tree_sortable_get_sort_column_id (sortable, &sort_column_id, &reversed);
+ sort_attr = fm_list_model_get_attribute_from_sort_column_id (view->details->model, sort_column_id);
+
+ default_sort_column_id = fm_list_model_get_sort_column_id_from_attribute (view->details->model,
+ g_quark_from_string (get_default_sort_order (file, &default_sort_reversed)));
+ default_sort_attr = fm_list_model_get_attribute_from_sort_column_id (view->details->model, default_sort_column_id);
+ caja_file_set_metadata (file, CAJA_METADATA_KEY_LIST_VIEW_SORT_COLUMN,
+ g_quark_to_string (default_sort_attr), g_quark_to_string (sort_attr));
+
+ default_reversed_attr = (default_sort_reversed ? "true" : "false");
+
+ if (view->details->last_sort_attr != sort_attr &&
+ sort_criterion_changes_due_to_user (view->details->tree_view))
+ {
+ /* at this point, the sort order is always GTK_SORT_ASCENDING, if the sort column ID
+ * switched. Invert the sort order, if it's the default criterion with a reversed preference,
+ * or if it makes sense for the attribute (i.e. date). */
+ if (sort_attr == default_sort_attr)
+ {
+ /* use value from preferences */
+ reversed = eel_preferences_get_boolean (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_SORT_IN_REVERSE_ORDER);
+ }
+ else
+ {
+ reversed = caja_file_is_date_sort_attribute_q (sort_attr);
+ }
+
+ if (reversed)
+ {
+ g_signal_handlers_block_by_func (sortable, sort_column_changed_callback, view);
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (view->details->model),
+ sort_column_id,
+ GTK_SORT_DESCENDING);
+ g_signal_handlers_unblock_by_func (sortable, sort_column_changed_callback, view);
+ }
+ }
+
+
+ reversed_attr = (reversed ? "true" : "false");
+ caja_file_set_metadata (file, CAJA_METADATA_KEY_LIST_VIEW_SORT_REVERSED,
+ default_reversed_attr, reversed_attr);
+
+ /* Make sure selected item(s) is visible after sort */
+ fm_list_view_reveal_selection (FM_DIRECTORY_VIEW (view));
+
+ view->details->last_sort_attr = sort_attr;
+}
+
+static void
+cell_renderer_editing_started_cb (GtkCellRenderer *renderer,
+ GtkCellEditable *editable,
+ const gchar *path_str,
+ FMListView *list_view)
+{
+ GtkEntry *entry;
+ gint start_offset, end_offset;
+
+ entry = GTK_ENTRY (editable);
+ list_view->details->editable_widget = editable;
+
+ /* Free a previously allocated original_name */
+ g_free (list_view->details->original_name);
+
+ list_view->details->original_name = g_strdup (gtk_entry_get_text (entry));
+ eel_filename_get_rename_region (list_view->details->original_name,
+ &start_offset, &end_offset);
+ gtk_editable_select_region (GTK_EDITABLE (entry), start_offset, end_offset);
+
+ caja_clipboard_set_up_editable
+ (GTK_EDITABLE (entry),
+ fm_directory_view_get_ui_manager (FM_DIRECTORY_VIEW (list_view)),
+ FALSE);
+}
+
+static void
+cell_renderer_editing_canceled (GtkCellRendererText *cell,
+ FMListView *view)
+{
+ view->details->editable_widget = NULL;
+
+ fm_directory_view_unfreeze_updates (FM_DIRECTORY_VIEW (view));
+}
+
+static void
+cell_renderer_edited (GtkCellRendererText *cell,
+ const char *path_str,
+ const char *new_text,
+ FMListView *view)
+{
+ GtkTreePath *path;
+ CajaFile *file;
+ GtkTreeIter iter;
+
+ view->details->editable_widget = NULL;
+
+ /* Don't allow a rename with an empty string. Revert to original
+ * without notifying the user.
+ */
+ if (new_text[0] == '\0')
+ {
+ g_object_set (G_OBJECT (view->details->file_name_cell),
+ "editable", FALSE,
+ NULL);
+ fm_directory_view_unfreeze_updates (FM_DIRECTORY_VIEW (view));
+ return;
+ }
+
+ path = gtk_tree_path_new_from_string (path_str);
+
+ gtk_tree_model_get_iter (GTK_TREE_MODEL (view->details->model),
+ &iter, path);
+
+ gtk_tree_path_free (path);
+
+ gtk_tree_model_get (GTK_TREE_MODEL (view->details->model),
+ &iter,
+ FM_LIST_MODEL_FILE_COLUMN, &file,
+ -1);
+
+ /* Only rename if name actually changed */
+ if (strcmp (new_text, view->details->original_name) != 0)
+ {
+ view->details->renaming_file = caja_file_ref (file);
+ view->details->rename_done = FALSE;
+ fm_rename_file (file, new_text, fm_list_view_rename_callback, g_object_ref (view));
+ g_free (view->details->original_name);
+ view->details->original_name = g_strdup (new_text);
+ }
+
+ caja_file_unref (file);
+
+ /*We're done editing - make the filename-cells readonly again.*/
+ g_object_set (G_OBJECT (view->details->file_name_cell),
+ "editable", FALSE,
+ NULL);
+
+ fm_directory_view_unfreeze_updates (FM_DIRECTORY_VIEW (view));
+}
+
+static char *
+get_root_uri_callback (CajaTreeViewDragDest *dest,
+ gpointer user_data)
+{
+ FMListView *view;
+
+ view = FM_LIST_VIEW (user_data);
+
+ return fm_directory_view_get_uri (FM_DIRECTORY_VIEW (view));
+}
+
+static CajaFile *
+get_file_for_path_callback (CajaTreeViewDragDest *dest,
+ GtkTreePath *path,
+ gpointer user_data)
+{
+ FMListView *view;
+
+ view = FM_LIST_VIEW (user_data);
+
+ return fm_list_model_file_for_path (view->details->model, path);
+}
+
+/* Handles an URL received from Mozilla */
+static void
+list_view_handle_netscape_url (CajaTreeViewDragDest *dest, const char *encoded_url,
+ const char *target_uri, GdkDragAction action, int x, int y, FMListView *view)
+{
+ fm_directory_view_handle_netscape_url_drop (FM_DIRECTORY_VIEW (view),
+ encoded_url, target_uri, action, x, y);
+}
+
+static void
+list_view_handle_uri_list (CajaTreeViewDragDest *dest, const char *item_uris,
+ const char *target_uri,
+ GdkDragAction action, int x, int y, FMListView *view)
+{
+ fm_directory_view_handle_uri_list_drop (FM_DIRECTORY_VIEW (view),
+ item_uris, target_uri, action, x, y);
+}
+
+static void
+list_view_handle_text (CajaTreeViewDragDest *dest, const char *text,
+ const char *target_uri,
+ GdkDragAction action, int x, int y, FMListView *view)
+{
+ fm_directory_view_handle_text_drop (FM_DIRECTORY_VIEW (view),
+ text, target_uri, action, x, y);
+}
+
+static void
+list_view_handle_raw (CajaTreeViewDragDest *dest, const char *raw_data,
+ int length, const char *target_uri, const char *direct_save_uri,
+ GdkDragAction action, int x, int y, FMListView *view)
+{
+ fm_directory_view_handle_raw_drop (FM_DIRECTORY_VIEW (view),
+ raw_data, length, target_uri, direct_save_uri,
+ action, x, y);
+}
+
+static void
+move_copy_items_callback (CajaTreeViewDragDest *dest,
+ const GList *item_uris,
+ const char *target_uri,
+ guint action,
+ int x,
+ int y,
+ gpointer user_data)
+
+{
+ FMDirectoryView *view = user_data;
+
+ caja_clipboard_clear_if_colliding_uris (GTK_WIDGET (view),
+ item_uris,
+ fm_directory_view_get_copied_files_atom (view));
+ fm_directory_view_move_copy_items (item_uris,
+ NULL,
+ target_uri,
+ action,
+ x, y,
+ view);
+}
+
+static void
+apply_columns_settings (FMListView *list_view,
+ char **column_order,
+ char **visible_columns)
+{
+ GList *all_columns;
+ CajaFile *file;
+ GList *old_view_columns, *view_columns;
+ GHashTable *visible_columns_hash;
+ GtkTreeViewColumn *prev_view_column;
+ GList *l;
+ int i;
+
+ file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (list_view));
+
+ /* prepare ordered list of view columns using column_order and visible_columns */
+ view_columns = NULL;
+
+ all_columns = caja_get_columns_for_file (file);
+ all_columns = caja_sort_columns (all_columns, column_order);
+
+ /* hash table to lookup if a given column should be visible */
+ visible_columns_hash = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_free);
+ for (i = 0; visible_columns[i] != NULL; ++i)
+ {
+ g_hash_table_insert (visible_columns_hash,
+ g_ascii_strdown (visible_columns[i], -1),
+ g_ascii_strdown (visible_columns[i], -1));
+ }
+
+ for (l = all_columns; l != NULL; l = l->next)
+ {
+ char *name;
+ char *lowercase;
+
+ g_object_get (G_OBJECT (l->data), "name", &name, NULL);
+ lowercase = g_ascii_strdown (name, -1);
+
+ if (g_hash_table_lookup (visible_columns_hash, lowercase) != NULL)
+ {
+ GtkTreeViewColumn *view_column;
+
+ view_column = g_hash_table_lookup (list_view->details->columns, name);
+ if (view_column != NULL)
+ {
+ view_columns = g_list_prepend (view_columns, view_column);
+ }
+ }
+
+ g_free (name);
+ g_free (lowercase);
+ }
+
+ g_hash_table_destroy (visible_columns_hash);
+ caja_column_list_free (all_columns);
+
+ view_columns = g_list_reverse (view_columns);
+
+ /* remove columns that are not present in the configuration */
+ old_view_columns = gtk_tree_view_get_columns (list_view->details->tree_view);
+ for (l = old_view_columns; l != NULL; l = l->next)
+ {
+ if (g_list_find (view_columns, l->data) == NULL)
+ {
+ gtk_tree_view_remove_column (list_view->details->tree_view, l->data);
+ }
+ }
+ g_list_free (old_view_columns);
+
+ /* append new columns from the configuration */
+ old_view_columns = gtk_tree_view_get_columns (list_view->details->tree_view);
+ for (l = view_columns; l != NULL; l = l->next)
+ {
+ if (g_list_find (old_view_columns, l->data) == NULL)
+ {
+ gtk_tree_view_append_column (list_view->details->tree_view, l->data);
+ }
+ }
+ g_list_free (old_view_columns);
+
+ /* place columns in the correct order */
+ prev_view_column = NULL;
+ for (l = view_columns; l != NULL; l = l->next)
+ {
+ gtk_tree_view_move_column_after (list_view->details->tree_view, l->data, prev_view_column);
+ prev_view_column = l->data;
+ }
+ g_list_free (view_columns);
+}
+
+static void
+filename_cell_data_func (GtkTreeViewColumn *column,
+ GtkCellRenderer *renderer,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ FMListView *view)
+{
+ char *text;
+ GtkTreePath *path;
+ PangoUnderline underline;
+
+ gtk_tree_model_get (model, iter,
+ view->details->file_name_column_num, &text,
+ -1);
+
+ if (click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE)
+ {
+ path = gtk_tree_model_get_path (model, iter);
+
+ if (view->details->hover_path == NULL ||
+ gtk_tree_path_compare (path, view->details->hover_path))
+ {
+ underline = PANGO_UNDERLINE_NONE;
+ }
+ else
+ {
+ underline = PANGO_UNDERLINE_SINGLE;
+ }
+
+ gtk_tree_path_free (path);
+ }
+ else
+ {
+ underline = PANGO_UNDERLINE_NONE;
+ }
+
+ g_object_set (G_OBJECT (renderer),
+ "text", text,
+ "underline", underline,
+ NULL);
+ g_free (text);
+}
+
+static gboolean
+focus_in_event_callback (GtkWidget *widget, GdkEventFocus *event, gpointer user_data)
+{
+ CajaWindowSlotInfo *slot_info;
+ FMListView *list_view = FM_LIST_VIEW (user_data);
+
+ /* make the corresponding slot (and the pane that contains it) active */
+ slot_info = fm_directory_view_get_caja_window_slot (FM_DIRECTORY_VIEW (list_view));
+ caja_window_slot_info_make_hosting_pane_active (slot_info);
+
+ return FALSE;
+}
+
+static void
+create_and_set_up_tree_view (FMListView *view)
+{
+ GtkCellRenderer *cell;
+ GtkTreeViewColumn *column;
+ GtkBindingSet *binding_set;
+ AtkObject *atk_obj;
+ GList *caja_columns;
+ GList *l;
+
+ view->details->tree_view = GTK_TREE_VIEW (gtk_tree_view_new ());
+ view->details->columns = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify)g_free,
+ (GDestroyNotify) g_object_unref);
+ gtk_tree_view_set_enable_search (view->details->tree_view, TRUE);
+
+ /* Don't handle backspace key. It's used to open the parent folder. */
+ binding_set = gtk_binding_set_by_class (GTK_WIDGET_GET_CLASS (view->details->tree_view));
+ gtk_binding_entry_remove (binding_set, GDK_BackSpace, 0);
+
+ view->details->drag_dest =
+ caja_tree_view_drag_dest_new (view->details->tree_view);
+
+ g_signal_connect_object (view->details->drag_dest,
+ "get_root_uri",
+ G_CALLBACK (get_root_uri_callback),
+ view, 0);
+ g_signal_connect_object (view->details->drag_dest,
+ "get_file_for_path",
+ G_CALLBACK (get_file_for_path_callback),
+ view, 0);
+ g_signal_connect_object (view->details->drag_dest,
+ "move_copy_items",
+ G_CALLBACK (move_copy_items_callback),
+ view, 0);
+ g_signal_connect_object (view->details->drag_dest, "handle_netscape_url",
+ G_CALLBACK (list_view_handle_netscape_url), view, 0);
+ g_signal_connect_object (view->details->drag_dest, "handle_uri_list",
+ G_CALLBACK (list_view_handle_uri_list), view, 0);
+ g_signal_connect_object (view->details->drag_dest, "handle_text",
+ G_CALLBACK (list_view_handle_text), view, 0);
+ g_signal_connect_object (view->details->drag_dest, "handle_raw",
+ G_CALLBACK (list_view_handle_raw), view, 0);
+
+ g_signal_connect_object (gtk_tree_view_get_selection (view->details->tree_view),
+ "changed",
+ G_CALLBACK (list_selection_changed_callback), view, 0);
+
+ g_signal_connect_object (view->details->tree_view, "drag_begin",
+ G_CALLBACK (drag_begin_callback), view, 0);
+ g_signal_connect_object (view->details->tree_view, "drag_data_get",
+ G_CALLBACK (drag_data_get_callback), view, 0);
+ g_signal_connect_object (view->details->tree_view, "motion_notify_event",
+ G_CALLBACK (motion_notify_callback), view, 0);
+ g_signal_connect_object (view->details->tree_view, "enter_notify_event",
+ G_CALLBACK (enter_notify_callback), view, 0);
+ g_signal_connect_object (view->details->tree_view, "leave_notify_event",
+ G_CALLBACK (leave_notify_callback), view, 0);
+ g_signal_connect_object (view->details->tree_view, "button_press_event",
+ G_CALLBACK (button_press_callback), view, 0);
+ g_signal_connect_object (view->details->tree_view, "button_release_event",
+ G_CALLBACK (button_release_callback), view, 0);
+ g_signal_connect_object (view->details->tree_view, "key_press_event",
+ G_CALLBACK (key_press_callback), view, 0);
+ g_signal_connect_object (view->details->tree_view, "popup_menu",
+ G_CALLBACK (popup_menu_callback), view, 0);
+ g_signal_connect_object (view->details->tree_view, "row_expanded",
+ G_CALLBACK (row_expanded_callback), view, 0);
+ g_signal_connect_object (view->details->tree_view, "row_collapsed",
+ G_CALLBACK (row_collapsed_callback), view, 0);
+ g_signal_connect_object (view->details->tree_view, "row-activated",
+ G_CALLBACK (row_activated_callback), view, 0);
+
+ g_signal_connect_object (view->details->tree_view, "focus_in_event",
+ G_CALLBACK(focus_in_event_callback), view, 0);
+
+ view->details->model = g_object_new (FM_TYPE_LIST_MODEL, NULL);
+ gtk_tree_view_set_model (view->details->tree_view, GTK_TREE_MODEL (view->details->model));
+ /* Need the model for the dnd drop icon "accept" change */
+ fm_list_model_set_drag_view (FM_LIST_MODEL (view->details->model),
+ view->details->tree_view, 0, 0);
+
+ g_signal_connect_object (view->details->model, "sort_column_changed",
+ G_CALLBACK (sort_column_changed_callback), view, 0);
+
+ g_signal_connect_object (view->details->model, "subdirectory_unloaded",
+ G_CALLBACK (subdirectory_unloaded_callback), view, 0);
+
+ gtk_tree_selection_set_mode (gtk_tree_view_get_selection (view->details->tree_view), GTK_SELECTION_MULTIPLE);
+ gtk_tree_view_set_rules_hint (view->details->tree_view, TRUE);
+
+ caja_columns = caja_get_all_columns ();
+
+ for (l = caja_columns; l != NULL; l = l->next)
+ {
+ CajaColumn *caja_column;
+ int column_num;
+ char *name;
+ char *label;
+ float xalign;
+
+ caja_column = CAJA_COLUMN (l->data);
+
+ g_object_get (caja_column,
+ "name", &name,
+ "label", &label,
+ "xalign", &xalign, NULL);
+
+ column_num = fm_list_model_add_column (view->details->model,
+ caja_column);
+
+ /* Created the name column specially, because it
+ * has the icon in it.*/
+ if (!strcmp (name, "name"))
+ {
+ /* Create the file name column */
+ cell = caja_cell_renderer_pixbuf_emblem_new ();
+ view->details->pixbuf_cell = (GtkCellRendererPixbuf *)cell;
+
+ view->details->file_name_column = gtk_tree_view_column_new ();
+ g_object_ref_sink (view->details->file_name_column);
+ view->details->file_name_column_num = column_num;
+
+ g_hash_table_insert (view->details->columns,
+ g_strdup ("name"),
+ view->details->file_name_column);
+
+ gtk_tree_view_set_search_column (view->details->tree_view, column_num);
+
+ gtk_tree_view_column_set_sort_column_id (view->details->file_name_column, column_num);
+ gtk_tree_view_column_set_title (view->details->file_name_column, _("Name"));
+ gtk_tree_view_column_set_resizable (view->details->file_name_column, TRUE);
+
+ gtk_tree_view_column_pack_start (view->details->file_name_column, cell, FALSE);
+ gtk_tree_view_column_set_attributes (view->details->file_name_column,
+ cell,
+ "pixbuf", FM_LIST_MODEL_SMALLEST_ICON_COLUMN,
+ "pixbuf_emblem", FM_LIST_MODEL_SMALLEST_EMBLEM_COLUMN,
+ NULL);
+
+ cell = caja_cell_renderer_text_ellipsized_new ();
+ view->details->file_name_cell = (GtkCellRendererText *)cell;
+ g_signal_connect (cell, "edited", G_CALLBACK (cell_renderer_edited), view);
+ g_signal_connect (cell, "editing-canceled", G_CALLBACK (cell_renderer_editing_canceled), view);
+ g_signal_connect (cell, "editing-started", G_CALLBACK (cell_renderer_editing_started_cb), view);
+
+ gtk_tree_view_column_pack_start (view->details->file_name_column, cell, TRUE);
+ gtk_tree_view_column_set_cell_data_func (view->details->file_name_column, cell,
+ (GtkTreeCellDataFunc) filename_cell_data_func,
+ view, NULL);
+ }
+ else
+ {
+ cell = gtk_cell_renderer_text_new ();
+ g_object_set (cell, "xalign", xalign, NULL);
+ view->details->cells = g_list_append (view->details->cells,
+ cell);
+ column = gtk_tree_view_column_new_with_attributes (label,
+ cell,
+ "text", column_num,
+ NULL);
+ g_object_ref_sink (column);
+ gtk_tree_view_column_set_sort_column_id (column, column_num);
+ g_hash_table_insert (view->details->columns,
+ g_strdup (name),
+ column);
+
+ gtk_tree_view_column_set_resizable (column, TRUE);
+ gtk_tree_view_column_set_visible (column, TRUE);
+ }
+ g_free (name);
+ g_free (label);
+ }
+ caja_column_list_free (caja_columns);
+
+ /* Apply the default column order and visible columns, to get it
+ * right most of the time. The metadata will be checked when a
+ * folder is loaded */
+ apply_columns_settings (view,
+ default_column_order_auto_value,
+ default_visible_columns_auto_value);
+
+ gtk_widget_show (GTK_WIDGET (view->details->tree_view));
+ gtk_container_add (GTK_CONTAINER (view), GTK_WIDGET (view->details->tree_view));
+
+
+ atk_obj = gtk_widget_get_accessible (GTK_WIDGET (view->details->tree_view));
+ atk_object_set_name (atk_obj, _("List View"));
+}
+
+static void
+fm_list_view_add_file (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory)
+{
+ FMListModel *model;
+
+ model = FM_LIST_VIEW (view)->details->model;
+ fm_list_model_add_file (model, file, directory);
+}
+
+static char **
+get_visible_columns (FMListView *list_view)
+{
+ CajaFile *file;
+ GList *visible_columns;
+ char **ret;
+
+ ret = NULL;
+
+ file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (list_view));
+
+ visible_columns = caja_file_get_metadata_list
+ (file,
+ CAJA_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS);
+
+ if (visible_columns)
+ {
+ GPtrArray *res;
+ GList *l;
+
+ res = g_ptr_array_new ();
+ for (l = visible_columns; l != NULL; l = l->next)
+ {
+ g_ptr_array_add (res, l->data);
+ }
+ g_ptr_array_add (res, NULL);
+
+ ret = (char **) g_ptr_array_free (res, FALSE);
+ g_list_free (visible_columns);
+ }
+
+ if (ret != NULL)
+ {
+ return ret;
+ }
+
+ return caja_file_is_in_trash (file) ?
+ g_strdupv ((gchar **) default_trash_visible_columns) :
+ g_strdupv (default_visible_columns_auto_value);
+}
+
+static char **
+get_column_order (FMListView *list_view)
+{
+ CajaFile *file;
+ GList *column_order;
+ char **ret;
+
+ ret = NULL;
+
+ file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (list_view));
+
+ column_order = caja_file_get_metadata_list
+ (file,
+ CAJA_METADATA_KEY_LIST_VIEW_COLUMN_ORDER);
+
+ if (column_order)
+ {
+ GPtrArray *res;
+ GList *l;
+
+ res = g_ptr_array_new ();
+ for (l = column_order; l != NULL; l = l->next)
+ {
+ g_ptr_array_add (res, l->data);
+ }
+ g_ptr_array_add (res, NULL);
+
+ ret = (char **) g_ptr_array_free (res, FALSE);
+ g_list_free (column_order);
+ }
+
+ if (ret != NULL)
+ {
+ return ret;
+ }
+
+ return caja_file_is_in_trash (file) ?
+ g_strdupv ((gchar **) default_trash_columns_order) :
+ g_strdupv (default_column_order_auto_value);
+}
+
+static void
+set_columns_settings_from_metadata_and_preferences (FMListView *list_view)
+{
+ char **column_order;
+ char **visible_columns;
+
+ column_order = get_column_order (list_view);
+ visible_columns = get_visible_columns (list_view);
+
+ apply_columns_settings (list_view, column_order, visible_columns);
+
+ g_strfreev (column_order);
+ g_strfreev (visible_columns);
+}
+
+static void
+set_sort_order_from_metadata_and_preferences (FMListView *list_view)
+{
+ char *sort_attribute;
+ int sort_column_id;
+ CajaFile *file;
+ gboolean sort_reversed, default_sort_reversed;
+ const gchar *default_sort_order;
+
+ file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (list_view));
+ sort_attribute = caja_file_get_metadata (file,
+ CAJA_METADATA_KEY_LIST_VIEW_SORT_COLUMN,
+ NULL);
+ sort_column_id = fm_list_model_get_sort_column_id_from_attribute (list_view->details->model,
+ g_quark_from_string (sort_attribute));
+ g_free (sort_attribute);
+
+ default_sort_order = get_default_sort_order (file, &default_sort_reversed);
+
+ if (sort_column_id == -1)
+ {
+ sort_column_id =
+ fm_list_model_get_sort_column_id_from_attribute (list_view->details->model,
+ g_quark_from_string (default_sort_order));
+ }
+
+ sort_reversed = caja_file_get_boolean_metadata (file,
+ CAJA_METADATA_KEY_LIST_VIEW_SORT_REVERSED,
+ default_sort_reversed);
+
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (list_view->details->model),
+ sort_column_id,
+ sort_reversed ? GTK_SORT_DESCENDING : GTK_SORT_ASCENDING);
+}
+
+static gboolean
+list_view_changed_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ gtk_tree_model_row_changed (model, path, iter);
+ return FALSE;
+}
+
+static CajaZoomLevel
+get_default_zoom_level (void)
+{
+ CajaZoomLevel default_zoom_level;
+
+ default_zoom_level = default_zoom_level_auto_value;
+
+ if (default_zoom_level < CAJA_ZOOM_LEVEL_SMALLEST
+ || CAJA_ZOOM_LEVEL_LARGEST < default_zoom_level)
+ {
+ default_zoom_level = CAJA_ZOOM_LEVEL_SMALL;
+ }
+
+ return default_zoom_level;
+}
+
+static void
+set_zoom_level_from_metadata_and_preferences (FMListView *list_view)
+{
+ CajaFile *file;
+ int level;
+
+ if (fm_directory_view_supports_zooming (FM_DIRECTORY_VIEW (list_view)))
+ {
+ file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (list_view));
+ level = caja_file_get_integer_metadata (file,
+ CAJA_METADATA_KEY_LIST_VIEW_ZOOM_LEVEL,
+ get_default_zoom_level ());
+ fm_list_view_set_zoom_level (list_view, level, TRUE);
+
+ /* updated the rows after updating the font size */
+ gtk_tree_model_foreach (GTK_TREE_MODEL (list_view->details->model),
+ list_view_changed_foreach, NULL);
+ }
+}
+
+static void
+fm_list_view_begin_loading (FMDirectoryView *view)
+{
+ FMListView *list_view;
+
+ list_view = FM_LIST_VIEW (view);
+
+ set_sort_order_from_metadata_and_preferences (list_view);
+ set_zoom_level_from_metadata_and_preferences (list_view);
+ set_columns_settings_from_metadata_and_preferences (list_view);
+}
+
+static void
+stop_cell_editing (FMListView *list_view)
+{
+ GtkTreeViewColumn *column;
+
+ /* Stop an ongoing rename to commit the name changes when the user
+ * changes directories without exiting cell edit mode. It also prevents
+ * the edited handler from being called on the cleared list model.
+ */
+ column = list_view->details->file_name_column;
+ if (column != NULL && list_view->details->editable_widget != NULL &&
+ GTK_IS_CELL_EDITABLE (list_view->details->editable_widget))
+ {
+ gtk_cell_editable_editing_done (list_view->details->editable_widget);
+ }
+}
+
+static void
+fm_list_view_clear (FMDirectoryView *view)
+{
+ FMListView *list_view;
+
+ list_view = FM_LIST_VIEW (view);
+
+ if (list_view->details->model != NULL)
+ {
+ stop_cell_editing (list_view);
+ fm_list_model_clear (list_view->details->model);
+ }
+}
+
+static void
+fm_list_view_rename_callback (CajaFile *file,
+ GFile *result_location,
+ GError *error,
+ gpointer callback_data)
+{
+ FMListView *view;
+
+ view = FM_LIST_VIEW (callback_data);
+
+ if (view->details->renaming_file)
+ {
+ view->details->rename_done = TRUE;
+
+ if (error != NULL)
+ {
+ /* If the rename failed (or was cancelled), kill renaming_file.
+ * We won't get a change event for the rename, so otherwise
+ * it would stay around forever.
+ */
+ caja_file_unref (view->details->renaming_file);
+ view->details->renaming_file = NULL;
+ }
+ }
+
+ g_object_unref (view);
+}
+
+
+static void
+fm_list_view_file_changed (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory)
+{
+ FMListView *listview;
+ GtkTreeIter iter;
+ GtkTreePath *file_path;
+
+ listview = FM_LIST_VIEW (view);
+
+ fm_list_model_file_changed (listview->details->model, file, directory);
+
+ if (listview->details->renaming_file != NULL &&
+ file == listview->details->renaming_file &&
+ listview->details->rename_done)
+ {
+ /* This is (probably) the result of the rename operation, and
+ * the tree-view changes above could have resorted the list, so
+ * scroll to the new position
+ */
+ if (fm_list_model_get_tree_iter_from_file (listview->details->model, file, directory, &iter))
+ {
+ file_path = gtk_tree_model_get_path (GTK_TREE_MODEL (listview->details->model), &iter);
+ gtk_tree_view_scroll_to_cell (listview->details->tree_view,
+ file_path, NULL,
+ FALSE, 0.0, 0.0);
+ gtk_tree_path_free (file_path);
+ }
+
+ caja_file_unref (listview->details->renaming_file);
+ listview->details->renaming_file = NULL;
+ }
+}
+
+static GtkWidget *
+fm_list_view_get_background_widget (FMDirectoryView *view)
+{
+ return GTK_WIDGET (view);
+}
+
+static void
+fm_list_view_get_selection_foreach_func (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
+{
+ GList **list;
+ CajaFile *file;
+
+ list = data;
+
+ gtk_tree_model_get (model, iter,
+ FM_LIST_MODEL_FILE_COLUMN, &file,
+ -1);
+
+ if (file != NULL)
+ {
+ (* list) = g_list_prepend ((* list), file);
+ }
+}
+
+static GList *
+fm_list_view_get_selection (FMDirectoryView *view)
+{
+ GList *list;
+
+ list = NULL;
+
+ gtk_tree_selection_selected_foreach (gtk_tree_view_get_selection (FM_LIST_VIEW (view)->details->tree_view),
+ fm_list_view_get_selection_foreach_func, &list);
+
+ return g_list_reverse (list);
+}
+
+static void
+fm_list_view_get_selection_for_file_transfer_foreach_func (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
+{
+ CajaFile *file;
+ struct SelectionForeachData *selection_data;
+ GtkTreeIter parent, child;
+
+ selection_data = data;
+
+ gtk_tree_model_get (model, iter,
+ FM_LIST_MODEL_FILE_COLUMN, &file,
+ -1);
+
+ if (file != NULL)
+ {
+ /* If the parent folder is also selected, don't include this file in the
+ * file operation, since that would copy it to the toplevel target instead
+ * of keeping it as a child of the copied folder
+ */
+ child = *iter;
+ while (gtk_tree_model_iter_parent (model, &parent, &child))
+ {
+ if (gtk_tree_selection_iter_is_selected (selection_data->selection,
+ &parent))
+ {
+ return;
+ }
+ child = parent;
+ }
+
+ caja_file_ref (file);
+ selection_data->list = g_list_prepend (selection_data->list, file);
+ }
+}
+
+
+static GList *
+fm_list_view_get_selection_for_file_transfer (FMDirectoryView *view)
+{
+ struct SelectionForeachData selection_data;
+
+ selection_data.list = NULL;
+ selection_data.selection = gtk_tree_view_get_selection (FM_LIST_VIEW (view)->details->tree_view);
+
+ gtk_tree_selection_selected_foreach (selection_data.selection,
+ fm_list_view_get_selection_for_file_transfer_foreach_func, &selection_data);
+
+ return g_list_reverse (selection_data.list);
+}
+
+
+
+
+static guint
+fm_list_view_get_item_count (FMDirectoryView *view)
+{
+ g_return_val_if_fail (FM_IS_LIST_VIEW (view), 0);
+
+ return fm_list_model_get_length (FM_LIST_VIEW (view)->details->model);
+}
+
+static gboolean
+fm_list_view_is_empty (FMDirectoryView *view)
+{
+ return fm_list_model_is_empty (FM_LIST_VIEW (view)->details->model);
+}
+
+static void
+fm_list_view_end_file_changes (FMDirectoryView *view)
+{
+ FMListView *list_view;
+
+ list_view = FM_LIST_VIEW (view);
+
+ if (list_view->details->new_selection_path)
+ {
+ gtk_tree_view_set_cursor (list_view->details->tree_view,
+ list_view->details->new_selection_path,
+ NULL, FALSE);
+ gtk_tree_path_free (list_view->details->new_selection_path);
+ list_view->details->new_selection_path = NULL;
+ }
+}
+
+static void
+fm_list_view_remove_file (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory)
+{
+ GtkTreePath *path;
+ GtkTreePath *file_path;
+ GtkTreeIter iter;
+ GtkTreeIter temp_iter;
+ GtkTreeRowReference* row_reference;
+ FMListView *list_view;
+ GtkTreeModel* tree_model;
+ GtkTreeSelection *selection;
+
+ path = NULL;
+ row_reference = NULL;
+ list_view = FM_LIST_VIEW (view);
+ tree_model = GTK_TREE_MODEL(list_view->details->model);
+
+ if (fm_list_model_get_tree_iter_from_file (list_view->details->model, file, directory, &iter))
+ {
+ selection = gtk_tree_view_get_selection (list_view->details->tree_view);
+ file_path = gtk_tree_model_get_path (tree_model, &iter);
+
+ if (gtk_tree_selection_path_is_selected (selection, file_path))
+ {
+ /* get reference for next element in the list view. If the element to be deleted is the
+ * last one, get reference to previous element. If there is only one element in view
+ * no need to select anything.
+ */
+ temp_iter = iter;
+
+ if (gtk_tree_model_iter_next (tree_model, &iter))
+ {
+ path = gtk_tree_model_get_path (tree_model, &iter);
+ row_reference = gtk_tree_row_reference_new (tree_model, path);
+ }
+ else
+ {
+ path = gtk_tree_model_get_path (tree_model, &temp_iter);
+ if (gtk_tree_path_prev (path))
+ {
+ row_reference = gtk_tree_row_reference_new (tree_model, path);
+ }
+ }
+ gtk_tree_path_free (path);
+ }
+
+ gtk_tree_path_free (file_path);
+
+ fm_list_model_remove_file (list_view->details->model, file, directory);
+
+ if (gtk_tree_row_reference_valid (row_reference))
+ {
+ if (list_view->details->new_selection_path)
+ {
+ gtk_tree_path_free (list_view->details->new_selection_path);
+ }
+ list_view->details->new_selection_path = gtk_tree_row_reference_get_path (row_reference);
+ }
+
+ if (row_reference)
+ {
+ gtk_tree_row_reference_free (row_reference);
+ }
+ }
+
+
+}
+
+static void
+fm_list_view_set_selection (FMDirectoryView *view, GList *selection)
+{
+ FMListView *list_view;
+ GtkTreeSelection *tree_selection;
+ GList *node;
+ GList *iters, *l;
+ CajaFile *file;
+
+ list_view = FM_LIST_VIEW (view);
+ tree_selection = gtk_tree_view_get_selection (list_view->details->tree_view);
+
+ g_signal_handlers_block_by_func (tree_selection, list_selection_changed_callback, view);
+
+ gtk_tree_selection_unselect_all (tree_selection);
+ for (node = selection; node != NULL; node = node->next)
+ {
+ file = node->data;
+ iters = fm_list_model_get_all_iters_for_file (list_view->details->model, file);
+
+ for (l = iters; l != NULL; l = l->next)
+ {
+ gtk_tree_selection_select_iter (tree_selection,
+ (GtkTreeIter *)l->data);
+ }
+ eel_g_list_free_deep (iters);
+ }
+
+ g_signal_handlers_unblock_by_func (tree_selection, list_selection_changed_callback, view);
+ fm_directory_view_notify_selection_changed (view);
+}
+
+static void
+fm_list_view_invert_selection (FMDirectoryView *view)
+{
+ FMListView *list_view;
+ GtkTreeSelection *tree_selection;
+ GList *node;
+ GList *iters, *l;
+ CajaFile *file;
+ GList *selection = NULL;
+
+ list_view = FM_LIST_VIEW (view);
+ tree_selection = gtk_tree_view_get_selection (list_view->details->tree_view);
+
+ g_signal_handlers_block_by_func (tree_selection, list_selection_changed_callback, view);
+
+ gtk_tree_selection_selected_foreach (tree_selection,
+ fm_list_view_get_selection_foreach_func, &selection);
+
+ gtk_tree_selection_select_all (tree_selection);
+
+ for (node = selection; node != NULL; node = node->next)
+ {
+ file = node->data;
+ iters = fm_list_model_get_all_iters_for_file (list_view->details->model, file);
+
+ for (l = iters; l != NULL; l = l->next)
+ {
+ gtk_tree_selection_unselect_iter (tree_selection,
+ (GtkTreeIter *)l->data);
+ }
+ eel_g_list_free_deep (iters);
+ }
+
+ g_list_free (selection);
+
+ g_signal_handlers_unblock_by_func (tree_selection, list_selection_changed_callback, view);
+ fm_directory_view_notify_selection_changed (view);
+}
+
+static void
+fm_list_view_select_all (FMDirectoryView *view)
+{
+ gtk_tree_selection_select_all (gtk_tree_view_get_selection (FM_LIST_VIEW (view)->details->tree_view));
+}
+
+static void
+column_editor_response_callback (GtkWidget *dialog,
+ int response_id,
+ gpointer user_data)
+{
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+static void
+column_chooser_changed_callback (CajaColumnChooser *chooser,
+ FMListView *view)
+{
+ CajaFile *file;
+ char **visible_columns;
+ char **column_order;
+ GList *list;
+ int i;
+
+ file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (view));
+
+ caja_column_chooser_get_settings (chooser,
+ &visible_columns,
+ &column_order);
+
+ list = NULL;
+ for (i = 0; visible_columns[i] != NULL; ++i)
+ {
+ list = g_list_prepend (list, visible_columns[i]);
+ }
+ list = g_list_reverse (list);
+ caja_file_set_metadata_list (file,
+ CAJA_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS,
+ list);
+ g_list_free (list);
+
+ list = NULL;
+ for (i = 0; column_order[i] != NULL; ++i)
+ {
+ list = g_list_prepend (list, column_order[i]);
+ }
+ list = g_list_reverse (list);
+ caja_file_set_metadata_list (file,
+ CAJA_METADATA_KEY_LIST_VIEW_COLUMN_ORDER,
+ list);
+ g_list_free (list);
+
+ apply_columns_settings (view, column_order, visible_columns);
+
+ g_strfreev (visible_columns);
+ g_strfreev (column_order);
+}
+
+static void
+column_chooser_set_from_arrays (CajaColumnChooser *chooser,
+ FMListView *view,
+ char **visible_columns,
+ char **column_order)
+{
+ g_signal_handlers_block_by_func
+ (chooser, G_CALLBACK (column_chooser_changed_callback), view);
+
+ caja_column_chooser_set_settings (chooser,
+ visible_columns,
+ column_order);
+
+ g_signal_handlers_unblock_by_func
+ (chooser, G_CALLBACK (column_chooser_changed_callback), view);
+}
+
+static void
+column_chooser_set_from_settings (CajaColumnChooser *chooser,
+ FMListView *view)
+{
+ char **visible_columns;
+ char **column_order;
+
+ visible_columns = get_visible_columns (view);
+ column_order = get_column_order (view);
+
+ column_chooser_set_from_arrays (chooser, view,
+ visible_columns, column_order);
+
+ g_strfreev (visible_columns);
+ g_strfreev (column_order);
+}
+
+static void
+column_chooser_use_default_callback (CajaColumnChooser *chooser,
+ FMListView *view)
+{
+ CajaFile *file;
+ char **default_columns;
+ char **default_order;
+
+ file = fm_directory_view_get_directory_as_file
+ (FM_DIRECTORY_VIEW (view));
+
+ caja_file_set_metadata_list (file, CAJA_METADATA_KEY_LIST_VIEW_COLUMN_ORDER, NULL);
+ caja_file_set_metadata_list (file, CAJA_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS, NULL);
+
+ /* set view values ourselves, as new metadata could not have been
+ * updated yet.
+ */
+ default_columns = caja_file_is_in_trash (file) ?
+ g_strdupv ((gchar **) default_trash_visible_columns) :
+ g_strdupv (default_visible_columns_auto_value);
+
+ default_order = caja_file_is_in_trash (file) ?
+ g_strdupv ((gchar **) default_trash_columns_order) :
+ g_strdupv (default_column_order_auto_value);
+
+ apply_columns_settings (view, default_order, default_columns);
+ column_chooser_set_from_arrays (chooser, view,
+ default_columns, default_order);
+
+ g_strfreev (default_columns);
+ g_strfreev (default_order);
+}
+
+static GtkWidget *
+create_column_editor (FMListView *view)
+{
+ GtkWidget *window;
+ GtkWidget *label;
+ GtkWidget *box;
+ GtkWidget *column_chooser;
+ GtkWidget *alignment;
+ CajaFile *file;
+ char *str;
+ char *name;
+ const char *label_text;
+
+ file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (view));
+ name = caja_file_get_display_name (file);
+ str = g_strdup_printf (_("%s Visible Columns"), name);
+ g_free (name);
+
+ window = gtk_dialog_new_with_buttons (str,
+ GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (view))),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
+ NULL);
+ g_free (str);
+ g_signal_connect (window, "response",
+ G_CALLBACK (column_editor_response_callback), NULL);
+
+ gtk_window_set_default_size (GTK_WINDOW (window), 300, 400);
+
+ box = gtk_vbox_new (FALSE, 12);
+ gtk_container_set_border_width (GTK_CONTAINER (box), 12);
+ gtk_widget_show (box);
+ gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (window))), box);
+
+ label_text = _("Choose the order of information to appear in this folder:");
+ str = g_strconcat ("<b>", label_text, "</b>", NULL);
+ label = gtk_label_new (NULL);
+ gtk_label_set_markup (GTK_LABEL (label), str);
+ gtk_label_set_line_wrap (GTK_LABEL (label), FALSE);
+ gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
+ gtk_widget_show (label);
+ gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
+
+ g_free (str);
+
+ alignment = gtk_alignment_new (0.5, 0.5, 1, 1);
+ gtk_alignment_set_padding (GTK_ALIGNMENT (alignment),
+ 0, 0, 12, 0);
+ gtk_widget_show (alignment);
+ gtk_box_pack_start (GTK_BOX (box), alignment, TRUE, TRUE, 0);
+
+ column_chooser = caja_column_chooser_new (file);
+ gtk_widget_show (column_chooser);
+ gtk_container_add (GTK_CONTAINER (alignment), column_chooser);
+
+ g_signal_connect (column_chooser, "changed",
+ G_CALLBACK (column_chooser_changed_callback),
+ view);
+ g_signal_connect (column_chooser, "use_default",
+ G_CALLBACK (column_chooser_use_default_callback),
+ view);
+
+ column_chooser_set_from_settings
+ (CAJA_COLUMN_CHOOSER (column_chooser), view);
+
+ return window;
+}
+
+static void
+action_visible_columns_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ FMListView *list_view;
+
+ list_view = FM_LIST_VIEW (callback_data);
+
+ if (list_view->details->column_editor)
+ {
+ gtk_window_present (GTK_WINDOW (list_view->details->column_editor));
+ }
+ else
+ {
+ list_view->details->column_editor = create_column_editor (list_view);
+ eel_add_weak_pointer (&list_view->details->column_editor);
+
+ gtk_widget_show (list_view->details->column_editor);
+ }
+}
+
+static const GtkActionEntry list_view_entries[] =
+{
+ /* name, stock id */ { "Visible Columns", NULL,
+ /* label, accelerator */ N_("Visible _Columns..."), NULL,
+ /* tooltip */ N_("Select the columns visible in this folder"),
+ G_CALLBACK (action_visible_columns_callback)
+ },
+};
+
+static void
+fm_list_view_merge_menus (FMDirectoryView *view)
+{
+ FMListView *list_view;
+ GtkUIManager *ui_manager;
+ GtkActionGroup *action_group;
+ const char *ui;
+
+ EEL_CALL_PARENT (FM_DIRECTORY_VIEW_CLASS, merge_menus, (view));
+
+ list_view = FM_LIST_VIEW (view);
+
+ ui_manager = fm_directory_view_get_ui_manager (view);
+
+ action_group = gtk_action_group_new ("ListViewActions");
+ gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
+ list_view->details->list_action_group = action_group;
+ gtk_action_group_add_actions (action_group,
+ list_view_entries, G_N_ELEMENTS (list_view_entries),
+ list_view);
+
+ gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
+ g_object_unref (action_group); /* owned by ui manager */
+
+ ui = caja_ui_string_get ("caja-list-view-ui.xml");
+ list_view->details->list_merge_id = gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, NULL);
+
+ list_view->details->menus_ready = TRUE;
+}
+
+static void
+fm_list_view_unmerge_menus (FMDirectoryView *view)
+{
+ FMListView *list_view;
+ GtkUIManager *ui_manager;
+
+ list_view = FM_LIST_VIEW (view);
+
+ FM_DIRECTORY_VIEW_CLASS (fm_list_view_parent_class)->unmerge_menus (view);
+
+ ui_manager = fm_directory_view_get_ui_manager (view);
+ if (ui_manager != NULL)
+ {
+ caja_ui_unmerge_ui (ui_manager,
+ &list_view->details->list_merge_id,
+ &list_view->details->list_action_group);
+ }
+}
+
+static void
+fm_list_view_update_menus (FMDirectoryView *view)
+{
+ FMListView *list_view;
+
+ list_view = FM_LIST_VIEW (view);
+
+ /* don't update if the menus aren't ready */
+ if (!list_view->details->menus_ready)
+ {
+ return;
+ }
+
+ EEL_CALL_PARENT (FM_DIRECTORY_VIEW_CLASS, update_menus, (view));
+}
+
+/* Reset sort criteria and zoom level to match defaults */
+static void
+fm_list_view_reset_to_defaults (FMDirectoryView *view)
+{
+ CajaFile *file;
+ const gchar *default_sort_order;
+ gboolean default_sort_reversed;
+
+ file = fm_directory_view_get_directory_as_file (view);
+
+ caja_file_set_metadata (file, CAJA_METADATA_KEY_LIST_VIEW_SORT_COLUMN, NULL, NULL);
+ caja_file_set_metadata (file, CAJA_METADATA_KEY_LIST_VIEW_SORT_REVERSED, NULL, NULL);
+ caja_file_set_metadata (file, CAJA_METADATA_KEY_LIST_VIEW_ZOOM_LEVEL, NULL, NULL);
+ caja_file_set_metadata_list (file, CAJA_METADATA_KEY_LIST_VIEW_COLUMN_ORDER, NULL);
+ caja_file_set_metadata_list (file, CAJA_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS, NULL);
+
+ default_sort_order = get_default_sort_order (file, &default_sort_reversed);
+
+ gtk_tree_sortable_set_sort_column_id
+ (GTK_TREE_SORTABLE (FM_LIST_VIEW (view)->details->model),
+ fm_list_model_get_sort_column_id_from_attribute (FM_LIST_VIEW (view)->details->model,
+ g_quark_from_string (default_sort_order)),
+ default_sort_reversed ? GTK_SORT_DESCENDING : GTK_SORT_ASCENDING);
+
+ fm_list_view_set_zoom_level (FM_LIST_VIEW (view), get_default_zoom_level (), FALSE);
+ set_columns_settings_from_metadata_and_preferences (FM_LIST_VIEW (view));
+}
+
+static void
+fm_list_view_scale_font_size (FMListView *view,
+ CajaZoomLevel new_level)
+{
+ GList *l;
+ static gboolean first_time = TRUE;
+ static double pango_scale[7];
+ int medium;
+ int i;
+
+ g_return_if_fail (new_level >= CAJA_ZOOM_LEVEL_SMALLEST &&
+ new_level <= CAJA_ZOOM_LEVEL_LARGEST);
+
+ if (first_time)
+ {
+ first_time = FALSE;
+ medium = CAJA_ZOOM_LEVEL_SMALLER;
+ pango_scale[medium] = PANGO_SCALE_MEDIUM;
+ for (i = medium; i > CAJA_ZOOM_LEVEL_SMALLEST; i--)
+ {
+ pango_scale[i - 1] = (1 / 1.2) * pango_scale[i];
+ }
+ for (i = medium; i < CAJA_ZOOM_LEVEL_LARGEST; i++)
+ {
+ pango_scale[i + 1] = 1.2 * pango_scale[i];
+ }
+ }
+
+ g_object_set (G_OBJECT (view->details->file_name_cell),
+ "scale", pango_scale[new_level],
+ NULL);
+ for (l = view->details->cells; l != NULL; l = l->next)
+ {
+ g_object_set (G_OBJECT (l->data),
+ "scale", pango_scale[new_level],
+ NULL);
+ }
+}
+
+static void
+fm_list_view_set_zoom_level (FMListView *view,
+ CajaZoomLevel new_level,
+ gboolean always_emit)
+{
+ int icon_size;
+ int column, emblem_column;
+
+ g_return_if_fail (FM_IS_LIST_VIEW (view));
+ g_return_if_fail (new_level >= CAJA_ZOOM_LEVEL_SMALLEST &&
+ new_level <= CAJA_ZOOM_LEVEL_LARGEST);
+
+ if (view->details->zoom_level == new_level)
+ {
+ if (always_emit)
+ {
+ g_signal_emit_by_name (FM_DIRECTORY_VIEW(view), "zoom_level_changed");
+ }
+ return;
+ }
+
+ view->details->zoom_level = new_level;
+ g_signal_emit_by_name (FM_DIRECTORY_VIEW(view), "zoom_level_changed");
+
+ caja_file_set_integer_metadata
+ (fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (view)),
+ CAJA_METADATA_KEY_LIST_VIEW_ZOOM_LEVEL,
+ get_default_zoom_level (),
+ new_level);
+
+ /* Select correctly scaled icons. */
+ column = fm_list_model_get_column_id_from_zoom_level (new_level);
+ emblem_column = fm_list_model_get_emblem_column_id_from_zoom_level (new_level);
+ gtk_tree_view_column_set_attributes (view->details->file_name_column,
+ GTK_CELL_RENDERER (view->details->pixbuf_cell),
+ "pixbuf", column,
+ "pixbuf_emblem", emblem_column,
+ NULL);
+
+ /* Scale text. */
+ fm_list_view_scale_font_size (view, new_level);
+
+ /* Make all rows the same size. */
+ icon_size = caja_get_icon_size_for_zoom_level (new_level);
+ gtk_cell_renderer_set_fixed_size (GTK_CELL_RENDERER (view->details->pixbuf_cell),
+ -1, icon_size);
+
+ fm_directory_view_update_menus (FM_DIRECTORY_VIEW (view));
+}
+
+static void
+fm_list_view_bump_zoom_level (FMDirectoryView *view, int zoom_increment)
+{
+ FMListView *list_view;
+ gint new_level;
+
+ g_return_if_fail (FM_IS_LIST_VIEW (view));
+
+ list_view = FM_LIST_VIEW (view);
+ new_level = list_view->details->zoom_level + zoom_increment;
+
+ if (new_level >= CAJA_ZOOM_LEVEL_SMALLEST &&
+ new_level <= CAJA_ZOOM_LEVEL_LARGEST)
+ {
+ fm_list_view_set_zoom_level (list_view, new_level, FALSE);
+ }
+}
+
+static CajaZoomLevel
+fm_list_view_get_zoom_level (FMDirectoryView *view)
+{
+ FMListView *list_view;
+
+ g_return_val_if_fail (FM_IS_LIST_VIEW (view), CAJA_ZOOM_LEVEL_STANDARD);
+
+ list_view = FM_LIST_VIEW (view);
+
+ return list_view->details->zoom_level;
+}
+
+static void
+fm_list_view_zoom_to_level (FMDirectoryView *view,
+ CajaZoomLevel zoom_level)
+{
+ FMListView *list_view;
+
+ g_return_if_fail (FM_IS_LIST_VIEW (view));
+
+ list_view = FM_LIST_VIEW (view);
+
+ fm_list_view_set_zoom_level (list_view, zoom_level, FALSE);
+}
+
+static void
+fm_list_view_restore_default_zoom_level (FMDirectoryView *view)
+{
+ FMListView *list_view;
+
+ g_return_if_fail (FM_IS_LIST_VIEW (view));
+
+ list_view = FM_LIST_VIEW (view);
+
+ fm_list_view_set_zoom_level (list_view, get_default_zoom_level (), FALSE);
+}
+
+static gboolean
+fm_list_view_can_zoom_in (FMDirectoryView *view)
+{
+ g_return_val_if_fail (FM_IS_LIST_VIEW (view), FALSE);
+
+ return FM_LIST_VIEW (view)->details->zoom_level < CAJA_ZOOM_LEVEL_LARGEST;
+}
+
+static gboolean
+fm_list_view_can_zoom_out (FMDirectoryView *view)
+{
+ g_return_val_if_fail (FM_IS_LIST_VIEW (view), FALSE);
+
+ return FM_LIST_VIEW (view)->details->zoom_level > CAJA_ZOOM_LEVEL_SMALLEST;
+}
+
+static void
+fm_list_view_start_renaming_file (FMDirectoryView *view,
+ CajaFile *file,
+ gboolean select_all)
+{
+ FMListView *list_view;
+ GtkTreeIter iter;
+ GtkTreePath *path;
+
+ list_view = FM_LIST_VIEW (view);
+
+ /* Select all if we are in renaming mode already */
+ if (list_view->details->file_name_column && list_view->details->editable_widget)
+ {
+ gtk_editable_select_region (
+ GTK_EDITABLE (list_view->details->editable_widget),
+ 0,
+ -1);
+ return;
+ }
+
+ if (!fm_list_model_get_first_iter_for_file (list_view->details->model, file, &iter))
+ {
+ return;
+ }
+
+ /* Freeze updates to the view to prevent losing rename focus when the tree view updates */
+ fm_directory_view_freeze_updates (FM_DIRECTORY_VIEW (view));
+
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (list_view->details->model), &iter);
+
+ /* Make filename-cells editable. */
+ g_object_set (G_OBJECT (list_view->details->file_name_cell),
+ "editable", TRUE,
+ NULL);
+
+ gtk_tree_view_scroll_to_cell (list_view->details->tree_view,
+ NULL,
+ list_view->details->file_name_column,
+ TRUE, 0.0, 0.0);
+ gtk_tree_view_set_cursor (list_view->details->tree_view,
+ path,
+ list_view->details->file_name_column,
+ TRUE);
+
+ gtk_tree_path_free (path);
+}
+
+static void
+fm_list_view_click_policy_changed (FMDirectoryView *directory_view)
+{
+ GdkWindow *win;
+ GdkDisplay *display;
+ FMListView *view;
+ GtkTreeIter iter;
+ GtkTreeView *tree;
+
+ view = FM_LIST_VIEW (directory_view);
+
+ /* ensure that we unset the hand cursor and refresh underlined rows */
+ if (click_policy_auto_value == CAJA_CLICK_POLICY_DOUBLE)
+ {
+ if (view->details->hover_path != NULL)
+ {
+ if (gtk_tree_model_get_iter (GTK_TREE_MODEL (view->details->model),
+ &iter, view->details->hover_path))
+ {
+ gtk_tree_model_row_changed (GTK_TREE_MODEL (view->details->model),
+ view->details->hover_path, &iter);
+ }
+
+ gtk_tree_path_free (view->details->hover_path);
+ view->details->hover_path = NULL;
+ }
+
+ tree = view->details->tree_view;
+ if (gtk_widget_get_realized (GTK_WIDGET (tree)))
+ {
+ win = gtk_widget_get_window (GTK_WIDGET (tree));
+ gdk_window_set_cursor (win, NULL);
+
+ display = gtk_widget_get_display (GTK_WIDGET (view));
+ if (display != NULL)
+ {
+ gdk_display_flush (display);
+ }
+ }
+
+ if (hand_cursor != NULL)
+ {
+ gdk_cursor_unref (hand_cursor);
+ hand_cursor = NULL;
+ }
+ }
+ else if (click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE)
+ {
+ if (hand_cursor == NULL)
+ {
+ hand_cursor = gdk_cursor_new(GDK_HAND2);
+ }
+ }
+}
+
+static void
+default_sort_order_changed_callback (gpointer callback_data)
+{
+ FMListView *list_view;
+
+ list_view = FM_LIST_VIEW (callback_data);
+
+ set_sort_order_from_metadata_and_preferences (list_view);
+}
+
+static void
+default_zoom_level_changed_callback (gpointer callback_data)
+{
+ FMListView *list_view;
+
+ list_view = FM_LIST_VIEW (callback_data);
+
+ set_zoom_level_from_metadata_and_preferences (list_view);
+}
+
+static void
+default_visible_columns_changed_callback (gpointer callback_data)
+{
+ FMListView *list_view;
+
+ list_view = FM_LIST_VIEW (callback_data);
+
+ set_columns_settings_from_metadata_and_preferences (list_view);
+}
+
+static void
+default_column_order_changed_callback (gpointer callback_data)
+{
+ FMListView *list_view;
+
+ list_view = FM_LIST_VIEW (callback_data);
+
+ set_columns_settings_from_metadata_and_preferences (list_view);
+}
+
+static void
+fm_list_view_sort_directories_first_changed (FMDirectoryView *view)
+{
+ FMListView *list_view;
+
+ list_view = FM_LIST_VIEW (view);
+
+ fm_list_model_set_should_sort_directories_first (list_view->details->model,
+ fm_directory_view_should_sort_directories_first (view));
+}
+
+static int
+fm_list_view_compare_files (FMDirectoryView *view, CajaFile *file1, CajaFile *file2)
+{
+ FMListView *list_view;
+
+ list_view = FM_LIST_VIEW (view);
+ return fm_list_model_compare_func (list_view->details->model, file1, file2);
+}
+
+static gboolean
+fm_list_view_using_manual_layout (FMDirectoryView *view)
+{
+ g_return_val_if_fail (FM_IS_LIST_VIEW (view), FALSE);
+
+ return FALSE;
+}
+
+static void
+fm_list_view_dispose (GObject *object)
+{
+ FMListView *list_view;
+
+ list_view = FM_LIST_VIEW (object);
+
+ if (list_view->details->model)
+ {
+ stop_cell_editing (list_view);
+ g_object_unref (list_view->details->model);
+ list_view->details->model = NULL;
+ }
+
+ if (list_view->details->drag_dest)
+ {
+ g_object_unref (list_view->details->drag_dest);
+ list_view->details->drag_dest = NULL;
+ }
+
+ if (list_view->details->renaming_file_activate_timeout != 0)
+ {
+ g_source_remove (list_view->details->renaming_file_activate_timeout);
+ list_view->details->renaming_file_activate_timeout = 0;
+ }
+
+ if (list_view->details->clipboard_handler_id != 0)
+ {
+ g_signal_handler_disconnect (caja_clipboard_monitor_get (),
+ list_view->details->clipboard_handler_id);
+ list_view->details->clipboard_handler_id = 0;
+ }
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+fm_list_view_finalize (GObject *object)
+{
+ FMListView *list_view;
+
+ list_view = FM_LIST_VIEW (object);
+
+ g_free (list_view->details->original_name);
+ list_view->details->original_name = NULL;
+
+ if (list_view->details->double_click_path[0])
+ {
+ gtk_tree_path_free (list_view->details->double_click_path[0]);
+ }
+ if (list_view->details->double_click_path[1])
+ {
+ gtk_tree_path_free (list_view->details->double_click_path[1]);
+ }
+ if (list_view->details->new_selection_path)
+ {
+ gtk_tree_path_free (list_view->details->new_selection_path);
+ }
+
+ g_list_free (list_view->details->cells);
+ g_hash_table_destroy (list_view->details->columns);
+
+ if (list_view->details->hover_path != NULL)
+ {
+ gtk_tree_path_free (list_view->details->hover_path);
+ }
+
+ if (list_view->details->column_editor != NULL)
+ {
+ gtk_widget_destroy (list_view->details->column_editor);
+ }
+
+ g_free (list_view->details);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+fm_list_view_emblems_changed (FMDirectoryView *directory_view)
+{
+ g_assert (FM_IS_LIST_VIEW (directory_view));
+
+ /* FIXME: This needs to update the emblems of the icons, since
+ * relative emblems may have changed.
+ */
+}
+
+static char *
+fm_list_view_get_first_visible_file (CajaView *view)
+{
+ CajaFile *file;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ FMListView *list_view;
+
+ list_view = FM_LIST_VIEW (view);
+
+ if (gtk_tree_view_get_path_at_pos (list_view->details->tree_view,
+ 0, 0,
+ &path, NULL, NULL, NULL))
+ {
+ gtk_tree_model_get_iter (GTK_TREE_MODEL (list_view->details->model),
+ &iter, path);
+
+ gtk_tree_path_free (path);
+
+ gtk_tree_model_get (GTK_TREE_MODEL (list_view->details->model),
+ &iter,
+ FM_LIST_MODEL_FILE_COLUMN, &file,
+ -1);
+ if (file)
+ {
+ char *uri;
+
+ uri = caja_file_get_uri (file);
+
+ caja_file_unref (file);
+
+ return uri;
+ }
+ }
+
+ return NULL;
+}
+
+static void
+fm_list_view_scroll_to_file (FMListView *view,
+ CajaFile *file)
+{
+ GtkTreePath *path;
+ GtkTreeIter iter;
+
+ if (!fm_list_model_get_first_iter_for_file (view->details->model, file, &iter))
+ {
+ return;
+ }
+
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (view->details->model), &iter);
+
+ gtk_tree_view_scroll_to_cell (view->details->tree_view,
+ path, NULL,
+ TRUE, 0.0, 0.0);
+
+ gtk_tree_path_free (path);
+}
+
+static void
+list_view_scroll_to_file (CajaView *view,
+ const char *uri)
+{
+ CajaFile *file;
+
+ if (uri != NULL)
+ {
+ /* Only if existing, since we don't want to add the file to
+ the directory if it has been removed since then */
+ file = caja_file_get_existing_by_uri (uri);
+ if (file != NULL)
+ {
+ fm_list_view_scroll_to_file (FM_LIST_VIEW (view), file);
+ caja_file_unref (file);
+ }
+ }
+}
+
+static void
+list_view_notify_clipboard_info (CajaClipboardMonitor *monitor,
+ CajaClipboardInfo *info,
+ FMListView *view)
+{
+ /* this could be called as a result of _end_loading() being
+ * called after _dispose(), where the model is cleared.
+ */
+ if (view->details->model == NULL)
+ {
+ return;
+ }
+
+ if (info != NULL && info->cut)
+ {
+ fm_list_model_set_highlight_for_files (view->details->model, info->files);
+ }
+ else
+ {
+ fm_list_model_set_highlight_for_files (view->details->model, NULL);
+ }
+}
+
+static void
+fm_list_view_end_loading (FMDirectoryView *view,
+ gboolean all_files_seen)
+{
+ CajaClipboardMonitor *monitor;
+ CajaClipboardInfo *info;
+
+ monitor = caja_clipboard_monitor_get ();
+ info = caja_clipboard_monitor_get_clipboard_info (monitor);
+
+ list_view_notify_clipboard_info (monitor, info, FM_LIST_VIEW (view));
+}
+
+static void
+real_set_is_active (FMDirectoryView *view,
+ gboolean is_active)
+{
+ GtkWidget *tree_view;
+ GtkStyle *style;
+ GdkColor color;
+
+ tree_view = GTK_WIDGET (fm_list_view_get_tree_view (FM_LIST_VIEW (view)));
+
+ if (is_active)
+ {
+ gtk_widget_modify_base (tree_view, GTK_STATE_NORMAL, NULL);
+ }
+ else
+ {
+ style = gtk_widget_get_style (tree_view);
+ color = style->base[GTK_STATE_INSENSITIVE];
+ gtk_widget_modify_base (tree_view, GTK_STATE_NORMAL, &color);
+ }
+
+ EEL_CALL_PARENT (FM_DIRECTORY_VIEW_CLASS,
+ set_is_active, (view, is_active));
+}
+
+static void
+fm_list_view_class_init (FMListViewClass *class)
+{
+ FMDirectoryViewClass *fm_directory_view_class;
+
+ fm_directory_view_class = FM_DIRECTORY_VIEW_CLASS (class);
+
+ G_OBJECT_CLASS (class)->dispose = fm_list_view_dispose;
+ G_OBJECT_CLASS (class)->finalize = fm_list_view_finalize;
+
+ fm_directory_view_class->add_file = fm_list_view_add_file;
+ fm_directory_view_class->begin_loading = fm_list_view_begin_loading;
+ fm_directory_view_class->end_loading = fm_list_view_end_loading;
+ fm_directory_view_class->bump_zoom_level = fm_list_view_bump_zoom_level;
+ fm_directory_view_class->can_zoom_in = fm_list_view_can_zoom_in;
+ fm_directory_view_class->can_zoom_out = fm_list_view_can_zoom_out;
+ fm_directory_view_class->click_policy_changed = fm_list_view_click_policy_changed;
+ fm_directory_view_class->clear = fm_list_view_clear;
+ fm_directory_view_class->file_changed = fm_list_view_file_changed;
+ fm_directory_view_class->get_background_widget = fm_list_view_get_background_widget;
+ fm_directory_view_class->get_selection = fm_list_view_get_selection;
+ fm_directory_view_class->get_selection_for_file_transfer = fm_list_view_get_selection_for_file_transfer;
+ fm_directory_view_class->get_item_count = fm_list_view_get_item_count;
+ fm_directory_view_class->is_empty = fm_list_view_is_empty;
+ fm_directory_view_class->remove_file = fm_list_view_remove_file;
+ fm_directory_view_class->merge_menus = fm_list_view_merge_menus;
+ fm_directory_view_class->unmerge_menus = fm_list_view_unmerge_menus;
+ fm_directory_view_class->update_menus = fm_list_view_update_menus;
+ fm_directory_view_class->reset_to_defaults = fm_list_view_reset_to_defaults;
+ fm_directory_view_class->restore_default_zoom_level = fm_list_view_restore_default_zoom_level;
+ fm_directory_view_class->reveal_selection = fm_list_view_reveal_selection;
+ fm_directory_view_class->select_all = fm_list_view_select_all;
+ fm_directory_view_class->set_selection = fm_list_view_set_selection;
+ fm_directory_view_class->invert_selection = fm_list_view_invert_selection;
+ fm_directory_view_class->compare_files = fm_list_view_compare_files;
+ fm_directory_view_class->sort_directories_first_changed = fm_list_view_sort_directories_first_changed;
+ fm_directory_view_class->start_renaming_file = fm_list_view_start_renaming_file;
+ fm_directory_view_class->get_zoom_level = fm_list_view_get_zoom_level;
+ fm_directory_view_class->zoom_to_level = fm_list_view_zoom_to_level;
+ fm_directory_view_class->emblems_changed = fm_list_view_emblems_changed;
+ fm_directory_view_class->end_file_changes = fm_list_view_end_file_changes;
+ fm_directory_view_class->using_manual_layout = fm_list_view_using_manual_layout;
+ fm_directory_view_class->set_is_active = real_set_is_active;
+
+ eel_preferences_add_auto_enum (CAJA_PREFERENCES_CLICK_POLICY,
+ &click_policy_auto_value);
+ eel_preferences_add_auto_string (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_SORT_ORDER,
+ (const char **) &default_sort_order_auto_value);
+ eel_preferences_add_auto_boolean (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_SORT_IN_REVERSE_ORDER,
+ &default_sort_reversed_auto_value);
+ eel_preferences_add_auto_enum (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_ZOOM_LEVEL,
+ (int *) &default_zoom_level_auto_value);
+ eel_preferences_add_auto_string_array (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS,
+ &default_visible_columns_auto_value);
+ eel_preferences_add_auto_string_array (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_COLUMN_ORDER,
+ &default_column_order_auto_value);
+}
+
+static const char *
+fm_list_view_get_id (CajaView *view)
+{
+ return FM_LIST_VIEW_ID;
+}
+
+
+static void
+fm_list_view_iface_init (CajaViewIface *iface)
+{
+ fm_directory_view_init_view_iface (iface);
+
+ iface->get_view_id = fm_list_view_get_id;
+ iface->get_first_visible_file = fm_list_view_get_first_visible_file;
+ iface->scroll_to_file = list_view_scroll_to_file;
+ iface->get_title = NULL;
+}
+
+
+static void
+fm_list_view_init (FMListView *list_view)
+{
+ list_view->details = g_new0 (FMListViewDetails, 1);
+
+ create_and_set_up_tree_view (list_view);
+
+ eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_SORT_ORDER,
+ default_sort_order_changed_callback,
+ list_view, G_OBJECT (list_view));
+ eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_SORT_IN_REVERSE_ORDER,
+ default_sort_order_changed_callback,
+ list_view, G_OBJECT (list_view));
+ eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_ZOOM_LEVEL,
+ default_zoom_level_changed_callback,
+ list_view, G_OBJECT (list_view));
+ eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS,
+ default_visible_columns_changed_callback,
+ list_view, G_OBJECT (list_view));
+ eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_LIST_VIEW_DEFAULT_COLUMN_ORDER,
+ default_column_order_changed_callback,
+ list_view, G_OBJECT (list_view));
+
+ fm_list_view_click_policy_changed (FM_DIRECTORY_VIEW (list_view));
+
+ fm_list_view_sort_directories_first_changed (FM_DIRECTORY_VIEW (list_view));
+
+ /* ensure that the zoom level is always set in begin_loading */
+ list_view->details->zoom_level = CAJA_ZOOM_LEVEL_SMALLEST - 1;
+
+ list_view->details->hover_path = NULL;
+ list_view->details->clipboard_handler_id =
+ g_signal_connect (caja_clipboard_monitor_get (),
+ "clipboard_info",
+ G_CALLBACK (list_view_notify_clipboard_info), list_view);
+}
+
+static CajaView *
+fm_list_view_create (CajaWindowSlotInfo *slot)
+{
+ FMListView *view;
+
+ view = g_object_new (FM_TYPE_LIST_VIEW,
+ "window-slot", slot,
+ NULL);
+ return CAJA_VIEW (view);
+}
+
+static gboolean
+fm_list_view_supports_uri (const char *uri,
+ GFileType file_type,
+ const char *mime_type)
+{
+ if (file_type == G_FILE_TYPE_DIRECTORY)
+ {
+ return TRUE;
+ }
+ if (strcmp (mime_type, CAJA_SAVED_SEARCH_MIMETYPE) == 0)
+ {
+ return TRUE;
+ }
+ if (g_str_has_prefix (uri, "trash:"))
+ {
+ return TRUE;
+ }
+ if (g_str_has_prefix (uri, EEL_SEARCH_URI))
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static CajaViewInfo fm_list_view =
+{
+ FM_LIST_VIEW_ID,
+ /* translators: this is used in the view selection dropdown
+ * of navigation windows and in the preferences dialog */
+ N_("List View"),
+ /* translators: this is used in the view menu */
+ N_("_List"),
+ N_("The list view encountered an error."),
+ N_("The list view encountered an error while starting up."),
+ N_("Display this location with the list view."),
+ fm_list_view_create,
+ fm_list_view_supports_uri
+};
+
+void
+fm_list_view_register (void)
+{
+ fm_list_view.view_combo_label = _(fm_list_view.view_combo_label);
+ fm_list_view.view_menu_label_with_mnemonic = _(fm_list_view.view_menu_label_with_mnemonic);
+ fm_list_view.error_label = _(fm_list_view.error_label);
+ fm_list_view.startup_error_label = _(fm_list_view.startup_error_label);
+ fm_list_view.display_location_label = _(fm_list_view.display_location_label);
+
+ caja_view_factory_register (&fm_list_view);
+}
+
+GtkTreeView*
+fm_list_view_get_tree_view (FMListView *list_view)
+{
+ return list_view->details->tree_view;
+}
diff --git a/src/file-manager/fm-list-view.h b/src/file-manager/fm-list-view.h
new file mode 100644
index 00000000..2defc6ca
--- /dev/null
+++ b/src/file-manager/fm-list-view.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* fm-list-view.h - interface for list view of directory.
+
+ Copyright (C) 2000 Eazel, Inc.
+ Copyright (C) 2001 Anders Carlsson <[email protected]>
+
+ The Mate Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Mate Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: John Sullivan <[email protected]>
+ Anders Carlsson <[email protected]>
+*/
+
+#ifndef FM_LIST_VIEW_H
+#define FM_LIST_VIEW_H
+
+#include "fm-directory-view.h"
+
+#define FM_TYPE_LIST_VIEW fm_list_view_get_type()
+#define FM_LIST_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_LIST_VIEW, FMListView))
+#define FM_LIST_VIEW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_LIST_VIEW, FMListViewClass))
+#define FM_IS_LIST_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_LIST_VIEW))
+#define FM_IS_LIST_VIEW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_LIST_VIEW))
+#define FM_LIST_VIEW_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_LIST_VIEW, FMListViewClass))
+
+#define FM_LIST_VIEW_ID "OAFIID:Caja_File_Manager_List_View"
+
+typedef struct FMListViewDetails FMListViewDetails;
+
+typedef struct
+{
+ FMDirectoryView parent_instance;
+ FMListViewDetails *details;
+} FMListView;
+
+typedef struct
+{
+ FMDirectoryViewClass parent_class;
+} FMListViewClass;
+
+GType fm_list_view_get_type (void);
+void fm_list_view_register (void);
+GtkTreeView* fm_list_view_get_tree_view (FMListView *list_view);
+
+#endif /* FM_LIST_VIEW_H */
diff --git a/src/file-manager/fm-properties-window.c b/src/file-manager/fm-properties-window.c
new file mode 100644
index 00000000..6310b407
--- /dev/null
+++ b/src/file-manager/fm-properties-window.c
@@ -0,0 +1,5835 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* fm-properties-window.c - window that lets user modify file properties
+
+ Copyright (C) 2000 Eazel, Inc.
+
+ The Mate Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Mate Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Darin Adler <[email protected]>
+*/
+
+#include <config.h>
+#include "fm-properties-window.h"
+#include "fm-ditem-page.h"
+
+#define MATE_DESKTOP_USE_UNSTABLE_API
+
+#include "fm-error-reporting.h"
+#include "libcaja-private/caja-mime-application-chooser.h"
+#include <eel/eel-accessibility.h>
+#include <eel/eel-gdk-pixbuf-extensions.h>
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-mate-extensions.h>
+#include <eel/eel-gtk-extensions.h>
+#include <eel/eel-labeled-image.h>
+#include <eel/eel-stock-dialogs.h>
+#include <eel/eel-string.h>
+#include <eel/eel-vfs-extensions.h>
+#include <eel/eel-wrap-table.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <glib/gi18n.h>
+#include <libmateui/mate-desktop-thumbnail.h>
+#include <libcaja-extension/caja-property-page-provider.h>
+#include <libcaja-private/caja-customization-data.h>
+#include <libcaja-private/caja-entry.h>
+#include <libcaja-private/caja-file-attributes.h>
+#include <libcaja-private/caja-file-operations.h>
+#include <libcaja-private/caja-desktop-icon-file.h>
+#include <libcaja-private/caja-global-preferences.h>
+#include <libcaja-private/caja-emblem-utils.h>
+#include <libcaja-private/caja-link.h>
+#include <libcaja-private/caja-metadata.h>
+#include <libcaja-private/caja-module.h>
+#include <libcaja-private/caja-undo-signal-handlers.h>
+#include <libcaja-private/caja-mime-actions.h>
+#include <libcaja-private/caja-undo.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <cairo.h>
+
+#if HAVE_SYS_VFS_H
+#include <sys/vfs.h>
+#elif HAVE_SYS_MOUNT_H
+#if HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif
+#include <sys/mount.h>
+#endif
+
+#define USED_FILL_R (0.988235294 * 65535)
+#define USED_FILL_G (0.91372549 * 65535)
+#define USED_FILL_B (0.309803922 * 65535)
+
+#define FREE_FILL_R (0.447058824 * 65535)
+#define FREE_FILL_G (0.623529412 * 65535)
+#define FREE_FILL_B (0.811764706 * 65535)
+
+
+#define PREVIEW_IMAGE_WIDTH 96
+
+#define ROW_PAD 6
+
+static GHashTable *windows;
+static GHashTable *pending_lists;
+
+struct FMPropertiesWindowDetails {
+ GList *original_files;
+ GList *target_files;
+
+ GtkNotebook *notebook;
+
+ GtkTable *basic_table;
+ GtkTable *permissions_table;
+ gboolean advanced_permissions;
+
+ GtkWidget *icon_button;
+ GtkWidget *icon_image;
+ GtkWidget *icon_chooser;
+
+ GtkLabel *name_label;
+ GtkWidget *name_field;
+ unsigned int name_row;
+ char *pending_name;
+
+ GtkLabel *directory_contents_title_field;
+ GtkLabel *directory_contents_value_field;
+ guint update_directory_contents_timeout_id;
+ guint update_files_timeout_id;
+
+ GList *emblem_buttons;
+ GHashTable *initial_emblems;
+
+ CajaFile *group_change_file;
+ char *group_change_group;
+ unsigned int group_change_timeout;
+ CajaFile *owner_change_file;
+ char *owner_change_owner;
+ unsigned int owner_change_timeout;
+
+ GList *permission_buttons;
+ GList *permission_combos;
+ GHashTable *initial_permissions;
+ gboolean has_recursive_apply;
+
+ GList *value_fields;
+
+ GList *mime_list;
+
+ gboolean deep_count_finished;
+
+ guint total_count;
+ goffset total_size;
+
+ guint long_operation_underway;
+
+ GList *changed_files;
+
+ guint64 volume_capacity;
+ guint64 volume_free;
+
+ GdkColor used_color;
+ GdkColor free_color;
+ GdkColor used_stroke_color;
+ GdkColor free_stroke_color;
+};
+
+enum {
+ PERMISSIONS_CHECKBOXES_OWNER_ROW,
+ PERMISSIONS_CHECKBOXES_GROUP_ROW,
+ PERMISSIONS_CHECKBOXES_OTHERS_ROW,
+ PERMISSIONS_CHECKBOXES_ROW_COUNT
+};
+
+enum {
+ PERMISSIONS_CHECKBOXES_READ_COLUMN,
+ PERMISSIONS_CHECKBOXES_WRITE_COLUMN,
+ PERMISSIONS_CHECKBOXES_EXECUTE_COLUMN,
+ PERMISSIONS_CHECKBOXES_COLUMN_COUNT
+};
+
+enum {
+ TITLE_COLUMN,
+ VALUE_COLUMN,
+ COLUMN_COUNT
+};
+
+typedef struct {
+ GList *original_files;
+ GList *target_files;
+ GtkWidget *parent_widget;
+ char *pending_key;
+ GHashTable *pending_files;
+} StartupData;
+
+/* drag and drop definitions */
+
+enum {
+ TARGET_URI_LIST,
+ TARGET_MATE_URI_LIST,
+ TARGET_RESET_BACKGROUND
+};
+
+static const GtkTargetEntry target_table[] = {
+ { "text/uri-list", 0, TARGET_URI_LIST },
+ { "x-special/mate-icon-list", 0, TARGET_MATE_URI_LIST },
+ { "x-special/mate-reset-background", 0, TARGET_RESET_BACKGROUND }
+};
+
+#define DIRECTORY_CONTENTS_UPDATE_INTERVAL 200 /* milliseconds */
+#define FILES_UPDATE_INTERVAL 200 /* milliseconds */
+#define STANDARD_EMBLEM_HEIGHT 52
+#define EMBLEM_LABEL_SPACING 2
+
+/*
+ * A timeout before changes through the user/group combo box will be applied.
+ * When quickly changing owner/groups (i.e. by keyboard or scroll wheel),
+ * this ensures that the GUI doesn't end up unresponsive.
+ *
+ * Both combos react on changes by scheduling a new change and unscheduling
+ * or cancelling old pending changes.
+ */
+#define CHOWN_CHGRP_TIMEOUT 300 /* milliseconds */
+
+static void directory_contents_value_field_update (FMPropertiesWindow *window);
+static void file_changed_callback (CajaFile *file,
+ gpointer user_data);
+static void permission_button_update (FMPropertiesWindow *window,
+ GtkToggleButton *button);
+static void permission_combo_update (FMPropertiesWindow *window,
+ GtkComboBox *combo);
+static void value_field_update (FMPropertiesWindow *window,
+ GtkLabel *field);
+static void properties_window_update (FMPropertiesWindow *window,
+ GList *files);
+static void is_directory_ready_callback (CajaFile *file,
+ gpointer data);
+static void cancel_group_change_callback (FMPropertiesWindow *window);
+static void cancel_owner_change_callback (FMPropertiesWindow *window);
+static void parent_widget_destroyed_callback (GtkWidget *widget,
+ gpointer callback_data);
+static void select_image_button_callback (GtkWidget *widget,
+ FMPropertiesWindow *properties_window);
+static void set_icon (const char *icon_path,
+ FMPropertiesWindow *properties_window);
+static void remove_pending (StartupData *data,
+ gboolean cancel_call_when_ready,
+ gboolean cancel_timed_wait,
+ gboolean cancel_destroy_handler);
+static void append_extension_pages (FMPropertiesWindow *window);
+
+static gboolean name_field_focus_out (CajaEntry *name_field,
+ GdkEventFocus *event,
+ gpointer callback_data);
+static void name_field_activate (CajaEntry *name_field,
+ gpointer callback_data);
+static GtkLabel *attach_ellipsizing_value_label (GtkTable *table,
+ int row,
+ int column,
+ const char *initial_text);
+
+static GtkWidget* create_pie_widget (FMPropertiesWindow *window);
+
+G_DEFINE_TYPE (FMPropertiesWindow, fm_properties_window, GTK_TYPE_DIALOG);
+#define parent_class fm_properties_window_parent_class
+
+static gboolean
+is_multi_file_window (FMPropertiesWindow *window)
+{
+ GList *l;
+ int count;
+
+ count = 0;
+
+ for (l = window->details->original_files; l != NULL; l = l->next) {
+ if (!caja_file_is_gone (CAJA_FILE (l->data))) {
+ count++;
+ if (count > 1) {
+ return TRUE;
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+static int
+get_not_gone_original_file_count (FMPropertiesWindow *window)
+{
+ GList *l;
+ int count;
+
+ count = 0;
+
+ for (l = window->details->original_files; l != NULL; l = l->next) {
+ if (!caja_file_is_gone (CAJA_FILE (l->data))) {
+ count++;
+ }
+ }
+
+ return count;
+}
+
+static CajaFile *
+get_original_file (FMPropertiesWindow *window)
+{
+ g_return_val_if_fail (!is_multi_file_window (window), NULL);
+
+ if (window->details->original_files == NULL) {
+ return NULL;
+ }
+
+ return CAJA_FILE (window->details->original_files->data);
+}
+
+static CajaFile *
+get_target_file_for_original_file (CajaFile *file)
+{
+ CajaFile *target_file;
+ GFile *location;
+ char *uri_to_display;
+ CajaDesktopLink *link;
+
+ target_file = NULL;
+ if (CAJA_IS_DESKTOP_ICON_FILE (file)) {
+ link = caja_desktop_icon_file_get_link (CAJA_DESKTOP_ICON_FILE (file));
+
+ if (link != NULL) {
+ /* map to linked URI for these types of links */
+ location = caja_desktop_link_get_activation_location (link);
+ if (location) {
+ target_file = caja_file_get (location);
+ g_object_unref (location);
+ }
+
+ g_object_unref (link);
+ }
+ } else {
+ uri_to_display = caja_file_get_activation_uri (file);
+ if (uri_to_display != NULL) {
+ target_file = caja_file_get_by_uri (uri_to_display);
+ g_free (uri_to_display);
+ }
+ }
+
+ if (target_file != NULL) {
+ return target_file;
+ }
+
+ /* Ref passed-in file here since we've decided to use it. */
+ caja_file_ref (file);
+ return file;
+}
+
+static CajaFile *
+get_target_file (FMPropertiesWindow *window)
+{
+ return CAJA_FILE (window->details->target_files->data);
+}
+
+static void
+add_prompt (GtkVBox *vbox, const char *prompt_text, gboolean pack_at_start)
+{
+ GtkWidget *prompt;
+
+ prompt = gtk_label_new (prompt_text);
+ gtk_label_set_justify (GTK_LABEL (prompt), GTK_JUSTIFY_LEFT);
+ gtk_label_set_line_wrap (GTK_LABEL (prompt), TRUE);
+ gtk_widget_show (prompt);
+ if (pack_at_start) {
+ gtk_box_pack_start (GTK_BOX (vbox), prompt, FALSE, FALSE, 0);
+ } else {
+ gtk_box_pack_end (GTK_BOX (vbox), prompt, FALSE, FALSE, 0);
+ }
+}
+
+static void
+add_prompt_and_separator (GtkVBox *vbox, const char *prompt_text)
+{
+ GtkWidget *separator_line;
+
+ add_prompt (vbox, prompt_text, FALSE);
+
+ separator_line = gtk_hseparator_new ();
+ gtk_widget_show (separator_line);
+ gtk_box_pack_end (GTK_BOX (vbox), separator_line, TRUE, TRUE, 2*ROW_PAD);
+}
+
+static void
+get_image_for_properties_window (FMPropertiesWindow *window,
+ char **icon_name,
+ GdkPixbuf **icon_pixbuf)
+{
+ CajaIconInfo *icon, *new_icon;
+ GList *l;
+
+ icon = NULL;
+ for (l = window->details->original_files; l != NULL; l = l->next) {
+ CajaFile *file;
+
+ file = CAJA_FILE (l->data);
+
+ if (!icon) {
+ icon = caja_file_get_icon (file, CAJA_ICON_SIZE_STANDARD, CAJA_FILE_ICON_FLAGS_USE_THUMBNAILS | CAJA_FILE_ICON_FLAGS_IGNORE_VISITING);
+ } else {
+ new_icon = caja_file_get_icon (file, CAJA_ICON_SIZE_STANDARD, CAJA_FILE_ICON_FLAGS_USE_THUMBNAILS | CAJA_FILE_ICON_FLAGS_IGNORE_VISITING);
+ if (!new_icon || new_icon != icon) {
+ g_object_unref (icon);
+ g_object_unref (new_icon);
+ icon = NULL;
+ break;
+ }
+ g_object_unref (new_icon);
+ }
+ }
+
+ if (!icon) {
+ icon = caja_icon_info_lookup_from_name ("text-x-generic", CAJA_ICON_SIZE_STANDARD);
+ }
+
+ if (icon_name != NULL) {
+ *icon_name = g_strdup (caja_icon_info_get_used_name (icon));
+ }
+
+ if (icon_pixbuf != NULL) {
+ *icon_pixbuf = caja_icon_info_get_pixbuf_at_size (icon, CAJA_ICON_SIZE_STANDARD);
+ }
+
+ g_object_unref (icon);
+}
+
+
+static void
+update_properties_window_icon (GtkImage *image)
+{
+ FMPropertiesWindow *window;
+ GdkPixbuf *pixbuf;
+ char *name;
+
+ window = g_object_get_data (G_OBJECT (image), "properties_window");
+
+ get_image_for_properties_window (window, &name, &pixbuf);
+
+ if (name != NULL) {
+ gtk_window_set_icon_name (GTK_WINDOW (window), name);
+ } else {
+ gtk_window_set_icon (GTK_WINDOW (window), pixbuf);
+ }
+
+ gtk_image_set_from_pixbuf (image, pixbuf);
+
+ g_free (name);
+ g_object_unref (pixbuf);
+}
+
+/* utility to test if a uri refers to a local image */
+static gboolean
+uri_is_local_image (const char *uri)
+{
+ GdkPixbuf *pixbuf;
+ char *image_path;
+
+ image_path = g_filename_from_uri (uri, NULL, NULL);
+ if (image_path == NULL) {
+ return FALSE;
+ }
+
+ pixbuf = gdk_pixbuf_new_from_file (image_path, NULL);
+ g_free (image_path);
+
+ if (pixbuf == NULL) {
+ return FALSE;
+ }
+ g_object_unref (pixbuf);
+ return TRUE;
+}
+
+
+static void
+reset_icon (FMPropertiesWindow *properties_window)
+{
+ GList *l;
+
+ for (l = properties_window->details->original_files; l != NULL; l = l->next) {
+ CajaFile *file;
+
+ file = CAJA_FILE (l->data);
+
+ caja_file_set_metadata (file,
+ CAJA_METADATA_KEY_ICON_SCALE,
+ NULL, NULL);
+ caja_file_set_metadata (file,
+ CAJA_METADATA_KEY_CUSTOM_ICON,
+ NULL, NULL);
+ }
+}
+
+
+static void
+fm_properties_window_drag_data_received (GtkWidget *widget, GdkDragContext *context,
+ int x, int y,
+ GtkSelectionData *selection_data,
+ guint info, guint time)
+{
+ char **uris;
+ gboolean exactly_one;
+ GtkImage *image;
+ GtkWindow *window;
+
+ image = GTK_IMAGE (widget);
+ window = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (image)));
+
+ if (info == TARGET_RESET_BACKGROUND) {
+ reset_icon (FM_PROPERTIES_WINDOW (window));
+
+ return;
+ }
+
+ uris = g_strsplit (gtk_selection_data_get_data (selection_data), "\r\n", 0);
+ exactly_one = uris[0] != NULL && (uris[1] == NULL || uris[1][0] == '\0');
+
+
+ if (!exactly_one) {
+ eel_show_error_dialog
+ (_("You cannot assign more than one custom icon at a time!"),
+ _("Please drag just one image to set a custom icon."),
+ window);
+ } else {
+ if (uri_is_local_image (uris[0])) {
+ set_icon (uris[0], FM_PROPERTIES_WINDOW (window));
+ } else {
+ GFile *f;
+
+ f = g_file_new_for_uri (uris[0]);
+ if (!g_file_is_native (f)) {
+ eel_show_error_dialog
+ (_("The file that you dropped is not local."),
+ _("You can only use local images as custom icons."),
+ window);
+
+ } else {
+ eel_show_error_dialog
+ (_("The file that you dropped is not an image."),
+ _("You can only use local images as custom icons."),
+ window);
+ }
+ g_object_unref (f);
+ }
+ }
+ g_strfreev (uris);
+}
+
+static GtkWidget *
+create_image_widget (FMPropertiesWindow *window,
+ gboolean is_customizable)
+{
+ GtkWidget *button;
+ GtkWidget *image;
+ GdkPixbuf *pixbuf;
+
+ get_image_for_properties_window (window, NULL, &pixbuf);
+
+ image = gtk_image_new ();
+ gtk_widget_show (image);
+
+ button = NULL;
+ if (is_customizable) {
+ button = gtk_button_new ();
+ gtk_container_add (GTK_CONTAINER (button), image);
+
+ /* prepare the image to receive dropped objects to assign custom images */
+ gtk_drag_dest_set (GTK_WIDGET (image),
+ GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP,
+ target_table, G_N_ELEMENTS (target_table),
+ GDK_ACTION_COPY | GDK_ACTION_MOVE);
+
+ g_signal_connect (image, "drag_data_received",
+ G_CALLBACK (fm_properties_window_drag_data_received), NULL);
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (select_image_button_callback), window);
+ }
+
+ gtk_image_set_from_pixbuf (GTK_IMAGE (image), pixbuf);
+
+ g_object_unref (pixbuf);
+
+ g_object_set_data (G_OBJECT (image), "properties_window", window);
+
+ window->details->icon_image = image;
+ window->details->icon_button = button;
+
+ return button != NULL ? button : image;
+}
+
+static void
+set_name_field (FMPropertiesWindow *window, const gchar *original_name,
+ const gchar *name)
+{
+ gboolean new_widget;
+ gboolean use_label;
+
+ /* There are four cases here:
+ * 1) Changing the text of a label
+ * 2) Changing the text of an entry
+ * 3) Creating label (potentially replacing entry)
+ * 4) Creating entry (potentially replacing label)
+ */
+ use_label = is_multi_file_window (window) || !caja_file_can_rename (get_original_file (window));
+ new_widget = !window->details->name_field || (use_label ? CAJA_IS_ENTRY (window->details->name_field) : GTK_IS_LABEL (window->details->name_field));
+
+ if (new_widget) {
+ if (window->details->name_field) {
+ gtk_widget_destroy (window->details->name_field);
+ }
+
+ if (use_label) {
+ window->details->name_field =
+ GTK_WIDGET (attach_ellipsizing_value_label
+ (window->details->basic_table,
+ window->details->name_row,
+ VALUE_COLUMN, name));
+ } else {
+ window->details->name_field = caja_entry_new ();
+ gtk_entry_set_text (GTK_ENTRY (window->details->name_field), name);
+ gtk_widget_show (window->details->name_field);
+ gtk_table_attach (window->details->basic_table,
+ window->details->name_field,
+ VALUE_COLUMN,
+ VALUE_COLUMN + 1,
+ window->details->name_row,
+ window->details->name_row + 1,
+ GTK_FILL, 0,
+ 0, 0);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (window->details->name_label), window->details->name_field);
+
+ /* FIXME bugzilla.gnome.org 42151:
+ * With this (and one place elsewhere in this file, not sure which is the
+ * trouble-causer) code in place, bug 2151 happens (crash on quit). Since
+ * we've removed Undo from Caja for now, I'm just ifdeffing out this
+ * code rather than trying to fix 2151 now. Note that it might be possible
+ * to fix 2151 without making Undo actually work, it's just not worth the
+ * trouble.
+ */
+#ifdef UNDO_ENABLED
+ /* Set up name field for undo */
+ caja_undo_set_up_caja_entry_for_undo ( CAJA_ENTRY (window->details->name_field));
+ caja_undo_editable_set_undo_key (GTK_EDITABLE (window->details->name_field), TRUE);
+#endif
+
+ g_signal_connect_object (window->details->name_field, "focus_out_event",
+ G_CALLBACK (name_field_focus_out), window, 0);
+ g_signal_connect_object (window->details->name_field, "activate",
+ G_CALLBACK (name_field_activate), window, 0);
+ }
+
+ gtk_widget_show (window->details->name_field);
+ }
+ /* Only replace text if the file's name has changed. */
+ else if (original_name == NULL || strcmp (original_name, name) != 0) {
+
+ if (use_label) {
+ gtk_label_set_text (GTK_LABEL (window->details->name_field), name);
+ } else {
+ /* Only reset the text if it's different from what is
+ * currently showing. This causes minimal ripples (e.g.
+ * selection change).
+ */
+ gchar *displayed_name = gtk_editable_get_chars (GTK_EDITABLE (window->details->name_field), 0, -1);
+ if (strcmp (displayed_name, name) != 0) {
+ gtk_entry_set_text (GTK_ENTRY (window->details->name_field), name);
+ }
+ g_free (displayed_name);
+ }
+ }
+}
+
+static void
+update_name_field (FMPropertiesWindow *window)
+{
+ CajaFile *file;
+
+ gtk_label_set_text_with_mnemonic (window->details->name_label,
+ ngettext ("_Name:", "_Names:",
+ get_not_gone_original_file_count (window)));
+
+ if (is_multi_file_window (window)) {
+ /* Multifile property dialog, show all names */
+ GString *str;
+ char *name;
+ gboolean first;
+ GList *l;
+
+ str = g_string_new ("");
+
+ first = TRUE;
+
+ for (l = window->details->target_files; l != NULL; l = l->next) {
+ file = CAJA_FILE (l->data);
+
+ if (!caja_file_is_gone (file)) {
+ if (!first) {
+ g_string_append (str, ", ");
+ }
+ first = FALSE;
+
+ name = caja_file_get_display_name (file);
+ g_string_append (str, name);
+ g_free (name);
+ }
+ }
+ set_name_field (window, NULL, str->str);
+ g_string_free (str, TRUE);
+ } else {
+ const char *original_name = NULL;
+ char *current_name;
+
+ file = get_original_file (window);
+
+ if (file == NULL || caja_file_is_gone (file)) {
+ current_name = g_strdup ("");
+ } else {
+ current_name = caja_file_get_display_name (file);
+ }
+
+ /* If the file name has changed since the original name was stored,
+ * update the text in the text field, possibly (deliberately) clobbering
+ * an edit in progress. If the name hasn't changed (but some other
+ * aspect of the file might have), then don't clobber changes.
+ */
+ if (window->details->name_field) {
+ original_name = (const char *) g_object_get_data (G_OBJECT (window->details->name_field), "original_name");
+ }
+
+ set_name_field (window, original_name, current_name);
+
+ if (original_name == NULL ||
+ eel_strcmp (original_name, current_name) != 0) {
+ g_object_set_data_full (G_OBJECT (window->details->name_field),
+ "original_name",
+ current_name,
+ g_free);
+ } else {
+ g_free (current_name);
+ }
+ }
+}
+
+static void
+name_field_restore_original_name (CajaEntry *name_field)
+{
+ const char *original_name;
+ char *displayed_name;
+
+ original_name = (const char *) g_object_get_data (G_OBJECT (name_field),
+ "original_name");
+
+ if (!original_name) {
+ return;
+ }
+
+ displayed_name = gtk_editable_get_chars (GTK_EDITABLE (name_field), 0, -1);
+
+ if (strcmp (original_name, displayed_name) != 0) {
+ gtk_entry_set_text (GTK_ENTRY (name_field), original_name);
+ }
+ caja_entry_select_all (name_field);
+
+ g_free (displayed_name);
+}
+
+static void
+rename_callback (CajaFile *file, GFile *res_loc, GError *error, gpointer callback_data)
+{
+ FMPropertiesWindow *window;
+ char *new_name;
+
+ window = FM_PROPERTIES_WINDOW (callback_data);
+
+ /* Complain to user if rename failed. */
+ if (error != NULL) {
+ new_name = window->details->pending_name;
+ fm_report_error_renaming_file (file,
+ window->details->pending_name,
+ error,
+ GTK_WINDOW (window));
+ if (window->details->name_field != NULL) {
+ name_field_restore_original_name (CAJA_ENTRY (window->details->name_field));
+ }
+ }
+
+ g_object_unref (window);
+}
+
+static void
+set_pending_name (FMPropertiesWindow *window, const char *name)
+{
+ g_free (window->details->pending_name);
+ window->details->pending_name = g_strdup (name);
+}
+
+static void
+name_field_done_editing (CajaEntry *name_field, FMPropertiesWindow *window)
+{
+ CajaFile *file;
+ char *new_name;
+ const char *original_name;
+
+ g_return_if_fail (CAJA_IS_ENTRY (name_field));
+
+ /* Don't apply if the dialog has more than one file */
+ if (is_multi_file_window (window)) {
+ return;
+ }
+
+ file = get_original_file (window);
+
+ /* This gets called when the window is closed, which might be
+ * caused by the file having been deleted.
+ */
+ if (file == NULL || caja_file_is_gone (file)) {
+ return;
+ }
+
+ new_name = gtk_editable_get_chars (GTK_EDITABLE (name_field), 0, -1);
+
+ /* Special case: silently revert text if new text is empty. */
+ if (strlen (new_name) == 0) {
+ name_field_restore_original_name (CAJA_ENTRY (name_field));
+ } else {
+ original_name = (const char *) g_object_get_data (G_OBJECT (window->details->name_field),
+ "original_name");
+ /* Don't rename if not changed since we read the display name.
+ This is needed so that we don't save the display name to the
+ file when nothing is changed */
+ if (strcmp (new_name, original_name) != 0) {
+ set_pending_name (window, new_name);
+ g_object_ref (window);
+ caja_file_rename (file, new_name,
+ rename_callback, window);
+ }
+ }
+
+ g_free (new_name);
+}
+
+static gboolean
+name_field_focus_out (CajaEntry *name_field,
+ GdkEventFocus *event,
+ gpointer callback_data)
+{
+ g_assert (FM_IS_PROPERTIES_WINDOW (callback_data));
+
+ if (gtk_widget_get_sensitive (GTK_WIDGET (name_field))) {
+ name_field_done_editing (name_field, FM_PROPERTIES_WINDOW (callback_data));
+ }
+
+ return FALSE;
+}
+
+static void
+name_field_activate (CajaEntry *name_field, gpointer callback_data)
+{
+ g_assert (CAJA_IS_ENTRY (name_field));
+ g_assert (FM_IS_PROPERTIES_WINDOW (callback_data));
+
+ /* Accept changes. */
+ name_field_done_editing (name_field, FM_PROPERTIES_WINDOW (callback_data));
+
+ caja_entry_select_all_at_idle (name_field);
+}
+
+static gboolean
+file_has_keyword (CajaFile *file, const char *keyword)
+{
+ GList *keywords, *word;
+
+ keywords = caja_file_get_keywords (file);
+ word = g_list_find_custom (keywords, keyword, (GCompareFunc) strcmp);
+ eel_g_list_free_deep (keywords);
+
+ return (word != NULL);
+}
+
+static void
+get_initial_emblem_state (FMPropertiesWindow *window,
+ const char *name,
+ GList **on,
+ GList **off)
+{
+ GList *l;
+
+ *on = NULL;
+ *off = NULL;
+
+ for (l = window->details->original_files; l != NULL; l = l->next) {
+ GList *initial_emblems;
+
+ initial_emblems = g_hash_table_lookup (window->details->initial_emblems,
+ l->data);
+
+ if (g_list_find_custom (initial_emblems, name, (GCompareFunc) strcmp)) {
+ *on = g_list_prepend (*on, l->data);
+ } else {
+ *off = g_list_prepend (*off, l->data);
+ }
+ }
+}
+
+static void
+emblem_button_toggled (GtkToggleButton *button,
+ FMPropertiesWindow *window)
+{
+ GList *l;
+ GList *keywords;
+ GList *word;
+ char *name;
+ GList *files_on;
+ GList *files_off;
+
+ name = g_object_get_data (G_OBJECT (button), "caja_emblem_name");
+
+ files_on = NULL;
+ files_off = NULL;
+ if (gtk_toggle_button_get_active (button)
+ && !gtk_toggle_button_get_inconsistent (button)) {
+ /* Go to the initial state unless the initial state was
+ consistent */
+ get_initial_emblem_state (window, name,
+ &files_on, &files_off);
+
+ if (!(files_on && files_off)) {
+ g_list_free (files_on);
+ g_list_free (files_off);
+ files_on = g_list_copy (window->details->original_files);
+ files_off = NULL;
+ }
+ } else if (gtk_toggle_button_get_inconsistent (button)
+ && !gtk_toggle_button_get_active (button)) {
+ files_on = g_list_copy (window->details->original_files);
+ files_off = NULL;
+ } else {
+ files_off = g_list_copy (window->details->original_files);
+ files_on = NULL;
+ }
+
+ g_signal_handlers_block_by_func (G_OBJECT (button),
+ G_CALLBACK (emblem_button_toggled),
+ window);
+
+ gtk_toggle_button_set_active (button, files_on != NULL);
+ gtk_toggle_button_set_inconsistent (button, files_on && files_off);
+
+ g_signal_handlers_unblock_by_func (G_OBJECT (button),
+ G_CALLBACK (emblem_button_toggled),
+ window);
+
+ for (l = files_on; l != NULL; l = l->next) {
+ CajaFile *file;
+
+ file = CAJA_FILE (l->data);
+
+ keywords = caja_file_get_keywords (file);
+
+ word = g_list_find_custom (keywords, name, (GCompareFunc)strcmp);
+ if (!word) {
+ keywords = g_list_prepend (keywords, g_strdup (name));
+ }
+ caja_file_set_keywords (file, keywords);
+ eel_g_list_free_deep (keywords);
+ }
+
+ for (l = files_off; l != NULL; l = l->next) {
+ CajaFile *file;
+
+ file = CAJA_FILE (l->data);
+
+ keywords = caja_file_get_keywords (file);
+
+ word = g_list_find_custom (keywords, name, (GCompareFunc)strcmp);
+ if (word) {
+ keywords = g_list_remove_link (keywords, word);
+ eel_g_list_free_deep (word);
+ }
+ caja_file_set_keywords (file, keywords);
+ eel_g_list_free_deep (keywords);
+ }
+
+ g_list_free (files_on);
+ g_list_free (files_off);
+}
+
+static void
+emblem_button_update (FMPropertiesWindow *window,
+ GtkToggleButton *button)
+{
+ GList *l;
+ char *name;
+ gboolean all_set;
+ gboolean all_unset;
+
+ name = g_object_get_data (G_OBJECT (button), "caja_emblem_name");
+
+ all_set = TRUE;
+ all_unset = TRUE;
+ for (l = window->details->original_files; l != NULL; l = l->next) {
+ gboolean has_keyword;
+ CajaFile *file;
+
+ file = CAJA_FILE (l->data);
+
+ has_keyword = file_has_keyword (file, name);
+
+ if (has_keyword) {
+ all_unset = FALSE;
+ } else {
+ all_set = FALSE;
+ }
+ }
+
+ g_signal_handlers_block_by_func (G_OBJECT (button),
+ G_CALLBACK (emblem_button_toggled),
+ window);
+
+ gtk_toggle_button_set_active (button, !all_unset);
+ gtk_toggle_button_set_inconsistent (button, !all_unset && !all_set);
+
+ g_signal_handlers_unblock_by_func (G_OBJECT (button),
+ G_CALLBACK (emblem_button_toggled),
+ window);
+
+}
+
+static void
+update_properties_window_title (FMPropertiesWindow *window)
+{
+ char *name, *title;
+ CajaFile *file;
+
+ g_return_if_fail (GTK_IS_WINDOW (window));
+
+ title = g_strdup_printf (_("Properties"));
+
+ if (!is_multi_file_window (window)) {
+ file = get_original_file (window);
+
+ if (file != NULL) {
+ g_free (title);
+ name = caja_file_get_display_name (file);
+ title = g_strdup_printf (_("%s Properties"), name);
+ g_free (name);
+ }
+ }
+
+ gtk_window_set_title (GTK_WINDOW (window), title);
+
+ g_free (title);
+}
+
+static void
+clear_extension_pages (FMPropertiesWindow *window)
+{
+ int i;
+ int num_pages;
+ GtkWidget *page;
+
+ num_pages = gtk_notebook_get_n_pages
+ (GTK_NOTEBOOK (window->details->notebook));
+
+ for (i = 0; i < num_pages; i++) {
+ page = gtk_notebook_get_nth_page
+ (GTK_NOTEBOOK (window->details->notebook), i);
+
+ if (g_object_get_data (G_OBJECT (page), "is-extension-page")) {
+ gtk_notebook_remove_page
+ (GTK_NOTEBOOK (window->details->notebook), i);
+ num_pages--;
+ i--;
+ }
+ }
+}
+
+static void
+refresh_extension_pages (FMPropertiesWindow *window)
+{
+ clear_extension_pages (window);
+ append_extension_pages (window);
+}
+
+static void
+remove_from_dialog (FMPropertiesWindow *window,
+ CajaFile *file)
+{
+ int index;
+ GList *original_link;
+ GList *target_link;
+ CajaFile *original_file;
+ CajaFile *target_file;
+
+ index = g_list_index (window->details->target_files, file);
+ if (index == -1) {
+ index = g_list_index (window->details->original_files, file);
+ g_return_if_fail (index != -1);
+ }
+
+ original_link = g_list_nth (window->details->original_files, index);
+ target_link = g_list_nth (window->details->target_files, index);
+
+ g_return_if_fail (original_link && target_link);
+
+ original_file = CAJA_FILE (original_link->data);
+ target_file = CAJA_FILE (target_link->data);
+
+ window->details->original_files = g_list_remove_link (window->details->original_files, original_link);
+ g_list_free (original_link);
+
+ window->details->target_files = g_list_remove_link (window->details->target_files, target_link);
+ g_list_free (target_link);
+
+ g_hash_table_remove (window->details->initial_emblems, original_file);
+ g_hash_table_remove (window->details->initial_permissions, target_file);
+
+ g_signal_handlers_disconnect_by_func (original_file,
+ G_CALLBACK (file_changed_callback),
+ window);
+ g_signal_handlers_disconnect_by_func (target_file,
+ G_CALLBACK (file_changed_callback),
+ window);
+
+ caja_file_monitor_remove (original_file, &window->details->original_files);
+ caja_file_monitor_remove (target_file, &window->details->target_files);
+
+ caja_file_unref (original_file);
+ caja_file_unref (target_file);
+
+}
+
+static gboolean
+mime_list_equal (GList *a, GList *b)
+{
+ while (a && b) {
+ if (strcmp (a->data, b->data)) {
+ return FALSE;
+ }
+ a = a->next;
+ b = b->next;
+ }
+
+ return (a == b);
+}
+
+static GList *
+get_mime_list (FMPropertiesWindow *window)
+{
+ GList *ret;
+ GList *l;
+
+ ret = NULL;
+ for (l = window->details->target_files; l != NULL; l = l->next) {
+ ret = g_list_append (ret, caja_file_get_mime_type (CAJA_FILE (l->data)));
+ }
+ ret = g_list_reverse (ret);
+ return ret;
+}
+
+static void
+properties_window_update (FMPropertiesWindow *window,
+ GList *files)
+{
+ GList *l;
+ GList *mime_list;
+ GList *tmp;
+ CajaFile *changed_file;
+ gboolean dirty_original = FALSE;
+ gboolean dirty_target = FALSE;
+
+ if (files == NULL) {
+ dirty_original = TRUE;
+ dirty_target = TRUE;
+ }
+
+ for (tmp = files; tmp != NULL; tmp = tmp->next) {
+ changed_file = CAJA_FILE (tmp->data);
+
+ if (changed_file && caja_file_is_gone (changed_file)) {
+ /* Remove the file from the property dialog */
+ remove_from_dialog (window, changed_file);
+ changed_file = NULL;
+
+ if (window->details->original_files == NULL) {
+ return;
+ }
+ }
+ if (changed_file == NULL ||
+ g_list_find (window->details->original_files, changed_file)) {
+ dirty_original = TRUE;
+ }
+ if (changed_file == NULL ||
+ g_list_find (window->details->target_files, changed_file)) {
+ dirty_target = TRUE;
+ }
+
+ }
+
+ if (dirty_original) {
+ update_properties_window_title (window);
+ update_properties_window_icon (GTK_IMAGE (window->details->icon_image));
+
+ update_name_field (window);
+
+ for (l = window->details->emblem_buttons; l != NULL; l = l->next) {
+ emblem_button_update (window, GTK_TOGGLE_BUTTON (l->data));
+ }
+
+ /* If any of the value fields start to depend on the original
+ * value, value_field_updates should be added here */
+ }
+
+ if (dirty_target) {
+ for (l = window->details->permission_buttons; l != NULL; l = l->next) {
+ permission_button_update (window, GTK_TOGGLE_BUTTON (l->data));
+ }
+
+ for (l = window->details->permission_combos; l != NULL; l = l->next) {
+ permission_combo_update (window, GTK_COMBO_BOX (l->data));
+ }
+
+ for (l = window->details->value_fields; l != NULL; l = l->next) {
+ value_field_update (window, GTK_LABEL (l->data));
+ }
+ }
+
+ mime_list = get_mime_list (window);
+
+ if (!window->details->mime_list) {
+ window->details->mime_list = mime_list;
+ } else {
+ if (!mime_list_equal (window->details->mime_list, mime_list)) {
+ refresh_extension_pages (window);
+ }
+
+ eel_g_list_free_deep (window->details->mime_list);
+ window->details->mime_list = mime_list;
+ }
+}
+
+static gboolean
+update_files_callback (gpointer data)
+{
+ FMPropertiesWindow *window;
+
+ window = FM_PROPERTIES_WINDOW (data);
+
+ window->details->update_files_timeout_id = 0;
+
+ properties_window_update (window, window->details->changed_files);
+
+ if (window->details->original_files == NULL) {
+ /* Close the window if no files are left */
+ gtk_widget_destroy (GTK_WIDGET (window));
+ } else {
+ caja_file_list_free (window->details->changed_files);
+ window->details->changed_files = NULL;
+ }
+
+ return FALSE;
+ }
+
+static void
+schedule_files_update (FMPropertiesWindow *window)
+ {
+ g_assert (FM_IS_PROPERTIES_WINDOW (window));
+
+ if (window->details->update_files_timeout_id == 0) {
+ window->details->update_files_timeout_id
+ = g_timeout_add (FILES_UPDATE_INTERVAL,
+ update_files_callback,
+ window);
+ }
+ }
+
+static gboolean
+file_list_attributes_identical (GList *file_list, const char *attribute_name)
+{
+ gboolean identical;
+ char *first_attr;
+ GList *l;
+
+ first_attr = NULL;
+ identical = TRUE;
+
+ for (l = file_list; l != NULL; l = l->next) {
+ CajaFile *file;
+
+ file = CAJA_FILE (l->data);
+
+ if (caja_file_is_gone (file)) {
+ continue;
+ }
+
+ if (first_attr == NULL) {
+ first_attr = caja_file_get_string_attribute_with_default (file, attribute_name);
+ } else {
+ char *attr;
+ attr = caja_file_get_string_attribute_with_default (file, attribute_name);
+ if (strcmp (attr, first_attr)) {
+ identical = FALSE;
+ g_free (attr);
+ break;
+ }
+ g_free (attr);
+ }
+ }
+
+ g_free (first_attr);
+ return identical;
+}
+
+static char *
+file_list_get_string_attribute (GList *file_list,
+ const char *attribute_name,
+ const char *inconsistent_value)
+{
+ if (file_list_attributes_identical (file_list, attribute_name)) {
+ GList *l;
+
+ for (l = file_list; l != NULL; l = l->next) {
+ CajaFile *file;
+
+ file = CAJA_FILE (l->data);
+ if (!caja_file_is_gone (file)) {
+ return caja_file_get_string_attribute_with_default
+ (file,
+ attribute_name);
+ }
+ }
+ return g_strdup (_("unknown"));
+ } else {
+ return g_strdup (inconsistent_value);
+ }
+}
+
+
+static gboolean
+file_list_all_directories (GList *file_list)
+{
+ GList *l;
+ for (l = file_list; l != NULL; l = l->next) {
+ if (!caja_file_is_directory (CAJA_FILE (l->data))) {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static void
+value_field_update_internal (GtkLabel *label,
+ GList *file_list)
+{
+ const char *attribute_name;
+ char *attribute_value;
+ char *inconsistent_string;
+ char *mime_type, *tmp;
+
+ g_assert (GTK_IS_LABEL (label));
+
+ attribute_name = g_object_get_data (G_OBJECT (label), "file_attribute");
+ inconsistent_string = g_object_get_data (G_OBJECT (label), "inconsistent_string");
+ attribute_value = file_list_get_string_attribute (file_list,
+ attribute_name,
+ inconsistent_string);
+ if (!strcmp (attribute_name, "type") && strcmp (attribute_value, inconsistent_string)) {
+ mime_type = file_list_get_string_attribute (file_list,
+ "mime_type",
+ inconsistent_string);
+ if (strcmp (mime_type, inconsistent_string)) {
+ tmp = attribute_value;
+ attribute_value = g_strdup_printf (C_("MIME type description (MIME type)", "%s (%s)"), attribute_value, mime_type);
+ g_free (tmp);
+ }
+ g_free (mime_type);
+ }
+
+ gtk_label_set_text (label, attribute_value);
+ g_free (attribute_value);
+}
+
+static void
+value_field_update (FMPropertiesWindow *window, GtkLabel *label)
+{
+ gboolean use_original;
+
+ use_original = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (label), "show_original"));
+
+ value_field_update_internal (label,
+ (use_original ?
+ window->details->original_files :
+ window->details->target_files));
+}
+
+static GtkLabel *
+attach_label (GtkTable *table,
+ int row,
+ int column,
+ const char *initial_text,
+ gboolean right_aligned,
+ gboolean bold,
+ gboolean ellipsize_text,
+ gboolean selectable,
+ gboolean mnemonic)
+{
+ GtkWidget *label_field;
+
+ if (ellipsize_text) {
+ label_field = gtk_label_new (initial_text);
+ gtk_label_set_ellipsize (GTK_LABEL (label_field),
+ right_aligned ? PANGO_ELLIPSIZE_START :
+ PANGO_ELLIPSIZE_END);
+ } else if (mnemonic) {
+ label_field = gtk_label_new_with_mnemonic (initial_text);
+ } else {
+ label_field = gtk_label_new (initial_text);
+ }
+
+ if (selectable) {
+ gtk_label_set_selectable (GTK_LABEL (label_field), TRUE);
+ }
+
+ if (bold) {
+ eel_gtk_label_make_bold (GTK_LABEL (label_field));
+ }
+ gtk_misc_set_alignment (GTK_MISC (label_field), right_aligned ? 1 : 0, 0.5);
+ gtk_widget_show (label_field);
+ gtk_table_attach (table, label_field,
+ column, column + 1,
+ row, row + 1,
+ ellipsize_text
+ ? GTK_FILL | GTK_EXPAND
+ : GTK_FILL,
+ 0,
+ 0, 0);
+
+ return GTK_LABEL (label_field);
+}
+
+static GtkLabel *
+attach_value_label (GtkTable *table,
+ int row,
+ int column,
+ const char *initial_text)
+{
+ return attach_label (table, row, column, initial_text, FALSE, FALSE, FALSE, TRUE, FALSE);
+}
+
+static GtkLabel *
+attach_ellipsizing_value_label (GtkTable *table,
+ int row,
+ int column,
+ const char *initial_text)
+{
+ return attach_label (table, row, column, initial_text, FALSE, FALSE, TRUE, TRUE, FALSE);
+}
+
+static GtkWidget*
+attach_value_field_internal (FMPropertiesWindow *window,
+ GtkTable *table,
+ int row,
+ int column,
+ const char *file_attribute_name,
+ const char *inconsistent_string,
+ gboolean show_original,
+ gboolean ellipsize_text)
+{
+ GtkLabel *value_field;
+
+ if (ellipsize_text) {
+ value_field = attach_ellipsizing_value_label (table, row, column, "");
+ } else {
+ value_field = attach_value_label (table, row, column, "");
+ }
+
+ /* Stash a copy of the file attribute name in this field for the callback's sake. */
+ g_object_set_data_full (G_OBJECT (value_field), "file_attribute",
+ g_strdup (file_attribute_name), g_free);
+
+ g_object_set_data_full (G_OBJECT (value_field), "inconsistent_string",
+ g_strdup (inconsistent_string), g_free);
+
+ g_object_set_data (G_OBJECT (value_field), "show_original", GINT_TO_POINTER (show_original));
+
+ window->details->value_fields = g_list_prepend (window->details->value_fields,
+ value_field);
+ return GTK_WIDGET(value_field);
+}
+
+static GtkWidget*
+attach_value_field (FMPropertiesWindow *window,
+ GtkTable *table,
+ int row,
+ int column,
+ const char *file_attribute_name,
+ const char *inconsistent_string,
+ gboolean show_original)
+{
+ return attach_value_field_internal (window,
+ table, row, column,
+ file_attribute_name,
+ inconsistent_string,
+ show_original,
+ FALSE);
+}
+
+static GtkWidget*
+attach_ellipsizing_value_field (FMPropertiesWindow *window,
+ GtkTable *table,
+ int row,
+ int column,
+ const char *file_attribute_name,
+ const char *inconsistent_string,
+ gboolean show_original)
+{
+ return attach_value_field_internal (window,
+ table, row, column,
+ file_attribute_name,
+ inconsistent_string,
+ show_original,
+ TRUE);
+}
+
+static void
+group_change_callback (CajaFile *file,
+ GFile *res_loc,
+ GError *error,
+ FMPropertiesWindow *window)
+{
+ char *group;
+
+ g_assert (FM_IS_PROPERTIES_WINDOW (window));
+ g_assert (window->details->group_change_file == file);
+
+ group = window->details->group_change_group;
+ g_assert (group != NULL);
+
+ /* Report the error if it's an error. */
+ eel_timed_wait_stop ((EelCancelCallback) cancel_group_change_callback, window);
+ fm_report_error_setting_group (file, error, GTK_WINDOW (window));
+
+ caja_file_unref (file);
+ g_free (group);
+
+ window->details->group_change_file = NULL;
+ window->details->group_change_group = NULL;
+ g_object_unref (G_OBJECT (window));
+}
+
+static void
+cancel_group_change_callback (FMPropertiesWindow *window)
+{
+ CajaFile *file;
+ char *group;
+
+ file = window->details->group_change_file;
+ g_assert (CAJA_IS_FILE (file));
+
+ group = window->details->group_change_group;
+ g_assert (group != NULL);
+
+ caja_file_cancel (file, (CajaFileOperationCallback) group_change_callback, window);
+
+ g_free (group);
+ caja_file_unref (file);
+
+ window->details->group_change_file = NULL;
+ window->details->group_change_group = NULL;
+ g_object_unref (window);
+}
+
+static gboolean
+schedule_group_change_timeout (FMPropertiesWindow *window)
+{
+ CajaFile *file;
+ char *group;
+
+ g_assert (FM_IS_PROPERTIES_WINDOW (window));
+
+ file = window->details->group_change_file;
+ g_assert (CAJA_IS_FILE (file));
+
+ group = window->details->group_change_group;
+ g_assert (group != NULL);
+
+ eel_timed_wait_start
+ ((EelCancelCallback) cancel_group_change_callback,
+ window,
+ _("Cancel Group Change?"),
+ GTK_WINDOW (window));
+
+ caja_file_set_group
+ (file, group,
+ (CajaFileOperationCallback) group_change_callback, window);
+
+ window->details->group_change_timeout = 0;
+ return FALSE;
+}
+
+static void
+schedule_group_change (FMPropertiesWindow *window,
+ CajaFile *file,
+ const char *group)
+{
+ g_assert (FM_IS_PROPERTIES_WINDOW (window));
+ g_assert (window->details->group_change_group == NULL);
+ g_assert (window->details->group_change_file == NULL);
+ g_assert (CAJA_IS_FILE (file));
+
+ window->details->group_change_file = caja_file_ref (file);
+ window->details->group_change_group = g_strdup (group);
+ g_object_ref (G_OBJECT (window));
+ window->details->group_change_timeout =
+ g_timeout_add (CHOWN_CHGRP_TIMEOUT,
+ (GSourceFunc) schedule_group_change_timeout,
+ window);
+}
+
+static void
+unschedule_or_cancel_group_change (FMPropertiesWindow *window)
+{
+ CajaFile *file;
+ char *group;
+
+ g_assert (FM_IS_PROPERTIES_WINDOW (window));
+
+ file = window->details->group_change_file;
+ group = window->details->group_change_group;
+
+ g_assert ((file == NULL && group == NULL) ||
+ (file != NULL && group != NULL));
+
+ if (file != NULL) {
+ g_assert (CAJA_IS_FILE (file));
+
+ if (window->details->group_change_timeout == 0) {
+ caja_file_cancel (file,
+ (CajaFileOperationCallback) group_change_callback, window);
+ eel_timed_wait_stop ((EelCancelCallback) cancel_group_change_callback, window);
+ }
+
+ caja_file_unref (file);
+ g_free (group);
+
+ window->details->group_change_file = NULL;
+ window->details->group_change_group = NULL;
+ g_object_unref (G_OBJECT (window));
+ }
+
+ if (window->details->group_change_timeout > 0) {
+ g_assert (file != NULL);
+ g_source_remove (window->details->group_change_timeout);
+ window->details->group_change_timeout = 0;
+ }
+}
+
+static void
+changed_group_callback (GtkComboBox *combo_box, CajaFile *file)
+{
+ FMPropertiesWindow *window;
+ char *group;
+ char *cur_group;
+
+ g_assert (GTK_IS_COMBO_BOX (combo_box));
+ g_assert (CAJA_IS_FILE (file));
+
+ group = gtk_combo_box_get_active_text (combo_box);
+ cur_group = caja_file_get_group_name (file);
+
+ if (group != NULL && strcmp (group, cur_group) != 0) {
+ /* Try to change file group. If this fails, complain to user. */
+ window = FM_PROPERTIES_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (combo_box), GTK_TYPE_WINDOW));
+
+ unschedule_or_cancel_group_change (window);
+ schedule_group_change (window, file, group);
+ }
+ g_free (group);
+ g_free (cur_group);
+}
+
+/* checks whether the given column at the first level
+ * of model has the specified entries in the given order. */
+static gboolean
+tree_model_entries_equal (GtkTreeModel *model,
+ unsigned int column,
+ GList *entries)
+{
+ GtkTreeIter iter;
+ gboolean empty_model;
+
+ g_assert (GTK_IS_TREE_MODEL (model));
+ g_assert (gtk_tree_model_get_column_type (model, column) == G_TYPE_STRING);
+
+ empty_model = !gtk_tree_model_get_iter_first (model, &iter);
+
+ if (!empty_model && entries != NULL) {
+ GList *l;
+
+ l = entries;
+
+ do {
+ char *val;
+
+ gtk_tree_model_get (model, &iter,
+ column, &val,
+ -1);
+ if ((val == NULL && l->data != NULL) ||
+ (val != NULL && l->data == NULL) ||
+ (val != NULL && strcmp (val, l->data))) {
+ g_free (val);
+ return FALSE;
+ }
+
+ g_free (val);
+ l = l->next;
+ } while (gtk_tree_model_iter_next (model, &iter));
+
+ return l == NULL;
+ } else {
+ return (empty_model && entries == NULL) ||
+ (!empty_model && entries != NULL);
+ }
+}
+
+static char *
+combo_box_get_active_entry (GtkComboBox *combo_box,
+ unsigned int column)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ char *val;
+
+ g_assert (GTK_IS_COMBO_BOX (combo_box));
+
+ if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo_box), &iter)) {
+ model = gtk_combo_box_get_model (combo_box);
+ g_assert (GTK_IS_TREE_MODEL (model));
+
+ gtk_tree_model_get (model, &iter,
+ column, &val,
+ -1);
+ return val;
+ }
+
+ return NULL;
+}
+
+/* returns the index of the given entry in the the given column
+ * at the first level of model. Returns -1 if entry can't be found
+ * or entry is NULL.
+ * */
+static int
+tree_model_get_entry_index (GtkTreeModel *model,
+ unsigned int column,
+ const char *entry)
+{
+ GtkTreeIter iter;
+ int index;
+ gboolean empty_model;
+
+ g_assert (GTK_IS_TREE_MODEL (model));
+ g_assert (gtk_tree_model_get_column_type (model, column) == G_TYPE_STRING);
+
+ empty_model = !gtk_tree_model_get_iter_first (model, &iter);
+ if (!empty_model && entry != NULL) {
+ index = 0;
+
+ do {
+ char *val;
+
+ gtk_tree_model_get (model, &iter,
+ column, &val,
+ -1);
+ if (val != NULL && !strcmp (val, entry)) {
+ g_free (val);
+ return index;
+ }
+
+ g_free (val);
+ index++;
+ } while (gtk_tree_model_iter_next (model, &iter));
+ }
+
+ return -1;
+}
+
+
+static void
+synch_groups_combo_box (GtkComboBox *combo_box, CajaFile *file)
+{
+ GList *groups;
+ GList *node;
+ GtkTreeModel *model;
+ GtkListStore *store;
+ const char *group_name;
+ char *current_group_name;
+ int group_index;
+ int current_group_index;
+
+ g_assert (GTK_IS_COMBO_BOX (combo_box));
+ g_assert (CAJA_IS_FILE (file));
+
+ if (caja_file_is_gone (file)) {
+ return;
+ }
+
+ groups = caja_file_get_settable_group_names (file);
+
+ model = gtk_combo_box_get_model (combo_box);
+ store = GTK_LIST_STORE (model);
+ g_assert (GTK_IS_LIST_STORE (model));
+
+ if (!tree_model_entries_equal (model, 0, groups)) {
+ /* Clear the contents of ComboBox in a wacky way because there
+ * is no function to clear all items and also no function to obtain
+ * the number of items in a combobox.
+ */
+ gtk_list_store_clear (store);
+
+ for (node = groups, group_index = 0; node != NULL; node = node->next, ++group_index) {
+ group_name = (const char *)node->data;
+ gtk_combo_box_append_text (combo_box, group_name);
+ }
+ }
+
+ current_group_name = caja_file_get_group_name (file);
+ current_group_index = tree_model_get_entry_index (model, 0, current_group_name);
+
+ /* If current group wasn't in list, we prepend it (with a separator).
+ * This can happen if the current group is an id with no matching
+ * group in the groups file.
+ */
+ if (current_group_index < 0 && current_group_name != NULL) {
+ if (groups != NULL) {
+ /* add separator */
+ gtk_combo_box_prepend_text (combo_box, "-");
+ }
+
+ gtk_combo_box_prepend_text (combo_box, current_group_name);
+ current_group_index = 0;
+ }
+ gtk_combo_box_set_active (combo_box, current_group_index);
+
+ g_free (current_group_name);
+ eel_g_list_free_deep (groups);
+}
+
+static gboolean
+combo_box_row_separator_func (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ gchar *text;
+ gboolean ret;
+
+ gtk_tree_model_get (model, iter, 0, &text, -1);
+
+ if (text == NULL) {
+ return FALSE;
+ }
+
+ if (strcmp (text, "-") == 0) {
+ ret = TRUE;
+ } else {
+ ret = FALSE;
+ }
+
+ g_free (text);
+ return ret;
+}
+
+static GtkComboBox *
+attach_combo_box (GtkTable *table,
+ int row,
+ gboolean two_columns)
+{
+ GtkWidget *combo_box;
+ GtkWidget *aligner;
+
+ if (!two_columns) {
+ combo_box = gtk_combo_box_new_text ();
+ } else {
+ GtkTreeModel *model;
+ GtkCellRenderer *renderer;
+
+ model = GTK_TREE_MODEL (gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING));
+ combo_box = gtk_combo_box_new_with_model (model);
+ g_object_unref (G_OBJECT (model));
+
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box), renderer, TRUE);
+ gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (combo_box), renderer,
+ "text", 0);
+
+ }
+ gtk_widget_show (combo_box);
+
+ gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (combo_box),
+ combo_box_row_separator_func,
+ NULL,
+ NULL);
+
+ /* Put combo box in alignment to make it left-justified
+ * but minimally sized.
+ */
+ aligner = gtk_alignment_new (0, 0.5, 0, 0);
+ gtk_widget_show (aligner);
+
+ gtk_container_add (GTK_CONTAINER (aligner), combo_box);
+ gtk_table_attach (table, aligner,
+ VALUE_COLUMN, VALUE_COLUMN + 1,
+ row, row + 1,
+ GTK_FILL, 0,
+ 0, 0);
+
+ return GTK_COMBO_BOX (combo_box);
+}
+
+static GtkComboBox*
+attach_group_combo_box (GtkTable *table,
+ int row,
+ CajaFile *file)
+{
+ GtkComboBox *combo_box;
+
+ combo_box = attach_combo_box (table, row, FALSE);
+
+ synch_groups_combo_box (combo_box, file);
+
+ /* Connect to signal to update menu when file changes. */
+ g_signal_connect_object (file, "changed",
+ G_CALLBACK (synch_groups_combo_box),
+ combo_box, G_CONNECT_SWAPPED);
+ g_signal_connect_data (combo_box, "changed",
+ G_CALLBACK (changed_group_callback),
+ caja_file_ref (file),
+ (GClosureNotify)caja_file_unref, 0);
+
+ return combo_box;
+}
+
+static void
+owner_change_callback (CajaFile *file,
+ GFile *result_location,
+ GError *error,
+ FMPropertiesWindow *window)
+{
+ char *owner;
+
+ g_assert (FM_IS_PROPERTIES_WINDOW (window));
+ g_assert (window->details->owner_change_file == file);
+
+ owner = window->details->owner_change_owner;
+ g_assert (owner != NULL);
+
+ /* Report the error if it's an error. */
+ eel_timed_wait_stop ((EelCancelCallback) cancel_owner_change_callback, window);
+ fm_report_error_setting_owner (file, error, GTK_WINDOW (window));
+
+ caja_file_unref (file);
+ g_free (owner);
+
+ window->details->owner_change_file = NULL;
+ window->details->owner_change_owner = NULL;
+ g_object_unref (G_OBJECT (window));
+}
+
+static void
+cancel_owner_change_callback (FMPropertiesWindow *window)
+{
+ CajaFile *file;
+ char *owner;
+
+ file = window->details->owner_change_file;
+ g_assert (CAJA_IS_FILE (file));
+
+ owner = window->details->owner_change_owner;
+ g_assert (owner != NULL);
+
+ caja_file_cancel (file, (CajaFileOperationCallback) owner_change_callback, window);
+
+ caja_file_unref (file);
+ g_free (owner);
+
+ window->details->owner_change_file = NULL;
+ window->details->owner_change_owner = NULL;
+ g_object_unref (window);
+}
+
+static gboolean
+schedule_owner_change_timeout (FMPropertiesWindow *window)
+{
+ CajaFile *file;
+ char *owner;
+
+ g_assert (FM_IS_PROPERTIES_WINDOW (window));
+
+ file = window->details->owner_change_file;
+ g_assert (CAJA_IS_FILE (file));
+
+ owner = window->details->owner_change_owner;
+ g_assert (owner != NULL);
+
+ eel_timed_wait_start
+ ((EelCancelCallback) cancel_owner_change_callback,
+ window,
+ _("Cancel Owner Change?"),
+ GTK_WINDOW (window));
+
+ caja_file_set_owner
+ (file, owner,
+ (CajaFileOperationCallback) owner_change_callback, window);
+
+ window->details->owner_change_timeout = 0;
+ return FALSE;
+}
+
+static void
+schedule_owner_change (FMPropertiesWindow *window,
+ CajaFile *file,
+ const char *owner)
+{
+ g_assert (FM_IS_PROPERTIES_WINDOW (window));
+ g_assert (window->details->owner_change_owner == NULL);
+ g_assert (window->details->owner_change_file == NULL);
+ g_assert (CAJA_IS_FILE (file));
+
+ window->details->owner_change_file = caja_file_ref (file);
+ window->details->owner_change_owner = g_strdup (owner);
+ g_object_ref (G_OBJECT (window));
+ window->details->owner_change_timeout =
+ g_timeout_add (CHOWN_CHGRP_TIMEOUT,
+ (GSourceFunc) schedule_owner_change_timeout,
+ window);
+}
+
+static void
+unschedule_or_cancel_owner_change (FMPropertiesWindow *window)
+{
+ CajaFile *file;
+ char *owner;
+
+ g_assert (FM_IS_PROPERTIES_WINDOW (window));
+
+ file = window->details->owner_change_file;
+ owner = window->details->owner_change_owner;
+
+ g_assert ((file == NULL && owner == NULL) ||
+ (file != NULL && owner != NULL));
+
+ if (file != NULL) {
+ g_assert (CAJA_IS_FILE (file));
+
+ if (window->details->owner_change_timeout == 0) {
+ caja_file_cancel (file,
+ (CajaFileOperationCallback) owner_change_callback, window);
+ eel_timed_wait_stop ((EelCancelCallback) cancel_owner_change_callback, window);
+ }
+
+ caja_file_unref (file);
+ g_free (owner);
+
+ window->details->owner_change_file = NULL;
+ window->details->owner_change_owner = NULL;
+ g_object_unref (G_OBJECT (window));
+ }
+
+ if (window->details->owner_change_timeout > 0) {
+ g_assert (file != NULL);
+ g_source_remove (window->details->owner_change_timeout);
+ window->details->owner_change_timeout = 0;
+ }
+}
+
+static void
+changed_owner_callback (GtkComboBox *combo_box, CajaFile* file)
+{
+ FMPropertiesWindow *window;
+ char *owner_text;
+ char **name_array;
+ char *new_owner;
+ char *cur_owner;
+
+ g_assert (GTK_IS_COMBO_BOX (combo_box));
+ g_assert (CAJA_IS_FILE (file));
+
+ owner_text = combo_box_get_active_entry (combo_box, 0);
+ if (! owner_text)
+ return;
+ name_array = g_strsplit (owner_text, " - ", 2);
+ new_owner = name_array[0];
+ g_free (owner_text);
+ cur_owner = caja_file_get_owner_name (file);
+
+ if (strcmp (new_owner, cur_owner) != 0) {
+ /* Try to change file owner. If this fails, complain to user. */
+ window = FM_PROPERTIES_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (combo_box), GTK_TYPE_WINDOW));
+
+ unschedule_or_cancel_owner_change (window);
+ schedule_owner_change (window, file, new_owner);
+ }
+ g_strfreev (name_array);
+ g_free (cur_owner);
+}
+
+static void
+synch_user_menu (GtkComboBox *combo_box, CajaFile *file)
+{
+ GList *users;
+ GList *node;
+ GtkTreeModel *model;
+ GtkListStore *store;
+ GtkTreeIter iter;
+ char *user_name;
+ char *owner_name;
+ int user_index;
+ int owner_index;
+ char **name_array;
+ char *combo_text;
+
+ g_assert (GTK_IS_COMBO_BOX (combo_box));
+ g_assert (CAJA_IS_FILE (file));
+
+ if (caja_file_is_gone (file)) {
+ return;
+ }
+
+ users = caja_get_user_names ();
+
+ model = gtk_combo_box_get_model (combo_box);
+ store = GTK_LIST_STORE (model);
+ g_assert (GTK_IS_LIST_STORE (model));
+
+ if (!tree_model_entries_equal (model, 1, users)) {
+ /* Clear the contents of ComboBox in a wacky way because there
+ * is no function to clear all items and also no function to obtain
+ * the number of items in a combobox.
+ */
+ gtk_list_store_clear (store);
+
+ for (node = users, user_index = 0; node != NULL; node = node->next, ++user_index) {
+ user_name = (char *)node->data;
+
+ name_array = g_strsplit (user_name, "\n", 2);
+ if (name_array[1] != NULL) {
+ combo_text = g_strdup_printf ("%s - %s", name_array[0], name_array[1]);
+ } else {
+ combo_text = g_strdup (name_array[0]);
+ }
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ 0, combo_text,
+ 1, user_name,
+ -1);
+
+ g_strfreev (name_array);
+ g_free (combo_text);
+ }
+ }
+
+ owner_name = caja_file_get_string_attribute (file, "owner");
+ owner_index = tree_model_get_entry_index (model, 0, owner_name);
+
+ /* If owner wasn't in list, we prepend it (with a separator).
+ * This can happen if the owner is an id with no matching
+ * identifier in the passwords file.
+ */
+ if (owner_index < 0 && owner_name != NULL) {
+ if (users != NULL) {
+ /* add separator */
+ gtk_list_store_prepend (store, &iter);
+ gtk_list_store_set (store, &iter,
+ 0, "-",
+ 1, NULL,
+ -1);
+ }
+
+ name_array = g_strsplit (owner_name, " - ", 2);
+ if (name_array[1] != NULL) {
+ user_name = g_strdup_printf ("%s\n%s", name_array[0], name_array[1]);
+ } else {
+ user_name = g_strdup (name_array[0]);
+ }
+ owner_index = 0;
+
+ gtk_list_store_prepend (store, &iter);
+ gtk_list_store_set (store, &iter,
+ 0, owner_name,
+ 1, user_name,
+ -1);
+
+ g_free (user_name);
+ g_strfreev (name_array);
+ }
+
+ gtk_combo_box_set_active (combo_box, owner_index);
+
+ g_free (owner_name);
+ eel_g_list_free_deep (users);
+}
+
+static GtkComboBox*
+attach_owner_combo_box (GtkTable *table,
+ int row,
+ CajaFile *file)
+{
+ GtkComboBox *combo_box;
+
+ combo_box = attach_combo_box (table, row, TRUE);
+
+ synch_user_menu (combo_box, file);
+
+ /* Connect to signal to update menu when file changes. */
+ g_signal_connect_object (file, "changed",
+ G_CALLBACK (synch_user_menu),
+ combo_box, G_CONNECT_SWAPPED);
+ g_signal_connect_data (combo_box, "changed",
+ G_CALLBACK (changed_owner_callback),
+ caja_file_ref (file),
+ (GClosureNotify)caja_file_unref, 0);
+
+ return combo_box;
+}
+
+static guint
+append_row (GtkTable *table)
+{
+ guint new_row_count;
+ gint nrows, ncols;
+
+ g_object_get (table, "n-rows", &nrows, "n-columns", &ncols, NULL);
+
+ new_row_count = nrows + 1;
+
+ gtk_table_resize (table, new_row_count, ncols);
+ gtk_table_set_row_spacing (table, new_row_count - 1, ROW_PAD);
+
+ return new_row_count - 1;
+}
+
+static gboolean
+file_has_prefix (CajaFile *file,
+ GList *prefix_candidates)
+{
+ GList *p;
+ GFile *location, *candidate_location;
+
+ location = caja_file_get_location (file);
+
+ for (p = prefix_candidates; p != NULL; p = p->next) {
+ if (file == p->data) {
+ continue;
+ }
+
+ candidate_location = caja_file_get_location (CAJA_FILE (p->data));
+ if (g_file_has_prefix (location, candidate_location)) {
+ g_object_unref (location);
+ g_object_unref (candidate_location);
+ return TRUE;
+ }
+ g_object_unref (candidate_location);
+ }
+
+ g_object_unref (location);
+
+ return FALSE;
+}
+
+static void
+directory_contents_value_field_update (FMPropertiesWindow *window)
+{
+ CajaRequestStatus file_status, status;
+ char *text, *temp;
+ guint directory_count;
+ guint file_count;
+ guint total_count;
+ guint unreadable_directory_count;
+ goffset total_size;
+ gboolean used_two_lines;
+ CajaFile *file;
+ GList *l;
+ guint file_unreadable;
+ goffset file_size;
+
+ g_assert (FM_IS_PROPERTIES_WINDOW (window));
+
+ status = CAJA_REQUEST_DONE;
+ file_status = CAJA_REQUEST_NOT_STARTED;
+ total_count = window->details->total_count;
+ total_size = window->details->total_size;
+ unreadable_directory_count = FALSE;
+
+ for (l = window->details->target_files; l; l = l->next) {
+ file = CAJA_FILE (l->data);
+
+ if (file_has_prefix (file, window->details->target_files)) {
+ /* don't count nested files twice */
+ continue;
+ }
+
+ if (caja_file_is_directory (file)) {
+ file_status = caja_file_get_deep_counts (file,
+ &directory_count,
+ &file_count,
+ &file_unreadable,
+ &file_size,
+ TRUE);
+ total_count += (file_count + directory_count);
+ total_size += file_size;
+
+ if (file_unreadable) {
+ unreadable_directory_count = TRUE;
+ }
+
+ if (file_status != CAJA_REQUEST_DONE) {
+ status = file_status;
+ }
+ } else {
+ ++total_count;
+ total_size += caja_file_get_size (file);
+ }
+ }
+
+ /* If we've already displayed the total once, don't do another visible
+ * count-up if the deep_count happens to get invalidated.
+ * But still display the new total, since it might have changed.
+ */
+ if (window->details->deep_count_finished &&
+ status != CAJA_REQUEST_DONE) {
+ return;
+ }
+
+ text = NULL;
+ used_two_lines = FALSE;
+
+ if (total_count == 0) {
+ switch (status) {
+ case CAJA_REQUEST_DONE:
+ if (unreadable_directory_count == 0) {
+ text = g_strdup (_("nothing"));
+ } else {
+ text = g_strdup (_("unreadable"));
+ }
+
+ break;
+ default:
+ text = g_strdup ("...");
+ }
+ } else {
+ char *size_str;
+
+ #if GLIB_CHECK_VERSION(2, 30, 0)
+ size_str = g_format_size(total_size);
+ #else
+ size_str = g_format_size_for_display(total_size);
+ #endif
+
+ text = g_strdup_printf (ngettext("%'d item, with size %s",
+ "%'d items, totalling %s",
+ total_count),
+ total_count, size_str);
+ g_free (size_str);
+
+ if (unreadable_directory_count != 0) {
+ temp = text;
+ text = g_strconcat (temp, "\n",
+ _("(some contents unreadable)"),
+ NULL);
+ g_free (temp);
+ used_two_lines = TRUE;
+ }
+ }
+
+ gtk_label_set_text (window->details->directory_contents_value_field,
+ text);
+ g_free (text);
+
+ /* Also set the title field here, with a trailing carriage return &
+ * space if the value field has two lines. This is a hack to get the
+ * "Contents:" title to line up with the first line of the
+ * 2-line value. Maybe there's a better way to do this, but I
+ * couldn't think of one.
+ */
+ text = g_strdup (_("Contents:"));
+ if (used_two_lines) {
+ temp = text;
+ text = g_strconcat (temp, "\n ", NULL);
+ g_free (temp);
+ }
+ gtk_label_set_text (window->details->directory_contents_title_field,
+ text);
+ g_free (text);
+
+ if (status == CAJA_REQUEST_DONE) {
+ window->details->deep_count_finished = TRUE;
+ }
+}
+
+static gboolean
+update_directory_contents_callback (gpointer data)
+{
+ FMPropertiesWindow *window;
+
+ window = FM_PROPERTIES_WINDOW (data);
+
+ window->details->update_directory_contents_timeout_id = 0;
+ directory_contents_value_field_update (window);
+
+ return FALSE;
+}
+
+static void
+schedule_directory_contents_update (FMPropertiesWindow *window)
+{
+ g_assert (FM_IS_PROPERTIES_WINDOW (window));
+
+ if (window->details->update_directory_contents_timeout_id == 0) {
+ window->details->update_directory_contents_timeout_id
+ = g_timeout_add (DIRECTORY_CONTENTS_UPDATE_INTERVAL,
+ update_directory_contents_callback,
+ window);
+ }
+}
+
+static GtkLabel *
+attach_directory_contents_value_field (FMPropertiesWindow *window,
+ GtkTable *table,
+ int row)
+{
+ GtkLabel *value_field;
+ GList *l;
+ CajaFile *file;
+
+ value_field = attach_value_label (table, row, VALUE_COLUMN, "");
+
+ g_assert (window->details->directory_contents_value_field == NULL);
+ window->details->directory_contents_value_field = value_field;
+
+ gtk_label_set_line_wrap (value_field, TRUE);
+
+ /* Fill in the initial value. */
+ directory_contents_value_field_update (window);
+
+ for (l = window->details->target_files; l; l = l->next) {
+ file = CAJA_FILE (l->data);
+ caja_file_recompute_deep_counts (file);
+
+ g_signal_connect_object (file,
+ "updated_deep_count_in_progress",
+ G_CALLBACK (schedule_directory_contents_update),
+ window, G_CONNECT_SWAPPED);
+ }
+
+ return value_field;
+}
+
+static GtkLabel *
+attach_title_field (GtkTable *table,
+ int row,
+ const char *title)
+{
+ return attach_label (table, row, TITLE_COLUMN, title, FALSE, FALSE, FALSE, FALSE, TRUE);
+}
+
+static guint
+append_title_field (GtkTable *table, const char *title, GtkLabel **label)
+{
+ guint last_row;
+ GtkLabel *title_label;
+
+ last_row = append_row (table);
+ title_label = attach_title_field (table, last_row, title);
+
+ if (label) {
+ *label = title_label;
+ }
+
+ return last_row;
+}
+
+#define INCONSISTENT_STATE_STRING \
+ "\xE2\x80\x92"
+
+static guint
+append_title_value_pair (FMPropertiesWindow *window,
+ GtkTable *table,
+ const char *title,
+ const char *file_attribute_name,
+ const char *inconsistent_state,
+ gboolean show_original)
+{
+ guint last_row;
+ GtkLabel *title_label;
+ GtkWidget *value;
+
+ last_row = append_title_field (table, title, &title_label);
+ value = attach_value_field (window, table, last_row, VALUE_COLUMN,
+ file_attribute_name,
+ inconsistent_state,
+ show_original);
+ gtk_label_set_mnemonic_widget (title_label, value);
+ return last_row;
+}
+
+static guint
+append_title_and_ellipsizing_value (FMPropertiesWindow *window,
+ GtkTable *table,
+ const char *title,
+ const char *file_attribute_name,
+ const char *inconsistent_state,
+ gboolean show_original)
+{
+ GtkLabel *title_label;
+ GtkWidget *value;
+ guint last_row;
+
+ last_row = append_title_field (table, title, &title_label);
+ value = attach_ellipsizing_value_field (window, table, last_row, VALUE_COLUMN,
+ file_attribute_name,
+ inconsistent_state,
+ show_original);
+ gtk_label_set_mnemonic_widget (title_label, value);
+
+ return last_row;
+}
+
+static guint
+append_directory_contents_fields (FMPropertiesWindow *window,
+ GtkTable *table)
+{
+ GtkLabel *title_field, *value_field;
+ guint last_row;
+
+ last_row = append_row (table);
+
+ title_field = attach_title_field (table, last_row, "");
+ window->details->directory_contents_title_field = title_field;
+ gtk_label_set_line_wrap (title_field, TRUE);
+
+ value_field = attach_directory_contents_value_field
+ (window, table, last_row);
+
+ gtk_label_set_mnemonic_widget(title_field, GTK_WIDGET(value_field));
+ return last_row;
+}
+
+static GtkWidget *
+create_page_with_hbox (GtkNotebook *notebook,
+ const char *title)
+{
+ GtkWidget *hbox;
+
+ g_assert (GTK_IS_NOTEBOOK (notebook));
+ g_assert (title != NULL);
+
+ hbox = gtk_hbox_new (FALSE, 0);
+ gtk_widget_show (hbox);
+ gtk_container_set_border_width (GTK_CONTAINER (hbox), 12);
+ gtk_box_set_spacing (GTK_BOX (hbox), 12);
+ gtk_notebook_append_page (notebook, hbox, gtk_label_new (title));
+
+ return hbox;
+}
+
+static GtkWidget *
+create_page_with_vbox (GtkNotebook *notebook,
+ const char *title)
+{
+ GtkWidget *vbox;
+
+ g_assert (GTK_IS_NOTEBOOK (notebook));
+ g_assert (title != NULL);
+
+ vbox = gtk_vbox_new (FALSE, 0);
+ gtk_widget_show (vbox);
+ gtk_container_set_border_width (GTK_CONTAINER (vbox), 12);
+ gtk_notebook_append_page (notebook, vbox, gtk_label_new (title));
+
+ return vbox;
+}
+
+static GtkWidget *
+append_blank_row (GtkTable *table)
+{
+ GtkWidget *separator;
+
+ append_title_field (table, "", (GtkLabel **) &separator);
+
+ return separator;
+}
+
+static void
+apply_standard_table_padding (GtkTable *table)
+{
+ gtk_table_set_row_spacings (table, ROW_PAD);
+ gtk_table_set_col_spacings (table, 12);
+}
+
+static GtkWidget *
+create_attribute_value_table (GtkVBox *vbox, int row_count)
+{
+ GtkWidget *table;
+
+ table = gtk_table_new (row_count, COLUMN_COUNT, FALSE);
+ apply_standard_table_padding (GTK_TABLE (table));
+ gtk_widget_show (table);
+ gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
+
+ return table;
+}
+
+static gboolean
+is_merged_trash_directory (CajaFile *file)
+{
+ char *file_uri;
+ gboolean result;
+
+ file_uri = caja_file_get_uri (file);
+ result = strcmp (file_uri, "trash:///") == 0;
+ g_free (file_uri);
+
+ return result;
+}
+
+static gboolean
+is_computer_directory (CajaFile *file)
+{
+ char *file_uri;
+ gboolean result;
+
+ file_uri = caja_file_get_uri (file);
+ result = strcmp (file_uri, "computer:///") == 0;
+ g_free (file_uri);
+
+ return result;
+}
+
+static gboolean
+is_network_directory (CajaFile *file)
+{
+ char *file_uri;
+ gboolean result;
+
+ file_uri = caja_file_get_uri (file);
+ result = strcmp (file_uri, "network:///") == 0;
+ g_free (file_uri);
+
+ return result;
+}
+
+static gboolean
+is_burn_directory (CajaFile *file)
+{
+ char *file_uri;
+ gboolean result;
+
+ file_uri = caja_file_get_uri (file);
+ result = strcmp (file_uri, "burn:///") == 0;
+ g_free (file_uri);
+
+ return result;
+}
+
+static gboolean
+should_show_custom_icon_buttons (FMPropertiesWindow *window)
+{
+ if (is_multi_file_window (window)) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+should_show_file_type (FMPropertiesWindow *window)
+{
+ if (!is_multi_file_window (window)
+ && (is_merged_trash_directory (get_target_file (window)) ||
+ is_computer_directory (get_target_file (window)) ||
+ is_network_directory (get_target_file (window)) ||
+ is_burn_directory (get_target_file (window)))) {
+ return FALSE;
+ }
+
+
+ return TRUE;
+}
+
+static gboolean
+should_show_location_info (FMPropertiesWindow *window)
+{
+ if (!is_multi_file_window (window)
+ && (is_merged_trash_directory (get_target_file (window)) ||
+ is_computer_directory (get_target_file (window)) ||
+ is_network_directory (get_target_file (window)) ||
+ is_burn_directory (get_target_file (window)))) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+should_show_accessed_date (FMPropertiesWindow *window)
+{
+ /* Accessed date for directory seems useless. If we some
+ * day decide that it is useful, we should separately
+ * consider whether it's useful for "trash:".
+ */
+ if (file_list_all_directories (window->details->target_files)) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+should_show_link_target (FMPropertiesWindow *window)
+{
+ if (!is_multi_file_window (window)
+ && caja_file_is_symbolic_link (get_target_file (window))) {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+should_show_free_space (FMPropertiesWindow *window)
+{
+
+ if (!is_multi_file_window (window)
+ && (is_merged_trash_directory (get_target_file (window)) ||
+ is_computer_directory (get_target_file (window)) ||
+ is_network_directory (get_target_file (window)) ||
+ is_burn_directory (get_target_file (window)))) {
+ return FALSE;
+ }
+
+ if (file_list_all_directories (window->details->target_files)) {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+should_show_volume_usage (FMPropertiesWindow *window)
+{
+ CajaFile *file;
+ gboolean success = FALSE;
+
+ if (is_multi_file_window (window)) {
+ return FALSE;
+ }
+
+ file = get_original_file (window);
+
+ if (file == NULL) {
+ return FALSE;
+ }
+
+ if (caja_file_can_unmount (file)) {
+ return TRUE;
+ }
+
+#ifdef TODO_GIO
+ /* Look at is_mountpoint for activation uri */
+#endif
+ return success;
+}
+
+static void
+paint_used_legend (GtkWidget *widget, GdkEventExpose *eev, gpointer data)
+{
+ FMPropertiesWindow *window;
+ cairo_t *cr;
+ gint width, height;
+ GtkAllocation allocation;
+
+ gtk_widget_get_allocation (widget, &allocation);
+
+ width = allocation.width;
+ height = allocation.height;
+
+ window = FM_PROPERTIES_WINDOW (data);
+
+ cr = gdk_cairo_create (gtk_widget_get_window (widget));
+
+ cairo_rectangle (cr,
+ 2,
+ 2,
+ width - 4,
+ height - 4);
+
+ cairo_set_source_rgb (cr, (double) window->details->used_color.red / 65535, (double) window->details->used_color.green / 65535, (double) window->details->used_color.blue / 65535);
+ cairo_fill_preserve (cr);
+
+ cairo_set_source_rgb (cr, (double) window->details->used_stroke_color.red / 65535, (double) window->details->used_stroke_color.green / 65535, (double) window->details->used_stroke_color.blue / 65535);
+ cairo_stroke (cr);
+
+ cairo_destroy (cr);
+}
+
+static void
+paint_free_legend (GtkWidget *widget, GdkEventExpose *eev, gpointer data)
+{
+ FMPropertiesWindow *window;
+ cairo_t *cr;
+ gint width, height;
+ GtkAllocation allocation;
+
+ window = FM_PROPERTIES_WINDOW (data);
+ gtk_widget_get_allocation (widget, &allocation);
+
+ width = allocation.width;
+ height = allocation.height;
+ cr = gdk_cairo_create (gtk_widget_get_window (widget));
+
+ cairo_rectangle (cr,
+ 2,
+ 2,
+ width - 4,
+ height - 4);
+
+ cairo_set_source_rgb (cr, (double) window->details->free_color.red / 65535, (double) window->details->free_color.green / 65535, (double) window->details->free_color.blue / 65535);
+ cairo_fill_preserve(cr);
+
+ cairo_set_source_rgb (cr, (double) window->details->free_stroke_color.red / 65535, (double) window->details->free_stroke_color.green / 65535, (double) window->details->free_stroke_color.blue / 65535);
+ cairo_stroke (cr);
+
+ cairo_destroy (cr);
+}
+
+static void
+paint_pie_chart (GtkWidget *widget, GdkEventExpose *eev, gpointer data)
+{
+
+ FMPropertiesWindow *window;
+ cairo_t *cr;
+ gint width, height;
+ double free, used;
+ double angle1, angle2, split, xc, yc, radius;
+ GtkAllocation allocation;
+
+ window = FM_PROPERTIES_WINDOW (data);
+ gtk_widget_get_allocation (widget, &allocation);
+
+ width = allocation.width;
+ height = allocation.height;
+
+
+ free = (double)window->details->volume_free / (double)window->details->volume_capacity;
+ used = 1.0 - free;
+
+ angle1 = free * 2 * G_PI;
+ angle2 = used * 2 * G_PI;
+ split = (2 * G_PI - angle1) * .5;
+ xc = width / 2;
+ yc = height / 2;
+
+ cr = gdk_cairo_create (gtk_widget_get_window (widget));
+
+ if (width < height) {
+ radius = width / 2 - 8;
+ } else {
+ radius = height / 2 - 8;
+ }
+
+ if (angle1 != 2 * G_PI && angle1 != 0) {
+ angle1 = angle1 + split;
+ }
+
+ if (angle2 != 2 * G_PI && angle2 != 0) {
+ angle2 = angle2 - split;
+ }
+
+ if (used > 0) {
+ if (free != 0) {
+ cairo_move_to (cr,xc,yc);
+ }
+
+ cairo_arc (cr, xc, yc, radius, angle1, angle2);
+
+ if (free != 0) {
+ cairo_line_to (cr,xc,yc);
+ }
+
+ cairo_set_source_rgb (cr, (double) window->details->used_color.red / 65535, (double) window->details->used_color.green / 65535, (double) window->details->used_color.blue / 65535);
+ cairo_fill_preserve (cr);
+
+ cairo_set_source_rgb (cr, (double) window->details->used_stroke_color.red / 65535, (double) window->details->used_stroke_color.green / 65535, (double) window->details->used_stroke_color.blue / 65535);
+ cairo_stroke (cr);
+ }
+
+ if (free > 0) {
+ if (used != 0) {
+ cairo_move_to (cr,xc,yc);
+ }
+
+ cairo_arc_negative (cr, xc, yc, radius, angle1, angle2);
+
+ if (used != 0) {
+ cairo_line_to (cr,xc,yc);
+ }
+
+ cairo_set_source_rgb (cr, (double) window->details->free_color.red / 65535, (double) window->details->free_color.green / 65535,(double) window->details->free_color.blue / 65535);
+ cairo_fill_preserve(cr);
+
+ cairo_set_source_rgb (cr, (double) window->details->free_stroke_color.red / 65535, (double) window->details->free_stroke_color.green / 65535, (double) window->details->free_stroke_color.blue / 65535);
+ cairo_stroke (cr);
+ }
+
+ cairo_destroy (cr);
+}
+
+
+/* Copied from gtk/gtkstyle.c */
+
+static void
+rgb_to_hls (gdouble *r,
+ gdouble *g,
+ gdouble *b)
+{
+ gdouble min;
+ gdouble max;
+ gdouble red;
+ gdouble green;
+ gdouble blue;
+ gdouble h, l, s;
+ gdouble delta;
+
+ red = *r;
+ green = *g;
+ blue = *b;
+
+ if (red > green)
+ {
+ if (red > blue)
+ max = red;
+ else
+ max = blue;
+
+ if (green < blue)
+ min = green;
+ else
+ min = blue;
+ }
+ else
+ {
+ if (green > blue)
+ max = green;
+ else
+ max = blue;
+
+ if (red < blue)
+ min = red;
+ else
+ min = blue;
+ }
+
+ l = (max + min) / 2;
+ s = 0;
+ h = 0;
+
+ if (max != min)
+ {
+ if (l <= 0.5)
+ s = (max - min) / (max + min);
+ else
+ s = (max - min) / (2 - max - min);
+
+ delta = max -min;
+ if (red == max)
+ h = (green - blue) / delta;
+ else if (green == max)
+ h = 2 + (blue - red) / delta;
+ else if (blue == max)
+ h = 4 + (red - green) / delta;
+
+ h *= 60;
+ if (h < 0.0)
+ h += 360;
+ }
+
+ *r = h;
+ *g = l;
+ *b = s;
+}
+
+static void
+hls_to_rgb (gdouble *h,
+ gdouble *l,
+ gdouble *s)
+{
+ gdouble hue;
+ gdouble lightness;
+ gdouble saturation;
+ gdouble m1, m2;
+ gdouble r, g, b;
+
+ lightness = *l;
+ saturation = *s;
+
+ if (lightness <= 0.5)
+ m2 = lightness * (1 + saturation);
+ else
+ m2 = lightness + saturation - lightness * saturation;
+ m1 = 2 * lightness - m2;
+
+ if (saturation == 0)
+ {
+ *h = lightness;
+ *l = lightness;
+ *s = lightness;
+ }
+ else
+ {
+ hue = *h + 120;
+ while (hue > 360)
+ hue -= 360;
+ while (hue < 0)
+ hue += 360;
+
+ if (hue < 60)
+ r = m1 + (m2 - m1) * hue / 60;
+ else if (hue < 180)
+ r = m2;
+ else if (hue < 240)
+ r = m1 + (m2 - m1) * (240 - hue) / 60;
+ else
+ r = m1;
+
+ hue = *h;
+ while (hue > 360)
+ hue -= 360;
+ while (hue < 0)
+ hue += 360;
+
+ if (hue < 60)
+ g = m1 + (m2 - m1) * hue / 60;
+ else if (hue < 180)
+ g = m2;
+ else if (hue < 240)
+ g = m1 + (m2 - m1) * (240 - hue) / 60;
+ else
+ g = m1;
+
+ hue = *h - 120;
+ while (hue > 360)
+ hue -= 360;
+ while (hue < 0)
+ hue += 360;
+
+ if (hue < 60)
+ b = m1 + (m2 - m1) * hue / 60;
+ else if (hue < 180)
+ b = m2;
+ else if (hue < 240)
+ b = m1 + (m2 - m1) * (240 - hue) / 60;
+ else
+ b = m1;
+
+ *h = r;
+ *l = g;
+ *s = b;
+ }
+}
+static void
+_pie_style_shade (GdkColor *a,
+ GdkColor *b,
+ gdouble k)
+{
+ gdouble red;
+ gdouble green;
+ gdouble blue;
+
+ red = (gdouble) a->red / 65535.0;
+ green = (gdouble) a->green / 65535.0;
+ blue = (gdouble) a->blue / 65535.0;
+
+ rgb_to_hls (&red, &green, &blue);
+
+ green *= k;
+ if (green > 1.0)
+ green = 1.0;
+ else if (green < 0.0)
+ green = 0.0;
+
+ blue *= k;
+ if (blue > 1.0)
+ blue = 1.0;
+ else if (blue < 0.0)
+ blue = 0.0;
+
+ hls_to_rgb (&red, &green, &blue);
+
+ b->red = red * 65535.0;
+ b->green = green * 65535.0;
+ b->blue = blue * 65535.0;
+}
+
+
+static GtkWidget*
+create_pie_widget (FMPropertiesWindow *window)
+{
+ CajaFile *file;
+ GtkTable *table;
+ GtkStyle *style;
+ GtkWidget *pie_canvas;
+ GtkWidget *used_canvas;
+ GtkWidget *used_label;
+ GtkWidget *free_canvas;
+ GtkWidget *free_label;
+ GtkWidget *capacity_label;
+ GtkWidget *fstype_label;
+ gchar *capacity;
+ gchar *used;
+ gchar *free;
+ const char *fs_type;
+ gchar *uri;
+ GFile *location;
+ GFileInfo *info;
+
+ #if GLIB_CHECK_VERSION(2, 30, 0)
+ capacity = g_format_size(window->details->volume_capacity);
+ free = g_format_size(window->details->volume_free);
+ used = g_format_size(window->details->volume_capacity - window->details->volume_free);
+ #else
+ capacity = g_format_size_for_display(window->details->volume_capacity);
+ free = g_format_size_for_display(window->details->volume_free);
+ used = g_format_size_for_display(window->details->volume_capacity - window->details->volume_free);
+ #endif
+
+ file = get_original_file (window);
+
+ uri = caja_file_get_activation_uri (file);
+
+ table = GTK_TABLE (gtk_table_new (4, 3, FALSE));
+
+ style = gtk_rc_get_style (GTK_WIDGET(table));
+
+ if (!gtk_style_lookup_color (style, "chart_color_1", &window->details->used_color)) {
+ window->details->used_color.red = USED_FILL_R;
+ window->details->used_color.green = USED_FILL_G;
+ window->details->used_color.blue = USED_FILL_B;
+ }
+
+ if (!gtk_style_lookup_color (style, "chart_color_2", &window->details->free_color)) {
+ window->details->free_color.red = FREE_FILL_R;
+ window->details->free_color.green = FREE_FILL_G;
+ window->details->free_color.blue = FREE_FILL_B;
+ }
+
+ _pie_style_shade (&window->details->used_color, &window->details->used_stroke_color, 0.7);
+ _pie_style_shade (&window->details->free_color, &window->details->free_stroke_color, 0.7);
+
+ pie_canvas = gtk_drawing_area_new ();
+ gtk_widget_set_size_request (pie_canvas, 200, 200);
+
+ used_canvas = gtk_drawing_area_new ();
+ gtk_widget_set_size_request (used_canvas, 20, 20);
+ /* Translators: "used" refers to the capacity of the filesystem */
+ used_label = gtk_label_new (g_strconcat (used, " ", _("used"), NULL));
+
+ free_canvas = gtk_drawing_area_new ();
+ gtk_widget_set_size_request (free_canvas,20,20);
+ /* Translators: "free" refers to the capacity of the filesystem */
+ free_label = gtk_label_new (g_strconcat (free, " ", _("free"), NULL));
+
+ capacity_label = gtk_label_new (g_strconcat (_("Total capacity:"), " ", capacity, NULL));
+ fstype_label = gtk_label_new (NULL);
+
+ location = g_file_new_for_uri (uri);
+ info = g_file_query_filesystem_info (location, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE,
+ NULL, NULL);
+ if (info) {
+ fs_type = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE);
+ if (fs_type != NULL) {
+ gtk_label_set_text (GTK_LABEL (fstype_label), g_strconcat (_("Filesystem type:"), " ", fs_type, NULL));
+ }
+
+ g_object_unref (info);
+ }
+ g_object_unref (location);
+
+ g_free (uri);
+ g_free (capacity);
+ g_free (used);
+ g_free (free);
+
+ gtk_table_attach (table, pie_canvas , 0, 1, 0, 4, GTK_FILL, GTK_SHRINK, 5, 5);
+
+ gtk_table_attach (table, used_canvas, 1, 2, 0, 1, 0, 0, 5, 5);
+ gtk_table_attach (table, used_label , 2, 3, 0, 1, GTK_FILL, 0, 5, 5);
+
+ gtk_table_attach (table, free_canvas, 1, 2, 1, 2, 0, 0, 5, 5);
+ gtk_table_attach (table, free_label , 2, 3, 1, 2, GTK_FILL, 0, 5, 5);
+
+ gtk_table_attach (table, capacity_label , 1, 3, 2, 3, GTK_FILL, 0, 5, 5);
+ gtk_table_attach (table, fstype_label , 1, 3, 3, 4, GTK_FILL, 0, 5, 5);
+
+ g_signal_connect (G_OBJECT (pie_canvas), "expose-event", G_CALLBACK (paint_pie_chart), window);
+ g_signal_connect (G_OBJECT (used_canvas), "expose-event", G_CALLBACK (paint_used_legend), window);
+ g_signal_connect (G_OBJECT (free_canvas), "expose-event", G_CALLBACK (paint_free_legend), window);
+
+ return GTK_WIDGET (table);
+}
+
+static GtkWidget*
+create_volume_usage_widget (FMPropertiesWindow *window)
+{
+ GtkWidget *piewidget;
+ gchar *uri;
+ CajaFile *file;
+ GFile *location;
+ GFileInfo *info;
+
+ file = get_original_file (window);
+
+ uri = caja_file_get_activation_uri (file);
+
+ location = g_file_new_for_uri (uri);
+ info = g_file_query_filesystem_info (location, "filesystem::*", NULL, NULL);
+
+ if (info) {
+ window->details->volume_capacity = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE);
+ window->details->volume_free = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
+
+ g_object_unref (info);
+ } else {
+ window->details->volume_capacity = 0;
+ window->details->volume_free = 0;
+ }
+
+ g_object_unref (location);
+
+ piewidget = create_pie_widget (window);
+
+ gtk_widget_show_all (piewidget);
+
+ return piewidget;
+}
+
+static void
+create_basic_page (FMPropertiesWindow *window)
+{
+ GtkTable *table;
+ GtkWidget *icon_aligner;
+ GtkWidget *icon_pixmap_widget;
+ GtkWidget *volume_usage;
+ GtkWidget *hbox, *vbox;
+
+ guint last_row, row;
+
+ hbox = create_page_with_hbox (window->details->notebook, _("Basic"));
+
+ /* Icon pixmap */
+
+ icon_pixmap_widget = create_image_widget (
+ window, should_show_custom_icon_buttons (window));
+ gtk_widget_show (icon_pixmap_widget);
+
+ icon_aligner = gtk_alignment_new (1, 0, 0, 0);
+ gtk_widget_show (icon_aligner);
+
+ gtk_container_add (GTK_CONTAINER (icon_aligner), icon_pixmap_widget);
+ gtk_box_pack_start (GTK_BOX (hbox), icon_aligner, FALSE, FALSE, 0);
+
+ window->details->icon_chooser = NULL;
+
+ /* Table */
+
+ vbox = gtk_vbox_new (FALSE, 0);
+ gtk_widget_show (vbox);
+ gtk_container_add (GTK_CONTAINER (hbox), vbox);
+
+ table = GTK_TABLE (create_attribute_value_table (GTK_VBOX (vbox), 0));
+ window->details->basic_table = table;
+
+ /* Name label. The text will be determined in update_name_field */
+ row = append_title_field (table, NULL, &window->details->name_label);
+ window->details->name_row = row;
+
+ /* Name field */
+ window->details->name_field = NULL;
+ update_name_field (window);
+
+ /* Start with name field selected, if it's an entry. */
+ if (CAJA_IS_ENTRY (window->details->name_field)) {
+ caja_entry_select_all (CAJA_ENTRY (window->details->name_field));
+ gtk_widget_grab_focus (GTK_WIDGET (window->details->name_field));
+ }
+
+ if (fm_ditem_page_should_show (window->details->target_files)) {
+ GtkSizeGroup *label_size_group;
+ GtkWidget *box;
+
+ row = append_row (table);
+
+ label_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+ gtk_size_group_add_widget (label_size_group,
+ GTK_WIDGET (window->details->name_label));
+ box = fm_ditem_page_make_box (label_size_group,
+ window->details->target_files);
+
+ gtk_table_attach (window->details->basic_table, box,
+ TITLE_COLUMN, VALUE_COLUMN + 1,
+ row, row + 1,
+ GTK_FILL, 0,
+ 0, 0);
+ }
+
+ if (should_show_file_type (window)) {
+ append_title_value_pair (window,
+ table, _("Type:"),
+ "type",
+ INCONSISTENT_STATE_STRING,
+ FALSE);
+ }
+
+ if (should_show_link_target (window)) {
+ append_title_and_ellipsizing_value (window, table,
+ _("Link target:"),
+ "link_target",
+ INCONSISTENT_STATE_STRING,
+ FALSE);
+ }
+
+ if (is_multi_file_window (window) ||
+ caja_file_is_directory (get_target_file (window))) {
+ append_directory_contents_fields (window, table);
+ } else {
+ append_title_value_pair (window, table, _("Size:"),
+ "size_detail",
+ INCONSISTENT_STATE_STRING,
+ FALSE);
+ }
+
+ append_blank_row (table);
+
+ if (should_show_location_info (window)) {
+ append_title_and_ellipsizing_value (window, table, _("Location:"),
+ "where",
+ INCONSISTENT_STATE_STRING,
+ TRUE);
+
+ append_title_and_ellipsizing_value (window, table,
+ _("Volume:"),
+ "volume",
+ INCONSISTENT_STATE_STRING,
+ FALSE);
+ }
+
+ if (should_show_accessed_date (window)) {
+ append_blank_row (table);
+
+ append_title_value_pair (window, table, _("Accessed:"),
+ "date_accessed",
+ INCONSISTENT_STATE_STRING,
+ FALSE);
+ append_title_value_pair (window, table, _("Modified:"),
+ "date_modified",
+ INCONSISTENT_STATE_STRING,
+ FALSE);
+ }
+
+ if (should_show_free_space (window)) {
+ append_blank_row (table);
+
+ append_title_value_pair (window, table, _("Free space:"),
+ "free_space",
+ INCONSISTENT_STATE_STRING,
+ FALSE);
+ }
+
+ if (should_show_volume_usage (window)) {
+ last_row = append_row (table);
+ volume_usage = create_volume_usage_widget (window);
+ gtk_table_attach_defaults (GTK_TABLE(table), volume_usage, 0, 2, last_row, last_row+1);
+ }
+}
+
+static GHashTable *
+get_initial_emblems (GList *files)
+{
+ GHashTable *ret;
+ GList *l;
+
+ ret = g_hash_table_new_full (g_direct_hash,
+ g_direct_equal,
+ NULL,
+ (GDestroyNotify)eel_g_list_free_deep);
+
+ for (l = files; l != NULL; l = l->next) {
+ CajaFile *file;
+ GList *keywords;
+
+ file = CAJA_FILE (l->data);
+
+ keywords = caja_file_get_keywords (file);
+ g_hash_table_insert (ret, file, keywords);
+ }
+
+ return ret;
+}
+
+static gboolean
+files_has_directory (FMPropertiesWindow *window)
+{
+ GList *l;
+
+ for (l = window->details->target_files; l != NULL; l = l->next) {
+ CajaFile *file;
+ file = CAJA_FILE (l->data);
+ if (caja_file_is_directory (file)) {
+ return TRUE;
+ }
+
+ }
+
+ return FALSE;
+}
+
+static gboolean
+files_has_changable_permissions_directory (FMPropertiesWindow *window)
+{
+ GList *l;
+
+ for (l = window->details->target_files; l != NULL; l = l->next) {
+ CajaFile *file;
+ file = CAJA_FILE (l->data);
+ if (caja_file_is_directory (file) &&
+ caja_file_can_get_permissions (file) &&
+ caja_file_can_set_permissions (file)) {
+ return TRUE;
+ }
+
+ }
+
+ return FALSE;
+}
+
+
+static gboolean
+files_has_file (FMPropertiesWindow *window)
+{
+ GList *l;
+
+ for (l = window->details->target_files; l != NULL; l = l->next) {
+ CajaFile *file;
+ file = CAJA_FILE (l->data);
+ if (!caja_file_is_directory (file)) {
+ return TRUE;
+ }
+
+ }
+
+ return FALSE;
+}
+
+
+static void
+create_emblems_page (FMPropertiesWindow *window)
+{
+ GtkWidget *emblems_table, *button, *scroller;
+ char *emblem_name;
+ GdkPixbuf *pixbuf;
+ char *label;
+ GList *icons, *l;
+ CajaIconInfo *info;
+
+ /* The emblems wrapped table */
+ scroller = eel_scrolled_wrap_table_new (TRUE, GTK_SHADOW_NONE, &emblems_table);
+
+ gtk_container_set_border_width (GTK_CONTAINER (emblems_table), 12);
+
+ gtk_widget_show (scroller);
+
+ gtk_notebook_append_page (window->details->notebook,
+ scroller, gtk_label_new (_("Emblems")));
+
+ icons = caja_emblem_list_available ();
+
+ window->details->initial_emblems = get_initial_emblems (window->details->original_files);
+
+ l = icons;
+ while (l != NULL) {
+ emblem_name = l->data;
+ l = l->next;
+
+ if (!caja_emblem_should_show_in_list (emblem_name)) {
+ continue;
+ }
+
+ info = caja_icon_info_lookup_from_name (emblem_name, CAJA_ICON_SIZE_SMALL);
+ pixbuf = caja_icon_info_get_pixbuf_nodefault_at_size (info, CAJA_ICON_SIZE_SMALL);
+
+ if (pixbuf == NULL) {
+ continue;
+ }
+
+ label = g_strdup (caja_icon_info_get_display_name (info));
+ g_object_unref (info);
+
+ if (label == NULL) {
+ label = caja_emblem_get_keyword_from_icon_name (emblem_name);
+ }
+
+ button = eel_labeled_image_check_button_new (label, pixbuf);
+ eel_labeled_image_set_fixed_image_height (EEL_LABELED_IMAGE (gtk_bin_get_child (GTK_BIN (button))), STANDARD_EMBLEM_HEIGHT);
+ eel_labeled_image_set_spacing (EEL_LABELED_IMAGE (gtk_bin_get_child (GTK_BIN (button))), EMBLEM_LABEL_SPACING);
+
+ g_free (label);
+ g_object_unref (pixbuf);
+
+ /* Attach parameters and signal handler. */
+ g_object_set_data_full (G_OBJECT (button), "caja_emblem_name",
+ caja_emblem_get_keyword_from_icon_name (emblem_name), g_free);
+
+ window->details->emblem_buttons =
+ g_list_append (window->details->emblem_buttons,
+ button);
+
+ g_signal_connect_object (button, "toggled",
+ G_CALLBACK (emblem_button_toggled),
+ G_OBJECT (window),
+ 0);
+
+ gtk_container_add (GTK_CONTAINER (emblems_table), button);
+ }
+ eel_g_list_free_deep (icons);
+ gtk_widget_show_all (emblems_table);
+}
+
+static void
+start_long_operation (FMPropertiesWindow *window)
+{
+ if (window->details->long_operation_underway == 0) {
+ /* start long operation */
+ GdkCursor * cursor;
+
+ cursor = gdk_cursor_new (GDK_WATCH);
+ gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (window)), cursor);
+ gdk_cursor_unref (cursor);
+ }
+ window->details->long_operation_underway ++;
+}
+
+static void
+end_long_operation (FMPropertiesWindow *window)
+{
+ if (gtk_widget_get_window (GTK_WIDGET (window)) != NULL &&
+ window->details->long_operation_underway == 1) {
+ /* finished !! */
+ gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (window)), NULL);
+ }
+ window->details->long_operation_underway--;
+}
+
+static void
+permission_change_callback (CajaFile *file,
+ GFile *res_loc,
+ GError *error,
+ gpointer callback_data)
+{
+ FMPropertiesWindow *window;
+ g_assert (callback_data != NULL);
+
+ window = FM_PROPERTIES_WINDOW (callback_data);
+ end_long_operation (window);
+
+ /* Report the error if it's an error. */
+ fm_report_error_setting_permissions (file, error, NULL);
+
+ g_object_unref (window);
+}
+
+static void
+update_permissions (FMPropertiesWindow *window,
+ guint32 vfs_new_perm,
+ guint32 vfs_mask,
+ gboolean is_folder,
+ gboolean apply_to_both_folder_and_dir,
+ gboolean use_original)
+{
+ GList *l;
+
+ for (l = window->details->target_files; l != NULL; l = l->next) {
+ CajaFile *file;
+ guint32 permissions;
+
+ file = CAJA_FILE (l->data);
+
+ if (!caja_file_can_get_permissions (file)) {
+ continue;
+ }
+
+ if (!apply_to_both_folder_and_dir &&
+ ((caja_file_is_directory (file) && !is_folder) ||
+ (!caja_file_is_directory (file) && is_folder))) {
+ continue;
+ }
+
+ permissions = caja_file_get_permissions (file);
+ if (use_original) {
+ gpointer ptr;
+ if (g_hash_table_lookup_extended (window->details->initial_permissions,
+ file, NULL, &ptr)) {
+ permissions = (permissions & ~vfs_mask) | (GPOINTER_TO_INT (ptr) & vfs_mask);
+ }
+ } else {
+ permissions = (permissions & ~vfs_mask) | vfs_new_perm;
+ }
+
+ start_long_operation (window);
+ g_object_ref (window);
+ caja_file_set_permissions
+ (file, permissions,
+ permission_change_callback,
+ window);
+ }
+}
+
+static gboolean
+initial_permission_state_consistent (FMPropertiesWindow *window,
+ guint32 mask,
+ gboolean is_folder,
+ gboolean both_folder_and_dir)
+{
+ GList *l;
+ gboolean first;
+ guint32 first_permissions;
+
+ first = TRUE;
+ first_permissions = 0;
+ for (l = window->details->target_files; l != NULL; l = l->next) {
+ CajaFile *file;
+ guint32 permissions;
+
+ file = l->data;
+
+ if (!both_folder_and_dir &&
+ ((caja_file_is_directory (file) && !is_folder) ||
+ (!caja_file_is_directory (file) && is_folder))) {
+ continue;
+ }
+
+ permissions = GPOINTER_TO_INT (g_hash_table_lookup (window->details->initial_permissions,
+ file));
+
+ if (first) {
+ if ((permissions & mask) != mask &&
+ (permissions & mask) != 0) {
+ /* Not fully on or off -> inconsistent */
+ return FALSE;
+ }
+
+ first_permissions = permissions;
+ first = FALSE;
+
+ } else if ((permissions & mask) != first_permissions) {
+ /* Not same permissions as first -> inconsistent */
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static void
+permission_button_toggled (GtkToggleButton *button,
+ FMPropertiesWindow *window)
+{
+ gboolean is_folder, is_special;
+ guint32 permission_mask;
+ gboolean inconsistent;
+ gboolean on;
+
+ permission_mask = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
+ "permission"));
+ is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
+ "is-folder"));
+ is_special = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
+ "is-special"));
+
+ if (gtk_toggle_button_get_active (button)
+ && !gtk_toggle_button_get_inconsistent (button)) {
+ /* Go to the initial state unless the initial state was
+ consistent, or we support recursive apply */
+ inconsistent = TRUE;
+ on = TRUE;
+
+ if (!window->details->has_recursive_apply &&
+ initial_permission_state_consistent (window, permission_mask, is_folder, is_special)) {
+ inconsistent = FALSE;
+ on = TRUE;
+ }
+ } else if (gtk_toggle_button_get_inconsistent (button)
+ && !gtk_toggle_button_get_active (button)) {
+ inconsistent = FALSE;
+ on = TRUE;
+ } else {
+ inconsistent = FALSE;
+ on = FALSE;
+ }
+
+ g_signal_handlers_block_by_func (G_OBJECT (button),
+ G_CALLBACK (permission_button_toggled),
+ window);
+
+ gtk_toggle_button_set_active (button, on);
+ gtk_toggle_button_set_inconsistent (button, inconsistent);
+
+ g_signal_handlers_unblock_by_func (G_OBJECT (button),
+ G_CALLBACK (permission_button_toggled),
+ window);
+
+ update_permissions (window,
+ on?permission_mask:0,
+ permission_mask,
+ is_folder,
+ is_special,
+ inconsistent);
+}
+
+static void
+permission_button_update (FMPropertiesWindow *window,
+ GtkToggleButton *button)
+{
+ GList *l;
+ gboolean all_set;
+ gboolean all_unset;
+ gboolean all_cannot_set;
+ gboolean is_folder, is_special;
+ gboolean no_match;
+ gboolean sensitive;
+ guint32 button_permission;
+
+ if (gtk_toggle_button_get_inconsistent (button) &&
+ window->details->has_recursive_apply) {
+ /* Never change from an inconsistent state if we have dirs, even
+ * if the current state is now consistent, because its a useful
+ * state for recursive apply.
+ */
+ return;
+ }
+
+ button_permission = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
+ "permission"));
+ is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
+ "is-folder"));
+ is_special = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
+ "is-special"));
+
+ all_set = TRUE;
+ all_unset = TRUE;
+ all_cannot_set = TRUE;
+ no_match = TRUE;
+ for (l = window->details->target_files; l != NULL; l = l->next) {
+ CajaFile *file;
+ guint32 file_permissions;
+
+ file = CAJA_FILE (l->data);
+
+ if (!caja_file_can_get_permissions (file)) {
+ continue;
+ }
+
+ if (!is_special &&
+ ((caja_file_is_directory (file) && !is_folder) ||
+ (!caja_file_is_directory (file) && is_folder))) {
+ continue;
+ }
+
+ no_match = FALSE;
+
+ file_permissions = caja_file_get_permissions (file);
+
+ if ((file_permissions & button_permission) == button_permission) {
+ all_unset = FALSE;
+ } else if ((file_permissions & button_permission) == 0) {
+ all_set = FALSE;
+ } else {
+ all_unset = FALSE;
+ all_set = FALSE;
+ }
+
+ if (caja_file_can_set_permissions (file)) {
+ all_cannot_set = FALSE;
+ }
+ }
+
+ sensitive = !all_cannot_set;
+ if (!is_folder) {
+ /* Don't insitive files when we have recursive apply */
+ sensitive |= window->details->has_recursive_apply;
+ }
+
+
+ g_signal_handlers_block_by_func (G_OBJECT (button),
+ G_CALLBACK (permission_button_toggled),
+ window);
+
+ gtk_toggle_button_set_active (button, !all_unset);
+ /* if actually inconsistent, or default value for file buttons
+ if no files are selected. (useful for recursive apply) */
+ gtk_toggle_button_set_inconsistent (button,
+ (!all_unset && !all_set) ||
+ (!is_folder && no_match));
+ gtk_widget_set_sensitive (GTK_WIDGET (button), sensitive);
+
+ g_signal_handlers_unblock_by_func (G_OBJECT (button),
+ G_CALLBACK (permission_button_toggled),
+ window);
+}
+
+static void
+set_up_permissions_checkbox (FMPropertiesWindow *window,
+ GtkWidget *check_button,
+ guint32 permission,
+ gboolean is_folder)
+{
+ /* Load up the check_button with data we'll need when updating its state. */
+ g_object_set_data (G_OBJECT (check_button), "permission",
+ GINT_TO_POINTER (permission));
+ g_object_set_data (G_OBJECT (check_button), "properties_window",
+ window);
+ g_object_set_data (G_OBJECT (check_button), "is-folder",
+ GINT_TO_POINTER (is_folder));
+
+ window->details->permission_buttons =
+ g_list_prepend (window->details->permission_buttons,
+ check_button);
+
+ g_signal_connect_object (check_button, "toggled",
+ G_CALLBACK (permission_button_toggled),
+ window,
+ 0);
+}
+
+static void
+add_permissions_checkbox_with_label (FMPropertiesWindow *window,
+ GtkTable *table,
+ int row, int column,
+ const char *label,
+ guint32 permission_to_check,
+ GtkLabel *label_for,
+ gboolean is_folder)
+{
+ GtkWidget *check_button;
+ gboolean a11y_enabled;
+
+ check_button = gtk_check_button_new_with_mnemonic (label);
+ gtk_widget_show (check_button);
+ gtk_table_attach (table, check_button,
+ column, column + 1,
+ row, row + 1,
+ GTK_FILL, 0,
+ 0, 0);
+
+ set_up_permissions_checkbox (window,
+ check_button,
+ permission_to_check,
+ is_folder);
+
+ a11y_enabled = GTK_IS_ACCESSIBLE (gtk_widget_get_accessible (check_button));
+ if (a11y_enabled && label_for != NULL) {
+ eel_accessibility_set_up_label_widget_relation (GTK_WIDGET (label_for),
+ check_button);
+ }
+}
+
+static void
+add_permissions_checkbox (FMPropertiesWindow *window,
+ GtkTable *table,
+ int row, int column,
+ guint32 permission_to_check,
+ GtkLabel *label_for,
+ gboolean is_folder)
+{
+ gchar *label;
+
+ if (column == PERMISSIONS_CHECKBOXES_READ_COLUMN) {
+ label = _("_Read");
+ } else if (column == PERMISSIONS_CHECKBOXES_WRITE_COLUMN) {
+ label = _("_Write");
+ } else {
+ label = _("E_xecute");
+ }
+
+ add_permissions_checkbox_with_label (window, table,
+ row, column,
+ label,
+ permission_to_check,
+ label_for,
+ is_folder);
+}
+
+enum {
+ UNIX_PERM_SUID = S_ISUID,
+ UNIX_PERM_SGID = S_ISGID,
+ UNIX_PERM_STICKY = 01000, /* S_ISVTX not defined on all systems */
+ UNIX_PERM_USER_READ = S_IRUSR,
+ UNIX_PERM_USER_WRITE = S_IWUSR,
+ UNIX_PERM_USER_EXEC = S_IXUSR,
+ UNIX_PERM_USER_ALL = S_IRUSR | S_IWUSR | S_IXUSR,
+ UNIX_PERM_GROUP_READ = S_IRGRP,
+ UNIX_PERM_GROUP_WRITE = S_IWGRP,
+ UNIX_PERM_GROUP_EXEC = S_IXGRP,
+ UNIX_PERM_GROUP_ALL = S_IRGRP | S_IWGRP | S_IXGRP,
+ UNIX_PERM_OTHER_READ = S_IROTH,
+ UNIX_PERM_OTHER_WRITE = S_IWOTH,
+ UNIX_PERM_OTHER_EXEC = S_IXOTH,
+ UNIX_PERM_OTHER_ALL = S_IROTH | S_IWOTH | S_IXOTH
+};
+
+typedef enum {
+ PERMISSION_READ = (1<<0),
+ PERMISSION_WRITE = (1<<1),
+ PERMISSION_EXEC = (1<<2)
+} PermissionValue;
+
+typedef enum {
+ PERMISSION_USER,
+ PERMISSION_GROUP,
+ PERMISSION_OTHER
+} PermissionType;
+
+static guint32 vfs_perms[3][3] = {
+ {UNIX_PERM_USER_READ, UNIX_PERM_USER_WRITE, UNIX_PERM_USER_EXEC},
+ {UNIX_PERM_GROUP_READ, UNIX_PERM_GROUP_WRITE, UNIX_PERM_GROUP_EXEC},
+ {UNIX_PERM_OTHER_READ, UNIX_PERM_OTHER_WRITE, UNIX_PERM_OTHER_EXEC},
+};
+
+static guint32
+permission_to_vfs (PermissionType type, PermissionValue perm)
+{
+ guint32 vfs_perm;
+ g_assert (type >= 0 && type < 3);
+
+ vfs_perm = 0;
+ if (perm & PERMISSION_READ) {
+ vfs_perm |= vfs_perms[type][0];
+ }
+ if (perm & PERMISSION_WRITE) {
+ vfs_perm |= vfs_perms[type][1];
+ }
+ if (perm & PERMISSION_EXEC) {
+ vfs_perm |= vfs_perms[type][2];
+ }
+
+ return vfs_perm;
+}
+
+
+static PermissionValue
+permission_from_vfs (PermissionType type, guint32 vfs_perm)
+{
+ PermissionValue perm;
+ g_assert (type >= 0 && type < 3);
+
+ perm = 0;
+ if (vfs_perm & vfs_perms[type][0]) {
+ perm |= PERMISSION_READ;
+ }
+ if (vfs_perm & vfs_perms[type][1]) {
+ perm |= PERMISSION_WRITE;
+ }
+ if (vfs_perm & vfs_perms[type][2]) {
+ perm |= PERMISSION_EXEC;
+ }
+
+ return perm;
+}
+
+static void
+permission_combo_changed (GtkWidget *combo, FMPropertiesWindow *window)
+{
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ gboolean is_folder, use_original;
+ PermissionType type;
+ int new_perm, mask;
+ guint32 vfs_new_perm, vfs_mask;
+
+ is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "is-folder"));
+ type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "permission-type"));
+
+ if (is_folder) {
+ mask = PERMISSION_READ|PERMISSION_WRITE|PERMISSION_EXEC;
+ } else {
+ mask = PERMISSION_READ|PERMISSION_WRITE;
+ }
+
+ vfs_mask = permission_to_vfs (type, mask);
+
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));
+
+ if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter)) {
+ return;
+ }
+ gtk_tree_model_get (model, &iter, 1, &new_perm, 2, &use_original, -1);
+ vfs_new_perm = permission_to_vfs (type, new_perm);
+
+ update_permissions (window, vfs_new_perm, vfs_mask,
+ is_folder, FALSE, use_original);
+}
+
+static void
+permission_combo_add_multiple_choice (GtkComboBox *combo, GtkTreeIter *iter)
+{
+ GtkTreeModel *model;
+ GtkListStore *store;
+ gboolean found;
+
+ model = gtk_combo_box_get_model (combo);
+ store = GTK_LIST_STORE (model);
+
+ found = FALSE;
+ gtk_tree_model_get_iter_first (model, iter);
+ do {
+ gboolean multi;
+ gtk_tree_model_get (model, iter, 2, &multi, -1);
+
+ if (multi) {
+ found = TRUE;
+ break;
+ }
+ } while (gtk_tree_model_iter_next (model, iter));
+
+ if (!found) {
+ gtk_list_store_append (store, iter);
+ gtk_list_store_set (store, iter, 0, "---", 1, 0, 2, TRUE, -1);
+ }
+}
+
+static void
+permission_combo_update (FMPropertiesWindow *window,
+ GtkComboBox *combo)
+{
+ PermissionType type;
+ PermissionValue perm, all_dir_perm, all_file_perm, all_perm;
+ gboolean is_folder, no_files, no_dirs, all_file_same, all_dir_same, all_same;
+ gboolean all_dir_cannot_set, all_file_cannot_set, sensitive;
+ GtkTreeIter iter;
+ int mask;
+ GtkTreeModel *model;
+ GtkListStore *store;
+ GList *l;
+ gboolean is_multi;
+
+ model = gtk_combo_box_get_model (combo);
+
+ is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "is-folder"));
+ type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "permission-type"));
+
+ is_multi = FALSE;
+ if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter)) {
+ gtk_tree_model_get (model, &iter, 2, &is_multi, -1);
+ }
+
+ if (is_multi && window->details->has_recursive_apply) {
+ /* Never change from an inconsistent state if we have dirs, even
+ * if the current state is now consistent, because its a useful
+ * state for recursive apply.
+ */
+ return;
+ }
+
+ no_files = TRUE;
+ no_dirs = TRUE;
+ all_dir_same = TRUE;
+ all_file_same = TRUE;
+ all_dir_perm = 0;
+ all_file_perm = 0;
+ all_dir_cannot_set = TRUE;
+ all_file_cannot_set = TRUE;
+
+ for (l = window->details->target_files; l != NULL; l = l->next) {
+ CajaFile *file;
+ guint32 file_permissions;
+
+ file = CAJA_FILE (l->data);
+
+ if (!caja_file_can_get_permissions (file)) {
+ continue;
+ }
+
+ if (caja_file_is_directory (file)) {
+ mask = PERMISSION_READ|PERMISSION_WRITE|PERMISSION_EXEC;
+ } else {
+ mask = PERMISSION_READ|PERMISSION_WRITE;
+ }
+
+ file_permissions = caja_file_get_permissions (file);
+
+ perm = permission_from_vfs (type, file_permissions) & mask;
+
+ if (caja_file_is_directory (file)) {
+ if (no_dirs) {
+ all_dir_perm = perm;
+ no_dirs = FALSE;
+ } else if (perm != all_dir_perm) {
+ all_dir_same = FALSE;
+ }
+
+ if (caja_file_can_set_permissions (file)) {
+ all_dir_cannot_set = FALSE;
+ }
+ } else {
+ if (no_files) {
+ all_file_perm = perm;
+ no_files = FALSE;
+ } else if (perm != all_file_perm) {
+ all_file_same = FALSE;
+ }
+
+ if (caja_file_can_set_permissions (file)) {
+ all_file_cannot_set = FALSE;
+ }
+ }
+ }
+
+ if (is_folder) {
+ all_same = all_dir_same;
+ all_perm = all_dir_perm;
+ } else {
+ all_same = all_file_same && !no_files;
+ all_perm = all_file_perm;
+ }
+
+ store = GTK_LIST_STORE (model);
+ if (all_same) {
+ gboolean found;
+
+ found = FALSE;
+ gtk_tree_model_get_iter_first (model, &iter);
+ do {
+ int current_perm;
+ gtk_tree_model_get (model, &iter, 1, &current_perm, -1);
+
+ if (current_perm == all_perm) {
+ found = TRUE;
+ break;
+ }
+ } while (gtk_tree_model_iter_next (model, &iter));
+
+ if (!found) {
+ GString *str;
+ str = g_string_new ("");
+
+ if (!(all_perm & PERMISSION_READ)) {
+ /* translators: this gets concatenated to "no read",
+ * "no access", etc. (see following strings)
+ */
+ g_string_append (str, _("no "));
+ }
+ if (is_folder) {
+ g_string_append (str, _("list"));
+ } else {
+ g_string_append (str, _("read"));
+ }
+
+ g_string_append (str, ", ");
+
+ if (!(all_perm & PERMISSION_WRITE)) {
+ g_string_append (str, _("no "));
+ }
+ if (is_folder) {
+ g_string_append (str, _("create/delete"));
+ } else {
+ g_string_append (str, _("write"));
+ }
+
+ if (is_folder) {
+ g_string_append (str, ", ");
+
+ if (!(all_perm & PERMISSION_EXEC)) {
+ g_string_append (str, _("no "));
+ }
+ g_string_append (str, _("access"));
+ }
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ 0, str->str,
+ 1, all_perm, -1);
+
+ g_string_free (str, TRUE);
+ }
+ } else {
+ permission_combo_add_multiple_choice (combo, &iter);
+ }
+
+ g_signal_handlers_block_by_func (G_OBJECT (combo),
+ G_CALLBACK (permission_combo_changed),
+ window);
+
+ gtk_combo_box_set_active_iter (combo, &iter);
+
+ /* Also enable if no files found (for recursive
+ file changes when only selecting folders) */
+ if (is_folder) {
+ sensitive = !all_dir_cannot_set;
+ } else {
+ sensitive = !all_file_cannot_set ||
+ window->details->has_recursive_apply;
+ }
+ gtk_widget_set_sensitive (GTK_WIDGET (combo), sensitive);
+
+ g_signal_handlers_unblock_by_func (G_OBJECT (combo),
+ G_CALLBACK (permission_combo_changed),
+ window);
+
+}
+
+static void
+add_permissions_combo_box (FMPropertiesWindow *window, GtkTable *table,
+ PermissionType type, gboolean is_folder,
+ gboolean short_label)
+{
+ GtkWidget *combo;
+ GtkLabel *label;
+ GtkListStore *store;
+ GtkCellRenderer *cell;
+ GtkTreeIter iter;
+ int row;
+
+ if (short_label) {
+ row = append_title_field (table, _("Access:"), &label);
+ } else if (is_folder) {
+ row = append_title_field (table, _("Folder access:"), &label);
+ } else {
+ row = append_title_field (table, _("File access:"), &label);
+ }
+
+ store = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
+ combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL (store));
+
+ g_object_set_data (G_OBJECT (combo), "is-folder", GINT_TO_POINTER (is_folder));
+ g_object_set_data (G_OBJECT (combo), "permission-type", GINT_TO_POINTER (type));
+
+ if (is_folder) {
+ if (type != PERMISSION_USER) {
+ gtk_list_store_append (store, &iter);
+ /* Translators: this is referred to the permissions
+ * the user has in a directory.
+ */
+ gtk_list_store_set (store, &iter, 0, _("None"), 1, 0, -1);
+ }
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter, 0, _("List files only"), 1, PERMISSION_READ, -1);
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter, 0, _("Access files"), 1, PERMISSION_READ|PERMISSION_EXEC, -1);
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter, 0, _("Create and delete files"), 1, PERMISSION_READ|PERMISSION_EXEC|PERMISSION_WRITE, -1);
+ } else {
+ if (type != PERMISSION_USER) {
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter, 0, _("None"), 1, 0, -1);
+ }
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter, 0, _("Read-only"), 1, PERMISSION_READ, -1);
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter, 0, _("Read and write"), 1, PERMISSION_READ|PERMISSION_WRITE, -1);
+ }
+ if (window->details->has_recursive_apply) {
+ permission_combo_add_multiple_choice (GTK_COMBO_BOX (combo), &iter);
+ }
+
+ g_object_unref (store);
+
+ window->details->permission_combos =
+ g_list_prepend (window->details->permission_combos,
+ combo);
+
+ g_signal_connect (combo, "changed", G_CALLBACK (permission_combo_changed), window);
+
+ cell = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), cell, TRUE);
+ gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), cell,
+ "text", 0,
+ NULL);
+
+ gtk_label_set_mnemonic_widget (label, combo);
+ gtk_widget_show (combo);
+
+ gtk_table_attach (table, combo,
+ VALUE_COLUMN, VALUE_COLUMN + 1,
+ row, row + 1,
+ GTK_FILL, 0,
+ 0, 0);
+}
+
+
+static GtkWidget *
+append_special_execution_checkbox (FMPropertiesWindow *window,
+ GtkTable *table,
+ const char *label_text,
+ guint32 permission_to_check)
+{
+ GtkWidget *check_button;
+ guint last_row;
+
+ last_row = append_row (table);
+
+ check_button = gtk_check_button_new_with_mnemonic (label_text);
+ gtk_widget_show (check_button);
+
+ gtk_table_attach (table, check_button,
+ VALUE_COLUMN, VALUE_COLUMN + 1,
+ last_row, last_row + 1,
+ GTK_FILL, 0,
+ 0, 0);
+
+ set_up_permissions_checkbox (window,
+ check_button,
+ permission_to_check,
+ FALSE);
+ g_object_set_data (G_OBJECT (check_button), "is-special",
+ GINT_TO_POINTER (TRUE));
+
+ return check_button;
+}
+
+static void
+append_special_execution_flags (FMPropertiesWindow *window, GtkTable *table)
+{
+ gint nrows;
+
+ append_special_execution_checkbox
+ (window, table, _("Set _user ID"), UNIX_PERM_SUID);
+
+ g_object_get (table, "n-rows", &nrows, NULL);
+ attach_title_field (table, nrows - 1, _("Special flags:"));
+
+ append_special_execution_checkbox (window, table, _("Set gro_up ID"), UNIX_PERM_SGID);
+ append_special_execution_checkbox (window, table, _("_Sticky"), UNIX_PERM_STICKY);
+
+ g_object_get (table, "n-rows", &nrows, NULL);
+ gtk_table_set_row_spacing (table, nrows - 1, 18);
+}
+
+static gboolean
+all_can_get_permissions (GList *file_list)
+{
+ GList *l;
+ for (l = file_list; l != NULL; l = l->next) {
+ CajaFile *file;
+
+ file = CAJA_FILE (l->data);
+
+ if (!caja_file_can_get_permissions (file)) {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+all_can_set_permissions (GList *file_list)
+{
+ GList *l;
+ for (l = file_list; l != NULL; l = l->next) {
+ CajaFile *file;
+
+ file = CAJA_FILE (l->data);
+
+ if (!caja_file_can_set_permissions (file)) {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static GHashTable *
+get_initial_permissions (GList *file_list)
+{
+ GHashTable *ret;
+ GList *l;
+
+ ret = g_hash_table_new (g_direct_hash,
+ g_direct_equal);
+
+ for (l = file_list; l != NULL; l = l->next) {
+ guint32 permissions;
+ CajaFile *file;
+
+ file = CAJA_FILE (l->data);
+
+ permissions = caja_file_get_permissions (file);
+ g_hash_table_insert (ret, file,
+ GINT_TO_POINTER (permissions));
+ }
+
+ return ret;
+}
+
+static void
+create_simple_permissions (FMPropertiesWindow *window, GtkTable *page_table)
+{
+ gboolean has_file, has_directory;
+ GtkLabel *group_label;
+ GtkLabel *owner_label;
+ GtkLabel *execute_label;
+ GtkWidget *value;
+ GtkComboBox *group_combo_box;
+ GtkComboBox *owner_combo_box;
+ guint last_row;
+ gint nrows;
+
+ last_row = 0;
+
+ has_file = files_has_file (window);
+ has_directory = files_has_directory (window);
+
+ if (!is_multi_file_window (window) && caja_file_can_set_owner (get_target_file (window))) {
+ owner_label = attach_title_field (page_table, last_row, _("_Owner:"));
+ /* Combo box in this case. */
+ owner_combo_box = attach_owner_combo_box (page_table, last_row, get_target_file (window));
+ gtk_label_set_mnemonic_widget (owner_label,
+ GTK_WIDGET (owner_combo_box));
+ } else {
+ owner_label = attach_title_field (page_table, last_row, _("Owner:"));
+ /* Static text in this case. */
+ value = attach_value_field (window,
+ page_table, last_row, VALUE_COLUMN,
+ "owner",
+ INCONSISTENT_STATE_STRING,
+ FALSE);
+ gtk_label_set_mnemonic_widget (owner_label, value);
+ }
+
+ if (has_directory) {
+ add_permissions_combo_box (window, page_table,
+ PERMISSION_USER, TRUE, FALSE);
+ }
+ if (has_file || window->details->has_recursive_apply) {
+ add_permissions_combo_box (window, page_table,
+ PERMISSION_USER, FALSE, !has_directory);
+ }
+
+ g_object_get (page_table, "n-rows", &nrows, NULL);
+ gtk_table_set_row_spacing (page_table, nrows - 1, 18);
+
+ if (!is_multi_file_window (window) && caja_file_can_set_group (get_target_file (window))) {
+ last_row = append_title_field (page_table,
+ _("_Group:"),
+ &group_label);
+ /* Combo box in this case. */
+ group_combo_box = attach_group_combo_box (page_table, last_row,
+ get_target_file (window));
+ gtk_label_set_mnemonic_widget (group_label,
+ GTK_WIDGET (group_combo_box));
+ } else {
+ last_row = append_title_field (page_table,
+ _("Group:"),
+ &group_label);
+ /* Static text in this case. */
+ value = attach_value_field (window, page_table, last_row,
+ VALUE_COLUMN,
+ "group",
+ INCONSISTENT_STATE_STRING,
+ FALSE);
+ gtk_label_set_mnemonic_widget (group_label, value);
+ }
+
+ if (has_directory) {
+ add_permissions_combo_box (window, page_table,
+ PERMISSION_GROUP, TRUE,
+ FALSE);
+ }
+ if (has_file || window->details->has_recursive_apply) {
+ add_permissions_combo_box (window, page_table,
+ PERMISSION_GROUP, FALSE,
+ !has_directory);
+ }
+
+ g_object_get (page_table, "n-rows", &nrows, NULL);
+ gtk_table_set_row_spacing (page_table, nrows - 1, 18);
+
+ append_title_field (page_table,
+ _("Others"),
+ &group_label);
+
+ if (has_directory) {
+ add_permissions_combo_box (window, page_table,
+ PERMISSION_OTHER, TRUE,
+ FALSE);
+ }
+ if (has_file || window->details->has_recursive_apply) {
+ add_permissions_combo_box (window, page_table,
+ PERMISSION_OTHER, FALSE,
+ !has_directory);
+ }
+
+ g_object_get (page_table, "n-rows", &nrows, NULL);
+ gtk_table_set_row_spacing (page_table, nrows - 1, 18);
+
+ last_row = append_title_field (page_table,
+ _("Execute:"),
+ &execute_label);
+ add_permissions_checkbox_with_label (window, page_table,
+ last_row, 1,
+ _("Allow _executing file as program"),
+ UNIX_PERM_USER_EXEC|UNIX_PERM_GROUP_EXEC|UNIX_PERM_OTHER_EXEC,
+ execute_label, FALSE);
+
+}
+
+static void
+create_permission_checkboxes (FMPropertiesWindow *window,
+ GtkTable *page_table,
+ gboolean is_folder)
+{
+ guint checkbox_titles_row;
+ GtkLabel *owner_perm_label;
+ GtkLabel *group_perm_label;
+ GtkLabel *other_perm_label;
+ GtkTable *check_button_table;
+
+ checkbox_titles_row = append_title_field (page_table, _("Owner:"), &owner_perm_label);
+ append_title_field (page_table, _("Group:"), &group_perm_label);
+ append_title_field (page_table, _("Others:"), &other_perm_label);
+
+ check_button_table = GTK_TABLE (gtk_table_new
+ (PERMISSIONS_CHECKBOXES_ROW_COUNT,
+ PERMISSIONS_CHECKBOXES_COLUMN_COUNT,
+ FALSE));
+ apply_standard_table_padding (check_button_table);
+ gtk_widget_show (GTK_WIDGET (check_button_table));
+ gtk_table_attach (page_table, GTK_WIDGET (check_button_table),
+ VALUE_COLUMN, VALUE_COLUMN + 1,
+ checkbox_titles_row, checkbox_titles_row + PERMISSIONS_CHECKBOXES_ROW_COUNT,
+ 0, 0,
+ 0, 0);
+
+ add_permissions_checkbox (window,
+ check_button_table,
+ PERMISSIONS_CHECKBOXES_OWNER_ROW,
+ PERMISSIONS_CHECKBOXES_READ_COLUMN,
+ UNIX_PERM_USER_READ,
+ owner_perm_label,
+ is_folder);
+
+ add_permissions_checkbox (window,
+ check_button_table,
+ PERMISSIONS_CHECKBOXES_OWNER_ROW,
+ PERMISSIONS_CHECKBOXES_WRITE_COLUMN,
+ UNIX_PERM_USER_WRITE,
+ owner_perm_label,
+ is_folder);
+
+ add_permissions_checkbox (window,
+ check_button_table,
+ PERMISSIONS_CHECKBOXES_OWNER_ROW,
+ PERMISSIONS_CHECKBOXES_EXECUTE_COLUMN,
+ UNIX_PERM_USER_EXEC,
+ owner_perm_label,
+ is_folder);
+
+ add_permissions_checkbox (window,
+ check_button_table,
+ PERMISSIONS_CHECKBOXES_GROUP_ROW,
+ PERMISSIONS_CHECKBOXES_READ_COLUMN,
+ UNIX_PERM_GROUP_READ,
+ group_perm_label,
+ is_folder);
+
+ add_permissions_checkbox (window,
+ check_button_table,
+ PERMISSIONS_CHECKBOXES_GROUP_ROW,
+ PERMISSIONS_CHECKBOXES_WRITE_COLUMN,
+ UNIX_PERM_GROUP_WRITE,
+ group_perm_label,
+ is_folder);
+
+ add_permissions_checkbox (window,
+ check_button_table,
+ PERMISSIONS_CHECKBOXES_GROUP_ROW,
+ PERMISSIONS_CHECKBOXES_EXECUTE_COLUMN,
+ UNIX_PERM_GROUP_EXEC,
+ group_perm_label,
+ is_folder);
+
+ add_permissions_checkbox (window,
+ check_button_table,
+ PERMISSIONS_CHECKBOXES_OTHERS_ROW,
+ PERMISSIONS_CHECKBOXES_READ_COLUMN,
+ UNIX_PERM_OTHER_READ,
+ other_perm_label,
+ is_folder);
+
+ add_permissions_checkbox (window,
+ check_button_table,
+ PERMISSIONS_CHECKBOXES_OTHERS_ROW,
+ PERMISSIONS_CHECKBOXES_WRITE_COLUMN,
+ UNIX_PERM_OTHER_WRITE,
+ other_perm_label,
+ is_folder);
+
+ add_permissions_checkbox (window,
+ check_button_table,
+ PERMISSIONS_CHECKBOXES_OTHERS_ROW,
+ PERMISSIONS_CHECKBOXES_EXECUTE_COLUMN,
+ UNIX_PERM_OTHER_EXEC,
+ other_perm_label,
+ is_folder);
+}
+
+static void
+create_advanced_permissions (FMPropertiesWindow *window, GtkTable *page_table)
+{
+ guint last_row;
+ GtkLabel *group_label;
+ GtkLabel *owner_label;
+ GtkComboBox *group_combo_box;
+ GtkComboBox *owner_combo_box;
+ gboolean has_directory, has_file;
+ gint nrows;
+
+ last_row = 0;
+
+ if (!is_multi_file_window (window) && caja_file_can_set_owner (get_target_file (window))) {
+
+ owner_label = attach_title_field (page_table, last_row, _("_Owner:"));
+ /* Combo box in this case. */
+ owner_combo_box = attach_owner_combo_box (page_table, last_row, get_target_file (window));
+ gtk_label_set_mnemonic_widget (owner_label,
+ GTK_WIDGET (owner_combo_box));
+ } else {
+ GtkWidget *value;
+
+ owner_label = attach_title_field (page_table, last_row, _("Owner:"));
+ /* Static text in this case. */
+ value = attach_value_field (window,
+ page_table, last_row, VALUE_COLUMN,
+ "owner",
+ INCONSISTENT_STATE_STRING,
+ FALSE);
+ gtk_label_set_mnemonic_widget (owner_label, value);
+ }
+
+ if (!is_multi_file_window (window) && caja_file_can_set_group (get_target_file (window))) {
+ last_row = append_title_field (page_table,
+ _("_Group:"),
+ &group_label);
+ /* Combo box in this case. */
+ group_combo_box = attach_group_combo_box (page_table, last_row,
+ get_target_file (window));
+ gtk_label_set_mnemonic_widget (group_label,
+ GTK_WIDGET (group_combo_box));
+ } else {
+ last_row = append_title_field (page_table,
+ _("Group:"),
+ NULL);
+ /* Static text in this case. */
+ attach_value_field (window, page_table, last_row,
+ VALUE_COLUMN,
+ "group",
+ INCONSISTENT_STATE_STRING,
+ FALSE);
+ }
+
+ g_object_get (page_table, "n-rows", &nrows, NULL);
+ gtk_table_set_row_spacing (page_table, nrows - 1, 18);
+
+ has_directory = files_has_directory (window);
+ has_file = files_has_file (window);
+
+ if (has_directory) {
+ if (has_file || window->details->has_recursive_apply) {
+ append_title_field (page_table,
+ _("Folder Permissions:"),
+ NULL);
+ }
+ create_permission_checkboxes (window, page_table, TRUE);
+ g_object_get (page_table, "n-rows", &nrows, NULL);
+ gtk_table_set_row_spacing (page_table, nrows - 1, 18);
+
+ }
+
+
+ if (has_file || window->details->has_recursive_apply) {
+ if (has_directory) {
+ append_title_field (page_table,
+ _("File Permissions:"),
+ NULL);
+ }
+ create_permission_checkboxes (window, page_table, FALSE);
+ g_object_get (page_table, "n-rows", &nrows, NULL);
+ gtk_table_set_row_spacing (page_table, nrows - 1, 18);
+ }
+
+ append_special_execution_flags (window, page_table);
+
+ append_title_value_pair
+ (window, page_table, _("Text view:"),
+ "permissions", INCONSISTENT_STATE_STRING,
+ FALSE);
+}
+
+static void
+set_recursive_permissions_done (gpointer callback_data)
+{
+ FMPropertiesWindow *window;
+
+ window = FM_PROPERTIES_WINDOW (callback_data);
+ end_long_operation (window);
+
+ g_object_unref (window);
+}
+
+
+static void
+apply_recursive_clicked (GtkWidget *recursive_button,
+ FMPropertiesWindow *window)
+{
+ guint32 file_permission, file_permission_mask;
+ guint32 dir_permission, dir_permission_mask;
+ guint32 vfs_mask, vfs_new_perm, p;
+ GtkWidget *button, *combo;
+ gboolean active, is_folder, is_special, use_original;
+ GList *l;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ PermissionType type;
+ int new_perm, mask;
+
+ file_permission = 0;
+ file_permission_mask = 0;
+ dir_permission = 0;
+ dir_permission_mask = 0;
+
+ /* Advanced mode and execute checkbox: */
+ for (l = window->details->permission_buttons; l != NULL; l = l->next) {
+ button = l->data;
+
+ if (gtk_toggle_button_get_inconsistent (GTK_TOGGLE_BUTTON (button))) {
+ continue;
+ }
+
+ active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button));
+ p = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
+ "permission"));
+ is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
+ "is-folder"));
+ is_special = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
+ "is-special"));
+
+ if (is_folder || is_special) {
+ dir_permission_mask |= p;
+ if (active) {
+ dir_permission |= p;
+ }
+ }
+ if (!is_folder || is_special) {
+ file_permission_mask |= p;
+ if (active) {
+ file_permission |= p;
+ }
+ }
+ }
+ /* Simple mode, minus exec checkbox */
+ for (l = window->details->permission_combos; l != NULL; l = l->next) {
+ combo = l->data;
+
+ if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter)) {
+ continue;
+ }
+
+ type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "permission-type"));
+ is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo),
+ "is-folder"));
+
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));
+ gtk_tree_model_get (model, &iter, 1, &new_perm, 2, &use_original, -1);
+ if (use_original) {
+ continue;
+ }
+ vfs_new_perm = permission_to_vfs (type, new_perm);
+
+ if (is_folder) {
+ mask = PERMISSION_READ|PERMISSION_WRITE|PERMISSION_EXEC;
+ } else {
+ mask = PERMISSION_READ|PERMISSION_WRITE;
+ }
+ vfs_mask = permission_to_vfs (type, mask);
+
+ if (is_folder) {
+ dir_permission_mask |= vfs_mask;
+ dir_permission |= vfs_new_perm;
+ } else {
+ file_permission_mask |= vfs_mask;
+ file_permission |= vfs_new_perm;
+ }
+ }
+
+ for (l = window->details->target_files; l != NULL; l = l->next) {
+ CajaFile *file;
+ char *uri;
+
+ file = CAJA_FILE (l->data);
+
+ if (caja_file_is_directory (file) &&
+ caja_file_can_set_permissions (file)) {
+ uri = caja_file_get_uri (file);
+ start_long_operation (window);
+ g_object_ref (window);
+ caja_file_set_permissions_recursive (uri,
+ file_permission,
+ file_permission_mask,
+ dir_permission,
+ dir_permission_mask,
+ set_recursive_permissions_done,
+ window);
+ g_free (uri);
+ }
+ }
+}
+
+static void
+create_permissions_page (FMPropertiesWindow *window)
+{
+ GtkWidget *vbox, *button, *hbox;
+ GtkTable *page_table;
+ char *file_name, *prompt_text;
+ GList *file_list;
+ guint last_row;
+ gint nrows;
+
+ vbox = create_page_with_vbox (window->details->notebook,
+ _("Permissions"));
+
+ file_list = window->details->original_files;
+
+ window->details->initial_permissions = NULL;
+
+ if (all_can_get_permissions (file_list) && all_can_get_permissions (window->details->target_files)) {
+ window->details->initial_permissions = get_initial_permissions (window->details->target_files);
+ window->details->has_recursive_apply = files_has_changable_permissions_directory (window);
+
+ if (!all_can_set_permissions (file_list)) {
+ add_prompt_and_separator (
+ GTK_VBOX (vbox),
+ _("You are not the owner, so you cannot change these permissions."));
+ }
+
+ page_table = GTK_TABLE (gtk_table_new (1, COLUMN_COUNT, FALSE));
+ window->details->permissions_table = page_table;
+
+ apply_standard_table_padding (page_table);
+ gtk_widget_show (GTK_WIDGET (page_table));
+ gtk_box_pack_start (GTK_BOX (vbox),
+ GTK_WIDGET (page_table),
+ TRUE, TRUE, 0);
+
+ if (eel_preferences_get_boolean (CAJA_PREFERENCES_SHOW_ADVANCED_PERMISSIONS)) {
+ window->details->advanced_permissions = TRUE;
+ create_advanced_permissions (window, page_table);
+ } else {
+ window->details->advanced_permissions = FALSE;
+ create_simple_permissions (window, page_table);
+ }
+
+ g_object_get (page_table, "n-rows", &nrows, NULL);
+ gtk_table_set_row_spacing (page_table, nrows - 1, 18);
+
+#ifdef HAVE_SELINUX
+ append_title_value_pair
+ (window, page_table, _("SELinux context:"),
+ "selinux_context", INCONSISTENT_STATE_STRING,
+ FALSE);
+#endif
+ append_title_value_pair
+ (window, page_table, _("Last changed:"),
+ "date_permissions", INCONSISTENT_STATE_STRING,
+ FALSE);
+
+ if (window->details->has_recursive_apply) {
+ last_row = append_row (page_table);
+ hbox = gtk_hbox_new (FALSE, 0);
+ gtk_widget_show (hbox);
+ gtk_table_attach (page_table, hbox,
+ 0, 2,
+ last_row, last_row+1,
+ GTK_FILL, 0,
+ 0, 0);
+
+ button = gtk_button_new_with_mnemonic (_("Apply Permissions to Enclosed Files"));
+ gtk_widget_show (button);
+ gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (apply_recursive_clicked),
+ window);
+ }
+ } else {
+ if (!is_multi_file_window (window)) {
+ file_name = caja_file_get_display_name (get_target_file (window));
+ prompt_text = g_strdup_printf (_("The permissions of \"%s\" could not be determined."), file_name);
+ g_free (file_name);
+ } else {
+ prompt_text = g_strdup (_("The permissions of the selected file could not be determined."));
+ }
+
+ add_prompt (GTK_VBOX (vbox), prompt_text, TRUE);
+ g_free (prompt_text);
+ }
+}
+
+static void
+append_extension_pages (FMPropertiesWindow *window)
+{
+ GList *providers;
+ GList *p;
+
+ providers = caja_module_get_extensions_for_type (CAJA_TYPE_PROPERTY_PAGE_PROVIDER);
+
+ for (p = providers; p != NULL; p = p->next) {
+ CajaPropertyPageProvider *provider;
+ GList *pages;
+ GList *l;
+
+ provider = CAJA_PROPERTY_PAGE_PROVIDER (p->data);
+
+ pages = caja_property_page_provider_get_pages
+ (provider, window->details->original_files);
+
+ for (l = pages; l != NULL; l = l->next) {
+ CajaPropertyPage *page;
+ GtkWidget *page_widget;
+ GtkWidget *label;
+
+ page = CAJA_PROPERTY_PAGE (l->data);
+
+ g_object_get (G_OBJECT (page),
+ "page", &page_widget, "label", &label,
+ NULL);
+
+ gtk_notebook_append_page (window->details->notebook,
+ page_widget, label);
+
+ g_object_set_data (G_OBJECT (page_widget),
+ "is-extension-page",
+ page);
+
+ g_object_unref (page_widget);
+ g_object_unref (label);
+
+ g_object_unref (page);
+ }
+
+ g_list_free (pages);
+ }
+
+ caja_module_extension_list_free (providers);
+}
+
+static gboolean
+should_show_emblems (FMPropertiesWindow *window)
+{
+ /* FIXME bugzilla.gnome.org 45643:
+ * Emblems aren't displayed on the the desktop Trash icon, so
+ * we shouldn't pretend that they work by showing them here.
+ * When bug 5643 is fixed we can remove this case.
+ */
+ if (!is_multi_file_window (window)
+ && is_merged_trash_directory (get_target_file (window))) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+should_show_permissions (FMPropertiesWindow *window)
+{
+ CajaFile *file;
+
+ file = get_target_file (window);
+
+ /* Don't show permissions for Trash and Computer since they're not
+ * really file system objects.
+ */
+ if (!is_multi_file_window (window)
+ && (is_merged_trash_directory (file) ||
+ is_computer_directory (file))) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static char *
+get_pending_key (GList *file_list)
+{
+ GList *l;
+ GList *uris;
+ GString *key;
+ char *ret;
+
+ uris = NULL;
+ for (l = file_list; l != NULL; l = l->next) {
+ uris = g_list_prepend (uris, caja_file_get_uri (CAJA_FILE (l->data)));
+ }
+ uris = g_list_sort (uris, (GCompareFunc)strcmp);
+
+ key = g_string_new ("");
+ for (l = uris; l != NULL; l = l->next) {
+ g_string_append (key, l->data);
+ g_string_append (key, ";");
+ }
+
+ eel_g_list_free_deep (uris);
+
+ ret = key->str;
+ g_string_free (key, FALSE);
+
+ return ret;
+}
+
+static StartupData *
+startup_data_new (GList *original_files,
+ GList *target_files,
+ const char *pending_key,
+ GtkWidget *parent_widget)
+{
+ StartupData *data;
+ GList *l;
+
+ data = g_new0 (StartupData, 1);
+ data->original_files = caja_file_list_copy (original_files);
+ data->target_files = caja_file_list_copy (target_files);
+ data->parent_widget = parent_widget;
+ data->pending_key = g_strdup (pending_key);
+ data->pending_files = g_hash_table_new (g_direct_hash,
+ g_direct_equal);
+
+ for (l = data->target_files; l != NULL; l = l->next) {
+ g_hash_table_insert (data->pending_files, l->data, l->data);
+ }
+
+ return data;
+}
+
+static void
+startup_data_free (StartupData *data)
+{
+ caja_file_list_free (data->original_files);
+ caja_file_list_free (data->target_files);
+ g_hash_table_destroy (data->pending_files);
+ g_free (data->pending_key);
+ g_free (data);
+}
+
+static void
+file_changed_callback (CajaFile *file, gpointer user_data)
+{
+ FMPropertiesWindow *window = FM_PROPERTIES_WINDOW (user_data);
+
+ if (!g_list_find (window->details->changed_files, file)) {
+ caja_file_ref (file);
+ window->details->changed_files = g_list_prepend (window->details->changed_files, file);
+
+ schedule_files_update (window);
+ }
+}
+
+static gboolean
+is_a_special_file (CajaFile *file)
+{
+ if (file == NULL ||
+ CAJA_IS_DESKTOP_ICON_FILE (file) ||
+ caja_file_is_caja_link (file) ||
+ is_merged_trash_directory (file) ||
+ is_computer_directory (file)) {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static gboolean
+should_show_open_with (FMPropertiesWindow *window)
+{
+ CajaFile *file;
+
+ /* Don't show open with tab for desktop special icons (trash, etc)
+ * or desktop files. We don't get the open-with menu for these anyway.
+ *
+ * Also don't show it for folders. Changing the default app for folders
+ * leads to all sort of hard to understand errors.
+ */
+
+ if (is_multi_file_window (window)) {
+ if (!file_list_attributes_identical (window->details->original_files,
+ "mime_type")) {
+ return FALSE;
+ } else {
+
+ GList *l;
+
+ for (l = window->details->original_files; l; l = l->next) {
+ file = CAJA_FILE (l->data);
+ if (caja_file_is_directory (file) ||
+ is_a_special_file (file)) {
+ return FALSE;
+ }
+ }
+ }
+ } else {
+ file = get_original_file (window);
+ if (caja_file_is_directory (file) ||
+ is_a_special_file (file)) {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static void
+create_open_with_page (FMPropertiesWindow *window)
+{
+ GtkWidget *vbox;
+ char *mime_type;
+ char *uri;
+
+ mime_type = caja_file_get_mime_type (get_target_file (window));
+
+ if (!is_multi_file_window (window)) {
+ uri = caja_file_get_uri (get_target_file (window));
+ if (uri == NULL) {
+ return;
+ }
+ vbox = caja_mime_application_chooser_new (uri, mime_type);
+
+ g_free (uri);
+ } else {
+ GList *uris;
+
+ uris = window->details->original_files;
+ if (uris == NULL) {
+ return;
+ }
+ vbox = caja_mime_application_chooser_new_for_multiple_files (uris, mime_type);
+ }
+
+ gtk_widget_show (vbox);
+ g_free (mime_type);
+
+ gtk_notebook_append_page (window->details->notebook,
+ vbox, gtk_label_new (_("Open With")));
+}
+
+
+static FMPropertiesWindow *
+create_properties_window (StartupData *startup_data)
+{
+ FMPropertiesWindow *window;
+ GList *l;
+
+ window = FM_PROPERTIES_WINDOW (gtk_widget_new (fm_properties_window_get_type (), NULL));
+
+ window->details->original_files = caja_file_list_copy (startup_data->original_files);
+
+ window->details->target_files = caja_file_list_copy (startup_data->target_files);
+
+ gtk_window_set_wmclass (GTK_WINDOW (window), "file_properties", "Caja");
+ gtk_window_set_screen (GTK_WINDOW (window),
+ gtk_widget_get_screen (startup_data->parent_widget));
+
+ gtk_window_set_type_hint (GTK_WINDOW (window), GDK_WINDOW_TYPE_HINT_DIALOG);
+
+ /* Set initial window title */
+ update_properties_window_title (window);
+
+ /* Start monitoring the file attributes we display. Note that some
+ * of the attributes are for the original file, and some for the
+ * target files.
+ */
+
+ for (l = window->details->original_files; l != NULL; l = l->next) {
+ CajaFile *file;
+ CajaFileAttributes attributes;
+
+ file = CAJA_FILE (l->data);
+
+ attributes =
+ CAJA_FILE_ATTRIBUTES_FOR_ICON |
+ CAJA_FILE_ATTRIBUTE_INFO |
+ CAJA_FILE_ATTRIBUTE_LINK_INFO;
+
+ caja_file_monitor_add (CAJA_FILE (l->data),
+ &window->details->original_files,
+ attributes);
+ }
+
+ for (l = window->details->target_files; l != NULL; l = l->next) {
+ CajaFile *file;
+ CajaFileAttributes attributes;
+
+ file = CAJA_FILE (l->data);
+
+ attributes = 0;
+ if (caja_file_is_directory (file)) {
+ attributes |= CAJA_FILE_ATTRIBUTE_DEEP_COUNTS;
+ }
+
+ attributes |= CAJA_FILE_ATTRIBUTE_INFO;
+ caja_file_monitor_add (file, &window->details->target_files, attributes);
+ }
+
+ for (l = window->details->target_files; l != NULL; l = l->next) {
+ g_signal_connect_object (CAJA_FILE (l->data),
+ "changed",
+ G_CALLBACK (file_changed_callback),
+ G_OBJECT (window),
+ 0);
+ }
+
+ for (l = window->details->original_files; l != NULL; l = l->next) {
+ g_signal_connect_object (CAJA_FILE (l->data),
+ "changed",
+ G_CALLBACK (file_changed_callback),
+ G_OBJECT (window),
+ 0);
+ }
+
+ /* Create the notebook tabs. */
+ window->details->notebook = GTK_NOTEBOOK (gtk_notebook_new ());
+ gtk_widget_show (GTK_WIDGET (window->details->notebook));
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (window))),
+ GTK_WIDGET (window->details->notebook),
+ TRUE, TRUE, 0);
+
+ /* Create the pages. */
+ create_basic_page (window);
+
+ if (should_show_emblems (window)) {
+ create_emblems_page (window);
+ }
+
+ if (should_show_permissions (window)) {
+ create_permissions_page (window);
+ }
+
+ if (should_show_open_with (window)) {
+ create_open_with_page (window);
+ }
+
+ /* append pages from available views */
+ append_extension_pages (window);
+
+ gtk_dialog_add_buttons (GTK_DIALOG (window),
+ GTK_STOCK_HELP, GTK_RESPONSE_HELP,
+ GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
+ NULL);
+
+ /* FIXME - HIGificiation, should be done inside GTK+ */
+ gtk_widget_ensure_style (GTK_WIDGET (window));
+ gtk_container_set_border_width (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (window))), 12);
+ gtk_container_set_border_width (GTK_CONTAINER (gtk_dialog_get_action_area (GTK_DIALOG (window))), 0);
+ gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (window))), 12);
+ gtk_dialog_set_has_separator (GTK_DIALOG (window), FALSE);
+
+ /* Update from initial state */
+ properties_window_update (window, NULL);
+
+ return window;
+}
+
+static GList *
+get_target_file_list (GList *original_files)
+{
+ GList *ret;
+ GList *l;
+
+ ret = NULL;
+
+ for (l = original_files; l != NULL; l = l->next) {
+ CajaFile *target;
+
+ target = get_target_file_for_original_file (CAJA_FILE (l->data));
+
+ ret = g_list_prepend (ret, target);
+ }
+
+ ret = g_list_reverse (ret);
+
+ return ret;
+}
+
+static void
+add_window (FMPropertiesWindow *window)
+{
+ if (!is_multi_file_window (window)) {
+ g_hash_table_insert (windows,
+ get_original_file (window),
+ window);
+ g_object_set_data (G_OBJECT (window), "window_key",
+ get_original_file (window));
+ }
+}
+
+static void
+remove_window (FMPropertiesWindow *window)
+{
+ gpointer key;
+
+ key = g_object_get_data (G_OBJECT (window), "window_key");
+ if (key) {
+ g_hash_table_remove (windows, key);
+ }
+}
+
+static GtkWindow *
+get_existing_window (GList *file_list)
+{
+ if (!file_list->next) {
+ return g_hash_table_lookup (windows, file_list->data);
+ }
+
+ return NULL;
+}
+
+static void
+cancel_create_properties_window_callback (gpointer callback_data)
+{
+ remove_pending ((StartupData *)callback_data, TRUE, FALSE, TRUE);
+}
+
+static void
+parent_widget_destroyed_callback (GtkWidget *widget, gpointer callback_data)
+{
+ g_assert (widget == ((StartupData *)callback_data)->parent_widget);
+
+ remove_pending ((StartupData *)callback_data, TRUE, TRUE, FALSE);
+}
+
+static void
+cancel_call_when_ready_callback (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ caja_file_cancel_call_when_ready
+ (CAJA_FILE (key),
+ is_directory_ready_callback,
+ user_data);
+}
+
+static void
+remove_pending (StartupData *startup_data,
+ gboolean cancel_call_when_ready,
+ gboolean cancel_timed_wait,
+ gboolean cancel_destroy_handler)
+{
+ if (cancel_call_when_ready) {
+ g_hash_table_foreach (startup_data->pending_files,
+ cancel_call_when_ready_callback,
+ startup_data);
+
+ }
+ if (cancel_timed_wait) {
+ eel_timed_wait_stop
+ (cancel_create_properties_window_callback, startup_data);
+ }
+ if (cancel_destroy_handler) {
+ g_signal_handlers_disconnect_by_func (startup_data->parent_widget,
+ G_CALLBACK (parent_widget_destroyed_callback),
+ startup_data);
+ }
+
+ g_hash_table_remove (pending_lists, startup_data->pending_key);
+
+ startup_data_free (startup_data);
+}
+
+static void
+is_directory_ready_callback (CajaFile *file,
+ gpointer data)
+{
+ StartupData *startup_data;
+
+ startup_data = data;
+
+ g_hash_table_remove (startup_data->pending_files, file);
+
+ if (g_hash_table_size (startup_data->pending_files) == 0) {
+ FMPropertiesWindow *new_window;
+
+ new_window = create_properties_window (startup_data);
+
+ add_window (new_window);
+
+ remove_pending (startup_data, FALSE, TRUE, TRUE);
+
+/* FIXME bugzilla.gnome.org 42151:
+ * See comment elsewhere in this file about bug 2151.
+ */
+#ifdef UNDO_ENABLED
+ caja_undo_share_undo_manager (GTK_OBJECT (new_window),
+ GTK_OBJECT (callback_data));
+#endif
+ gtk_window_present (GTK_WINDOW (new_window));
+ }
+}
+
+
+void
+fm_properties_window_present (GList *original_files,
+ GtkWidget *parent_widget)
+{
+ GList *l, *next;
+ GtkWidget *parent_window;
+ StartupData *startup_data;
+ GList *target_files;
+ GtkWindow *existing_window;
+ char *pending_key;
+
+ g_return_if_fail (original_files != NULL);
+ g_return_if_fail (GTK_IS_WIDGET (parent_widget));
+
+ /* Create the hash tables first time through. */
+ if (windows == NULL) {
+ windows = eel_g_hash_table_new_free_at_exit
+ (NULL, NULL, "property windows");
+ }
+
+ if (pending_lists == NULL) {
+ pending_lists = eel_g_hash_table_new_free_at_exit
+ (g_str_hash, g_str_equal, "pending property window files");
+ }
+
+ /* Look to see if there's already a window for this file. */
+ existing_window = get_existing_window (original_files);
+ if (existing_window != NULL) {
+ gtk_window_set_screen (existing_window,
+ gtk_widget_get_screen (parent_widget));
+ gtk_window_present (existing_window);
+ return;
+ }
+
+
+ pending_key = get_pending_key (original_files);
+
+ /* Look to see if we're already waiting for a window for this file. */
+ if (g_hash_table_lookup (pending_lists, pending_key) != NULL) {
+ return;
+ }
+
+ target_files = get_target_file_list (original_files);
+
+ startup_data = startup_data_new (original_files,
+ target_files,
+ pending_key,
+ parent_widget);
+
+ caja_file_list_free (target_files);
+ g_free(pending_key);
+
+ /* Wait until we can tell whether it's a directory before showing, since
+ * some one-time layout decisions depend on that info.
+ */
+
+ g_hash_table_insert (pending_lists, startup_data->pending_key, startup_data->pending_key);
+ g_signal_connect (parent_widget, "destroy",
+ G_CALLBACK (parent_widget_destroyed_callback), startup_data);
+
+ parent_window = gtk_widget_get_ancestor (parent_widget, GTK_TYPE_WINDOW);
+
+ eel_timed_wait_start
+ (cancel_create_properties_window_callback,
+ startup_data,
+ _("Creating Properties window."),
+ parent_window == NULL ? NULL : GTK_WINDOW (parent_window));
+
+
+ for (l = startup_data->target_files; l != NULL; l = next) {
+ next = l->next;
+ caja_file_call_when_ready
+ (CAJA_FILE (l->data),
+ CAJA_FILE_ATTRIBUTE_INFO,
+ is_directory_ready_callback,
+ startup_data);
+ }
+}
+
+static void
+real_response (GtkDialog *dialog,
+ int response)
+{
+ GError *error = NULL;
+
+ switch (response) {
+ case GTK_RESPONSE_HELP:
+ gtk_show_uri (gtk_window_get_screen (GTK_WINDOW (dialog)),
+ "ghelp:user-guide#goscaja-51",
+ gtk_get_current_event_time (),
+ &error);
+ if (error != NULL) {
+ eel_show_error_dialog (_("There was an error displaying help."), error->message,
+ GTK_WINDOW (dialog));
+ g_error_free (error);
+ }
+ break;
+
+ case GTK_RESPONSE_NONE:
+ case GTK_RESPONSE_CLOSE:
+ case GTK_RESPONSE_DELETE_EVENT:
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+static void
+real_destroy (GtkObject *object)
+{
+ FMPropertiesWindow *window;
+ GList *l;
+
+ window = FM_PROPERTIES_WINDOW (object);
+
+ remove_window (window);
+
+ for (l = window->details->original_files; l != NULL; l = l->next) {
+ caja_file_monitor_remove (CAJA_FILE (l->data), &window->details->original_files);
+ }
+ caja_file_list_free (window->details->original_files);
+ window->details->original_files = NULL;
+
+ for (l = window->details->target_files; l != NULL; l = l->next) {
+ caja_file_monitor_remove (CAJA_FILE (l->data), &window->details->target_files);
+ }
+ caja_file_list_free (window->details->target_files);
+ window->details->target_files = NULL;
+
+ caja_file_list_free (window->details->changed_files);
+ window->details->changed_files = NULL;
+
+ window->details->name_field = NULL;
+
+ g_list_free (window->details->emblem_buttons);
+ window->details->emblem_buttons = NULL;
+
+ if (window->details->initial_emblems) {
+ g_hash_table_destroy (window->details->initial_emblems);
+ window->details->initial_emblems = NULL;
+ }
+
+ g_list_free (window->details->permission_buttons);
+ window->details->permission_buttons = NULL;
+
+ g_list_free (window->details->permission_combos);
+ window->details->permission_combos = NULL;
+
+ if (window->details->initial_permissions) {
+ g_hash_table_destroy (window->details->initial_permissions);
+ window->details->initial_permissions = NULL;
+ }
+
+ g_list_free (window->details->value_fields);
+ window->details->value_fields = NULL;
+
+ if (window->details->update_directory_contents_timeout_id != 0) {
+ g_source_remove (window->details->update_directory_contents_timeout_id);
+ window->details->update_directory_contents_timeout_id = 0;
+ }
+
+ if (window->details->update_files_timeout_id != 0) {
+ g_source_remove (window->details->update_files_timeout_id);
+ window->details->update_files_timeout_id = 0;
+ }
+
+ GTK_OBJECT_CLASS (parent_class)->destroy (object);
+}
+
+static void
+real_finalize (GObject *object)
+{
+ FMPropertiesWindow *window;
+
+ window = FM_PROPERTIES_WINDOW (object);
+
+ eel_g_list_free_deep (window->details->mime_list);
+
+ g_free (window->details->pending_name);
+ g_free (window->details);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+/* converts
+ * file://foo/foobar/foofoo/bar
+ * to
+ * foofoo/bar
+ * if
+ * file://foo/foobar
+ * is the parent
+ *
+ * It does not resolve any symlinks.
+ * */
+static char *
+make_relative_uri_from_full (const char *uri,
+ const char *base_uri)
+{
+ g_assert (uri != NULL);
+ g_assert (base_uri != NULL);
+
+ if (g_str_has_prefix (uri, base_uri)) {
+ uri += strlen (base_uri);
+ if (*uri != '/') {
+ return NULL;
+ }
+
+ while (*uri == '/') {
+ uri++;
+ }
+
+ if (*uri != '\0') {
+ return g_strdup (uri);
+ }
+ }
+
+ return NULL;
+}
+
+/* icon selection callback to set the image of the file object to the selected file */
+static void
+set_icon (const char* icon_uri, FMPropertiesWindow *properties_window)
+{
+ CajaFile *file;
+ char *file_uri;
+ char *icon_path;
+ char *real_icon_uri;
+
+ g_assert (icon_uri != NULL);
+ g_assert (FM_IS_PROPERTIES_WINDOW (properties_window));
+
+ icon_path = g_filename_from_uri (icon_uri, NULL, NULL);
+ /* we don't allow remote URIs */
+ if (icon_path != NULL) {
+ GList *l;
+
+ for (l = properties_window->details->original_files; l != NULL; l = l->next) {
+ file = CAJA_FILE (l->data);
+
+ file_uri = caja_file_get_uri (file);
+
+ if (caja_file_is_mime_type (file, "application/x-desktop")) {
+ if (caja_link_local_set_icon (file_uri, icon_path)) {
+ caja_file_invalidate_attributes (file,
+ CAJA_FILE_ATTRIBUTE_INFO |
+ CAJA_FILE_ATTRIBUTE_LINK_INFO);
+ }
+ } else {
+ real_icon_uri = make_relative_uri_from_full (icon_uri, file_uri);
+ if (real_icon_uri == NULL) {
+ real_icon_uri = g_strdup (icon_uri);
+ }
+
+ caja_file_set_metadata (file, CAJA_METADATA_KEY_CUSTOM_ICON, NULL, real_icon_uri);
+ caja_file_set_metadata (file, CAJA_METADATA_KEY_ICON_SCALE, NULL, NULL);
+
+ g_free (real_icon_uri);
+ }
+
+ g_free (file_uri);
+ }
+
+ g_free (icon_path);
+ }
+}
+
+static void
+update_preview_callback (GtkFileChooser *icon_chooser,
+ FMPropertiesWindow *window)
+{
+ GtkWidget *preview_widget;
+ GdkPixbuf *pixbuf, *scaled_pixbuf;
+ char *filename;
+ double scale;
+
+ pixbuf = NULL;
+
+ filename = gtk_file_chooser_get_filename (icon_chooser);
+ if (filename != NULL) {
+ pixbuf = gdk_pixbuf_new_from_file (filename, NULL);
+ }
+
+ if (pixbuf != NULL) {
+ preview_widget = gtk_file_chooser_get_preview_widget (icon_chooser);
+ gtk_file_chooser_set_preview_widget_active (icon_chooser, TRUE);
+
+ if (gdk_pixbuf_get_width (pixbuf) > PREVIEW_IMAGE_WIDTH) {
+ scale = (double)gdk_pixbuf_get_height (pixbuf) /
+ gdk_pixbuf_get_width (pixbuf);
+
+ scaled_pixbuf = mate_desktop_thumbnail_scale_down_pixbuf
+ (pixbuf,
+ PREVIEW_IMAGE_WIDTH,
+ scale * PREVIEW_IMAGE_WIDTH);
+ g_object_unref (pixbuf);
+ pixbuf = scaled_pixbuf;
+ }
+
+ gtk_image_set_from_pixbuf (GTK_IMAGE (preview_widget), pixbuf);
+ } else {
+ gtk_file_chooser_set_preview_widget_active (icon_chooser, FALSE);
+ }
+
+ g_free (filename);
+
+ if (pixbuf != NULL) {
+ g_object_unref (pixbuf);
+ }
+}
+
+static void
+custom_icon_file_chooser_response_cb (GtkDialog *dialog,
+ gint response,
+ FMPropertiesWindow *window)
+{
+ char *uri;
+
+ switch (response) {
+ case GTK_RESPONSE_NO:
+ reset_icon (window);
+ break;
+
+ case GTK_RESPONSE_OK:
+ uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog));
+ set_icon (uri, window);
+ g_free (uri);
+ break;
+
+ default:
+ break;
+ }
+
+ gtk_widget_hide (GTK_WIDGET (dialog));
+}
+
+static void
+select_image_button_callback (GtkWidget *widget,
+ FMPropertiesWindow *window)
+{
+ GtkWidget *dialog, *preview;
+ GtkFileFilter *filter;
+ GList *l;
+ CajaFile *file;
+ char *uri;
+ char *image_path;
+ gboolean revert_is_sensitive;
+
+ g_assert (FM_IS_PROPERTIES_WINDOW (window));
+
+ dialog = window->details->icon_chooser;
+
+ if (dialog == NULL) {
+ dialog = gtk_file_chooser_dialog_new (_("Select Custom Icon"), GTK_WINDOW (window),
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ GTK_STOCK_REVERT_TO_SAVED, GTK_RESPONSE_NO,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OPEN, GTK_RESPONSE_OK,
+ NULL);
+ gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (dialog), "/usr/share/pixmaps", NULL);
+ gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE);
+
+ filter = gtk_file_filter_new ();
+ gtk_file_filter_add_pixbuf_formats (filter);
+ gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), filter);
+
+ preview = gtk_image_new ();
+ gtk_widget_set_size_request (preview, PREVIEW_IMAGE_WIDTH, -1);
+ gtk_file_chooser_set_preview_widget (GTK_FILE_CHOOSER (dialog), preview);
+ gtk_file_chooser_set_use_preview_label (GTK_FILE_CHOOSER (dialog), FALSE);
+ gtk_file_chooser_set_preview_widget_active (GTK_FILE_CHOOSER (dialog), FALSE);
+
+ g_signal_connect (dialog, "update-preview",
+ G_CALLBACK (update_preview_callback), window);
+
+ window->details->icon_chooser = dialog;
+
+ g_object_add_weak_pointer (G_OBJECT (dialog),
+ (gpointer *) &window->details->icon_chooser);
+ }
+
+ /* it's likely that the user wants to pick an icon that is inside a local directory */
+ if (g_list_length (window->details->original_files) == 1) {
+ file = CAJA_FILE (window->details->original_files->data);
+
+ if (caja_file_is_directory (file)) {
+ uri = caja_file_get_uri (file);
+
+ image_path = g_filename_from_uri (uri, NULL, NULL);
+ if (image_path != NULL) {
+ gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), image_path);
+ g_free (image_path);
+ }
+
+ g_free (uri);
+ }
+ }
+
+ revert_is_sensitive = FALSE;
+ for (l = window->details->original_files; l != NULL; l = l->next) {
+ file = CAJA_FILE (l->data);
+ image_path = caja_file_get_metadata (file, CAJA_METADATA_KEY_CUSTOM_ICON, NULL);
+ revert_is_sensitive = (image_path != NULL);
+ g_free (image_path);
+
+ if (revert_is_sensitive) {
+ break;
+ }
+ }
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_NO, revert_is_sensitive);
+
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (custom_icon_file_chooser_response_cb), window);
+ gtk_widget_show (dialog);
+}
+
+static void
+fm_properties_window_class_init (FMPropertiesWindowClass *class)
+{
+ GtkBindingSet *binding_set;
+
+ G_OBJECT_CLASS (class)->finalize = real_finalize;
+ GTK_OBJECT_CLASS (class)->destroy = real_destroy;
+ GTK_DIALOG_CLASS (class)->response = real_response;
+
+ binding_set = gtk_binding_set_by_class (class);
+ gtk_binding_entry_add_signal (binding_set, GDK_Escape, 0,
+ "close", 0);
+}
+
+static void
+fm_properties_window_init (FMPropertiesWindow *window)
+{
+ window->details = g_new0 (FMPropertiesWindowDetails, 1);
+}
diff --git a/src/file-manager/fm-properties-window.h b/src/file-manager/fm-properties-window.h
new file mode 100644
index 00000000..6044e8b9
--- /dev/null
+++ b/src/file-manager/fm-properties-window.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* fm-properties-window.h - interface for window that lets user modify
+ icon properties
+
+ Copyright (C) 2000 Eazel, Inc.
+
+ The Mate Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Mate Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Darin Adler <[email protected]>
+*/
+
+#ifndef FM_PROPERTIES_WINDOW_H
+#define FM_PROPERTIES_WINDOW_H
+
+#include <gtk/gtk.h>
+#include <libcaja-private/caja-file.h>
+
+typedef struct FMPropertiesWindow FMPropertiesWindow;
+
+#define FM_TYPE_PROPERTIES_WINDOW fm_properties_window_get_type()
+#define FM_PROPERTIES_WINDOW(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_PROPERTIES_WINDOW, FMPropertiesWindow))
+#define FM_PROPERTIES_WINDOW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_PROPERTIES_WINDOW, FMPropertiesWindowClass))
+#define FM_IS_PROPERTIES_WINDOW(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_PROPERTIES_WINDOW))
+#define FM_IS_PROPERTIES_WINDOW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_PROPERTIES_WINDOW))
+#define FM_PROPERTIES_WINDOW_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_PROPERTIES_WINDOW, FMPropertiesWindowClass))
+
+typedef struct FMPropertiesWindowDetails FMPropertiesWindowDetails;
+
+struct FMPropertiesWindow
+{
+ GtkDialog window;
+ FMPropertiesWindowDetails *details;
+};
+
+struct FMPropertiesWindowClass
+{
+ GtkDialogClass parent_class;
+
+ /* Keybinding signals */
+ void (* close) (FMPropertiesWindow *window);
+};
+
+typedef struct FMPropertiesWindowClass FMPropertiesWindowClass;
+
+GType fm_properties_window_get_type (void);
+
+void fm_properties_window_present (GList *files,
+ GtkWidget *parent_widget);
+
+#endif /* FM_PROPERTIES_WINDOW_H */
diff --git a/src/file-manager/fm-tree-model.c b/src/file-manager/fm-tree-model.c
new file mode 100644
index 00000000..6356db90
--- /dev/null
+++ b/src/file-manager/fm-tree-model.c
@@ -0,0 +1,2152 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Copyright C) 2000, 2001 Eazel, Inc
+ * Copyright (C) 2002 Anders Carlsson
+ * Copyright (C) 2002 Bent Spoon Software
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Anders Carlsson <[email protected]>
+ * Darin Adler <[email protected]>
+ */
+
+/* fm-tree-model.c - model for the tree view */
+
+#include <config.h>
+#include "fm-tree-model.h"
+
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-gdk-pixbuf-extensions.h>
+#include <glib/gi18n.h>
+#include <libcaja-private/caja-directory.h>
+#include <libcaja-private/caja-file-attributes.h>
+#include <libcaja-private/caja-file.h>
+#include <gtk/gtk.h>
+#include <string.h>
+
+enum
+{
+ ROW_LOADED,
+ LAST_SIGNAL
+};
+
+static guint tree_model_signals[LAST_SIGNAL] = { 0 };
+
+typedef gboolean (* FilePredicate) (CajaFile *);
+
+/* The user_data of the GtkTreeIter is the TreeNode pointer.
+ * It's NULL for the dummy node. If it's NULL, then user_data2
+ * is the TreeNode pointer to the parent.
+ */
+
+typedef struct TreeNode TreeNode;
+typedef struct FMTreeModelRoot FMTreeModelRoot;
+
+struct TreeNode
+{
+ /* part of this node for the file itself */
+ int ref_count;
+
+ CajaFile *file;
+ char *display_name;
+ GIcon *icon;
+ GMount *mount;
+ GdkPixbuf *closed_pixbuf;
+ GdkPixbuf *open_pixbuf;
+ GdkPixbuf *emblem_pixbuf;
+
+ FMTreeModelRoot *root;
+
+ TreeNode *parent;
+ TreeNode *next;
+ TreeNode *prev;
+
+ /* part of the node used only for directories */
+ int dummy_child_ref_count;
+ int all_children_ref_count;
+
+ CajaDirectory *directory;
+ guint done_loading_id;
+ guint files_added_id;
+ guint files_changed_id;
+
+ TreeNode *first_child;
+
+ /* misc. flags */
+ guint done_loading : 1;
+ guint force_has_dummy : 1;
+ guint inserted : 1;
+};
+
+struct FMTreeModelDetails
+{
+ int stamp;
+
+ TreeNode *root_node;
+
+ guint monitoring_update_idle_id;
+
+ gboolean show_hidden_files;
+ gboolean show_backup_files;
+ gboolean show_only_directories;
+
+ GList *highlighted_files;
+};
+
+struct FMTreeModelRoot
+{
+ FMTreeModel *model;
+
+ /* separate hash table for each root node needed */
+ GHashTable *file_to_node_map;
+
+ TreeNode *root_node;
+};
+
+typedef struct
+{
+ CajaDirectory *directory;
+ FMTreeModel *model;
+} DoneLoadingParameters;
+
+static void fm_tree_model_tree_model_init (GtkTreeModelIface *iface);
+static void schedule_monitoring_update (FMTreeModel *model);
+static void destroy_node_without_reporting (FMTreeModel *model,
+ TreeNode *node);
+static void report_node_contents_changed (FMTreeModel *model,
+ TreeNode *node);
+
+G_DEFINE_TYPE_WITH_CODE (FMTreeModel, fm_tree_model, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL,
+ fm_tree_model_tree_model_init));
+
+static GtkTreeModelFlags
+fm_tree_model_get_flags (GtkTreeModel *tree_model)
+{
+ return GTK_TREE_MODEL_ITERS_PERSIST;
+}
+
+static void
+object_unref_if_not_NULL (gpointer object)
+{
+ if (object == NULL)
+ {
+ return;
+ }
+ g_object_unref (object);
+}
+
+static FMTreeModelRoot *
+tree_model_root_new (FMTreeModel *model)
+{
+ FMTreeModelRoot *root;
+
+ root = g_new0 (FMTreeModelRoot, 1);
+ root->model = model;
+ root->file_to_node_map = g_hash_table_new (NULL, NULL);
+
+ return root;
+}
+
+static TreeNode *
+tree_node_new (CajaFile *file, FMTreeModelRoot *root)
+{
+ TreeNode *node;
+
+ node = g_new0 (TreeNode, 1);
+ node->file = caja_file_ref (file);
+ node->root = root;
+ return node;
+}
+
+static void
+tree_node_unparent (FMTreeModel *model, TreeNode *node)
+{
+ TreeNode *parent, *next, *prev;
+
+ parent = node->parent;
+ next = node->next;
+ prev = node->prev;
+
+ if (parent == NULL &&
+ node == model->details->root_node)
+ {
+ /* it's the first root node -> if there is a next then let it be the first root node */
+ model->details->root_node = next;
+ }
+
+ if (next != NULL)
+ {
+ next->prev = prev;
+ }
+ if (prev == NULL && parent != NULL)
+ {
+ g_assert (parent->first_child == node);
+ parent->first_child = next;
+ }
+ else if (prev != NULL)
+ {
+ prev->next = next;
+ }
+
+ node->parent = NULL;
+ node->next = NULL;
+ node->prev = NULL;
+ node->root = NULL;
+}
+
+static void
+tree_node_destroy (FMTreeModel *model, TreeNode *node)
+{
+ g_assert (node->first_child == NULL);
+ g_assert (node->ref_count == 0);
+
+ tree_node_unparent (model, node);
+
+ g_object_unref (node->file);
+ g_free (node->display_name);
+ object_unref_if_not_NULL (node->icon);
+ object_unref_if_not_NULL (node->closed_pixbuf);
+ object_unref_if_not_NULL (node->open_pixbuf);
+ object_unref_if_not_NULL (node->emblem_pixbuf);
+
+ g_assert (node->done_loading_id == 0);
+ g_assert (node->files_added_id == 0);
+ g_assert (node->files_changed_id == 0);
+ caja_directory_unref (node->directory);
+
+ g_free (node);
+}
+
+static void
+tree_node_parent (TreeNode *node, TreeNode *parent)
+{
+ TreeNode *first_child;
+
+ g_assert (parent != NULL);
+ g_assert (node->parent == NULL);
+ g_assert (node->prev == NULL);
+ g_assert (node->next == NULL);
+
+ first_child = parent->first_child;
+
+ node->parent = parent;
+ node->root = parent->root;
+ node->next = first_child;
+
+ if (first_child != NULL)
+ {
+ g_assert (first_child->prev == NULL);
+ first_child->prev = node;
+ }
+
+ parent->first_child = node;
+}
+
+static GdkPixbuf *
+get_menu_icon (GIcon *icon)
+{
+ CajaIconInfo *info;
+ GdkPixbuf *pixbuf;
+ int size;
+
+ size = caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU);
+
+ info = caja_icon_info_lookup (icon, size);
+ pixbuf = caja_icon_info_get_pixbuf_nodefault_at_size (info, size);
+ g_object_unref (info);
+
+ return pixbuf;
+}
+
+static GdkPixbuf *
+get_menu_icon_for_file (TreeNode *node,
+ CajaFile *file,
+ CajaFileIconFlags flags)
+{
+ CajaIconInfo *info;
+ GdkPixbuf *pixbuf, *retval;
+ gboolean highlight;
+ int size;
+ FMTreeModel *model;
+
+ size = caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU);
+
+ info = caja_file_get_icon (file, size, flags);
+ retval = caja_icon_info_get_pixbuf_nodefault_at_size (info, size);
+ model = node->root->model;
+
+ highlight = (g_list_find_custom (model->details->highlighted_files,
+ file, (GCompareFunc) caja_file_compare_location) != NULL);
+
+ if (highlight)
+ {
+ pixbuf = eel_gdk_pixbuf_render (retval, 1, 255, 255, 0, 0);
+
+ if (pixbuf != NULL)
+ {
+ g_object_unref (retval);
+ retval = pixbuf;
+ }
+ }
+
+ g_object_unref (info);
+
+ return retval;
+}
+
+static GdkPixbuf *
+tree_node_get_pixbuf (TreeNode *node,
+ CajaFileIconFlags flags)
+{
+ if (node->parent == NULL)
+ {
+ return get_menu_icon (node->icon);
+ }
+ return get_menu_icon_for_file (node, node->file, flags);
+}
+
+static gboolean
+tree_node_update_pixbuf (TreeNode *node,
+ GdkPixbuf **pixbuf_storage,
+ CajaFileIconFlags flags)
+{
+ GdkPixbuf *pixbuf;
+
+ if (*pixbuf_storage == NULL)
+ {
+ return FALSE;
+ }
+ pixbuf = tree_node_get_pixbuf (node, flags);
+ if (pixbuf == *pixbuf_storage)
+ {
+ g_object_unref (pixbuf);
+ return FALSE;
+ }
+ g_object_unref (*pixbuf_storage);
+ *pixbuf_storage = pixbuf;
+ return TRUE;
+}
+
+static gboolean
+tree_node_update_closed_pixbuf (TreeNode *node)
+{
+ return tree_node_update_pixbuf (node, &node->closed_pixbuf, 0);
+}
+
+static gboolean
+tree_node_update_open_pixbuf (TreeNode *node)
+{
+ return tree_node_update_pixbuf (node, &node->open_pixbuf, CAJA_FILE_ICON_FLAGS_FOR_OPEN_FOLDER);
+}
+
+static GdkPixbuf *
+tree_node_get_emblem_pixbuf_internal (TreeNode *node)
+{
+ GdkPixbuf *pixbuf;
+ GList *emblem_pixbufs;
+ char *emblems_to_ignore[3];
+ int i;
+
+ i = 0;
+ emblems_to_ignore[i++] = CAJA_FILE_EMBLEM_NAME_TRASH;
+
+ if (node->parent && node->parent->file)
+ {
+ if (!caja_file_can_write (node->parent->file))
+ {
+ emblems_to_ignore[i++] = CAJA_FILE_EMBLEM_NAME_CANT_WRITE;
+ }
+ }
+
+ emblems_to_ignore[i++] = NULL;
+
+ emblem_pixbufs = caja_file_get_emblem_pixbufs (node->file,
+ caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU),
+ TRUE,
+ emblems_to_ignore);
+
+
+ if (emblem_pixbufs != NULL)
+ {
+ pixbuf = g_object_ref (emblem_pixbufs->data);
+ }
+ else
+ {
+ pixbuf = NULL;
+ }
+
+ eel_gdk_pixbuf_list_free (emblem_pixbufs);
+
+ return pixbuf;
+}
+
+static gboolean
+tree_node_update_emblem_pixbuf (TreeNode *node)
+{
+ GdkPixbuf *pixbuf;
+
+ if (node->emblem_pixbuf == NULL)
+ {
+ return FALSE;
+ }
+ pixbuf = tree_node_get_emblem_pixbuf_internal (node);
+ if (pixbuf == node->emblem_pixbuf)
+ {
+ g_object_unref (pixbuf);
+ return FALSE;
+ }
+ g_object_unref (node->emblem_pixbuf);
+ node->emblem_pixbuf = pixbuf;
+ return TRUE;
+}
+
+static gboolean
+tree_node_update_display_name (TreeNode *node)
+{
+ char *display_name;
+
+ if (node->display_name == NULL)
+ {
+ return FALSE;
+ }
+ /* don't update root node display names */
+ if (node->parent == NULL)
+ {
+ return FALSE;
+ }
+ display_name = caja_file_get_display_name (node->file);
+ if (strcmp (display_name, node->display_name) == 0)
+ {
+ g_free (display_name);
+ return FALSE;
+ }
+ g_free (node->display_name);
+ node->display_name = NULL;
+ return TRUE;
+}
+
+static GdkPixbuf *
+tree_node_get_closed_pixbuf (TreeNode *node)
+{
+ if (node->closed_pixbuf == NULL)
+ {
+ node->closed_pixbuf = tree_node_get_pixbuf (node, 0);
+ }
+ return node->closed_pixbuf;
+}
+
+static GdkPixbuf *
+tree_node_get_open_pixbuf (TreeNode *node)
+{
+ if (node->open_pixbuf == NULL)
+ {
+ node->open_pixbuf = tree_node_get_pixbuf (node, CAJA_FILE_ICON_FLAGS_FOR_OPEN_FOLDER);
+ }
+ return node->open_pixbuf;
+}
+
+static GdkPixbuf *
+tree_node_get_emblem_pixbuf (TreeNode *node)
+{
+ if (node->emblem_pixbuf == NULL)
+ {
+ node->emblem_pixbuf = tree_node_get_emblem_pixbuf_internal (node);
+ }
+ return node->emblem_pixbuf;
+}
+
+static const char *
+tree_node_get_display_name (TreeNode *node)
+{
+ if (node->display_name == NULL)
+ {
+ node->display_name = caja_file_get_display_name (node->file);
+ }
+ return node->display_name;
+}
+
+static gboolean
+tree_node_has_dummy_child (TreeNode *node)
+{
+ return (node->directory != NULL
+ && (!node->done_loading
+ || node->first_child == NULL
+ || node->force_has_dummy)) ||
+ /* Roots always have dummy nodes if directory isn't loaded yet */
+ (node->directory == NULL && node->parent == NULL);
+}
+
+static int
+tree_node_get_child_index (TreeNode *parent, TreeNode *child)
+{
+ int i;
+ TreeNode *node;
+
+ if (child == NULL)
+ {
+ g_assert (tree_node_has_dummy_child (parent));
+ return 0;
+ }
+
+ i = tree_node_has_dummy_child (parent) ? 1 : 0;
+ for (node = parent->first_child; node != NULL; node = node->next, i++)
+ {
+ if (child == node)
+ {
+ return i;
+ }
+ }
+
+ g_assert_not_reached ();
+ return 0;
+}
+
+static gboolean
+make_iter_invalid (GtkTreeIter *iter)
+{
+ iter->stamp = 0;
+ iter->user_data = NULL;
+ iter->user_data2 = NULL;
+ iter->user_data3 = NULL;
+ return FALSE;
+}
+
+static gboolean
+make_iter_for_node (TreeNode *node, GtkTreeIter *iter, int stamp)
+{
+ if (node == NULL)
+ {
+ return make_iter_invalid (iter);
+ }
+ iter->stamp = stamp;
+ iter->user_data = node;
+ iter->user_data2 = NULL;
+ iter->user_data3 = NULL;
+ return TRUE;
+}
+
+static gboolean
+make_iter_for_dummy_row (TreeNode *parent, GtkTreeIter *iter, int stamp)
+{
+ g_assert (tree_node_has_dummy_child (parent));
+ g_assert (parent != NULL);
+ iter->stamp = stamp;
+ iter->user_data = NULL;
+ iter->user_data2 = parent;
+ iter->user_data3 = NULL;
+ return TRUE;
+}
+
+static TreeNode *
+get_node_from_file (FMTreeModelRoot *root, CajaFile *file)
+{
+ return g_hash_table_lookup (root->file_to_node_map, file);
+}
+
+static TreeNode *
+get_parent_node_from_file (FMTreeModelRoot *root, CajaFile *file)
+{
+ CajaFile *parent_file;
+ TreeNode *parent_node;
+
+ parent_file = caja_file_get_parent (file);
+ parent_node = get_node_from_file (root, parent_file);
+ caja_file_unref (parent_file);
+ return parent_node;
+}
+
+static TreeNode *
+create_node_for_file (FMTreeModelRoot *root, CajaFile *file)
+{
+ TreeNode *node;
+
+ g_assert (get_node_from_file (root, file) == NULL);
+ node = tree_node_new (file, root);
+ g_hash_table_insert (root->file_to_node_map, node->file, node);
+ return node;
+}
+
+#ifdef LOG_REF_COUNTS
+
+static char *
+get_node_uri (GtkTreeIter *iter)
+{
+ TreeNode *node, *parent;
+ char *parent_uri, *node_uri;
+
+ node = iter->user_data;
+ if (node != NULL)
+ {
+ return caja_file_get_uri (node->file);
+ }
+
+ parent = iter->user_data2;
+ parent_uri = caja_file_get_uri (parent->file);
+ node_uri = g_strconcat (parent_uri, " -- DUMMY", NULL);
+ g_free (parent_uri);
+ return node_uri;
+}
+
+#endif
+
+static void
+decrement_ref_count (FMTreeModel *model, TreeNode *node, int count)
+{
+ node->all_children_ref_count -= count;
+ if (node->all_children_ref_count == 0)
+ {
+ schedule_monitoring_update (model);
+ }
+}
+
+static void
+abandon_node_ref_count (FMTreeModel *model, TreeNode *node)
+{
+ if (node->parent != NULL)
+ {
+ decrement_ref_count (model, node->parent, node->ref_count);
+#ifdef LOG_REF_COUNTS
+ if (node->ref_count != 0)
+ {
+ char *uri;
+
+ uri = caja_file_get_uri (node->file);
+ g_message ("abandoning %d ref of %s, count is now %d",
+ node->ref_count, uri, node->parent->all_children_ref_count);
+ g_free (uri);
+ }
+#endif
+ }
+ node->ref_count = 0;
+}
+
+static void
+abandon_dummy_row_ref_count (FMTreeModel *model, TreeNode *node)
+{
+ decrement_ref_count (model, node, node->dummy_child_ref_count);
+ if (node->dummy_child_ref_count != 0)
+ {
+#ifdef LOG_REF_COUNTS
+ char *uri;
+
+ uri = caja_file_get_uri (node->file);
+ g_message ("abandoning %d ref of %s -- DUMMY, count is now %d",
+ node->dummy_child_ref_count, uri, node->all_children_ref_count);
+ g_free (uri);
+#endif
+ }
+ node->dummy_child_ref_count = 0;
+}
+
+static void
+report_row_inserted (FMTreeModel *model, GtkTreeIter *iter)
+{
+ GtkTreePath *path;
+
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter);
+ gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, iter);
+ gtk_tree_path_free (path);
+}
+
+static void
+report_row_contents_changed (FMTreeModel *model, GtkTreeIter *iter)
+{
+ GtkTreePath *path;
+
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter);
+ gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, iter);
+ gtk_tree_path_free (path);
+}
+
+static void
+report_row_has_child_toggled (FMTreeModel *model, GtkTreeIter *iter)
+{
+ GtkTreePath *path;
+
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter);
+ gtk_tree_model_row_has_child_toggled (GTK_TREE_MODEL (model), path, iter);
+ gtk_tree_path_free (path);
+}
+
+static GtkTreePath *
+get_node_path (FMTreeModel *model, TreeNode *node)
+{
+ GtkTreeIter iter;
+
+ make_iter_for_node (node, &iter, model->details->stamp);
+ return gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
+}
+
+static void
+report_dummy_row_inserted (FMTreeModel *model, TreeNode *parent)
+{
+ GtkTreeIter iter;
+
+ if (!parent->inserted)
+ {
+ return;
+ }
+ make_iter_for_dummy_row (parent, &iter, model->details->stamp);
+ report_row_inserted (model, &iter);
+}
+
+static void
+report_dummy_row_deleted (FMTreeModel *model, TreeNode *parent)
+{
+ GtkTreeIter iter;
+ GtkTreePath *path;
+
+ if (parent->inserted)
+ {
+ make_iter_for_node (parent, &iter, model->details->stamp);
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
+ gtk_tree_path_append_index (path, 0);
+ gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path);
+ gtk_tree_path_free (path);
+ }
+ abandon_dummy_row_ref_count (model, parent);
+}
+
+static void
+report_node_inserted (FMTreeModel *model, TreeNode *node)
+{
+ GtkTreeIter iter;
+
+ make_iter_for_node (node, &iter, model->details->stamp);
+ report_row_inserted (model, &iter);
+ node->inserted = TRUE;
+
+ if (tree_node_has_dummy_child (node))
+ {
+ report_dummy_row_inserted (model, node);
+ }
+
+ if (node->directory != NULL ||
+ node->parent == NULL)
+ {
+ report_row_has_child_toggled (model, &iter);
+ }
+}
+
+static void
+report_node_contents_changed (FMTreeModel *model, TreeNode *node)
+{
+ GtkTreeIter iter;
+
+ if (!node->inserted)
+ {
+ return;
+ }
+ make_iter_for_node (node, &iter, model->details->stamp);
+ report_row_contents_changed (model, &iter);
+}
+
+static void
+report_node_has_child_toggled (FMTreeModel *model, TreeNode *node)
+{
+ GtkTreeIter iter;
+
+ if (!node->inserted)
+ {
+ return;
+ }
+ make_iter_for_node (node, &iter, model->details->stamp);
+ report_row_has_child_toggled (model, &iter);
+}
+
+static void
+report_dummy_row_contents_changed (FMTreeModel *model, TreeNode *parent)
+{
+ GtkTreeIter iter;
+
+ if (!parent->inserted)
+ {
+ return;
+ }
+ make_iter_for_dummy_row (parent, &iter, model->details->stamp);
+ report_row_contents_changed (model, &iter);
+}
+
+static void
+stop_monitoring_directory (FMTreeModel *model, TreeNode *node)
+{
+ CajaDirectory *directory;
+
+ if (node->done_loading_id == 0)
+ {
+ g_assert (node->files_added_id == 0);
+ g_assert (node->files_changed_id == 0);
+ return;
+ }
+
+ directory = node->directory;
+
+ g_signal_handler_disconnect (node->directory, node->done_loading_id);
+ g_signal_handler_disconnect (node->directory, node->files_added_id);
+ g_signal_handler_disconnect (node->directory, node->files_changed_id);
+
+ node->done_loading_id = 0;
+ node->files_added_id = 0;
+ node->files_changed_id = 0;
+
+ caja_directory_file_monitor_remove (node->directory, model);
+}
+
+static void
+destroy_children_without_reporting (FMTreeModel *model, TreeNode *parent)
+{
+ while (parent->first_child != NULL)
+ {
+ destroy_node_without_reporting (model, parent->first_child);
+ }
+}
+
+static void
+destroy_node_without_reporting (FMTreeModel *model, TreeNode *node)
+{
+ abandon_node_ref_count (model, node);
+ stop_monitoring_directory (model, node);
+ node->inserted = FALSE;
+ destroy_children_without_reporting (model, node);
+ g_hash_table_remove (node->root->file_to_node_map, node->file);
+ tree_node_destroy (model, node);
+}
+
+static void
+destroy_node (FMTreeModel *model, TreeNode *node)
+{
+ TreeNode *parent;
+ gboolean parent_had_dummy_child;
+ GtkTreePath *path;
+
+ parent = node->parent;
+ parent_had_dummy_child = tree_node_has_dummy_child (parent);
+
+ path = get_node_path (model, node);
+
+ /* Report row_deleted before actually deleting */
+ gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path);
+ gtk_tree_path_free (path);
+
+ destroy_node_without_reporting (model, node);
+
+ if (tree_node_has_dummy_child (parent))
+ {
+ if (!parent_had_dummy_child)
+ {
+ report_dummy_row_inserted (model, parent);
+ }
+ }
+ else
+ {
+ g_assert (!parent_had_dummy_child);
+ }
+}
+
+static void
+destroy_children (FMTreeModel *model, TreeNode *parent)
+{
+ while (parent->first_child != NULL)
+ {
+ destroy_node (model, parent->first_child);
+ }
+}
+
+static void
+destroy_children_by_function (FMTreeModel *model, TreeNode *parent, FilePredicate f)
+{
+ TreeNode *child, *next;
+
+ for (child = parent->first_child; child != NULL; child = next)
+ {
+ next = child->next;
+ if (f (child->file))
+ {
+ destroy_node (model, child);
+ }
+ else
+ {
+ destroy_children_by_function (model, child, f);
+ }
+ }
+}
+
+static void
+destroy_by_function (FMTreeModel *model, FilePredicate f)
+{
+ TreeNode *node;
+ for (node = model->details->root_node; node != NULL; node = node->next)
+ {
+ destroy_children_by_function (model, node, f);
+ }
+}
+
+static gboolean
+update_node_without_reporting (FMTreeModel *model, TreeNode *node)
+{
+ gboolean changed;
+
+ changed = FALSE;
+
+ if (node->directory == NULL &&
+ (caja_file_is_directory (node->file) || node->parent == NULL))
+ {
+ node->directory = caja_directory_get_for_file (node->file);
+ }
+ else if (node->directory != NULL &&
+ !(caja_file_is_directory (node->file) || node->parent == NULL))
+ {
+ stop_monitoring_directory (model, node);
+ destroy_children (model, node);
+ caja_directory_unref (node->directory);
+ node->directory = NULL;
+ }
+
+ changed |= tree_node_update_display_name (node);
+ changed |= tree_node_update_closed_pixbuf (node);
+ changed |= tree_node_update_open_pixbuf (node);
+ changed |= tree_node_update_emblem_pixbuf (node);
+
+ return changed;
+}
+
+static void
+insert_node (FMTreeModel *model, TreeNode *parent, TreeNode *node)
+{
+ gboolean parent_empty;
+
+ parent_empty = parent->first_child == NULL;
+ if (parent_empty)
+ {
+ /* Make sure the dummy lives as we insert the new row */
+ parent->force_has_dummy = TRUE;
+ }
+
+ tree_node_parent (node, parent);
+
+ update_node_without_reporting (model, node);
+ report_node_inserted (model, node);
+
+ if (parent_empty)
+ {
+ parent->force_has_dummy = FALSE;
+ if (!tree_node_has_dummy_child (parent))
+ {
+ /* Temporarily set this back so that row_deleted is
+ * sent before actually removing the dummy child */
+ parent->force_has_dummy = TRUE;
+ report_dummy_row_deleted (model, parent);
+ parent->force_has_dummy = FALSE;
+ }
+ }
+}
+
+static void
+reparent_node (FMTreeModel *model, TreeNode *node)
+{
+ GtkTreePath *path;
+ TreeNode *new_parent;
+
+ new_parent = get_parent_node_from_file (node->root, node->file);
+ if (new_parent == NULL || new_parent->directory == NULL)
+ {
+ destroy_node (model, node);
+ return;
+ }
+
+ path = get_node_path (model, node);
+
+ /* Report row_deleted before actually deleting */
+ gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path);
+ gtk_tree_path_free (path);
+
+ abandon_node_ref_count (model, node);
+ tree_node_unparent (model, node);
+
+ insert_node (model, new_parent, node);
+}
+
+static gboolean
+should_show_file (FMTreeModel *model, CajaFile *file)
+{
+ gboolean should;
+ TreeNode *node;
+
+ should = caja_file_should_show (file,
+ model->details->show_hidden_files,
+ model->details->show_backup_files,
+ TRUE);
+
+ if (should
+ && model->details->show_only_directories
+ &&! caja_file_is_directory (file))
+ {
+ should = FALSE;
+ }
+
+ if (should && caja_file_is_gone (file))
+ {
+ should = FALSE;
+ }
+
+ for (node = model->details->root_node; node != NULL; node = node->next)
+ {
+ if (!should && node != NULL && file == node->file)
+ {
+ should = TRUE;
+ }
+ }
+
+ return should;
+}
+
+static void
+update_node (FMTreeModel *model, TreeNode *node)
+{
+ gboolean had_dummy_child, has_dummy_child;
+ gboolean had_directory, has_directory;
+ gboolean changed;
+
+ if (!should_show_file (model, node->file))
+ {
+ destroy_node (model, node);
+ return;
+ }
+
+ if (node->parent != NULL && node->parent->directory != NULL
+ && !caja_directory_contains_file (node->parent->directory, node->file))
+ {
+ reparent_node (model, node);
+ return;
+ }
+
+ had_dummy_child = tree_node_has_dummy_child (node);
+ had_directory = node->directory != NULL;
+
+ changed = update_node_without_reporting (model, node);
+
+ has_dummy_child = tree_node_has_dummy_child (node);
+ has_directory = node->directory != NULL;
+
+ if (had_dummy_child != has_dummy_child)
+ {
+ if (has_dummy_child)
+ {
+ report_dummy_row_inserted (model, node);
+ }
+ else
+ {
+ /* Temporarily set this back so that row_deleted is
+ * sent before actually removing the dummy child */
+ node->force_has_dummy = TRUE;
+ report_dummy_row_deleted (model, node);
+ node->force_has_dummy = FALSE;
+ }
+ }
+ if (had_directory != has_directory)
+ {
+ report_node_has_child_toggled (model, node);
+ }
+
+ if (changed)
+ {
+ report_node_contents_changed (model, node);
+ }
+}
+
+static void
+process_file_change (FMTreeModelRoot *root,
+ CajaFile *file)
+{
+ TreeNode *node, *parent;
+
+ node = get_node_from_file (root, file);
+ if (node != NULL)
+ {
+ update_node (root->model, node);
+ return;
+ }
+
+ if (!should_show_file (root->model, file))
+ {
+ return;
+ }
+
+ parent = get_parent_node_from_file (root, file);
+ if (parent == NULL)
+ {
+ return;
+ }
+
+ insert_node (root->model, parent, create_node_for_file (root, file));
+}
+
+static void
+files_changed_callback (CajaDirectory *directory,
+ GList *changed_files,
+ gpointer callback_data)
+{
+ FMTreeModelRoot *root;
+ GList *node;
+
+ root = (FMTreeModelRoot *) (callback_data);
+
+ for (node = changed_files; node != NULL; node = node->next)
+ {
+ process_file_change (root, CAJA_FILE (node->data));
+ }
+}
+
+static void
+set_done_loading (FMTreeModel *model, TreeNode *node, gboolean done_loading)
+{
+ gboolean had_dummy;
+
+ if (node == NULL || node->done_loading == done_loading)
+ {
+ return;
+ }
+
+ had_dummy = tree_node_has_dummy_child (node);
+
+ node->done_loading = done_loading;
+
+ if (tree_node_has_dummy_child (node))
+ {
+ if (had_dummy)
+ {
+ report_dummy_row_contents_changed (model, node);
+ }
+ else
+ {
+ report_dummy_row_inserted (model, node);
+ }
+ }
+ else
+ {
+ if (had_dummy)
+ {
+ /* Temporarily set this back so that row_deleted is
+ * sent before actually removing the dummy child */
+ node->force_has_dummy = TRUE;
+ report_dummy_row_deleted (model, node);
+ node->force_has_dummy = FALSE;
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+ }
+}
+
+static void
+done_loading_callback (CajaDirectory *directory,
+ FMTreeModelRoot *root)
+{
+ CajaFile *file;
+ TreeNode *node;
+ GtkTreeIter iter;
+
+ file = caja_directory_get_corresponding_file (directory);
+ node = get_node_from_file (root, file);
+ if (node == NULL)
+ {
+ /* This can happen for non-existing files as tree roots,
+ * since the directory <-> file object relation gets
+ * broken due to caja_directory_remove_file()
+ * getting called when i/o fails.
+ */
+ return;
+ }
+ set_done_loading (root->model, node, TRUE);
+ caja_file_unref (file);
+
+ make_iter_for_node (node, &iter, root->model->details->stamp);
+ g_signal_emit (root->model,
+ tree_model_signals[ROW_LOADED], 0,
+ &iter);
+}
+
+static CajaFileAttributes
+get_tree_monitor_attributes (void)
+{
+ CajaFileAttributes attributes;
+
+ attributes =
+ CAJA_FILE_ATTRIBUTES_FOR_ICON |
+ CAJA_FILE_ATTRIBUTE_INFO |
+ CAJA_FILE_ATTRIBUTE_LINK_INFO;
+
+ return attributes;
+}
+
+static void
+start_monitoring_directory (FMTreeModel *model, TreeNode *node)
+{
+ CajaDirectory *directory;
+ CajaFileAttributes attributes;
+
+ if (node->done_loading_id != 0)
+ {
+ return;
+ }
+
+ g_assert (node->files_added_id == 0);
+ g_assert (node->files_changed_id == 0);
+
+ directory = node->directory;
+
+ node->done_loading_id = g_signal_connect
+ (directory, "done_loading",
+ G_CALLBACK (done_loading_callback), node->root);
+ node->files_added_id = g_signal_connect
+ (directory, "files_added",
+ G_CALLBACK (files_changed_callback), node->root);
+ node->files_changed_id = g_signal_connect
+ (directory, "files_changed",
+ G_CALLBACK (files_changed_callback), node->root);
+
+ set_done_loading (model, node, caja_directory_are_all_files_seen (directory));
+
+ attributes = get_tree_monitor_attributes ();
+ caja_directory_file_monitor_add (directory, model,
+ model->details->show_hidden_files,
+ model->details->show_backup_files,
+ attributes, files_changed_callback, node->root);
+}
+
+static int
+fm_tree_model_get_n_columns (GtkTreeModel *model)
+{
+ return FM_TREE_MODEL_NUM_COLUMNS;
+}
+
+static GType
+fm_tree_model_get_column_type (GtkTreeModel *model, int index)
+{
+ switch (index)
+ {
+ case FM_TREE_MODEL_DISPLAY_NAME_COLUMN:
+ return G_TYPE_STRING;
+ case FM_TREE_MODEL_CLOSED_PIXBUF_COLUMN:
+ return GDK_TYPE_PIXBUF;
+ case FM_TREE_MODEL_OPEN_PIXBUF_COLUMN:
+ return GDK_TYPE_PIXBUF;
+ case FM_TREE_MODEL_EMBLEM_PIXBUF_COLUMN:
+ return GDK_TYPE_PIXBUF;
+ case FM_TREE_MODEL_FONT_STYLE_COLUMN:
+ return PANGO_TYPE_STYLE;
+ default:
+ g_assert_not_reached ();
+ }
+
+ return G_TYPE_INVALID;
+}
+
+static gboolean
+iter_is_valid (FMTreeModel *model, const GtkTreeIter *iter)
+{
+ TreeNode *node, *parent;
+
+ if (iter->stamp != model->details->stamp)
+ {
+ return FALSE;
+ }
+
+ node = iter->user_data;
+ parent = iter->user_data2;
+ if (node == NULL)
+ {
+ if (parent != NULL)
+ {
+ if (!CAJA_IS_FILE (parent->file))
+ {
+ return FALSE;
+ }
+ if (!tree_node_has_dummy_child (parent))
+ {
+ return FALSE;
+ }
+ }
+ }
+ else
+ {
+ if (!CAJA_IS_FILE (node->file))
+ {
+ return FALSE;
+ }
+ if (parent != NULL)
+ {
+ return FALSE;
+ }
+ }
+ if (iter->user_data3 != NULL)
+ {
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static gboolean
+fm_tree_model_get_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreePath *path)
+{
+ int *indices;
+ GtkTreeIter parent;
+ int depth, i;
+
+ indices = gtk_tree_path_get_indices (path);
+ depth = gtk_tree_path_get_depth (path);
+
+ if (! gtk_tree_model_iter_nth_child (model, iter, NULL, indices[0]))
+ {
+ return FALSE;
+ }
+
+ for (i = 1; i < depth; i++)
+ {
+ parent = *iter;
+
+ if (! gtk_tree_model_iter_nth_child (model, iter, &parent, indices[i]))
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static GtkTreePath *
+fm_tree_model_get_path (GtkTreeModel *model, GtkTreeIter *iter)
+{
+ FMTreeModel *tree_model;
+ TreeNode *node, *parent, *cnode;
+ GtkTreePath *path;
+ GtkTreeIter parent_iter;
+ int i;
+
+ g_return_val_if_fail (FM_IS_TREE_MODEL (model), NULL);
+ tree_model = FM_TREE_MODEL (model);
+ g_return_val_if_fail (iter_is_valid (tree_model, iter), NULL);
+
+ node = iter->user_data;
+ if (node == NULL)
+ {
+ parent = iter->user_data2;
+ if (parent == NULL)
+ {
+ return gtk_tree_path_new ();
+ }
+ }
+ else
+ {
+ parent = node->parent;
+ if (parent == NULL)
+ {
+ i = 0;
+ for (cnode = tree_model->details->root_node; cnode != node; cnode = cnode->next)
+ {
+ i++;
+ }
+ path = gtk_tree_path_new ();
+ gtk_tree_path_append_index (path, i);
+ return path;
+ }
+ }
+
+ parent_iter.stamp = iter->stamp;
+ parent_iter.user_data = parent;
+ parent_iter.user_data2 = NULL;
+ parent_iter.user_data3 = NULL;
+
+ path = fm_tree_model_get_path (model, &parent_iter);
+
+ gtk_tree_path_append_index (path, tree_node_get_child_index (parent, node));
+
+ return path;
+}
+
+static void
+fm_tree_model_get_value (GtkTreeModel *model, GtkTreeIter *iter, int column, GValue *value)
+{
+ TreeNode *node, *parent;
+ FMTreeModel *fm_model;
+
+ g_return_if_fail (FM_IS_TREE_MODEL (model));
+ g_return_if_fail (iter_is_valid (FM_TREE_MODEL (model), iter));
+
+ fm_model = FM_TREE_MODEL (model);
+ node = iter->user_data;
+
+ switch (column)
+ {
+ case FM_TREE_MODEL_DISPLAY_NAME_COLUMN:
+ g_value_init (value, G_TYPE_STRING);
+ if (node == NULL)
+ {
+ parent = iter->user_data2;
+ g_value_set_static_string (value, parent->done_loading
+ ? _("(Empty)") : _("Loading..."));
+ }
+ else
+ {
+ g_value_set_string (value, tree_node_get_display_name (node));
+ }
+ break;
+ case FM_TREE_MODEL_CLOSED_PIXBUF_COLUMN:
+ g_value_init (value, GDK_TYPE_PIXBUF);
+ g_value_set_object (value, node == NULL ? NULL : tree_node_get_closed_pixbuf (node));
+ break;
+ case FM_TREE_MODEL_OPEN_PIXBUF_COLUMN:
+ g_value_init (value, GDK_TYPE_PIXBUF);
+ g_value_set_object (value, node == NULL ? NULL : tree_node_get_open_pixbuf (node));
+ break;
+ case FM_TREE_MODEL_EMBLEM_PIXBUF_COLUMN:
+ g_value_init (value, GDK_TYPE_PIXBUF);
+ g_value_set_object (value, node == NULL ? NULL : tree_node_get_emblem_pixbuf (node));
+ break;
+ case FM_TREE_MODEL_FONT_STYLE_COLUMN:
+ g_value_init (value, PANGO_TYPE_STYLE);
+ if (node == NULL)
+ {
+ g_value_set_enum (value, PANGO_STYLE_ITALIC);
+ }
+ else
+ {
+ g_value_set_enum (value, PANGO_STYLE_NORMAL);
+ }
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static gboolean
+fm_tree_model_iter_next (GtkTreeModel *model, GtkTreeIter *iter)
+{
+ TreeNode *node, *parent, *next;
+
+ g_return_val_if_fail (FM_IS_TREE_MODEL (model), FALSE);
+ g_return_val_if_fail (iter_is_valid (FM_TREE_MODEL (model), iter), FALSE);
+
+ node = iter->user_data;
+
+ if (node == NULL)
+ {
+ parent = iter->user_data2;
+ next = parent->first_child;
+ }
+ else
+ {
+ next = node->next;
+ }
+
+ return make_iter_for_node (next, iter, iter->stamp);
+}
+
+static gboolean
+fm_tree_model_iter_children (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *parent_iter)
+{
+ TreeNode *parent;
+
+ g_return_val_if_fail (FM_IS_TREE_MODEL (model), FALSE);
+ g_return_val_if_fail (iter_is_valid (FM_TREE_MODEL (model), parent_iter), FALSE);
+
+ parent = parent_iter->user_data;
+ if (parent == NULL)
+ {
+ return make_iter_invalid (iter);
+ }
+
+ if (tree_node_has_dummy_child (parent))
+ {
+ return make_iter_for_dummy_row (parent, iter, parent_iter->stamp);
+ }
+ return make_iter_for_node (parent->first_child, iter, parent_iter->stamp);
+}
+
+static gboolean
+fm_tree_model_iter_parent (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *child_iter)
+{
+ TreeNode *child, *parent;
+
+ g_return_val_if_fail (FM_IS_TREE_MODEL (model), FALSE);
+ g_return_val_if_fail (iter_is_valid (FM_TREE_MODEL (model), child_iter), FALSE);
+
+ child = child_iter->user_data;
+
+ if (child == NULL)
+ {
+ parent = child_iter->user_data2;
+ }
+ else
+ {
+ parent = child->parent;
+ }
+
+ return make_iter_for_node (parent, iter, child_iter->stamp);
+}
+
+static gboolean
+fm_tree_model_iter_has_child (GtkTreeModel *model, GtkTreeIter *iter)
+{
+ gboolean has_child;
+ TreeNode *node;
+
+ g_return_val_if_fail (FM_IS_TREE_MODEL (model), FALSE);
+ g_return_val_if_fail (iter_is_valid (FM_TREE_MODEL (model), iter), FALSE);
+
+ node = iter->user_data;
+
+ has_child = node != NULL && (node->directory != NULL || node->parent == NULL);
+
+#if 0
+ g_warning ("Node '%s' %s",
+ node && node->file ? caja_file_get_uri (node->file) : "no name",
+ has_child ? "has child" : "no child");
+#endif
+
+ return has_child;
+}
+
+static int
+fm_tree_model_iter_n_children (GtkTreeModel *model, GtkTreeIter *iter)
+{
+ FMTreeModel *tree_model;
+ TreeNode *parent, *node;
+ int n;
+
+ g_return_val_if_fail (FM_IS_TREE_MODEL (model), FALSE);
+ g_return_val_if_fail (iter == NULL || iter_is_valid (FM_TREE_MODEL (model), iter), FALSE);
+
+ tree_model = FM_TREE_MODEL (model);
+
+ if (iter == NULL)
+ {
+ return 1;
+ }
+
+ parent = iter->user_data;
+ if (parent == NULL)
+ {
+ return 0;
+ }
+
+ n = tree_node_has_dummy_child (parent) ? 1 : 0;
+ for (node = parent->first_child; node != NULL; node = node->next)
+ {
+ n++;
+ }
+
+ return n;
+}
+
+static gboolean
+fm_tree_model_iter_nth_child (GtkTreeModel *model, GtkTreeIter *iter,
+ GtkTreeIter *parent_iter, int n)
+{
+ FMTreeModel *tree_model;
+ TreeNode *parent, *node;
+ int i;
+
+ g_return_val_if_fail (FM_IS_TREE_MODEL (model), FALSE);
+ g_return_val_if_fail (parent_iter == NULL
+ || iter_is_valid (FM_TREE_MODEL (model), parent_iter), FALSE);
+
+ tree_model = FM_TREE_MODEL (model);
+
+ if (parent_iter == NULL)
+ {
+ node = tree_model->details->root_node;
+ for (i = 0; i < n && node != NULL; i++, node = node->next);
+ return make_iter_for_node (node, iter,
+ tree_model->details->stamp);
+ }
+
+ parent = parent_iter->user_data;
+ if (parent == NULL)
+ {
+ return make_iter_invalid (iter);
+ }
+
+ i = tree_node_has_dummy_child (parent) ? 1 : 0;
+ if (n == 0 && i == 1)
+ {
+ return make_iter_for_dummy_row (parent, iter, parent_iter->stamp);
+ }
+ for (node = parent->first_child; i != n; i++, node = node->next)
+ {
+ if (node == NULL)
+ {
+ return make_iter_invalid (iter);
+ }
+ }
+
+ return make_iter_for_node (node, iter, parent_iter->stamp);
+}
+
+static void
+update_monitoring (FMTreeModel *model, TreeNode *node)
+{
+ TreeNode *child;
+
+ if (node->all_children_ref_count == 0)
+ {
+ stop_monitoring_directory (model, node);
+ destroy_children (model, node);
+ }
+ else
+ {
+ for (child = node->first_child; child != NULL; child = child->next)
+ {
+ update_monitoring (model, child);
+ }
+ start_monitoring_directory (model, node);
+ }
+}
+
+static gboolean
+update_monitoring_idle_callback (gpointer callback_data)
+{
+ FMTreeModel *model;
+ TreeNode *node;
+
+ model = FM_TREE_MODEL (callback_data);
+ model->details->monitoring_update_idle_id = 0;
+ for (node = model->details->root_node; node != NULL; node = node->next)
+ {
+ update_monitoring (model, node);
+ }
+ return FALSE;
+}
+
+static void
+schedule_monitoring_update (FMTreeModel *model)
+{
+ if (model->details->monitoring_update_idle_id == 0)
+ {
+ model->details->monitoring_update_idle_id =
+ g_idle_add (update_monitoring_idle_callback, model);
+ }
+}
+
+static void
+stop_monitoring_directory_and_children (FMTreeModel *model, TreeNode *node)
+{
+ TreeNode *child;
+
+ stop_monitoring_directory (model, node);
+ for (child = node->first_child; child != NULL; child = child->next)
+ {
+ stop_monitoring_directory_and_children (model, child);
+ }
+}
+
+static void
+stop_monitoring (FMTreeModel *model)
+{
+ TreeNode *node;
+
+ for (node = model->details->root_node; node != NULL; node = node->next)
+ {
+ stop_monitoring_directory_and_children (model, node);
+ }
+}
+
+static void
+fm_tree_model_ref_node (GtkTreeModel *model, GtkTreeIter *iter)
+{
+ TreeNode *node, *parent;
+#ifdef LOG_REF_COUNTS
+ char *uri;
+#endif
+
+ g_return_if_fail (FM_IS_TREE_MODEL (model));
+ g_return_if_fail (iter_is_valid (FM_TREE_MODEL (model), iter));
+
+ node = iter->user_data;
+ if (node == NULL)
+ {
+ parent = iter->user_data2;
+ g_assert (parent->dummy_child_ref_count >= 0);
+ ++parent->dummy_child_ref_count;
+ }
+ else
+ {
+ parent = node->parent;
+ g_assert (node->ref_count >= 0);
+ ++node->ref_count;
+ }
+
+ if (parent != NULL)
+ {
+ g_assert (parent->all_children_ref_count >= 0);
+ if (++parent->all_children_ref_count == 1)
+ {
+ if (parent->first_child == NULL)
+ {
+ parent->done_loading = FALSE;
+ }
+ schedule_monitoring_update (FM_TREE_MODEL (model));
+ }
+#ifdef LOG_REF_COUNTS
+ uri = get_node_uri (iter);
+ g_message ("ref of %s, count is now %d",
+ uri, parent->all_children_ref_count);
+ g_free (uri);
+#endif
+ }
+}
+
+static void
+fm_tree_model_unref_node (GtkTreeModel *model, GtkTreeIter *iter)
+{
+ TreeNode *node, *parent;
+#ifdef LOG_REF_COUNTS
+ char *uri;
+#endif
+
+ g_return_if_fail (FM_IS_TREE_MODEL (model));
+ g_return_if_fail (iter_is_valid (FM_TREE_MODEL (model), iter));
+
+ node = iter->user_data;
+ if (node == NULL)
+ {
+ parent = iter->user_data2;
+ g_assert (parent->dummy_child_ref_count > 0);
+ --parent->dummy_child_ref_count;
+ }
+ else
+ {
+ parent = node->parent;
+ g_assert (node->ref_count > 0);
+ --node->ref_count;
+ }
+
+ if (parent != NULL)
+ {
+ g_assert (parent->all_children_ref_count > 0);
+#ifdef LOG_REF_COUNTS
+ uri = get_node_uri (iter);
+ g_message ("unref of %s, count is now %d",
+ uri, parent->all_children_ref_count - 1);
+ g_free (uri);
+#endif
+ if (--parent->all_children_ref_count == 0)
+ {
+ schedule_monitoring_update (FM_TREE_MODEL (model));
+ }
+ }
+}
+
+void
+fm_tree_model_add_root_uri (FMTreeModel *model, const char *root_uri, const char *display_name, GIcon *icon, GMount *mount)
+{
+ CajaFile *file;
+ TreeNode *node, *cnode;
+ FMTreeModelRoot *newroot;
+
+ file = caja_file_get_by_uri (root_uri);
+
+ newroot = tree_model_root_new (model);
+ node = create_node_for_file (newroot, file);
+ node->display_name = g_strdup (display_name);
+ node->icon = g_object_ref (icon);
+ if (mount)
+ {
+ node->mount = g_object_ref (mount);
+ }
+ newroot->root_node = node;
+ node->parent = NULL;
+ if (model->details->root_node == NULL)
+ {
+ model->details->root_node = node;
+ }
+ else
+ {
+ /* append it */
+ for (cnode = model->details->root_node; cnode->next != NULL; cnode = cnode->next);
+ cnode->next = node;
+ node->prev = cnode;
+ }
+
+ caja_file_unref (file);
+
+ update_node_without_reporting (model, node);
+ report_node_inserted (model, node);
+}
+
+GMount *
+fm_tree_model_get_mount_for_root_node_file (FMTreeModel *model, CajaFile *file)
+{
+ TreeNode *node;
+
+ for (node = model->details->root_node; node != NULL; node = node->next)
+ {
+ if (file == node->file)
+ {
+ break;
+ }
+ }
+
+ if (node)
+ {
+ return node->mount;
+ }
+
+ return NULL;
+}
+
+void
+fm_tree_model_remove_root_uri (FMTreeModel *model, const char *uri)
+{
+ TreeNode *node;
+ GtkTreePath *path;
+ FMTreeModelRoot *root;
+ CajaFile *file;
+
+ file = caja_file_get_by_uri (uri);
+ for (node = model->details->root_node; node != NULL; node = node->next)
+ {
+ if (file == node->file)
+ {
+ break;
+ }
+ }
+ caja_file_unref (file);
+
+ if (node)
+ {
+ /* remove the node */
+
+ if (node->mount)
+ {
+ g_object_unref (node->mount);
+ node->mount = NULL;
+ }
+
+ caja_file_monitor_remove (node->file, model);
+ path = get_node_path (model, node);
+
+ /* Report row_deleted before actually deleting */
+ gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path);
+ gtk_tree_path_free (path);
+
+ if (node->prev)
+ {
+ node->prev->next = node->next;
+ }
+ if (node->next)
+ {
+ node->next->prev = node->prev;
+ }
+ if (node == model->details->root_node)
+ {
+ model->details->root_node = node->next;
+ }
+
+ /* destroy the root identifier */
+ root = node->root;
+ destroy_node_without_reporting (model, node);
+ g_hash_table_destroy (root->file_to_node_map);
+ g_free (root);
+ }
+}
+
+FMTreeModel *
+fm_tree_model_new (void)
+{
+ FMTreeModel *model;
+
+ model = g_object_new (FM_TYPE_TREE_MODEL, NULL);
+
+ return model;
+}
+
+void
+fm_tree_model_set_show_hidden_files (FMTreeModel *model,
+ gboolean show_hidden_files)
+{
+ g_return_if_fail (FM_IS_TREE_MODEL (model));
+ g_return_if_fail (show_hidden_files == FALSE || show_hidden_files == TRUE);
+
+ show_hidden_files = show_hidden_files != FALSE;
+ if (model->details->show_hidden_files == show_hidden_files)
+ {
+ return;
+ }
+ model->details->show_hidden_files = show_hidden_files;
+ model->details->show_backup_files = show_hidden_files;
+ stop_monitoring (model);
+ if (!show_hidden_files)
+ {
+ destroy_by_function (model, caja_file_is_hidden_file);
+ }
+ schedule_monitoring_update (model);
+}
+
+static gboolean
+file_is_not_directory (CajaFile *file)
+{
+ return !caja_file_is_directory (file);
+}
+
+void
+fm_tree_model_set_show_only_directories (FMTreeModel *model,
+ gboolean show_only_directories)
+{
+ g_return_if_fail (FM_IS_TREE_MODEL (model));
+ g_return_if_fail (show_only_directories == FALSE || show_only_directories == TRUE);
+
+ show_only_directories = show_only_directories != FALSE;
+ if (model->details->show_only_directories == show_only_directories)
+ {
+ return;
+ }
+ model->details->show_only_directories = show_only_directories;
+ stop_monitoring (model);
+ if (show_only_directories)
+ {
+ destroy_by_function (model, file_is_not_directory);
+ }
+ schedule_monitoring_update (model);
+}
+
+CajaFile *
+fm_tree_model_iter_get_file (FMTreeModel *model, GtkTreeIter *iter)
+{
+ TreeNode *node;
+
+ g_return_val_if_fail (FM_IS_TREE_MODEL (model), NULL);
+ g_return_val_if_fail (iter_is_valid (FM_TREE_MODEL (model), iter), NULL);
+
+ node = iter->user_data;
+ return node == NULL ? NULL : caja_file_ref (node->file);
+}
+
+/* This is used to work around some sort order stability problems
+ with gtktreemodelsort */
+int
+fm_tree_model_iter_compare_roots (FMTreeModel *model,
+ GtkTreeIter *iter_a,
+ GtkTreeIter *iter_b)
+{
+ TreeNode *a, *b, *n;
+
+ g_return_val_if_fail (FM_IS_TREE_MODEL (model), 0);
+ g_return_val_if_fail (iter_is_valid (model, iter_a), 0);
+ g_return_val_if_fail (iter_is_valid (model, iter_b), 0);
+
+ a = iter_a->user_data;
+ b = iter_b->user_data;
+
+ g_assert (a != NULL && a->parent == NULL);
+ g_assert (b != NULL && b->parent == NULL);
+
+ if (a == b)
+ {
+ return 0;
+ }
+
+ for (n = model->details->root_node; n != NULL; n = n->next)
+ {
+ if (n == a)
+ {
+ return -1;
+ }
+ if (n == b)
+ {
+ return 1;
+ }
+ }
+ g_assert_not_reached ();
+}
+
+gboolean
+fm_tree_model_iter_is_root (FMTreeModel *model, GtkTreeIter *iter)
+{
+ TreeNode *node;
+
+ g_return_val_if_fail (FM_IS_TREE_MODEL (model), 0);
+ g_return_val_if_fail (iter_is_valid (model, iter), 0);
+ node = iter->user_data;
+ if (node == NULL)
+ {
+ return FALSE;
+ }
+ else
+ {
+ return (node->parent == NULL);
+ }
+}
+
+gboolean
+fm_tree_model_file_get_iter (FMTreeModel *model,
+ GtkTreeIter *iter,
+ CajaFile *file,
+ GtkTreeIter *current_iter)
+{
+ TreeNode *node, *root_node;
+
+ if (current_iter != NULL && current_iter->user_data != NULL)
+ {
+ node = get_node_from_file (((TreeNode *) current_iter->user_data)->root, file);
+ return make_iter_for_node (node, iter, model->details->stamp);
+ }
+
+ for (root_node = model->details->root_node; root_node != NULL; root_node = root_node->next)
+ {
+ node = get_node_from_file (root_node->root, file);
+ if (node != NULL)
+ {
+ return make_iter_for_node (node, iter, model->details->stamp);
+ }
+ }
+ return FALSE;
+}
+
+static void
+do_update_node (CajaFile *file,
+ FMTreeModel *model)
+{
+ TreeNode *root, *node = NULL;
+
+ for (root = model->details->root_node; root != NULL; root = root->next)
+ {
+ node = get_node_from_file (root->root, file);
+
+ if (node != NULL)
+ {
+ break;
+ }
+ }
+
+ if (node == NULL)
+ {
+ return;
+ }
+
+ update_node (model, node);
+}
+
+void
+fm_tree_model_set_highlight_for_files (FMTreeModel *model,
+ GList *files)
+{
+ GList *old_files;
+
+ if (model->details->highlighted_files != NULL)
+ {
+ old_files = model->details->highlighted_files;
+ model->details->highlighted_files = NULL;
+
+ g_list_foreach (old_files,
+ (GFunc) do_update_node, model);
+
+ caja_file_list_free (old_files);
+ }
+
+ if (files != NULL)
+ {
+ model->details->highlighted_files =
+ caja_file_list_copy (files);
+ g_list_foreach (model->details->highlighted_files,
+ (GFunc) do_update_node, model);
+ }
+}
+
+static void
+fm_tree_model_init (FMTreeModel *model)
+{
+ model->details = g_new0 (FMTreeModelDetails, 1);
+
+ do
+ {
+ model->details->stamp = g_random_int ();
+ }
+ while (model->details->stamp == 0);
+}
+
+static void
+fm_tree_model_finalize (GObject *object)
+{
+ FMTreeModel *model;
+ TreeNode *root_node, *next_root;
+ FMTreeModelRoot *root;
+
+ model = FM_TREE_MODEL (object);
+
+ for (root_node = model->details->root_node; root_node != NULL; root_node = next_root)
+ {
+ next_root = root_node->next;
+ root = root_node->root;
+ destroy_node_without_reporting (model, root_node);
+ g_hash_table_destroy (root->file_to_node_map);
+ g_free (root);
+ }
+
+ if (model->details->monitoring_update_idle_id != 0)
+ {
+ g_source_remove (model->details->monitoring_update_idle_id);
+ }
+
+ if (model->details->highlighted_files != NULL)
+ {
+ caja_file_list_free (model->details->highlighted_files);
+ }
+
+ g_free (model->details);
+
+ G_OBJECT_CLASS (fm_tree_model_parent_class)->finalize (object);
+}
+
+static void
+fm_tree_model_class_init (FMTreeModelClass *class)
+{
+ G_OBJECT_CLASS (class)->finalize = fm_tree_model_finalize;
+
+ tree_model_signals[ROW_LOADED] =
+ g_signal_new ("row_loaded",
+ FM_TYPE_TREE_MODEL,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (FMTreeModelClass, row_loaded),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__BOXED,
+ G_TYPE_NONE, 1,
+ GTK_TYPE_TREE_ITER);
+}
+
+static void
+fm_tree_model_tree_model_init (GtkTreeModelIface *iface)
+{
+ iface->get_flags = fm_tree_model_get_flags;
+ iface->get_n_columns = fm_tree_model_get_n_columns;
+ iface->get_column_type = fm_tree_model_get_column_type;
+ iface->get_iter = fm_tree_model_get_iter;
+ iface->get_path = fm_tree_model_get_path;
+ iface->get_value = fm_tree_model_get_value;
+ iface->iter_next = fm_tree_model_iter_next;
+ iface->iter_children = fm_tree_model_iter_children;
+ iface->iter_has_child = fm_tree_model_iter_has_child;
+ iface->iter_n_children = fm_tree_model_iter_n_children;
+ iface->iter_nth_child = fm_tree_model_iter_nth_child;
+ iface->iter_parent = fm_tree_model_iter_parent;
+ iface->ref_node = fm_tree_model_ref_node;
+ iface->unref_node = fm_tree_model_unref_node;
+}
+
+
diff --git a/src/file-manager/fm-tree-model.h b/src/file-manager/fm-tree-model.h
new file mode 100644
index 00000000..f74ce06c
--- /dev/null
+++ b/src/file-manager/fm-tree-model.h
@@ -0,0 +1,104 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Copyright (C) 2002 Anders Carlsson
+ * Copyright (C) 2002 Bent Spoon Software
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Anders Carlsson <[email protected]>
+ */
+
+/* fm-tree-model.h - Model for the tree view */
+
+#ifndef FM_TREE_MODEL_H
+#define FM_TREE_MODEL_H
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+#include <libcaja-private/caja-file.h>
+
+#define FM_TYPE_TREE_MODEL fm_tree_model_get_type()
+#define FM_TREE_MODEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_TREE_MODEL, FMTreeModel))
+#define FM_TREE_MODEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_TREE_MODEL, FMTreeModelClass))
+#define FM_IS_TREE_MODEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_TREE_MODEL))
+#define FM_IS_TREE_MODEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_TREE_MODEL))
+#define FM_TREE_MODEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_TREE_MODEL, FMTreeModelClass))
+
+enum
+{
+ FM_TREE_MODEL_DISPLAY_NAME_COLUMN,
+ FM_TREE_MODEL_CLOSED_PIXBUF_COLUMN,
+ FM_TREE_MODEL_OPEN_PIXBUF_COLUMN,
+ FM_TREE_MODEL_EMBLEM_PIXBUF_COLUMN,
+ FM_TREE_MODEL_FONT_STYLE_COLUMN,
+ FM_TREE_MODEL_NUM_COLUMNS
+};
+
+typedef struct FMTreeModelDetails FMTreeModelDetails;
+
+typedef struct
+{
+ GObject parent;
+ FMTreeModelDetails *details;
+} FMTreeModel;
+
+typedef struct
+{
+ GObjectClass parent_class;
+
+ void (* row_loaded) (FMTreeModel *tree_model,
+ GtkTreeIter *iter);
+} FMTreeModelClass;
+
+GType fm_tree_model_get_type (void);
+FMTreeModel *fm_tree_model_new (void);
+void fm_tree_model_set_show_hidden_files (FMTreeModel *model,
+ gboolean show_hidden_files);
+void fm_tree_model_set_show_only_directories (FMTreeModel *model,
+ gboolean show_only_directories);
+CajaFile * fm_tree_model_iter_get_file (FMTreeModel *model,
+ GtkTreeIter *iter);
+void fm_tree_model_add_root_uri (FMTreeModel *model,
+ const char *root_uri,
+ const char *display_name,
+ GIcon *icon,
+ GMount *mount);
+void fm_tree_model_remove_root_uri (FMTreeModel *model,
+ const char *root_uri);
+gboolean fm_tree_model_iter_is_root (FMTreeModel *model,
+ GtkTreeIter *iter);
+int fm_tree_model_iter_compare_roots (FMTreeModel *model,
+ GtkTreeIter *iter_a,
+ GtkTreeIter *iter_b);
+gboolean fm_tree_model_file_get_iter (FMTreeModel *model,
+ GtkTreeIter *iter,
+ CajaFile *file,
+ GtkTreeIter *currentIter);
+
+GMount * fm_tree_model_get_mount_for_root_node_file
+(FMTreeModel *model,
+ CajaFile *file);
+void fm_tree_model_set_highlight_for_files (FMTreeModel *model,
+ GList *files);
+
+#endif /* FM_TREE_MODEL_H */
diff --git a/src/file-manager/fm-tree-view.c b/src/file-manager/fm-tree-view.c
new file mode 100644
index 00000000..2a6cbd60
--- /dev/null
+++ b/src/file-manager/fm-tree-view.c
@@ -0,0 +1,1814 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Copyright (C) 2000, 2001 Eazel, Inc
+ * Copyright (C) 2002 Anders Carlsson
+ * Copyright (C) 2002 Darin Adler
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors:
+ * Maciej Stachowiak <[email protected]>
+ * Anders Carlsson <[email protected]>
+ * Darin Adler <[email protected]>
+ */
+
+/* fm-tree-view.c - tree sidebar panel
+ */
+
+#include <config.h>
+#include "fm-tree-view.h"
+
+#include "fm-tree-model.h"
+#include "fm-properties-window.h"
+#include <string.h>
+#include <eel/eel-alert-dialog.h>
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-gtk-extensions.h>
+#include <eel/eel-preferences.h>
+#include <eel/eel-stock-dialogs.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <libcaja-private/caja-clipboard.h>
+#include <libcaja-private/caja-clipboard-monitor.h>
+#include <libcaja-private/caja-desktop-icon-file.h>
+#include <libcaja-private/caja-debug-log.h>
+#include <libcaja-private/caja-file-attributes.h>
+#include <libcaja-private/caja-file-operations.h>
+#include <libcaja-private/caja-file-utilities.h>
+#include <libcaja-private/caja-global-preferences.h>
+#include <libcaja-private/caja-icon-names.h>
+#include <libcaja-private/caja-program-choosing.h>
+#include <libcaja-private/caja-tree-view-drag-dest.h>
+#include <libcaja-private/caja-cell-renderer-pixbuf-emblem.h>
+#include <libcaja-private/caja-sidebar-provider.h>
+#include <libcaja-private/caja-module.h>
+#include <libcaja-private/caja-window-info.h>
+#include <libcaja-private/caja-window-slot-info.h>
+
+typedef struct
+{
+ GObject parent;
+} FMTreeViewProvider;
+
+typedef struct
+{
+ GObjectClass parent;
+} FMTreeViewProviderClass;
+
+
+struct FMTreeViewDetails
+{
+ CajaWindowInfo *window;
+ GtkTreeView *tree_widget;
+ GtkTreeModelSort *sort_model;
+ FMTreeModel *child_model;
+
+ GVolumeMonitor *volume_monitor;
+
+ CajaFile *activation_file;
+ CajaWindowOpenFlags activation_flags;
+
+ CajaTreeViewDragDest *drag_dest;
+
+ char *selection_location;
+ gboolean selecting;
+
+ guint show_selection_idle_id;
+ gulong clipboard_handler_id;
+
+ GtkWidget *popup;
+ GtkWidget *popup_open;
+ GtkWidget *popup_open_in_new_window;
+ GtkWidget *popup_create_folder;
+ GtkWidget *popup_cut;
+ GtkWidget *popup_copy;
+ GtkWidget *popup_paste;
+ GtkWidget *popup_rename;
+ GtkWidget *popup_trash;
+ GtkWidget *popup_delete;
+ GtkWidget *popup_properties;
+ GtkWidget *popup_unmount_separator;
+ GtkWidget *popup_unmount;
+ GtkWidget *popup_eject;
+ CajaFile *popup_file;
+ guint popup_file_idle_handler;
+
+ guint selection_changed_timer;
+};
+
+typedef struct
+{
+ GList *uris;
+ FMTreeView *view;
+} PrependURIParameters;
+
+static GdkAtom copied_files_atom;
+static gboolean show_delete_command_auto_value;
+
+static void fm_tree_view_iface_init (CajaSidebarIface *iface);
+static void sidebar_provider_iface_init (CajaSidebarProviderIface *iface);
+static void fm_tree_view_activate_file (FMTreeView *view,
+ CajaFile *file,
+ CajaWindowOpenFlags flags);
+static GType fm_tree_view_provider_get_type (void);
+
+static void create_popup_menu (FMTreeView *view);
+
+G_DEFINE_TYPE_WITH_CODE (FMTreeView, fm_tree_view, GTK_TYPE_SCROLLED_WINDOW,
+ G_IMPLEMENT_INTERFACE (CAJA_TYPE_SIDEBAR,
+ fm_tree_view_iface_init));
+#define parent_class fm_tree_view_parent_class
+
+G_DEFINE_TYPE_WITH_CODE (FMTreeViewProvider, fm_tree_view_provider, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (CAJA_TYPE_SIDEBAR_PROVIDER,
+ sidebar_provider_iface_init));
+
+static void
+notify_clipboard_info (CajaClipboardMonitor *monitor,
+ CajaClipboardInfo *info,
+ FMTreeView *view)
+{
+ if (info != NULL && info->cut)
+ {
+ fm_tree_model_set_highlight_for_files (view->details->child_model, info->files);
+ }
+ else
+ {
+ fm_tree_model_set_highlight_for_files (view->details->child_model, NULL);
+ }
+}
+
+
+static gboolean
+show_iter_for_file (FMTreeView *view, CajaFile *file, GtkTreeIter *iter)
+{
+ GtkTreeModel *model;
+ CajaFile *parent_file;
+ GtkTreeIter parent_iter;
+ GtkTreePath *path, *sort_path;
+ GtkTreeIter cur_iter;
+
+ if (view->details->child_model == NULL)
+ {
+ return FALSE;
+ }
+ model = GTK_TREE_MODEL (view->details->child_model);
+
+ /* check if file is visible in the same root as the currently selected folder is */
+ gtk_tree_view_get_cursor (view->details->tree_widget, &path, NULL);
+ if (path != NULL)
+ {
+ if (gtk_tree_model_get_iter (model, &cur_iter, path) &&
+ fm_tree_model_file_get_iter (view->details->child_model, iter,
+ file, &cur_iter))
+ {
+ gtk_tree_path_free (path);
+ return TRUE;
+ }
+ gtk_tree_path_free (path);
+ }
+ /* check if file is visible at all */
+ if (fm_tree_model_file_get_iter (view->details->child_model,
+ iter, file, NULL))
+ {
+ return TRUE;
+ }
+
+ parent_file = caja_file_get_parent (file);
+
+ if (parent_file == NULL)
+ {
+ return FALSE;
+ }
+ if (!show_iter_for_file (view, parent_file, &parent_iter))
+ {
+ caja_file_unref (parent_file);
+ return FALSE;
+ }
+ caja_file_unref (parent_file);
+
+ if (parent_iter.user_data == NULL || parent_iter.stamp == 0)
+ {
+ return FALSE;
+ }
+ path = gtk_tree_model_get_path (model, &parent_iter);
+ sort_path = gtk_tree_model_sort_convert_child_path_to_path
+ (view->details->sort_model, path);
+ gtk_tree_path_free (path);
+ gtk_tree_view_expand_row (view->details->tree_widget, sort_path, FALSE);
+ gtk_tree_path_free (sort_path);
+
+ return FALSE;
+}
+
+static void
+refresh_highlight (FMTreeView *view)
+{
+ CajaClipboardMonitor *monitor;
+ CajaClipboardInfo *info;
+
+ monitor = caja_clipboard_monitor_get ();
+ info = caja_clipboard_monitor_get_clipboard_info (monitor);
+
+ notify_clipboard_info (monitor, info, view);
+}
+
+static gboolean
+show_selection_idle_callback (gpointer callback_data)
+{
+ FMTreeView *view;
+ CajaFile *file, *old_file;
+ GtkTreeIter iter;
+ GtkTreePath *path, *sort_path;
+
+ view = FM_TREE_VIEW (callback_data);
+
+ view->details->show_selection_idle_id = 0;
+
+ file = caja_file_get_by_uri (view->details->selection_location);
+ if (file == NULL)
+ {
+ return FALSE;
+ }
+
+ if (!caja_file_is_directory (file))
+ {
+ old_file = file;
+ file = caja_file_get_parent (file);
+ caja_file_unref (old_file);
+ if (file == NULL)
+ {
+ return FALSE;
+ }
+ }
+
+ view->details->selecting = TRUE;
+ if (!show_iter_for_file (view, file, &iter))
+ {
+ caja_file_unref (file);
+ return FALSE;
+ }
+ view->details->selecting = FALSE;
+
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (view->details->child_model), &iter);
+ sort_path = gtk_tree_model_sort_convert_child_path_to_path
+ (view->details->sort_model, path);
+ gtk_tree_path_free (path);
+ gtk_tree_view_set_cursor (view->details->tree_widget, sort_path, NULL, FALSE);
+ if (gtk_widget_get_realized (GTK_WIDGET (view->details->tree_widget)))
+ {
+ gtk_tree_view_scroll_to_cell (view->details->tree_widget, sort_path, NULL, FALSE, 0, 0);
+ }
+ gtk_tree_path_free (sort_path);
+
+ caja_file_unref (file);
+ refresh_highlight (view);
+
+ return FALSE;
+}
+
+static void
+schedule_show_selection (FMTreeView *view)
+{
+ if (view->details->show_selection_idle_id == 0)
+ {
+ view->details->show_selection_idle_id = g_idle_add (show_selection_idle_callback, view);
+ }
+}
+
+static void
+schedule_select_and_show_location (FMTreeView *view, char *location)
+{
+ if (view->details->selection_location != NULL)
+ {
+ g_free (view->details->selection_location);
+ }
+ view->details->selection_location = g_strdup (location);
+ schedule_show_selection (view);
+}
+
+static void
+row_loaded_callback (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ FMTreeView *view)
+{
+ CajaFile *file, *tmp_file, *selection_file;
+
+ if (view->details->selection_location == NULL
+ || !view->details->selecting
+ || iter->user_data == NULL || iter->stamp == 0)
+ {
+ return;
+ }
+
+ file = fm_tree_model_iter_get_file (view->details->child_model, iter);
+ if (file == NULL)
+ {
+ return;
+ }
+ if (!caja_file_is_directory (file))
+ {
+ caja_file_unref(file);
+ return;
+ }
+
+ /* if iter is ancestor of wanted selection_location then update selection */
+ selection_file = caja_file_get_by_uri (view->details->selection_location);
+ while (selection_file != NULL)
+ {
+ if (file == selection_file)
+ {
+ caja_file_unref (file);
+ caja_file_unref (selection_file);
+
+ schedule_show_selection (view);
+ return;
+ }
+ tmp_file = caja_file_get_parent (selection_file);
+ caja_file_unref (selection_file);
+ selection_file = tmp_file;
+ }
+ caja_file_unref (file);
+}
+
+static CajaFile *
+sort_model_iter_to_file (FMTreeView *view, GtkTreeIter *iter)
+{
+ GtkTreeIter child_iter;
+
+ gtk_tree_model_sort_convert_iter_to_child_iter (view->details->sort_model, &child_iter, iter);
+ return fm_tree_model_iter_get_file (view->details->child_model, &child_iter);
+}
+
+static CajaFile *
+sort_model_path_to_file (FMTreeView *view, GtkTreePath *path)
+{
+ GtkTreeIter iter;
+
+ if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (view->details->sort_model), &iter, path))
+ {
+ return NULL;
+ }
+ return sort_model_iter_to_file (view, &iter);
+}
+
+static void
+got_activation_uri_callback (CajaFile *file, gpointer callback_data)
+{
+ char *uri, *file_uri;
+ FMTreeView *view;
+ GdkScreen *screen;
+ GFile *location;
+ CajaWindowSlotInfo *slot;
+ gboolean open_in_same_slot;
+
+ view = FM_TREE_VIEW (callback_data);
+
+ screen = gtk_widget_get_screen (GTK_WIDGET (view->details->tree_widget));
+
+ g_assert (file == view->details->activation_file);
+
+ open_in_same_slot =
+ (view->details->activation_flags &
+ (CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW |
+ CAJA_WINDOW_OPEN_FLAG_NEW_TAB)) == 0;
+
+ slot = caja_window_info_get_active_slot (view->details->window);
+
+ uri = caja_file_get_activation_uri (file);
+ if (caja_file_is_launcher (file))
+ {
+ file_uri = caja_file_get_uri (file);
+ caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
+ "tree view launch_desktop_file window=%p: %s",
+ view->details->window, file_uri);
+ caja_launch_desktop_file (screen, file_uri, NULL, NULL);
+ g_free (file_uri);
+ }
+ else if (uri != NULL
+ && caja_file_is_executable (file)
+ && caja_file_can_execute (file)
+ && !caja_file_is_directory (file))
+ {
+
+ file_uri = g_filename_from_uri (uri, NULL, NULL);
+
+ /* Non-local executables don't get launched. They act like non-executables. */
+ if (file_uri == NULL)
+ {
+ caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
+ "tree view window_info_open_location window=%p: %s",
+ view->details->window, uri);
+ location = g_file_new_for_uri (uri);
+ caja_window_slot_info_open_location
+ (slot,
+ location,
+ CAJA_WINDOW_OPEN_ACCORDING_TO_MODE,
+ view->details->activation_flags,
+ NULL);
+ g_object_unref (location);
+ }
+ else
+ {
+ caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
+ "tree view launch_application_from_command window=%p: %s",
+ view->details->window, file_uri);
+ caja_launch_application_from_command (screen, NULL, file_uri, FALSE, NULL);
+ g_free (file_uri);
+ }
+
+ }
+ else if (uri != NULL)
+ {
+ if (!open_in_same_slot ||
+ view->details->selection_location == NULL ||
+ strcmp (uri, view->details->selection_location) != 0)
+ {
+ if (open_in_same_slot)
+ {
+ if (view->details->selection_location != NULL)
+ {
+ g_free (view->details->selection_location);
+ }
+ view->details->selection_location = g_strdup (uri);
+ }
+
+ caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
+ "tree view window_info_open_location window=%p: %s",
+ view->details->window, uri);
+ location = g_file_new_for_uri (uri);
+ caja_window_slot_info_open_location
+ (slot,
+ location,
+ CAJA_WINDOW_OPEN_ACCORDING_TO_MODE,
+ view->details->activation_flags,
+ NULL);
+ g_object_unref (location);
+ }
+ }
+
+ g_free (uri);
+ caja_file_unref (view->details->activation_file);
+ view->details->activation_file = NULL;
+}
+
+static void
+cancel_activation (FMTreeView *view)
+{
+ if (view->details->activation_file == NULL)
+ {
+ return;
+ }
+
+ caja_file_cancel_call_when_ready
+ (view->details->activation_file,
+ got_activation_uri_callback, view);
+ caja_file_unref (view->details->activation_file);
+ view->details->activation_file = NULL;
+}
+
+static void
+row_activated_callback (GtkTreeView *treeview, GtkTreePath *path,
+ GtkTreeViewColumn *column, FMTreeView *view)
+{
+ if (gtk_tree_view_row_expanded (view->details->tree_widget, path))
+ {
+ gtk_tree_view_collapse_row (view->details->tree_widget, path);
+ }
+ else
+ {
+ gtk_tree_view_expand_row (view->details->tree_widget,
+ path, FALSE);
+ }
+}
+
+static gboolean
+selection_changed_timer_callback(FMTreeView *view)
+{
+ CajaFileAttributes attributes;
+ GtkTreeIter iter;
+ GtkTreeSelection *selection;
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view->details->tree_widget));
+
+ /* no activation if popup menu is open */
+ if (view->details->popup_file != NULL)
+ {
+ return FALSE;
+ }
+
+ cancel_activation (view);
+
+ if (!gtk_tree_selection_get_selected (selection, NULL, &iter))
+ {
+ return FALSE;
+ }
+
+ view->details->activation_file = sort_model_iter_to_file (view, &iter);
+ if (view->details->activation_file == NULL)
+ {
+ return FALSE;
+ }
+ view->details->activation_flags = 0;
+
+ attributes = CAJA_FILE_ATTRIBUTE_INFO | CAJA_FILE_ATTRIBUTE_LINK_INFO;
+ caja_file_call_when_ready (view->details->activation_file, attributes,
+ got_activation_uri_callback, view);
+ return FALSE; /* remove timeout */
+}
+
+static void
+selection_changed_callback (GtkTreeSelection *selection,
+ FMTreeView *view)
+{
+ GdkEvent *event;
+ gboolean is_keyboard;
+
+ if (view->details->selection_changed_timer)
+ {
+ g_source_remove (view->details->selection_changed_timer);
+ view->details->selection_changed_timer = 0;
+ }
+
+ event = gtk_get_current_event ();
+ if (event)
+ {
+ is_keyboard = (event->type == GDK_KEY_PRESS || event->type == GDK_KEY_RELEASE);
+ gdk_event_free (event);
+
+ if (is_keyboard)
+ {
+ /* on keyboard event: delay the change */
+ /* TODO: make dependent on keyboard repeat rate as per Markus Bertheau ? */
+ view->details->selection_changed_timer = g_timeout_add (300, (GSourceFunc) selection_changed_timer_callback, view);
+ }
+ else
+ {
+ /* on mouse event: show the change immediately */
+ selection_changed_timer_callback (view);
+ }
+ }
+}
+
+static int
+compare_rows (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer callback_data)
+{
+ CajaFile *file_a, *file_b;
+ int result;
+
+ /* Dummy rows are always first */
+ if (a->user_data == NULL)
+ {
+ return -1;
+ }
+ else if (b->user_data == NULL)
+ {
+ return 1;
+ }
+
+ /* don't sort root nodes */
+ if (fm_tree_model_iter_is_root (FM_TREE_MODEL (model), a) &&
+ fm_tree_model_iter_is_root (FM_TREE_MODEL (model), b))
+ {
+ return fm_tree_model_iter_compare_roots (FM_TREE_MODEL (model), a, b);
+ }
+
+ file_a = fm_tree_model_iter_get_file (FM_TREE_MODEL (model), a);
+ file_b = fm_tree_model_iter_get_file (FM_TREE_MODEL (model), b);
+
+ if (file_a == file_b)
+ {
+ result = 0;
+ }
+ else if (file_a == NULL)
+ {
+ result = -1;
+ }
+ else if (file_b == NULL)
+ {
+ result = +1;
+ }
+ else
+ {
+ result = caja_file_compare_for_sort (file_a, file_b,
+ CAJA_FILE_SORT_BY_DISPLAY_NAME,
+ FALSE, FALSE);
+ }
+
+ caja_file_unref (file_a);
+ caja_file_unref (file_b);
+
+ return result;
+}
+
+
+static char *
+get_root_uri_callback (CajaTreeViewDragDest *dest,
+ gpointer user_data)
+{
+ FMTreeView *view;
+
+ view = FM_TREE_VIEW (user_data);
+
+ /* Don't allow drops on background */
+ return NULL;
+}
+
+static CajaFile *
+get_file_for_path_callback (CajaTreeViewDragDest *dest,
+ GtkTreePath *path,
+ gpointer user_data)
+{
+ FMTreeView *view;
+
+ view = FM_TREE_VIEW (user_data);
+
+ return sort_model_path_to_file (view, path);
+}
+
+static void
+move_copy_items_callback (CajaTreeViewDragDest *dest,
+ const GList *item_uris,
+ const char *target_uri,
+ GdkDragAction action,
+ int x,
+ int y,
+ gpointer user_data)
+{
+ FMTreeView *view;
+
+ view = FM_TREE_VIEW (user_data);
+
+ caja_clipboard_clear_if_colliding_uris (GTK_WIDGET (view),
+ item_uris,
+ copied_files_atom);
+ caja_file_operations_copy_move
+ (item_uris,
+ NULL,
+ target_uri,
+ action,
+ GTK_WIDGET (view->details->tree_widget),
+ NULL, NULL);
+}
+
+static void
+add_root_for_mount (FMTreeView *view,
+ GMount *mount)
+{
+ char *mount_uri, *name;
+ GFile *root;
+ GIcon *icon;
+
+ if (g_mount_is_shadowed (mount))
+ return;
+
+ icon = g_mount_get_icon (mount);
+ root = g_mount_get_root (mount);
+ mount_uri = g_file_get_uri (root);
+ g_object_unref (root);
+ name = g_mount_get_name (mount);
+
+ fm_tree_model_add_root_uri(view->details->child_model,
+ mount_uri, name, icon, mount);
+
+ g_object_unref (icon);
+ g_free (name);
+ g_free (mount_uri);
+
+}
+
+static void
+mount_added_callback (GVolumeMonitor *volume_monitor,
+ GMount *mount,
+ FMTreeView *view)
+{
+ add_root_for_mount (view, mount);
+}
+
+static void
+mount_removed_callback (GVolumeMonitor *volume_monitor,
+ GMount *mount,
+ FMTreeView *view)
+{
+ GFile *root;
+ char *mount_uri;
+
+ root = g_mount_get_root (mount);
+ mount_uri = g_file_get_uri (root);
+ g_object_unref (root);
+ fm_tree_model_remove_root_uri (view->details->child_model,
+ mount_uri);
+ g_free (mount_uri);
+}
+
+static void
+clipboard_contents_received_callback (GtkClipboard *clipboard,
+ GtkSelectionData *selection_data,
+ gpointer data)
+{
+ FMTreeView *view;
+
+ view = FM_TREE_VIEW (data);
+
+ if (gtk_selection_data_get_data_type (selection_data) == copied_files_atom
+ && gtk_selection_data_get_length (selection_data) > 0 &&
+ view->details->popup != NULL)
+ {
+ gtk_widget_set_sensitive (view->details->popup_paste, TRUE);
+ }
+
+ g_object_unref (view);
+}
+
+static gboolean
+is_parent_writable (CajaFile *file)
+{
+ CajaFile *parent;
+ gboolean result;
+
+ parent = caja_file_get_parent (file);
+
+ /* No parent directory, return FALSE */
+ if (parent == NULL)
+ {
+ return FALSE;
+ }
+
+ result = caja_file_can_write (parent);
+ caja_file_unref (parent);
+
+ return result;
+}
+
+static gboolean
+button_pressed_callback (GtkTreeView *treeview, GdkEventButton *event,
+ FMTreeView *view)
+{
+ GtkTreePath *path, *cursor_path;
+ gboolean parent_file_is_writable;
+ gboolean file_is_home_or_desktop;
+ gboolean file_is_special_link;
+ gboolean can_move_file_to_trash;
+ gboolean can_delete_file;
+
+ if (event->button == 3)
+ {
+ gboolean show_unmount = FALSE;
+ gboolean show_eject = FALSE;
+ GMount *mount = NULL;
+
+ if (view->details->popup_file != NULL)
+ {
+ return FALSE; /* Already up, ignore */
+ }
+
+ if (!gtk_tree_view_get_path_at_pos (treeview, event->x, event->y,
+ &path, NULL, NULL, NULL))
+ {
+ return FALSE;
+ }
+
+ view->details->popup_file = sort_model_path_to_file (view, path);
+ if (view->details->popup_file == NULL)
+ {
+ gtk_tree_path_free (path);
+ return FALSE;
+ }
+ gtk_tree_view_get_cursor (view->details->tree_widget, &cursor_path, NULL);
+ gtk_tree_view_set_cursor (view->details->tree_widget, path, NULL, FALSE);
+ gtk_tree_path_free (path);
+
+ create_popup_menu (view);
+
+ gtk_widget_set_sensitive (view->details->popup_open_in_new_window,
+ caja_file_is_directory (view->details->popup_file));
+ gtk_widget_set_sensitive (view->details->popup_create_folder,
+ caja_file_is_directory (view->details->popup_file) &&
+ caja_file_can_write (view->details->popup_file));
+ gtk_widget_set_sensitive (view->details->popup_paste, FALSE);
+ if (caja_file_is_directory (view->details->popup_file) &&
+ caja_file_can_write (view->details->popup_file))
+ {
+ gtk_clipboard_request_contents (caja_clipboard_get (GTK_WIDGET (view->details->tree_widget)),
+ copied_files_atom,
+ clipboard_contents_received_callback, g_object_ref (view));
+ }
+ can_move_file_to_trash = caja_file_can_trash (view->details->popup_file);
+ gtk_widget_set_sensitive (view->details->popup_trash, can_move_file_to_trash);
+
+ if (show_delete_command_auto_value)
+ {
+ parent_file_is_writable = is_parent_writable (view->details->popup_file);
+ file_is_home_or_desktop = caja_file_is_home (view->details->popup_file)
+ || caja_file_is_desktop_directory (view->details->popup_file);
+ file_is_special_link = CAJA_IS_DESKTOP_ICON_FILE (view->details->popup_file);
+
+ can_delete_file = parent_file_is_writable
+ && !file_is_home_or_desktop
+ && !file_is_special_link;
+
+ gtk_widget_show (view->details->popup_delete);
+ gtk_widget_set_sensitive (view->details->popup_delete, can_delete_file);
+ }
+ else
+ {
+ gtk_widget_hide (view->details->popup_delete);
+ }
+
+ mount = fm_tree_model_get_mount_for_root_node_file (view->details->child_model, view->details->popup_file);
+ if (mount)
+ {
+ show_unmount = g_mount_can_unmount (mount);
+ show_eject = g_mount_can_eject (mount);
+ }
+
+ if (show_unmount)
+ {
+ gtk_widget_show (view->details->popup_unmount);
+ }
+ else
+ {
+ gtk_widget_hide (view->details->popup_unmount);
+ }
+
+ if (show_eject)
+ {
+ gtk_widget_show (view->details->popup_eject);
+ }
+ else
+ {
+ gtk_widget_hide (view->details->popup_eject);
+ }
+
+ if (show_unmount || show_eject)
+ {
+ gtk_widget_show (view->details->popup_unmount_separator);
+ }
+ else
+ {
+ gtk_widget_hide (view->details->popup_unmount_separator);
+ }
+
+ gtk_menu_popup (GTK_MENU (view->details->popup),
+ NULL, NULL, NULL, NULL,
+ event->button, event->time);
+
+ gtk_tree_view_set_cursor (view->details->tree_widget, cursor_path, NULL, FALSE);
+ gtk_tree_path_free (cursor_path);
+
+ return TRUE;
+ }
+ else if (event->button == 2 && event->type == GDK_BUTTON_PRESS)
+ {
+ CajaFile *file;
+
+ if (!gtk_tree_view_get_path_at_pos (treeview, event->x, event->y,
+ &path, NULL, NULL, NULL))
+ {
+ return FALSE;
+ }
+
+ file = sort_model_path_to_file (view, path);
+ if (file)
+ {
+ fm_tree_view_activate_file (view, file,
+ (event->state & GDK_CONTROL_MASK) != 0 ?
+ CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW :
+ CAJA_WINDOW_OPEN_FLAG_NEW_TAB);
+ caja_file_unref (file);
+ }
+
+ gtk_tree_path_free (path);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+fm_tree_view_activate_file (FMTreeView *view,
+ CajaFile *file,
+ CajaWindowOpenFlags flags)
+{
+ CajaFileAttributes attributes;
+
+ cancel_activation (view);
+
+ view->details->activation_file = caja_file_ref (file);
+ view->details->activation_flags = flags;
+
+ attributes = CAJA_FILE_ATTRIBUTE_INFO | CAJA_FILE_ATTRIBUTE_LINK_INFO;
+ caja_file_call_when_ready (view->details->activation_file, attributes,
+ got_activation_uri_callback, view);
+}
+
+static void
+fm_tree_view_open_cb (GtkWidget *menu_item,
+ FMTreeView *view)
+{
+ fm_tree_view_activate_file (view, view->details->popup_file, 0);
+}
+
+static void
+fm_tree_view_open_in_new_tab_cb (GtkWidget *menu_item,
+ FMTreeView *view)
+{
+ fm_tree_view_activate_file (view, view->details->popup_file, CAJA_WINDOW_OPEN_FLAG_NEW_TAB);
+}
+
+static void
+fm_tree_view_open_in_new_window_cb (GtkWidget *menu_item,
+ FMTreeView *view)
+{
+ fm_tree_view_activate_file (view, view->details->popup_file, CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW);
+}
+
+static void
+new_folder_done (GFile *new_folder, gpointer data)
+{
+ GList *list;
+
+ /* show the properties window for the newly created
+ * folder so the user can change its name
+ */
+ list = g_list_prepend (NULL, caja_file_get (new_folder));
+
+ fm_properties_window_present (list, GTK_WIDGET (data));
+
+ caja_file_list_free (list);
+}
+
+static void
+fm_tree_view_create_folder_cb (GtkWidget *menu_item,
+ FMTreeView *view)
+{
+ char *parent_uri;
+
+ parent_uri = caja_file_get_uri (view->details->popup_file);
+ caja_file_operations_new_folder (GTK_WIDGET (view->details->tree_widget),
+ NULL,
+ parent_uri,
+ new_folder_done, view->details->tree_widget);
+
+ g_free (parent_uri);
+}
+
+static void
+copy_or_cut_files (FMTreeView *view,
+ gboolean cut)
+{
+ char *status_string, *name;
+ CajaClipboardInfo info;
+ GtkTargetList *target_list;
+ GtkTargetEntry *targets;
+ int n_targets;
+
+ info.cut = cut;
+ info.files = g_list_prepend (NULL, view->details->popup_file);
+
+ target_list = gtk_target_list_new (NULL, 0);
+ gtk_target_list_add (target_list, copied_files_atom, 0, 0);
+ gtk_target_list_add_uri_targets (target_list, 0);
+ gtk_target_list_add_text_targets (target_list, 0);
+
+ targets = gtk_target_table_new_from_list (target_list, &n_targets);
+ gtk_target_list_unref (target_list);
+
+ gtk_clipboard_set_with_data (caja_clipboard_get (GTK_WIDGET (view->details->tree_widget)),
+ targets, n_targets,
+ caja_get_clipboard_callback, caja_clear_clipboard_callback,
+ NULL);
+ gtk_target_table_free (targets, n_targets);
+
+ caja_clipboard_monitor_set_clipboard_info (caja_clipboard_monitor_get (),
+ &info);
+ g_list_free (info.files);
+
+ name = caja_file_get_display_name (view->details->popup_file);
+ if (cut)
+ {
+ status_string = g_strdup_printf (_("\"%s\" will be moved "
+ "if you select the Paste command"),
+ name);
+ }
+ else
+ {
+ status_string = g_strdup_printf (_("\"%s\" will be copied "
+ "if you select the Paste command"),
+ name);
+ }
+ g_free (name);
+
+ caja_window_info_push_status (view->details->window,
+ status_string);
+ g_free (status_string);
+}
+
+static void
+fm_tree_view_cut_cb (GtkWidget *menu_item,
+ FMTreeView *view)
+{
+ copy_or_cut_files (view, TRUE);
+}
+
+static void
+fm_tree_view_copy_cb (GtkWidget *menu_item,
+ FMTreeView *view)
+{
+ copy_or_cut_files (view, FALSE);
+}
+
+static void
+paste_clipboard_data (FMTreeView *view,
+ GtkSelectionData *selection_data,
+ char *destination_uri)
+{
+ gboolean cut;
+ GList *item_uris;
+
+ cut = FALSE;
+ item_uris = caja_clipboard_get_uri_list_from_selection_data (selection_data, &cut,
+ copied_files_atom);
+
+ if (item_uris == NULL|| destination_uri == NULL)
+ {
+ caja_window_info_push_status (view->details->window,
+ _("There is nothing on the clipboard to paste."));
+ }
+ else
+ {
+ caja_file_operations_copy_move
+ (item_uris, NULL, destination_uri,
+ cut ? GDK_ACTION_MOVE : GDK_ACTION_COPY,
+ GTK_WIDGET (view->details->tree_widget),
+ NULL, NULL);
+
+ /* If items are cut then remove from clipboard */
+ if (cut)
+ {
+ gtk_clipboard_clear (caja_clipboard_get (GTK_WIDGET (view)));
+ }
+
+ eel_g_list_free_deep (item_uris);
+ }
+}
+
+static void
+paste_into_clipboard_received_callback (GtkClipboard *clipboard,
+ GtkSelectionData *selection_data,
+ gpointer data)
+{
+ FMTreeView *view;
+ char *directory_uri;
+
+ view = FM_TREE_VIEW (data);
+
+ directory_uri = caja_file_get_uri (view->details->popup_file);
+
+ paste_clipboard_data (view, selection_data, directory_uri);
+
+ g_free (directory_uri);
+}
+
+static void
+fm_tree_view_paste_cb (GtkWidget *menu_item,
+ FMTreeView *view)
+{
+ gtk_clipboard_request_contents (caja_clipboard_get (GTK_WIDGET (view->details->tree_widget)),
+ copied_files_atom,
+ paste_into_clipboard_received_callback, view);
+}
+
+static GtkWindow *
+fm_tree_view_get_containing_window (FMTreeView *view)
+{
+ GtkWidget *window;
+
+ g_assert (FM_IS_TREE_VIEW (view));
+
+ window = gtk_widget_get_ancestor (GTK_WIDGET (view), GTK_TYPE_WINDOW);
+ if (window == NULL)
+ {
+ return NULL;
+ }
+
+ return GTK_WINDOW (window);
+}
+
+static void
+fm_tree_view_trash_cb (GtkWidget *menu_item,
+ FMTreeView *view)
+{
+ GList *list;
+
+ if (!caja_file_can_trash (view->details->popup_file))
+ {
+ return;
+ }
+
+ list = g_list_prepend (NULL,
+ caja_file_get_location (view->details->popup_file));
+
+ caja_file_operations_trash_or_delete (list,
+ fm_tree_view_get_containing_window (view),
+ NULL, NULL);
+ eel_g_object_list_free (list);
+}
+
+static void
+fm_tree_view_delete_cb (GtkWidget *menu_item,
+ FMTreeView *view)
+{
+ GList *location_list;
+
+ if (!show_delete_command_auto_value)
+ {
+ return;
+ }
+
+ location_list = g_list_prepend (NULL,
+ caja_file_get_location (view->details->popup_file));
+
+ caja_file_operations_delete (location_list, fm_tree_view_get_containing_window (view), NULL, NULL);
+ eel_g_object_list_free (location_list);
+}
+
+static void
+fm_tree_view_properties_cb (GtkWidget *menu_item,
+ FMTreeView *view)
+{
+ GList *list;
+
+ list = g_list_prepend (NULL, caja_file_ref (view->details->popup_file));
+
+ fm_properties_window_present (list, GTK_WIDGET (view->details->tree_widget));
+
+ caja_file_list_free (list);
+}
+
+static void
+fm_tree_view_unmount_cb (GtkWidget *menu_item,
+ FMTreeView *view)
+{
+ CajaFile *file = view->details->popup_file;
+ GMount *mount;
+
+ if (file == NULL)
+ {
+ return;
+ }
+
+ mount = fm_tree_model_get_mount_for_root_node_file (view->details->child_model, file);
+
+ if (mount != NULL)
+ {
+ caja_file_operations_unmount_mount (fm_tree_view_get_containing_window (view),
+ mount, FALSE, TRUE);
+ }
+}
+
+static void
+fm_tree_view_eject_cb (GtkWidget *menu_item,
+ FMTreeView *view)
+{
+ CajaFile *file = view->details->popup_file;
+ GMount *mount;
+
+ if (file == NULL)
+ {
+ return;
+ }
+
+ mount = fm_tree_model_get_mount_for_root_node_file (view->details->child_model, file);
+
+ if (mount != NULL)
+ {
+ caja_file_operations_unmount_mount (fm_tree_view_get_containing_window (view),
+ mount, TRUE, TRUE);
+ }
+}
+
+static gboolean
+free_popup_file_in_idle_cb (gpointer data)
+{
+ FMTreeView *view;
+
+ view = FM_TREE_VIEW (data);
+
+ if (view->details->popup_file != NULL)
+ {
+ caja_file_unref (view->details->popup_file);
+ view->details->popup_file = NULL;
+ }
+ view->details->popup_file_idle_handler = 0;
+ return FALSE;
+}
+
+static void
+popup_menu_deactivated (GtkMenuShell *menu_shell, gpointer data)
+{
+ FMTreeView *view;
+
+ view = FM_TREE_VIEW (data);
+
+ /* The popup menu is deactivated. (I.E. hidden)
+ We want to free popup_file, but can't right away as it might immediately get
+ used if we're deactivation due to activating a menu item. So, we free it in
+ idle */
+
+ if (view->details->popup_file != NULL &&
+ view->details->popup_file_idle_handler == 0)
+ {
+ view->details->popup_file_idle_handler = g_idle_add (free_popup_file_in_idle_cb, view);
+ }
+}
+
+static void
+create_popup_menu (FMTreeView *view)
+{
+ GtkWidget *popup, *menu_item, *menu_image;
+
+ if (view->details->popup != NULL)
+ {
+ /* already created */
+ return;
+ }
+
+ popup = gtk_menu_new ();
+
+ g_signal_connect (popup, "deactivate",
+ G_CALLBACK (popup_menu_deactivated),
+ view);
+
+
+ /* add the "open" menu item */
+ menu_image = gtk_image_new_from_stock (GTK_STOCK_OPEN,
+ GTK_ICON_SIZE_MENU);
+ gtk_widget_show (menu_image);
+ menu_item = gtk_image_menu_item_new_with_mnemonic (_("_Open"));
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item),
+ menu_image);
+ g_signal_connect (menu_item, "activate",
+ G_CALLBACK (fm_tree_view_open_cb),
+ view);
+ gtk_widget_show (menu_item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item);
+ view->details->popup_open = menu_item;
+
+ /* add the "open in new tab" menu item */
+ menu_item = gtk_menu_item_new_with_mnemonic (_("Open in New _Tab"));
+ g_signal_connect (menu_item, "activate",
+ G_CALLBACK (fm_tree_view_open_in_new_tab_cb),
+ view);
+ gtk_widget_show (menu_item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item);
+ view->details->popup_open_in_new_window = menu_item;
+
+ /* add the "open in new window" menu item */
+ menu_item = gtk_menu_item_new_with_mnemonic (_("Open in New _Window"));
+ g_signal_connect (menu_item, "activate",
+ G_CALLBACK (fm_tree_view_open_in_new_window_cb),
+ view);
+ gtk_widget_show (menu_item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item);
+ view->details->popup_open_in_new_window = menu_item;
+
+ eel_gtk_menu_append_separator (GTK_MENU (popup));
+
+ /* add the "create folder" menu item */
+ menu_item = gtk_image_menu_item_new_with_mnemonic (_("Create _Folder"));
+ g_signal_connect (menu_item, "activate",
+ G_CALLBACK (fm_tree_view_create_folder_cb),
+ view);
+ gtk_widget_show (menu_item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item);
+ view->details->popup_create_folder = menu_item;
+
+ eel_gtk_menu_append_separator (GTK_MENU (popup));
+
+ /* add the "cut folder" menu item */
+ menu_item = gtk_image_menu_item_new_from_stock (GTK_STOCK_CUT, NULL);
+ g_signal_connect (menu_item, "activate",
+ G_CALLBACK (fm_tree_view_cut_cb),
+ view);
+ gtk_widget_show (menu_item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item);
+ view->details->popup_cut = menu_item;
+
+ /* add the "copy folder" menu item */
+ menu_item = gtk_image_menu_item_new_from_stock (GTK_STOCK_COPY, NULL);
+ g_signal_connect (menu_item, "activate",
+ G_CALLBACK (fm_tree_view_copy_cb),
+ view);
+ gtk_widget_show (menu_item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item);
+ view->details->popup_copy = menu_item;
+
+ /* add the "paste files into folder" menu item */
+ menu_image = gtk_image_new_from_stock (GTK_STOCK_PASTE,
+ GTK_ICON_SIZE_MENU);
+ gtk_widget_show (menu_image);
+ menu_item = gtk_image_menu_item_new_with_mnemonic (_("_Paste Into Folder"));
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item),
+ menu_image);
+ g_signal_connect (menu_item, "activate",
+ G_CALLBACK (fm_tree_view_paste_cb),
+ view);
+ gtk_widget_show (menu_item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item);
+ view->details->popup_paste = menu_item;
+
+ eel_gtk_menu_append_separator (GTK_MENU (popup));
+
+ /* add the "move to trash" menu item */
+ menu_image = gtk_image_new_from_icon_name (CAJA_ICON_TRASH_FULL,
+ GTK_ICON_SIZE_MENU);
+ gtk_widget_show (menu_image);
+ menu_item = gtk_image_menu_item_new_with_mnemonic (_("Mo_ve to Trash"));
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item),
+ menu_image);
+ g_signal_connect (menu_item, "activate",
+ G_CALLBACK (fm_tree_view_trash_cb),
+ view);
+ gtk_widget_show (menu_item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item);
+ view->details->popup_trash = menu_item;
+
+ /* add the "delete" menu item */
+ menu_image = gtk_image_new_from_icon_name (CAJA_ICON_DELETE,
+ GTK_ICON_SIZE_MENU);
+ gtk_widget_show (menu_image);
+ menu_item = gtk_image_menu_item_new_with_mnemonic (_("_Delete"));
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item),
+ menu_image);
+ g_signal_connect (menu_item, "activate",
+ G_CALLBACK (fm_tree_view_delete_cb),
+ view);
+ gtk_widget_show (menu_item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item);
+ view->details->popup_delete = menu_item;
+
+ eel_gtk_menu_append_separator (GTK_MENU (popup));
+
+ /* add the "Unmount" menu item */
+ menu_item = gtk_image_menu_item_new_with_mnemonic (_("_Unmount"));
+ g_signal_connect (menu_item, "activate",
+ G_CALLBACK (fm_tree_view_unmount_cb),
+ view);
+ gtk_widget_show (menu_item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item);
+ view->details->popup_unmount = menu_item;
+
+ /* add the "Eject" menu item */
+ menu_item = gtk_image_menu_item_new_with_mnemonic (_("_Eject"));
+ g_signal_connect (menu_item, "activate",
+ G_CALLBACK (fm_tree_view_eject_cb),
+ view);
+ gtk_widget_show (menu_item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item);
+ view->details->popup_eject = menu_item;
+
+ /* add the unmount separator menu item */
+ view->details->popup_unmount_separator =
+ GTK_WIDGET (eel_gtk_menu_append_separator (GTK_MENU (popup)));
+
+ /* add the "properties" menu item */
+ menu_item = gtk_image_menu_item_new_from_stock (GTK_STOCK_PROPERTIES, NULL);
+ g_signal_connect (menu_item, "activate",
+ G_CALLBACK (fm_tree_view_properties_cb),
+ view);
+ gtk_widget_show (menu_item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item);
+ view->details->popup_properties = menu_item;
+
+ view->details->popup = popup;
+}
+
+static void
+create_tree (FMTreeView *view)
+{
+ GtkCellRenderer *cell;
+ GtkTreeViewColumn *column;
+ GVolumeMonitor *volume_monitor;
+ char *home_uri;
+ GList *mounts, *l;
+ char *location;
+ GIcon *icon;
+ CajaWindowSlotInfo *slot;
+
+ view->details->child_model = fm_tree_model_new ();
+ view->details->sort_model = GTK_TREE_MODEL_SORT
+ (gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (view->details->child_model)));
+ view->details->tree_widget = GTK_TREE_VIEW
+ (gtk_tree_view_new_with_model (GTK_TREE_MODEL (view->details->sort_model)));
+ g_object_unref (view->details->sort_model);
+
+ gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (view->details->sort_model),
+ compare_rows, view, NULL);
+
+ g_signal_connect_object
+ (view->details->child_model, "row_loaded",
+ G_CALLBACK (row_loaded_callback),
+ view, G_CONNECT_AFTER);
+ home_uri = caja_get_home_directory_uri ();
+ icon = g_themed_icon_new (CAJA_ICON_HOME);
+ fm_tree_model_add_root_uri (view->details->child_model, home_uri, _("Home Folder"), icon, NULL);
+ g_object_unref (icon);
+ g_free (home_uri);
+ icon = g_themed_icon_new (CAJA_ICON_FILESYSTEM);
+ fm_tree_model_add_root_uri (view->details->child_model, "file:///", _("File System"), icon, NULL);
+ g_object_unref (icon);
+#ifdef NOT_YET_USABLE /* Do we really want this? */
+ icon = g_themed_icon_new (CAJA_ICON_NETWORK);
+ fm_tree_model_add_root_uri (view->details->child_model, "network:///", _("Network Neighbourhood"), icon, NULL);
+ g_object_unref (icon);
+#endif
+
+ volume_monitor = g_volume_monitor_get ();
+ view->details->volume_monitor = volume_monitor;
+ mounts = g_volume_monitor_get_mounts (volume_monitor);
+ for (l = mounts; l != NULL; l = l->next)
+ {
+ add_root_for_mount (view, l->data);
+ g_object_unref (l->data);
+ }
+ g_list_free (mounts);
+
+ g_signal_connect_object (volume_monitor, "mount_added",
+ G_CALLBACK (mount_added_callback), view, 0);
+ g_signal_connect_object (volume_monitor, "mount_removed",
+ G_CALLBACK (mount_removed_callback), view, 0);
+
+ g_object_unref (view->details->child_model);
+
+ gtk_tree_view_set_headers_visible (view->details->tree_widget, FALSE);
+
+ view->details->drag_dest =
+ caja_tree_view_drag_dest_new (view->details->tree_widget);
+ g_signal_connect_object (view->details->drag_dest,
+ "get_root_uri",
+ G_CALLBACK (get_root_uri_callback),
+ view, 0);
+ g_signal_connect_object (view->details->drag_dest,
+ "get_file_for_path",
+ G_CALLBACK (get_file_for_path_callback),
+ view, 0);
+ g_signal_connect_object (view->details->drag_dest,
+ "move_copy_items",
+ G_CALLBACK (move_copy_items_callback),
+ view, 0);
+
+ /* Create column */
+ column = gtk_tree_view_column_new ();
+
+ cell = caja_cell_renderer_pixbuf_emblem_new ();
+ gtk_tree_view_column_pack_start (column, cell, FALSE);
+ gtk_tree_view_column_set_attributes (column, cell,
+ "pixbuf", FM_TREE_MODEL_CLOSED_PIXBUF_COLUMN,
+ "pixbuf_expander_closed", FM_TREE_MODEL_CLOSED_PIXBUF_COLUMN,
+ "pixbuf_expander_open", FM_TREE_MODEL_OPEN_PIXBUF_COLUMN,
+ "pixbuf_emblem", FM_TREE_MODEL_EMBLEM_PIXBUF_COLUMN,
+ NULL);
+
+ cell = gtk_cell_renderer_text_new ();
+ gtk_tree_view_column_pack_start (column, cell, TRUE);
+ gtk_tree_view_column_set_attributes (column, cell,
+ "text", FM_TREE_MODEL_DISPLAY_NAME_COLUMN,
+ "style", FM_TREE_MODEL_FONT_STYLE_COLUMN,
+ NULL);
+
+ gtk_tree_view_append_column (view->details->tree_widget, column);
+
+ gtk_widget_show (GTK_WIDGET (view->details->tree_widget));
+
+ gtk_container_add (GTK_CONTAINER (view),
+ GTK_WIDGET (view->details->tree_widget));
+
+ g_signal_connect_object (gtk_tree_view_get_selection (GTK_TREE_VIEW (view->details->tree_widget)), "changed",
+ G_CALLBACK (selection_changed_callback), view, 0);
+
+ g_signal_connect (G_OBJECT (view->details->tree_widget),
+ "row-activated", G_CALLBACK (row_activated_callback),
+ view);
+
+ g_signal_connect (G_OBJECT (view->details->tree_widget),
+ "button_press_event", G_CALLBACK (button_pressed_callback),
+ view);
+
+ slot = caja_window_info_get_active_slot (view->details->window);
+ location = caja_window_slot_info_get_current_location (slot);
+ schedule_select_and_show_location (view, location);
+ g_free (location);
+}
+
+static void
+update_filtering_from_preferences (FMTreeView *view)
+{
+ CajaWindowShowHiddenFilesMode mode;
+
+ if (view->details->child_model == NULL)
+ {
+ return;
+ }
+
+ mode = caja_window_info_get_hidden_files_mode (view->details->window);
+
+ if (mode == CAJA_WINDOW_SHOW_HIDDEN_FILES_DEFAULT)
+ {
+ fm_tree_model_set_show_hidden_files
+ (view->details->child_model,
+ eel_preferences_get_boolean (CAJA_PREFERENCES_SHOW_HIDDEN_FILES));
+ }
+ else
+ {
+ fm_tree_model_set_show_hidden_files
+ (view->details->child_model,
+ mode == CAJA_WINDOW_SHOW_HIDDEN_FILES_ENABLE);
+ }
+ fm_tree_model_set_show_only_directories
+ (view->details->child_model,
+ eel_preferences_get_boolean (CAJA_PREFERENCES_TREE_SHOW_ONLY_DIRECTORIES));
+}
+
+static void
+parent_set_callback (GtkWidget *widget,
+ GtkWidget *previous_parent,
+ gpointer callback_data)
+{
+ FMTreeView *view;
+
+ view = FM_TREE_VIEW (callback_data);
+
+ if (gtk_widget_get_parent (widget) != NULL && view->details->tree_widget == NULL)
+ {
+ create_tree (view);
+ update_filtering_from_preferences (view);
+ }
+}
+
+static void
+filtering_changed_callback (gpointer callback_data)
+{
+ update_filtering_from_preferences (FM_TREE_VIEW (callback_data));
+}
+
+static void
+loading_uri_callback (CajaWindowInfo *window,
+ char *location,
+ gpointer callback_data)
+{
+ FMTreeView *view;
+
+ view = FM_TREE_VIEW (callback_data);
+ schedule_select_and_show_location (view, location);
+}
+
+static void
+fm_tree_view_init (FMTreeView *view)
+{
+ view->details = g_new0 (FMTreeViewDetails, 1);
+
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (view),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_hadjustment (GTK_SCROLLED_WINDOW (view), NULL);
+ gtk_scrolled_window_set_vadjustment (GTK_SCROLLED_WINDOW (view), NULL);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (view), GTK_SHADOW_IN);
+
+ gtk_widget_show (GTK_WIDGET (view));
+
+ g_signal_connect_object (view, "parent_set",
+ G_CALLBACK (parent_set_callback), view, 0);
+
+ view->details->selection_location = NULL;
+
+ view->details->selecting = FALSE;
+
+ eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_SHOW_HIDDEN_FILES,
+ filtering_changed_callback, view, G_OBJECT (view));
+ eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_SHOW_BACKUP_FILES,
+ filtering_changed_callback, view, G_OBJECT (view));
+ eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_TREE_SHOW_ONLY_DIRECTORIES,
+ filtering_changed_callback, view, G_OBJECT (view));
+
+ view->details->popup_file = NULL;
+
+ view->details->clipboard_handler_id =
+ g_signal_connect (caja_clipboard_monitor_get (),
+ "clipboard_info",
+ G_CALLBACK (notify_clipboard_info), view);
+}
+
+static void
+fm_tree_view_dispose (GObject *object)
+{
+ FMTreeView *view;
+
+ view = FM_TREE_VIEW (object);
+
+ if (view->details->selection_changed_timer)
+ {
+ g_source_remove (view->details->selection_changed_timer);
+ view->details->selection_changed_timer = 0;
+ }
+
+ if (view->details->drag_dest)
+ {
+ g_object_unref (view->details->drag_dest);
+ view->details->drag_dest = NULL;
+ }
+
+ if (view->details->show_selection_idle_id)
+ {
+ g_source_remove (view->details->show_selection_idle_id);
+ view->details->show_selection_idle_id = 0;
+ }
+
+ if (view->details->clipboard_handler_id != 0)
+ {
+ g_signal_handler_disconnect (caja_clipboard_monitor_get (),
+ view->details->clipboard_handler_id);
+ view->details->clipboard_handler_id = 0;
+ }
+
+ cancel_activation (view);
+
+ if (view->details->popup != NULL)
+ {
+ gtk_widget_destroy (view->details->popup);
+ view->details->popup = NULL;
+ }
+
+ if (view->details->popup_file_idle_handler != 0)
+ {
+ g_source_remove (view->details->popup_file_idle_handler);
+ view->details->popup_file_idle_handler = 0;
+ }
+
+ if (view->details->popup_file != NULL)
+ {
+ caja_file_unref (view->details->popup_file);
+ view->details->popup_file = NULL;
+ }
+
+ if (view->details->selection_location != NULL)
+ {
+ g_free (view->details->selection_location);
+ view->details->selection_location = NULL;
+ }
+
+ if (view->details->volume_monitor != NULL)
+ {
+ g_object_unref (view->details->volume_monitor);
+ view->details->volume_monitor = NULL;
+ }
+
+ view->details->window = NULL;
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+fm_tree_view_finalize (GObject *object)
+{
+ FMTreeView *view;
+
+ view = FM_TREE_VIEW (object);
+
+ g_free (view->details);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+fm_tree_view_class_init (FMTreeViewClass *class)
+{
+ G_OBJECT_CLASS (class)->dispose = fm_tree_view_dispose;
+ G_OBJECT_CLASS (class)->finalize = fm_tree_view_finalize;
+
+ copied_files_atom = gdk_atom_intern ("x-special/mate-copied-files", FALSE);
+
+ eel_preferences_add_auto_boolean (CAJA_PREFERENCES_ENABLE_DELETE,
+ &show_delete_command_auto_value);
+}
+
+static const char *
+fm_tree_view_get_sidebar_id (CajaSidebar *sidebar)
+{
+ return TREE_SIDEBAR_ID;
+}
+
+static char *
+fm_tree_view_get_tab_label (CajaSidebar *sidebar)
+{
+ return g_strdup (_("Tree"));
+}
+
+static char *
+fm_tree_view_get_tab_tooltip (CajaSidebar *sidebar)
+{
+ return g_strdup (_("Show Tree"));
+}
+
+static GdkPixbuf *
+fm_tree_view_get_tab_icon (CajaSidebar *sidebar)
+{
+ return NULL;
+}
+
+static void
+fm_tree_view_is_visible_changed (CajaSidebar *sidebar,
+ gboolean is_visible)
+{
+ /* Do nothing */
+}
+
+static void
+hidden_files_mode_changed_callback (CajaWindowInfo *window,
+ FMTreeView *view)
+{
+ update_filtering_from_preferences (view);
+}
+
+static void
+fm_tree_view_iface_init (CajaSidebarIface *iface)
+{
+ iface->get_sidebar_id = fm_tree_view_get_sidebar_id;
+ iface->get_tab_label = fm_tree_view_get_tab_label;
+ iface->get_tab_tooltip = fm_tree_view_get_tab_tooltip;
+ iface->get_tab_icon = fm_tree_view_get_tab_icon;
+ iface->is_visible_changed = fm_tree_view_is_visible_changed;
+}
+
+static void
+fm_tree_view_set_parent_window (FMTreeView *sidebar,
+ CajaWindowInfo *window)
+{
+ char *location;
+ CajaWindowSlotInfo *slot;
+
+ sidebar->details->window = window;
+
+ slot = caja_window_info_get_active_slot (window);
+
+ g_signal_connect_object (window, "loading_uri",
+ G_CALLBACK (loading_uri_callback), sidebar, 0);
+ location = caja_window_slot_info_get_current_location (slot);
+ loading_uri_callback (window, location, sidebar);
+ g_free (location);
+
+ g_signal_connect_object (window, "hidden_files_mode_changed",
+ G_CALLBACK (hidden_files_mode_changed_callback), sidebar, 0);
+
+}
+
+static CajaSidebar *
+fm_tree_view_create (CajaSidebarProvider *provider,
+ CajaWindowInfo *window)
+{
+ FMTreeView *sidebar;
+
+ sidebar = g_object_new (fm_tree_view_get_type (), NULL);
+ fm_tree_view_set_parent_window (sidebar, window);
+ g_object_ref_sink (sidebar);
+
+ return CAJA_SIDEBAR (sidebar);
+}
+
+static void
+sidebar_provider_iface_init (CajaSidebarProviderIface *iface)
+{
+ iface->create = fm_tree_view_create;
+}
+
+static void
+fm_tree_view_provider_init (FMTreeViewProvider *sidebar)
+{
+}
+
+static void
+fm_tree_view_provider_class_init (FMTreeViewProviderClass *class)
+{
+}
+
+void
+fm_tree_view_register (void)
+{
+ caja_module_add_type (fm_tree_view_provider_get_type ());
+}
diff --git a/src/file-manager/fm-tree-view.h b/src/file-manager/fm-tree-view.h
new file mode 100644
index 00000000..a1bbe458
--- /dev/null
+++ b/src/file-manager/fm-tree-view.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Copyright (C) 2000, 2001 Eazel, Inc
+ * Copyright (C) 2002 Anders Carlsson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Maciej Stachowiak <[email protected]>
+ * Anders Carlsson <[email protected]>
+ */
+
+/* fm-tree-view.h - tree view. */
+
+
+#ifndef FM_TREE_VIEW_H
+#define FM_TREE_VIEW_H
+
+#include <gtk/gtk.h>
+
+#define FM_TYPE_TREE_VIEW fm_tree_view_get_type()
+#define FM_TREE_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), FM_TYPE_TREE_VIEW, FMTreeView))
+#define FM_TREE_VIEW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), FM_TYPE_TREE_VIEW, FMTreeViewClass))
+#define FM_IS_TREE_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FM_TYPE_TREE_VIEW))
+#define FM_IS_TREE_VIEW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), FM_TYPE_TREE_VIEW))
+#define FM_TREE_VIEW_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), FM_TYPE_TREE_VIEW, FMTreeViewClass))
+
+#define TREE_SIDEBAR_ID "CajaTreeSidebar"
+
+typedef struct FMTreeViewDetails FMTreeViewDetails;
+
+typedef struct
+{
+ GtkScrolledWindow parent;
+
+ FMTreeViewDetails *details;
+} FMTreeView;
+
+typedef struct
+{
+ GtkScrolledWindowClass parent_class;
+} FMTreeViewClass;
+
+GType fm_tree_view_get_type (void);
+void fm_tree_view_register (void);
+
+#endif /* FM_TREE_VIEW_H */
diff --git a/src/mate-network-scheme.desktop.in b/src/mate-network-scheme.desktop.in
new file mode 100644
index 00000000..f25b4fdb
--- /dev/null
+++ b/src/mate-network-scheme.desktop.in
@@ -0,0 +1,11 @@
+[Desktop Entry]
+_Name=Network
+_Comment=Browse bookmarked and local network locations
+TryExec=caja
+Exec=caja --no-desktop network:
+Terminal=false
+StartupNotify=true
+Type=Application
+Icon=network-workgroup
+Categories=Core;
+OnlyShowIn=MATE;